package checker import ( "reflect" "slices" "unsafe" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/printer" ) func (ch *Checker) IsTypeSymbolAccessible(typeSymbol *ast.Symbol, enclosingDeclaration *ast.Node) bool { access := ch.isSymbolAccessibleWorker(typeSymbol, enclosingDeclaration, ast.SymbolFlagsType /*shouldComputeAliasesToMakeVisible*/, false /*allowModules*/, true) return access.Accessibility == printer.SymbolAccessibilityAccessible } func (ch *Checker) IsValueSymbolAccessible(symbol *ast.Symbol, enclosingDeclaration *ast.Node) bool { access := ch.isSymbolAccessibleWorker(symbol, enclosingDeclaration, ast.SymbolFlagsValue /*shouldComputeAliasesToMakeVisible*/, false /*allowModules*/, true) return access.Accessibility == printer.SymbolAccessibilityAccessible } func (ch *Checker) IsSymbolAccessibleByFlags(symbol *ast.Symbol, enclosingDeclaration *ast.Node, flags ast.SymbolFlags) bool { access := ch.isSymbolAccessibleWorker(symbol, enclosingDeclaration, flags /*shouldComputeAliasesToMakeVisible*/, false /*allowModules*/, false) // TODO: Strada bug? Why is this allowModules: false? return access.Accessibility == printer.SymbolAccessibilityAccessible } func (ch *Checker) IsAnySymbolAccessible(symbols []*ast.Symbol, enclosingDeclaration *ast.Node, initialSymbol *ast.Symbol, meaning ast.SymbolFlags, shouldComputeAliasesToMakeVisible bool, allowModules bool) *printer.SymbolAccessibilityResult { if len(symbols) == 0 { return nil } var hadAccessibleChain *ast.Symbol earlyModuleBail := false for _, symbol := range symbols { // Symbol is accessible if it by itself is accessible accessibleSymbolChain := ch.getAccessibleSymbolChain(symbol, enclosingDeclaration, meaning /*useOnlyExternalAliasing*/, false) if len(accessibleSymbolChain) > 0 { hadAccessibleChain = symbol // TODO: going through emit resolver here is weird. Relayer these APIs. hasAccessibleDeclarations := ch.GetEmitResolver().hasVisibleDeclarations(accessibleSymbolChain[0], shouldComputeAliasesToMakeVisible) if hasAccessibleDeclarations != nil { return hasAccessibleDeclarations } } if allowModules { if core.Some(symbol.Declarations, hasNonGlobalAugmentationExternalModuleSymbol) { if shouldComputeAliasesToMakeVisible { earlyModuleBail = true // Generally speaking, we want to use the aliases that already exist to refer to a module, if present // In order to do so, we need to find those aliases in order to retain them in declaration emit; so // if we are in declaration emit, we cannot use the fast path for module visibility until we've exhausted // all other visibility options (in order to capture the possible aliases used to reference the module) continue } // Any meaning of a module symbol is always accessible via an `import` type return &printer.SymbolAccessibilityResult{ Accessibility: printer.SymbolAccessibilityAccessible, } } } // If we haven't got the accessible symbol, it doesn't mean the symbol is actually inaccessible. // It could be a qualified symbol and hence verify the path // e.g.: // module m { // export class c { // } // } // const x: typeof m.c // In the above example when we start with checking if typeof m.c symbol is accessible, // we are going to see if c can be accessed in scope directly. // But it can't, hence the accessible is going to be undefined, but that doesn't mean m.c is inaccessible // It is accessible if the parent m is accessible because then m.c can be accessed through qualification containers := ch.getContainersOfSymbol(symbol, enclosingDeclaration, meaning) nextMeaning := meaning if initialSymbol == symbol { nextMeaning = getQualifiedLeftMeaning(meaning) } parentResult := ch.IsAnySymbolAccessible(containers, enclosingDeclaration, initialSymbol, nextMeaning, shouldComputeAliasesToMakeVisible, allowModules) if parentResult != nil { return parentResult } } if earlyModuleBail { return &printer.SymbolAccessibilityResult{ Accessibility: printer.SymbolAccessibilityAccessible, } } if hadAccessibleChain != nil { var moduleName string if hadAccessibleChain != initialSymbol { moduleName = ch.symbolToStringEx(hadAccessibleChain, enclosingDeclaration, ast.SymbolFlagsNamespace, SymbolFormatFlagsAllowAnyNodeKind) } return &printer.SymbolAccessibilityResult{ Accessibility: printer.SymbolAccessibilityNotAccessible, ErrorSymbolName: ch.symbolToStringEx(initialSymbol, enclosingDeclaration, meaning, SymbolFormatFlagsAllowAnyNodeKind), ErrorModuleName: moduleName, } } return nil } func hasNonGlobalAugmentationExternalModuleSymbol(declaration *ast.Node) bool { return ast.IsModuleWithStringLiteralName(declaration) || (declaration.Kind == ast.KindSourceFile && ast.IsExternalOrCommonJSModule(declaration.AsSourceFile())) } func getQualifiedLeftMeaning(rightMeaning ast.SymbolFlags) ast.SymbolFlags { // If we are looking in value space, the parent meaning is value, other wise it is namespace if rightMeaning == ast.SymbolFlagsValue { return ast.SymbolFlagsValue } return ast.SymbolFlagsNamespace } func (ch *Checker) getWithAlternativeContainers(container *ast.Symbol, symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags) []*ast.Symbol { additionalContainers := core.MapNonNil(container.Declarations, func(d *ast.Node) *ast.Symbol { return ch.getFileSymbolIfFileSymbolExportEqualsContainer(d, container) }) var reexportContainers []*ast.Symbol if enclosingDeclaration != nil { reexportContainers = ch.getAlternativeContainingModules(symbol, enclosingDeclaration) } objectLiteralContainer := ch.getVariableDeclarationOfObjectLiteral(container, meaning) leftMeaning := getQualifiedLeftMeaning(meaning) if enclosingDeclaration != nil && container.Flags&leftMeaning != 0 && len(ch.getAccessibleSymbolChain(container, enclosingDeclaration, ast.SymbolFlagsNamespace /*useOnlyExternalAliasing*/, false)) > 0 { // This order expresses a preference for the real container if it is in scope res := append(append([]*ast.Symbol{container}, additionalContainers...), reexportContainers...) if objectLiteralContainer != nil { res = append(res, objectLiteralContainer) } return res } // we potentially have a symbol which is a member of the instance side of something - look for a variable in scope with the container's type // which may be acting like a namespace (eg, `Symbol` acts like a namespace when looking up `Symbol.toStringTag`) var firstVariableMatch *ast.Symbol if (meaning == ast.SymbolFlagsValue && container.Flags&leftMeaning == 0) && container.Flags&ast.SymbolFlagsType != 0 && ch.getDeclaredTypeOfSymbol(container).flags&TypeFlagsObject != 0 { ch.someSymbolTableInScope(enclosingDeclaration, func(t ast.SymbolTable, _ bool, _ bool, _ *ast.Node) bool { for _, s := range t { if s.Flags&leftMeaning != 0 && ch.getTypeOfSymbol(s) == ch.getDeclaredTypeOfSymbol(container) { firstVariableMatch = s return true } } return false }) } var res []*ast.Symbol if firstVariableMatch != nil { res = append(res, firstVariableMatch) } res = append(res, additionalContainers...) res = append(res, container) if objectLiteralContainer != nil { res = append(res, objectLiteralContainer) } res = append(res, reexportContainers...) return res } func (ch *Checker) getAlternativeContainingModules(symbol *ast.Symbol, enclosingDeclaration *ast.Node) []*ast.Symbol { if enclosingDeclaration == nil { return nil } containingFile := ast.GetSourceFileOfNode(enclosingDeclaration) id := ast.GetNodeId(containingFile.AsNode()) links := ch.symbolContainerLinks.Get(symbol) if links.extendedContainersByFile == nil { links.extendedContainersByFile = make(map[ast.NodeId][]*ast.Symbol) } existing, ok := links.extendedContainersByFile[id] if ok && existing != nil { return existing } var results []*ast.Symbol if len(containingFile.Imports()) > 0 { // Try to make an import using an import already in the enclosing file, if possible for _, importRef := range containingFile.Imports() { if ast.NodeIsSynthesized(importRef) { // Synthetic names can't be resolved by `resolveExternalModuleName` - they'll cause a debug assert if they error continue } resolvedModule := ch.resolveExternalModuleName(enclosingDeclaration, importRef /*ignoreErrors*/, true) if resolvedModule == nil { continue } ref := ch.getAliasForSymbolInContainer(resolvedModule, symbol) if ref == nil { continue } results = append(results, resolvedModule) } if len(results) > 0 { links.extendedContainersByFile[id] = results return results } } if links.extendedContainers != nil { return *links.extendedContainers } // No results from files already being imported by this file - expand search (expensive, but not location-specific, so cached) otherFiles := ch.program.SourceFiles() for _, file := range otherFiles { if !ast.IsExternalModule(file) { continue } sym := ch.getSymbolOfDeclaration(file.AsNode()) ref := ch.getAliasForSymbolInContainer(sym, symbol) if ref == nil { continue } results = append(results, sym) } links.extendedContainers = &results return results } func (ch *Checker) getVariableDeclarationOfObjectLiteral(symbol *ast.Symbol, meaning ast.SymbolFlags) *ast.Symbol { // If we're trying to reference some object literal in, eg `var a = { x: 1 }`, the symbol for the literal, `__object`, is distinct // from the symbol of the declaration it is being assigned to. Since we can use the declaration to refer to the literal, however, // we'd like to make that connection here - potentially causing us to paint the declaration's visibility, and therefore the literal. if meaning&ast.SymbolFlagsValue == 0 { return nil } if len(symbol.Declarations) == 0 { return nil } firstDecl := symbol.Declarations[0] if firstDecl.Parent == nil { return nil } if !ast.IsVariableDeclaration(firstDecl.Parent) { return nil } if ast.IsObjectLiteralExpression(firstDecl) && firstDecl == firstDecl.Parent.Initializer() || ast.IsTypeLiteralNode(firstDecl) && firstDecl == firstDecl.Parent.Type() { return ch.getSymbolOfDeclaration(firstDecl.Parent) } return nil } func hasExternalModuleSymbol(declaration *ast.Node) bool { return ast.IsAmbientModule(declaration) || (declaration.Kind == ast.KindSourceFile && ast.IsExternalOrCommonJSModule(declaration.AsSourceFile())) } func (ch *Checker) getExternalModuleContainer(declaration *ast.Node) *ast.Symbol { node := ast.FindAncestor(declaration, hasExternalModuleSymbol) if node == nil { return nil } return ch.getSymbolOfDeclaration(node) } func (ch *Checker) getFileSymbolIfFileSymbolExportEqualsContainer(d *ast.Node, container *ast.Symbol) *ast.Symbol { fileSymbol := ch.getExternalModuleContainer(d) if fileSymbol == nil || fileSymbol.Exports == nil { return nil } exported, ok := fileSymbol.Exports[ast.InternalSymbolNameExportEquals] if !ok || exported == nil { return nil } if ch.getSymbolIfSameReference(exported, container) != nil { return fileSymbol } return nil } /** * Attempts to find the symbol corresponding to the container a symbol is in - usually this * is just its' `.parent`, but for locals, this value is `undefined` */ func (ch *Checker) getContainersOfSymbol(symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags) []*ast.Symbol { container := ch.getParentOfSymbol(symbol) // Type parameters end up in the `members` lists but are not externally visible if container != nil && (symbol.Flags&ast.SymbolFlagsTypeParameter == 0) { return ch.getWithAlternativeContainers(container, symbol, enclosingDeclaration, meaning) } var candidates []*ast.Symbol for _, d := range symbol.Declarations { if !ast.IsAmbientModule(d) && d.Parent != nil { // direct children of a module if hasNonGlobalAugmentationExternalModuleSymbol(d.Parent) { sym := ch.getSymbolOfDeclaration(d.Parent) if sym != nil && !slices.Contains(candidates, sym) { candidates = append(candidates, sym) } continue } // export ='d member of an ambient module if ast.IsModuleBlock(d.Parent) && d.Parent.Parent != nil && ch.resolveExternalModuleSymbol(ch.getSymbolOfDeclaration(d.Parent.Parent), false) == symbol { sym := ch.getSymbolOfDeclaration(d.Parent.Parent) if sym != nil && !slices.Contains(candidates, sym) { candidates = append(candidates, sym) } continue } } if ast.IsClassExpression(d) && ast.IsBinaryExpression(d.Parent) && d.Parent.AsBinaryExpression().OperatorToken.Kind == ast.KindEqualsToken && ast.IsAccessExpression(d.Parent.AsBinaryExpression().Left) && ast.IsEntityNameExpression(d.Parent.AsBinaryExpression().Left.Expression()) { if ast.IsModuleExportsAccessExpression(d.Parent.AsBinaryExpression().Left) || ast.IsExportsIdentifier(d.Parent.AsBinaryExpression().Left.Expression()) { sym := ch.getSymbolOfDeclaration(ast.GetSourceFileOfNode(d).AsNode()) if sym != nil && !slices.Contains(candidates, sym) { candidates = append(candidates, sym) } continue } ch.checkExpressionCached(d.Parent.AsBinaryExpression().Left.Expression()) sym := ch.symbolNodeLinks.Get(d.Parent.AsBinaryExpression().Left.Expression()).resolvedSymbol if sym != nil && !slices.Contains(candidates, sym) { candidates = append(candidates, sym) } continue } } if len(candidates) == 0 { return nil } var bestContainers []*ast.Symbol var alternativeContainers []*ast.Symbol for _, container := range candidates { if ch.getAliasForSymbolInContainer(container, symbol) == nil { continue } allAlts := ch.getWithAlternativeContainers(container, symbol, enclosingDeclaration, meaning) if len(allAlts) == 0 { continue } bestContainers = append(bestContainers, allAlts[0]) alternativeContainers = append(alternativeContainers, allAlts[1:]...) } return append(bestContainers, alternativeContainers...) } func (ch *Checker) getAliasForSymbolInContainer(container *ast.Symbol, symbol *ast.Symbol) *ast.Symbol { if container == ch.getParentOfSymbol(symbol) { // fast path, `symbol` is either already the alias or isn't aliased return symbol } // Check if container is a thing with an `export=` which points directly at `symbol`, and if so, return // the container itself as the alias for the symbol if container.Exports != nil { exportEquals, ok := container.Exports[ast.InternalSymbolNameExportEquals] if ok && exportEquals != nil && ch.getSymbolIfSameReference(exportEquals, symbol) != nil { return container } } exports := ch.getExportsOfSymbol(container) quick, ok := exports[symbol.Name] if ok && quick != nil && ch.getSymbolIfSameReference(quick, symbol) != nil { return quick } var candidates []*ast.Symbol for _, exported := range exports { if ch.getSymbolIfSameReference(exported, symbol) != nil { candidates = append(candidates, exported) } } if len(candidates) > 0 { ch.sortSymbols(candidates) // _must_ sort exports for stable results - symbol table is randomly iterated return candidates[0] } return nil } func (ch *Checker) getAccessibleSymbolChain( symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags, useOnlyExternalAliasing bool, ) []*ast.Symbol { return ch.getAccessibleSymbolChainEx(accessibleSymbolChainContext{symbol, enclosingDeclaration, meaning, useOnlyExternalAliasing, make(map[ast.SymbolId]map[unsafe.Pointer]struct{})}) } func (ch *Checker) GetAccessibleSymbolChain( symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags, useOnlyExternalAliasing bool, ) []*ast.Symbol { return ch.getAccessibleSymbolChain(symbol, enclosingDeclaration, meaning, useOnlyExternalAliasing) } type accessibleSymbolChainContext struct { symbol *ast.Symbol enclosingDeclaration *ast.Node meaning ast.SymbolFlags useOnlyExternalAliasing bool visitedSymbolTablesMap map[ast.SymbolId]map[unsafe.Pointer]struct{} } func (ch *Checker) getAccessibleSymbolChainEx(ctx accessibleSymbolChainContext) []*ast.Symbol { if ctx.symbol == nil { return nil } if isPropertyOrMethodDeclarationSymbol(ctx.symbol) { return nil } // Go from enclosingDeclaration to the first scope we check, so the cache is keyed off the scope and thus shared more var firstRelevantLocation *ast.Node ch.someSymbolTableInScope(ctx.enclosingDeclaration, func(_ ast.SymbolTable, _ bool, _ bool, node *ast.Node) bool { firstRelevantLocation = node return true }) links := ch.symbolContainerLinks.Get(ctx.symbol) linkKey := accessibleChainCacheKey{ctx.useOnlyExternalAliasing, firstRelevantLocation, ctx.meaning} if links.accessibleChainCache == nil { links.accessibleChainCache = make(map[accessibleChainCacheKey][]*ast.Symbol) } existing, ok := links.accessibleChainCache[linkKey] if ok { return existing } var result []*ast.Symbol ch.someSymbolTableInScope(ctx.enclosingDeclaration, func(t ast.SymbolTable, ignoreQualification bool, isLocalNameLookup bool, _ *ast.Node) bool { res := ch.getAccessibleSymbolChainFromSymbolTable(ctx, t, ignoreQualification, isLocalNameLookup) if len(res) > 0 { result = res return true } return false }) links.accessibleChainCache[linkKey] = result return result } /** * @param {ignoreQualification} boolean Set when a symbol is being looked for through the exports of another symbol (meaning we have a route to qualify it already) */ func (ch *Checker) getAccessibleSymbolChainFromSymbolTable(ctx accessibleSymbolChainContext, t ast.SymbolTable, ignoreQualification bool, isLocalNameLookup bool) []*ast.Symbol { symId := ast.GetSymbolId(ctx.symbol) visitedSymbolTables, ok := ctx.visitedSymbolTablesMap[symId] if !ok { visitedSymbolTables = make(map[unsafe.Pointer]struct{}) ctx.visitedSymbolTablesMap[symId] = visitedSymbolTables } id := reflect.ValueOf(t).UnsafePointer() // TODO: Is this seriously the only way to check reference equality of maps? _, present := visitedSymbolTables[id] if present { return nil } visitedSymbolTables[id] = struct{}{} res := ch.trySymbolTable(ctx, t, ignoreQualification, isLocalNameLookup) delete(visitedSymbolTables, id) return res } func (ch *Checker) trySymbolTable( ctx accessibleSymbolChainContext, symbols ast.SymbolTable, ignoreQualification bool, isLocalNameLookup bool, ) []*ast.Symbol { // If symbol is directly available by its name in the symbol table res, ok := symbols[ctx.symbol.Name] if ok && res != nil && ch.isAccessible(ctx, res /*resolvedAliasSymbol*/, nil, ignoreQualification) { return []*ast.Symbol{ctx.symbol} } var candidateChains [][]*ast.Symbol // collect all possible chains to sort them and return the shortest/best for _, symbolFromSymbolTable := range symbols { // for every non-default, non-export= alias symbol in scope, check if it refers to or can chain to the target symbol if symbolFromSymbolTable.Flags&ast.SymbolFlagsAlias != 0 && symbolFromSymbolTable.Name != ast.InternalSymbolNameExportEquals && symbolFromSymbolTable.Name != ast.InternalSymbolNameDefault && !(isUMDExportSymbol(symbolFromSymbolTable) && ctx.enclosingDeclaration != nil && ast.IsExternalModule(ast.GetSourceFileOfNode(ctx.enclosingDeclaration))) && // If `!useOnlyExternalAliasing`, we can use any type of alias to get the name (!ctx.useOnlyExternalAliasing || core.Some(symbolFromSymbolTable.Declarations, ast.IsExternalModuleImportEqualsDeclaration)) && // If we're looking up a local name to reference directly, omit namespace reexports, otherwise when we're trawling through an export list to make a dotted name, we can keep it (isLocalNameLookup && !core.Some(symbolFromSymbolTable.Declarations, isNamespaceReexportDeclaration) || !isLocalNameLookup) && // While exports are generally considered to be in scope, export-specifier declared symbols are _not_ // See similar comment in `resolveName` for details (ignoreQualification || len(getDeclarationsOfKind(symbolFromSymbolTable, ast.KindExportSpecifier)) == 0) { resolvedImportedSymbol := ch.resolveAlias(symbolFromSymbolTable) candidate := ch.getCandidateListForSymbol(ctx, symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification) if len(candidate) > 0 { candidateChains = append(candidateChains, candidate) } } if symbolFromSymbolTable.Name == ctx.symbol.Name && symbolFromSymbolTable.ExportSymbol != nil { if ch.isAccessible(ctx, ch.getMergedSymbol(symbolFromSymbolTable.ExportSymbol) /*resolvedAliasSymbol*/, nil, ignoreQualification) { candidateChains = append(candidateChains, []*ast.Symbol{ctx.symbol}) } } } if len(candidateChains) > 0 { // pick first, shortest slices.SortStableFunc(candidateChains, ch.compareSymbolChains) return candidateChains[0] } // If there's no result and we're looking at the global symbol table, treat `globalThis` like an alias and try to lookup thru that if reflect.ValueOf(ch.globals).UnsafePointer() == reflect.ValueOf(symbols).UnsafePointer() { return ch.getCandidateListForSymbol(ctx, ch.globalThisSymbol, ch.globalThisSymbol, ignoreQualification) } return nil } func (ch *Checker) compareSymbolChainsWorker(a []*ast.Symbol, b []*ast.Symbol) int { chainLen := len(a) - len(b) if chainLen != 0 { return chainLen } idx := 0 for idx < len(a) { comparison := ch.compareSymbols(a[idx], b[idx]) if comparison != 0 { return comparison } idx++ } return 0 } func isUMDExportSymbol(symbol *ast.Symbol) bool { return symbol != nil && len(symbol.Declarations) > 0 && symbol.Declarations[0] != nil && ast.IsNamespaceExportDeclaration(symbol.Declarations[0]) } func isNamespaceReexportDeclaration(node *ast.Node) bool { return ast.IsNamespaceExport(node) && node.Parent.AsExportDeclaration().ModuleSpecifier != nil } func (ch *Checker) getCandidateListForSymbol( ctx accessibleSymbolChainContext, symbolFromSymbolTable *ast.Symbol, resolvedImportedSymbol *ast.Symbol, ignoreQualification bool, ) []*ast.Symbol { if ch.isAccessible(ctx, symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification) { return []*ast.Symbol{symbolFromSymbolTable} } // Look in the exported members, if we can find accessibleSymbolChain, symbol is accessible using this chain // but only if the symbolFromSymbolTable can be qualified candidateTable := ch.getExportsOfSymbol(resolvedImportedSymbol) if candidateTable == nil { return nil } accessibleSymbolsFromExports := ch.getAccessibleSymbolChainFromSymbolTable(ctx, candidateTable /*ignoreQualification*/, true, false) if len(accessibleSymbolsFromExports) == 0 { return nil } if !ch.canQualifySymbol(ctx, symbolFromSymbolTable, getQualifiedLeftMeaning(ctx.meaning)) { return nil } return append([]*ast.Symbol{symbolFromSymbolTable}, accessibleSymbolsFromExports...) } func (ch *Checker) isAccessible( ctx accessibleSymbolChainContext, symbolFromSymbolTable *ast.Symbol, resolvedAliasSymbol *ast.Symbol, ignoreQualification bool, ) bool { likeSymbols := false if ctx.symbol == resolvedAliasSymbol { likeSymbols = true } if ctx.symbol == symbolFromSymbolTable { likeSymbols = true } symbol := ch.getMergedSymbol(ctx.symbol) if symbol == ch.getMergedSymbol(resolvedAliasSymbol) { likeSymbols = true } if symbol == ch.getMergedSymbol(symbolFromSymbolTable) { likeSymbols = true } if !likeSymbols { return false } // if the symbolFromSymbolTable is not external module (it could be if it was determined as ambient external module and would be in globals table) // and if symbolFromSymbolTable or alias resolution matches the symbol, // check the symbol can be qualified, it is only then this symbol is accessible return !core.Some(symbolFromSymbolTable.Declarations, hasNonGlobalAugmentationExternalModuleSymbol) && (ignoreQualification || ch.canQualifySymbol(ctx, ch.getMergedSymbol(symbolFromSymbolTable), ctx.meaning)) } func (ch *Checker) canQualifySymbol( ctx accessibleSymbolChainContext, symbolFromSymbolTable *ast.Symbol, meaning ast.SymbolFlags, ) bool { // If the symbol is equivalent and doesn't need further qualification, this symbol is accessible return !ch.needsQualification(symbolFromSymbolTable, ctx.enclosingDeclaration, meaning) || // If symbol needs qualification, make sure that parent is accessible, if it is then this symbol is accessible too len(ch.getAccessibleSymbolChainEx(accessibleSymbolChainContext{symbolFromSymbolTable.Parent, ctx.enclosingDeclaration, getQualifiedLeftMeaning(meaning), ctx.useOnlyExternalAliasing, ctx.visitedSymbolTablesMap})) > 0 } func (ch *Checker) needsQualification(symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags) bool { qualify := false ch.someSymbolTableInScope(enclosingDeclaration, func(symbolTable ast.SymbolTable, _ bool, _ bool, _ *ast.Node) bool { // If symbol of this name is not available in the symbol table we are ok res, ok := symbolTable[symbol.Name] if !ok || res == nil { return false } symbolFromSymbolTable := ch.getMergedSymbol(res) if symbolFromSymbolTable == nil { // Continue to the next symbol table return false } // If the symbol with this name is present it should refer to the symbol if symbolFromSymbolTable == symbol { // No need to qualify return true } // Qualify if the symbol from symbol table has same meaning as expected shouldResolveAlias := symbolFromSymbolTable.Flags&ast.SymbolFlagsAlias != 0 && ast.GetDeclarationOfKind(symbolFromSymbolTable, ast.KindExportSpecifier) == nil if shouldResolveAlias { symbolFromSymbolTable = ch.resolveAlias(symbolFromSymbolTable) } flags := symbolFromSymbolTable.Flags if shouldResolveAlias { flags = ch.getSymbolFlags(symbolFromSymbolTable) } if flags&meaning != 0 { qualify = true return true } // Continue to the next symbol table return false }) return qualify } func isPropertyOrMethodDeclarationSymbol(symbol *ast.Symbol) bool { if len(symbol.Declarations) > 0 { for _, declaration := range symbol.Declarations { switch declaration.Kind { case ast.KindPropertyDeclaration, ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor: continue default: return false } } return true } return false } func (ch *Checker) someSymbolTableInScope( enclosingDeclaration *ast.Node, callback func(symbolTable ast.SymbolTable, ignoreQualification bool, isLocalNameLookup bool, scopeNode *ast.Node) bool, ) bool { for location := enclosingDeclaration; location != nil; location = location.Parent { // Locals of a source file are not in scope (because they get merged into the global symbol table) if canHaveLocals(location) && location.Locals() != nil && !ast.IsGlobalSourceFile(location) { if callback(location.Locals(), false, true, location) { return true } } switch location.Kind { case ast.KindSourceFile, ast.KindModuleDeclaration: if ast.IsSourceFile(location) && !ast.IsExternalOrCommonJSModule(location.AsSourceFile()) { break } sym := ch.getSymbolOfDeclaration(location) if callback(sym.Exports, false, true, location) { return true } case ast.KindClassDeclaration, ast.KindClassExpression, ast.KindInterfaceDeclaration: // Type parameters are bound into `members` lists so they can merge across declarations // This is troublesome, since in all other respects, they behave like locals :cries: // TODO: the below is shared with similar code in `resolveName` - in fact, rephrasing all this symbol // lookup logic in terms of `resolveName` would be nice // The below is used to lookup type parameters within a class or interface, as they are added to the class/interface locals // These can never be latebound, so the symbol's raw members are sufficient. `getMembersOfNode` cannot be used, as it would // trigger resolving late-bound names, which we may already be in the process of doing while we're here! var table ast.SymbolTable sym := ch.getSymbolOfDeclaration(location) // TODO: Should this filtered table be cached in some way? for key, memberSymbol := range sym.Members { if memberSymbol.Flags&(ast.SymbolFlagsType & ^ast.SymbolFlagsAssignment) != 0 { if table == nil { table = make(ast.SymbolTable) } table[key] = memberSymbol } } if table != nil && callback(table, false, false, location) { return true } } } return callback(ch.globals, false, true, nil) } /** * Check if the given symbol in given enclosing declaration is accessible and mark all associated alias to be visible if requested * * @param symbol a Symbol to check if accessible * @param enclosingDeclaration a Node containing reference to the symbol * @param meaning a SymbolFlags to check if such meaning of the symbol is accessible * @param shouldComputeAliasToMakeVisible a boolean value to indicate whether to return aliases to be mark visible in case the symbol is accessible */ func (c *Checker) IsSymbolAccessible(symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags, shouldComputeAliasesToMakeVisible bool) printer.SymbolAccessibilityResult { return c.isSymbolAccessibleWorker(symbol, enclosingDeclaration, meaning, shouldComputeAliasesToMakeVisible, true /*allowModules*/) } func (c *Checker) isSymbolAccessibleWorker(symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags, shouldComputeAliasesToMakeVisible bool, allowModules bool) printer.SymbolAccessibilityResult { if symbol != nil && enclosingDeclaration != nil { result := c.IsAnySymbolAccessible([]*ast.Symbol{symbol}, enclosingDeclaration, symbol, meaning, shouldComputeAliasesToMakeVisible, allowModules) if result != nil { return *result } // This could be a symbol that is not exported in the external module // or it could be a symbol from different external module that is not aliased and hence cannot be named symbolExternalModule := core.FirstNonNil(symbol.Declarations, c.getExternalModuleContainer) if symbolExternalModule != nil { enclosingExternalModule := c.getExternalModuleContainer(enclosingDeclaration) if symbolExternalModule != enclosingExternalModule { // name from different external module that is not visible return printer.SymbolAccessibilityResult{ Accessibility: printer.SymbolAccessibilityCannotBeNamed, ErrorSymbolName: c.symbolToStringEx(symbol, enclosingDeclaration, meaning, SymbolFormatFlagsAllowAnyNodeKind), ErrorModuleName: c.symbolToString(symbolExternalModule), ErrorNode: core.IfElse(ast.IsInJSFile(enclosingDeclaration), enclosingDeclaration, nil), } } } // Just a local name that is not accessible return printer.SymbolAccessibilityResult{ Accessibility: printer.SymbolAccessibilityNotAccessible, ErrorSymbolName: c.symbolToStringEx(symbol, enclosingDeclaration, meaning, SymbolFormatFlagsAllowAnyNodeKind), } } return printer.SymbolAccessibilityResult{ Accessibility: printer.SymbolAccessibilityAccessible, } }