package binder import ( "efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/diagnostics" ) type NameResolver struct { CompilerOptions *core.CompilerOptions GetSymbolOfDeclaration func(node *ast.Node) *ast.Symbol Error func(location *ast.Node, message *diagnostics.Message, args ...any) *ast.Diagnostic Globals ast.SymbolTable ArgumentsSymbol *ast.Symbol RequireSymbol *ast.Symbol GetModuleSymbol func(sourceFile *ast.Node) *ast.Symbol Lookup func(symbols ast.SymbolTable, name string, meaning ast.SymbolFlags) *ast.Symbol SymbolReferenced func(symbol *ast.Symbol, meaning ast.SymbolFlags) SetRequiresScopeChangeCache func(node *ast.Node, value core.Tristate) GetRequiresScopeChangeCache func(node *ast.Node) core.Tristate OnPropertyWithInvalidInitializer func(location *ast.Node, name string, declaration *ast.Node, result *ast.Symbol) bool OnFailedToResolveSymbol func(location *ast.Node, name string, meaning ast.SymbolFlags, nameNotFoundMessage *diagnostics.Message) OnSuccessfullyResolvedSymbol func(location *ast.Node, result *ast.Symbol, meaning ast.SymbolFlags, lastLocation *ast.Node, associatedDeclarationForContainingInitializerOrBindingName *ast.Node, withinDeferredContext bool) } func (r *NameResolver) Resolve(location *ast.Node, name string, meaning ast.SymbolFlags, nameNotFoundMessage *diagnostics.Message, isUse bool, excludeGlobals bool) *ast.Symbol { var result *ast.Symbol var lastLocation *ast.Node var lastSelfReferenceLocation *ast.Node var propertyWithInvalidInitializer *ast.Node var associatedDeclarationForContainingInitializerOrBindingName *ast.Node var withinDeferredContext bool var grandparent *ast.Node originalLocation := location // needed for did-you-mean error reporting, which gathers candidates starting from the original location nameIsConst := name == "const" loop: for location != nil { if nameIsConst && ast.IsConstAssertion(location) { // `const` in an `as const` has no symbol, but issues no error because there is no *actual* lookup of the type // (it refers to the constant type of the expression instead) return nil } if ast.IsModuleOrEnumDeclaration(location) && lastLocation != nil && location.Name() == lastLocation { // If lastLocation is the name of a namespace or enum, skip the parent since it will have is own locals that could // conflict. lastLocation = location location = location.Parent } locals := location.Locals() // Locals of a source file are not in scope (because they get merged into the global symbol table) if locals != nil && !ast.IsGlobalSourceFile(location) { result = r.lookup(locals, name, meaning) if result != nil { useResult := true if ast.IsFunctionLike(location) && lastLocation != nil && lastLocation != location.Body() { // symbol lookup restrictions for function-like declarations // - Type parameters of a function are in scope in the entire function declaration, including the parameter // list and return type. However, local types are only in scope in the function body. // - parameters are only in the scope of function body if meaning&result.Flags&ast.SymbolFlagsType != 0 { useResult = result.Flags&ast.SymbolFlagsTypeParameter != 0 && (lastLocation == location.Type() || ast.IsParameterLike(lastLocation)) } if meaning&result.Flags&ast.SymbolFlagsVariable != 0 { // expression inside parameter will lookup as normal variable scope when targeting es2015+ if r.useOuterVariableScopeInParameter(result, location, lastLocation) { useResult = false } else if result.Flags&ast.SymbolFlagsFunctionScopedVariable != 0 { // parameters are visible only inside function body, parameter list and return type // technically for parameter list case here we might mix parameters and variables declared in function, // however it is detected separately when checking initializers of parameters // to make sure that they reference no variables declared after them. useResult = lastLocation.Kind == ast.KindParameter || lastLocation.Flags&ast.NodeFlagsSynthesized != 0 || lastLocation == location.Type() && ast.FindAncestor(result.ValueDeclaration, ast.IsParameter) != nil } } } else if location.Kind == ast.KindConditionalType { // A type parameter declared using 'infer T' in a conditional type is visible only in // the true branch of the conditional type. useResult = lastLocation == location.AsConditionalTypeNode().TrueType } if useResult { break loop } result = nil } } withinDeferredContext = withinDeferredContext || getIsDeferredContext(location, lastLocation) switch location.Kind { case ast.KindSourceFile: if !ast.IsExternalOrCommonJSModule(location.AsSourceFile()) { break } fallthrough case ast.KindModuleDeclaration: moduleExports := r.getSymbolOfDeclaration(location).Exports if ast.IsSourceFile(location) || (ast.IsModuleDeclaration(location) && location.Flags&ast.NodeFlagsAmbient != 0 && !ast.IsGlobalScopeAugmentation(location)) { // It's an external module. First see if the module has an export default and if the local // name of that export default matches. result = moduleExports[ast.InternalSymbolNameDefault] if result != nil { localSymbol := GetLocalSymbolForExportDefault(result) if localSymbol != nil && result.Flags&meaning != 0 && localSymbol.Name == name { break loop } result = nil } // Because of module/namespace merging, a module's exports are in scope, // yet we never want to treat an export specifier as putting a member in scope. // Therefore, if the name we find is purely an export specifier, it is not actually considered in scope. // Two things to note about this: // 1. We have to check this without calling getSymbol. The problem with calling getSymbol // on an export specifier is that it might find the export specifier itself, and try to // resolve it as an alias. This will cause the checker to consider the export specifier // a circular alias reference when it might not be. // 2. We check === SymbolFlags.Alias in order to check that the symbol is *purely* // an alias. If we used &, we'd be throwing out symbols that have non alias aspects, // which is not the desired behavior. moduleExport := moduleExports[name] if moduleExport != nil && moduleExport.Flags == ast.SymbolFlagsAlias && (ast.GetDeclarationOfKind(moduleExport, ast.KindExportSpecifier) != nil || ast.GetDeclarationOfKind(moduleExport, ast.KindNamespaceExport) != nil) { break } } if name != ast.InternalSymbolNameDefault { result = r.lookup(moduleExports, name, meaning&ast.SymbolFlagsModuleMember) if result != nil { break loop } } case ast.KindEnumDeclaration: result = r.lookup(r.getSymbolOfDeclaration(location).Exports, name, meaning&ast.SymbolFlagsEnumMember) if result != nil { if nameNotFoundMessage != nil && r.CompilerOptions.GetIsolatedModules() && location.Flags&ast.NodeFlagsAmbient == 0 && ast.GetSourceFileOfNode(location) != ast.GetSourceFileOfNode(result.ValueDeclaration) { isolatedModulesLikeFlagName := core.IfElse(r.CompilerOptions.VerbatimModuleSyntax == core.TSTrue, "verbatimModuleSyntax", "isolatedModules") r.error(originalLocation, diagnostics.Cannot_access_0_from_another_file_without_qualification_when_1_is_enabled_Use_2_instead, name, isolatedModulesLikeFlagName, r.getSymbolOfDeclaration(location).Name+"."+name) } break loop } case ast.KindPropertyDeclaration: if !ast.IsStatic(location) { ctor := ast.FindConstructorDeclaration(location.Parent) if ctor != nil && ctor.Locals() != nil { if r.lookup(ctor.Locals(), name, meaning&ast.SymbolFlagsValue) != nil { // Remember the property node, it will be used later to report appropriate error propertyWithInvalidInitializer = location } } } case ast.KindClassDeclaration, ast.KindClassExpression, ast.KindInterfaceDeclaration: result = r.lookup(r.getSymbolOfDeclaration(location).Members, name, meaning&ast.SymbolFlagsType) if result != nil { if !isTypeParameterSymbolDeclaredInContainer(result, location) { // ignore type parameters not declared in this container result = nil break } if lastLocation != nil && ast.IsStatic(lastLocation) { // TypeScript 1.0 spec (April 2014): 3.4.1 // The scope of a type parameter extends over the entire declaration with which the type // parameter list is associated, with the exception of static member declarations in classes. if nameNotFoundMessage != nil { r.error(originalLocation, diagnostics.Static_members_cannot_reference_class_type_parameters) } return nil } break loop } if ast.IsClassExpression(location) && meaning&ast.SymbolFlagsClass != 0 { className := location.Name() if className != nil && name == className.Text() { result = location.Symbol() break loop } } case ast.KindExpressionWithTypeArguments: if lastLocation == location.AsExpressionWithTypeArguments().Expression && ast.IsHeritageClause(location.Parent) && location.Parent.AsHeritageClause().Token == ast.KindExtendsKeyword { container := location.Parent.Parent if ast.IsClassLike(container) { result = r.lookup(r.getSymbolOfDeclaration(container).Members, name, meaning&ast.SymbolFlagsType) if result != nil { if nameNotFoundMessage != nil { r.error(originalLocation, diagnostics.Base_class_expressions_cannot_reference_class_type_parameters) } return nil } } } // It is not legal to reference a class's own type parameters from a computed property name that // belongs to the class. For example: // // function foo() { return '' } // class C { // <-- Class's own type parameter T // [foo()]() { } // <-- Reference to T from class's own computed property // } case ast.KindComputedPropertyName: grandparent = location.Parent.Parent if ast.IsClassLike(grandparent) || ast.IsInterfaceDeclaration(grandparent) { // A reference to this grandparent's type parameters would be an error result = r.lookup(r.getSymbolOfDeclaration(grandparent).Members, name, meaning&ast.SymbolFlagsType) if result != nil { if nameNotFoundMessage != nil { r.error(originalLocation, diagnostics.A_computed_property_name_cannot_reference_a_type_parameter_from_its_containing_type) } return nil } } case ast.KindArrowFunction: // when targeting ES6 or higher there is no 'arguments' in an arrow function // for lower compile targets the resolved symbol is used to emit an error if r.CompilerOptions.GetEmitScriptTarget() >= core.ScriptTargetES2015 { break } fallthrough case ast.KindMethodDeclaration, ast.KindConstructor, ast.KindGetAccessor, ast.KindSetAccessor, ast.KindFunctionDeclaration: if meaning&ast.SymbolFlagsVariable != 0 && name == "arguments" { result = r.argumentsSymbol() break loop } case ast.KindFunctionExpression: if meaning&ast.SymbolFlagsVariable != 0 && name == "arguments" { result = r.argumentsSymbol() break loop } if meaning&ast.SymbolFlagsFunction != 0 { functionName := location.AsFunctionExpression().Name() if functionName != nil && name == functionName.AsIdentifier().Text { result = location.AsFunctionExpression().Symbol break loop } } case ast.KindDecorator: // Decorators are resolved at the class declaration. Resolving at the parameter // or member would result in looking up locals in the method. // // function y() {} // class C { // method(@y x, y) {} // <-- decorator y should be resolved at the class declaration, not the parameter. // } // if location.Parent != nil && location.Parent.Kind == ast.KindParameter { location = location.Parent } // function y() {} // class C { // @y method(x, y) {} // <-- decorator y should be resolved at the class declaration, not the method. // } // // class Decorators are resolved outside of the class to avoid referencing type parameters of that class. // // type T = number; // declare function y(x: T): any; // @param(1 as T) // <-- T should resolve to the type alias outside of class C // class C {} if location.Parent != nil && (ast.IsClassElement(location.Parent) || location.Parent.Kind == ast.KindClassDeclaration) { location = location.Parent } case ast.KindParameter: parameterDeclaration := location.AsParameterDeclaration() if lastLocation != nil && (lastLocation == parameterDeclaration.Initializer || lastLocation == parameterDeclaration.Name() && ast.IsBindingPattern(lastLocation)) { if associatedDeclarationForContainingInitializerOrBindingName == nil { associatedDeclarationForContainingInitializerOrBindingName = location } } case ast.KindBindingElement: bindingElement := location.AsBindingElement() if lastLocation != nil && (lastLocation == bindingElement.Initializer || lastLocation == bindingElement.Name() && ast.IsBindingPattern(lastLocation)) { if ast.IsPartOfParameterDeclaration(location) && associatedDeclarationForContainingInitializerOrBindingName == nil { associatedDeclarationForContainingInitializerOrBindingName = location } } case ast.KindInferType: if meaning&ast.SymbolFlagsTypeParameter != 0 { parameterName := location.AsInferTypeNode().TypeParameter.AsTypeParameter().Name() if parameterName != nil && name == parameterName.AsIdentifier().Text { result = location.AsInferTypeNode().TypeParameter.AsTypeParameter().Symbol break loop } } case ast.KindExportSpecifier: exportSpecifier := location.AsExportSpecifier() if lastLocation != nil && lastLocation == exportSpecifier.PropertyName && location.Parent.Parent.AsExportDeclaration().ModuleSpecifier != nil { location = location.Parent.Parent.Parent } } if isSelfReferenceLocation(location, lastLocation) { lastSelfReferenceLocation = location } lastLocation = location location = location.Parent } // We just climbed up parents looking for the name, meaning that we started in a descendant node of `lastLocation`. // If `result === lastSelfReferenceLocation.symbol`, that means that we are somewhere inside `lastSelfReferenceLocation` looking up a name, and resolving to `lastLocation` itself. // That means that this is a self-reference of `lastLocation`, and shouldn't count this when considering whether `lastLocation` is used. if isUse && result != nil && (lastSelfReferenceLocation == nil || result != lastSelfReferenceLocation.Symbol()) { if r.SymbolReferenced != nil { r.SymbolReferenced(result, meaning) } } if result == nil { if lastLocation != nil && lastLocation.Kind == ast.KindSourceFile && lastLocation.AsSourceFile().CommonJSModuleIndicator != nil && name == "exports" && meaning&lastLocation.Symbol().Flags != 0 { return lastLocation.Symbol() } if lastLocation != nil && r.GetModuleSymbol != nil && lastLocation.Kind == ast.KindSourceFile && lastLocation.AsSourceFile().CommonJSModuleIndicator != nil && name == "module" && originalLocation.Parent != nil && ast.IsModuleExportsAccessExpression(originalLocation.Parent) && meaning&lastLocation.Symbol().Flags != 0 { return r.GetModuleSymbol(lastLocation) } if !excludeGlobals { result = r.lookup(r.Globals, name, meaning|ast.SymbolFlagsGlobalLookup) } } if result == nil { if originalLocation != nil && ast.IsInJSFile(originalLocation) && originalLocation.Parent != nil { if ast.IsRequireCall(originalLocation.Parent, false /*requireStringLiteralLikeArgument*/) { return r.RequireSymbol } } } if nameNotFoundMessage != nil { if propertyWithInvalidInitializer != nil && r.OnPropertyWithInvalidInitializer != nil && r.OnPropertyWithInvalidInitializer(originalLocation, name, propertyWithInvalidInitializer, result) { return nil } if result == nil { if r.OnFailedToResolveSymbol != nil { r.OnFailedToResolveSymbol(originalLocation, name, meaning, nameNotFoundMessage) } } else { if r.OnSuccessfullyResolvedSymbol != nil { r.OnSuccessfullyResolvedSymbol(originalLocation, result, meaning, lastLocation, associatedDeclarationForContainingInitializerOrBindingName, withinDeferredContext) } } } return result } func (r *NameResolver) useOuterVariableScopeInParameter(result *ast.Symbol, location *ast.Node, lastLocation *ast.Node) bool { if ast.IsParameter(lastLocation) { body := location.Body() if body != nil && result.ValueDeclaration != nil && result.ValueDeclaration.Pos() >= body.Pos() && result.ValueDeclaration.End() <= body.End() { // check for several cases where we introduce temporaries that require moving the name/initializer of the parameter to the body // - static field in a class expression // - optional chaining pre-es2020 // - nullish coalesce pre-es2020 // - spread assignment in binding pattern pre-es2017 target := r.CompilerOptions.GetEmitScriptTarget() if target >= core.ScriptTargetES2015 { functionLocation := location declarationRequiresScopeChange := core.TSUnknown if r.GetRequiresScopeChangeCache != nil { declarationRequiresScopeChange = r.GetRequiresScopeChangeCache(functionLocation) } if declarationRequiresScopeChange == core.TSUnknown { declarationRequiresScopeChange = core.IfElse(core.Some(functionLocation.Parameters(), r.requiresScopeChange), core.TSTrue, core.TSFalse) if r.SetRequiresScopeChangeCache != nil { r.SetRequiresScopeChangeCache(functionLocation, declarationRequiresScopeChange) } } return declarationRequiresScopeChange != core.TSTrue } } } return false } func (r *NameResolver) requiresScopeChange(node *ast.Node) bool { d := node.AsParameterDeclaration() return r.requiresScopeChangeWorker(d.Name()) || d.Initializer != nil && r.requiresScopeChangeWorker(d.Initializer) } func (r *NameResolver) requiresScopeChangeWorker(node *ast.Node) bool { switch node.Kind { case ast.KindArrowFunction, ast.KindFunctionExpression, ast.KindFunctionDeclaration, ast.KindConstructor: return false case ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor, ast.KindPropertyAssignment: return r.requiresScopeChangeWorker(node.Name()) case ast.KindPropertyDeclaration: if ast.HasStaticModifier(node) { return !r.CompilerOptions.GetEmitStandardClassFields() } return r.requiresScopeChangeWorker(node.AsPropertyDeclaration().Name()) default: if ast.IsNullishCoalesce(node) || ast.IsOptionalChain(node) { return r.CompilerOptions.GetEmitScriptTarget() < core.ScriptTargetES2020 } if ast.IsBindingElement(node) && node.AsBindingElement().DotDotDotToken != nil && ast.IsObjectBindingPattern(node.Parent) { return r.CompilerOptions.GetEmitScriptTarget() < core.ScriptTargetES2017 } if ast.IsTypeNode(node) { return false } return node.ForEachChild(r.requiresScopeChangeWorker) } } func (r *NameResolver) error(location *ast.Node, message *diagnostics.Message, args ...any) { if r.Error != nil { r.Error(location, message, args...) } // Default implementation does not report errors } func (r *NameResolver) getSymbolOfDeclaration(node *ast.Node) *ast.Symbol { if r.GetSymbolOfDeclaration != nil { return r.GetSymbolOfDeclaration(node) } // Default implementation does not support merged symbols return node.Symbol() } func (r *NameResolver) lookup(symbols ast.SymbolTable, name string, meaning ast.SymbolFlags) *ast.Symbol { if r.Lookup != nil { return r.Lookup(symbols, name, meaning) } // Default implementation does not support following aliases or merged symbols if meaning != 0 { symbol := symbols[name] if symbol != nil { if symbol.Flags&meaning != 0 { return symbol } } } return nil } func (r *NameResolver) argumentsSymbol() *ast.Symbol { if r.ArgumentsSymbol == nil { // Default implementation synthesizes a transient symbol for `arguments` r.ArgumentsSymbol = &ast.Symbol{Name: "arguments", Flags: ast.SymbolFlagsProperty | ast.SymbolFlagsTransient} } return r.ArgumentsSymbol } func GetLocalSymbolForExportDefault(symbol *ast.Symbol) *ast.Symbol { if !isExportDefaultSymbol(symbol) || len(symbol.Declarations) == 0 { return nil } for _, decl := range symbol.Declarations { localSymbol := decl.LocalSymbol() if localSymbol != nil { return localSymbol } } return nil } func isExportDefaultSymbol(symbol *ast.Symbol) bool { return symbol != nil && len(symbol.Declarations) > 0 && ast.HasSyntacticModifier(symbol.Declarations[0], ast.ModifierFlagsDefault) } func getIsDeferredContext(location *ast.Node, lastLocation *ast.Node) bool { if location.Kind != ast.KindArrowFunction && location.Kind != ast.KindFunctionExpression { // initializers in instance property declaration of class like entities are executed in constructor and thus deferred // A name is evaluated within the enclosing scope - so it shouldn't count as deferred return ast.IsTypeQueryNode(location) || (ast.IsFunctionLikeDeclaration(location) || location.Kind == ast.KindPropertyDeclaration && !ast.IsStatic(location)) && (lastLocation == nil || lastLocation != location.Name()) } if lastLocation != nil && lastLocation == location.Name() { return false } // generator functions and async functions are not inlined in control flow when immediately invoked if location.BodyData().AsteriskToken != nil || ast.HasSyntacticModifier(location, ast.ModifierFlagsAsync) { return true } return ast.GetImmediatelyInvokedFunctionExpression(location) == nil } func isTypeParameterSymbolDeclaredInContainer(symbol *ast.Symbol, container *ast.Node) bool { for _, decl := range symbol.Declarations { if decl.Kind == ast.KindTypeParameter { parent := decl.Parent if parent == container { return true } } } return false } func isSelfReferenceLocation(node *ast.Node, lastLocation *ast.Node) bool { switch node.Kind { case ast.KindParameter: return lastLocation != nil && lastLocation == node.Name() case ast.KindFunctionDeclaration, ast.KindClassDeclaration, ast.KindInterfaceDeclaration, ast.KindEnumDeclaration, ast.KindTypeAliasDeclaration, ast.KindJSTypeAliasDeclaration, ast.KindModuleDeclaration: // For `namespace N { N; }` return true } return false }