vendor tsgo
This commit is contained in:
900
tools/tsgo/internal/checker/nodecopy.go
Normal file
900
tools/tsgo/internal/checker/nodecopy.go
Normal file
@@ -0,0 +1,900 @@
|
||||
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<any>` 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
|
||||
}
|
||||
Reference in New Issue
Block a user