2025-10-15 10:12:44 +03:00

1113 lines
43 KiB
Go

package checker
import (
"maps"
"slices"
"sync"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/binder"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/debug"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/evaluator"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/jsnum"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/nodebuilder"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/printer"
)
var _ printer.EmitResolver = (*emitResolver)(nil)
// Links for jsx
type JSXLinks struct {
importRef *ast.Node
}
// Links for declarations
type DeclarationLinks struct {
isVisible core.Tristate // if declaration is depended upon by exported declarations
}
type DeclarationFileLinks struct {
aliasesMarked bool // if file has had alias visibility marked
}
type emitResolver struct {
checker *Checker
checkerMu sync.Mutex
isValueAliasDeclaration func(node *ast.Node) bool
aliasMarkingVisitor func(node *ast.Node) bool
referenceResolver binder.ReferenceResolver
jsxLinks core.LinkStore[*ast.Node, JSXLinks]
declarationLinks core.LinkStore[*ast.Node, DeclarationLinks]
declarationFileLinks core.LinkStore[*ast.Node, DeclarationFileLinks]
}
func newEmitResolver(checker *Checker) *emitResolver {
e := &emitResolver{checker: checker}
e.isValueAliasDeclaration = e.isValueAliasDeclarationWorker
e.aliasMarkingVisitor = e.aliasMarkingVisitorWorker
return e
}
func (r *emitResolver) GetJsxFactoryEntity(location *ast.Node) *ast.Node {
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
return r.checker.getJsxFactoryEntity(location)
}
func (r *emitResolver) GetJsxFragmentFactoryEntity(location *ast.Node) *ast.Node {
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
return r.checker.getJsxFragmentFactoryEntity(location)
}
func (r *emitResolver) IsOptionalParameter(node *ast.Node) bool {
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
return r.isOptionalParameter(node)
}
func (r *emitResolver) IsLateBound(node *ast.Node) bool {
// TODO: Require an emitContext to construct an EmitResolver, remove all emitContext arguments
// node = r.emitContext.ParseNode(node)
if node == nil {
return false
}
if !ast.IsParseTreeNode(node) {
return false
}
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
symbol := r.checker.getSymbolOfDeclaration(node)
if symbol == nil {
return false
}
return symbol.CheckFlags&ast.CheckFlagsLate != 0
}
func (r *emitResolver) GetEnumMemberValue(node *ast.Node) evaluator.Result {
// node = r.emitContext.ParseNode(node)
if !ast.IsParseTreeNode(node) {
return evaluator.NewResult(nil, false, false, false)
}
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
r.checker.computeEnumMemberValues(node.Parent)
if !r.checker.enumMemberLinks.Has(node) {
return evaluator.NewResult(nil, false, false, false)
}
return r.checker.enumMemberLinks.Get(node).value
}
func (r *emitResolver) IsDeclarationVisible(node *ast.Node) bool {
// Only lock on external API func to prevent deadlocks
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
return r.isDeclarationVisible(node)
}
func (r *emitResolver) isDeclarationVisible(node *ast.Node) bool {
// node = r.emitContext.ParseNode(node)
if !ast.IsParseTreeNode(node) {
return false
}
if node == nil {
return false
}
links := r.declarationLinks.Get(node)
if links.isVisible == core.TSUnknown {
if r.determineIfDeclarationIsVisible(node) {
links.isVisible = core.TSTrue
} else {
links.isVisible = core.TSFalse
}
}
return links.isVisible == core.TSTrue
}
func (r *emitResolver) determineIfDeclarationIsVisible(node *ast.Node) bool {
switch node.Kind {
case ast.KindJSDocCallbackTag,
// ast.KindJSDocEnumTag, // !!! TODO: JSDoc @enum support?
ast.KindJSDocTypedefTag:
// Top-level jsdoc type aliases are considered exported
// First parent is comment node, second is hosting declaration or token; we only care about those tokens or declarations whose parent is a source file
return node.Parent != nil && node.Parent.Parent != nil && node.Parent.Parent.Parent != nil && ast.IsSourceFile(node.Parent.Parent.Parent)
case ast.KindBindingElement:
return r.isDeclarationVisible(node.Parent.Parent)
case ast.KindVariableDeclaration,
ast.KindModuleDeclaration,
ast.KindClassDeclaration,
ast.KindInterfaceDeclaration,
ast.KindTypeAliasDeclaration,
ast.KindJSTypeAliasDeclaration,
ast.KindFunctionDeclaration,
ast.KindEnumDeclaration,
ast.KindImportEqualsDeclaration:
if ast.IsVariableDeclaration(node) {
if ast.IsBindingPattern(node.Name()) &&
len(node.Name().AsBindingPattern().Elements.Nodes) == 0 {
// If the binding pattern is empty, this variable declaration is not visible
return false
}
// falls through
}
// external module augmentation is always visible
if ast.IsExternalModuleAugmentation(node) {
return true
}
parent := ast.GetDeclarationContainer(node)
// If the node is not exported or it is not ambient module element (except import declaration)
if r.checker.getCombinedModifierFlagsCached(node)&ast.ModifierFlagsExport == 0 &&
!(node.Kind != ast.KindImportEqualsDeclaration && parent.Kind != ast.KindSourceFile && parent.Flags&ast.NodeFlagsAmbient != 0) {
return ast.IsGlobalSourceFile(parent)
}
// Exported members/ambient module elements (exception import declaration) are visible if parent is visible
return r.isDeclarationVisible(parent)
case ast.KindPropertyDeclaration,
ast.KindPropertySignature,
ast.KindGetAccessor,
ast.KindSetAccessor,
ast.KindMethodDeclaration,
ast.KindMethodSignature:
if r.checker.GetEffectiveDeclarationFlags(node, ast.ModifierFlagsPrivate|ast.ModifierFlagsProtected) != 0 {
// Private/protected properties/methods are not visible
return false
}
// Public properties/methods are visible if its parents are visible, so:
return r.isDeclarationVisible(node.Parent)
case ast.KindConstructor,
ast.KindConstructSignature,
ast.KindCallSignature,
ast.KindIndexSignature,
ast.KindParameter,
ast.KindModuleBlock,
ast.KindFunctionType,
ast.KindConstructorType,
ast.KindTypeLiteral,
ast.KindTypeReference,
ast.KindArrayType,
ast.KindTupleType,
ast.KindUnionType,
ast.KindIntersectionType,
ast.KindParenthesizedType,
ast.KindNamedTupleMember:
return r.isDeclarationVisible(node.Parent)
// Default binding, import specifier and namespace import is visible
// only on demand so by default it is not visible
case ast.KindImportClause,
ast.KindNamespaceImport,
ast.KindImportSpecifier:
return false
// Type parameters are always visible
case ast.KindTypeParameter:
return true
// Source file and namespace export are always visible
case ast.KindSourceFile,
ast.KindNamespaceExportDeclaration:
return true
// Export assignments do not create name bindings outside the module
case ast.KindExportAssignment, ast.KindJSExportAssignment:
return false
default:
return false
}
}
func (r *emitResolver) PrecalculateDeclarationEmitVisibility(file *ast.SourceFile) {
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
if r.declarationFileLinks.Get(file.AsNode()).aliasesMarked {
return
}
r.declarationFileLinks.Get(file.AsNode()).aliasesMarked = true
// TODO: Does this even *have* to be an upfront walk? If it's not possible for a
// import a = a.b.c statement to chain into exposing a statement in a sibling scope,
// it could at least be pushed into scope entry - then it wouldn't need to be recursive.
file.AsNode().ForEachChild(r.aliasMarkingVisitor)
}
func (r *emitResolver) aliasMarkingVisitorWorker(node *ast.Node) bool {
switch node.Kind {
case ast.KindExportAssignment, ast.KindJSExportAssignment:
if node.AsExportAssignment().Expression.Kind == ast.KindIdentifier {
r.markLinkedAliases(node.Expression())
}
case ast.KindExportSpecifier:
r.markLinkedAliases(node.PropertyNameOrName())
}
return node.ForEachChild(r.aliasMarkingVisitor)
}
// Sets the isVisible link on statements the Identifier or ExportName node points at
// Follows chains of import d = a.b.c
func (r *emitResolver) markLinkedAliases(node *ast.Node) {
var exportSymbol *ast.Symbol
if node.Kind != ast.KindStringLiteral && node.Parent != nil && node.Parent.Kind == ast.KindExportAssignment {
exportSymbol = r.checker.resolveName(node, node.AsIdentifier().Text, ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace|ast.SymbolFlagsAlias /*nameNotFoundMessage*/, nil /*isUse*/, false, false)
} else if node.Parent.Kind == ast.KindExportSpecifier {
exportSymbol = r.checker.getTargetOfExportSpecifier(node.Parent, ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace|ast.SymbolFlagsAlias, false)
}
visited := make(map[ast.SymbolId]struct{}, 2) // guard against circular imports
for exportSymbol != nil {
_, seen := visited[ast.GetSymbolId(exportSymbol)]
if seen {
break
}
visited[ast.GetSymbolId(exportSymbol)] = struct{}{}
var nextSymbol *ast.Symbol
for _, declaration := range exportSymbol.Declarations {
r.declarationLinks.Get(declaration).isVisible = core.TSTrue
if ast.IsInternalModuleImportEqualsDeclaration(declaration) {
// Add the referenced top container visible
internalModuleReference := declaration.AsImportEqualsDeclaration().ModuleReference
firstIdentifier := ast.GetFirstIdentifier(internalModuleReference)
importSymbol := r.checker.resolveName(declaration, firstIdentifier.AsIdentifier().Text, ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace|ast.SymbolFlagsAlias /*nameNotFoundMessage*/, nil /*isUse*/, false, false)
nextSymbol = importSymbol
}
}
exportSymbol = nextSymbol
}
}
func getMeaningOfEntityNameReference(entityName *ast.Node) ast.SymbolFlags {
// get symbol of the first identifier of the entityName
if entityName.Parent.Kind == ast.KindTypeQuery ||
entityName.Parent.Kind == ast.KindExpressionWithTypeArguments && !ast.IsPartOfTypeNode(entityName.Parent) ||
entityName.Parent.Kind == ast.KindComputedPropertyName ||
entityName.Parent.Kind == ast.KindTypePredicate && entityName.Parent.AsTypePredicateNode().ParameterName == entityName {
// Typeof value
return ast.SymbolFlagsValue | ast.SymbolFlagsExportValue
}
if entityName.Kind == ast.KindQualifiedName || entityName.Kind == ast.KindPropertyAccessExpression ||
entityName.Parent.Kind == ast.KindImportEqualsDeclaration ||
(entityName.Parent.Kind == ast.KindQualifiedName && entityName.Parent.AsQualifiedName().Left == entityName) ||
(entityName.Parent.Kind == ast.KindPropertyAccessExpression && (entityName.Parent.AsPropertyAccessExpression()).Expression == entityName) ||
(entityName.Parent.Kind == ast.KindElementAccessExpression && (entityName.Parent.AsElementAccessExpression()).Expression == entityName) {
// Left identifier from type reference or TypeAlias
// Entity name of the import declaration
return ast.SymbolFlagsNamespace
}
// Type Reference or TypeAlias entity = Identifier
return ast.SymbolFlagsType
}
func (r *emitResolver) IsEntityNameVisible(entityName *ast.Node, enclosingDeclaration *ast.Node) printer.SymbolAccessibilityResult {
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
return r.isEntityNameVisible(entityName, enclosingDeclaration, true)
}
func (r *emitResolver) isEntityNameVisible(entityName *ast.Node, enclosingDeclaration *ast.Node, shouldComputeAliasToMakeVisible bool) printer.SymbolAccessibilityResult {
// node = r.emitContext.ParseNode(entityName)
if !ast.IsParseTreeNode(entityName) {
return printer.SymbolAccessibilityResult{Accessibility: printer.SymbolAccessibilityNotAccessible}
}
meaning := getMeaningOfEntityNameReference(entityName)
firstIdentifier := ast.GetFirstIdentifier(entityName)
symbol := r.checker.resolveName(enclosingDeclaration, firstIdentifier.Text(), meaning, nil, false, false)
if symbol != nil && symbol.Flags&ast.SymbolFlagsTypeParameter != 0 && meaning&ast.SymbolFlagsType != 0 {
return printer.SymbolAccessibilityResult{Accessibility: printer.SymbolAccessibilityAccessible}
}
if symbol == nil && ast.IsThisIdentifier(firstIdentifier) {
sym := r.checker.getSymbolOfDeclaration(r.checker.getThisContainer(firstIdentifier, false, false))
if r.isSymbolAccessible(sym, enclosingDeclaration, meaning, false).Accessibility == printer.SymbolAccessibilityAccessible {
return printer.SymbolAccessibilityResult{Accessibility: printer.SymbolAccessibilityAccessible}
}
}
if symbol == nil {
return printer.SymbolAccessibilityResult{
Accessibility: printer.SymbolAccessibilityNotResolved,
ErrorSymbolName: firstIdentifier.Text(),
ErrorNode: firstIdentifier,
}
}
visible := r.hasVisibleDeclarations(symbol, shouldComputeAliasToMakeVisible)
if visible != nil {
return *visible
}
return printer.SymbolAccessibilityResult{
Accessibility: printer.SymbolAccessibilityNotAccessible,
ErrorSymbolName: firstIdentifier.Text(),
ErrorNode: firstIdentifier,
}
}
func noopAddVisibleAlias(declaration *ast.Node, aliasingStatement *ast.Node) {}
func (r *emitResolver) hasVisibleDeclarations(symbol *ast.Symbol, shouldComputeAliasToMakeVisible bool) *printer.SymbolAccessibilityResult {
var aliasesToMakeVisibleSet map[ast.NodeId]*ast.Node
var addVisibleAlias func(declaration *ast.Node, aliasingStatement *ast.Node)
if shouldComputeAliasToMakeVisible {
addVisibleAlias = func(declaration *ast.Node, aliasingStatement *ast.Node) {
r.declarationLinks.Get(declaration).isVisible = core.TSTrue
if aliasesToMakeVisibleSet == nil {
aliasesToMakeVisibleSet = make(map[ast.NodeId]*ast.Node)
}
aliasesToMakeVisibleSet[ast.GetNodeId(declaration)] = aliasingStatement
}
} else {
addVisibleAlias = noopAddVisibleAlias
}
for _, declaration := range symbol.Declarations {
if ast.IsIdentifier(declaration) {
continue
}
if !r.isDeclarationVisible(declaration) {
// Mark the unexported alias as visible if its parent is visible
// because these kind of aliases can be used to name types in declaration file
anyImportSyntax := getAnyImportSyntax(declaration)
if anyImportSyntax != nil &&
!ast.HasSyntacticModifier(anyImportSyntax, ast.ModifierFlagsExport) && // import clause without export
r.isDeclarationVisible(anyImportSyntax.Parent) {
addVisibleAlias(declaration, anyImportSyntax)
continue
}
if ast.IsVariableDeclaration(declaration) && ast.IsVariableStatement(declaration.Parent.Parent) &&
!ast.HasSyntacticModifier(declaration.Parent.Parent, ast.ModifierFlagsExport) && // unexported variable statement
r.isDeclarationVisible(declaration.Parent.Parent.Parent) {
addVisibleAlias(declaration, declaration.Parent.Parent)
continue
}
if ast.IsLateVisibilityPaintedStatement(declaration) && // unexported top-level statement
!ast.HasSyntacticModifier(declaration, ast.ModifierFlagsExport) &&
r.isDeclarationVisible(declaration.Parent) {
addVisibleAlias(declaration, declaration)
continue
}
if ast.IsBindingElement(declaration) {
if symbol.Flags&ast.SymbolFlagsAlias != 0 && ast.IsInJSFile(declaration) && declaration.Parent != nil && declaration.Parent.Parent != nil && // exported import-like top-level JS require statement
ast.IsVariableDeclaration(declaration.Parent.Parent) &&
declaration.Parent.Parent.Parent.Parent != nil && ast.IsVariableStatement(declaration.Parent.Parent.Parent.Parent) &&
!ast.HasSyntacticModifier(declaration.Parent.Parent.Parent.Parent, ast.ModifierFlagsExport) &&
declaration.Parent.Parent.Parent.Parent.Parent != nil && // check if the thing containing the variable statement is visible (ie, the file)
r.isDeclarationVisible(declaration.Parent.Parent.Parent.Parent.Parent) {
addVisibleAlias(declaration, declaration.Parent.Parent.Parent.Parent)
continue
}
if symbol.Flags&ast.SymbolFlagsBlockScopedVariable != 0 {
rootDeclaration := ast.WalkUpBindingElementsAndPatterns(declaration)
if ast.IsParameter(rootDeclaration) {
return nil
}
variableStatement := rootDeclaration.Parent.Parent
if !ast.IsVariableStatement(variableStatement) {
return nil
}
if ast.HasSyntacticModifier(variableStatement, ast.ModifierFlagsExport) {
continue // no alias to add, already exported
}
if !r.isDeclarationVisible(variableStatement.Parent) {
return nil // not visible
}
addVisibleAlias(declaration, variableStatement)
continue
}
}
// Declaration is not visible
return nil
}
}
return &printer.SymbolAccessibilityResult{
Accessibility: printer.SymbolAccessibilityAccessible,
AliasesToMakeVisible: slices.Collect(maps.Values(aliasesToMakeVisibleSet)),
}
}
func (r *emitResolver) IsImplementationOfOverload(node *ast.SignatureDeclaration) bool {
// node = r.emitContext.ParseNode(node)
if !ast.IsParseTreeNode(node) {
return false
}
if ast.NodeIsPresent(node.Body()) {
if ast.IsGetAccessorDeclaration(node) || ast.IsSetAccessorDeclaration(node) {
return false // Get or set accessors can never be overload implementations, but can have up to 2 signatures
}
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
symbol := r.checker.getSymbolOfDeclaration(node)
signaturesOfSymbol := r.checker.getSignaturesOfSymbol(symbol)
// If this function body corresponds to function with multiple signature, it is implementation of overload
// e.g.: function foo(a: string): string;
// function foo(a: number): number;
// function foo(a: any) { // This is implementation of the overloads
// return a;
// }
return len(signaturesOfSymbol) > 1 ||
// If there is single signature for the symbol, it is overload if that signature isn't coming from the node
// e.g.: function foo(a: string): string;
// function foo(a: any) { // This is implementation of the overloads
// return a;
// }
(len(signaturesOfSymbol) == 1 && signaturesOfSymbol[0].declaration != node)
}
return false
}
func (r *emitResolver) IsImportRequiredByAugmentation(decl *ast.ImportDeclaration) bool {
// node = r.emitContext.ParseNode(node)
if !ast.IsParseTreeNode(decl.AsNode()) {
return false
}
file := ast.GetSourceFileOfNode(decl.AsNode())
if file.Symbol == nil {
// script file
return false
}
importTarget := r.GetExternalModuleFileFromDeclaration(decl.AsNode())
if importTarget == nil {
return false
}
if importTarget == file {
return false
}
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
exports := r.checker.getExportsOfModule(file.Symbol)
for s := range maps.Values(exports) {
merged := r.checker.getMergedSymbol(s)
if merged != s {
if len(merged.Declarations) > 0 {
for _, d := range merged.Declarations {
declFile := ast.GetSourceFileOfNode(d)
if declFile == importTarget {
return true
}
}
}
}
}
return false
}
func (r *emitResolver) IsDefinitelyReferenceToGlobalSymbolObject(node *ast.Node) bool {
if !ast.IsPropertyAccessExpression(node) ||
!ast.IsIdentifier(node.Name()) ||
!ast.IsPropertyAccessExpression(node.Expression()) && !ast.IsIdentifier(node.Expression()) {
return false
}
if node.Expression().Kind == ast.KindIdentifier {
if node.Expression().AsIdentifier().Text != "Symbol" {
return false
}
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
// Exactly `Symbol.something` and `Symbol` either does not resolve or definitely resolves to the global Symbol
return r.checker.getResolvedSymbol(node.Expression()) == r.checker.getGlobalSymbol("Symbol", ast.SymbolFlagsValue|ast.SymbolFlagsExportValue, nil /*diagnostic*/)
}
if node.Expression().Expression().Kind != ast.KindIdentifier || node.Expression().Expression().AsIdentifier().Text != "globalThis" || node.Expression().Name().Text() != "Symbol" {
return false
}
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
// Exactly `globalThis.Symbol.something` and `globalThis` resolves to the global `globalThis`
return r.checker.getResolvedSymbol(node.Expression().Expression()) == r.checker.globalThisSymbol
}
func (r *emitResolver) RequiresAddingImplicitUndefined(declaration *ast.Node, symbol *ast.Symbol, enclosingDeclaration *ast.Node) bool {
if !ast.IsParseTreeNode(declaration) {
return false
}
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
return r.requiresAddingImplicitUndefined(declaration, symbol, enclosingDeclaration)
}
func (r *emitResolver) requiresAddingImplicitUndefined(declaration *ast.Node, symbol *ast.Symbol, enclosingDeclaration *ast.Node) bool {
// node = r.emitContext.ParseNode(node)
if !ast.IsParseTreeNode(declaration) {
return false
}
switch declaration.Kind {
case ast.KindPropertyDeclaration, ast.KindPropertySignature, ast.KindJSDocPropertyTag:
if symbol == nil {
symbol = r.checker.getSymbolOfDeclaration(declaration)
}
t := r.checker.getTypeOfSymbol(symbol)
r.checker.mappedSymbolLinks.Has(symbol)
return (symbol.Flags&ast.SymbolFlagsProperty != 0) && (symbol.Flags&ast.SymbolFlagsOptional != 0) && isOptionalDeclaration(declaration) && r.checker.ReverseMappedSymbolLinks.Has(symbol) && r.checker.ReverseMappedSymbolLinks.Get(symbol).mappedType != nil && containsNonMissingUndefinedType(r.checker, t)
case ast.KindParameter, ast.KindJSDocParameterTag:
return r.requiresAddingImplicitUndefinedWorker(declaration, enclosingDeclaration)
default:
panic("Node cannot possibly require adding undefined")
}
}
func (r *emitResolver) requiresAddingImplicitUndefinedWorker(parameter *ast.Node, enclosingDeclaration *ast.Node) bool {
return (r.isRequiredInitializedParameter(parameter, enclosingDeclaration) || r.isOptionalUninitializedParameterProperty(parameter)) && !r.declaredParameterTypeContainsUndefined(parameter)
}
func (r *emitResolver) declaredParameterTypeContainsUndefined(parameter *ast.Node) bool {
// typeNode := getNonlocalEffectiveTypeAnnotationNode(parameter); // !!! JSDoc Support
typeNode := parameter.Type()
if typeNode == nil {
return false
}
t := r.checker.getTypeFromTypeNode(typeNode)
// allow error type here to avoid confusing errors that the annotation has to contain undefined when it does in cases like this:
//
// export function fn(x?: Unresolved | undefined): void {}
return r.checker.isErrorType(t) || r.checker.containsUndefinedType(t)
}
func (r *emitResolver) isOptionalUninitializedParameterProperty(parameter *ast.Node) bool {
return r.checker.strictNullChecks &&
r.isOptionalParameter(parameter) &&
( /*isJSDocParameterTag(parameter) ||*/ parameter.Initializer() == nil) && // !!! TODO: JSDoc support
ast.HasSyntacticModifier(parameter, ast.ModifierFlagsParameterPropertyModifier)
}
func (r *emitResolver) isRequiredInitializedParameter(parameter *ast.Node, enclosingDeclaration *ast.Node) bool {
if !r.checker.strictNullChecks || r.isOptionalParameter(parameter) || /*isJSDocParameterTag(parameter) ||*/ parameter.Initializer() == nil { // !!! TODO: JSDoc Support
return false
}
if ast.HasSyntacticModifier(parameter, ast.ModifierFlagsParameterPropertyModifier) {
return enclosingDeclaration != nil && ast.IsFunctionLikeDeclaration(enclosingDeclaration)
}
return true
}
func (r *emitResolver) isOptionalParameter(node *ast.Node) bool {
// !!! TODO: JSDoc support
// if (hasEffectiveQuestionToken(node)) {
// return true;
// }
if ast.IsParameter(node) && node.AsParameterDeclaration().QuestionToken != nil {
return true
}
if !ast.IsParameter(node) {
return false
}
if node.Initializer() != nil {
signature := r.checker.getSignatureFromDeclaration(node.Parent)
parameterIndex := core.FindIndex(node.Parent.Parameters(), func(p *ast.ParameterDeclarationNode) bool { return p == node })
debug.Assert(parameterIndex >= 0)
// Only consider syntactic or instantiated parameters as optional, not `void` parameters as this function is used
// in grammar checks and checking for `void` too early results in parameter types widening too early
// and causes some noImplicitAny errors to be lost.
return parameterIndex >= r.checker.getMinArgumentCountEx(signature, MinArgumentCountFlagsStrongArityForUntypedJS|MinArgumentCountFlagsVoidIsNonOptional)
}
iife := ast.GetImmediatelyInvokedFunctionExpression(node.Parent)
if iife != nil {
parameterIndex := core.FindIndex(node.Parent.Parameters(), func(p *ast.ParameterDeclarationNode) bool { return p == node })
return node.Type() == nil &&
node.AsParameterDeclaration().DotDotDotToken == nil &&
parameterIndex >= len(r.checker.getEffectiveCallArguments(iife))
}
return false
}
func (r *emitResolver) IsLiteralConstDeclaration(node *ast.Node) bool {
// node = r.emitContext.ParseNode(node)
if !ast.IsParseTreeNode(node) {
return false
}
if isDeclarationReadonly(node) || ast.IsVariableDeclaration(node) && ast.IsVarConst(node) {
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
return isFreshLiteralType(r.checker.getTypeOfSymbol(r.checker.getSymbolOfDeclaration(node)))
}
return false
}
func (r *emitResolver) IsExpandoFunctionDeclaration(node *ast.Node) bool {
// node = r.emitContext.ParseNode(node)
// !!! TODO: expando function support
return false
}
func (r *emitResolver) isSymbolAccessible(symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags, shouldComputeAliasToMarkVisible bool) printer.SymbolAccessibilityResult {
return r.checker.IsSymbolAccessible(symbol, enclosingDeclaration, meaning, shouldComputeAliasToMarkVisible)
}
func (r *emitResolver) IsSymbolAccessible(symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags, shouldComputeAliasToMarkVisible bool) printer.SymbolAccessibilityResult {
// TODO: Split into locking and non-locking API methods - only current usage is the symbol tracker, which is non-locking,
// as all tracker calls happen within a CreateX call below, which already holds a lock
// r.checkerMu.Lock()
// defer r.checkerMu.Unlock()
return r.isSymbolAccessible(symbol, enclosingDeclaration, meaning, shouldComputeAliasToMarkVisible)
}
func isConstEnumOrConstEnumOnlyModule(s *ast.Symbol) bool {
return isConstEnumSymbol(s) || s.Flags&ast.SymbolFlagsConstEnumOnlyModule != 0
}
func (r *emitResolver) IsReferencedAliasDeclaration(node *ast.Node) bool {
c := r.checker
if !c.canCollectSymbolAliasAccessibilityData || !ast.IsParseTreeNode(node) {
return true
}
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
if ast.IsAliasSymbolDeclaration(node) {
if symbol := c.getSymbolOfDeclaration(node); symbol != nil {
aliasLinks := c.aliasSymbolLinks.Get(symbol)
if aliasLinks.referenced {
return true
}
target := aliasLinks.aliasTarget
if target != nil && node.ModifierFlags()&ast.ModifierFlagsExport != 0 &&
c.getSymbolFlags(target)&ast.SymbolFlagsValue != 0 &&
(c.compilerOptions.ShouldPreserveConstEnums() || !isConstEnumOrConstEnumOnlyModule(target)) {
return true
}
}
}
return false
}
func (r *emitResolver) IsValueAliasDeclaration(node *ast.Node) bool {
c := r.checker
if !c.canCollectSymbolAliasAccessibilityData || !ast.IsParseTreeNode(node) {
return true
}
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
return r.isValueAliasDeclarationWorker(node)
}
func (r *emitResolver) isValueAliasDeclarationWorker(node *ast.Node) bool {
c := r.checker
switch node.Kind {
case ast.KindImportEqualsDeclaration:
return r.isAliasResolvedToValue(c.getSymbolOfDeclaration(node), false /*excludeTypeOnlyValues*/)
case ast.KindImportClause,
ast.KindNamespaceImport,
ast.KindImportSpecifier,
ast.KindExportSpecifier:
symbol := c.getSymbolOfDeclaration(node)
return symbol != nil && r.isAliasResolvedToValue(symbol, true /*excludeTypeOnlyValues*/)
case ast.KindExportDeclaration:
exportClause := node.AsExportDeclaration().ExportClause
return exportClause != nil && (ast.IsNamespaceExport(exportClause) ||
core.Some(exportClause.AsNamedExports().Elements.Nodes, r.isValueAliasDeclaration))
case ast.KindExportAssignment, ast.KindJSExportAssignment:
if node.AsExportAssignment().Expression != nil && node.AsExportAssignment().Expression.Kind == ast.KindIdentifier {
return r.isAliasResolvedToValue(c.getSymbolOfDeclaration(node), true /*excludeTypeOnlyValues*/)
}
return true
}
return false
}
func (r *emitResolver) isAliasResolvedToValue(symbol *ast.Symbol, excludeTypeOnlyValues bool) bool {
c := r.checker
if symbol == nil {
return false
}
if symbol.ValueDeclaration != nil {
if container := ast.GetSourceFileOfNode(symbol.ValueDeclaration); container != nil {
fileSymbol := c.getSymbolOfDeclaration(container.AsNode())
// Ensures cjs export assignment is setup, since this symbol may point at, and merge with, the file itself.
// If we don't, the merge may not have yet occurred, and the flags check below will be missing flags that
// are added as a result of the merge.
c.resolveExternalModuleSymbol(fileSymbol, false /*dontResolveAlias*/)
}
}
target := c.getExportSymbolOfValueSymbolIfExported(c.resolveAlias(symbol))
if target == c.unknownSymbol {
return !excludeTypeOnlyValues || c.getTypeOnlyAliasDeclaration(symbol) == nil
}
// const enums and modules that contain only const enums are not considered values from the emit perspective
// unless 'preserveConstEnums' option is set to true
return c.getSymbolFlagsEx(symbol, excludeTypeOnlyValues, true /*excludeLocalMeanings*/)&ast.SymbolFlagsValue != 0 &&
(c.compilerOptions.ShouldPreserveConstEnums() ||
!isConstEnumOrConstEnumOnlyModule(target))
}
func (r *emitResolver) IsTopLevelValueImportEqualsWithEntityName(node *ast.Node) bool {
c := r.checker
if !c.canCollectSymbolAliasAccessibilityData {
return true
}
if !ast.IsParseTreeNode(node) || node.Kind != ast.KindImportEqualsDeclaration || node.Parent.Kind != ast.KindSourceFile {
return false
}
if ast.IsImportEqualsDeclaration(node) &&
(ast.NodeIsMissing(node.AsImportEqualsDeclaration().ModuleReference) || node.AsImportEqualsDeclaration().ModuleReference.Kind != ast.KindExternalModuleReference) {
return false
}
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
return r.isAliasResolvedToValue(c.getSymbolOfDeclaration(node), false /*excludeTypeOnlyValues*/)
}
func (r *emitResolver) MarkLinkedReferencesRecursively(file *ast.SourceFile) {
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
if file != nil {
var visit ast.Visitor
visit = func(n *ast.Node) bool {
if ast.IsImportEqualsDeclaration(n) && n.ModifierFlags()&ast.ModifierFlagsExport == 0 {
return false // These are deferred and marked in a chain when referenced
}
if ast.IsJSExportAssignment(n) {
return false
}
if ast.IsImportDeclaration(n) {
return false // likewise, these are ultimately what get marked by calls on other nodes - we want to skip them
}
r.checker.markLinkedReferences(n, ReferenceHintUnspecified, nil /*propSymbol*/, nil /*parentType*/)
n.ForEachChild(visit)
return false
}
file.ForEachChild(visit)
}
}
func (r *emitResolver) GetExternalModuleFileFromDeclaration(declaration *ast.Node) *ast.SourceFile {
if !ast.IsParseTreeNode(declaration) {
return nil
}
var specifier *ast.Node
if declaration.Kind == ast.KindModuleDeclaration {
if ast.IsStringLiteral(declaration.Name()) {
specifier = declaration.Name()
}
} else {
specifier = ast.GetExternalModuleName(declaration)
}
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
moduleSymbol := r.checker.resolveExternalModuleNameWorker(specifier, specifier /*moduleNotFoundError*/, nil, false, false) // TODO: GH#18217
if moduleSymbol == nil {
return nil
}
decl := ast.GetDeclarationOfKind(moduleSymbol, ast.KindSourceFile)
if decl == nil {
return nil
}
return decl.AsSourceFile()
}
func (r *emitResolver) getReferenceResolver() binder.ReferenceResolver {
if r.referenceResolver == nil {
r.referenceResolver = binder.NewReferenceResolver(r.checker.compilerOptions, binder.ReferenceResolverHooks{
ResolveName: r.checker.resolveName,
GetResolvedSymbol: r.checker.getResolvedSymbol,
GetMergedSymbol: r.checker.getMergedSymbol,
GetParentOfSymbol: r.checker.getParentOfSymbol,
GetSymbolOfDeclaration: r.checker.getSymbolOfDeclaration,
GetTypeOnlyAliasDeclaration: r.checker.getTypeOnlyAliasDeclarationEx,
GetExportSymbolOfValueSymbolIfExported: r.checker.getExportSymbolOfValueSymbolIfExported,
GetElementAccessExpressionName: r.checker.tryGetElementAccessExpressionName,
})
}
return r.referenceResolver
}
func (r *emitResolver) GetReferencedExportContainer(node *ast.IdentifierNode, prefixLocals bool) *ast.Node /*SourceFile|ModuleDeclaration|EnumDeclaration*/ {
if !ast.IsParseTreeNode(node) {
return nil
}
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
return r.getReferenceResolver().GetReferencedExportContainer(node, prefixLocals)
}
func (r *emitResolver) SetReferencedImportDeclaration(node *ast.IdentifierNode, ref *ast.Declaration) {
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
r.jsxLinks.Get(node).importRef = ref
}
func (r *emitResolver) GetReferencedImportDeclaration(node *ast.IdentifierNode) *ast.Declaration {
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
if !ast.IsParseTreeNode(node) {
return r.jsxLinks.Get(node).importRef
}
return r.getReferenceResolver().GetReferencedImportDeclaration(node)
}
func (r *emitResolver) GetReferencedValueDeclaration(node *ast.IdentifierNode) *ast.Declaration {
if !ast.IsParseTreeNode(node) {
return nil
}
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
return r.getReferenceResolver().GetReferencedValueDeclaration(node)
}
func (r *emitResolver) GetReferencedValueDeclarations(node *ast.IdentifierNode) []*ast.Declaration {
if !ast.IsParseTreeNode(node) {
return nil
}
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
return r.getReferenceResolver().GetReferencedValueDeclarations(node)
}
func (r *emitResolver) GetElementAccessExpressionName(expression *ast.ElementAccessExpression) string {
if !ast.IsParseTreeNode(expression.AsNode()) {
return ""
}
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
return r.getReferenceResolver().GetElementAccessExpressionName(expression)
}
// TODO: the emit resolver being responsible for some amount of node construction is a very leaky abstraction,
// and requires giving it access to a lot of context it's otherwise not required to have, which also further complicates the API
// and likely reduces performance. There's probably some refactoring that could be done here to simplify this.
func (r *emitResolver) CreateReturnTypeOfSignatureDeclaration(emitContext *printer.EmitContext, signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
original := emitContext.ParseNode(signatureDeclaration)
if original == nil {
return emitContext.Factory.NewKeywordTypeNode(ast.KindAnyKeyword)
}
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) // TODO: cache per-context
return requestNodeBuilder.SerializeReturnTypeForSignature(original, enclosingDeclaration, flags, internalFlags, tracker)
}
func (r *emitResolver) CreateTypeParametersOfSignatureDeclaration(emitContext *printer.EmitContext, signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node {
original := emitContext.ParseNode(signatureDeclaration)
if original == nil {
return nil
}
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) // TODO: cache per-context
return requestNodeBuilder.SerializeTypeParametersForSignature(original, enclosingDeclaration, flags, internalFlags, tracker)
}
func (r *emitResolver) CreateTypeOfDeclaration(emitContext *printer.EmitContext, declaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
original := emitContext.ParseNode(declaration)
if original == nil {
return emitContext.Factory.NewKeywordTypeNode(ast.KindAnyKeyword)
}
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) // TODO: cache per-context
// // Get type of the symbol if this is the valid symbol otherwise get type at location
symbol := r.checker.getSymbolOfDeclaration(declaration)
return requestNodeBuilder.SerializeTypeForDeclaration(declaration, symbol, enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals, internalFlags, tracker)
}
func (r *emitResolver) CreateLiteralConstValue(emitContext *printer.EmitContext, node *ast.Node, tracker nodebuilder.SymbolTracker) *ast.Node {
node = emitContext.ParseNode(node)
r.checkerMu.Lock()
t := r.checker.getTypeOfSymbol(r.checker.getSymbolOfDeclaration(node))
r.checkerMu.Unlock()
if t == nil {
return nil // TODO: How!? Maybe this should be a panic. All symbols should have a type.
}
var enumResult *ast.Node
if t.flags&TypeFlagsEnumLike != 0 {
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) // TODO: cache per-context
enumResult = requestNodeBuilder.SymbolToExpression(t.symbol, ast.SymbolFlagsValue, node, nodebuilder.FlagsNone, nodebuilder.InternalFlagsNone, tracker)
// What about regularTrueType/regularFalseType - since those aren't fresh, we never make initializers from them
// TODO: handle those if this function is ever used for more than initializers in declaration emit
} else if t == r.checker.trueType {
enumResult = emitContext.Factory.NewKeywordExpression(ast.KindTrueKeyword)
} else if t == r.checker.falseType {
enumResult = emitContext.Factory.NewKeywordExpression(ast.KindFalseKeyword)
}
if enumResult != nil {
return enumResult
}
if t.flags&TypeFlagsLiteral == 0 {
return nil // non-literal type
}
switch value := t.AsLiteralType().value.(type) {
case string:
return emitContext.Factory.NewStringLiteral(value)
case jsnum.Number:
if value.Abs() != value {
// negative
return emitContext.Factory.NewPrefixUnaryExpression(
ast.KindMinusToken,
emitContext.Factory.NewNumericLiteral(value.String()[1:]),
)
}
return emitContext.Factory.NewNumericLiteral(value.String())
case jsnum.PseudoBigInt:
return emitContext.Factory.NewBigIntLiteral(pseudoBigIntToString(value) + "n")
case bool:
kind := ast.KindFalseKeyword
if value {
kind = ast.KindTrueKeyword
}
return emitContext.Factory.NewKeywordExpression(kind)
}
panic("unhandled literal const value kind")
}
func (r *emitResolver) CreateTypeOfExpression(emitContext *printer.EmitContext, expression *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
expression = emitContext.ParseNode(expression)
if expression == nil {
return emitContext.Factory.NewKeywordTypeNode(ast.KindAnyKeyword)
}
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) // TODO: cache per-context
return requestNodeBuilder.SerializeTypeForExpression(expression, enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals, internalFlags, tracker)
}
func (r *emitResolver) CreateLateBoundIndexSignatures(emitContext *printer.EmitContext, container *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node {
container = emitContext.ParseNode(container)
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
sym := container.Symbol()
staticInfos := r.checker.getIndexInfosOfType(r.checker.getTypeOfSymbol(sym))
instanceIndexSymbol := r.checker.getIndexSymbol(sym)
var instanceInfos []*IndexInfo
if instanceIndexSymbol != nil {
siblingSymbols := slices.Collect(maps.Values(r.checker.getMembersOfSymbol(sym)))
instanceInfos = r.checker.getIndexInfosOfIndexSymbol(instanceIndexSymbol, siblingSymbols)
}
requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) // TODO: cache per-context
var result []*ast.Node
for i, infoList := range [][]*IndexInfo{staticInfos, instanceInfos} {
isStatic := true
if i > 0 {
isStatic = false
}
if len(infoList) == 0 {
continue
}
for _, info := range infoList {
if info.declaration != nil {
continue
}
if info == r.checker.anyBaseTypeIndexInfo {
continue // inherited, but looks like a late-bound signature because it has no declarations
}
if len(info.components) != 0 {
// !!! TODO: Complete late-bound index info support - getObjectLiteralIndexInfo does not yet add late bound components to index signatures
allComponentComputedNamesSerializable := enclosingDeclaration != nil && core.Every(info.components, func(c *ast.Node) bool {
return c.Name() != nil &&
ast.IsComputedPropertyName(c.Name()) &&
ast.IsEntityNameExpression(c.Name().AsComputedPropertyName().Expression) &&
r.isEntityNameVisible(c.Name().AsComputedPropertyName().Expression, enclosingDeclaration, false).Accessibility == printer.SymbolAccessibilityAccessible
})
if allComponentComputedNamesSerializable {
for _, c := range info.components {
if r.checker.hasLateBindableName(c) {
// skip late bound props that contribute to the index signature - they'll be preserved via other means
continue
}
firstIdentifier := ast.GetFirstIdentifier(c.Name().Expression())
name := r.checker.resolveName(firstIdentifier, firstIdentifier.Text(), ast.SymbolFlagsValue|ast.SymbolFlagsExportValue, nil /*nameNotFoundMessage*/, true /*isUse*/, false /*excludeGlobals*/)
if name != nil {
tracker.TrackSymbol(name, enclosingDeclaration, ast.SymbolFlagsValue)
}
mods := core.IfElse(isStatic, []*ast.Node{emitContext.Factory.NewModifier(ast.KindStaticKeyword)}, nil)
if info.isReadonly {
mods = append(mods, emitContext.Factory.NewModifier(ast.KindReadonlyKeyword))
}
decl := emitContext.Factory.NewPropertyDeclaration(
core.IfElse(mods != nil, emitContext.Factory.NewModifierList(mods), nil),
c.Name(),
c.QuestionToken(),
requestNodeBuilder.TypeToTypeNode(r.checker.getTypeOfSymbol(c.Symbol()), enclosingDeclaration, flags, internalFlags, tracker),
nil,
)
result = append(result, decl)
}
continue
}
}
node := requestNodeBuilder.IndexInfoToIndexSignatureDeclaration(info, enclosingDeclaration, flags, internalFlags, tracker)
if node != nil && isStatic {
modNodes := []*ast.Node{emitContext.Factory.NewModifier(ast.KindStaticKeyword)}
mods := node.Modifiers()
if mods != nil {
modNodes = append(modNodes, mods.Nodes...)
}
mods = emitContext.Factory.NewModifierList(modNodes)
node = emitContext.Factory.UpdateIndexSignatureDeclaration(
node.AsIndexSignatureDeclaration(),
mods,
node.ParameterList(),
node.Type(),
)
}
if node != nil {
result = append(result, node)
}
}
}
return result
}
func (r *emitResolver) GetEffectiveDeclarationFlags(node *ast.Node, flags ast.ModifierFlags) ast.ModifierFlags {
// node = emitContext.ParseNode(node)
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
return r.checker.GetEffectiveDeclarationFlags(node, flags)
}
func (r *emitResolver) GetResolutionModeOverride(node *ast.Node) core.ResolutionMode {
// node = emitContext.ParseNode(node)
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
return r.checker.GetResolutionModeOverride(node.AsImportAttributes(), false)
}
func (r *emitResolver) GetConstantValue(node *ast.Node) any {
// node = emitContext.ParseNode(node)
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
return r.checker.GetConstantValue(node)
}