package checker import ( "strings" "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/nodebuilder" "github.com/microsoft/typescript-go/internal/printer" ) func (b *NodeBuilderImpl) reuseNode(node *ast.Node) *ast.Node { if node == nil { return node } return b.tryReuseExistingNodeHelper(node) } func (b *NodeBuilderImpl) tryJSTypeNodeToTypeNode(node *ast.Node) *ast.Node { return b.reuseNode(node) } func (b *NodeBuilderImpl) reuseName(node *ast.Node, isMethod bool) *ast.Node { res := b.reuseNode(node) if res == nil { return res } text, ok := ast.TryGetTextOfPropertyName(res) if !ok { return res } kind := classifyPropertyName(text, ast.IsStringLiteral(res), isMethod) if ast.IsIdentifier(res) && kind == propertyNameNodeKindIdentifier { return res } if ast.IsStringLiteral(res) && kind == propertyNameNodeKindStringLiteral { return res } var renamed *ast.Node switch kind { case propertyNameNodeKindIdentifier: renamed = b.newIdentifier(text, nil) case propertyNameNodeKindStringLiteral: renamed = b.f.NewStringLiteral(text, ast.TokenFlagsNone) default: return res } b.e.SetOriginal(renamed, res) return b.setTextRange(renamed, res) } func (b *NodeBuilderImpl) reuseTypeNode(node *ast.Node) *ast.Node { if node == nil { return node } r := b.reuseNode(node) if r != nil { // After successful reuse during hover, probe the reused AST for expandable // type references so canIncreaseExpansionDepth is set even though // typeToTypeNode (and shouldExpandType) were never called. if b.ctx.maxExpansionDepth >= 0 && !b.ctx.canIncreaseExpansionDepth { b.walkNodeForExpandability(node) } return r } b.ctx.tracker.ReportInferenceFallback(node) t := b.getTypeFromTypeNode(node, false) return b.typeToTypeNode(t) } // walkNodeForExpandability walks a reused AST node tree, calling checkTypeExpandability // on each type reference, type predicate, or import type node. // Short-circuits once canIncreaseExpansionDepth is set. func (b *NodeBuilderImpl) walkNodeForExpandability(node *ast.Node) { if b.ctx.canIncreaseExpansionDepth || node == nil { return } // Check these explicitly so we look into type arguments wehther or not they are in the tree or not. if ast.IsTypeReferenceNode(node) || ast.IsExpressionWithTypeArguments(node) || ast.IsTypePredicateNode(node) || ast.IsImportTypeNode(node) { t := b.getTypeFromTypeNode(node, false) if t != nil { b.checkTypeExpandability(t) if b.ctx.canIncreaseExpansionDepth { return } } } node.ForEachChild(func(child *ast.Node) bool { b.walkNodeForExpandability(child) return b.ctx.canIncreaseExpansionDepth }) } type recoveryBoundary struct { ctx *NodeBuilderContext hadError bool deferredReports []func() oldTracker nodebuilder.SymbolTracker oldTrackedSymbols []*TrackedSymbolArgs trackedSymbols []*TrackedSymbolArgs oldEncounteredError bool oldApproximateLength int } func (b *recoveryBoundary) markError(f func()) { b.hadError = true if f != nil { b.deferredReports = append(b.deferredReports, f) } } type originalRecoveryScopeState struct { trackedSymbolsTop int unreportedErrorsTop int hadError bool } func (b *recoveryBoundary) startRecoveryScope() originalRecoveryScopeState { trackedSymbolsTop := len(b.ctx.trackedSymbols) unreportedErrorsTop := len(b.deferredReports) return originalRecoveryScopeState{trackedSymbolsTop: trackedSymbolsTop, unreportedErrorsTop: unreportedErrorsTop, hadError: b.hadError} } func (b *recoveryBoundary) endRecoveryScope(state originalRecoveryScopeState) { b.hadError = state.hadError b.ctx.trackedSymbols = b.ctx.trackedSymbols[0:state.trackedSymbolsTop] b.deferredReports = b.deferredReports[0:state.unreportedErrorsTop] } type wrappingTracker struct { wrapped nodebuilder.SymbolTracker bound *recoveryBoundary } func (w *wrappingTracker) PopErrorFallbackNode() { w.wrapped.PopErrorFallbackNode() } func (w *wrappingTracker) PushErrorFallbackNode(node *ast.Node) { w.wrapped.PushErrorFallbackNode(node) } func (w *wrappingTracker) ReportCyclicStructureError() { w.bound.markError(w.wrapped.ReportCyclicStructureError) } func (w *wrappingTracker) ReportInaccessibleThisError() { w.bound.markError(w.wrapped.ReportInaccessibleThisError) } func (w *wrappingTracker) ReportInaccessibleUniqueSymbolError() { w.bound.markError(w.wrapped.ReportInaccessibleUniqueSymbolError) } func (w *wrappingTracker) ReportInferenceFallback(node *ast.Node) { w.wrapped.ReportInferenceFallback(node) // Should this also be deferred? } func (w *wrappingTracker) ReportLikelyUnsafeImportRequiredError(specifier string, symbolName string) { w.bound.markError(func() { w.wrapped.ReportLikelyUnsafeImportRequiredError(specifier, symbolName) }) } func (w *wrappingTracker) ReportNonSerializableProperty(propertyName string) { w.bound.markError(func() { w.wrapped.ReportNonSerializableProperty(propertyName) }) } func (w *wrappingTracker) ReportNonlocalAugmentation(containingFile *ast.SourceFile, parentSymbol *ast.Symbol, augmentingSymbol *ast.Symbol) { w.wrapped.ReportNonlocalAugmentation(containingFile, parentSymbol, augmentingSymbol) // Should this also be deferred? } func (w *wrappingTracker) ReportPrivateInBaseOfClassExpression(propertyName string) { w.bound.markError(func() { w.wrapped.ReportPrivateInBaseOfClassExpression(propertyName) }) } func (w *wrappingTracker) ReportTruncationError() { w.wrapped.ReportTruncationError() // Should this also be deferred? } func (w *wrappingTracker) TrackSymbol(symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags) bool { w.bound.trackedSymbols = append(w.bound.trackedSymbols, &TrackedSymbolArgs{symbol, enclosingDeclaration, meaning}) return false } func newWrappingTracker(inner nodebuilder.SymbolTracker, bound *recoveryBoundary) *wrappingTracker { return &wrappingTracker{ wrapped: inner, bound: bound, } } func (b *NodeBuilderImpl) createRecoveryBoundary() *recoveryBoundary { b.ch.checkNotCanceled() bound := &recoveryBoundary{ctx: b.ctx, oldTracker: b.ctx.tracker, oldTrackedSymbols: b.ctx.trackedSymbols, oldEncounteredError: b.ctx.encounteredError, oldApproximateLength: b.ctx.approximateLength} newTracker := NewSymbolTrackerImpl(b.ctx, newWrappingTracker(b.ctx.tracker, bound)) b.ctx.tracker = newTracker b.ctx.trackedSymbols = nil return bound } func (b *NodeBuilderImpl) finalizeBoundary(bound *recoveryBoundary) bool { b.ctx.tracker = bound.oldTracker b.ctx.trackedSymbols = bound.oldTrackedSymbols b.ctx.encounteredError = bound.oldEncounteredError b.ctx.approximateLength = bound.oldApproximateLength for _, f := range bound.deferredReports { f() } if bound.hadError { return false } for _, a := range bound.trackedSymbols { b.ctx.tracker.TrackSymbol(a.symbol, a.enclosingDeclaration, a.meaning) } return true } func (b *NodeBuilderImpl) tryReuseExistingNodeHelper(existing *ast.TypeNode) *ast.TypeNode { bound := b.createRecoveryBoundary() var transformed *ast.Node v := getExistingNodeTreeVisitor(b, bound) // !!! TODO: Cache visitor and just reset bound+host builder? We try this for a *lot* of nodes. transformed = v.VisitNode(existing) if !b.finalizeBoundary(bound) { return nil } b.ctx.approximateLength += existing.Loc.End() - existing.Loc.Pos() return transformed } func (b *NodeBuilderImpl) getModuleSpecifierOverride(parent *ast.Node, lit *ast.Node) string { if b.ctx.enclosingFile != ast.GetSourceFileOfNode(lit) { mode := core.ResolutionModeNone if parent.AsImportTypeNode().Attributes != nil { mode = b.ch.getResolutionModeOverride(parent.AsImportTypeNode().Attributes.AsImportAttributes(), false) } name := lit.Text() originalName := name nodeSymbol := b.tryGetResolvedSymbolFromTypeNode(parent) meaning := ast.SymbolFlagsType if parent.AsImportTypeNode().IsTypeOf { meaning = ast.SymbolFlagsValue } var parentSymbol *ast.Symbol if nodeSymbol != nil && b.ch.IsSymbolAccessible(nodeSymbol, b.ctx.enclosingDeclaration, meaning, false).Accessibility == printer.SymbolAccessibilityAccessible { parentSymbol = b.lookupSymbolChain(nodeSymbol, meaning, true)[0] } if parentSymbol != nil && IsExternalModuleSymbol(parentSymbol) { name = b.getSpecifierForModuleSymbol(parentSymbol, mode) } else { targetFile := b.ch.getExternalModuleFileFromDeclaration(parent) if targetFile != nil { name = b.getSpecifierForModuleSymbol(targetFile.Symbol, mode) } } if len(name) > 0 && strings.Contains(name, "/node_modules/") { b.ctx.encounteredError = true b.ctx.tracker.ReportLikelyUnsafeImportRequiredError(name, "") } if name != originalName { return name } } return "" } func (b *NodeBuilderImpl) rewriteModuleSpecifier(parent *ast.Node, lit *ast.Node) *ast.Node { newName := b.getModuleSpecifierOverride(parent, lit) if len(newName) == 0 { return lit } res := b.f.NewStringLiteral(newName, ast.TokenFlagsNone) b.e.SetOriginal(res, lit) return res } func (b *NodeBuilderImpl) getEnclosingDeclarationIgnoringFakeScope() *ast.Node { enc := b.ctx.enclosingDeclaration for enc != nil && b.links.Get(enc).fakeScopeForSignatureDeclaration != nil { enc = enc.Parent } return enc } func getExistingNodeTreeVisitor(b *NodeBuilderImpl, bound *recoveryBoundary) *ast.NodeVisitor { // TODO: wrap all these closures into methods on an object so we can guarantee we reuse the same memory on each invocation by reusing/resetting the object // instead of re-closing-over all of these each time we need a visitor. In theory the compiler could handle this, but in practice closure inlining hasn't been reliable var visitor *ast.NodeVisitor // note: also handles renaming type parameters renamed within the current context attachSymbolToLeftmostIdentifier := func(leftmost *ast.Node, node *ast.Node, sym *ast.Symbol) *ast.Node { var vis *ast.NodeVisitor visitorFunc := func(node *ast.Node) *ast.Node { if node == leftmost { var type_ *Type var name *ast.Node if sym != nil { type_ = b.ch.getDeclaredTypeOfSymbol(sym) if sym.Flags&ast.SymbolFlagsTypeParameter != 0 { name = b.typeParameterToName(type_).AsNode() } } if name == nil { name = b.newIdentifier(node.Text(), sym) } name = b.setTextRange(name, node) b.e.AddEmitFlags(name, printer.EFNoAsciiEscaping) return name } return b.setTextRange(node.VisitEachChild(vis), node) } vis = ast.NewNodeVisitor(visitorFunc, b.f, ast.NodeVisitorHooks{}) return visitorFunc(node) } trackExistingEntityName := func(node *ast.Node, overrideEnclosing *ast.Node) (bool, *ast.Node, *ast.Symbol) { enclosingDeclaration := b.ctx.enclosingDeclaration if overrideEnclosing != nil { enclosingDeclaration = overrideEnclosing } introducesError := false leftmost := ast.GetFirstIdentifier(node) if ast.IsInJSFile(node) && (ast.IsExportsIdentifier(leftmost) || ast.IsModuleExportsAccessExpression(leftmost.Parent) || (ast.IsQualifiedName(leftmost.Parent) && ast.IsModuleIdentifier(leftmost.Parent.AsQualifiedName().Left) && ast.IsExportsIdentifier(leftmost.Parent.AsQualifiedName().Right))) { introducesError = true return introducesError, b.setTextRange(b.f.DeepCloneNode(node), node), nil } meaning := getMeaningOfEntityNameReference(node) var sym *ast.Symbol if ast.IsThisIdentifier(leftmost) { // `this` isn't a bindable identifier - skip resolution, find a relevant `this` symbol directly and avoid exhaustive scope traversal sym = b.ch.getSymbolOfDeclaration(b.ch.getThisContainer(leftmost, false, false)) if b.ch.IsSymbolAccessible(sym, leftmost, meaning, false).Accessibility != printer.SymbolAccessibilityAccessible { introducesError = true b.ctx.tracker.ReportInaccessibleThisError() } return introducesError, attachSymbolToLeftmostIdentifier(leftmost, node, sym), nil } sym = b.ch.resolveEntityName(leftmost, meaning, true, true, nil) if b.ctx.enclosingDeclaration != nil && !(sym != nil && sym.Flags&ast.SymbolFlagsTypeParameter != 0) { sym = b.ch.getExportSymbolOfValueSymbolIfExported(sym) // Some declarations may be transplanted to a new location. // When this happens we need to make sure that the name has the same meaning at both locations // We also check for the unknownSymbol because when we create a fake scope some parameters may actually not be usable // either because they are the expanded rest parameter, // or because they are the newly added parameters from the tuple, which might have different meanings in the original context symAtLocation := b.ch.resolveEntityName(leftmost, meaning, true, true, b.ctx.enclosingDeclaration) if // Check for unusable parameters symbols symAtLocation == b.ch.unknownSymbol || // If the symbol is not found, but was not found in the original scope either we probably have an error, don't reuse the node (symAtLocation == nil && sym != nil) || // If the symbol is found both in declaration scope and in current scope then it should point to the same reference (symAtLocation != nil && sym != nil && b.ch.getSymbolIfSameReference(b.ch.getExportSymbolOfValueSymbolIfExported(symAtLocation), sym) == nil) { // In isolated declaration we will not do rest parameter expansion so there is no need to report on these. if symAtLocation != b.ch.unknownSymbol { b.ctx.tracker.ReportInferenceFallback(node) } introducesError = true return introducesError, b.setTextRange(b.f.DeepCloneNode(node), node), sym } else { sym = symAtLocation } } if sym != nil { // If a parameter is resolvable in the current context it is also visible, so no need to go to symbol accesibility if sym.Flags&ast.SymbolFlagsFunctionScopedVariable != 0 && sym.ValueDeclaration != nil { if ast.IsPartOfParameterDeclaration(sym.ValueDeclaration) || ast.IsJSDocParameterTag(sym.ValueDeclaration) { return introducesError, attachSymbolToLeftmostIdentifier(leftmost, node, sym), nil } } if sym.Flags&ast.SymbolFlagsTypeParameter == 0 /* Type parameters are visible in the current context if they are are resolvable */ && !ast.IsDeclarationName(node) && b.ch.IsSymbolAccessible(sym, enclosingDeclaration, meaning, false).Accessibility != printer.SymbolAccessibilityAccessible { b.ctx.tracker.ReportInferenceFallback(node) introducesError = true } else { b.ctx.tracker.TrackSymbol(sym, enclosingDeclaration, meaning) } return introducesError, attachSymbolToLeftmostIdentifier(leftmost, node, sym), nil } return introducesError, b.setTextRange(b.f.DeepCloneNode(node), node), nil } var tryVisitSimpleTypeNode func(node *ast.Node) *ast.Node tryVisitIndexedAccess := func(node *ast.Node) *ast.Node { resultObjectType := tryVisitSimpleTypeNode(node.AsIndexedAccessTypeNode().ObjectType) if resultObjectType == nil { return nil } return b.setTextRange(b.f.UpdateIndexedAccessTypeNode(node.AsIndexedAccessTypeNode(), resultObjectType, visitor.VisitNode(node.AsIndexedAccessTypeNode().IndexType)), node) } tryVisitKeyOf := func(node *ast.Node) *ast.Node { to := node.AsTypeOperatorNode() t := tryVisitSimpleTypeNode(to.Type) if t == nil { return nil } return b.setTextRange(b.f.UpdateTypeOperatorNode(to, to.Operator, t), node) } tryVisitTypeQuery := func(node *ast.Node) *ast.Node { introducesError, exprName, _ := trackExistingEntityName(node.AsTypeQueryNode().ExprName, nil) if !introducesError { return b.setTextRange(b.f.UpdateTypeQueryNode( node.AsTypeQueryNode(), exprName, visitor.VisitNodes(node.AsTypeQueryNode().TypeArguments), ), node) } serializedName := b.serializeTypeName(node.AsTypeQueryNode().ExprName, true, visitor.VisitNodes(node.AsTypeQueryNode().TypeArguments)) if serializedName != nil { return b.setTextRange(serializedName, node.AsTypeQueryNode().ExprName) } return nil } tryVisitTypeReference := func(node *ast.Node) *ast.Node { if ast.IsConstTypeReference(node) { return nil } s := b.tryGetResolvedSymbolFromTypeNode(node) if s == nil { return nil // ??? } if s.Flags&ast.SymbolFlagsTypeParameter != 0 { declaredType := b.ch.getDeclaredTypeOfSymbol(s) if b.ctx.mapper != nil && b.ctx.mapper.Map(declaredType) != declaredType { return nil // refers to type parameter remapped by context (TODO improvement: just return the remapped param name?) } } if !b.canReuseExistingJSTypeNode(node, b.getTypeFromTypeNode(node, false)) { // fallback to serialization for jsdoc types that have insufficient or incomplete type args, or are remapped by the checker in only jsdoc contexts // TODO: remappings like `promise` -> `Promise` are static, we *could* statically remap the nodes, too. But that only matters for `isolatedDeclarations` // in JS, should we enable that. return nil } introducesError, newName, _ := trackExistingEntityName(node.AsTypeReferenceNode().TypeName, nil) if !introducesError { typeArguments := visitor.VisitNodes(node.AsTypeReferenceNode().TypeArguments) return b.setTextRange(b.f.UpdateTypeReferenceNode( node.AsTypeReferenceNode(), newName, typeArguments, ), node) } else { serializedName := b.serializeTypeName(node.AsTypeReferenceNode().TypeName, false, visitor.VisitNodes(node.AsTypeReferenceNode().TypeArguments)) if serializedName != nil { return b.setTextRange(serializedName, node.AsTypeReferenceNode().TypeName) } return nil } } tryVisitSimpleTypeNode = func(node *ast.Node) *ast.Node { innerNode := ast.SkipParentheses(node) switch innerNode.Kind { case ast.KindTypeReference: return tryVisitTypeReference(innerNode) case ast.KindTypeQuery: return tryVisitTypeQuery(innerNode) case ast.KindIndexedAccessType: return tryVisitIndexedAccess(innerNode) case ast.KindTypeOperator: if innerNode.AsTypeOperatorNode().Operator == ast.KindKeyOfKeyword { return tryVisitKeyOf(innerNode) } } return visitor.VisitNode(node) } visitExistingNodeTreeSymbolsWorker := func(node *ast.Node) *ast.Node { factory := b.f // !!! TODO: the reparser *should* make all the jsdoc remapping logic here redundant, // assuming we only ever try to preserve reparsed nodes and never walk back to the jsdoc "originals" // accidentally. // Still, what can be ported of the logic is here, just in case. // Begin JSDoc handling if node.Kind == ast.KindJSDocTypeExpression { // Unwrap JSDocTypeExpressions return visitor.VisitNode(node.AsJSDocTypeExpression().Type) } // !!! TODO: We don't _actually_ support jsdoc namepath types, emit `any` instead; verify we handle as gracefully as strada if node.Kind == ast.KindJSDocAllType /* || node.Kind == ast.JSDocNamepathType */ { return factory.NewKeywordTypeNode(ast.KindAnyKeyword) } // !!! TODO: verify JSDocUnknwonType is hopefully just parsed into `unknown` upfront; the kind no longer exists // if node.Kind == ast.KindJSDocUnknownType { // return factory.NewKeywordTypeNode(ast.KindUnknownKeyword) // } if node.Kind == ast.KindJSDocNullableType { unionMembers := []*ast.Node{ visitor.VisitNode(node.AsJSDocNullableType().Type), factory.NewLiteralTypeNode(factory.NewKeywordExpression(ast.KindNullKeyword)), } return factory.NewUnionTypeNode(factory.NewNodeList(unionMembers)) } if node.Kind == ast.KindJSDocOptionalType { unionMembers := []*ast.Node{ visitor.VisitNode(node.AsJSDocOptionalType().Type), factory.NewKeywordTypeNode(ast.KindUndefinedKeyword), } return factory.NewUnionTypeNode(factory.NewNodeList(unionMembers)) } if node.Kind == ast.KindJSDocNonNullableType { // Unwrap return visitor.VisitNode(node.AsJSDocNonNullableType().Type) } if node.Kind == ast.KindJSDocVariadicType { // !!! TODO: verify this matches how jsdoc variadics are actually handled now? return factory.NewArrayTypeNode(visitor.VisitNode(node.AsJSDocVariadicType().Type)) } if node.Kind == ast.KindJSDocTypeLiteral { var members []*ast.Node for _, t := range node.AsJSDocTypeLiteral().JSDocPropertyTags { if t.Kind != ast.KindJSDocPropertyTag && t.Kind != ast.KindJSDocParameterTag { continue } n := t.Name() var targetName *ast.Node if ast.IsIdentifier(n) { targetName = n } else { targetName = n.AsQualifiedName().Right // !!! TODO: without typesystem backup, doing this cast unguarded seems really suspect, even though it is what strada does } name := visitor.VisitNode(targetName) shouldBeOptional := t.AsJSDocParameterOrPropertyTag().IsBracketed || (t.TypeExpression() != nil && t.TypeExpression().Kind == ast.KindJSDocOptionalType) var question *ast.Node if shouldBeOptional { question = factory.NewToken(ast.KindQuestionToken) } ty := visitor.VisitNode(t.TypeExpression()) // !!! TODO: alternate lookup locations for the type? serialize on demand if it doesn't serialze? strada does something funky here. members = append(members, factory.NewPropertySignatureDeclaration(nil, name, question, ty, nil)) } return factory.NewTypeLiteralNode(factory.NewNodeList(members)) } // if (ast.IsExpressionWithTypeArguments(node) || ast.IsTypeReferenceNode(node)) && ast.IsJSDocIndexSignature(node) { /// !!! TODO: JSDocIndexSignature handling hasn't been ported - readd if it's readded // args := node.TypeArguments() // if len(args) != 2 { // return factory.NewKeywordTypeNode(ast.KindAnyKeyword) // shouldn't be flagged as a jsdoc index signature in the first place // } // return factory.NewTypeLiteralNode(factory.NewNodeList([]*ast.Node{ // factory.NewIndexSignatureDeclaration(nil, factory.NewNodeList([]*ast.Node{ // factory.NewParameterDeclaration(nil, nil, factory.NewIdentifier("x"), nil, visitor.VisitNode(args[0]), nil), // }), visitor.VisitNode(args[1])), // })) // } // if node.Kind == ast.KindJSDocFunctionType {} // !!! no longer exists // End JSDoc handling if ast.IsTypeReferenceNode(node) && ast.IsIdentifier(node.AsTypeReferenceNode().TypeName) && node.AsTypeReferenceNode().TypeName.AsIdentifier().Text == "" { replacement := factory.NewKeywordTypeNode(ast.KindAnyKeyword) b.e.SetOriginal(replacement, node) return replacement } if ast.IsThisTypeNode(node) { // TODO: strada never marks `this` type nodes as an error - it calls `canReuseTypeNode` on it, but that function always returns `true` for `this` // type nodes, which in turn fails to verify that the `this` context is the same between the source and target locations. The conservative thing is to // _never_ copy a `this`. We could improve this, but strada is *definitely* wrong and overbroad here. (note that we're inling uses of `canReuseTypeNode` // in corsa because of the unfurled host structure meaning we don't need to defer to a host object for functionality it needs) // bound.markError(nil) // conservative approach return node } if ast.IsTypeParameterDeclaration(node) { _, newName, _ := trackExistingEntityName(node.Name(), nil) return factory.UpdateTypeParameterDeclaration( node.AsTypeParameterDeclaration(), visitor.VisitModifiers(node.Modifiers()), newName, visitor.VisitNode(node.AsTypeParameterDeclaration().Constraint), visitor.VisitNode(node.AsTypeParameterDeclaration().Expression), visitor.VisitNode(node.AsTypeParameterDeclaration().DefaultType), ) } if ast.IsIndexedAccessTypeNode(node) { result := tryVisitIndexedAccess(node) if result != nil { return result } bound.markError(nil) return node } if ast.IsTypeReferenceNode(node) { result := tryVisitTypeReference(node) if result != nil { return result } bound.markError(nil) return node } if ast.IsTypeQueryNode(node) { result := tryVisitTypeQuery(node) if result != nil { return result } bound.markError(nil) return node } if ast.IsTypeOperatorNode(node) { if node.AsTypeOperatorNode().Operator == ast.KindUniqueKeyword && node.AsTypeOperatorNode().Type.Kind == ast.KindSymbolKeyword { nonFakeEnclosing := b.getEnclosingDeclarationIgnoringFakeScope() sameScope := ast.FindAncestor(node, func(a *ast.Node) bool { return a == nonFakeEnclosing }) if sameScope == nil { bound.markError(nil) return node } } else if node.AsTypeOperatorNode().Operator == ast.KindKeyOfKeyword { result := tryVisitKeyOf(node) if result != nil { return result } bound.markError(nil) return node } } if ast.IsLiteralImportTypeNode(node) { // assert keyword in imported attributes is deprecated, so we don't reuse types that contain it // Ex: import("pkg", { assert: {} } if node.AsImportTypeNode().Attributes != nil && node.AsImportTypeNode().Attributes.AsImportAttributes().Token == ast.KindAssertKeyword { bound.markError(nil) return node } t := b.getTypeFromTypeNode(node, true) if t == nil { bound.markError(nil) return node } if ast.IsInJSFile(node) { // !!! TODO: invalidate node reuse if js fallback logic used in type param list/typeof lookup (but isn't this logic gone?) // s := b.ch.symbolNodeLinks.Get(node).resolvedSymbol } originalSpec := node.AsImportTypeNode().Argument.AsLiteralTypeNode().Literal specifier := b.rewriteModuleSpecifier(node, originalSpec) if originalSpec == specifier { specifier = visitor.VisitNode(specifier) // visit node if not replaced } arg := node.AsImportTypeNode().Argument if specifier != originalSpec { arg = factory.NewLiteralTypeNode(specifier) } return factory.UpdateImportTypeNode( node.AsImportTypeNode(), node.AsImportTypeNode().IsTypeOf, arg, visitor.VisitNode(node.AsImportTypeNode().Attributes), visitor.VisitNode(node.AsImportTypeNode().Qualifier), visitor.VisitNodes(node.AsImportTypeNode().TypeArguments), ) } if node.Name() != nil && node.Name().Kind == ast.KindComputedPropertyName && !b.ch.hasLateBindableName(node) { if !ast.HasDynamicName(node) { // !!! TODO: This matches strada, but rather than recursing, this should probably fall down to later cases. // Take a `["field"]` property declaration - it still needs a `: any` appended to it return visitor.VisitEachChild(node) } // !!! TODO: this condition matches strada, but it just seems wrong? Or at the very least extraordinarily approximate, and doesn't flag a builder error... shouldRemoveDeclaration := !((b.ctx.internalFlags&nodebuilder.InternalFlagsAllowUnresolvedNames != 0) && ast.IsEntityNameExpression(node.Name().AsComputedPropertyName().Expression) && (b.ch.checkComputedPropertyName(node.Name()).flags&TypeFlagsAny != 0)) if shouldRemoveDeclaration { return nil } } if (ast.IsFunctionLike(node) && node.Type() == nil) || (ast.IsPropertyDeclaration(node) && node.Type() == nil && node.Initializer() == nil) || (ast.IsPropertySignatureDeclaration(node) && node.Type() == nil && node.Initializer() == nil) || (ast.IsParameterDeclaration(node) && node.Type() == nil && node.Initializer() == nil) { visited := visitor.VisitEachChild(node) if visited == node { visited = b.setTextRange(node.Clone(factory), node) } node = visited newType := factory.NewKeywordTypeNode(ast.KindAnyKeyword) switch node.Kind { case ast.KindPropertyDeclaration: return factory.UpdatePropertyDeclaration( node.AsPropertyDeclaration(), node.Modifiers(), node.Name(), node.PostfixToken(), newType, nil, ) case ast.KindPropertySignature: return factory.UpdatePropertySignatureDeclaration( node.AsPropertySignatureDeclaration(), node.Modifiers(), node.Name(), node.PostfixToken(), newType, nil, ) case ast.KindParameter: return factory.UpdateParameterDeclaration( node.AsParameterDeclaration(), nil, node.AsParameterDeclaration().DotDotDotToken, node.Name(), node.AsParameterDeclaration().QuestionToken, newType, nil, ) case ast.KindMethodSignature: return factory.UpdateMethodSignatureDeclaration( node.AsMethodSignatureDeclaration(), node.Modifiers(), node.Name(), node.AsMethodSignatureDeclaration().PostfixToken, node.AsMethodSignatureDeclaration().TypeParameters, node.AsMethodSignatureDeclaration().Parameters, newType, ) case ast.KindCallSignature: return factory.UpdateCallSignatureDeclaration( node.AsCallSignatureDeclaration(), node.AsCallSignatureDeclaration().TypeParameters, node.AsCallSignatureDeclaration().Parameters, newType, ) case ast.KindJSDocSignature: return factory.UpdateJSDocSignature( node.AsJSDocSignature(), node.AsJSDocSignature().TypeParameters, node.AsJSDocSignature().Parameters, newType, ) case ast.KindConstructSignature: return factory.UpdateConstructSignatureDeclaration( node.AsConstructSignatureDeclaration(), node.AsConstructSignatureDeclaration().TypeParameters, node.AsConstructSignatureDeclaration().Parameters, newType, ) case ast.KindIndexSignature: return factory.UpdateIndexSignatureDeclaration( node.AsIndexSignatureDeclaration(), node.Modifiers(), node.AsIndexSignatureDeclaration().Parameters, newType, ) case ast.KindFunctionType: return factory.UpdateFunctionTypeNode( node.AsFunctionTypeNode(), node.AsFunctionTypeNode().TypeParameters, node.AsFunctionTypeNode().Parameters, newType, ) case ast.KindConstructorType: return factory.UpdateConstructorTypeNode( node.AsConstructorTypeNode(), node.Modifiers(), node.AsConstructorTypeNode().TypeParameters, node.AsConstructorTypeNode().Parameters, newType, ) } } if ast.IsComputedPropertyName(node) && ast.IsEntityNameExpression(node.AsComputedPropertyName().Expression) { introducesError, result, _ := trackExistingEntityName(node.AsComputedPropertyName().Expression, nil) if !introducesError { return factory.UpdateComputedPropertyName(node.AsComputedPropertyName(), result) } else { // !!! TODO: rewriting computed names based on evaluator/typecheck results? // strada's behavior seems hard to justify vs marking an error and moving on bound.markError(nil) return visitor.VisitEachChild(node) } } if ast.IsTypePredicateNode(node) { var parameterName *ast.Node if ast.IsIdentifier(node.AsTypePredicateNode().ParameterName) { introducesError, result, _ := trackExistingEntityName(node.AsTypePredicateNode().ParameterName, nil) // Should not usually happen the only case is when a type predicate comes from a JSDoc type annotation with it's own parameter symbol definition. // /** @type {(v: unknown) => v is undefined} */ // const isUndef = v => v === undefined; if introducesError { bound.markError(nil) } parameterName = result } else { parameterName = node.AsTypePredicateNode().ParameterName.Clone(factory) } return factory.UpdateTypePredicateNode( node.AsTypePredicateNode(), visitor.VisitNode(node.AsTypePredicateNode().AssertsModifier), parameterName, visitor.VisitNode(node.AsTypePredicateNode().Type), ) } if ast.IsConditionalTypeNode(node) { checkType := visitor.VisitNode(node.AsConditionalTypeNode().CheckType) dispose := b.enterNewScope(node, nil, b.ch.getInferTypeParameters(node), nil, nil) extendsType := visitor.VisitNode(node.AsConditionalTypeNode().ExtendsType) trueType := visitor.VisitNode(node.AsConditionalTypeNode().TrueType) dispose() falseType := visitor.VisitNode(node.AsConditionalTypeNode().FalseType) return factory.UpdateConditionalTypeNode( node.AsConditionalTypeNode(), checkType, extendsType, trueType, falseType, ) } // style applications if ast.IsTupleTypeNode(node) || (b.ctx.flags&nodebuilder.FlagsMultilineObjectLiterals == 0 && ast.IsTypeLiteralNode(node)) || ast.IsMappedTypeNode(node) { // make tuples/types/mappedtypes single line res := visitor.VisitEachChild(node) if res == node { res = res.Clone(factory) res = b.setTextRange(res, node) } b.e.AddEmitFlags(res, printer.EFSingleLine) return res } if ast.IsStringLiteralLike(node) { // Preserve the original characters of the literal (e.g. emojis) in declaration emit // rather than escaping them as ASCII Unicode escapes. Mirrors TypeScript's behavior // for synthesized string literal types in the node builder (checker.ts:6853). c := node.Clone(b.f) if ast.IsStringLiteral(node) && b.ctx.flags&nodebuilder.FlagsUseSingleQuotesForStringLiteralType != 0 && node.AsStringLiteral().TokenFlags&ast.TokenFlagsSingleQuote == 0 { // set single quote on string literals c.AsStringLiteral().TokenFlags ^= ast.TokenFlagsSingleQuote } b.e.AddEmitFlags(c, printer.EFNoAsciiEscaping) return c } return visitor.VisitEachChild(node) } nonLocalNode := true visitor = ast.NewNodeVisitor(func(node *ast.Node) *ast.Node { // If there was an error in a sibling node bail early, the result will be discarded anyway if bound.hadError { return node } recover_ := bound.startRecoveryScope() introducesNewScope := ast.IsFunctionLike(node) || ast.IsMappedTypeNode(node) var exit func() if introducesNewScope { var params []*ast.Symbol var typeParams []*Type if ast.IsFunctionLike(node) { sig := b.ch.getSignatureFromDeclaration(node) params = sig.parameters typeParams = sig.typeParameters } else if ast.IsConditionalTypeNode(node) { // !!! TODO: impossible in combination with the scope start check??? typeParams = b.ch.getInferTypeParameters(node) } else if ast.IsMappedTypeNode(node) { typeParams = []*Type{b.ch.getDeclaredTypeOfTypeParameter(b.ch.getSymbolOfDeclaration(node.AsMappedTypeNode().TypeParameter))} } exit = b.enterNewScope(node, params, typeParams, nil, nil) } result := visitExistingNodeTreeSymbolsWorker(node) if exit != nil { exit() } if result == node && !ast.NodeIsSynthesized(node) { result = b.f.DeepCloneNode(node) // always clone a new node } // We want to clone the subtree, so when we mark it up with __pos and __end in quickfixes, // we don't get odd behavior because of reused nodes. We also need to clone to _remove_ // the position information if the node comes from a different file than the one the node builder // is set to build for (even though we are reusing the node structure, the position information // would make the printer print invalid spans for literals and identifiers, and the formatter would // choke on the mismatched positonal spans between a parent and an injected child from another file). result = b.setTextRange(result, node) if bound.hadError { if ast.IsTypeNode(node) && !ast.IsTypePredicateNode(node) { bound.endRecoveryScope(recover_) // TODO: this fallback matches strada behavior, but it lacks any verification that the type from `node` actually matches // the type we'd expect at this traversal position within the parent type. t := b.getTypeFromTypeNode(node, false) return b.typeToTypeNode(t) } return b.setTextRange(node.Clone(b.f), node) } return result }, b.f, ast.NodeVisitorHooks{ VisitNodes: func(nodes *ast.NodeList, v *ast.NodeVisitor) *ast.NodeList { res := v.VisitNodes(nodes) if nonLocalNode && res != nil { // Remove position data from node lists originating in other files if res == nodes { res = nodes.Clone(b.f) } res.Loc = core.NewTextRange(-1, -1) } return res }, VisitNode: func(node *ast.Node, v *ast.NodeVisitor) *ast.Node { // Capture if the current node is in the current file so node lists knoww if they can keep positions or not oldNonLocalNode := nonLocalNode nonLocalNode = b.ctx.enclosingFile == nil || b.ctx.enclosingFile != ast.GetSourceFileOfNode(b.e.MostOriginal(node)) res := v.VisitNode(node) nonLocalNode = oldNonLocalNode return res }, }) return visitor }