package binder import ( "slices" "strconv" "sync" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/debug" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/diagnostics" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/scanner" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" ) type ContainerFlags int32 const ( // The current node is not a container, and no container manipulation should happen before // recursing into it. ContainerFlagsNone ContainerFlags = 0 // The current node is a container. It should be set as the current container (and block- // container) before recursing into it. The current node does not have locals. Examples: // // Classes, ObjectLiterals, TypeLiterals, Interfaces... ContainerFlagsIsContainer ContainerFlags = 1 << 0 // The current node is a block-scoped-container. It should be set as the current block- // container before recursing into it. Examples: // // Blocks (when not parented by functions), Catch clauses, For/For-in/For-of statements... ContainerFlagsIsBlockScopedContainer ContainerFlags = 1 << 1 // The current node is the container of a control flow path. The current control flow should // be saved and restored, and a new control flow initialized within the container. ContainerFlagsIsControlFlowContainer ContainerFlags = 1 << 2 ContainerFlagsIsFunctionLike ContainerFlags = 1 << 3 ContainerFlagsIsFunctionExpression ContainerFlags = 1 << 4 ContainerFlagsHasLocals ContainerFlags = 1 << 5 ContainerFlagsIsInterface ContainerFlags = 1 << 6 ContainerFlagsIsObjectLiteralOrClassExpressionMethodOrAccessor ContainerFlags = 1 << 7 ContainerFlagsIsThisContainer ContainerFlags = 1 << 8 ) type Binder struct { file *ast.SourceFile bindFunc func(*ast.Node) bool unreachableFlow *ast.FlowNode reportedUnreachableFlow *ast.FlowNode container *ast.Node thisContainer *ast.Node blockScopeContainer *ast.Node lastContainer *ast.Node currentFlow *ast.FlowNode currentBreakTarget *ast.FlowLabel currentContinueTarget *ast.FlowLabel currentReturnTarget *ast.FlowLabel currentTrueTarget *ast.FlowLabel currentFalseTarget *ast.FlowLabel currentExceptionTarget *ast.FlowLabel preSwitchCaseFlow *ast.FlowNode activeLabelList *ActiveLabel emitFlags ast.NodeFlags seenThisKeyword bool hasExplicitReturn bool hasFlowEffects bool inStrictMode bool inAssignmentPattern bool seenParseError bool symbolCount int classifiableNames collections.Set[string] symbolPool core.Pool[ast.Symbol] flowNodePool core.Pool[ast.FlowNode] flowListPool core.Pool[ast.FlowList] singleDeclarationsPool core.Pool[*ast.Node] } func (b *Binder) options() core.SourceFileAffectingCompilerOptions { return b.file.ParseOptions().CompilerOptions } type ActiveLabel struct { next *ActiveLabel breakTarget *ast.FlowLabel continueTarget *ast.FlowLabel name string referenced bool } func (label *ActiveLabel) BreakTarget() *ast.FlowNode { return label.breakTarget } func (label *ActiveLabel) ContinueTarget() *ast.FlowNode { return label.continueTarget } func BindSourceFile(file *ast.SourceFile) { // This is constructed this way to make the compiler "out-line" the function, // avoiding most work in the common case where the file has already been bound. if !file.IsBound() { bindSourceFile(file) } } var binderPool = sync.Pool{ New: func() any { b := &Binder{} b.bindFunc = b.bind // Allocate closure once return b }, } func getBinder() *Binder { return binderPool.Get().(*Binder) } func putBinder(b *Binder) { *b = Binder{bindFunc: b.bindFunc} binderPool.Put(b) } func bindSourceFile(file *ast.SourceFile) { file.BindOnce(func() { b := getBinder() defer putBinder(b) b.file = file b.inStrictMode = b.options().BindInStrictMode && !file.IsDeclarationFile || ast.IsExternalModule(file) b.unreachableFlow = b.newFlowNode(ast.FlowFlagsUnreachable) b.reportedUnreachableFlow = b.newFlowNode(ast.FlowFlagsUnreachable) b.bind(file.AsNode()) file.SymbolCount = b.symbolCount file.ClassifiableNames = b.classifiableNames }) } func (b *Binder) newSymbol(flags ast.SymbolFlags, name string) *ast.Symbol { b.symbolCount++ result := b.symbolPool.New() result.Flags = flags result.Name = name return result } /** * Declares a Symbol for the node and adds it to symbols. Reports errors for conflicting identifier names. * @param symbolTable - The symbol table which node will be added to. * @param parent - node's parent declaration. * @param node - The declaration to be added to the symbol table * @param includes - The SymbolFlags that node has in addition to its declaration type (eg: export, ambient, etc.) * @param excludes - The flags which node cannot be declared alongside in a symbol table. Used to report forbidden declarations. */ func (b *Binder) declareSymbol(symbolTable ast.SymbolTable, parent *ast.Symbol, node *ast.Node, includes ast.SymbolFlags, excludes ast.SymbolFlags) *ast.Symbol { return b.declareSymbolEx(symbolTable, parent, node, includes, excludes, false /*isReplaceableByMethod*/, false /*isComputedName*/) } func (b *Binder) declareSymbolEx(symbolTable ast.SymbolTable, parent *ast.Symbol, node *ast.Node, includes ast.SymbolFlags, excludes ast.SymbolFlags, isReplaceableByMethod bool, isComputedName bool) *ast.Symbol { debug.Assert(isComputedName || !ast.HasDynamicName(node)) isDefaultExport := ast.HasSyntacticModifier(node, ast.ModifierFlagsDefault) || ast.IsExportSpecifier(node) && ast.ModuleExportNameIsDefault(node.AsExportSpecifier().Name()) // The exported symbol for an export default function/class node is always named "default" var name string switch { case isComputedName: name = ast.InternalSymbolNameComputed case isDefaultExport && parent != nil: name = ast.InternalSymbolNameDefault default: name = b.getDeclarationName(node) } var symbol *ast.Symbol if name == ast.InternalSymbolNameMissing { symbol = b.newSymbol(ast.SymbolFlagsNone, ast.InternalSymbolNameMissing) } else { // Check and see if the symbol table already has a symbol with this name. If not, // create a new symbol with this name and add it to the table. Note that we don't // give the new symbol any flags *yet*. This ensures that it will not conflict // with the 'excludes' flags we pass in. // // If we do get an existing symbol, see if it conflicts with the new symbol we're // creating. For example, a 'var' symbol and a 'class' symbol will conflict within // the same symbol table. If we have a conflict, report the issue on each // declaration we have for this symbol, and then create a new symbol for this // declaration. // // Note that when properties declared in Javascript constructors // (marked by isReplaceableByMethod) conflict with another symbol, the property loses. // Always. This allows the common Javascript pattern of overwriting a prototype method // with an bound instance method of the same type: `this.method = this.method.bind(this)` // // If we created a new symbol, either because we didn't have a symbol with this name // in the symbol table, or we conflicted with an existing symbol, then just add this // node as the sole declaration of the new symbol. // // Otherwise, we'll be merging into a compatible existing symbol (for example when // you have multiple 'vars' with the same name in the same container). In this case // just add this node into the declarations list of the symbol. symbol = symbolTable[name] if includes&ast.SymbolFlagsClassifiable != 0 { b.classifiableNames.Add(name) } if symbol == nil { symbol = b.newSymbol(ast.SymbolFlagsNone, name) symbolTable[name] = symbol if isReplaceableByMethod { symbol.Flags |= ast.SymbolFlagsReplaceableByMethod } } else if isReplaceableByMethod && symbol.Flags&ast.SymbolFlagsReplaceableByMethod == 0 { // A symbol already exists, so don't add this as a declaration. return symbol } else if symbol.Flags&excludes != 0 { if symbol.Flags&ast.SymbolFlagsReplaceableByMethod != 0 { // Javascript constructor-declared symbols can be discarded in favor of // prototype symbols like methods. symbol = b.newSymbol(ast.SymbolFlagsNone, name) symbolTable[name] = symbol } else if !(includes&ast.SymbolFlagsVariable != 0 && symbol.Flags&ast.SymbolFlagsAssignment != 0 || includes&ast.SymbolFlagsAssignment != 0 && symbol.Flags&ast.SymbolFlagsVariable != 0) { // Assignment declarations are allowed to merge with variables, no matter what other flags they have. // Report errors every position with duplicate declaration // Report errors on previous encountered declarations var message *diagnostics.Message if symbol.Flags&ast.SymbolFlagsBlockScopedVariable != 0 { message = diagnostics.Cannot_redeclare_block_scoped_variable_0 } else { message = diagnostics.Duplicate_identifier_0 } messageNeedsName := true if symbol.Flags&ast.SymbolFlagsEnum != 0 || includes&ast.SymbolFlagsEnum != 0 { message = diagnostics.Enum_declarations_can_only_merge_with_namespace_or_other_enum_declarations messageNeedsName = false } multipleDefaultExports := false if len(symbol.Declarations) != 0 { // If the current node is a default export of some sort, then check if // there are any other default exports that we need to error on. // We'll know whether we have other default exports depending on if `symbol` already has a declaration list set. if isDefaultExport { message = diagnostics.A_module_cannot_have_multiple_default_exports messageNeedsName = false multipleDefaultExports = true } else { // This is to properly report an error in the case "export default { }" is after export default of class declaration or function declaration. // Error on multiple export default in the following case: // 1. multiple export default of class declaration or function declaration by checking NodeFlags.Default // 2. multiple export default of export assignment. This one doesn't have NodeFlags.Default on (as export default doesn't considered as modifiers) if len(symbol.Declarations) != 0 && ast.IsExportAssignment(node) && !node.AsExportAssignment().IsExportEquals { message = diagnostics.A_module_cannot_have_multiple_default_exports messageNeedsName = false multipleDefaultExports = true } } } var declarationName *ast.Node = ast.GetNameOfDeclaration(node) if declarationName == nil { declarationName = node } var diag *ast.Diagnostic if messageNeedsName { diag = b.createDiagnosticForNode(declarationName, message, b.getDisplayName(node)) } else { diag = b.createDiagnosticForNode(declarationName, message) } if ast.IsTypeAliasDeclaration(node) && ast.NodeIsMissing(node.AsTypeAliasDeclaration().Type) && ast.HasSyntacticModifier(node, ast.ModifierFlagsExport) && symbol.Flags&(ast.SymbolFlagsAlias|ast.SymbolFlagsType|ast.SymbolFlagsNamespace) != 0 { // export type T; - may have meant export type { T }? diag.AddRelatedInfo(b.createDiagnosticForNode(node, diagnostics.Did_you_mean_0, "export type { "+node.AsTypeAliasDeclaration().Name().AsIdentifier().Text+" }")) } for index, declaration := range symbol.Declarations { var decl *ast.Node = ast.GetNameOfDeclaration(declaration) if decl == nil { decl = declaration } var d *ast.Diagnostic if messageNeedsName { d = b.createDiagnosticForNode(decl, message, b.getDisplayName(declaration)) } else { d = b.createDiagnosticForNode(decl, message) } if multipleDefaultExports { d.AddRelatedInfo(b.createDiagnosticForNode(declarationName, core.IfElse(index == 0, diagnostics.Another_export_default_is_here, diagnostics.X_and_here))) } b.addDiagnostic(d) if multipleDefaultExports { diag.AddRelatedInfo(b.createDiagnosticForNode(decl, diagnostics.The_first_export_default_is_here)) } } b.addDiagnostic(diag) // When get or set accessor conflicts with a non-accessor or an accessor of a different kind, we mark // the symbol as a full accessor such that all subsequent declarations are considered conflicting. This // for example ensures that a get accessor followed by a non-accessor followed by a set accessor with the // same name are all marked as duplicates. if symbol.Flags&ast.SymbolFlagsAccessor != 0 && symbol.Flags&ast.SymbolFlagsAccessor != includes&ast.SymbolFlagsAccessor { symbol.Flags |= ast.SymbolFlagsAccessor } symbol = b.newSymbol(ast.SymbolFlagsNone, name) } } } b.addDeclarationToSymbol(symbol, node, includes) if symbol.Parent == nil { symbol.Parent = parent } else if symbol.Parent != parent { panic("Existing symbol parent should match new one") } return symbol } // Should not be called on a declaration with a computed property name, // unless it is a well known Symbol. func (b *Binder) getDeclarationName(node *ast.Node) string { if ast.IsExportAssignment(node) { return core.IfElse(node.AsExportAssignment().IsExportEquals, ast.InternalSymbolNameExportEquals, ast.InternalSymbolNameDefault) } else if ast.IsJSExportAssignment(node) { return ast.InternalSymbolNameExportEquals } name := ast.GetNameOfDeclaration(node) if name != nil { if ast.IsAmbientModule(node) { moduleName := name.Text() if ast.IsGlobalScopeAugmentation(node) { return ast.InternalSymbolNameGlobal } return "\"" + moduleName + "\"" } if ast.IsPrivateIdentifier(name) { // containingClass exists because private names only allowed inside classes containingClass := ast.GetContainingClass(node) if containingClass == nil { // we can get here in cases where there is already a parse error. return ast.InternalSymbolNameMissing } return GetSymbolNameForPrivateIdentifier(containingClass.Symbol(), name.Text()) } if ast.IsPropertyNameLiteral(name) || ast.IsJsxNamespacedName(name) { return name.Text() } if ast.IsComputedPropertyName(name) { nameExpression := name.AsComputedPropertyName().Expression // treat computed property names where expression is string/numeric literal as just string/numeric literal if ast.IsStringOrNumericLiteralLike(nameExpression) { return nameExpression.Text() } if ast.IsSignedNumericLiteral(nameExpression) { unaryExpression := nameExpression.AsPrefixUnaryExpression() return scanner.TokenToString(unaryExpression.Operator) + unaryExpression.Operand.Text() } panic("Only computed properties with literal names have declaration names") } return ast.InternalSymbolNameMissing } switch node.Kind { case ast.KindConstructor: return ast.InternalSymbolNameConstructor case ast.KindFunctionType, ast.KindCallSignature: return ast.InternalSymbolNameCall case ast.KindConstructorType, ast.KindConstructSignature: return ast.InternalSymbolNameNew case ast.KindIndexSignature: return ast.InternalSymbolNameIndex case ast.KindExportDeclaration: return ast.InternalSymbolNameExportStar case ast.KindSourceFile: return ast.InternalSymbolNameExportEquals } return ast.InternalSymbolNameMissing } func (b *Binder) getDisplayName(node *ast.Node) string { nameNode := node.Name() if nameNode != nil { return scanner.DeclarationNameToString(nameNode) } name := b.getDeclarationName(node) if name != ast.InternalSymbolNameMissing { return name } return "(Missing)" } func GetSymbolNameForPrivateIdentifier(containingClassSymbol *ast.Symbol, description string) string { return ast.InternalSymbolNamePrefix + "#" + strconv.Itoa(int(ast.GetSymbolId(containingClassSymbol))) + "@" + description } func (b *Binder) declareModuleMember(node *ast.Node, symbolFlags ast.SymbolFlags, symbolExcludes ast.SymbolFlags) *ast.Symbol { container := b.container if node.Kind == ast.KindCommonJSExport { container = b.file.AsNode() } hasExportModifier := ast.GetCombinedModifierFlags(node)&ast.ModifierFlagsExport != 0 if symbolFlags&ast.SymbolFlagsAlias != 0 { if node.Kind == ast.KindExportSpecifier || (node.Kind == ast.KindImportEqualsDeclaration && hasExportModifier) { return b.declareSymbol(ast.GetExports(container.Symbol()), container.Symbol(), node, symbolFlags, symbolExcludes) } return b.declareSymbol(ast.GetLocals(container), nil /*parent*/, node, symbolFlags, symbolExcludes) } // Exported module members are given 2 symbols: A local symbol that is classified with an ExportValue flag, // and an associated export symbol with all the correct flags set on it. There are 2 main reasons: // // 1. We treat locals and exports of the same name as mutually exclusive within a container. // That means the binder will issue a Duplicate Identifier error if you mix locals and exports // with the same name in the same container. // TODO: Make this a more specific error and decouple it from the exclusion logic. // 2. When we checkIdentifier in the checker, we set its resolved symbol to the local symbol, // but return the export symbol (by calling getExportSymbolOfValueSymbolIfExported). That way // when the emitter comes back to it, it knows not to qualify the name if it was found in a containing scope. // // NOTE: Nested ambient modules always should go to to 'locals' table to prevent their automatic merge // during global merging in the checker. Why? The only case when ambient module is permitted inside another module is module augmentation // and this case is specially handled. Module augmentations should only be merged with original module definition // and should never be merged directly with other augmentation, and the latter case would be possible if automatic merge is allowed. if !ast.IsAmbientModule(node) && (hasExportModifier || container.Flags&ast.NodeFlagsExportContext != 0) { if !ast.IsLocalsContainer(container) || (ast.HasSyntacticModifier(node, ast.ModifierFlagsDefault) && b.getDeclarationName(node) == ast.InternalSymbolNameMissing) || ast.IsCommonJSExport(node) { return b.declareSymbol(ast.GetExports(container.Symbol()), container.Symbol(), node, symbolFlags, symbolExcludes) // No local symbol for an unnamed default! } exportKind := ast.SymbolFlagsNone if symbolFlags&ast.SymbolFlagsValue != 0 { exportKind = ast.SymbolFlagsExportValue } local := b.declareSymbol(ast.GetLocals(container), nil /*parent*/, node, exportKind, symbolExcludes) local.ExportSymbol = b.declareSymbol(ast.GetExports(container.Symbol()), container.Symbol(), node, symbolFlags, symbolExcludes) node.ExportableData().LocalSymbol = local return local } return b.declareSymbol(ast.GetLocals(container), nil /*parent*/, node, symbolFlags, symbolExcludes) } func (b *Binder) declareClassMember(node *ast.Node, symbolFlags ast.SymbolFlags, symbolExcludes ast.SymbolFlags) *ast.Symbol { if ast.IsStatic(node) { return b.declareSymbol(ast.GetExports(b.container.Symbol()), b.container.Symbol(), node, symbolFlags, symbolExcludes) } return b.declareSymbol(ast.GetMembers(b.container.Symbol()), b.container.Symbol(), node, symbolFlags, symbolExcludes) } func (b *Binder) declareSourceFileMember(node *ast.Node, symbolFlags ast.SymbolFlags, symbolExcludes ast.SymbolFlags) *ast.Symbol { if ast.IsExternalOrCommonJSModule(b.file) { return b.declareModuleMember(node, symbolFlags, symbolExcludes) } return b.declareSymbol(ast.GetLocals(b.file.AsNode()), nil /*parent*/, node, symbolFlags, symbolExcludes) } func (b *Binder) declareSymbolAndAddToSymbolTable(node *ast.Node, symbolFlags ast.SymbolFlags, symbolExcludes ast.SymbolFlags) *ast.Symbol { switch b.container.Kind { case ast.KindModuleDeclaration: return b.declareModuleMember(node, symbolFlags, symbolExcludes) case ast.KindSourceFile: return b.declareSourceFileMember(node, symbolFlags, symbolExcludes) case ast.KindClassExpression, ast.KindClassDeclaration: return b.declareClassMember(node, symbolFlags, symbolExcludes) case ast.KindEnumDeclaration: return b.declareSymbol(ast.GetExports(b.container.Symbol()), b.container.Symbol(), node, symbolFlags, symbolExcludes) case ast.KindTypeLiteral, ast.KindObjectLiteralExpression, ast.KindInterfaceDeclaration, ast.KindJsxAttributes: return b.declareSymbol(ast.GetMembers(b.container.Symbol()), b.container.Symbol(), node, symbolFlags, symbolExcludes) case ast.KindFunctionType, ast.KindConstructorType, ast.KindCallSignature, ast.KindConstructSignature, ast.KindIndexSignature, ast.KindMethodDeclaration, ast.KindMethodSignature, ast.KindConstructor, ast.KindGetAccessor, ast.KindSetAccessor, ast.KindFunctionDeclaration, ast.KindFunctionExpression, ast.KindArrowFunction, ast.KindClassStaticBlockDeclaration, ast.KindTypeAliasDeclaration, ast.KindJSTypeAliasDeclaration, ast.KindMappedType: return b.declareSymbol(ast.GetLocals(b.container), nil /*parent*/, node, symbolFlags, symbolExcludes) } panic("Unhandled case in declareSymbolAndAddToSymbolTable") } func (b *Binder) newFlowNode(flags ast.FlowFlags) *ast.FlowNode { result := b.flowNodePool.New() result.Flags = flags return result } func (b *Binder) newFlowNodeEx(flags ast.FlowFlags, node *ast.Node, antecedent *ast.FlowNode) *ast.FlowNode { result := b.newFlowNode(flags) result.Node = node result.Antecedent = antecedent return result } func (b *Binder) createLoopLabel() *ast.FlowLabel { return b.newFlowNode(ast.FlowFlagsLoopLabel) } func (b *Binder) createBranchLabel() *ast.FlowLabel { return b.newFlowNode(ast.FlowFlagsBranchLabel) } func (b *Binder) createReduceLabel(target *ast.FlowLabel, antecedents *ast.FlowList, antecedent *ast.FlowNode) *ast.FlowNode { return b.newFlowNodeEx(ast.FlowFlagsReduceLabel, ast.NewFlowReduceLabelData(target, antecedents), antecedent) } func (b *Binder) createFlowCondition(flags ast.FlowFlags, antecedent *ast.FlowNode, expression *ast.Node) *ast.FlowNode { if antecedent.Flags&ast.FlowFlagsUnreachable != 0 { return antecedent } if expression == nil { if flags&ast.FlowFlagsTrueCondition != 0 { return antecedent } return b.unreachableFlow } if (expression.Kind == ast.KindTrueKeyword && flags&ast.FlowFlagsFalseCondition != 0 || expression.Kind == ast.KindFalseKeyword && flags&ast.FlowFlagsTrueCondition != 0) && !ast.IsExpressionOfOptionalChainRoot(expression) && !ast.IsNullishCoalesce(expression.Parent) { return b.unreachableFlow } if !isNarrowingExpression(expression) { return antecedent } setFlowNodeReferenced(antecedent) return b.newFlowNodeEx(flags, expression, antecedent) } func (b *Binder) createFlowMutation(flags ast.FlowFlags, antecedent *ast.FlowNode, node *ast.Node) *ast.FlowNode { setFlowNodeReferenced(antecedent) b.hasFlowEffects = true result := b.newFlowNodeEx(flags, node, antecedent) if b.currentExceptionTarget != nil { b.addAntecedent(b.currentExceptionTarget, result) } return result } func (b *Binder) createFlowSwitchClause(antecedent *ast.FlowNode, switchStatement *ast.Node, clauseStart int, clauseEnd int) *ast.FlowNode { setFlowNodeReferenced(antecedent) return b.newFlowNodeEx(ast.FlowFlagsSwitchClause, ast.NewFlowSwitchClauseData(switchStatement, clauseStart, clauseEnd), antecedent) } func (b *Binder) createFlowCall(antecedent *ast.FlowNode, node *ast.Node) *ast.FlowNode { setFlowNodeReferenced(antecedent) b.hasFlowEffects = true return b.newFlowNodeEx(ast.FlowFlagsCall, node, antecedent) } func (b *Binder) newFlowList(head *ast.FlowNode, tail *ast.FlowList) *ast.FlowList { result := b.flowListPool.New() result.Flow = head result.Next = tail return result } func (b *Binder) combineFlowLists(head *ast.FlowList, tail *ast.FlowList) *ast.FlowList { if head == nil { return tail } return b.newFlowList(head.Flow, b.combineFlowLists(head.Next, tail)) } func (b *Binder) newSingleDeclaration(declaration *ast.Node) []*ast.Node { return b.singleDeclarationsPool.NewSlice1(declaration) } func setFlowNodeReferenced(flow *ast.FlowNode) { // On first reference we set the Referenced flag, thereafter we set the Shared flag if flow.Flags&ast.FlowFlagsReferenced == 0 { flow.Flags |= ast.FlowFlagsReferenced } else { flow.Flags |= ast.FlowFlagsShared } } func (b *Binder) addAntecedent(label *ast.FlowLabel, antecedent *ast.FlowNode) { if antecedent.Flags&ast.FlowFlagsUnreachable != 0 { return } // If antecedent isn't already on the Antecedents list, add it to the end of the list var last *ast.FlowList for list := label.Antecedents; list != nil; list = list.Next { if list.Flow == antecedent { return } last = list } if last == nil { label.Antecedents = b.newFlowList(antecedent, nil) } else { last.Next = b.newFlowList(antecedent, nil) } setFlowNodeReferenced(antecedent) } func (b *Binder) finishFlowLabel(label *ast.FlowLabel) *ast.FlowNode { if label.Antecedents == nil { return b.unreachableFlow } if label.Antecedents.Next == nil { return label.Antecedents.Flow } return label } func (b *Binder) bind(node *ast.Node) bool { if node == nil { return false } saveInStrictMode := b.inStrictMode // Even though in the AST the jsdoc @typedef node belongs to the current node, // its symbol might be in the same scope with the current node's symbol. Consider: // // /** @typedef {string | number} MyType */ // function foo(); // // Here the current node is "foo", which is a container, but the scope of "MyType" should // not be inside "foo". Therefore we always bind @typedef before bind the parent node, // and skip binding this tag later when binding all the other jsdoc tags. // First we bind declaration nodes to a symbol if possible. We'll both create a symbol // and then potentially add the symbol to an appropriate symbol table. Possible // destination symbol tables are: // // 1) The 'exports' table of the current container's symbol. // 2) The 'members' table of the current container's symbol. // 3) The 'locals' table of the current container. // // However, not all symbols will end up in any of these tables. 'Anonymous' symbols // (like TypeLiterals for example) will not be put in any table. switch node.Kind { case ast.KindIdentifier: node.AsIdentifier().FlowNode = b.currentFlow b.checkContextualIdentifier(node) case ast.KindThisKeyword, ast.KindSuperKeyword: node.AsKeywordExpression().FlowNode = b.currentFlow case ast.KindQualifiedName: if b.currentFlow != nil && ast.IsPartOfTypeQuery(node) { node.AsQualifiedName().FlowNode = b.currentFlow } case ast.KindMetaProperty: node.AsMetaProperty().FlowNode = b.currentFlow case ast.KindPrivateIdentifier: b.checkPrivateIdentifier(node) case ast.KindPropertyAccessExpression, ast.KindElementAccessExpression: if b.currentFlow != nil && isNarrowableReference(node) { setFlowNode(node, b.currentFlow) } case ast.KindBinaryExpression: switch ast.GetAssignmentDeclarationKind(node.AsBinaryExpression()) { case ast.JSDeclarationKindProperty: b.bindExpandoPropertyAssignment(node) case ast.JSDeclarationKindThisProperty: b.bindThisPropertyAssignment(node) } b.checkStrictModeBinaryExpression(node) case ast.KindCatchClause: b.checkStrictModeCatchClause(node) case ast.KindDeleteExpression: b.checkStrictModeDeleteExpression(node) case ast.KindPostfixUnaryExpression: b.checkStrictModePostfixUnaryExpression(node) case ast.KindPrefixUnaryExpression: b.checkStrictModePrefixUnaryExpression(node) case ast.KindWithStatement: b.checkStrictModeWithStatement(node) case ast.KindLabeledStatement: b.checkStrictModeLabeledStatement(node) case ast.KindThisType: b.seenThisKeyword = true case ast.KindTypeParameter: b.bindTypeParameter(node) case ast.KindParameter: b.bindParameter(node) case ast.KindVariableDeclaration: b.bindVariableDeclarationOrBindingElement(node) case ast.KindBindingElement: node.AsBindingElement().FlowNode = b.currentFlow b.bindVariableDeclarationOrBindingElement(node) case ast.KindCommonJSExport: b.declareModuleMember(node, ast.SymbolFlagsFunctionScopedVariable, ast.SymbolFlagsFunctionScopedVariableExcludes) case ast.KindPropertyDeclaration, ast.KindPropertySignature: b.bindPropertyWorker(node) case ast.KindPropertyAssignment, ast.KindShorthandPropertyAssignment: b.bindPropertyOrMethodOrAccessor(node, ast.SymbolFlagsProperty, ast.SymbolFlagsPropertyExcludes) case ast.KindEnumMember: b.bindPropertyOrMethodOrAccessor(node, ast.SymbolFlagsEnumMember, ast.SymbolFlagsEnumMemberExcludes) case ast.KindCallSignature, ast.KindConstructSignature, ast.KindIndexSignature: b.declareSymbolAndAddToSymbolTable(node, ast.SymbolFlagsSignature, ast.SymbolFlagsNone) case ast.KindMethodDeclaration, ast.KindMethodSignature: b.bindPropertyOrMethodOrAccessor(node, ast.SymbolFlagsMethod|getOptionalSymbolFlagForNode(node), core.IfElse(ast.IsObjectLiteralMethod(node), ast.SymbolFlagsPropertyExcludes, ast.SymbolFlagsMethodExcludes)) case ast.KindFunctionDeclaration: b.bindFunctionDeclaration(node) case ast.KindConstructor: b.declareSymbolAndAddToSymbolTable(node, ast.SymbolFlagsConstructor, ast.SymbolFlagsNone) case ast.KindGetAccessor: b.bindPropertyOrMethodOrAccessor(node, ast.SymbolFlagsGetAccessor, ast.SymbolFlagsGetAccessorExcludes) case ast.KindSetAccessor: b.bindPropertyOrMethodOrAccessor(node, ast.SymbolFlagsSetAccessor, ast.SymbolFlagsSetAccessorExcludes) case ast.KindFunctionType, ast.KindConstructorType: b.bindFunctionOrConstructorType(node) case ast.KindTypeLiteral, ast.KindMappedType: b.bindAnonymousDeclaration(node, ast.SymbolFlagsTypeLiteral, ast.InternalSymbolNameType) case ast.KindObjectLiteralExpression: b.bindAnonymousDeclaration(node, ast.SymbolFlagsObjectLiteral, ast.InternalSymbolNameObject) case ast.KindFunctionExpression, ast.KindArrowFunction: b.bindFunctionExpression(node) case ast.KindClassExpression, ast.KindClassDeclaration: b.inStrictMode = true b.bindClassLikeDeclaration(node) case ast.KindInterfaceDeclaration: b.bindBlockScopedDeclaration(node, ast.SymbolFlagsInterface, ast.SymbolFlagsInterfaceExcludes) case ast.KindCallExpression: b.bindCallExpression(node) case ast.KindTypeAliasDeclaration, ast.KindJSTypeAliasDeclaration: b.bindBlockScopedDeclaration(node, ast.SymbolFlagsTypeAlias, ast.SymbolFlagsTypeAliasExcludes) case ast.KindEnumDeclaration: b.bindEnumDeclaration(node) case ast.KindModuleDeclaration: b.bindModuleDeclaration(node) case ast.KindImportEqualsDeclaration, ast.KindNamespaceImport, ast.KindImportSpecifier, ast.KindExportSpecifier: b.declareSymbolAndAddToSymbolTable(node, ast.SymbolFlagsAlias, ast.SymbolFlagsAliasExcludes) case ast.KindNamespaceExportDeclaration: b.bindNamespaceExportDeclaration(node) case ast.KindImportClause: b.bindImportClause(node) case ast.KindExportDeclaration: b.bindExportDeclaration(node) case ast.KindExportAssignment, ast.KindJSExportAssignment: b.bindExportAssignment(node) case ast.KindSourceFile: b.updateStrictModeStatementList(node.AsSourceFile().Statements) b.bindSourceFileIfExternalModule() case ast.KindBlock: if ast.IsFunctionLikeOrClassStaticBlockDeclaration(node.Parent) { b.updateStrictModeStatementList(node.AsBlock().Statements) } case ast.KindModuleBlock: b.updateStrictModeStatementList(node.AsModuleBlock().Statements) case ast.KindJsxAttributes: b.bindJsxAttributes(node) case ast.KindJsxAttribute: b.bindJsxAttribute(node, ast.SymbolFlagsProperty, ast.SymbolFlagsPropertyExcludes) } // Then we recurse into the children of the node to bind them as well. For certain // symbols we do specialized work when we recurse. For example, we'll keep track of // the current 'container' node when it changes. This helps us know which symbol table // a local should go into for example. Since terminal nodes are known not to have // children, as an optimization we don't process those. thisNodeOrAnySubnodesHasError := node.Flags&ast.NodeFlagsThisNodeHasError != 0 if node.Kind > ast.KindLastToken { saveSeenParseError := b.seenParseError b.seenParseError = false containerFlags := GetContainerFlags(node) if containerFlags == ContainerFlagsNone { b.bindChildren(node) } else { b.bindContainer(node, containerFlags) } if b.seenParseError { thisNodeOrAnySubnodesHasError = true } b.seenParseError = saveSeenParseError } if thisNodeOrAnySubnodesHasError { node.Flags |= ast.NodeFlagsThisNodeOrAnySubNodesHasError b.seenParseError = true } b.inStrictMode = saveInStrictMode return false } func (b *Binder) bindPropertyWorker(node *ast.Node) { isAutoAccessor := ast.IsAutoAccessorPropertyDeclaration(node) includes := core.IfElse(isAutoAccessor, ast.SymbolFlagsAccessor, ast.SymbolFlagsProperty) excludes := core.IfElse(isAutoAccessor, ast.SymbolFlagsAccessorExcludes, ast.SymbolFlagsPropertyExcludes) b.bindPropertyOrMethodOrAccessor(node, includes|getOptionalSymbolFlagForNode(node), excludes) } func (b *Binder) bindSourceFileIfExternalModule() { b.setExportContextFlag(b.file.AsNode()) if ast.IsExternalOrCommonJSModule(b.file) { b.bindSourceFileAsExternalModule() } else if ast.IsJsonSourceFile(b.file) { b.bindSourceFileAsExternalModule() // Create symbol equivalent for the module.exports = {} originalSymbol := b.file.Symbol b.declareSymbol(ast.GetSymbolTable(&b.file.Symbol.Exports), b.file.Symbol, b.file.AsNode(), ast.SymbolFlagsProperty, ast.SymbolFlagsAll) b.file.Symbol = originalSymbol } } func (b *Binder) bindSourceFileAsExternalModule() { b.bindAnonymousDeclaration(b.file.AsNode(), ast.SymbolFlagsValueModule, "\""+tspath.RemoveFileExtension(b.file.FileName())+"\"") } func (b *Binder) bindModuleDeclaration(node *ast.Node) { b.setExportContextFlag(node) if ast.IsAmbientModule(node) { if ast.HasSyntacticModifier(node, ast.ModifierFlagsExport) { b.errorOnFirstToken(node, diagnostics.X_export_modifier_cannot_be_applied_to_ambient_modules_and_module_augmentations_since_they_are_always_visible) } if ast.IsModuleAugmentationExternal(node) { b.declareModuleSymbol(node) } else { name := node.AsModuleDeclaration().Name() symbol := b.declareSymbolAndAddToSymbolTable(node, ast.SymbolFlagsValueModule, ast.SymbolFlagsValueModuleExcludes) if ast.IsStringLiteral(name) { pattern := core.TryParsePattern(name.AsStringLiteral().Text) if !pattern.IsValid() { // An invalid pattern - must have multiple wildcards. b.errorOnFirstToken(name, diagnostics.Pattern_0_can_have_at_most_one_Asterisk_character, name.AsStringLiteral().Text) } else if pattern.StarIndex >= 0 { b.file.PatternAmbientModules = append(b.file.PatternAmbientModules, &ast.PatternAmbientModule{Pattern: pattern, Symbol: symbol}) } } } } else { state := b.declareModuleSymbol(node) if state != ast.ModuleInstanceStateNonInstantiated { symbol := node.AsModuleDeclaration().Symbol if symbol.Flags&(ast.SymbolFlagsFunction|ast.SymbolFlagsClass|ast.SymbolFlagsRegularEnum) != 0 || state != ast.ModuleInstanceStateConstEnumOnly { // if module was already merged with some function, class or non-const enum, treat it as non-const-enum-only symbol.Flags &^= ast.SymbolFlagsConstEnumOnlyModule } } } } func (b *Binder) declareModuleSymbol(node *ast.Node) ast.ModuleInstanceState { state := ast.GetModuleInstanceState(node) instantiated := state != ast.ModuleInstanceStateNonInstantiated b.declareSymbolAndAddToSymbolTable(node, core.IfElse(instantiated, ast.SymbolFlagsValueModule, ast.SymbolFlagsNamespaceModule), core.IfElse(instantiated, ast.SymbolFlagsValueModuleExcludes, ast.SymbolFlagsNamespaceModuleExcludes)) return state } func (b *Binder) bindNamespaceExportDeclaration(node *ast.Node) { if node.Modifiers() != nil { b.errorOnNode(node, diagnostics.Modifiers_cannot_appear_here) } switch { case !ast.IsSourceFile(node.Parent): b.errorOnNode(node, diagnostics.Global_module_exports_may_only_appear_at_top_level) case !ast.IsExternalModule(node.Parent.AsSourceFile()): b.errorOnNode(node, diagnostics.Global_module_exports_may_only_appear_in_module_files) case !node.Parent.AsSourceFile().IsDeclarationFile: b.errorOnNode(node, diagnostics.Global_module_exports_may_only_appear_in_declaration_files) default: b.declareSymbol(ast.GetSymbolTable(&b.file.Symbol.GlobalExports), b.file.Symbol, node, ast.SymbolFlagsAlias, ast.SymbolFlagsAliasExcludes) } } func (b *Binder) bindImportClause(node *ast.Node) { if node.AsImportClause().Name() != nil { b.declareSymbolAndAddToSymbolTable(node, ast.SymbolFlagsAlias, ast.SymbolFlagsAliasExcludes) } } func (b *Binder) bindExportDeclaration(node *ast.Node) { decl := node.AsExportDeclaration() if b.container.Symbol() == nil { // Export * in some sort of block construct b.bindAnonymousDeclaration(node, ast.SymbolFlagsExportStar, b.getDeclarationName(node)) } else if decl.ExportClause == nil { // All export * declarations are collected in an __export symbol b.declareSymbol(ast.GetExports(b.container.Symbol()), b.container.Symbol(), node, ast.SymbolFlagsExportStar, ast.SymbolFlagsNone) } else if ast.IsNamespaceExport(decl.ExportClause) { b.declareSymbol(ast.GetExports(b.container.Symbol()), b.container.Symbol(), decl.ExportClause, ast.SymbolFlagsAlias, ast.SymbolFlagsAliasExcludes) } } func (b *Binder) bindExportAssignment(node *ast.Node) { container := b.container if ast.IsJSExportAssignment(node) { container = b.file.AsNode() } if container.Symbol() == nil && ast.IsExportAssignment(node) { // Incorrect export assignment in some sort of block construct b.bindAnonymousDeclaration(node, ast.SymbolFlagsValue, b.getDeclarationName(node)) } else { flags := ast.SymbolFlagsProperty if ast.ExportAssignmentIsAlias(node) { flags = ast.SymbolFlagsAlias } // If there is an `export default x;` alias declaration, can't `export default` anything else. // (In contrast, you can still have `export default function f() {}` and `export default interface I {}`.) symbol := b.declareSymbol(ast.GetExports(container.Symbol()), container.Symbol(), node, flags, ast.SymbolFlagsAll) if ast.IsJSExportAssignment(node) || node.AsExportAssignment().IsExportEquals { // Will be an error later, since the module already has other exports. Just make sure this has a valueDeclaration set. SetValueDeclaration(symbol, node) } } } func (b *Binder) bindJsxAttributes(node *ast.Node) { b.bindAnonymousDeclaration(node, ast.SymbolFlagsObjectLiteral, ast.InternalSymbolNameJSXAttributes) } func (b *Binder) bindJsxAttribute(node *ast.Node, symbolFlags ast.SymbolFlags, symbolExcludes ast.SymbolFlags) { b.declareSymbolAndAddToSymbolTable(node, symbolFlags, symbolExcludes) } func (b *Binder) setExportContextFlag(node *ast.Node) { // A declaration source file or ambient module declaration that contains no export declarations (but possibly regular // declarations with export modifiers) is an export context in which declarations are implicitly exported. if node.Flags&ast.NodeFlagsAmbient != 0 && !b.hasExportDeclarations(node) { node.Flags |= ast.NodeFlagsExportContext } else { node.Flags &= ^ast.NodeFlagsExportContext } } func (b *Binder) hasExportDeclarations(node *ast.Node) bool { var statements []*ast.Node switch node.Kind { case ast.KindSourceFile: statements = node.AsSourceFile().Statements.Nodes case ast.KindModuleDeclaration: body := node.AsModuleDeclaration().Body if body != nil && ast.IsModuleBlock(body) { statements = body.AsModuleBlock().Statements.Nodes } } return core.Some(statements, func(s *ast.Node) bool { return ast.IsExportDeclaration(s) || ast.IsExportAssignment(s) || ast.IsJSExportAssignment(s) }) } func (b *Binder) bindFunctionExpression(node *ast.Node) { setFlowNode(node, b.currentFlow) bindingName := ast.InternalSymbolNameFunction if ast.IsFunctionExpression(node) && node.AsFunctionExpression().Name() != nil { b.checkStrictModeFunctionName(node) bindingName = node.AsFunctionExpression().Name().AsIdentifier().Text } b.bindAnonymousDeclaration(node, ast.SymbolFlagsFunction, bindingName) } func (b *Binder) bindCallExpression(node *ast.Node) { // !!! for ModuleDetectionKind.Force, external module indicator is forced to `true` in Strada for source files, in which case // we should set the commonjs module indicator but not call b.bindSourceFileAsExternalModule // !!! && file.externalModuleIndicator !== true (used for ModuleDetectionKind.Force) if ast.IsInJSFile(node) && b.file.ExternalModuleIndicator == nil && b.file.CommonJSModuleIndicator == nil && ast.IsRequireCall(node, false /*requireStringLiteralLikeArgument*/) { b.file.CommonJSModuleIndicator = node b.bindSourceFileAsExternalModule() } } func (b *Binder) bindClassLikeDeclaration(node *ast.Node) { name := node.Name() switch node.Kind { case ast.KindClassDeclaration: b.bindBlockScopedDeclaration(node, ast.SymbolFlagsClass, ast.SymbolFlagsClassExcludes) case ast.KindClassExpression: nameText := ast.InternalSymbolNameClass if name != nil { nameText = name.AsIdentifier().Text b.classifiableNames.Add(nameText) } b.bindAnonymousDeclaration(node, ast.SymbolFlagsClass, nameText) } symbol := node.Symbol() // TypeScript 1.0 spec (April 2014): 8.4 // Every class automatically contains a static property member named 'prototype', the // type of which is an instantiation of the class type with type Any supplied as a type // argument for each type parameter. It is an error to explicitly declare a static // property member with the name 'prototype'. // // Note: we check for this here because this class may be merging into a module. The // module might have an exported variable called 'prototype'. We can't allow that as // that would clash with the built-in 'prototype' for the class. prototypeSymbol := b.newSymbol(ast.SymbolFlagsProperty|ast.SymbolFlagsPrototype, "prototype") symbolExport := ast.GetExports(symbol)[prototypeSymbol.Name] if symbolExport != nil { b.errorOnNode(symbolExport.Declarations[0], diagnostics.Duplicate_identifier_0, ast.SymbolName(prototypeSymbol)) } ast.GetExports(symbol)[prototypeSymbol.Name] = prototypeSymbol prototypeSymbol.Parent = symbol } func (b *Binder) bindPropertyOrMethodOrAccessor(node *ast.Node, symbolFlags ast.SymbolFlags, symbolExcludes ast.SymbolFlags) { if b.currentFlow != nil && ast.IsObjectLiteralOrClassExpressionMethodOrAccessor(node) { setFlowNode(node, b.currentFlow) } if ast.HasDynamicName(node) { b.bindAnonymousDeclaration(node, symbolFlags, ast.InternalSymbolNameComputed) } else { b.declareSymbolAndAddToSymbolTable(node, symbolFlags, symbolExcludes) } } func (b *Binder) bindFunctionOrConstructorType(node *ast.Node) { // For a given function symbol "<...>(...) => T" we want to generate a symbol identical // to the one we would get for: { <...>(...): T } // // We do that by making an anonymous type literal symbol, and then setting the function // symbol as its sole member. To the rest of the system, this symbol will be indistinguishable // from an actual type literal symbol you would have gotten had you used the long form. symbol := b.newSymbol(ast.SymbolFlagsSignature, b.getDeclarationName(node)) b.addDeclarationToSymbol(symbol, node, ast.SymbolFlagsSignature) typeLiteralSymbol := b.newSymbol(ast.SymbolFlagsTypeLiteral, ast.InternalSymbolNameType) b.addDeclarationToSymbol(typeLiteralSymbol, node, ast.SymbolFlagsTypeLiteral) typeLiteralSymbol.Members = make(ast.SymbolTable) typeLiteralSymbol.Members[symbol.Name] = symbol } func addLateBoundAssignmentDeclarationToSymbol(node *ast.Node, symbol *ast.Symbol) { symbol.AssignmentDeclarationMembers.Add(node) } func (b *Binder) bindExpandoPropertyAssignment(node *ast.Node) { expr := node.AsBinaryExpression() parent := expr.Left.Expression() symbol := b.lookupEntity(parent, b.blockScopeContainer) if symbol == nil { symbol = b.lookupEntity(parent, b.container) } if symbol = getInitializerSymbol(symbol); symbol != nil { if ast.HasDynamicName(node) { b.bindAnonymousDeclaration(node, ast.SymbolFlagsProperty|ast.SymbolFlagsAssignment, ast.InternalSymbolNameComputed) addLateBoundAssignmentDeclarationToSymbol(node, symbol) } else { b.declareSymbol(ast.GetExports(symbol), symbol, node, ast.SymbolFlagsProperty|ast.SymbolFlagsAssignment, ast.SymbolFlagsPropertyExcludes) } } } func getInitializerSymbol(symbol *ast.Symbol) *ast.Symbol { if symbol == nil || symbol.ValueDeclaration == nil { return nil } declaration := symbol.ValueDeclaration // For an assignment 'fn.xxx = ...', where 'fn' is a previously declared function or a previously // declared const variable initialized with a function expression or arrow function, we add expando // property declarations to the function's symbol. // This also applies to class expressions and empty object literals. switch { case ast.IsFunctionDeclaration(declaration) || ast.IsInJSFile(declaration) && ast.IsClassDeclaration(declaration): return symbol case ast.IsVariableDeclaration(declaration) && (declaration.Parent.Flags&ast.NodeFlagsConst != 0 || ast.IsInJSFile(declaration)): initializer := declaration.Initializer() if ast.IsExpandoInitializer(initializer) { return initializer.Symbol() } case ast.IsBinaryExpression(declaration) && ast.IsInJSFile(declaration): initializer := declaration.AsBinaryExpression().Right if ast.IsExpandoInitializer(initializer) { return initializer.Symbol() } } return nil } func (b *Binder) bindThisPropertyAssignment(node *ast.Node) { if !ast.IsInJSFile(node) { return } bin := node.AsBinaryExpression() if ast.IsPropertyAccessExpression(bin.Left) && ast.IsPrivateIdentifier(bin.Left.AsPropertyAccessExpression().Name()) || b.thisContainer == nil { return } if classSymbol, symbolTable := b.getThisClassAndSymbolTable(); symbolTable != nil { if ast.HasDynamicName(node) { b.declareSymbolEx(symbolTable, classSymbol, node, ast.SymbolFlagsProperty, ast.SymbolFlagsNone, true /*isReplaceableByMethod*/, true /*isComputedName*/) addLateBoundAssignmentDeclarationToSymbol(node, classSymbol) } else { b.declareSymbolEx(symbolTable, classSymbol, node, ast.SymbolFlagsProperty|ast.SymbolFlagsAssignment, ast.SymbolFlagsNone, true /*isReplaceableByMethod*/, false /*isComputedName*/) } } else if b.thisContainer.Kind != ast.KindFunctionDeclaration && b.thisContainer.Kind != ast.KindFunctionExpression { // !!! constructor functions panic("Unhandled case in bindThisPropertyAssignment: " + b.thisContainer.Kind.String()) } } func (b *Binder) getThisClassAndSymbolTable() (classSymbol *ast.Symbol, symbolTable ast.SymbolTable) { if b.thisContainer == nil { return nil, nil } switch b.thisContainer.Kind { case ast.KindFunctionDeclaration, ast.KindFunctionExpression: // !!! constructor functions case ast.KindConstructor, ast.KindPropertyDeclaration, ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor, ast.KindClassStaticBlockDeclaration: // this.property assignment in class member -- bind to the containing class classSymbol = b.thisContainer.Parent.Symbol() if ast.IsStatic(b.thisContainer) { symbolTable = ast.GetExports(classSymbol) } else { symbolTable = ast.GetMembers(classSymbol) } } return classSymbol, symbolTable } func (b *Binder) bindEnumDeclaration(node *ast.Node) { if ast.IsEnumConst(node) { b.bindBlockScopedDeclaration(node, ast.SymbolFlagsConstEnum, ast.SymbolFlagsConstEnumExcludes) } else { b.bindBlockScopedDeclaration(node, ast.SymbolFlagsRegularEnum, ast.SymbolFlagsRegularEnumExcludes) } } func (b *Binder) bindVariableDeclarationOrBindingElement(node *ast.Node) { if b.inStrictMode { b.checkStrictModeEvalOrArguments(node, node.Name()) } if name := node.Name(); name != nil && !ast.IsBindingPattern(name) { switch { case ast.IsVariableDeclarationInitializedToRequire(node): b.declareSymbolAndAddToSymbolTable(node, ast.SymbolFlagsAlias, ast.SymbolFlagsAliasExcludes) case ast.IsBlockOrCatchScoped(node): b.bindBlockScopedDeclaration(node, ast.SymbolFlagsBlockScopedVariable, ast.SymbolFlagsBlockScopedVariableExcludes) case ast.IsPartOfParameterDeclaration(node): // It is safe to walk up parent chain to find whether the node is a destructuring parameter declaration // because its parent chain has already been set up, since parents are set before descending into children. // // If node is a binding element in parameter declaration, we need to use ParameterExcludes. // Using ParameterExcludes flag allows the compiler to report an error on duplicate identifiers in Parameter Declaration // For example: // function foo([a,a]) {} // Duplicate Identifier error // function bar(a,a) {} // Duplicate Identifier error, parameter declaration in this case is handled in bindParameter // // which correctly set excluded symbols b.declareSymbolAndAddToSymbolTable(node, ast.SymbolFlagsFunctionScopedVariable, ast.SymbolFlagsParameterExcludes) default: b.declareSymbolAndAddToSymbolTable(node, ast.SymbolFlagsFunctionScopedVariable, ast.SymbolFlagsFunctionScopedVariableExcludes) } } } func (b *Binder) bindParameter(node *ast.Node) { decl := node.AsParameterDeclaration() if b.inStrictMode && node.Flags&ast.NodeFlagsAmbient == 0 { // It is a SyntaxError if the identifier eval or arguments appears within a FormalParameterList of a // strict mode FunctionLikeDeclaration or FunctionExpression(13.1) b.checkStrictModeEvalOrArguments(node, decl.Name()) } if ast.IsBindingPattern(decl.Name()) { index := slices.Index(node.Parent.Parameters(), node) b.bindAnonymousDeclaration(node, ast.SymbolFlagsFunctionScopedVariable, "__"+strconv.Itoa(index)) } else { b.declareSymbolAndAddToSymbolTable(node, ast.SymbolFlagsFunctionScopedVariable, ast.SymbolFlagsParameterExcludes) } // If this is a property-parameter, then also declare the property symbol into the // containing class. if ast.IsParameterPropertyDeclaration(node, node.Parent) { classDeclaration := node.Parent.Parent flags := ast.SymbolFlagsProperty | core.IfElse(decl.QuestionToken != nil, ast.SymbolFlagsOptional, ast.SymbolFlagsNone) b.declareSymbol(ast.GetMembers(classDeclaration.Symbol()), classDeclaration.Symbol(), node, flags, ast.SymbolFlagsPropertyExcludes) } } func (b *Binder) bindFunctionDeclaration(node *ast.Node) { b.checkStrictModeFunctionName(node) if b.inStrictMode { b.bindBlockScopedDeclaration(node, ast.SymbolFlagsFunction, ast.SymbolFlagsFunctionExcludes) } else { b.declareSymbolAndAddToSymbolTable(node, ast.SymbolFlagsFunction, ast.SymbolFlagsFunctionExcludes) } } func (b *Binder) getInferTypeContainer(node *ast.Node) *ast.Node { extendsType := ast.FindAncestor(node, func(n *ast.Node) bool { parent := n.Parent return parent != nil && ast.IsConditionalTypeNode(parent) && parent.AsConditionalTypeNode().ExtendsType == n }) if extendsType != nil { return extendsType.Parent } return nil } func (b *Binder) bindAnonymousDeclaration(node *ast.Node, symbolFlags ast.SymbolFlags, name string) { symbol := b.newSymbol(symbolFlags, name) if symbolFlags&(ast.SymbolFlagsEnumMember|ast.SymbolFlagsClassMember) != 0 { symbol.Parent = b.container.Symbol() } b.addDeclarationToSymbol(symbol, node, symbolFlags) } func (b *Binder) bindBlockScopedDeclaration(node *ast.Node, symbolFlags ast.SymbolFlags, symbolExcludes ast.SymbolFlags) { switch b.blockScopeContainer.Kind { case ast.KindModuleDeclaration: b.declareModuleMember(node, symbolFlags, symbolExcludes) case ast.KindSourceFile: if ast.IsExternalOrCommonJSModule(b.container.AsSourceFile()) { b.declareModuleMember(node, symbolFlags, symbolExcludes) break } fallthrough default: b.declareSymbol(ast.GetLocals(b.blockScopeContainer), nil /*parent*/, node, symbolFlags, symbolExcludes) } } func (b *Binder) bindTypeParameter(node *ast.Node) { if node.Parent.Kind == ast.KindInferType { container := b.getInferTypeContainer(node.Parent) if container != nil { b.declareSymbol(ast.GetLocals(container), nil /*parent*/, node, ast.SymbolFlagsTypeParameter, ast.SymbolFlagsTypeParameterExcludes) } else { b.bindAnonymousDeclaration(node, ast.SymbolFlagsTypeParameter, b.getDeclarationName(node)) } } else { b.declareSymbolAndAddToSymbolTable(node, ast.SymbolFlagsTypeParameter, ast.SymbolFlagsTypeParameterExcludes) } } func (b *Binder) lookupEntity(node *ast.Node, container *ast.Node) *ast.Symbol { if ast.IsIdentifier(node) { return b.lookupName(node.AsIdentifier().Text, container) } if ast.IsPropertyAccessExpression(node) && node.AsPropertyAccessExpression().Expression.Kind == ast.KindThisKeyword || ast.IsElementAccessExpression(node) && node.AsElementAccessExpression().Expression.Kind == ast.KindThisKeyword { if _, symbolTable := b.getThisClassAndSymbolTable(); symbolTable != nil { if name := ast.GetElementOrPropertyAccessName(node); name != nil { return symbolTable[name.Text()] } } return nil } if symbol := getInitializerSymbol(b.lookupEntity(node.Expression(), container)); symbol != nil && symbol.Exports != nil { if name := ast.GetElementOrPropertyAccessName(node); name != nil { return symbol.Exports[name.Text()] } } return nil } func (b *Binder) lookupName(name string, container *ast.Node) *ast.Symbol { if localsContainer := container.LocalsContainerData(); localsContainer != nil { if local := localsContainer.Locals[name]; local != nil { return core.OrElse(local.ExportSymbol, local) } } if declaration := container.DeclarationData(); declaration != nil && declaration.Symbol != nil { return declaration.Symbol.Exports[name] } return nil } // The binder visits every node in the syntax tree so it is a convenient place to perform a single localized // check for reserved words used as identifiers in strict mode code, as well as `yield` or `await` in // [Yield] or [Await] contexts, respectively. func (b *Binder) checkContextualIdentifier(node *ast.Node) { // Report error only if there are no parse errors in file if len(b.file.Diagnostics()) == 0 && node.Flags&ast.NodeFlagsAmbient == 0 && node.Flags&ast.NodeFlagsJSDoc == 0 && !ast.IsIdentifierName(node) { // strict mode identifiers originalKeywordKind := scanner.GetIdentifierToken(node.AsIdentifier().Text) if originalKeywordKind == ast.KindIdentifier { return } if b.inStrictMode && originalKeywordKind >= ast.KindFirstFutureReservedWord && originalKeywordKind <= ast.KindLastFutureReservedWord { b.errorOnNode(node, b.getStrictModeIdentifierMessage(node), scanner.DeclarationNameToString(node)) } else if originalKeywordKind == ast.KindAwaitKeyword { if ast.IsExternalModule(b.file) && ast.IsInTopLevelContext(node) { b.errorOnNode(node, diagnostics.Identifier_expected_0_is_a_reserved_word_at_the_top_level_of_a_module, scanner.DeclarationNameToString(node)) } else if node.Flags&ast.NodeFlagsAwaitContext != 0 { b.errorOnNode(node, diagnostics.Identifier_expected_0_is_a_reserved_word_that_cannot_be_used_here, scanner.DeclarationNameToString(node)) } } else if originalKeywordKind == ast.KindYieldKeyword && node.Flags&ast.NodeFlagsYieldContext != 0 { b.errorOnNode(node, diagnostics.Identifier_expected_0_is_a_reserved_word_that_cannot_be_used_here, scanner.DeclarationNameToString(node)) } } } func (b *Binder) checkPrivateIdentifier(node *ast.Node) { if node.AsPrivateIdentifier().Text == "#constructor" { // Report error only if there are no parse errors in file if len(b.file.Diagnostics()) == 0 { b.errorOnNode(node, diagnostics.X_constructor_is_a_reserved_word, scanner.DeclarationNameToString(node)) } } } func (b *Binder) getStrictModeIdentifierMessage(node *ast.Node) *diagnostics.Message { // Provide specialized messages to help the user understand why we think they're in // strict mode. if ast.GetContainingClass(node) != nil { return diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode_Class_definitions_are_automatically_in_strict_mode } if b.file.ExternalModuleIndicator != nil { return diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode_Modules_are_automatically_in_strict_mode } return diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode } func (b *Binder) updateStrictModeStatementList(statements *ast.NodeList) { if !b.inStrictMode { useStrictDirective := FindUseStrictPrologue(b.file, statements.Nodes) if useStrictDirective != nil { b.inStrictMode = true } } } // Should be called only on prologue directives (ast.IsPrologueDirective(node) should be true) func isUseStrictPrologueDirective(sourceFile *ast.SourceFile, node *ast.Node) bool { nodeText := scanner.GetSourceTextOfNodeFromSourceFile(sourceFile, node.AsExpressionStatement().Expression, false /*includeTrivia*/) // Note: the node text must be exactly "use strict" or 'use strict'. It is not ok for the // string to contain unicode escapes (as per ES5). return nodeText == "\"use strict\"" || nodeText == "'use strict'" } func FindUseStrictPrologue(sourceFile *ast.SourceFile, statements []*ast.Node) *ast.Node { for _, statement := range statements { if ast.IsPrologueDirective(statement) { if isUseStrictPrologueDirective(sourceFile, statement) { return statement } } else { return nil } } return nil } func (b *Binder) checkStrictModeFunctionName(node *ast.Node) { if b.inStrictMode && node.Flags&ast.NodeFlagsAmbient == 0 { // It is a SyntaxError if the identifier eval or arguments appears within a FormalParameterList of a strict mode FunctionDeclaration or FunctionExpression (13.1)) b.checkStrictModeEvalOrArguments(node, node.Name()) } } func (b *Binder) getStrictModeBlockScopeFunctionDeclarationMessage(node *ast.Node) *diagnostics.Message { // Provide specialized messages to help the user understand why we think they're in strict mode. if ast.GetContainingClass(node) != nil { return diagnostics.Function_declarations_are_not_allowed_inside_blocks_in_strict_mode_when_targeting_ES5_Class_definitions_are_automatically_in_strict_mode } if b.file.ExternalModuleIndicator != nil { return diagnostics.Function_declarations_are_not_allowed_inside_blocks_in_strict_mode_when_targeting_ES5_Modules_are_automatically_in_strict_mode } return diagnostics.Function_declarations_are_not_allowed_inside_blocks_in_strict_mode_when_targeting_ES5 } func (b *Binder) checkStrictModeBinaryExpression(node *ast.Node) { expr := node.AsBinaryExpression() if b.inStrictMode && ast.IsLeftHandSideExpression(expr.Left) && ast.IsAssignmentOperator(expr.OperatorToken.Kind) { // ECMA 262 (Annex C) The identifier eval or arguments may not appear as the LeftHandSideExpression of an // Assignment operator(11.13) or of a PostfixExpression(11.3) b.checkStrictModeEvalOrArguments(node, expr.Left) } } func (b *Binder) checkStrictModeCatchClause(node *ast.Node) { // It is a SyntaxError if a TryStatement with a Catch occurs within strict code and the Identifier of the // Catch production is eval or arguments clause := node.AsCatchClause() if b.inStrictMode && clause.VariableDeclaration != nil { b.checkStrictModeEvalOrArguments(node, clause.VariableDeclaration.AsVariableDeclaration().Name()) } } func (b *Binder) checkStrictModeDeleteExpression(node *ast.Node) { // Grammar checking expr := node.AsDeleteExpression() if b.inStrictMode && expr.Expression.Kind == ast.KindIdentifier { // When a delete operator occurs within strict mode code, a SyntaxError is thrown if its // UnaryExpression is a direct reference to a variable, function argument, or function name b.errorOnNode(expr.Expression, diagnostics.X_delete_cannot_be_called_on_an_identifier_in_strict_mode) } } func (b *Binder) checkStrictModePostfixUnaryExpression(node *ast.Node) { // Grammar checking // The identifier eval or arguments may not appear as the LeftHandSideExpression of an // Assignment operator(11.13) or of a PostfixExpression(11.3) or as the UnaryExpression // operated upon by a Prefix Increment(11.4.4) or a Prefix Decrement(11.4.5) operator. if b.inStrictMode { b.checkStrictModeEvalOrArguments(node, node.AsPostfixUnaryExpression().Operand) } } func (b *Binder) checkStrictModePrefixUnaryExpression(node *ast.Node) { // Grammar checking if b.inStrictMode { expr := node.AsPrefixUnaryExpression() if expr.Operator == ast.KindPlusPlusToken || expr.Operator == ast.KindMinusMinusToken { b.checkStrictModeEvalOrArguments(node, expr.Operand) } } } func (b *Binder) checkStrictModeWithStatement(node *ast.Node) { // Grammar checking for withStatement if b.inStrictMode { b.errorOnFirstToken(node, diagnostics.X_with_statements_are_not_allowed_in_strict_mode) } } func (b *Binder) checkStrictModeLabeledStatement(node *ast.Node) { // Grammar checking for labeledStatement if b.inStrictMode { data := node.AsLabeledStatement() if ast.IsDeclarationStatement(data.Statement) || ast.IsVariableStatement(data.Statement) { b.errorOnFirstToken(data.Label, diagnostics.A_label_is_not_allowed_here) } } } func isEvalOrArgumentsIdentifier(node *ast.Node) bool { if ast.IsIdentifier(node) { text := node.AsIdentifier().Text return text == "eval" || text == "arguments" } return false } func (b *Binder) checkStrictModeEvalOrArguments(contextNode *ast.Node, name *ast.Node) { if name != nil && isEvalOrArgumentsIdentifier(name) { // We check first if the name is inside class declaration or class expression; if so give explicit message // otherwise report generic error message. b.errorOnNode(name, b.getStrictModeEvalOrArgumentsMessage(contextNode), name.AsIdentifier().Text) } } func (b *Binder) getStrictModeEvalOrArgumentsMessage(node *ast.Node) *diagnostics.Message { // Provide specialized messages to help the user understand why we think they're in strict mode if ast.GetContainingClass(node) != nil { return diagnostics.Code_contained_in_a_class_is_evaluated_in_JavaScript_s_strict_mode_which_does_not_allow_this_use_of_0_For_more_information_see_https_Colon_Slash_Slashdeveloper_mozilla_org_Slashen_US_Slashdocs_SlashWeb_SlashJavaScript_SlashReference_SlashStrict_mode } if b.file.ExternalModuleIndicator != nil { return diagnostics.Invalid_use_of_0_Modules_are_automatically_in_strict_mode } return diagnostics.Invalid_use_of_0_in_strict_mode } // All container nodes are kept on a linked list in declaration order. This list is used by // the getLocalNameOfContainer function in the type checker to validate that the local name // used for a container is unique. func (b *Binder) bindContainer(node *ast.Node, containerFlags ContainerFlags) { // Before we recurse into a node's children, we first save the existing parent, container // and block-container. Then after we pop out of processing the children, we restore // these saved values. saveContainer := b.container saveThisContainer := b.thisContainer savedBlockScopeContainer := b.blockScopeContainer // Depending on what kind of node this is, we may have to adjust the current container // and block-container. If the current node is a container, then it is automatically // considered the current block-container as well. Also, for containers that we know // may contain locals, we eagerly initialize the .locals field. We do this because // it's highly likely that the .locals will be needed to place some child in (for example, // a parameter, or variable declaration). // // However, we do not proactively create the .locals for block-containers because it's // totally normal and common for block-containers to never actually have a block-scoped // variable in them. We don't want to end up allocating an object for every 'block' we // run into when most of them won't be necessary. // // Finally, if this is a block-container, then we clear out any existing .locals object // it may contain within it. This happens in incremental scenarios. Because we can be // reusing a node from a previous compilation, that node may have had 'locals' created // for it. We must clear this so we don't accidentally move any stale data forward from // a previous compilation. if containerFlags&ContainerFlagsIsContainer != 0 { b.container = node b.blockScopeContainer = node if containerFlags&ContainerFlagsHasLocals != 0 { // localsContainer := node // localsContainer.LocalsContainerData().locals = make(SymbolTable) b.addToContainerChain(node) } } else if containerFlags&ContainerFlagsIsBlockScopedContainer != 0 { b.blockScopeContainer = node b.addToContainerChain(node) } if containerFlags&ContainerFlagsIsThisContainer != 0 { b.thisContainer = node } if containerFlags&ContainerFlagsIsControlFlowContainer != 0 { saveCurrentFlow := b.currentFlow saveBreakTarget := b.currentBreakTarget saveContinueTarget := b.currentContinueTarget saveReturnTarget := b.currentReturnTarget saveExceptionTarget := b.currentExceptionTarget saveActiveLabelList := b.activeLabelList saveHasExplicitReturn := b.hasExplicitReturn isImmediatelyInvoked := (containerFlags&ContainerFlagsIsFunctionExpression != 0 && !ast.HasSyntacticModifier(node, ast.ModifierFlagsAsync) && !isGeneratorFunctionExpression(node) && ast.GetImmediatelyInvokedFunctionExpression(node) != nil) || node.Kind == ast.KindClassStaticBlockDeclaration // A non-async, non-generator IIFE is considered part of the containing control flow. Return statements behave // similarly to break statements that exit to a label just past the statement body. if !isImmediatelyInvoked { flowStart := b.newFlowNode(ast.FlowFlagsStart) b.currentFlow = flowStart if containerFlags&(ContainerFlagsIsFunctionExpression|ContainerFlagsIsObjectLiteralOrClassExpressionMethodOrAccessor) != 0 { flowStart.Node = node } } // We create a return control flow graph for IIFEs and constructors. For constructors // we use the return control flow graph in strict property initialization checks. if isImmediatelyInvoked || node.Kind == ast.KindConstructor { b.currentReturnTarget = b.newFlowNode(ast.FlowFlagsBranchLabel) } else { b.currentReturnTarget = nil } b.currentExceptionTarget = nil b.currentBreakTarget = nil b.currentContinueTarget = nil b.activeLabelList = nil b.hasExplicitReturn = false b.bindChildren(node) // Reset all reachability check related flags on node (for incremental scenarios) node.Flags &= ^ast.NodeFlagsReachabilityCheckFlags if b.currentFlow.Flags&ast.FlowFlagsUnreachable == 0 && containerFlags&ContainerFlagsIsFunctionLike != 0 { bodyData := node.BodyData() if bodyData != nil && ast.NodeIsPresent(bodyData.Body) { node.Flags |= ast.NodeFlagsHasImplicitReturn if b.hasExplicitReturn { node.Flags |= ast.NodeFlagsHasExplicitReturn } bodyData.EndFlowNode = b.currentFlow } } if node.Kind == ast.KindSourceFile { node.Flags |= b.emitFlags node.AsSourceFile().EndFlowNode = b.currentFlow } if b.currentReturnTarget != nil { b.addAntecedent(b.currentReturnTarget, b.currentFlow) b.currentFlow = b.finishFlowLabel(b.currentReturnTarget) if node.Kind == ast.KindConstructor || node.Kind == ast.KindClassStaticBlockDeclaration { setReturnFlowNode(node, b.currentFlow) } } if !isImmediatelyInvoked { b.currentFlow = saveCurrentFlow } b.currentBreakTarget = saveBreakTarget b.currentContinueTarget = saveContinueTarget b.currentReturnTarget = saveReturnTarget b.currentExceptionTarget = saveExceptionTarget b.activeLabelList = saveActiveLabelList b.hasExplicitReturn = saveHasExplicitReturn } else if containerFlags&ContainerFlagsIsInterface != 0 { b.seenThisKeyword = false b.bindChildren(node) // ContainsThis cannot overlap with HasExtendedUnicodeEscape on Identifier if b.seenThisKeyword { node.Flags |= ast.NodeFlagsContainsThis } else { node.Flags &= ^ast.NodeFlagsContainsThis } } else { b.bindChildren(node) } b.container = saveContainer b.thisContainer = saveThisContainer b.blockScopeContainer = savedBlockScopeContainer } func (b *Binder) bindChildren(node *ast.Node) { saveInAssignmentPattern := b.inAssignmentPattern // Most nodes aren't valid in an assignment pattern, so we clear the value here // and set it before we descend into nodes that could actually be part of an assignment pattern. b.inAssignmentPattern = false if b.checkUnreachable(node) { b.bindEachChild(node) b.inAssignmentPattern = saveInAssignmentPattern return } kind := node.Kind if kind >= ast.KindFirstStatement && kind <= ast.KindLastStatement && (b.options().AllowUnreachableCode != core.TSTrue || kind == ast.KindReturnStatement) { hasFlowNodeData := node.FlowNodeData() if hasFlowNodeData != nil { hasFlowNodeData.FlowNode = b.currentFlow } } switch node.Kind { case ast.KindWhileStatement: b.bindWhileStatement(node) case ast.KindDoStatement: b.bindDoStatement(node) case ast.KindForStatement: b.bindForStatement(node) case ast.KindForInStatement, ast.KindForOfStatement: b.bindForInOrForOfStatement(node) case ast.KindIfStatement: b.bindIfStatement(node) case ast.KindReturnStatement: b.bindReturnStatement(node) case ast.KindThrowStatement: b.bindThrowStatement(node) case ast.KindBreakStatement: b.bindBreakStatement(node) case ast.KindContinueStatement: b.bindContinueStatement(node) case ast.KindTryStatement: b.bindTryStatement(node) case ast.KindSwitchStatement: b.bindSwitchStatement(node) case ast.KindCaseBlock: b.bindCaseBlock(node) case ast.KindCaseClause, ast.KindDefaultClause: b.bindCaseOrDefaultClause(node) case ast.KindExpressionStatement: b.bindExpressionStatement(node) case ast.KindLabeledStatement: b.bindLabeledStatement(node) case ast.KindPrefixUnaryExpression: b.bindPrefixUnaryExpressionFlow(node) case ast.KindPostfixUnaryExpression: b.bindPostfixUnaryExpressionFlow(node) case ast.KindBinaryExpression: if ast.IsDestructuringAssignment(node) { // Carry over whether we are in an assignment pattern to // binary expressions that could actually be an initializer b.inAssignmentPattern = saveInAssignmentPattern b.bindDestructuringAssignmentFlow(node) return } b.bindBinaryExpressionFlow(node) case ast.KindDeleteExpression: b.bindDeleteExpressionFlow(node) case ast.KindConditionalExpression: b.bindConditionalExpressionFlow(node) case ast.KindVariableDeclaration: b.bindVariableDeclarationFlow(node) case ast.KindPropertyAccessExpression, ast.KindElementAccessExpression: b.bindAccessExpressionFlow(node) case ast.KindCallExpression: b.bindCallExpressionFlow(node) case ast.KindNonNullExpression: b.bindNonNullExpressionFlow(node) case ast.KindSourceFile: sourceFile := node.AsSourceFile() b.bindEachStatementFunctionsFirst(sourceFile.Statements) b.bind(sourceFile.EndOfFileToken) case ast.KindBlock: b.bindEachStatementFunctionsFirst(node.AsBlock().Statements) case ast.KindModuleBlock: b.bindEachStatementFunctionsFirst(node.AsModuleBlock().Statements) case ast.KindBindingElement: b.bindBindingElementFlow(node) case ast.KindParameter: b.bindParameterFlow(node) case ast.KindObjectLiteralExpression, ast.KindArrayLiteralExpression, ast.KindPropertyAssignment, ast.KindSpreadElement: b.inAssignmentPattern = saveInAssignmentPattern b.bindEachChild(node) default: b.bindEachChild(node) } b.inAssignmentPattern = saveInAssignmentPattern } func (b *Binder) bindEachChild(node *ast.Node) { node.ForEachChild(b.bindFunc) } func (b *Binder) bindEach(nodes []*ast.Node) { for _, node := range nodes { b.bind(node) } } func (b *Binder) bindNodeList(nodeList *ast.NodeList) { if nodeList != nil { b.bindEach(nodeList.Nodes) } } func (b *Binder) bindModifiers(modifiers *ast.ModifierList) { if modifiers != nil { b.bindEach(modifiers.Nodes) } } func (b *Binder) bindEachStatementFunctionsFirst(statements *ast.NodeList) { for _, node := range statements.Nodes { if node.Kind == ast.KindFunctionDeclaration { b.bind(node) } } for _, node := range statements.Nodes { if node.Kind != ast.KindFunctionDeclaration { b.bind(node) } } } func (b *Binder) checkUnreachable(node *ast.Node) bool { if b.currentFlow.Flags&ast.FlowFlagsUnreachable == 0 { return false } if b.currentFlow == b.unreachableFlow { // report errors on all statements except empty ones // report errors on class declarations // report errors on enums with preserved emit // report errors on instantiated modules reportError := ast.IsStatementButNotDeclaration(node) && !ast.IsEmptyStatement(node) || ast.IsClassDeclaration(node) || isEnumDeclarationWithPreservedEmit(node, b.options()) || ast.IsModuleDeclaration(node) && b.shouldReportErrorOnModuleDeclaration(node) if reportError { b.currentFlow = b.reportedUnreachableFlow if b.options().AllowUnreachableCode != core.TSTrue { // unreachable code is reported if // - user has explicitly asked about it AND // - statement is in not ambient context (statements in ambient context is already an error // so we should not report extras) AND // - node is not variable statement OR // - node is block scoped variable statement OR // - node is not block scoped variable statement and at least one variable declaration has initializer // Rationale: we don't want to report errors on non-initialized var's since they are hoisted // On the other side we do want to report errors on non-initialized 'lets' because of TDZ isError := unreachableCodeIsError(b.options()) && node.Flags&ast.NodeFlagsAmbient == 0 && (!ast.IsVariableStatement(node) || ast.GetCombinedNodeFlags(node.AsVariableStatement().DeclarationList)&ast.NodeFlagsBlockScoped != 0 || core.Some(node.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes, func(d *ast.Node) bool { return d.AsVariableDeclaration().Initializer != nil })) b.errorOnEachUnreachableRange(node, isError) } } } return true } func (b *Binder) shouldReportErrorOnModuleDeclaration(node *ast.Node) bool { instanceState := ast.GetModuleInstanceState(node) return instanceState == ast.ModuleInstanceStateInstantiated || (instanceState == ast.ModuleInstanceStateConstEnumOnly && b.options().ShouldPreserveConstEnums) } func (b *Binder) errorOnEachUnreachableRange(node *ast.Node, isError bool) { if b.isExecutableStatement(node) && ast.IsBlock(node.Parent) { statements := node.Parent.AsBlock().Statements.Nodes index := slices.Index(statements, node) var first, last *ast.Node for _, s := range statements[index:] { if b.isExecutableStatement(s) { if first == nil { first = s } last = s } else if first != nil { b.errorOrSuggestionOnRange(isError, first, last, diagnostics.Unreachable_code_detected) first = nil } } if first != nil { b.errorOrSuggestionOnRange(isError, first, last, diagnostics.Unreachable_code_detected) } } else { b.errorOrSuggestionOnNode(isError, node, diagnostics.Unreachable_code_detected) } } // As opposed to a pure declaration like an `interface` func (b *Binder) isExecutableStatement(s *ast.Node) bool { // Don't remove statements that can validly be used before they appear. return !ast.IsFunctionDeclaration(s) && !b.isPurelyTypeDeclaration(s) && !(ast.IsVariableStatement(s) && ast.GetCombinedNodeFlags(s)&ast.NodeFlagsBlockScoped == 0 && core.Some(s.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes, func(d *ast.Node) bool { return d.AsVariableDeclaration().Initializer == nil })) } func (b *Binder) isPurelyTypeDeclaration(s *ast.Node) bool { switch s.Kind { case ast.KindInterfaceDeclaration, ast.KindTypeAliasDeclaration, ast.KindJSTypeAliasDeclaration: return true case ast.KindModuleDeclaration: return ast.GetModuleInstanceState(s) != ast.ModuleInstanceStateInstantiated case ast.KindEnumDeclaration: return !isEnumDeclarationWithPreservedEmit(s, b.options()) default: return false } } func (b *Binder) setContinueTarget(node *ast.Node, target *ast.FlowLabel) *ast.FlowLabel { label := b.activeLabelList for label != nil && node.Parent.Kind == ast.KindLabeledStatement { label.continueTarget = target label = label.next node = node.Parent } return target } func (b *Binder) doWithConditionalBranches(action func(b *Binder, value *ast.Node) bool, value *ast.Node, trueTarget *ast.FlowLabel, falseTarget *ast.FlowLabel) { savedTrueTarget := b.currentTrueTarget savedFalseTarget := b.currentFalseTarget b.currentTrueTarget = trueTarget b.currentFalseTarget = falseTarget action(b, value) b.currentTrueTarget = savedTrueTarget b.currentFalseTarget = savedFalseTarget } func (b *Binder) bindCondition(node *ast.Node, trueTarget *ast.FlowLabel, falseTarget *ast.FlowLabel) { b.doWithConditionalBranches((*Binder).bind, node, trueTarget, falseTarget) if node == nil || !isLogicalAssignmentExpression(node) && !ast.IsLogicalExpression(node) && !(ast.IsOptionalChain(node) && ast.IsOutermostOptionalChain(node)) { b.addAntecedent(trueTarget, b.createFlowCondition(ast.FlowFlagsTrueCondition, b.currentFlow, node)) b.addAntecedent(falseTarget, b.createFlowCondition(ast.FlowFlagsFalseCondition, b.currentFlow, node)) } } func (b *Binder) bindIterativeStatement(node *ast.Node, breakTarget *ast.FlowLabel, continueTarget *ast.FlowLabel) { saveBreakTarget := b.currentBreakTarget saveContinueTarget := b.currentContinueTarget b.currentBreakTarget = breakTarget b.currentContinueTarget = continueTarget b.bind(node) b.currentBreakTarget = saveBreakTarget b.currentContinueTarget = saveContinueTarget } func isLogicalAssignmentExpression(node *ast.Node) bool { return ast.IsLogicalOrCoalescingAssignmentExpression(ast.SkipParentheses(node)) } func (b *Binder) bindAssignmentTargetFlow(node *ast.Node) { switch node.Kind { case ast.KindArrayLiteralExpression: for _, e := range node.AsArrayLiteralExpression().Elements.Nodes { if e.Kind == ast.KindSpreadElement { b.bindAssignmentTargetFlow(e.AsSpreadElement().Expression) } else { b.bindDestructuringTargetFlow(e) } } case ast.KindObjectLiteralExpression: for _, p := range node.AsObjectLiteralExpression().Properties.Nodes { switch p.Kind { case ast.KindPropertyAssignment: b.bindDestructuringTargetFlow(p.AsPropertyAssignment().Initializer) case ast.KindShorthandPropertyAssignment: b.bindAssignmentTargetFlow(p.AsShorthandPropertyAssignment().Name()) case ast.KindSpreadAssignment: b.bindAssignmentTargetFlow(p.AsSpreadAssignment().Expression) } } default: if isNarrowableReference(node) { b.currentFlow = b.createFlowMutation(ast.FlowFlagsAssignment, b.currentFlow, node) } } } func (b *Binder) bindDestructuringTargetFlow(node *ast.Node) { if ast.IsBinaryExpression(node) && node.AsBinaryExpression().OperatorToken.Kind == ast.KindEqualsToken { b.bindAssignmentTargetFlow(node.AsBinaryExpression().Left) } else { b.bindAssignmentTargetFlow(node) } } func (b *Binder) bindWhileStatement(node *ast.Node) { stmt := node.AsWhileStatement() preWhileLabel := b.setContinueTarget(node, b.createLoopLabel()) preBodyLabel := b.createBranchLabel() postWhileLabel := b.createBranchLabel() b.addAntecedent(preWhileLabel, b.currentFlow) b.currentFlow = preWhileLabel b.bindCondition(stmt.Expression, preBodyLabel, postWhileLabel) b.currentFlow = b.finishFlowLabel(preBodyLabel) b.bindIterativeStatement(stmt.Statement, postWhileLabel, preWhileLabel) b.addAntecedent(preWhileLabel, b.currentFlow) b.currentFlow = b.finishFlowLabel(postWhileLabel) } func (b *Binder) bindDoStatement(node *ast.Node) { stmt := node.AsDoStatement() preDoLabel := b.createLoopLabel() preConditionLabel := b.setContinueTarget(node, b.createBranchLabel()) postDoLabel := b.createBranchLabel() b.addAntecedent(preDoLabel, b.currentFlow) b.currentFlow = preDoLabel b.bindIterativeStatement(stmt.Statement, postDoLabel, preConditionLabel) b.addAntecedent(preConditionLabel, b.currentFlow) b.currentFlow = b.finishFlowLabel(preConditionLabel) b.bindCondition(stmt.Expression, preDoLabel, postDoLabel) b.currentFlow = b.finishFlowLabel(postDoLabel) } func (b *Binder) bindForStatement(node *ast.Node) { stmt := node.AsForStatement() preLoopLabel := b.setContinueTarget(node, b.createLoopLabel()) preBodyLabel := b.createBranchLabel() preIncrementorLabel := b.createBranchLabel() postLoopLabel := b.createBranchLabel() b.bind(stmt.Initializer) b.addAntecedent(preLoopLabel, b.currentFlow) b.currentFlow = preLoopLabel b.bindCondition(stmt.Condition, preBodyLabel, postLoopLabel) b.currentFlow = b.finishFlowLabel(preBodyLabel) b.bindIterativeStatement(stmt.Statement, postLoopLabel, preIncrementorLabel) b.addAntecedent(preIncrementorLabel, b.currentFlow) b.currentFlow = b.finishFlowLabel(preIncrementorLabel) b.bind(stmt.Incrementor) b.addAntecedent(preLoopLabel, b.currentFlow) b.currentFlow = b.finishFlowLabel(postLoopLabel) } func (b *Binder) bindForInOrForOfStatement(node *ast.Node) { stmt := node.AsForInOrOfStatement() preLoopLabel := b.setContinueTarget(node, b.createLoopLabel()) postLoopLabel := b.createBranchLabel() b.bind(stmt.Expression) b.addAntecedent(preLoopLabel, b.currentFlow) b.currentFlow = preLoopLabel if node.Kind == ast.KindForOfStatement { b.bind(stmt.AwaitModifier) } b.addAntecedent(postLoopLabel, b.currentFlow) b.bind(stmt.Initializer) if stmt.Initializer.Kind != ast.KindVariableDeclarationList { b.bindAssignmentTargetFlow(stmt.Initializer) } b.bindIterativeStatement(stmt.Statement, postLoopLabel, preLoopLabel) b.addAntecedent(preLoopLabel, b.currentFlow) b.currentFlow = b.finishFlowLabel(postLoopLabel) } func (b *Binder) bindIfStatement(node *ast.Node) { stmt := node.AsIfStatement() thenLabel := b.createBranchLabel() elseLabel := b.createBranchLabel() postIfLabel := b.createBranchLabel() b.bindCondition(stmt.Expression, thenLabel, elseLabel) b.currentFlow = b.finishFlowLabel(thenLabel) b.bind(stmt.ThenStatement) b.addAntecedent(postIfLabel, b.currentFlow) b.currentFlow = b.finishFlowLabel(elseLabel) b.bind(stmt.ElseStatement) b.addAntecedent(postIfLabel, b.currentFlow) b.currentFlow = b.finishFlowLabel(postIfLabel) } func (b *Binder) bindReturnStatement(node *ast.Node) { b.bind(node.AsReturnStatement().Expression) if b.currentReturnTarget != nil { b.addAntecedent(b.currentReturnTarget, b.currentFlow) } b.currentFlow = b.unreachableFlow b.hasExplicitReturn = true b.hasFlowEffects = true } func (b *Binder) bindThrowStatement(node *ast.Node) { b.bind(node.AsThrowStatement().Expression) b.currentFlow = b.unreachableFlow b.hasFlowEffects = true } func (b *Binder) bindBreakStatement(node *ast.Node) { b.bindBreakOrContinueStatement(node.AsBreakStatement().Label, b.currentBreakTarget, (*ActiveLabel).BreakTarget) } func (b *Binder) bindContinueStatement(node *ast.Node) { b.bindBreakOrContinueStatement(node.AsContinueStatement().Label, b.currentContinueTarget, (*ActiveLabel).ContinueTarget) } func (b *Binder) bindBreakOrContinueStatement(label *ast.Node, currentTarget *ast.FlowNode, getTarget func(*ActiveLabel) *ast.FlowNode) { b.bind(label) if label != nil { activeLabel := b.findActiveLabel(label.AsIdentifier().Text) if activeLabel != nil { activeLabel.referenced = true b.bindBreakOrContinueFlow(getTarget(activeLabel)) } } else { b.bindBreakOrContinueFlow(currentTarget) } } func (b *Binder) findActiveLabel(name string) *ActiveLabel { for label := b.activeLabelList; label != nil; label = label.next { if label.name == name { return label } } return nil } func (b *Binder) bindBreakOrContinueFlow(flowLabel *ast.FlowLabel) { if flowLabel != nil { b.addAntecedent(flowLabel, b.currentFlow) b.currentFlow = b.unreachableFlow b.hasFlowEffects = true } } func (b *Binder) bindTryStatement(node *ast.Node) { // We conservatively assume that *any* code in the try block can cause an exception, but we only need // to track code that causes mutations (because only mutations widen the possible control flow type of // a variable). The exceptionLabel is the target label for control flows that result from exceptions. // We add all mutation flow nodes as antecedents of this label such that we can analyze them as possible // antecedents of the start of catch or finally blocks. Furthermore, we add the current control flow to // represent exceptions that occur before any mutations. stmt := node.AsTryStatement() saveReturnTarget := b.currentReturnTarget saveExceptionTarget := b.currentExceptionTarget normalExitLabel := b.createBranchLabel() returnLabel := b.createBranchLabel() exceptionLabel := b.createBranchLabel() if stmt.FinallyBlock != nil { b.currentReturnTarget = returnLabel } b.addAntecedent(exceptionLabel, b.currentFlow) b.currentExceptionTarget = exceptionLabel b.bind(stmt.TryBlock) b.addAntecedent(normalExitLabel, b.currentFlow) if stmt.CatchClause != nil { // Start of catch clause is the target of exceptions from try block. b.currentFlow = b.finishFlowLabel(exceptionLabel) // The currentExceptionTarget now represents control flows from exceptions in the catch clause. // Effectively, in a try-catch-finally, if an exception occurs in the try block, the catch block // acts like a second try block. exceptionLabel = b.createBranchLabel() b.addAntecedent(exceptionLabel, b.currentFlow) b.currentExceptionTarget = exceptionLabel b.bind(stmt.CatchClause) b.addAntecedent(normalExitLabel, b.currentFlow) } b.currentReturnTarget = saveReturnTarget b.currentExceptionTarget = saveExceptionTarget if stmt.FinallyBlock != nil { // Possible ways control can reach the finally block: // 1) Normal completion of try block of a try-finally or try-catch-finally // 2) Normal completion of catch block (following exception in try block) of a try-catch-finally // 3) Return in try or catch block of a try-finally or try-catch-finally // 4) Exception in try block of a try-finally // 5) Exception in catch block of a try-catch-finally // When analyzing a control flow graph that starts inside a finally block we want to consider all // five possibilities above. However, when analyzing a control flow graph that starts outside (past) // the finally block, we only want to consider the first two (if we're past a finally block then it // must have completed normally). Likewise, when analyzing a control flow graph from return statements // in try or catch blocks in an IIFE, we only want to consider the third. To make this possible, we // inject a ReduceLabel node into the control flow graph. This node contains an alternate reduced // set of antecedents for the pre-finally label. As control flow analysis passes by a ReduceLabel // node, the pre-finally label is temporarily switched to the reduced antecedent set. finallyLabel := b.createBranchLabel() finallyLabel.Antecedents = b.combineFlowLists(normalExitLabel.Antecedents, b.combineFlowLists(exceptionLabel.Antecedents, returnLabel.Antecedents)) b.currentFlow = finallyLabel b.bind(stmt.FinallyBlock) if b.currentFlow.Flags&ast.FlowFlagsUnreachable != 0 { // If the end of the finally block is unreachable, the end of the entire try statement is unreachable. b.currentFlow = b.unreachableFlow } else { // If we have an IIFE return target and return statements in the try or catch blocks, add a control // flow that goes back through the finally block and back through only the return statements. if b.currentReturnTarget != nil && returnLabel.Antecedents != nil { b.addAntecedent(b.currentReturnTarget, b.createReduceLabel(finallyLabel, returnLabel.Antecedents, b.currentFlow)) } // If we have an outer exception target (i.e. a containing try-finally or try-catch-finally), add a // control flow that goes back through the finally block and back through each possible exception source. if b.currentExceptionTarget != nil && exceptionLabel.Antecedents != nil { b.addAntecedent(b.currentExceptionTarget, b.createReduceLabel(finallyLabel, exceptionLabel.Antecedents, b.currentFlow)) } // If the end of the finally block is reachable, but the end of the try and catch blocks are not, // convert the current flow to unreachable. For example, 'try { return 1; } finally { ... }' should // result in an unreachable current control flow. if normalExitLabel.Antecedents != nil { b.currentFlow = b.createReduceLabel(finallyLabel, normalExitLabel.Antecedents, b.currentFlow) } else { b.currentFlow = b.unreachableFlow } } } else { b.currentFlow = b.finishFlowLabel(normalExitLabel) } } func (b *Binder) bindSwitchStatement(node *ast.Node) { stmt := node.AsSwitchStatement() postSwitchLabel := b.createBranchLabel() b.bind(stmt.Expression) saveBreakTarget := b.currentBreakTarget savePreSwitchCaseFlow := b.preSwitchCaseFlow b.currentBreakTarget = postSwitchLabel b.preSwitchCaseFlow = b.currentFlow b.bind(stmt.CaseBlock) b.addAntecedent(postSwitchLabel, b.currentFlow) hasDefault := core.Some(stmt.CaseBlock.AsCaseBlock().Clauses.Nodes, func(c *ast.Node) bool { return c.Kind == ast.KindDefaultClause }) if !hasDefault { b.addAntecedent(postSwitchLabel, b.createFlowSwitchClause(b.preSwitchCaseFlow, node, 0, 0)) } b.currentBreakTarget = saveBreakTarget b.preSwitchCaseFlow = savePreSwitchCaseFlow b.currentFlow = b.finishFlowLabel(postSwitchLabel) } func (b *Binder) bindCaseBlock(node *ast.Node) { switchStatement := node.Parent clauses := node.AsCaseBlock().Clauses.Nodes isNarrowingSwitch := switchStatement.Expression().Kind == ast.KindTrueKeyword || isNarrowingExpression(switchStatement.Expression()) var fallthroughFlow *ast.FlowNode = b.unreachableFlow for i := 0; i < len(clauses); i++ { clauseStart := i for len(clauses[i].AsCaseOrDefaultClause().Statements.Nodes) == 0 && i+1 < len(clauses) { if fallthroughFlow == b.unreachableFlow { b.currentFlow = b.preSwitchCaseFlow } b.bind(clauses[i]) i++ } preCaseLabel := b.createBranchLabel() preCaseFlow := b.preSwitchCaseFlow if isNarrowingSwitch { preCaseFlow = b.createFlowSwitchClause(b.preSwitchCaseFlow, switchStatement, clauseStart, i+1) } b.addAntecedent(preCaseLabel, preCaseFlow) b.addAntecedent(preCaseLabel, fallthroughFlow) b.currentFlow = b.finishFlowLabel(preCaseLabel) clause := clauses[i] b.bind(clause) fallthroughFlow = b.currentFlow if b.currentFlow.Flags&ast.FlowFlagsUnreachable == 0 && i != len(clauses)-1 { clause.AsCaseOrDefaultClause().FallthroughFlowNode = b.currentFlow } } } func (b *Binder) bindCaseOrDefaultClause(node *ast.Node) { clause := node.AsCaseOrDefaultClause() if clause.Expression != nil { saveCurrentFlow := b.currentFlow b.currentFlow = b.preSwitchCaseFlow b.bind(clause.Expression) b.currentFlow = saveCurrentFlow } b.bindEach(clause.Statements.Nodes) } func (b *Binder) bindExpressionStatement(node *ast.Node) { stmt := node.AsExpressionStatement() b.bind(stmt.Expression) b.maybeBindExpressionFlowIfCall(stmt.Expression) } func (b *Binder) maybeBindExpressionFlowIfCall(node *ast.Node) { // A top level or comma expression call expression with a dotted function name and at least one argument // is potentially an assertion and is therefore included in the control flow. if ast.IsCallExpression(node) { if node.Expression().Kind != ast.KindSuperKeyword && ast.IsDottedName(node.Expression()) { b.currentFlow = b.createFlowCall(b.currentFlow, node) } } } func (b *Binder) bindLabeledStatement(node *ast.Node) { stmt := node.AsLabeledStatement() postStatementLabel := b.createBranchLabel() b.activeLabelList = &ActiveLabel{ next: b.activeLabelList, name: stmt.Label.AsIdentifier().Text, breakTarget: postStatementLabel, continueTarget: nil, referenced: false, } b.bind(stmt.Label) b.bind(stmt.Statement) if !b.activeLabelList.referenced && b.options().AllowUnusedLabels != core.TSTrue { b.errorOrSuggestionOnNode(unusedLabelIsError(b.options()), stmt.Label, diagnostics.Unused_label) } b.activeLabelList = b.activeLabelList.next b.addAntecedent(postStatementLabel, b.currentFlow) b.currentFlow = b.finishFlowLabel(postStatementLabel) } func (b *Binder) bindPrefixUnaryExpressionFlow(node *ast.Node) { expr := node.AsPrefixUnaryExpression() if expr.Operator == ast.KindExclamationToken { saveTrueTarget := b.currentTrueTarget b.currentTrueTarget = b.currentFalseTarget b.currentFalseTarget = saveTrueTarget b.bindEachChild(node) b.currentFalseTarget = b.currentTrueTarget b.currentTrueTarget = saveTrueTarget } else { b.bindEachChild(node) if expr.Operator == ast.KindPlusPlusToken || expr.Operator == ast.KindMinusMinusToken { b.bindAssignmentTargetFlow(expr.Operand) } } } func (b *Binder) bindPostfixUnaryExpressionFlow(node *ast.Node) { expr := node.AsPostfixUnaryExpression() b.bindEachChild(node) if expr.Operator == ast.KindPlusPlusToken || expr.Operator == ast.KindMinusMinusToken { b.bindAssignmentTargetFlow(expr.Operand) } } func (b *Binder) bindDestructuringAssignmentFlow(node *ast.Node) { expr := node.AsBinaryExpression() if b.inAssignmentPattern { b.inAssignmentPattern = false b.bind(expr.OperatorToken) b.bind(expr.Right) b.inAssignmentPattern = true b.bind(expr.Left) b.bind(expr.Type) } else { b.inAssignmentPattern = true b.bind(expr.Left) b.bind(expr.Type) b.inAssignmentPattern = false b.bind(expr.OperatorToken) b.bind(expr.Right) } b.bindAssignmentTargetFlow(expr.Left) } func (b *Binder) bindBinaryExpressionFlow(node *ast.Node) { expr := node.AsBinaryExpression() operator := expr.OperatorToken.Kind if ast.IsLogicalOrCoalescingBinaryOperator(operator) || ast.IsLogicalOrCoalescingAssignmentOperator(operator) { if isTopLevelLogicalExpression(node) { postExpressionLabel := b.createBranchLabel() saveCurrentFlow := b.currentFlow saveHasFlowEffects := b.hasFlowEffects b.hasFlowEffects = false b.bindLogicalLikeExpression(node, postExpressionLabel, postExpressionLabel) if b.hasFlowEffects { b.currentFlow = b.finishFlowLabel(postExpressionLabel) } else { b.currentFlow = saveCurrentFlow } b.hasFlowEffects = b.hasFlowEffects || saveHasFlowEffects } else { b.bindLogicalLikeExpression(node, b.currentTrueTarget, b.currentFalseTarget) } } else { b.bind(expr.Left) b.bind(expr.Type) if operator == ast.KindCommaToken { b.maybeBindExpressionFlowIfCall(expr.Left) } b.bind(expr.OperatorToken) b.bind(expr.Right) if operator == ast.KindCommaToken { b.maybeBindExpressionFlowIfCall(expr.Right) } if ast.IsAssignmentOperator(operator) && !ast.IsAssignmentTarget(node) { b.bindAssignmentTargetFlow(expr.Left) if operator == ast.KindEqualsToken && expr.Left.Kind == ast.KindElementAccessExpression { elementAccess := expr.Left.AsElementAccessExpression() if isNarrowableOperand(elementAccess.Expression) { b.currentFlow = b.createFlowMutation(ast.FlowFlagsArrayMutation, b.currentFlow, node) } } } } } func (b *Binder) bindLogicalLikeExpression(node *ast.Node, trueTarget *ast.FlowLabel, falseTarget *ast.FlowLabel) { expr := node.AsBinaryExpression() preRightLabel := b.createBranchLabel() if expr.OperatorToken.Kind == ast.KindAmpersandAmpersandToken || expr.OperatorToken.Kind == ast.KindAmpersandAmpersandEqualsToken { b.bindCondition(expr.Left, preRightLabel, falseTarget) } else { b.bindCondition(expr.Left, trueTarget, preRightLabel) } b.currentFlow = b.finishFlowLabel(preRightLabel) b.bind(expr.OperatorToken) if ast.IsLogicalOrCoalescingAssignmentOperator(expr.OperatorToken.Kind) { b.doWithConditionalBranches((*Binder).bind, expr.Right, trueTarget, falseTarget) b.bindAssignmentTargetFlow(expr.Left) b.addAntecedent(trueTarget, b.createFlowCondition(ast.FlowFlagsTrueCondition, b.currentFlow, node)) b.addAntecedent(falseTarget, b.createFlowCondition(ast.FlowFlagsFalseCondition, b.currentFlow, node)) } else { b.bindCondition(expr.Right, trueTarget, falseTarget) } } func (b *Binder) bindDeleteExpressionFlow(node *ast.Node) { expr := node.AsDeleteExpression() b.bindEachChild(node) if expr.Expression.Kind == ast.KindPropertyAccessExpression { b.bindAssignmentTargetFlow(expr.Expression) } } func (b *Binder) bindConditionalExpressionFlow(node *ast.Node) { expr := node.AsConditionalExpression() trueLabel := b.createBranchLabel() falseLabel := b.createBranchLabel() postExpressionLabel := b.createBranchLabel() saveCurrentFlow := b.currentFlow saveHasFlowEffects := b.hasFlowEffects b.hasFlowEffects = false b.bindCondition(expr.Condition, trueLabel, falseLabel) b.currentFlow = b.finishFlowLabel(trueLabel) b.bind(expr.QuestionToken) b.bind(expr.WhenTrue) b.addAntecedent(postExpressionLabel, b.currentFlow) b.currentFlow = b.finishFlowLabel(falseLabel) b.bind(expr.ColonToken) b.bind(expr.WhenFalse) b.addAntecedent(postExpressionLabel, b.currentFlow) if b.hasFlowEffects { b.currentFlow = b.finishFlowLabel(postExpressionLabel) } else { b.currentFlow = saveCurrentFlow } b.hasFlowEffects = b.hasFlowEffects || saveHasFlowEffects } func (b *Binder) bindVariableDeclarationFlow(node *ast.Node) { b.bindEachChild(node) if node.AsVariableDeclaration().Initializer != nil || ast.IsForInOrOfStatement(node.Parent.Parent) { b.bindInitializedVariableFlow(node) } } func (b *Binder) bindInitializedVariableFlow(node *ast.Node) { var name *ast.Node switch node.Kind { case ast.KindVariableDeclaration: name = node.AsVariableDeclaration().Name() case ast.KindBindingElement: name = node.AsBindingElement().Name() } if name != nil && ast.IsBindingPattern(name) { for _, child := range name.AsBindingPattern().Elements.Nodes { b.bindInitializedVariableFlow(child) } } else { b.currentFlow = b.createFlowMutation(ast.FlowFlagsAssignment, b.currentFlow, node) } } func (b *Binder) bindAccessExpressionFlow(node *ast.Node) { if ast.IsOptionalChain(node) { b.bindOptionalChainFlow(node) } else { b.bindEachChild(node) } } func (b *Binder) bindOptionalChainFlow(node *ast.Node) { if isTopLevelLogicalExpression(node) { postExpressionLabel := b.createBranchLabel() saveCurrentFlow := b.currentFlow saveHasFlowEffects := b.hasFlowEffects b.bindOptionalChain(node, postExpressionLabel, postExpressionLabel) if b.hasFlowEffects { b.currentFlow = b.finishFlowLabel(postExpressionLabel) } else { b.currentFlow = saveCurrentFlow } b.hasFlowEffects = b.hasFlowEffects || saveHasFlowEffects } else { b.bindOptionalChain(node, b.currentTrueTarget, b.currentFalseTarget) } } func (b *Binder) bindOptionalChain(node *ast.Node, trueTarget *ast.FlowLabel, falseTarget *ast.FlowLabel) { // For an optional chain, we emulate the behavior of a logical expression: // // a?.b -> a && a.b // a?.b.c -> a && a.b.c // a?.b?.c -> a && a.b && a.b.c // a?.[x = 1] -> a && a[x = 1] // // To do this we descend through the chain until we reach the root of a chain (the expression with a `?.`) // and build it's CFA graph as if it were the first condition (`a && ...`). Then we bind the rest // of the node as part of the "true" branch, and continue to do so as we ascend back up to the outermost // chain node. We then treat the entire node as the right side of the expression. var preChainLabel *ast.FlowLabel if ast.IsOptionalChainRoot(node) { preChainLabel = b.createBranchLabel() } b.bindOptionalExpression(node.Expression(), core.IfElse(preChainLabel != nil, preChainLabel, trueTarget), falseTarget) if preChainLabel != nil { b.currentFlow = b.finishFlowLabel(preChainLabel) } b.doWithConditionalBranches((*Binder).bindOptionalChainRest, node, trueTarget, falseTarget) if ast.IsOutermostOptionalChain(node) { b.addAntecedent(trueTarget, b.createFlowCondition(ast.FlowFlagsTrueCondition, b.currentFlow, node)) b.addAntecedent(falseTarget, b.createFlowCondition(ast.FlowFlagsFalseCondition, b.currentFlow, node)) } } func (b *Binder) bindOptionalExpression(node *ast.Node, trueTarget *ast.FlowLabel, falseTarget *ast.FlowLabel) { b.doWithConditionalBranches((*Binder).bind, node, trueTarget, falseTarget) if !ast.IsOptionalChain(node) || ast.IsOutermostOptionalChain(node) { b.addAntecedent(trueTarget, b.createFlowCondition(ast.FlowFlagsTrueCondition, b.currentFlow, node)) b.addAntecedent(falseTarget, b.createFlowCondition(ast.FlowFlagsFalseCondition, b.currentFlow, node)) } } func (b *Binder) bindOptionalChainRest(node *ast.Node) bool { switch node.Kind { case ast.KindPropertyAccessExpression: b.bind(node.AsPropertyAccessExpression().QuestionDotToken) b.bind(node.AsPropertyAccessExpression().Name()) case ast.KindElementAccessExpression: b.bind(node.AsElementAccessExpression().QuestionDotToken) b.bind(node.AsElementAccessExpression().ArgumentExpression) case ast.KindCallExpression: b.bind(node.AsCallExpression().QuestionDotToken) b.bindNodeList(node.AsCallExpression().TypeArguments) b.bindEach(node.AsCallExpression().Arguments.Nodes) } return false } func (b *Binder) bindCallExpressionFlow(node *ast.Node) { call := node.AsCallExpression() if ast.IsOptionalChain(node) { b.bindOptionalChainFlow(node) } else { // If the target of the call expression is a function expression or arrow function we have // an immediately invoked function expression (IIFE). Initialize the flowNode property to // the current control flow (which includes evaluation of the IIFE arguments). expr := ast.SkipParentheses(call.Expression) if expr.Kind == ast.KindFunctionExpression || expr.Kind == ast.KindArrowFunction { b.bindNodeList(call.TypeArguments) b.bindEach(call.Arguments.Nodes) b.bind(call.Expression) } else { b.bindEachChild(node) if call.Expression.Kind == ast.KindSuperKeyword { b.currentFlow = b.createFlowCall(b.currentFlow, node) } } } if ast.IsPropertyAccessExpression(call.Expression) { access := call.Expression.AsPropertyAccessExpression() if ast.IsIdentifier(access.Name()) && isNarrowableOperand(access.Expression) && ast.IsPushOrUnshiftIdentifier(access.Name()) { b.currentFlow = b.createFlowMutation(ast.FlowFlagsArrayMutation, b.currentFlow, node) } } } func (b *Binder) bindNonNullExpressionFlow(node *ast.Node) { if ast.IsOptionalChain(node) { b.bindOptionalChainFlow(node) } else { b.bindEachChild(node) } } func (b *Binder) bindBindingElementFlow(node *ast.Node) { // When evaluating a binding pattern, the initializer is evaluated before the binding pattern, per: // - https://tc39.es/ecma262/#sec-destructuring-binding-patterns-runtime-semantics-iteratorbindinginitialization // - `BindingElement: BindingPattern Initializer?` // - https://tc39.es/ecma262/#sec-runtime-semantics-keyedbindinginitialization // - `BindingElement: BindingPattern Initializer?` elem := node.AsBindingElement() b.bind(elem.DotDotDotToken) b.bind(elem.PropertyName) b.bindInitializer(elem.Initializer) b.bind(elem.Name()) } func (b *Binder) bindParameterFlow(node *ast.Node) { param := node.AsParameterDeclaration() b.bindModifiers(param.Modifiers()) b.bind(param.DotDotDotToken) b.bind(param.QuestionToken) b.bind(param.Type) b.bindInitializer(param.Initializer) b.bind(param.Name()) } // a BindingElement/Parameter does not have side effects if initializers are not evaluated and used. (see GH#49759) func (b *Binder) bindInitializer(node *ast.Node) { if node == nil { return } entryFlow := b.currentFlow b.bind(node) if entryFlow == b.unreachableFlow || entryFlow == b.currentFlow { return } exitFlow := b.createBranchLabel() b.addAntecedent(exitFlow, entryFlow) b.addAntecedent(exitFlow, b.currentFlow) b.currentFlow = b.finishFlowLabel(exitFlow) } func isEnumDeclarationWithPreservedEmit(node *ast.Node, options core.SourceFileAffectingCompilerOptions) bool { return node.Kind == ast.KindEnumDeclaration && (!ast.IsEnumConst(node) || options.ShouldPreserveConstEnums) } func setFlowNode(node *ast.Node, flowNode *ast.FlowNode) { data := node.FlowNodeData() if data != nil { data.FlowNode = flowNode } } func setReturnFlowNode(node *ast.Node, returnFlowNode *ast.FlowNode) { switch node.Kind { case ast.KindConstructor: node.AsConstructorDeclaration().ReturnFlowNode = returnFlowNode case ast.KindFunctionDeclaration: node.AsFunctionDeclaration().ReturnFlowNode = returnFlowNode case ast.KindFunctionExpression: node.AsFunctionExpression().ReturnFlowNode = returnFlowNode case ast.KindClassStaticBlockDeclaration: node.AsClassStaticBlockDeclaration().ReturnFlowNode = returnFlowNode } } func isGeneratorFunctionExpression(node *ast.Node) bool { return ast.IsFunctionExpression(node) && node.AsFunctionExpression().AsteriskToken != nil } func (b *Binder) addToContainerChain(next *ast.Node) { if b.lastContainer != nil { b.lastContainer.LocalsContainerData().NextContainer = next } b.lastContainer = next } func (b *Binder) addDeclarationToSymbol(symbol *ast.Symbol, node *ast.Node, symbolFlags ast.SymbolFlags) { symbol.Flags |= symbolFlags node.DeclarationData().Symbol = symbol if symbol.Declarations == nil { symbol.Declarations = b.newSingleDeclaration(node) } else { symbol.Declarations = core.AppendIfUnique(symbol.Declarations, node) } // On merge of const enum module with class or function, reset const enum only flag (namespaces will already recalculate) if symbol.Flags&ast.SymbolFlagsConstEnumOnlyModule != 0 && symbol.Flags&(ast.SymbolFlagsFunction|ast.SymbolFlagsClass|ast.SymbolFlagsRegularEnum) != 0 { symbol.Flags &^= ast.SymbolFlagsConstEnumOnlyModule } if symbolFlags&ast.SymbolFlagsValue != 0 { SetValueDeclaration(symbol, node) } } func SetValueDeclaration(symbol *ast.Symbol, node *ast.Node) { valueDeclaration := symbol.ValueDeclaration if valueDeclaration == nil || isAssignmentDeclaration(valueDeclaration) && !isAssignmentDeclaration(node) || valueDeclaration.Kind != node.Kind && isEffectiveModuleDeclaration(valueDeclaration) { // Non-assignment declarations take precedence over assignment declarations and // non-namespace declarations take precedence over namespace declarations. symbol.ValueDeclaration = node } } /** * Declares a Symbol for the node and adds it to symbols. Reports errors for conflicting identifier names. * @param symbolTable - The symbol table which node will be added to. * @param parent - node's parent declaration. * @param node - The declaration to be added to the symbol table * @param includes - The SymbolFlags that node has in addition to its declaration type (eg: export, ambient, etc.) * @param excludes - The flags which node cannot be declared alongside in a symbol table. Used to report forbidden declarations. */ func GetContainerFlags(node *ast.Node) ContainerFlags { switch node.Kind { case ast.KindClassExpression, ast.KindClassDeclaration, ast.KindEnumDeclaration, ast.KindObjectLiteralExpression, ast.KindTypeLiteral, ast.KindJsxAttributes: return ContainerFlagsIsContainer case ast.KindInterfaceDeclaration: return ContainerFlagsIsContainer | ContainerFlagsIsInterface case ast.KindModuleDeclaration, ast.KindTypeAliasDeclaration, ast.KindJSTypeAliasDeclaration, ast.KindMappedType, ast.KindIndexSignature: return ContainerFlagsIsContainer | ContainerFlagsHasLocals case ast.KindSourceFile: return ContainerFlagsIsContainer | ContainerFlagsIsControlFlowContainer | ContainerFlagsHasLocals case ast.KindGetAccessor, ast.KindSetAccessor, ast.KindMethodDeclaration: if ast.IsObjectLiteralOrClassExpressionMethodOrAccessor(node) { return ContainerFlagsIsContainer | ContainerFlagsIsControlFlowContainer | ContainerFlagsHasLocals | ContainerFlagsIsFunctionLike | ContainerFlagsIsObjectLiteralOrClassExpressionMethodOrAccessor | ContainerFlagsIsThisContainer } fallthrough case ast.KindConstructor, ast.KindClassStaticBlockDeclaration: return ContainerFlagsIsContainer | ContainerFlagsIsControlFlowContainer | ContainerFlagsHasLocals | ContainerFlagsIsFunctionLike | ContainerFlagsIsThisContainer case ast.KindMethodSignature, ast.KindCallSignature, ast.KindFunctionType, ast.KindConstructSignature, ast.KindConstructorType: return ContainerFlagsIsContainer | ContainerFlagsIsControlFlowContainer | ContainerFlagsHasLocals | ContainerFlagsIsFunctionLike case ast.KindFunctionDeclaration: return ContainerFlagsIsContainer | ContainerFlagsIsControlFlowContainer | ContainerFlagsHasLocals | ContainerFlagsIsFunctionLike | ContainerFlagsIsThisContainer case ast.KindFunctionExpression: return ContainerFlagsIsContainer | ContainerFlagsIsControlFlowContainer | ContainerFlagsHasLocals | ContainerFlagsIsFunctionLike | ContainerFlagsIsFunctionExpression | ContainerFlagsIsThisContainer case ast.KindArrowFunction: return ContainerFlagsIsContainer | ContainerFlagsIsControlFlowContainer | ContainerFlagsHasLocals | ContainerFlagsIsFunctionLike | ContainerFlagsIsFunctionExpression case ast.KindModuleBlock: return ContainerFlagsIsControlFlowContainer case ast.KindPropertyDeclaration: if node.AsPropertyDeclaration().Initializer != nil { return ContainerFlagsIsControlFlowContainer | ContainerFlagsIsThisContainer } else { return ContainerFlagsNone } case ast.KindCatchClause, ast.KindForStatement, ast.KindForInStatement, ast.KindForOfStatement, ast.KindCaseBlock: return ContainerFlagsIsBlockScopedContainer | ContainerFlagsHasLocals case ast.KindBlock: if ast.IsFunctionLike(node.Parent) || ast.IsClassStaticBlockDeclaration(node.Parent) { return ContainerFlagsNone } else { return ContainerFlagsIsBlockScopedContainer | ContainerFlagsHasLocals } } return ContainerFlagsNone } func isNarrowingExpression(expr *ast.Node) bool { switch expr.Kind { case ast.KindIdentifier, ast.KindThisKeyword: return true case ast.KindPropertyAccessExpression, ast.KindElementAccessExpression: return containsNarrowableReference(expr) case ast.KindCallExpression: return hasNarrowableArgument(expr) case ast.KindParenthesizedExpression: return isNarrowingExpression(expr.AsParenthesizedExpression().Expression) case ast.KindNonNullExpression: return isNarrowingExpression(expr.AsNonNullExpression().Expression) case ast.KindBinaryExpression: return isNarrowingBinaryExpression(expr.AsBinaryExpression()) case ast.KindPrefixUnaryExpression: return expr.AsPrefixUnaryExpression().Operator == ast.KindExclamationToken && isNarrowingExpression(expr.AsPrefixUnaryExpression().Operand) case ast.KindTypeOfExpression: return isNarrowingExpression(expr.AsTypeOfExpression().Expression) } return false } func containsNarrowableReference(expr *ast.Node) bool { if isNarrowableReference(expr) { return true } if expr.Flags&ast.NodeFlagsOptionalChain != 0 { switch expr.Kind { case ast.KindPropertyAccessExpression: return containsNarrowableReference(expr.AsPropertyAccessExpression().Expression) case ast.KindElementAccessExpression: return containsNarrowableReference(expr.AsElementAccessExpression().Expression) case ast.KindCallExpression: return containsNarrowableReference(expr.AsCallExpression().Expression) case ast.KindNonNullExpression: return containsNarrowableReference(expr.AsNonNullExpression().Expression) } } return false } func isNarrowableReference(node *ast.Node) bool { switch node.Kind { case ast.KindIdentifier, ast.KindThisKeyword, ast.KindSuperKeyword, ast.KindMetaProperty: return true case ast.KindPropertyAccessExpression: return isNarrowableReference(node.AsPropertyAccessExpression().Expression) case ast.KindParenthesizedExpression: return isNarrowableReference(node.AsParenthesizedExpression().Expression) case ast.KindNonNullExpression: return isNarrowableReference(node.AsNonNullExpression().Expression) case ast.KindElementAccessExpression: expr := node.AsElementAccessExpression() return ast.IsStringOrNumericLiteralLike(expr.ArgumentExpression) || ast.IsEntityNameExpression(expr.ArgumentExpression) && isNarrowableReference(expr.Expression) case ast.KindBinaryExpression: expr := node.AsBinaryExpression() return expr.OperatorToken.Kind == ast.KindCommaToken && isNarrowableReference(expr.Right) || ast.IsAssignmentOperator(expr.OperatorToken.Kind) && ast.IsLeftHandSideExpression(expr.Left) } return false } func hasNarrowableArgument(expr *ast.Node) bool { call := expr.AsCallExpression() for _, argument := range call.Arguments.Nodes { if containsNarrowableReference(argument) { return true } } if ast.IsPropertyAccessExpression(call.Expression) { if containsNarrowableReference(call.Expression.AsPropertyAccessExpression().Expression) { return true } } return false } func isNarrowingBinaryExpression(expr *ast.BinaryExpression) bool { switch expr.OperatorToken.Kind { case ast.KindEqualsToken, ast.KindBarBarEqualsToken, ast.KindAmpersandAmpersandEqualsToken, ast.KindQuestionQuestionEqualsToken: return containsNarrowableReference(expr.Left) case ast.KindEqualsEqualsToken, ast.KindExclamationEqualsToken, ast.KindEqualsEqualsEqualsToken, ast.KindExclamationEqualsEqualsToken: left := ast.SkipParentheses(expr.Left) right := ast.SkipParentheses(expr.Right) return isNarrowableOperand(left) || isNarrowableOperand(right) || isNarrowingTypeOfOperands(right, left) || isNarrowingTypeOfOperands(left, right) || (ast.IsBooleanLiteral(right) && isNarrowingExpression(left) || ast.IsBooleanLiteral(left) && isNarrowingExpression(right)) case ast.KindInstanceOfKeyword: return isNarrowableOperand(expr.Left) case ast.KindInKeyword: return isNarrowingExpression(expr.Right) case ast.KindCommaToken: return isNarrowingExpression(expr.Right) } return false } func isNarrowableOperand(expr *ast.Node) bool { switch expr.Kind { case ast.KindParenthesizedExpression: return isNarrowableOperand(expr.AsParenthesizedExpression().Expression) case ast.KindBinaryExpression: binary := expr.AsBinaryExpression() switch binary.OperatorToken.Kind { case ast.KindEqualsToken: return isNarrowableOperand(binary.Left) case ast.KindCommaToken: return isNarrowableOperand(binary.Right) } } return containsNarrowableReference(expr) } func isNarrowingTypeOfOperands(expr1 *ast.Node, expr2 *ast.Node) bool { return ast.IsTypeOfExpression(expr1) && isNarrowableOperand(expr1.AsTypeOfExpression().Expression) && ast.IsStringLiteralLike(expr2) } func (b *Binder) errorOnNode(node *ast.Node, message *diagnostics.Message, args ...any) { b.addDiagnostic(b.createDiagnosticForNode(node, message, args...)) } func (b *Binder) errorOnFirstToken(node *ast.Node, message *diagnostics.Message, args ...any) { span := scanner.GetRangeOfTokenAtPosition(b.file, node.Pos()) b.addDiagnostic(ast.NewDiagnostic(b.file, span, message, args...)) } func (b *Binder) errorOrSuggestionOnNode(isError bool, node *ast.Node, message *diagnostics.Message) { b.errorOrSuggestionOnRange(isError, node, node, message) } func (b *Binder) errorOrSuggestionOnRange(isError bool, startNode *ast.Node, endNode *ast.Node, message *diagnostics.Message) { textRange := core.NewTextRange(scanner.GetRangeOfTokenAtPosition(b.file, startNode.Pos()).Pos(), endNode.End()) diagnostic := ast.NewDiagnostic(b.file, textRange, message) if isError { b.addDiagnostic(diagnostic) } else { diagnostic.SetCategory(diagnostics.CategorySuggestion) b.file.BindSuggestionDiagnostics = append(b.file.BindSuggestionDiagnostics, diagnostic) } } // Inside the binder, we may create a diagnostic for an as-yet unbound node (with potentially no parent pointers, implying no accessible source file) // If so, the node _must_ be in the current file (as that's the only way anything could have traversed to it to yield it as the error node) // This version of `createDiagnosticForNode` uses the binder's context to account for this, and always yields correct diagnostics even in these situations. func (b *Binder) createDiagnosticForNode(node *ast.Node, message *diagnostics.Message, args ...any) *ast.Diagnostic { return ast.NewDiagnostic(b.file, scanner.GetErrorRangeForNode(b.file, node), message, args...) } func (b *Binder) addDiagnostic(diagnostic *ast.Diagnostic) { b.file.SetBindDiagnostics(append(b.file.BindDiagnostics(), diagnostic)) } func isSignedNumericLiteral(node *ast.Node) bool { if node.Kind == ast.KindPrefixUnaryExpression { node := node.AsPrefixUnaryExpression() return (node.Operator == ast.KindPlusToken || node.Operator == ast.KindMinusToken) && ast.IsNumericLiteral(node.Operand) } return false } func getOptionalSymbolFlagForNode(node *ast.Node) ast.SymbolFlags { postfixToken := getPostfixTokenFromNode(node) return core.IfElse(postfixToken != nil && postfixToken.Kind == ast.KindQuestionToken, ast.SymbolFlagsOptional, ast.SymbolFlagsNone) } func getPostfixTokenFromNode(node *ast.Node) *ast.Node { switch node.Kind { case ast.KindPropertyDeclaration: return node.AsPropertyDeclaration().PostfixToken case ast.KindPropertySignature: return node.AsPropertySignatureDeclaration().PostfixToken case ast.KindMethodDeclaration: return node.AsMethodDeclaration().PostfixToken case ast.KindMethodSignature: return node.AsMethodSignatureDeclaration().PostfixToken } panic("Unhandled case in getPostfixTokenFromNode") } func isAsyncFunction(node *ast.Node) bool { switch node.Kind { case ast.KindFunctionDeclaration, ast.KindFunctionExpression, ast.KindArrowFunction, ast.KindMethodDeclaration: data := node.BodyData() return data.Body != nil && data.AsteriskToken == nil && ast.HasSyntacticModifier(node, ast.ModifierFlagsAsync) } return false } func isFunctionSymbol(symbol *ast.Symbol) bool { d := symbol.ValueDeclaration if d != nil { if ast.IsFunctionDeclaration(d) { return true } if ast.IsVariableDeclaration(d) { varDecl := d.AsVariableDeclaration() if varDecl.Initializer != nil { return ast.IsFunctionLike(varDecl.Initializer) } } } return false } func unreachableCodeIsError(options core.SourceFileAffectingCompilerOptions) bool { return options.AllowUnreachableCode == core.TSFalse } func unusedLabelIsError(options core.SourceFileAffectingCompilerOptions) bool { return options.AllowUnusedLabels == core.TSFalse } func isStatementCondition(node *ast.Node) bool { switch node.Parent.Kind { case ast.KindIfStatement: return node.Parent.AsIfStatement().Expression == node case ast.KindWhileStatement: return node.Parent.AsWhileStatement().Expression == node case ast.KindDoStatement: return node.Parent.AsDoStatement().Expression == node case ast.KindForStatement: return node.Parent.AsForStatement().Condition == node case ast.KindConditionalExpression: return node.Parent.AsConditionalExpression().Condition == node } return false } func isTopLevelLogicalExpression(node *ast.Node) bool { for ast.IsParenthesizedExpression(node.Parent) || ast.IsPrefixUnaryExpression(node.Parent) && node.Parent.AsPrefixUnaryExpression().Operator == ast.KindExclamationToken { node = node.Parent } return !isStatementCondition(node) && !ast.IsLogicalExpression(node.Parent) && !(ast.IsOptionalChain(node.Parent) && node.Parent.Expression() == node) } func isAssignmentDeclaration(decl *ast.Node) bool { return ast.IsBinaryExpression(decl) || ast.IsAccessExpression(decl) || ast.IsIdentifier(decl) || ast.IsCallExpression(decl) } func isEffectiveModuleDeclaration(node *ast.Node) bool { return ast.IsModuleDeclaration(node) || ast.IsIdentifier(node) }