kittenipc/kitcom/internal/tsgo/binder/nameresolver.go
2025-10-15 10:12:44 +03:00

504 lines
22 KiB
Go

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<T>() { return '' }
// class C<T> { // <-- Class's own type parameter T
// [foo<T>()]() { } // <-- 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<T> {}
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
}