From 04400b27f1360accafc997a1fffdb6d3b2ac446d Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 29 Oct 2026 14:55:08 -0700 Subject: [PATCH 1/4] Binary search top-level statements in GetNodeAtPosition GetNodeAtPosition descends from the source file by linearly scanning each node's children. At the top level that scan visits every statement in the file, and ForEachDynamicImportOrRequireCall calls GetNodeAtPosition for every textual "import"/"require" match, so files with many statements and many imports did quadratic work. In non-JS files the top-level statements are in source order and do not overlap, so the first statement whose end is past the position can be found by binary search; it is the only candidate that can contain the position. JS files keep the linear scan because reparsed JSDoc statements (@typedef, @import, ...) are inserted ahead of the statement that hosts them and their ranges nest within it. Co-authored-by: Steven Chong <25894545+teamchong@users.noreply.github.com> --- tsc/internal/ast/utilities.go | 43 +++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/tsc/internal/ast/utilities.go b/tsc/internal/ast/utilities.go index f178903372dc2..be3f86b6c844a 100644 --- a/tsc/internal/ast/utilities.go +++ b/tsc/internal/ast/utilities.go @@ -2795,13 +2795,17 @@ func GetNodeAtPosition(file *SourceFile, position int, includeJSDoc bool) *Node } } if child == nil { - current.ForEachChild(func(node *Node) bool { - if nodeContainsPosition(node, position) { - child = node - return true - } - return false - }) + if current == file.AsNode() && !file.IsJS() { + child = getTopLevelNodeAtPosition(file, position) + } else { + current.ForEachChild(func(node *Node) bool { + if nodeContainsPosition(node, position) { + child = node + return true + } + return false + }) + } } if child == nil || IsMetaProperty(child) { return current @@ -2810,6 +2814,31 @@ func GetNodeAtPosition(file *SourceFile, position int, includeJSDoc bool) *Node } } +// getTopLevelNodeAtPosition is equivalent to a linear search over the children of +// the source file, but binary searches the statements. This is only valid for +// non-JS files, where the statements are in source order and do not overlap; JS +// files may contain reparsed JSDoc statements whose ranges nest within other +// statements. +func getTopLevelNodeAtPosition(file *SourceFile, position int) *Node { + var statements []*Node + if file.Statements != nil { + statements = file.Statements.Nodes + } + i, _ := slices.BinarySearchFunc(statements, position, func(node *Node, position int) int { + if node.End() <= position { + return -1 + } + return 1 + }) + if i < len(statements) && nodeContainsPosition(statements[i], position) { + return statements[i] + } + if file.EndOfFileToken != nil && nodeContainsPosition(file.EndOfFileToken, position) { + return file.EndOfFileToken + } + return nil +} + func nodeContainsPosition(node *Node, position int) bool { return node.Kind >= KindFirstNode && node.Pos() <= position && (position < node.End() || position == node.End() && node.Kind == KindEndOfFile) } From 18b60dcfa9a54e1e1ab6a4752eb6389eca645693 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 29 Oct 2026 15:01:12 -0700 Subject: [PATCH 2/4] Search for import/require with strings.Index ForEachDynamicImportOrRequireCall scanned the source text with strings.IndexAny(text, "ir"), which walks the text byte by byte and stops at every 'i' and 'r' (both very common letters) only to compare and usually reject the keyword there. Search for each keyword with strings.Index instead, which uses the vectorized byte search, and remember the next occurrence of each keyword so it is only searched for again once the scan has passed it. Matches are produced in exactly the same order as before. Co-authored-by: Steven Chong <25894545+teamchong@users.noreply.github.com> --- tsc/internal/ast/utilities.go | 64 ++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/tsc/internal/ast/utilities.go b/tsc/internal/ast/utilities.go index be3f86b6c844a..60e7af0d94775 100644 --- a/tsc/internal/ast/utilities.go +++ b/tsc/internal/ast/utilities.go @@ -2843,33 +2843,50 @@ func nodeContainsPosition(node *Node, position int) bool { return node.Kind >= KindFirstNode && node.Pos() <= position && (position < node.End() || position == node.End() && node.Kind == KindEndOfFile) } -func findImportOrRequire(text string, start int) (index int, size int) { - index = max(start, 0) - n := len(text) - for index < n { - next := strings.IndexAny(text[index:], "ir") - if next < 0 { - break - } - index += next +// importOrRequireFinder finds successive occurrences of "import" or "require" +// in text. It remembers the next occurrence of each keyword so that each one is +// only searched for again once it has been passed. +type importOrRequireFinder struct { + text string + nextImport int + nextRequire int +} - var expected string - if text[index] == 'i' { - size = 6 - expected = "import" - } else { - size = 7 - expected = "require" - } - if index+size <= n && text[index:index+size] == expected { - return index, size - } - index++ +func newImportOrRequireFinder(text string) importOrRequireFinder { + return importOrRequireFinder{ + text: text, + nextImport: indexFrom(text, "import", 0), + nextRequire: indexFrom(text, "require", 0), } +} +// next returns the index and length of the first "import" or "require" at or after start, or -1. +func (f *importOrRequireFinder) next(start int) (index int, size int) { + if f.nextImport >= 0 && f.nextImport < start { + f.nextImport = indexFrom(f.text, "import", start) + } + if f.nextRequire >= 0 && f.nextRequire < start { + f.nextRequire = indexFrom(f.text, "require", start) + } + if f.nextImport >= 0 && (f.nextRequire < 0 || f.nextImport < f.nextRequire) { + return f.nextImport, len("import") + } + if f.nextRequire >= 0 { + return f.nextRequire, len("require") + } return -1, 0 } +func indexFrom(text string, substr string, start int) int { + if start > len(text) { + return -1 + } + if i := strings.Index(text[start:], substr); i >= 0 { + return start + i + } + return -1 +} + func ForEachDynamicImportOrRequireCall( file *SourceFile, includeTypeSpaceImports bool, @@ -2877,7 +2894,8 @@ func ForEachDynamicImportOrRequireCall( cb func(node *Node, argument *Expression) bool, ) bool { isJavaScriptFile := IsInJSFile(file.AsNode()) - lastIndex, size := findImportOrRequire(file.Text(), 0) + finder := newImportOrRequireFinder(file.Text()) + lastIndex, size := finder.next(0) for lastIndex >= 0 { node := GetNodeAtPosition(file, lastIndex, isJavaScriptFile && includeTypeSpaceImports) if isJavaScriptFile && IsRequireCall(node, requireStringLiteralLikeArgument) { @@ -2895,7 +2913,7 @@ func ForEachDynamicImportOrRequireCall( } // skip past import/require lastIndex += size - lastIndex, size = findImportOrRequire(file.Text(), lastIndex) + lastIndex, size = finder.next(lastIndex) } return false } From 89801eccb55443b133f0a19d4bd0c93462e068d8 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 29 Oct 2026 15:09:56 -0700 Subject: [PATCH 3/4] Arena-allocate more frequently created node kinds The node factory already allocates the most common node kinds from per-factory arenas, which batches many small heap allocations into a few larger ones and keeps nodes of the same kind close together in memory. A number of other kinds that are created in large numbers when parsing real code and declaration files (imports, object and array literals, arrow functions, methods and properties, binding patterns, template literals, and several type nodes) were still allocated one at a time. Mark 28 more node kinds as arena-allocated so they take the same path. Co-authored-by: Steven Chong <25894545+teamchong@users.noreply.github.com> --- tools/scripts/tsc/ast.json | 84 ++++++++++++++++++---------- tsc/internal/ast/ast_generated.go | 92 ++++++++++++++++++++----------- 2 files changed, 116 insertions(+), 60 deletions(-) diff --git a/tools/scripts/tsc/ast.json b/tools/scripts/tsc/ast.json index a133915a10665..cd155f30cb845 100644 --- a/tools/scripts/tsc/ast.json +++ b/tools/scripts/tsc/ast.json @@ -1743,7 +1743,8 @@ "type": "BindingElement", "list": "NodeList" } - ] + ], + "arena": true }, "ParameterDeclaration": { "kind": "Parameter", @@ -1816,7 +1817,8 @@ "type": "Expression", "optional": true } - ] + ], + "arena": true }, "MissingDeclaration": { "extends": [ @@ -2141,7 +2143,8 @@ "type": "ImportAttributes", "optional": true } - ] + ], + "arena": true }, "ExternalModuleReference": { "generateSubtreeFacts": true, @@ -2182,7 +2185,8 @@ "type": "ImportSpecifier", "list": "NodeList" } - ] + ], + "arena": true }, "ExportAssignment": { "extends": [ @@ -2577,7 +2581,8 @@ "optional": true, "visit": "functionBody" } - ] + ], + "arena": true }, "PropertySignatureDeclaration": { "kind": "PropertySignature", @@ -2643,7 +2648,8 @@ "type": "Expression", "optional": true } - ] + ], + "arena": true }, "SemicolonClassElement": { "extends": [ @@ -2930,7 +2936,8 @@ "type": "ConciseBody", "visit": "functionBody" } - ] + ], + "arena": true }, "FunctionExpression": { "extends": [ @@ -3001,7 +3008,8 @@ "name": "Type", "type": "TypeNode" } - ] + ], + "arena": true }, "SatisfiesExpression": { "extends": [ @@ -3229,7 +3237,8 @@ "type": "TemplateSpan", "list": "NodeList" } - ] + ], + "arena": true }, "TemplateSpan": { "generateSubtreeFacts": true, @@ -3245,7 +3254,8 @@ "name": "Literal", "type": "TemplateMiddleOrTail" } - ] + ], + "arena": true }, "TaggedTemplateExpression": { "extends": [ @@ -3307,7 +3317,8 @@ "name": "MultiLine", "type": "bool" } - ] + ], + "arena": true }, "ObjectLiteralExpression": { "generateSubtreeFacts": true, @@ -3326,7 +3337,8 @@ "name": "MultiLine", "type": "bool" } - ] + ], + "arena": true }, "SpreadAssignment": { "extends": [ @@ -3408,7 +3420,8 @@ "type": "Expression", "optional": true } - ] + ], + "arena": true }, "DeleteExpression": { "generateSubtreeFacts": true, @@ -3432,7 +3445,8 @@ "name": "Expression", "type": "Expression" } - ] + ], + "arena": true }, "VoidExpression": { "generateSubtreeFacts": true, @@ -3455,7 +3469,8 @@ "name": "Expression", "type": "Expression" } - ] + ], + "arena": true }, "TypeAssertion": { "kind": "TypeAssertionExpression", @@ -3542,7 +3557,8 @@ "name": "FalseType", "type": "TypeNode" } - ] + ], + "arena": true }, "TypeOperatorNode": { "kind": "TypeOperator", @@ -3575,7 +3591,8 @@ "name": "TypeParameter", "type": "TypeParameterDeclaration" } - ] + ], + "arena": true }, "ArrayTypeNode": { "kind": "ArrayType", @@ -3660,7 +3677,8 @@ "kind": "ThisType", "extends": [ "TypeNodeBase" - ] + ], + "arena": true }, "TypePredicateNode": { "kind": "TypePredicate", @@ -3741,7 +3759,8 @@ "name": "TypeArguments", "inherited": true } - ] + ], + "arena": true }, "MappedTypeNode": { "kind": "MappedType", @@ -3819,7 +3838,8 @@ "type": "TypeNode", "list": "NodeList" } - ] + ], + "arena": true }, "NamedTupleMember": { "extends": [ @@ -3870,7 +3890,8 @@ "name": "Type", "type": "TypeNode" } - ] + ], + "arena": true }, "ParenthesizedTypeNode": { "kind": "ParenthesizedType", @@ -3949,7 +3970,8 @@ "inherited": true, "bitmask": "TokenFlagsTemplateLiteralLikeFlags" } - ] + ], + "arena": true }, "TemplateMiddle": { "extends": [ @@ -3970,7 +3992,8 @@ "inherited": true, "bitmask": "TokenFlagsTemplateLiteralLikeFlags" } - ] + ], + "arena": true }, "TemplateTail": { "extends": [ @@ -3991,7 +4014,8 @@ "inherited": true, "bitmask": "TokenFlagsTemplateLiteralLikeFlags" } - ] + ], + "arena": true }, "TemplateLiteralTypeNode": { "kind": "TemplateLiteralType", @@ -4008,7 +4032,8 @@ "type": "TemplateLiteralTypeSpan", "list": "NodeList" } - ] + ], + "arena": true }, "TemplateLiteralTypeSpan": { "extends": [ @@ -4023,7 +4048,8 @@ "name": "Literal", "type": "TemplateMiddleOrTail" } - ] + ], + "arena": true }, "SyntheticExpression": { "extends": [ @@ -4916,7 +4942,8 @@ "name": "TypeArguments", "inherited": true } - ] + ], + "arena": true }, "ImportClause": { "extends": [ @@ -4942,7 +4969,8 @@ "type": "NamedImportBindings", "optional": true } - ] + ], + "arena": true }, "ImportSpecifier": { "extends": [ diff --git a/tsc/internal/ast/ast_generated.go b/tsc/internal/ast/ast_generated.go index 58aef6d7bc11c..17f2d3520c566 100644 --- a/tsc/internal/ast/ast_generated.go +++ b/tsc/internal/ast/ast_generated.go @@ -20,11 +20,18 @@ var ( type NodeFactory struct { hooks NodeFactoryHooks + arrayLiteralExpressionArena core.Arena[ArrayLiteralExpression] arrayTypeNodeArena core.Arena[ArrayTypeNode] + arrowFunctionArena core.Arena[ArrowFunction] + asExpressionArena core.Arena[AsExpression] + awaitExpressionArena core.Arena[AwaitExpression] binaryExpressionArena core.Arena[BinaryExpression] + bindingElementArena core.Arena[BindingElement] + bindingPatternArena core.Arena[BindingPattern] blockArena core.Arena[Block] callExpressionArena core.Arena[CallExpression] conditionalExpressionArena core.Arena[ConditionalExpression] + conditionalTypeNodeArena core.Arena[ConditionalTypeNode] constructSignatureDeclarationArena core.Arena[ConstructSignatureDeclaration] elementAccessExpressionArena core.Arena[ElementAccessExpression] expressionStatementArena core.Arena[ExpressionStatement] @@ -34,8 +41,12 @@ type NodeFactory struct { heritageClauseArena core.Arena[HeritageClause] identifierArena core.Arena[Identifier] ifStatementArena core.Arena[IfStatement] + importClauseArena core.Arena[ImportClause] + importDeclarationArena core.Arena[ImportDeclaration] importSpecifierArena core.Arena[ImportSpecifier] + importTypeNodeArena core.Arena[ImportTypeNode] indexedAccessTypeNodeArena core.Arena[IndexedAccessTypeNode] + inferTypeNodeArena core.Arena[InferTypeNode] interfaceDeclarationArena core.Arena[InterfaceDeclaration] intersectionTypeNodeArena core.Arena[IntersectionTypeNode] jsdocArena core.Arena[JSDoc] @@ -45,24 +56,41 @@ type NodeFactory struct { keywordExpressionArena core.Arena[KeywordExpression] keywordTypeNodeArena core.Arena[KeywordTypeNode] literalTypeNodeArena core.Arena[LiteralTypeNode] + methodDeclarationArena core.Arena[MethodDeclaration] methodSignatureDeclarationArena core.Arena[MethodSignatureDeclaration] modifierListArena core.Arena[ModifierList] + namedImportsArena core.Arena[NamedImports] nodeListArena core.Arena[NodeList] numericLiteralArena core.Arena[NumericLiteral] + objectLiteralExpressionArena core.Arena[ObjectLiteralExpression] parameterDeclarationArena core.Arena[ParameterDeclaration] parenthesizedExpressionArena core.Arena[ParenthesizedExpression] parenthesizedTypeNodeArena core.Arena[ParenthesizedTypeNode] prefixUnaryExpressionArena core.Arena[PrefixUnaryExpression] propertyAccessExpressionArena core.Arena[PropertyAccessExpression] propertyAssignmentArena core.Arena[PropertyAssignment] + propertyDeclarationArena core.Arena[PropertyDeclaration] propertySignatureDeclarationArena core.Arena[PropertySignatureDeclaration] + restTypeNodeArena core.Arena[RestTypeNode] returnStatementArena core.Arena[ReturnStatement] + shorthandPropertyAssignmentArena core.Arena[ShorthandPropertyAssignment] stringLiteralArena core.Arena[StringLiteral] + templateExpressionArena core.Arena[TemplateExpression] + templateHeadArena core.Arena[TemplateHead] + templateLiteralTypeNodeArena core.Arena[TemplateLiteralTypeNode] + templateLiteralTypeSpanArena core.Arena[TemplateLiteralTypeSpan] + templateMiddleArena core.Arena[TemplateMiddle] + templateSpanArena core.Arena[TemplateSpan] + templateTailArena core.Arena[TemplateTail] + thisTypeNodeArena core.Arena[ThisTypeNode] tokenArena core.Arena[Token] + tupleTypeNodeArena core.Arena[TupleTypeNode] typeAliasDeclarationArena core.Arena[TypeAliasDeclaration] typeLiteralNodeArena core.Arena[TypeLiteralNode] + typeOfExpressionArena core.Arena[TypeOfExpression] typeOperatorNodeArena core.Arena[TypeOperatorNode] typeParameterDeclarationArena core.Arena[TypeParameterDeclaration] + typeQueryNodeArena core.Arena[TypeQueryNode] typeReferenceNodeArena core.Arena[TypeReferenceNode] unionTypeNodeArena core.Arena[UnionTypeNode] variableDeclarationArena core.Arena[VariableDeclaration] @@ -622,13 +650,13 @@ func (f *NodeFactory) NewToken(kind TokenSyntaxKind) *Node { data := &NoSubstitutionTemplateLiteral{} return f.newNode(kind, data.AsNode()) case KindTemplateHead: - data := &TemplateHead{} + data := f.templateHeadArena.New() return f.newNode(kind, data.AsNode()) case KindTemplateMiddle: - data := &TemplateMiddle{} + data := f.templateMiddleArena.New() return f.newNode(kind, data.AsNode()) case KindTemplateTail: - data := &TemplateTail{} + data := f.templateTailArena.New() return f.newNode(kind, data.AsNode()) case KindIdentifier: data := f.identifierArena.New() @@ -1858,7 +1886,7 @@ func (f *NodeFactory) NewBindingPattern(kind Kind, elements *BindingElementList) default: panic("Invalid node kind for BindingPattern") } - data := &BindingPattern{} + data := f.bindingPatternArena.New() data.Elements = elements return f.newNode(kind, data.AsNode()) } @@ -1966,7 +1994,7 @@ type BindingElement struct { } func (f *NodeFactory) NewBindingElement(dotDotDotToken *DotDotDotToken, propertyName *PropertyName, name *BindingName, initializer *Expression) *Node { - data := &BindingElement{} + data := f.bindingElementArena.New() data.DotDotDotToken = dotDotDotToken data.PropertyName = propertyName data.name = name @@ -2584,7 +2612,7 @@ type ImportDeclaration struct { } func (f *NodeFactory) NewImportDeclaration(modifiers *ModifierList, importClause *ImportClauseNode, moduleSpecifier *Expression, attributes *ImportAttributesNode) *Node { - data := &ImportDeclaration{} + data := f.importDeclarationArena.New() data.modifiers = modifiers data.ImportClause = importClause data.ModuleSpecifier = moduleSpecifier @@ -2593,7 +2621,7 @@ func (f *NodeFactory) NewImportDeclaration(modifiers *ModifierList, importClause } func (f *NodeFactory) NewJSImportDeclaration(modifiers *ModifierList, importClause *ImportClauseNode, moduleSpecifier *Expression, attributes *ImportAttributesNode) *Node { - data := &ImportDeclaration{} + data := f.importDeclarationArena.New() data.modifiers = modifiers data.ImportClause = importClause data.ModuleSpecifier = moduleSpecifier @@ -2753,7 +2781,7 @@ type NamedImports struct { } func (f *NodeFactory) NewNamedImports(elements *ImportSpecifierList) *Node { - data := &NamedImports{} + data := f.namedImportsArena.New() data.Elements = elements return f.newNode(KindNamedImports, data.AsNode()) } @@ -3378,7 +3406,7 @@ type MethodDeclaration struct { } func (f *NodeFactory) NewMethodDeclaration(modifiers *ModifierList, asteriskToken *AsteriskToken, name *PropertyName, postfixToken *TokenNode, typeParameters *TypeParameterList, parameters *ParameterList, typeNode *TypeNode, fullSignature *TypeNode, body *FunctionBody) *Node { - data := &MethodDeclaration{} + data := f.methodDeclarationArena.New() data.modifiers = modifiers data.AsteriskToken = asteriskToken data.name = name @@ -3496,7 +3524,7 @@ type PropertyDeclaration struct { } func (f *NodeFactory) NewPropertyDeclaration(modifiers *ModifierList, name *PropertyName, postfixToken *TokenNode, typeNode *TypeNode, initializer *Expression) *Node { - data := &PropertyDeclaration{} + data := f.propertyDeclarationArena.New() data.modifiers = modifiers data.name = name data.PostfixToken = postfixToken @@ -3973,7 +4001,7 @@ type ArrowFunction struct { } func (f *NodeFactory) NewArrowFunction(modifiers *ModifierList, typeParameters *TypeParameterList, parameters *ParameterList, typeNode *TypeNode, fullSignature *TypeNode, equalsGreaterThanToken *EqualsGreaterThanToken, body *ConciseBody) *Node { - data := &ArrowFunction{} + data := f.arrowFunctionArena.New() data.modifiers = modifiers data.TypeParameters = typeParameters data.Parameters = parameters @@ -4086,7 +4114,7 @@ type AsExpression struct { } func (f *NodeFactory) NewAsExpression(expression *Expression, typeNode *TypeNode) *Node { - data := &AsExpression{} + data := f.asExpressionArena.New() data.Expression = expression data.Type = typeNode return f.newNode(KindAsExpression, data.AsNode()) @@ -4546,7 +4574,7 @@ type TemplateExpression struct { } func (f *NodeFactory) NewTemplateExpression(head *TemplateHeadNode, templateSpans *TemplateSpanList) *Node { - data := &TemplateExpression{} + data := f.templateExpressionArena.New() data.Head = head data.TemplateSpans = templateSpans return f.newNode(KindTemplateExpression, data.AsNode()) @@ -4591,7 +4619,7 @@ type TemplateSpan struct { } func (f *NodeFactory) NewTemplateSpan(expression *Expression, literal *TemplateMiddleOrTail) *Node { - data := &TemplateSpan{} + data := f.templateSpanArena.New() data.Expression = expression data.Literal = literal return f.newNode(KindTemplateSpan, data.AsNode()) @@ -4729,7 +4757,7 @@ type ArrayLiteralExpression struct { } func (f *NodeFactory) NewArrayLiteralExpression(elements *ElementList, multiLine bool) *Node { - data := &ArrayLiteralExpression{} + data := f.arrayLiteralExpressionArena.New() data.Elements = elements data.MultiLine = multiLine return f.newNode(KindArrayLiteralExpression, data.AsNode()) @@ -4775,7 +4803,7 @@ type ObjectLiteralExpression struct { } func (f *NodeFactory) NewObjectLiteralExpression(properties *NodeList, multiLine bool) *Node { - data := &ObjectLiteralExpression{} + data := f.objectLiteralExpressionArena.New() data.Properties = properties data.MultiLine = multiLine return f.newNode(KindObjectLiteralExpression, data.AsNode()) @@ -4919,7 +4947,7 @@ type ShorthandPropertyAssignment struct { } func (f *NodeFactory) NewShorthandPropertyAssignment(modifiers *ModifierList, name *PropertyName, postfixToken *TokenNode, typeNode *TypeNode, equalsToken *EqualsToken, objectAssignmentInitializer *Expression) *Node { - data := &ShorthandPropertyAssignment{} + data := f.shorthandPropertyAssignmentArena.New() data.modifiers = modifiers data.name = name data.PostfixToken = postfixToken @@ -5013,7 +5041,7 @@ type TypeOfExpression struct { } func (f *NodeFactory) NewTypeOfExpression(expression *Expression) *Node { - data := &TypeOfExpression{} + data := f.typeOfExpressionArena.New() data.Expression = expression return f.newNode(KindTypeOfExpression, data.AsNode()) } @@ -5097,7 +5125,7 @@ type AwaitExpression struct { } func (f *NodeFactory) NewAwaitExpression(expression *Expression) *Node { - data := &AwaitExpression{} + data := f.awaitExpressionArena.New() data.Expression = expression return f.newNode(KindAwaitExpression, data.AsNode()) } @@ -5283,7 +5311,7 @@ type ConditionalTypeNode struct { } func (f *NodeFactory) NewConditionalTypeNode(checkType *TypeNode, extendsType *TypeNode, trueType *TypeNode, falseType *TypeNode) *Node { - data := &ConditionalTypeNode{} + data := f.conditionalTypeNodeArena.New() data.CheckType = checkType data.ExtendsType = extendsType data.TrueType = trueType @@ -5367,7 +5395,7 @@ type InferTypeNode struct { } func (f *NodeFactory) NewInferTypeNode(typeParameter *TypeParameterDeclarationNode) *Node { - data := &InferTypeNode{} + data := f.inferTypeNodeArena.New() data.TypeParameter = typeParameter return f.newNode(KindInferType, data.AsNode()) } @@ -5600,7 +5628,7 @@ type ThisTypeNode struct { } func (f *NodeFactory) NewThisTypeNode() *Node { - data := &ThisTypeNode{} + data := f.thisTypeNodeArena.New() return f.newNode(KindThisType, data.AsNode()) } @@ -5761,7 +5789,7 @@ type TypeQueryNode struct { } func (f *NodeFactory) NewTypeQueryNode(exprName *EntityName, typeArguments *TypeList) *Node { - data := &TypeQueryNode{} + data := f.typeQueryNodeArena.New() data.ExprName = exprName data.TypeArguments = typeArguments return f.newNode(KindTypeQuery, data.AsNode()) @@ -5894,7 +5922,7 @@ type TupleTypeNode struct { } func (f *NodeFactory) NewTupleTypeNode(elements *TypeList) *Node { - data := &TupleTypeNode{} + data := f.tupleTypeNodeArena.New() data.Elements = elements return f.newNode(KindTupleType, data.AsNode()) } @@ -6022,7 +6050,7 @@ type RestTypeNode struct { } func (f *NodeFactory) NewRestTypeNode(typeNode *TypeNode) *Node { - data := &RestTypeNode{} + data := f.restTypeNodeArena.New() data.Type = typeNode return f.newNode(KindRestType, data.AsNode()) } @@ -6180,7 +6208,7 @@ type TemplateHead struct { } func (f *NodeFactory) NewTemplateHead(text string, rawText string, templateFlags TokenFlags) *Node { - data := &TemplateHead{} + data := f.templateHeadArena.New() data.Text = text data.RawText = rawText data.TemplateFlags = templateFlags & TokenFlagsTemplateLiteralLikeFlags @@ -6206,7 +6234,7 @@ type TemplateMiddle struct { } func (f *NodeFactory) NewTemplateMiddle(text string, rawText string, templateFlags TokenFlags) *Node { - data := &TemplateMiddle{} + data := f.templateMiddleArena.New() data.Text = text data.RawText = rawText data.TemplateFlags = templateFlags & TokenFlagsTemplateLiteralLikeFlags @@ -6232,7 +6260,7 @@ type TemplateTail struct { } func (f *NodeFactory) NewTemplateTail(text string, rawText string, templateFlags TokenFlags) *Node { - data := &TemplateTail{} + data := f.templateTailArena.New() data.Text = text data.RawText = rawText data.TemplateFlags = templateFlags & TokenFlagsTemplateLiteralLikeFlags @@ -6259,7 +6287,7 @@ type TemplateLiteralTypeNode struct { } func (f *NodeFactory) NewTemplateLiteralTypeNode(head *TemplateHeadNode, templateSpans *TemplateLiteralTypeSpanList) *Node { - data := &TemplateLiteralTypeNode{} + data := f.templateLiteralTypeNodeArena.New() data.Head = head data.TemplateSpans = templateSpans return f.newNode(KindTemplateLiteralType, data.AsNode()) @@ -6299,7 +6327,7 @@ type TemplateLiteralTypeSpan struct { } func (f *NodeFactory) NewTemplateLiteralTypeSpan(typeNode *TypeNode, literal *TemplateMiddleOrTail) *Node { - data := &TemplateLiteralTypeSpan{} + data := f.templateLiteralTypeSpanArena.New() data.Type = typeNode data.Literal = literal return f.newNode(KindTemplateLiteralTypeSpan, data.AsNode()) @@ -8272,7 +8300,7 @@ type ImportTypeNode struct { } func (f *NodeFactory) NewImportTypeNode(isTypeOf bool, argument *TypeNode, attributes *ImportAttributesNode, qualifier *EntityName, typeArguments *TypeList) *Node { - data := &ImportTypeNode{} + data := f.importTypeNodeArena.New() data.IsTypeOf = isTypeOf data.Argument = argument data.Attributes = attributes @@ -8322,7 +8350,7 @@ type ImportClause struct { } func (f *NodeFactory) NewImportClause(phaseModifier ImportPhaseModifierSyntaxKind, name *IdentifierNode, namedBindings *NamedImportBindings) *Node { - data := &ImportClause{} + data := f.importClauseArena.New() data.PhaseModifier = phaseModifier data.name = name data.NamedBindings = namedBindings From 9ad5155e219fa77f0763839aef9d016c9c655d26 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 29 Oct 2026 16:28:40 -0700 Subject: [PATCH 4/4] Return early from indexFrom at the end of the text --- tsc/internal/ast/utilities.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsc/internal/ast/utilities.go b/tsc/internal/ast/utilities.go index 60e7af0d94775..8431c9e16dc29 100644 --- a/tsc/internal/ast/utilities.go +++ b/tsc/internal/ast/utilities.go @@ -2878,7 +2878,7 @@ func (f *importOrRequireFinder) next(start int) (index int, size int) { } func indexFrom(text string, substr string, start int) int { - if start > len(text) { + if start >= len(text) { return -1 } if i := strings.Index(text[start:], substr); i >= 0 {