From b6697009ffb6658739576466670e0307d7a144b9 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 1 Nov 2022 18:24:20 -0400 Subject: [PATCH] Move less-used fields off of Node to reduce polymorphism --- .gitignore | 1 + src/compiler/binder.ts | 72 +++---- src/compiler/checker.ts | 199 ++++++++++-------- src/compiler/emitter.ts | 48 ++--- src/compiler/factory/emitHelpers.ts | 4 +- src/compiler/factory/emitNode.ts | 50 +++-- src/compiler/factory/nodeFactory.ts | 167 ++++++++++----- src/compiler/factory/utilities.ts | 20 +- src/compiler/parser.ts | 20 +- src/compiler/program.ts | 2 +- src/compiler/transformers/classFields.ts | 2 +- src/compiler/transformers/declarations.ts | 4 +- src/compiler/transformers/jsx.ts | 2 +- src/compiler/transformers/module/module.ts | 2 +- src/compiler/types.ts | 101 ++++++--- src/compiler/utilities.ts | 128 ++++++++++- src/compiler/utilitiesPublic.ts | 11 +- src/harness/harnessUtils.ts | 13 +- .../codefixes/fixImportNonExportedMember.ts | 2 +- src/services/completions.ts | 2 +- src/services/jsDoc.ts | 2 +- src/services/navigationBar.ts | 2 +- src/services/services.ts | 6 +- src/services/smartSelection.ts | 8 +- src/services/textChanges.ts | 22 +- src/services/utilities.ts | 2 +- .../reference/api/tsserverlibrary.d.ts | 8 +- tests/baselines/reference/api/typescript.d.ts | 8 +- 28 files changed, 579 insertions(+), 329 deletions(-) diff --git a/.gitignore b/.gitignore index afd10c0786da9..7da43014e005c 100644 --- a/.gitignore +++ b/.gitignore @@ -84,3 +84,4 @@ tests/cases/user/puppeteer/puppeteer tests/cases/user/axios-src/axios-src tests/cases/user/prettier/prettier .eslintcache +isolate-*-v8.log \ No newline at end of file diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 4c3eac092b45a..29b93efb19e87 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -250,7 +250,7 @@ namespace ts { Debug.attachFlowNodeDebugInfo(unreachableFlow); Debug.attachFlowNodeDebugInfo(reportedUnreachableFlow); - if (!file.locals) { + if (!getBindExtraFields(file)?.locals) { tracing?.push(tracing.Phase.Bind, "bindSourceFile", { path: file.path }, /*separateBeginAndEnd*/ true); bind(file); tracing?.pop(); @@ -553,7 +553,7 @@ namespace ts { return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); } else { - return declareSymbol(container.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); + return declareSymbol(getBindExtraFields(container)!.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); } } else { @@ -574,17 +574,18 @@ namespace ts { // and should never be merged directly with other augmentation, and the latter case would be possible if automatic merge is allowed. if (isJSDocTypeAlias(node)) Debug.assert(isInJSFile(node)); // We shouldn't add symbols for JSDoc nodes if not in a JS file. if (!isAmbientModule(node) && (hasExportModifier || container.flags & NodeFlags.ExportContext)) { - if (!container.locals || (hasSyntacticModifier(node, ModifierFlags.Default) && !getDeclarationName(node))) { + const containerBindNode = getBindExtraFields(container); + if (!containerBindNode?.locals || (hasSyntacticModifier(node, ModifierFlags.Default) && !getDeclarationName(node))) { return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); // No local symbol for an unnamed default! } const exportKind = symbolFlags & SymbolFlags.Value ? SymbolFlags.ExportValue : 0; - const local = declareSymbol(container.locals, /*parent*/ undefined, node, exportKind, symbolExcludes); + const local = declareSymbol(containerBindNode.locals, /*parent*/ undefined, node, exportKind, symbolExcludes); local.exportSymbol = declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); - node.localSymbol = local; + getOrCreateBindExtraFields(node).localSymbol = local; return local; } else { - return declareSymbol(container.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); + return declareSymbol(getBindExtraFields(container)!.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); } } } @@ -641,13 +642,13 @@ namespace ts { } container = blockScopeContainer = node; if (containerFlags & ContainerFlags.HasLocals) { - container.locals = createSymbolTable(); + getOrCreateBindExtraFields(container).locals = createSymbolTable(); } addToContainerChain(container); } else if (containerFlags & ContainerFlags.IsBlockScopedContainer) { blockScopeContainer = node; - blockScopeContainer.locals = undefined; + getOrCreateBindExtraFields(blockScopeContainer).locals = undefined; } if (containerFlags & ContainerFlags.IsControlFlowContainer) { const saveCurrentFlow = currentFlow; @@ -685,7 +686,7 @@ namespace ts { if (!(currentFlow.flags & FlowFlags.Unreachable) && containerFlags & ContainerFlags.IsFunctionLike && nodeIsPresent((node as FunctionLikeDeclaration | ClassStaticBlockDeclaration).body)) { node.flags |= NodeFlags.HasImplicitReturn; if (hasExplicitReturn) node.flags |= NodeFlags.HasExplicitReturn; - (node as FunctionLikeDeclaration | ClassStaticBlockDeclaration).endFlowNode = currentFlow; + setEndFlowNode(node as FunctionLikeDeclarationBase | ClassStaticBlockDeclaration, currentFlow); } if (node.kind === SyntaxKind.SourceFile) { node.flags |= emitFlags; @@ -696,7 +697,7 @@ namespace ts { addAntecedent(currentReturnTarget, currentFlow); currentFlow = finishFlowLabel(currentReturnTarget); if (node.kind === SyntaxKind.Constructor || node.kind === SyntaxKind.ClassStaticBlockDeclaration || (isInJSFile(node) && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression))) { - (node as FunctionLikeDeclaration | ClassStaticBlockDeclaration).returnFlowNode = currentFlow; + setReturnFlowNode(node as FunctionLikeDeclaration | ClassStaticBlockDeclaration, currentFlow); } } if (!isImmediatelyInvoked) { @@ -752,7 +753,7 @@ namespace ts { return; } if (node.kind >= SyntaxKind.FirstStatement && node.kind <= SyntaxKind.LastStatement && !options.allowUnreachableCode) { - node.flowNode = currentFlow; + setFlowNode(node, currentFlow); } switch (node.kind) { case SyntaxKind.WhileStatement: @@ -1352,7 +1353,7 @@ namespace ts { bind(clause); fallthroughFlow = currentFlow; if (!(currentFlow.flags & FlowFlags.Unreachable) && i !== clauses.length - 1 && options.noFallthroughCasesInSwitch) { - clause.fallthroughFlowNode = currentFlow; + setFallthroughFlowNode(clause, currentFlow); } } } @@ -1905,7 +1906,7 @@ namespace ts { function addToContainerChain(next: Node) { if (lastContainer) { - lastContainer.nextContainer = next; + getOrCreateBindExtraFields(lastContainer).nextContainer = next; } lastContainer = next; @@ -1968,7 +1969,7 @@ namespace ts { // their container in the tree). To accomplish this, we simply add their declared // symbol to the 'locals' of the container. These symbols can then be found as // the type checker walks up the containers, checking them for matching names. - return declareSymbol(container.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); + return declareSymbol(getBindExtraFields(container)!.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); } } @@ -1981,7 +1982,7 @@ namespace ts { function declareSourceFileMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { return isExternalModule(file) ? declareModuleMember(node, symbolFlags, symbolExcludes) - : declareSymbol(file.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); + : declareSymbol(getBindExtraFields(container)!.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); } function hasExportDeclarations(node: ModuleDeclaration | SourceFile): boolean { @@ -2095,11 +2096,12 @@ namespace ts { } // falls through default: - if (!blockScopeContainer.locals) { - blockScopeContainer.locals = createSymbolTable(); + const bindNode = getOrCreateBindExtraFields(blockScopeContainer); + if (!bindNode.locals) { + bindNode.locals = createSymbolTable(); addToContainerChain(blockScopeContainer); } - declareSymbol(blockScopeContainer.locals, /*parent*/ undefined, node, symbolFlags, symbolExcludes); + declareSymbol(bindNode.locals, /*parent*/ undefined, node, symbolFlags, symbolExcludes); } } @@ -2450,12 +2452,12 @@ namespace ts { function bindJSDoc(node: Node) { if (hasJSDocNodes(node)) { if (isInJSFile(node)) { - for (const j of node.jsDoc!) { + for (const j of getJSDocExtraFields(node)!.jsDoc!) { bind(j); } } else { - for (const j of node.jsDoc!) { + for (const j of getJSDocExtraFields(node)!.jsDoc!) { setParent(j, node); setParentRecursive(j, /*incremental*/ false); } @@ -2505,17 +2507,17 @@ namespace ts { // falls through case SyntaxKind.ThisKeyword: if (currentFlow && (isExpression(node) || parent.kind === SyntaxKind.ShorthandPropertyAssignment)) { - node.flowNode = currentFlow; + setFlowNode(node, currentFlow); } return checkContextualIdentifier(node as Identifier); case SyntaxKind.QualifiedName: if (currentFlow && isPartOfTypeQuery(node)) { - node.flowNode = currentFlow; + setFlowNode(node, currentFlow); } break; case SyntaxKind.MetaProperty: case SyntaxKind.SuperKeyword: - node.flowNode = currentFlow; + setFlowNode(node, currentFlow); break; case SyntaxKind.PrivateIdentifier: return checkPrivateIdentifier(node as PrivateIdentifier); @@ -2523,7 +2525,7 @@ namespace ts { case SyntaxKind.ElementAccessExpression: const expr = node as PropertyAccessExpression | ElementAccessExpression; if (currentFlow && isNarrowableReference(expr)) { - expr.flowNode = currentFlow; + setFlowNode(expr, currentFlow); } if (isSpecialPropertyDeclaration(expr)) { bindSpecialPropertyDeclaration(expr); @@ -2532,7 +2534,7 @@ namespace ts { file.commonJsModuleIndicator && isModuleExportsAccessExpression(expr) && !lookupSymbolForName(blockScopeContainer, "module" as __String)) { - declareSymbol(file.locals!, /*parent*/ undefined, expr.expression, + declareSymbol(getBindExtraFields(file)!.locals!, /*parent*/ undefined, expr.expression, SymbolFlags.FunctionScopedVariable | SymbolFlags.ModuleExports, SymbolFlags.FunctionScopedVariableExcludes); } break; @@ -2598,7 +2600,7 @@ namespace ts { case SyntaxKind.VariableDeclaration: return bindVariableDeclarationOrBindingElement(node as VariableDeclaration); case SyntaxKind.BindingElement: - node.flowNode = currentFlow; + setFlowNode(node, currentFlow); return bindVariableDeclarationOrBindingElement(node as BindingElement); case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: @@ -3360,7 +3362,7 @@ namespace ts { } } if (currentFlow) { - node.flowNode = currentFlow; + setFlowNode(node, currentFlow); } checkStrictModeFunctionName(node); const bindingName = node.name ? node.name.escapedText : InternalSymbolName.Function; @@ -3373,7 +3375,7 @@ namespace ts { } if (currentFlow && isObjectLiteralOrClassExpressionMethodOrAccessor(node)) { - node.flowNode = currentFlow; + setFlowNode(node, currentFlow); } return hasDynamicName(node) @@ -3390,10 +3392,8 @@ namespace ts { if (isJSDocTemplateTag(node.parent)) { const container = getEffectiveContainerForJSDocTemplateTag(node.parent); if (container) { - if (!container.locals) { - container.locals = createSymbolTable(); - } - declareSymbol(container.locals, /*parent*/ undefined, node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); + const locals = getOrCreateBindExtraFields(container).locals ??= createSymbolTable(); + declareSymbol(locals, /*parent*/ undefined, node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); } else { declareSymbolAndAddToSymbolTable(node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); @@ -3402,10 +3402,8 @@ namespace ts { else if (node.parent.kind === SyntaxKind.InferType) { const container = getInferTypeContainer(node.parent); if (container) { - if (!container.locals) { - container.locals = createSymbolTable(); - } - declareSymbol(container.locals, /*parent*/ undefined, node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); + const locals = getOrCreateBindExtraFields(container).locals ??= createSymbolTable(); + declareSymbol(locals, /*parent*/ undefined, node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); } else { bindAnonymousDeclaration(node, SymbolFlags.TypeParameter, getDeclarationName(node)!); // TODO: GH#18217 @@ -3524,7 +3522,7 @@ namespace ts { } function lookupSymbolForName(container: Node, name: __String): Symbol | undefined { - const local = container.locals && container.locals.get(name); + const local = getBindExtraFields(container)?.locals?.get(name); if (local) { return local.exportSymbol || local; } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 07da87f932e43..619585b5f4fe7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1561,7 +1561,7 @@ namespace ts { const constructorDeclaration = parameter.parent; const classDeclaration = parameter.parent.parent; - const parameterSymbol = getSymbol(constructorDeclaration.locals!, parameterName, SymbolFlags.Value); + const parameterSymbol = getSymbol(getBindExtraFields(constructorDeclaration)!.locals!, parameterName, SymbolFlags.Value); const propertySymbol = getSymbol(getMembersOfSymbol(classDeclaration.symbol), parameterName, SymbolFlags.Value); if (parameterSymbol && propertySymbol) { @@ -1880,8 +1880,9 @@ namespace ts { return undefined; } // Locals of a source file are not in scope (because they get merged into the global symbol table) - if (location.locals && !isGlobalSourceFile(location)) { - if (result = lookup(location.locals, name, meaning)) { + const locationBindNode = getBindExtraFields(location); + if (locationBindNode?.locals && !isGlobalSourceFile(location)) { + if (result = lookup(locationBindNode.locals, name, meaning)) { let useResult = true; if (isFunctionLike(location) && lastLocation && lastLocation !== (location as FunctionLikeDeclaration).body) { // symbol lookup restrictions for function-like declarations @@ -1997,8 +1998,9 @@ namespace ts { // in initializer expressions for instance member variables. if (!isStatic(location)) { const ctor = findConstructorDeclaration(location.parent as ClassLikeDeclaration); - if (ctor && ctor.locals) { - if (lookup(ctor.locals, name, meaning & SymbolFlags.Value)) { + const ctorBindNode = ctor && getBindExtraFields(ctor); + if (ctorBindNode?.locals) { + if (lookup(ctorBindNode.locals, name, meaning & SymbolFlags.Value)) { // Remember the property node, it will be used later to report appropriate error Debug.assertNode(location, isPropertyDeclaration); propertyWithInvalidInitializer = location; @@ -2318,8 +2320,11 @@ namespace ts { error(errorLocation, Diagnostics.Parameter_0_cannot_reference_itself, declarationNameToString(associatedDeclarationForContainingInitializerOrBindingName.name)); } // And it cannot refer to any declarations which come after it - else if (candidate.valueDeclaration && candidate.valueDeclaration.pos > associatedDeclarationForContainingInitializerOrBindingName.pos && root.parent.locals && lookup(root.parent.locals, candidate.escapedName, meaning) === candidate) { - error(errorLocation, Diagnostics.Parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(associatedDeclarationForContainingInitializerOrBindingName.name), declarationNameToString(errorLocation as Identifier)); + else if (candidate.valueDeclaration && candidate.valueDeclaration.pos > associatedDeclarationForContainingInitializerOrBindingName.pos) { + const parentBindNode = getBindExtraFields(root.parent); + if (parentBindNode?.locals && lookup(parentBindNode.locals, candidate.escapedName, meaning) === candidate) { + error(errorLocation, Diagnostics.Parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(associatedDeclarationForContainingInitializerOrBindingName.name), declarationNameToString(errorLocation as Identifier)); + } } } if (result && errorLocation && meaning & SymbolFlags.Value && result.flags & SymbolFlags.Alias && !(result.flags & SymbolFlags.Value) && !isValidTypeOnlyAliasUseSite(errorLocation)) { @@ -3043,7 +3048,7 @@ namespace ts { } function reportNonExportedMember(node: Node, name: Identifier, declarationName: string, moduleSymbol: Symbol, moduleName: string): void { - const localSymbol = moduleSymbol.valueDeclaration?.locals?.get(name.escapedText); + const localSymbol = moduleSymbol.valueDeclaration?.extra?.bindExtraFields?.locals?.get(name.escapedText); const exports = moduleSymbol.exports; if (localSymbol) { const exportedEqualsSymbol = exports?.get(InternalSymbolName.ExportEquals); @@ -4429,8 +4434,9 @@ namespace ts { let result: T; for (let location = enclosingDeclaration; location; location = location.parent) { // Locals of a source file are not in scope (because they get merged into the global symbol table) - if (location.locals && !isGlobalSourceFile(location)) { - if (result = callback(location.locals, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true, location)) { + const locationBindNode = getBindExtraFields(location); + if (locationBindNode?.locals && !isGlobalSourceFile(location)) { + if (result = callback(locationBindNode.locals, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true, location)) { return result; } } @@ -5526,7 +5532,7 @@ namespace ts { context.truncating = true; } context.approximateLength += cachedResult.addedLength; - return deepCloneOrReuseNode(cachedResult) as TypeNode as T; + return deepCloneOrReuseNode(cachedResult.node) as TypeNode as T; } let depth: number | undefined; @@ -5542,11 +5548,12 @@ namespace ts { const result = transform(type); const addedLength = context.approximateLength - startLength; if (!context.reportedDiagnostic && !context.encounteredError) { - if (context.truncating) { - (result as any).truncating = true; - } - (result as any).addedLength = addedLength; - links?.serializedTypes?.set(key, result as TypeNode as TypeNode & {truncating?: boolean, addedLength: number}); + const entry: SerializedTypeEntry = { + node: result, + truncating: !!context.truncating, + addedLength + }; + links?.serializedTypes?.set(key, entry); } context.visitedTypes.delete(typeId); if (id) { @@ -7684,7 +7691,7 @@ namespace ts { // Create namespace as non-synthetic so it is usable as an enclosing declaration let fakespace = parseNodeFactory.createModuleDeclaration(/*modifiers*/ undefined, factory.createIdentifier(localName), factory.createModuleBlock([]), NodeFlags.Namespace); setParent(fakespace, enclosingDeclaration as SourceFile | NamespaceDeclaration); - fakespace.locals = createSymbolTable(props); + getOrCreateBindExtraFields(fakespace).locals = createSymbolTable(props); fakespace.symbol = props[0].parent!; const oldResults = results; @@ -8956,7 +8963,8 @@ namespace ts { function getSyntheticElementAccess(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression): ElementAccessExpression | undefined { const parentAccess = getParentElementAccess(node); - if (parentAccess && parentAccess.flowNode) { + const flowNode = parentAccess && getFlowNode(parentAccess); + if (flowNode) { const propName = getDestructuringPropertyName(node); if (propName) { const literal = setTextRange(parseNodeFactory.createStringLiteral(propName), node); @@ -8967,7 +8975,7 @@ namespace ts { if (lhsExpr !== parentAccess) { setParent(lhsExpr, result); } - result.flowNode = parentAccess.flowNode; + setFlowNode(result, flowNode); return result; } } @@ -9286,7 +9294,7 @@ namespace ts { } setParent(reference.expression, reference); setParent(reference, file); - reference.flowNode = file.endFlowNode; + setFlowNode(reference, file.endFlowNode); return getFlowTypeOfReference(reference, autoType, undefinedType); } @@ -9298,7 +9306,7 @@ namespace ts { const reference = factory.createPropertyAccessExpression(factory.createThis(), accessName); setParent(reference.expression, reference); setParent(reference, staticBlock); - reference.flowNode = staticBlock.returnFlowNode; + setFlowNode(reference, getReturnFlowNode(staticBlock)); const flowType = getFlowTypeOfProperty(reference, symbol); if (noImplicitAny && (flowType === autoType || flowType === autoArrayType)) { error(symbol.valueDeclaration, Diagnostics.Member_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType)); @@ -9318,7 +9326,7 @@ namespace ts { const reference = factory.createPropertyAccessExpression(factory.createThis(), accessName); setParent(reference.expression, reference); setParent(reference, constructor); - reference.flowNode = constructor.returnFlowNode; + setFlowNode(reference, getReturnFlowNode(constructor)); const flowType = getFlowTypeOfProperty(reference, symbol); if (noImplicitAny && (flowType === autoType || flowType === autoArrayType)) { error(symbol.valueDeclaration, Diagnostics.Member_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType)); @@ -16489,13 +16497,11 @@ namespace ts { function getInferTypeParameters(node: ConditionalTypeNode): TypeParameter[] | undefined { let result: TypeParameter[] | undefined; - if (node.locals) { - node.locals.forEach(symbol => { - if (symbol.flags & SymbolFlags.TypeParameter) { - result = append(result, getDeclaredTypeOfSymbol(symbol)); - } - }); - } + getBindExtraFields(node)?.locals?.forEach(symbol => { + if (symbol.flags & SymbolFlags.TypeParameter) { + result = append(result, getDeclaredTypeOfSymbol(symbol)); + } + }); return result; } @@ -17991,12 +17997,13 @@ namespace ts { } function checkExpressionForMutableLocationWithContextualType(next: Expression, sourcePropType: Type) { - next.contextualType = sourcePropType; + const checkNode = getOrCreateCheckExtraFields(next); + checkNode.contextualType = sourcePropType; try { return checkExpressionForMutableLocation(next, CheckMode.Contextual, sourcePropType); } finally { - next.contextualType = undefined; + checkNode.contextualType = undefined; } } @@ -18239,18 +18246,19 @@ namespace ts { } // recreate a tuple from the elements, if possible // Since we're re-doing the expression type, we need to reapply the contextual type - const oldContext = node.contextualType; - node.contextualType = target; + const checkNode = getOrCreateCheckExtraFields(node); + const oldContext = checkNode.contextualType; + checkNode.contextualType = target; try { const tupleizedType = checkArrayLiteral(node, CheckMode.Contextual, /*forceTuple*/ true); - node.contextualType = oldContext; + checkNode.contextualType = oldContext; if (isTupleLikeType(tupleizedType)) { return elaborateElementwise(generateLimitedTupleElements(node, target), tupleizedType, target, relation, containingMessageChain, errorOutputContainer); } return false; } finally { - node.contextualType = oldContext; + checkNode.contextualType = oldContext; } } @@ -24770,7 +24778,7 @@ namespace ts { return false; } - function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, flowNode = reference.flowNode) { + function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, flowNode = getFlowNode(reference)) { let key: string | undefined; let isKeySet = false; let flowDepth = 0; @@ -24879,7 +24887,7 @@ namespace ts { reference.kind !== SyntaxKind.PropertyAccessExpression && reference.kind !== SyntaxKind.ElementAccessExpression && reference.kind !== SyntaxKind.ThisKeyword) { - flow = container.flowNode!; + flow = getFlowNode(container)!; continue; } // At the top of the flow we have the initial type. @@ -26106,7 +26114,7 @@ namespace ts { links.flags &= ~NodeCheckFlags.InCheckIdentifier; if (parentTypeConstraint && parentTypeConstraint.flags & TypeFlags.Union && !(parent.kind === SyntaxKind.Parameter && isSymbolAssigned(symbol))) { const pattern = declaration.parent; - const narrowedType = getFlowTypeOfReference(pattern, parentTypeConstraint, parentTypeConstraint, /*flowContainer*/ undefined, location.flowNode); + const narrowedType = getFlowTypeOfReference(pattern, parentTypeConstraint, parentTypeConstraint, /*flowContainer*/ undefined, getFlowNode(location)); if (narrowedType.flags & TypeFlags.Never) { return neverType; } @@ -26142,7 +26150,7 @@ namespace ts { if (contextualSignature && contextualSignature.parameters.length === 1 && signatureHasRestParameter(contextualSignature)) { const restType = getReducedApparentType(getTypeOfSymbol(contextualSignature.parameters[0])); if (restType.flags & TypeFlags.Union && everyType(restType, isTupleType) && !isSymbolAssigned(symbol)) { - const narrowedType = getFlowTypeOfReference(func, restType, restType, /*flowContainer*/ undefined, location.flowNode); + const narrowedType = getFlowTypeOfReference(func, restType, restType, /*flowContainer*/ undefined, getFlowNode(location)); const index = func.parameters.indexOf(declaration) - (getThisParameter(func) ? 1 : 0); return getIndexedAccessType(narrowedType, getNumberLiteralType(index)); } @@ -26507,7 +26515,8 @@ namespace ts { // If a containing class does not have extends clause or the class extends null // skip checking whether super statement is called before "this" accessing. if (baseTypeNode && !classDeclarationExtendsNull(containingClassDecl)) { - if (node.flowNode && !isPostSuperFlowNode(node.flowNode, /*noCacheCheck*/ false)) { + const flowNode = getFlowNode(node); + if (flowNode && !isPostSuperFlowNode(flowNode, /*noCacheCheck*/ false)) { error(node, diagnosticMessage); } } @@ -27687,8 +27696,9 @@ namespace ts { // We cannot answer semantic questions within a with block, do not proceed any further return undefined; } - if (node.contextualType) { - return node.contextualType; + const contextualType = getCheckExtraFields(node)?.contextualType; + if (contextualType) { + return contextualType; } const { parent } = node; switch (parent.kind) { @@ -27758,16 +27768,18 @@ namespace ts { } function getInferenceContext(node: Node) { - const ancestor = findAncestor(node, n => !!n.inferenceContext); - return ancestor && ancestor.inferenceContext!; + return forEachAncestor(node, n => getCheckExtraFields(n)?.inferenceContext); } function getContextualJsxElementAttributesType(node: JsxOpeningLikeElement, contextFlags: ContextFlags | undefined) { - if (isJsxOpeningElement(node) && node.parent.contextualType && contextFlags !== ContextFlags.Completions) { - // Contextually applied type is moved from attributes up to the outer jsx attributes so when walking up from the children they get hit - // _However_ to hit them from the _attributes_ we must look for them here; otherwise we'll used the declared type - // (as below) instead! - return node.parent.contextualType; + if (isJsxOpeningElement(node) && contextFlags !== ContextFlags.Completions) { + const contextualType = getCheckExtraFields(node.parent)?.contextualType; + if (contextualType) { + // Contextually applied type is moved from attributes up to the outer jsx attributes so when walking up from the children they get hit + // _However_ to hit them from the _attributes_ we must look for them here; otherwise we'll used the declared type + // (as below) instead! + return contextualType; + } } return getContextualTypeForArgumentAtIndex(node, 0); } @@ -33378,7 +33390,8 @@ namespace ts { } function functionHasImplicitReturn(func: FunctionLikeDeclaration) { - return func.endFlowNode && isReachableFlowNode(func.endFlowNode); + const endFlowNode = getEndFlowNode(func); + return endFlowNode && isReachableFlowNode(endFlowNode); } /** NOTE: Return value of `[]` means a different thing than `undefined`. `[]` means func returns `void`, `undefined` means it returns `never`. */ @@ -35010,11 +35023,12 @@ namespace ts { function checkExpressionWithContextualType(node: Expression, contextualType: Type, inferenceContext: InferenceContext | undefined, checkMode: CheckMode): Type { const context = getContextNode(node); - const saveContextualType = context.contextualType; - const saveInferenceContext = context.inferenceContext; + const checkNode = getOrCreateCheckExtraFields(context); + const saveContextualType = checkNode.contextualType; + const saveInferenceContext = checkNode.inferenceContext; try { - context.contextualType = contextualType; - context.inferenceContext = inferenceContext; + checkNode.contextualType = contextualType; + checkNode.inferenceContext = inferenceContext; const type = checkExpression(node, checkMode | CheckMode.Contextual | (inferenceContext ? CheckMode.Inferential : 0)); // In CheckMode.Inferential we collect intra-expression inference sites to process before fixing any type // parameters. This information is no longer needed after the call to checkExpression. @@ -35032,8 +35046,8 @@ namespace ts { // In the event our operation is canceled or some other exception occurs, reset the contextual type // so that we do not accidentally hold onto an instance of the checker, as a Type created in the services layer // may hold onto the checker that created it. - context.contextualType = saveContextualType; - context.inferenceContext = saveInferenceContext; + checkNode.contextualType = saveContextualType; + checkNode.inferenceContext = saveInferenceContext; } } @@ -35398,8 +35412,9 @@ namespace ts { if (links.contextFreeType) { return links.contextFreeType; } - const saveContextualType = node.contextualType; - node.contextualType = anyType; + const checkNode = getOrCreateCheckExtraFields(node); + const saveContextualType = checkNode.contextualType; + checkNode.contextualType = anyType; try { const type = links.contextFreeType = checkExpression(node, CheckMode.SkipContextSensitive); return type; @@ -35408,7 +35423,7 @@ namespace ts { // In the event our operation is canceled or some other exception occurs, reset the contextual type // so that we do not accidentally hold onto an instance of the checker, as a Type created in the services layer // may hold onto the checker that created it. - node.contextualType = saveContextualType; + checkNode.contextualType = saveContextualType; } } @@ -36325,8 +36340,11 @@ namespace ts { function checkTypeReferenceNode(node: TypeReferenceNode | ExpressionWithTypeArguments) { checkGrammarTypeArguments(node, node.typeArguments); - if (node.kind === SyntaxKind.TypeReference && node.typeName.jsdocDotPos !== undefined && !isInJSFile(node) && !isInJSDoc(node)) { - grammarErrorAtPos(node, node.typeName.jsdocDotPos, 1, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); + if (node.kind === SyntaxKind.TypeReference) { + const jsDocDotPos = getJSDocExtraFields(node.typeName)?.jsDocDotPos; + if (jsDocDotPos !== undefined && !isInJSFile(node) && !isInJSDoc(node)) { + grammarErrorAtPos(node, jsDocDotPos, 1, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); + } } forEach(node.typeArguments, checkSourceElement); const type = getTypeFromTypeReference(node); @@ -36858,7 +36876,7 @@ namespace ts { function checkExportsOnMergedDeclarationsWorker(node: Declaration): void { // if localSymbol is defined on node then node itself is exported - check is required - let symbol = node.localSymbol; + let symbol = getBindExtraFields(node)?.localSymbol; if (!symbol) { // local symbol is undefined => this declaration is non-exported. // however symbol might contain other declarations that are exported @@ -37410,7 +37428,7 @@ namespace ts { // Verify there is no local declaration that could collide with the promise constructor. const rootName = promiseConstructorName && getFirstIdentifier(promiseConstructorName); - const collidingSymbol = getSymbol(node.locals!, rootName.escapedText, SymbolFlags.Value); + const collidingSymbol = getSymbol(getBindExtraFields(node)!.locals!, rootName.escapedText, SymbolFlags.Value); if (collidingSymbol) { error(collidingSymbol.valueDeclaration, Diagnostics.Duplicate_identifier_0_Compiler_uses_declaration_1_to_support_async_functions, idText(rootName), @@ -37775,7 +37793,7 @@ namespace ts { // - if node.localSymbol !== undefined - this is current declaration is exported and localSymbol points to the local symbol // - if node.localSymbol === undefined - this node is non-exported so we can just pick the result of getSymbolOfNode const symbol = getSymbolOfNode(node); - const localSymbol = node.localSymbol || symbol; + const localSymbol = getBindExtraFields(node)?.localSymbol || symbol; // Since the javascript won't do semantic analysis like typescript, // if the javascript file comes before the typescript file and both contain same name functions, @@ -38024,7 +38042,7 @@ namespace ts { const unusedImports = new Map(); const unusedDestructures = new Map(); const unusedVariables = new Map(); - nodeWithLocals.locals!.forEach(local => { + getBindExtraFields(nodeWithLocals)!.locals!.forEach(local => { // If it's purely a type parameter, ignore, will be checked in `checkUnusedTypeParameters`. // If it's a type parameter merged with a parameter, check if the parameter-side is used. if (local.flags & SymbolFlags.TypeParameter ? !(local.flags & SymbolFlags.Variable && !(local.isReferenced! & SymbolFlags.Variable)) : local.isReferenced || local.exportSymbol) { @@ -38168,7 +38186,7 @@ namespace ts { else { forEach(node.statements, checkSourceElement); } - if (node.locals) { + if (getBindExtraFields(node)?.locals) { registerForUnusedIdentifiersCheck(node); } } @@ -38850,7 +38868,7 @@ namespace ts { if (node.condition) checkTruthinessExpression(node.condition); if (node.incrementor) checkExpression(node.incrementor); checkSourceElement(node.statement); - if (node.locals) { + if (getBindExtraFields(node)?.locals) { registerForUnusedIdentifiersCheck(node); } } @@ -38913,7 +38931,7 @@ namespace ts { } checkSourceElement(node.statement); - if (node.locals) { + if (getBindExtraFields(node)?.locals) { registerForUnusedIdentifiersCheck(node); } } @@ -38964,7 +38982,7 @@ namespace ts { } checkSourceElement(node.statement); - if (node.locals) { + if (getBindExtraFields(node)?.locals) { registerForUnusedIdentifiersCheck(node); } } @@ -39922,8 +39940,11 @@ namespace ts { addLazyDiagnostic(createLazyCaseClauseDiagnostics(clause)); } forEach(clause.statements, checkSourceElement); - if (compilerOptions.noFallthroughCasesInSwitch && clause.fallthroughFlowNode && isReachableFlowNode(clause.fallthroughFlowNode)) { - error(clause, Diagnostics.Fallthrough_case_in_switch); + if (compilerOptions.noFallthroughCasesInSwitch) { + const fallthroughFlowNode = getFallthroughFlowNode(clause); + if (fallthroughFlowNode && isReachableFlowNode(fallthroughFlowNode)) { + error(clause, Diagnostics.Fallthrough_case_in_switch); + } } function createLazyCaseClauseDiagnostics(clause: CaseClause) { @@ -39945,7 +39966,7 @@ namespace ts { }; } }); - if (node.caseBlock.locals) { + if (getBindExtraFields(node.caseBlock)?.locals) { registerForUnusedIdentifiersCheck(node.caseBlock); } } @@ -40003,9 +40024,9 @@ namespace ts { grammarErrorOnFirstToken(declaration.initializer, Diagnostics.Catch_clause_variable_cannot_have_an_initializer); } else { - const blockLocals = catchClause.block.locals; + const blockLocals = getBindExtraFields(catchClause.block)?.locals; if (blockLocals) { - forEachKey(catchClause.locals!, caughtName => { + forEachKey(getBindExtraFields(catchClause)!.locals!, caughtName => { const blockLocal = blockLocals.get(caughtName); if (blockLocal?.valueDeclaration && (blockLocal.flags & SymbolFlags.BlockScopedVariable) !== 0) { grammarErrorOnNode(blockLocal.valueDeclaration, Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause, caughtName); @@ -40955,7 +40976,7 @@ namespace ts { const reference = factory.createPropertyAccessExpression(factory.createThis(), propName); setParent(reference.expression, reference); setParent(reference, staticBlock); - reference.flowNode = staticBlock.returnFlowNode; + setFlowNode(reference, getReturnFlowNode(staticBlock)); const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType)); if (!containsUndefinedType(flowType)) { return true; @@ -40971,7 +40992,7 @@ namespace ts { : factory.createPropertyAccessExpression(factory.createThis(), propName); setParent(reference.expression, reference); setParent(reference, constructor); - reference.flowNode = constructor.returnFlowNode; + setFlowNode(reference, getReturnFlowNode(constructor)); const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType)); return !containsUndefinedType(flowType); } @@ -42060,7 +42081,7 @@ namespace ts { } function checkSourceElementWorker(node: Node): void { - forEach((node as JSDocContainer).jsDoc, ({ comment, tags }) => { + forEach(getJSDocExtraFields(node)?.jsDoc, ({ comment, tags }) => { checkJSDocCommentWorker(comment); forEach(tags, tag => { checkJSDocCommentWorker(tag.comment); @@ -42082,8 +42103,11 @@ namespace ts { cancellationToken.throwIfCancellationRequested(); } } - if (kind >= SyntaxKind.FirstStatement && kind <= SyntaxKind.LastStatement && node.flowNode && !isReachableFlowNode(node.flowNode)) { - errorOrSuggestion(compilerOptions.allowUnreachableCode === false, node, Diagnostics.Unreachable_code_detected); + if (kind >= SyntaxKind.FirstStatement && kind <= SyntaxKind.LastStatement) { + const flowNode = getFlowNode(node); + if (flowNode && !isReachableFlowNode(flowNode)) { + errorOrSuggestion(compilerOptions.allowUnreachableCode === false, node, Diagnostics.Unreachable_code_detected); + } } switch (kind) { @@ -42599,8 +42623,9 @@ namespace ts { function populateSymbols() { while (location) { - if (location.locals && !isGlobalSourceFile(location)) { - copySymbols(location.locals, meaning); + const locationBindNode = getBindExtraFields(location); + if (locationBindNode?.locals && !isGlobalSourceFile(location)) { + copySymbols(locationBindNode.locals, meaning); } switch (location.kind) { @@ -43444,8 +43469,9 @@ namespace ts { // When resolved as an expression identifier, if the given node references an import, return the declaration of // that import. Otherwise, return undefined. function getReferencedImportDeclaration(nodeIn: Identifier): Declaration | undefined { - if (nodeIn.generatedImportReference) { - return nodeIn.generatedImportReference; + const generatedImportReference = getEmitNode(nodeIn)?.generatedImportReference; + if (generatedImportReference) { + return generatedImportReference; } const node = getParseTreeNode(nodeIn, isIdentifier); if (node) { @@ -44043,7 +44069,8 @@ namespace ts { Debug.assert(n && n.kind === SyntaxKind.SourceFile, "Non-sourcefile node passed into getDeclarationsForSourceFile"); const sym = getSymbolOfNode(node); if (!sym) { - return !node.locals ? [] : nodeBuilder.symbolTableToDeclarationStatements(node.locals, node, flags, tracker, bundled); + const bindNode = getBindExtraFields(node); + return !bindNode?.locals ? [] : nodeBuilder.symbolTableToDeclarationStatements(bindNode.locals, node, flags, tracker, bundled); } return !sym.exports ? [] : nodeBuilder.symbolTableToDeclarationStatements(sym.exports, node, flags, tracker, bundled); }, @@ -44199,13 +44226,13 @@ namespace ts { if (!isExternalOrCommonJsModule(file)) { // It is an error for a non-external-module (i.e. script) to declare its own `globalThis`. // We can't use `builtinGlobals` for this due to synthetic expando-namespace generation in JS files. - const fileGlobalThisSymbol = file.locals!.get("globalThis" as __String); + const fileGlobalThisSymbol = getBindExtraFields(file)!.locals!.get("globalThis" as __String); if (fileGlobalThisSymbol?.declarations) { for (const declaration of fileGlobalThisSymbol.declarations) { diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0, "globalThis")); } } - mergeSymbolTable(globals, file.locals!); + mergeSymbolTable(globals, getBindExtraFields(file)!.locals!); } if (file.jsGlobalAugmentations) { mergeSymbolTable(globals, file.jsGlobalAugmentations); diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index bb5c0f72cc360..f1b2868435191 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -3164,7 +3164,7 @@ namespace ts { emit(node.name); emit(node.exclamationToken); emitTypeAnnotation(node.type); - emitInitializer(node.initializer, node.type?.end ?? node.name.emitNode?.typeNode?.end ?? node.name.end, node, parenthesizer.parenthesizeExpressionForDisallowedComma); + emitInitializer(node.initializer, node.type?.end ?? node.name.extra?.emitExtraFields?.typeNode?.end ?? node.name.end, node, parenthesizer.parenthesizeExpressionForDisallowedComma); } function emitVariableDeclarationList(node: VariableDeclarationList) { @@ -5158,15 +5158,16 @@ namespace ts { * Generate the text for a generated identifier. */ function generateName(name: GeneratedIdentifier | GeneratedPrivateIdentifier) { - if ((name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) === GeneratedIdentifierFlags.Node) { + const idFields = getIdentifierExtraFields(name); + if ((idFields.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) === GeneratedIdentifierFlags.Node) { // Node names generate unique names based on their original node // and are cached based on that node's id. - return generateNameCached(getNodeForGeneratedName(name), isPrivateIdentifier(name), name.autoGenerateFlags, name.autoGeneratePrefix, name.autoGenerateSuffix); + return generateNameCached(getNodeForGeneratedName(name), isPrivateIdentifier(name), idFields.autoGenerateFlags, idFields.autoGeneratePrefix, idFields.autoGenerateSuffix); } else { // Auto, Loop, and Unique names are cached based on their unique // autoGenerateId. - const autoGenerateId = name.autoGenerateId!; + const autoGenerateId = idFields.autoGenerateId!; return autoGeneratedIdToGeneratedName[autoGenerateId] || (autoGeneratedIdToGeneratedName[autoGenerateId] = makeName(name)); } } @@ -5197,13 +5198,11 @@ namespace ts { * Returns a value indicating whether a name is unique within a container. */ function isUniqueLocalName(name: string, container: Node): boolean { - for (let node = container; isNodeDescendantOf(node, container); node = node.nextContainer!) { - if (node.locals) { - const local = node.locals.get(escapeLeadingUnderscores(name)); - // We conservatively include alias symbols to cover cases where they're emitted as locals - if (local && local.flags & (SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias)) { - return false; - } + for (let node: Node | undefined = container; node && isNodeDescendantOf(node, container); node = getBindExtraFields(node)?.nextContainer) { + const local = getBindExtraFields(node)?.locals?.get(escapeLeadingUnderscores(name)); + // We conservatively include alias symbols to cover cases where they're emitted as locals + if (local && local.flags & (SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias)) { + return false; } } return true; @@ -5419,27 +5418,28 @@ namespace ts { * Generates a unique identifier for a node. */ function makeName(name: GeneratedIdentifier | GeneratedPrivateIdentifier) { - const prefix = formatGeneratedNamePart(name.autoGeneratePrefix, generateName); - const suffix = formatGeneratedNamePart (name.autoGenerateSuffix); - switch (name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) { + const idFields = getIdentifierExtraFields(name); + const prefix = formatGeneratedNamePart(idFields.autoGeneratePrefix, generateName); + const suffix = formatGeneratedNamePart(idFields.autoGenerateSuffix); + switch (idFields.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) { case GeneratedIdentifierFlags.Auto: - return makeTempVariableName(TempFlags.Auto, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes), isPrivateIdentifier(name), prefix, suffix); + return makeTempVariableName(TempFlags.Auto, !!(idFields.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes), isPrivateIdentifier(name), prefix, suffix); case GeneratedIdentifierFlags.Loop: Debug.assertNode(name, isIdentifier); - return makeTempVariableName(TempFlags._i, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes), /*privateName*/ false, prefix, suffix); + return makeTempVariableName(TempFlags._i, !!(idFields.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes), /*privateName*/ false, prefix, suffix); case GeneratedIdentifierFlags.Unique: return makeUniqueName( idText(name), - (name.autoGenerateFlags & GeneratedIdentifierFlags.FileLevel) ? isFileLevelUniqueName : isUniqueName, - !!(name.autoGenerateFlags & GeneratedIdentifierFlags.Optimistic), - !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes), + (idFields.autoGenerateFlags & GeneratedIdentifierFlags.FileLevel) ? isFileLevelUniqueName : isUniqueName, + !!(idFields.autoGenerateFlags & GeneratedIdentifierFlags.Optimistic), + !!(idFields.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes), isPrivateIdentifier(name), prefix, suffix ); } - return Debug.fail(`Unsupported GeneratedIdentifierKind: ${Debug.formatEnum(name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask, (ts as any).GeneratedIdentifierFlags, /*isFlags*/ true)}.`); + return Debug.fail(`Unsupported GeneratedIdentifierKind: ${Debug.formatEnum(idFields.autoGenerateFlags & GeneratedIdentifierFlags.KindMask, (ts as any).GeneratedIdentifierFlags, /*isFlags*/ true)}.`); } // Comments @@ -5941,10 +5941,10 @@ namespace ts { return emitCallback(token, writer, tokenPos); } - const emitNode = node && node.emitNode; - const emitFlags = emitNode && emitNode.flags || EmitFlags.None; - const range = emitNode && emitNode.tokenSourceMapRanges && emitNode.tokenSourceMapRanges[token]; - const source = range && range.source || sourceMapSource; + const emitNode = node?.extra?.emitExtraFields; + const emitFlags = emitNode?.flags || EmitFlags.None; + const range = emitNode?.tokenSourceMapRanges?.[token]; + const source = range?.source || sourceMapSource; tokenPos = skipSourceTrivia(source, range ? range.pos : tokenPos); if ((emitFlags & EmitFlags.NoTokenLeadingSourceMaps) === 0 && tokenPos >= 0) { diff --git a/src/compiler/factory/emitHelpers.ts b/src/compiler/factory/emitHelpers.ts index 36063fadd55c7..b8294d756eecc 100644 --- a/src/compiler/factory/emitHelpers.ts +++ b/src/compiler/factory/emitHelpers.ts @@ -161,7 +161,7 @@ namespace ts { context.requestEmitHelper(asyncGeneratorHelper); // Mark this node as originally an async function - (generatorFunc.emitNode || (generatorFunc.emitNode = {} as EmitNode)).flags |= EmitFlags.AsyncFunctionBody | EmitFlags.ReuseTempVariableScope; + getOrCreateEmitNode(generatorFunc).flags |= EmitFlags.AsyncFunctionBody | EmitFlags.ReuseTempVariableScope; return factory.createCallExpression( getUnscopedHelperName("__asyncGenerator"), @@ -253,7 +253,7 @@ namespace ts { ); // Mark this node as originally an async function - (generatorFunc.emitNode || (generatorFunc.emitNode = {} as EmitNode)).flags |= EmitFlags.AsyncFunctionBody | EmitFlags.ReuseTempVariableScope; + getOrCreateEmitNode(generatorFunc).flags |= EmitFlags.AsyncFunctionBody | EmitFlags.ReuseTempVariableScope; return factory.createCallExpression( getUnscopedHelperName("__awaiter"), diff --git a/src/compiler/factory/emitNode.ts b/src/compiler/factory/emitNode.ts index 19ebfd53ba2ba..d68a6cdbf7bc2 100644 --- a/src/compiler/factory/emitNode.ts +++ b/src/compiler/factory/emitNode.ts @@ -1,29 +1,39 @@ namespace ts { + /** + * Associates a node with the current transformation, initializing + * various transient transformation properties. + * @internal + */ + export function getEmitNode(node: Node): EmitNode | undefined { + return node.extra?.emitExtraFields; + } + /** * Associates a node with the current transformation, initializing * various transient transformation properties. * @internal */ export function getOrCreateEmitNode(node: Node): EmitNode { - if (!node.emitNode) { + const extra = getOrCreateNodeExtraFields(node); + if (!extra.emitExtraFields) { if (isParseTreeNode(node)) { // To avoid holding onto transformation artifacts, we keep track of any // parse tree node we are annotating. This allows us to clean them up after // all transformations have completed. if (node.kind === SyntaxKind.SourceFile) { - return node.emitNode = { annotatedNodes: [node] } as EmitNode; + return extra.emitExtraFields = { annotatedNodes: [node] } as EmitNode; } const sourceFile = getSourceFileOfNode(getParseTreeNode(getSourceFileOfNode(node))) ?? Debug.fail("Could not determine parsed source file."); getOrCreateEmitNode(sourceFile).annotatedNodes!.push(node); } - node.emitNode = {} as EmitNode; + extra.emitExtraFields = {} as EmitNode; } else { - Debug.assert(!(node.emitNode.flags & EmitFlags.Immutable), "Invalid attempt to mutate an immutable node."); + Debug.assert(!(extra.emitExtraFields.flags & EmitFlags.Immutable), "Invalid attempt to mutate an immutable node."); } - return node.emitNode; + return extra.emitExtraFields; } /** @@ -37,10 +47,12 @@ namespace ts { // from these nodes to ensure we do not hold onto entire subtrees just for position // information. We also need to reset these nodes to a pre-transformation state // for incremental parsing scenarios so that we do not impact later emit. - const annotatedNodes = getSourceFileOfNode(getParseTreeNode(sourceFile))?.emitNode?.annotatedNodes; + const annotatedNodes = getSourceFileOfNode(getParseTreeNode(sourceFile))?.extra?.emitExtraFields?.annotatedNodes; if (annotatedNodes) { for (const node of annotatedNodes) { - node.emitNode = undefined; + if (node.extra) { + node.extra.emitExtraFields = undefined; + } } } } @@ -79,7 +91,7 @@ namespace ts { * Gets a custom text range to use when emitting source maps. */ export function getSourceMapRange(node: Node): SourceMapRange { - return node.emitNode?.sourceMapRange ?? node; + return node.extra?.emitExtraFields?.sourceMapRange ?? node; } /** @@ -94,7 +106,7 @@ namespace ts { * Gets the TextRange to use for source maps for a token of a node. */ export function getTokenSourceMapRange(node: Node, token: SyntaxKind): SourceMapRange | undefined { - return node.emitNode?.tokenSourceMapRanges?.[token]; + return node.extra?.emitExtraFields?.tokenSourceMapRanges?.[token]; } /** @@ -112,7 +124,7 @@ namespace ts { */ /*@internal*/ export function getStartsOnNewLine(node: Node) { - return node.emitNode?.startsOnNewLine; + return node.extra?.emitExtraFields?.startsOnNewLine; } /** @@ -128,7 +140,7 @@ namespace ts { * Gets a custom text range to use when emitting comments. */ export function getCommentRange(node: Node): TextRange { - return node.emitNode?.commentRange ?? node; + return node.extra?.emitExtraFields?.commentRange ?? node; } /** @@ -140,7 +152,7 @@ namespace ts { } export function getSyntheticLeadingComments(node: Node): SynthesizedComment[] | undefined { - return node.emitNode?.leadingComments; + return node.extra?.emitExtraFields?.leadingComments; } export function setSyntheticLeadingComments(node: T, comments: SynthesizedComment[] | undefined) { @@ -153,7 +165,7 @@ namespace ts { } export function getSyntheticTrailingComments(node: Node): SynthesizedComment[] | undefined { - return node.emitNode?.trailingComments; + return node.extra?.emitExtraFields?.trailingComments; } export function setSyntheticTrailingComments(node: T, comments: SynthesizedComment[] | undefined) { @@ -178,7 +190,7 @@ namespace ts { * Gets the constant value to emit for an expression representing an enum. */ export function getConstantValue(node: AccessExpression): string | number | undefined { - return node.emitNode?.constantValue; + return node.extra?.emitExtraFields?.constantValue; } /** @@ -216,7 +228,7 @@ namespace ts { * Removes an EmitHelper from a node. */ export function removeEmitHelper(node: Node, helper: EmitHelper): boolean { - const helpers = node.emitNode?.helpers; + const helpers = node.extra?.emitExtraFields?.helpers; if (helpers) { return orderedRemoveItem(helpers, helper); } @@ -227,14 +239,14 @@ namespace ts { * Gets the EmitHelpers of a node. */ export function getEmitHelpers(node: Node): EmitHelper[] | undefined { - return node.emitNode?.helpers; + return node.extra?.emitExtraFields?.helpers; } /** * Moves matching emit helpers from a source node to a target node. */ export function moveEmitHelpers(source: Node, target: Node, predicate: (helper: EmitHelper) => boolean) { - const sourceEmitNode = source.emitNode; + const sourceEmitNode = source.extra?.emitExtraFields; const sourceEmitHelpers = sourceEmitNode && sourceEmitNode.helpers; if (!some(sourceEmitHelpers)) return; @@ -261,7 +273,7 @@ namespace ts { */ /* @internal */ export function getSnippetElement(node: Node): SnippetElement | undefined { - return node.emitNode?.snippetElement; + return node.extra?.emitExtraFields?.snippetElement; } /** @@ -289,6 +301,6 @@ namespace ts { /* @internal */ export function getTypeNode(node: T): TypeNode | undefined { - return node.emitNode?.typeNode; + return node.extra?.emitExtraFields?.typeNode; } } diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index 713df9c86780a..e1a559f816c1d 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -572,6 +572,7 @@ namespace ts { const array = (length >= 1 && length <= 4 ? elements.slice() : elements) as MutableNodeArray; setTextRangePosEnd(array, -1, -1); array.hasTrailingComma = !!hasTrailingComma; + array.transformFlags = 0; aggregateChildrenFlags(array); Debug.attachNodeArrayDebugInfo(array); return array; @@ -584,14 +585,7 @@ namespace ts { function createBaseDeclaration( kind: T["kind"], ) { - const node = createBaseNode(kind); - // NOTE: The following properties are commonly set by the binder and are added here to - // ensure declarations have a stable shape. - node.symbol = undefined!; // initialized by binder - node.localSymbol = undefined; // initialized by binder - node.locals = undefined; // initialized by binder - node.nextContainer = undefined; // initialized by binder - return node; + return createBaseNode(kind); } function createBaseNamedDeclaration( @@ -860,15 +854,19 @@ namespace ts { const node = baseFactory.createBaseIdentifierNode(SyntaxKind.Identifier) as Mutable; node.originalKeywordKind = originalKeywordKind; node.escapedText = escapeLeadingUnderscores(text); + node.isInJSDocNamespace = undefined; + node.idExtra = undefined; return node; } function createBaseGeneratedIdentifier(text: string, autoGenerateFlags: GeneratedIdentifierFlags, prefix: string | GeneratedNamePart | undefined, suffix: string | undefined) { const node = createBaseIdentifier(text, /*originalKeywordKind*/ undefined) as Mutable; - node.autoGenerateFlags = autoGenerateFlags; - node.autoGenerateId = nextAutoGenerateId; - node.autoGeneratePrefix = prefix; - node.autoGenerateSuffix = suffix; + node.idExtra = { + autoGenerateFlags, + autoGenerateId: nextAutoGenerateId, + autoGeneratePrefix: prefix, + autoGenerateSuffix: suffix, + }; nextAutoGenerateId++; return node; } @@ -941,6 +939,7 @@ namespace ts { const node = baseFactory.createBasePrivateIdentifierNode(SyntaxKind.PrivateIdentifier) as Mutable; node.escapedText = escapeLeadingUnderscores(text); node.transformFlags |= TransformFlags.ContainsClassFields; + node.idExtra = undefined; return node; } @@ -951,11 +950,13 @@ namespace ts { } function createBaseGeneratedPrivateIdentifier(text: string, autoGenerateFlags: GeneratedIdentifierFlags, prefix: string | GeneratedNamePart | undefined, suffix: string | undefined) { - const node = createBasePrivateIdentifier(text); - node.autoGenerateFlags = autoGenerateFlags; - node.autoGenerateId = nextAutoGenerateId; - node.autoGeneratePrefix = prefix; - node.autoGenerateSuffix = suffix; + const node = createBasePrivateIdentifier(text) as Mutable; + node.idExtra = { + autoGenerateFlags, + autoGenerateId: nextAutoGenerateId, + autoGeneratePrefix: prefix, + autoGenerateSuffix: suffix, + }; nextAutoGenerateId++; return node; } @@ -997,7 +998,8 @@ namespace ts { function createToken(token: TKind): KeywordTypeNode; function createToken(token: TKind): ModifierToken; function createToken(token: TKind): KeywordToken; - function createToken(token: TKind): Token; + function createToken(token: TKind): EndOfFileToken; + function createToken(token: TKind): Token; function createToken(token: TKind): Token; function createToken(token: TKind) { Debug.assert(token >= SyntaxKind.FirstToken && token <= SyntaxKind.LastToken, "Invalid token"); @@ -1171,6 +1173,8 @@ namespace ts { node.constraint = constraint; node.default = defaultType; node.transformFlags = TransformFlags.ContainsTypeScript; + + node.expression = undefined; return node; } @@ -1438,6 +1442,7 @@ namespace ts { // The following properties are used only to report grammar errors node.exclamationToken = undefined; + return node; } @@ -1730,6 +1735,7 @@ namespace ts { type ); node.transformFlags = TransformFlags.ContainsTypeScript; + node.illegalDecorators = undefined; return node; } @@ -2373,16 +2379,26 @@ namespace ts { : node; } - // @api - function createPropertyAccessExpression(expression: Expression, name: string | Identifier | PrivateIdentifier) { - const node = createBaseExpression(SyntaxKind.PropertyAccessExpression); - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false); - node.name = asName(name); + function createBasePropertyAccessExpression(expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined, name: MemberName) { + const node = createBaseExpression(SyntaxKind.PropertyAccessExpression); + node.expression = expression; + node.questionDotToken = questionDotToken; + node.name = name; node.transformFlags = propagateChildFlags(node.expression) | + propagateChildFlags(node.questionDotToken) | (isIdentifier(node.name) ? propagateIdentifierNameFlags(node.name) : propagateChildFlags(node.name) | TransformFlags.ContainsPrivateIdentifierInExpression); + return node; + } + // @api + function createPropertyAccessExpression(expression: Expression, name: string | Identifier | PrivateIdentifier) { + const node = createBasePropertyAccessExpression( + parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false), + /*questionDotToken*/ undefined, + asName(name) + ); if (isSuperKeyword(expression)) { // super method calls require a lexical 'this' // super method calls require 'super' hoisting in ES2017 and ES2018 async functions and async generators @@ -2406,18 +2422,13 @@ namespace ts { // @api function createPropertyAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, name: string | Identifier | PrivateIdentifier) { - const node = createBaseExpression(SyntaxKind.PropertyAccessExpression); + const node = createBasePropertyAccessExpression( + parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ true), + questionDotToken, + asName(name) + ); node.flags |= NodeFlags.OptionalChain; - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ true); - node.questionDotToken = questionDotToken; - node.name = asName(name); - node.transformFlags |= - TransformFlags.ContainsES2020 | - propagateChildFlags(node.expression) | - propagateChildFlags(node.questionDotToken) | - (isIdentifier(node.name) ? - propagateIdentifierNameFlags(node.name) : - propagateChildFlags(node.name) | TransformFlags.ContainsPrivateIdentifierInExpression); + node.transformFlags |= TransformFlags.ContainsES2020; return node; } @@ -2433,14 +2444,26 @@ namespace ts { : node; } - // @api - function createElementAccessExpression(expression: Expression, index: number | Expression) { - const node = createBaseExpression(SyntaxKind.ElementAccessExpression); - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false); - node.argumentExpression = asExpression(index); + function createBaseElementAccessExpression(expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined, index: Expression) { + const node = createBaseExpression(SyntaxKind.ElementAccessExpression); + node.expression = expression; + node.questionDotToken = questionDotToken; + node.argumentExpression = index; node.transformFlags |= propagateChildFlags(node.expression) | + propagateChildFlags(node.questionDotToken) | propagateChildFlags(node.argumentExpression); + return node; + } + + + // @api + function createElementAccessExpression(expression: Expression, index: number | Expression) { + const node = createBaseElementAccessExpression( + parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false), + /*questionDotToken*/ undefined, + asExpression(index) + ); if (isSuperKeyword(expression)) { // super method calls require a lexical 'this' // super method calls require 'super' hoisting in ES2017 and ES2018 async functions and async generators @@ -2464,16 +2487,13 @@ namespace ts { // @api function createElementAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, index: number | Expression) { - const node = createBaseExpression(SyntaxKind.ElementAccessExpression); + const node = createBaseElementAccessExpression( + parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ true), + questionDotToken, + asExpression(index) + ); node.flags |= NodeFlags.OptionalChain; - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ true); - node.questionDotToken = questionDotToken; - node.argumentExpression = asExpression(index); - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.questionDotToken) | - propagateChildFlags(node.argumentExpression) | - TransformFlags.ContainsES2020; + node.transformFlags |= TransformFlags.ContainsES2020; return node; } @@ -2732,6 +2752,7 @@ namespace ts { if (modifiersToFlags(node.modifiers) & ModifierFlags.Async) { node.transformFlags |= TransformFlags.ContainsES2017 | TransformFlags.ContainsLexicalThis; } + return node; } @@ -3320,6 +3341,7 @@ namespace ts { if (modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) { node.transformFlags = TransformFlags.ContainsTypeScript; } + node.illegalDecorators = undefined; return node; } @@ -3729,6 +3751,8 @@ namespace ts { // The following properties are used only to report grammar errors node.illegalDecorators = undefined; + + return node; } @@ -5233,10 +5257,10 @@ namespace ts { propagateChildFlags(node.initializer); // The following properties are used only to report grammar errors - node.illegalDecorators = undefined; - node.modifiers = undefined; - node.questionToken = undefined; - node.exclamationToken = undefined; + node.illegalDecorators = undefined; // added by parser + node.modifiers = undefined; // added by parser + node.questionToken = undefined; // added by parser + node.exclamationToken = undefined; // added by parser return node; } @@ -5380,10 +5404,17 @@ namespace ts { ) { const node = (source.redirectInfo ? Object.create(source.redirectInfo.redirectTarget) : baseFactory.createBaseSourceFileNode(SyntaxKind.SourceFile)) as Mutable; for (const p in source) { - if (p === "emitNode" || hasProperty(node, p) || !hasProperty(source, p)) continue; + if (p === "extra" || hasProperty(node, p) || !hasProperty(source, p)) continue; (node as any)[p] = (source as any)[p]; } node.flags |= source.flags; + if (source.extra) { + const extra = getOrCreateNodeExtraFields(source); + extra.bindExtraFields = source.extra.bindExtraFields; + extra.flowExtraFields = source.extra.flowExtraFields; + extra.checkExtraFields = source.extra.checkExtraFields; + extra.jsDocExtraFields = source.extra.jsDocExtraFields; + } node.statements = createNodeArray(statements); node.endOfFileToken = source.endOfFileToken; node.isDeclarationFile = isDeclarationFile; @@ -5552,7 +5583,7 @@ namespace ts { } function flattenCommaElements(node: Expression): Expression | readonly Expression[] { - if (nodeIsSynthesized(node) && !isParseTreeNode(node) && !node.original && !node.emitNode && !node.id) { + if (nodeIsSynthesized(node) && !isParseTreeNode(node) && !node.original && !node.extra?.emitExtraFields && !node.id) { if (isCommaListExpression(node)) { return node.elements; } @@ -5585,7 +5616,7 @@ namespace ts { // @api function createEndOfDeclarationMarker(original: Node) { const node = createBaseNode(SyntaxKind.EndOfDeclarationMarker); - node.emitNode = {} as EmitNode; + getOrCreateNodeExtraFields(node).emitExtraFields = {} as EmitNode; node.original = original; return node; } @@ -5597,7 +5628,7 @@ namespace ts { // @api function createMergeDeclarationMarker(original: Node) { const node = createBaseNode(SyntaxKind.MergeDeclarationMarker); - node.emitNode = {} as EmitNode; + getOrCreateNodeExtraFields(node).emitExtraFields = {} as EmitNode; node.original = original; return node; } @@ -5640,6 +5671,26 @@ namespace ts { (clone as Mutable).flags |= (node.flags & ~NodeFlags.Synthesized); (clone as Mutable).transformFlags = node.transformFlags; + + // clone extra fields + if (node.extra) { + const extra = getOrCreateNodeExtraFields(clone); + extra.bindExtraFields = node.extra.bindExtraFields; + extra.checkExtraFields = node.extra.checkExtraFields; + extra.flowExtraFields = node.extra.flowExtraFields; + extra.jsDocExtraFields = node.extra.jsDocExtraFields; + extra.emitExtraFields = undefined; + } + + if (isMemberName(node) && node.idExtra) { + (clone as Node as MemberName).idExtra = { + autoGenerateFlags: node.idExtra.autoGenerateFlags, + autoGenerateId: node.idExtra.autoGenerateId, + autoGeneratePrefix: node.idExtra.autoGeneratePrefix, + autoGenerateSuffix: node.idExtra.autoGenerateSuffix, + }; + } + setOriginalNode(clone, node); for (const key in node) { @@ -6852,8 +6903,8 @@ namespace ts { export function setOriginalNode(node: T, original: Node | undefined): T { node.original = original; if (original) { - const emitNode = original.emitNode; - if (emitNode) node.emitNode = mergeEmitNode(emitNode, node.emitNode); + const emitNode = original.extra?.emitExtraFields; + if (emitNode) getOrCreateNodeExtraFields(node).emitExtraFields = mergeEmitNode(emitNode, node.extra?.emitExtraFields); } return node; } diff --git a/src/compiler/factory/utilities.ts b/src/compiler/factory/utilities.ts index 6c7f02c240f2e..10df0a4714cf6 100644 --- a/src/compiler/factory/utilities.ts +++ b/src/compiler/factory/utilities.ts @@ -468,13 +468,13 @@ namespace ts { export function getExternalHelpersModuleName(node: SourceFile) { const parseNode = getOriginalNode(node, isSourceFile); - const emitNode = parseNode && parseNode.emitNode; + const emitNode = parseNode?.extra?.emitExtraFields; return emitNode && emitNode.externalHelpersModuleName; } export function hasRecordedExternalHelpers(sourceFile: SourceFile) { const parseNode = getOriginalNode(sourceFile, isSourceFile); - const emitNode = parseNode && parseNode.emitNode; + const emitNode = parseNode?.extra?.emitExtraFields; return !!emitNode && (!!emitNode.externalHelpersModuleName || !!emitNode.externalHelpers); } @@ -1249,18 +1249,22 @@ namespace ts { * Gets the node from which a name should be generated. */ export function getNodeForGeneratedName(name: GeneratedIdentifier | GeneratedPrivateIdentifier) { - if (name.autoGenerateFlags & GeneratedIdentifierFlags.Node) { - const autoGenerateId = name.autoGenerateId; + const idFields = getIdentifierExtraFields(name); + if (idFields.autoGenerateFlags & GeneratedIdentifierFlags.Node) { + const autoGenerateId = idFields.autoGenerateId; let node = name as Node; let original = node.original; while (original) { node = original; // if "node" is a different generated name (having a different "autoGenerateId"), use it and stop traversing. - if (isMemberName(node) - && !!(node.autoGenerateFlags! & GeneratedIdentifierFlags.Node) - && node.autoGenerateId !== autoGenerateId) { - break; + if (isMemberName(node)) { + const idFields = getIdentifierExtraFields(node); + if (idFields + && !!(idFields.autoGenerateFlags! & GeneratedIdentifierFlags.Node) + && idFields.autoGenerateId !== autoGenerateId) { + break; + } } original = node.original; diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 3b242daf5cc76..4002f92e3c3e9 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1250,7 +1250,7 @@ namespace ts { const statement = factory.createExpressionStatement(expression) as JsonObjectExpressionStatement; finishNode(statement, pos); statements = createNodeArray([statement], pos); - endOfFileToken = parseExpectedToken(SyntaxKind.EndOfFileToken, Diagnostics.Unexpected_token); + endOfFileToken = parseExpectedToken(SyntaxKind.EndOfFileToken, Diagnostics.Unexpected_token) as EndOfFileToken; } // Set source file so that errors will be reported with this file name @@ -1385,9 +1385,10 @@ namespace ts { let hasDeprecatedTag = false; function addJSDocComment(node: T): T { - Debug.assert(!node.jsDoc); // Should only be called once per node + const jsDocFields = getOrCreateJSDocExtraFields(node); + Debug.assert(!jsDocFields.jsDoc); // Should only be called once per node const jsDoc = mapDefined(getJSDocCommentRanges(node, sourceText), comment => JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos)); - if (jsDoc.length) node.jsDoc = jsDoc; + if (jsDoc.length) jsDocFields.jsDoc = jsDoc; if (hasDeprecatedTag) { hasDeprecatedTag = false; (node as Mutable).flags |= NodeFlags.Deprecated; @@ -2677,9 +2678,10 @@ namespace ts { return undefined; } - if ((node as JSDocContainer).jsDocCache) { + const jsDocFields = getJSDocExtraFields(node); + if (jsDocFields?.jsDocCache) { // jsDocCache may include tags from parent nodes, which might have been modified. - (node as JSDocContainer).jsDocCache = undefined; + jsDocFields.jsDocCache = undefined; } return node; @@ -3075,7 +3077,7 @@ namespace ts { while (parseOptional(SyntaxKind.DotToken)) { if (token() === SyntaxKind.LessThanToken) { // the entity is part of a JSDoc-style generic, so record the trailing dot for later error reporting - entity.jsdocDotPos = dotPos; + getOrCreateJSDocExtraFields(entity).jsDocDotPos = dotPos; break; } dotPos = getNodePos(); @@ -9246,7 +9248,7 @@ namespace ts { forEachChild(node, visitNode, visitArray); if (hasJSDocNodes(node)) { - for (const jsDocComment of node.jsDoc!) { + for (const jsDocComment of getJSDocExtraFields(node)!.jsDoc!) { visitNode(jsDocComment as Node as IncrementalNode); } } @@ -9356,7 +9358,7 @@ namespace ts { pos = child.end; }; if (hasJSDocNodes(node)) { - for (const jsDocComment of node.jsDoc!) { + for (const jsDocComment of getJSDocExtraFields(node)!.jsDoc!) { visitNode(jsDocComment); } } @@ -9399,7 +9401,7 @@ namespace ts { adjustIntersectingElement(child, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); forEachChild(child, visitNode, visitArray); if (hasJSDocNodes(child)) { - for (const jsDocComment of child.jsDoc!) { + for (const jsDocComment of getJSDocExtraFields(child)!.jsDoc!) { visitNode(jsDocComment as Node as IncrementalNode); } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index cc773c39f4bae..dd5ea21fccda7 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -2700,7 +2700,7 @@ namespace ts { } }; while (true) { - const child = isJavaScriptFile && hasJSDocNodes(current) && forEach(current.jsDoc, getContainingChild) || forEachChild(current, getContainingChild); + const child = isJavaScriptFile && hasJSDocNodes(current) && forEach(getJSDocExtraFields(current)?.jsDoc, getContainingChild) || forEachChild(current, getContainingChild); if (!child) { return current; } diff --git a/src/compiler/transformers/classFields.ts b/src/compiler/transformers/classFields.ts index 3ae7696d7c96e..2cb415a71344d 100644 --- a/src/compiler/transformers/classFields.ts +++ b/src/compiler/transformers/classFields.ts @@ -1337,7 +1337,7 @@ namespace ts { // record an alias as the class name is not in scope for statics. enableSubstitutionForClassAliases(); const alias = factory.cloneNode(temp) as GeneratedIdentifier; - alias.autoGenerateFlags &= ~GeneratedIdentifierFlags.ReservedInNestedScopes; + getIdentifierExtraFields(alias).autoGenerateFlags &= ~GeneratedIdentifierFlags.ReservedInNestedScopes; classAliases[getOriginalNodeId(node)] = alias; } diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index e453b77a3bff2..74aa340670d88 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -696,7 +696,7 @@ namespace ts { function preserveJsDoc(updated: T, original: Node): T { if (hasJSDocNodes(updated) && hasJSDocNodes(original)) { - updated.jsDoc = original.jsDoc; + getOrCreateJSDocExtraFields(updated).jsDoc = getJSDocExtraFields(original)!.jsDoc; } return setCommentRange(updated, getCommentRange(original)); } @@ -1256,7 +1256,7 @@ namespace ts { // Use parseNodeFactory so it is usable as an enclosing declaration const fakespace = parseNodeFactory.createModuleDeclaration(/*modifiers*/ undefined, clean.name || factory.createIdentifier("_default"), factory.createModuleBlock([]), NodeFlags.Namespace); setParent(fakespace, enclosingDeclaration as SourceFile | NamespaceDeclaration); - fakespace.locals = createSymbolTable(props); + getOrCreateBindExtraFields(fakespace).locals = createSymbolTable(props); fakespace.symbol = props[0].parent!; const exportMappings: [Identifier, string][] = []; let declarations: (VariableStatement | ExportDeclaration)[] = mapDefined(props, p => { diff --git a/src/compiler/transformers/jsx.ts b/src/compiler/transformers/jsx.ts index 83a7f70d156a0..5473eeca2ec53 100644 --- a/src/compiler/transformers/jsx.ts +++ b/src/compiler/transformers/jsx.ts @@ -57,7 +57,7 @@ namespace ts { } const generatedName = factory.createUniqueName(`_${name}`, GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel | GeneratedIdentifierFlags.AllowNameSubstitution); const specifier = factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier(name), generatedName); - generatedName.generatedImportReference = specifier; + getOrCreateEmitNode(generatedName).generatedImportReference = specifier; specifierSourceImports.set(name, specifier); return generatedName; } diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index fbab915e871d4..3160b14fa9ec3 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -1904,7 +1904,7 @@ namespace ts { } return node; } - else if (!(isGeneratedIdentifier(node) && !(node.autoGenerateFlags & GeneratedIdentifierFlags.AllowNameSubstitution)) && !isLocalName(node)) { + else if (!(isGeneratedIdentifier(node) && !(getIdentifierExtraFields(node).autoGenerateFlags & GeneratedIdentifierFlags.AllowNameSubstitution)) && !isLocalName(node)) { const exportContainer = resolver.getReferencedExportContainer(node, isExportName(node)); if (exportContainer && exportContainer.kind === SyntaxKind.SourceFile) { return setTextRange( diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 6d16371717e9c..db058960e4b30 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -878,18 +878,48 @@ namespace ts { readonly parent: Node; // Parent node (initialized by binding) /* @internal */ original?: Node; // The original node if this is an updated node. /* @internal */ symbol: Symbol; // Symbol declared by node (initialized by binding) - /* @internal */ locals?: SymbolTable; // Locals associated with node (initialized by binding) - /* @internal */ nextContainer?: Node; // Next container in declaration order (initialized by binding) - /* @internal */ localSymbol?: Symbol; // Local symbol declared by node (initialized by binding only for exported nodes) - /* @internal */ flowNode?: FlowNode; // Associated FlowNode (initialized by binding) - /* @internal */ emitNode?: EmitNode; // Associated EmitNode (initialized by transforms) - /* @internal */ contextualType?: Type; // Used to temporarily assign a contextual type during overload resolution - /* @internal */ inferenceContext?: InferenceContext; // Inference context for contextual type + /* @internal */ extra?: NodeExtraFields; + } + + /* @internal */ + export interface NodeExtraFields { + bindExtraFields?: BindExtraFields; + flowExtraFields?: FlowExtraFields; + emitExtraFields?: EmitNode; + checkExtraFields?: CheckExtraFields; + jsDocExtraFields?: JSDocExtraFields; + } + + /* @internal */ + export interface BindExtraFields { + locals?: SymbolTable; // Locals associated with node (initialized by binding) + nextContainer?: Node; // Next container in declaration order (initialized by binding) + localSymbol?: Symbol; // Local symbol declared by node (initialized by binding only for exported nodes) + } + + /* @internal */ + export interface FlowExtraFields { + flowNode?: FlowNode; // Associated FlowNode (initialized by binding) + endFlowNode?: FlowNode; // Functions/Methods and Class Static Blocks + returnFlowNode?: FlowNode; // Functions/Methods and Class Static Blocks + fallthroughFlowNode?: FlowNode; // Case and Default Clauses + } + + /* @internal */ + export interface CheckExtraFields { + contextualType?: Type; // Used to temporarily assign a contextual type during overload resolution + inferenceContext?: InferenceContext; // Inference context for contextual type + } + + /* @internal */ + export interface JSDocExtraFields { + jsDoc?: JSDoc[]; // JSDoc that directly precedes this node + jsDocCache?: readonly JSDocTag[]; // Cache for getJSDocTags + jsDocDotPos?: number; // Identifier occurs in JSDoc-style generic: Id. } export interface JSDocContainer { - /* @internal */ jsDoc?: JSDoc[]; // JSDoc that directly precedes this node - /* @internal */ jsDocCache?: readonly JSDocTag[]; // Cache for getJSDocTags + _jsdocContainerBrand: any; } // Ideally, `ForEachChildNodes` and `VisitEachChildNodes` would not differ. @@ -1419,15 +1449,18 @@ namespace ts { */ readonly escapedText: __String; readonly originalKeywordKind?: SyntaxKind; // Original syntaxKind which get set so that we can report an error later - /*@internal*/ readonly autoGenerateFlags?: GeneratedIdentifierFlags; // Specifies whether to auto-generate the text for an identifier. - /*@internal*/ readonly autoGenerateId?: number; // Ensures unique generated identifiers get unique names, but clones get the same name. - /*@internal*/ readonly autoGeneratePrefix?: string | GeneratedNamePart; - /*@internal*/ readonly autoGenerateSuffix?: string; - /*@internal*/ generatedImportReference?: ImportSpecifier; // Reference to the generated import specifier this identifier refers to + /*@internal*/ readonly hasExtendedUnicodeEscape?: boolean; isInJSDocNamespace?: boolean; // if the node is a member in a JSDoc namespace /*@internal*/ typeArguments?: NodeArray; // Only defined on synthesized nodes. Though not syntactically valid, used in emitting diagnostics, quickinfo, and signature help. - /*@internal*/ jsdocDotPos?: number; // Identifier occurs in JSDoc-style generic: Id. - /*@internal*/ hasExtendedUnicodeEscape?: boolean; + /*@internal*/ idExtra?: IdentifierExtraFields; + } + + /* @internal */ + export interface IdentifierExtraFields { + autoGenerateFlags?: GeneratedIdentifierFlags; // Specifies whether to auto-generate the text for an identifier. + autoGenerateId?: number; // Ensures unique generated identifiers get unique names, but clones get the same name. + autoGeneratePrefix?: string | GeneratedNamePart; + autoGenerateSuffix?: string; } // Transient identifier node (marked by id === -1) @@ -1437,6 +1470,11 @@ namespace ts { /*@internal*/ export interface GeneratedIdentifier extends Identifier { + readonly idExtra: GeneratedIdentifierExtraFields; + } + + /* @internal */ + export interface GeneratedIdentifierExtraFields extends IdentifierExtraFields { autoGenerateFlags: GeneratedIdentifierFlags; } @@ -1513,10 +1551,7 @@ namespace ts { // escaping not strictly necessary // avoids gotchas in transforms and utils readonly escapedText: __String; - /*@internal*/ readonly autoGenerateFlags?: GeneratedIdentifierFlags; // Specifies whether to auto-generate the text for an identifier. - /*@internal*/ readonly autoGenerateId?: number; // Ensures unique generated identifiers get unique names, but clones get the same name. - /*@internal*/ readonly autoGeneratePrefix?: string | GeneratedNamePart; - /*@internal*/ readonly autoGenerateSuffix?: string; + /*@internal*/ idExtra?: IdentifierExtraFields; } /*@internal*/ @@ -1780,8 +1815,6 @@ namespace ts { readonly questionToken?: QuestionToken | undefined; readonly exclamationToken?: ExclamationToken | undefined; readonly body?: Block | Expression | undefined; - /* @internal */ endFlowNode?: FlowNode; - /* @internal */ returnFlowNode?: FlowNode; } export type FunctionLikeDeclaration = @@ -1894,9 +1927,6 @@ namespace ts { readonly parent: ClassDeclaration | ClassExpression; readonly body: Block; - /* @internal */ endFlowNode?: FlowNode; - /* @internal */ returnFlowNode?: FlowNode; - // The following properties are used only to report grammar errors /* @internal */ readonly illegalDecorators?: NodeArray | undefined; /* @internal */ readonly modifiers?: NodeArray | undefined; @@ -2653,7 +2683,7 @@ namespace ts { } // An ObjectLiteralExpression is the declaration node for an anonymous symbol. - export interface ObjectLiteralExpression extends ObjectLiteralExpressionBase { + export interface ObjectLiteralExpression extends ObjectLiteralExpressionBase, JSDocContainer { readonly kind: SyntaxKind.ObjectLiteralExpression; /* @internal */ multiLine?: boolean; @@ -3071,7 +3101,7 @@ namespace ts { /*@internal*/ multiLine?: boolean; } - export interface VariableStatement extends Statement { + export interface VariableStatement extends Statement, JSDocContainer { readonly kind: SyntaxKind.VariableStatement; readonly modifiers?: NodeArray; readonly declarationList: VariableDeclarationList; @@ -3185,14 +3215,12 @@ namespace ts { readonly parent: CaseBlock; readonly expression: Expression; readonly statements: NodeArray; - /* @internal */ fallthroughFlowNode?: FlowNode; } export interface DefaultClause extends Node { readonly kind: SyntaxKind.DefaultClause; readonly parent: CaseBlock; readonly statements: NodeArray; - /* @internal */ fallthroughFlowNode?: FlowNode; } export type CaseOrDefaultClause = @@ -5574,7 +5602,14 @@ namespace ts { isExhaustive?: boolean | 0; // Is node an exhaustive switch statement (0 indicates in-process resolution) skipDirectInference?: true; // Flag set by the API `getContextualType` call on a node when `Completions` is passed to force the checker to skip making inferences to a node's type declarationRequiresScopeChange?: boolean; // Set by `useOuterVariableScopeInParameter` in checker when downlevel emit would change the name resolution scope inside of a parameter. - serializedTypes?: ESMap; // Collection of types serialized at this location + serializedTypes?: ESMap; // Collection of types serialized at this location + } + + /* @internal */ + export interface SerializedTypeEntry { + node: TypeNode; + truncating: boolean; + addedLength: number; } export const enum TypeFlags { @@ -7357,7 +7392,8 @@ namespace ts { helpers?: EmitHelper[]; // Emit helpers for the node startsOnNewLine?: boolean; // If the node should begin on a new line snippetElement?: SnippetElement; // Snippet element of the node - typeNode?: TypeNode; // VariableDeclaration type + typeNode?: TypeNode; // VariableDeclaration type + generatedImportReference?: ImportSpecifier; // Reference to the generated import specifier this identifier refers to } /* @internal */ @@ -7704,7 +7740,8 @@ namespace ts { createToken(token: TKind): KeywordTypeNode; createToken(token: TKind): ModifierToken; createToken(token: TKind): KeywordToken; - createToken(token: TKind): Token; + createToken(token: TKind): EndOfFileToken; + createToken(token: TKind): Token; /*@internal*/ createToken(token: TKind): Token; // diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 0571a9c92df0d..6b95632914a3f 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -482,7 +482,7 @@ namespace ts { } if (includeJsDoc && hasJSDocNodes(node)) { - return getTokenPosOfNode(node.jsDoc![0], sourceFile); + return getTokenPosOfNode(getJSDocExtraFields(node)!.jsDoc![0], sourceFile); } // For a syntax list, it is possible that one of its children has JSDocComment nodes, while @@ -557,7 +557,7 @@ namespace ts { * Gets flags that control emit behavior of a node. */ export function getEmitFlags(node: Node): EmitFlags { - const emitNode = node.emitNode; + const emitNode = node.extra?.emitExtraFields; return emitNode && emitNode.flags || 0; } @@ -1000,7 +1000,7 @@ namespace ts { switch (name.kind) { case SyntaxKind.Identifier: case SyntaxKind.PrivateIdentifier: - return name.autoGenerateFlags ? undefined : name.escapedText; + return getIdentifierExtraFields(name)?.autoGenerateFlags ? undefined : name.escapedText; case SyntaxKind.StringLiteral: case SyntaxKind.NumericLiteral: case SyntaxKind.NoSubstitutionTemplateLiteral: @@ -2728,13 +2728,13 @@ namespace ts { let result: (JSDoc | JSDocTag)[] | undefined; // Pull parameter comments from declaring function as well if (isVariableLike(hostNode) && hasInitializer(hostNode) && hasJSDocNodes(hostNode.initializer!)) { - result = addRange(result, filterOwnedJSDocTags(hostNode, last((hostNode.initializer as HasJSDoc).jsDoc!))); + result = addRange(result, filterOwnedJSDocTags(hostNode, last(getJSDocExtraFields(hostNode.initializer)!.jsDoc!))); } let node: Node | undefined = hostNode; while (node && node.parent) { if (hasJSDocNodes(node)) { - result = addRange(result, filterOwnedJSDocTags(hostNode, last(node.jsDoc!))); + result = addRange(result, filterOwnedJSDocTags(hostNode, last(getJSDocExtraFields(node)!.jsDoc!))); } if (node.kind === SyntaxKind.Parameter) { @@ -2858,8 +2858,11 @@ namespace ts { } const host = jsDoc.parent; - if (host && host.jsDoc && jsDoc === lastOrUndefined(host.jsDoc)) { - return host; + if (host) { + const jsDocFields = getJSDocExtraFields(host); + if (jsDocFields?.jsDoc && jsDoc === lastOrUndefined(jsDocFields.jsDoc)) { + return host; + } } } @@ -5205,7 +5208,8 @@ namespace ts { export function getLocalSymbolForExportDefault(symbol: Symbol) { if (!isExportDefaultSymbol(symbol) || !symbol.declarations) return undefined; for (const decl of symbol.declarations) { - if (decl.localSymbol) return decl.localSymbol; + const localSymbol = getBindExtraFields(decl)?.localSymbol; + if (localSymbol) return localSymbol; } return undefined; } @@ -5995,7 +5999,9 @@ namespace ts { this.transformFlags = TransformFlags.None; this.parent = undefined!; this.original = undefined; - } + this.symbol = undefined!; + this.extra = undefined; + }; function Token(this: Mutable, kind: SyntaxKind, pos: number, end: number) { this.pos = pos; @@ -6005,6 +6011,7 @@ namespace ts { this.flags = NodeFlags.None; this.transformFlags = TransformFlags.None; this.parent = undefined!; + this.extra = undefined; } function Identifier(this: Mutable, kind: SyntaxKind, pos: number, end: number) { @@ -6016,7 +6023,8 @@ namespace ts { this.transformFlags = TransformFlags.None; this.parent = undefined!; this.original = undefined; - this.flowNode = undefined; + this.symbol = undefined!; + this.extra = undefined; } function SourceMapSource(this: SourceMapSource, fileName: string, text: string, skipTrivia?: (pos: number) => number) { @@ -7473,7 +7481,7 @@ namespace ts { function bindJSDoc(child: Node) { if (hasJSDocNodes(child)) { - for (const doc of child.jsDoc!) { + for (const doc of getJSDocExtraFields(child)!.jsDoc!) { bindParentToChildIgnoringJSDoc(doc, child); forEachChildRecursively(doc, bindParentToChildIgnoringJSDoc); } @@ -7778,4 +7786,102 @@ namespace ts { return isEnumDeclaration(node) || isVariableStatement(node) || isFunctionDeclaration(node) || isClassDeclaration(node) || isInterfaceDeclaration(node) || isTypeDeclaration(node) || (isModuleDeclaration(node) && !isExternalModuleAugmentation(node) && !isGlobalScopeAugmentation(node)); } + + export function getOrCreateNodeExtraFields(node: Node) { + return node.extra ??= { + bindExtraFields: undefined, + flowExtraFields: undefined, + emitExtraFields: undefined, + checkExtraFields: undefined, + jsDocExtraFields: undefined, + }; + } + + export function getBindExtraFields(node: Node) { + return node.extra?.bindExtraFields; + } + + export function getOrCreateBindExtraFields(node: Node) { + return getOrCreateNodeExtraFields(node).bindExtraFields ??= { + locals: undefined, + nextContainer: undefined, + localSymbol: undefined + }; + } + + export function getCheckExtraFields(node: Node) { + return node.extra?.checkExtraFields; + } + + export function getOrCreateCheckExtraFields(node: Node) { + return getOrCreateNodeExtraFields(node).checkExtraFields ??= { + contextualType: undefined, + inferenceContext: undefined + }; + } + + export function getFlowExtraFields(node: Node) { + return node.extra?.flowExtraFields; + } + + export function getOrCreateFlowExtraFields(node: Node) { + return getOrCreateNodeExtraFields(node).flowExtraFields ??= { + flowNode: undefined, + endFlowNode: undefined, + returnFlowNode: undefined + }; + } + + export function setFlowNode(node: Node, flowNode: T) { + getOrCreateFlowExtraFields(node).flowNode = flowNode; + return flowNode; + } + + export function getFlowNode(node: Node) { + return getFlowExtraFields(node)?.flowNode; + } + + export function getEndFlowNode(node: FunctionLikeDeclarationBase | ClassStaticBlockDeclaration) { + return getFlowExtraFields(node)?.endFlowNode; + } + + export function setEndFlowNode(node: FunctionLikeDeclarationBase | ClassStaticBlockDeclaration, flowNode: T) { + getOrCreateFlowExtraFields(node).endFlowNode = flowNode; + return flowNode; + } + + export function getReturnFlowNode(node: FunctionLikeDeclarationBase | ClassStaticBlockDeclaration) { + return getFlowExtraFields(node)?.returnFlowNode; + } + + export function setReturnFlowNode(node: FunctionLikeDeclarationBase | ClassStaticBlockDeclaration, flowNode: T) { + getOrCreateFlowExtraFields(node).returnFlowNode = flowNode; + return flowNode; + } + + export function setFallthroughFlowNode(node: CaseOrDefaultClause, flowNode: T) { + getOrCreateFlowExtraFields(node).fallthroughFlowNode = flowNode; + return flowNode; + } + + export function getFallthroughFlowNode(node: CaseOrDefaultClause) { + return getFlowExtraFields(node)?.fallthroughFlowNode; + } + + export function getJSDocExtraFields(node: Node) { + return node.extra?.jsDocExtraFields; + } + + export function getOrCreateJSDocExtraFields(node: Node) { + return getOrCreateNodeExtraFields(node).jsDocExtraFields ??= { + jsDoc: undefined, + jsDocCache: undefined + }; + } + + export function getIdentifierExtraFields(node: GeneratedIdentifier | GeneratedPrivateIdentifier): GeneratedIdentifierExtraFields; + export function getIdentifierExtraFields(node: Identifier | PrivateIdentifier): IdentifierExtraFields | undefined; + export function getIdentifierExtraFields(node: Identifier | PrivateIdentifier) { + return node.idExtra; + } } diff --git a/src/compiler/utilitiesPublic.ts b/src/compiler/utilitiesPublic.ts index c6e083e9d7e1a..04294c36d3859 100644 --- a/src/compiler/utilitiesPublic.ts +++ b/src/compiler/utilitiesPublic.ts @@ -876,14 +876,14 @@ namespace ts { } function getJSDocTagsWorker(node: Node, noCache?: boolean): readonly JSDocTag[] { - let tags = (node as JSDocContainer).jsDocCache; + let tags = getJSDocExtraFields(node)?.jsDocCache; // If cache is 'null', that means we did the work of searching for JSDoc tags and came up with nothing. if (tags === undefined || noCache) { const comments = getJSDocCommentsAndTags(node, noCache); Debug.assert(comments.length < 2 || comments[0] !== comments[1]); tags = flatMap(comments, j => isJSDoc(j) ? j.tags : j); if (!noCache) { - (node as JSDocContainer).jsDocCache = tags; + getOrCreateJSDocExtraFields(node).jsDocCache = tags; } } return tags; @@ -1196,12 +1196,12 @@ namespace ts { /* @internal */ export function isGeneratedIdentifier(node: Node): node is GeneratedIdentifier { - return isIdentifier(node) && (node.autoGenerateFlags! & GeneratedIdentifierFlags.KindMask) > GeneratedIdentifierFlags.None; + return isIdentifier(node) && (getIdentifierExtraFields(node)?.autoGenerateFlags! & GeneratedIdentifierFlags.KindMask) > GeneratedIdentifierFlags.None; } /* @internal */ export function isGeneratedPrivateIdentifier(node: Node): node is GeneratedPrivateIdentifier { - return isPrivateIdentifier(node) && (node.autoGenerateFlags! & GeneratedIdentifierFlags.KindMask) > GeneratedIdentifierFlags.None; + return isPrivateIdentifier(node) && (getIdentifierExtraFields(node)?.autoGenerateFlags! & GeneratedIdentifierFlags.KindMask) > GeneratedIdentifierFlags.None; } // Private Identifiers @@ -2008,8 +2008,7 @@ namespace ts { /* @internal */ // TODO: GH#19856 Would like to return `node is Node & { jsDoc: JSDoc[] }` but it causes long compile times export function hasJSDocNodes(node: Node): node is HasJSDoc { - const { jsDoc } = node as JSDocContainer; - return !!jsDoc && jsDoc.length > 0; + return some(getJSDocExtraFields(node)?.jsDoc); } /** True if has type node attached to it. */ diff --git a/src/harness/harnessUtils.ts b/src/harness/harnessUtils.ts index 5ac968035346d..1ab563682440b 100644 --- a/src/harness/harnessUtils.ts +++ b/src/harness/harnessUtils.ts @@ -190,8 +190,6 @@ namespace Utils { switch (propertyName) { case "parent": case "symbol": - case "locals": - case "localSymbol": case "kind": case "id": case "nodeCount": @@ -219,9 +217,14 @@ namespace Utils { o[propertyName] = convertDiagnostics((n as any)[propertyName]); break; - case "nextContainer": - if (n.nextContainer) { - o[propertyName] = { kind: n.nextContainer.kind, pos: n.nextContainer.pos, end: n.nextContainer.end }; + case "extra": + const nextContainer = ts.getBindExtraFields(n)?.nextContainer; + if (nextContainer) { + o.nextContainer = { kind: nextContainer.kind, pos: nextContainer.pos, end: nextContainer.end } as ts.Node; + } + const jsDocDotPos = ts.getJSDocExtraFields(n)?.jsDocDotPos; + if (jsDocDotPos !== undefined) { + o.jsdocDotPos = jsDocDotPos; } break; diff --git a/src/services/codefixes/fixImportNonExportedMember.ts b/src/services/codefixes/fixImportNonExportedMember.ts index 5eb3a0e81d301..9b9b2ff014aa4 100644 --- a/src/services/codefixes/fixImportNonExportedMember.ts +++ b/src/services/codefixes/fixImportNonExportedMember.ts @@ -88,7 +88,7 @@ namespace ts.codefix { if (moduleSourceFile === undefined || isSourceFileFromLibrary(program, moduleSourceFile)) return undefined; const moduleSymbol = moduleSourceFile.symbol; - const locals = moduleSymbol.valueDeclaration?.locals; + const locals = moduleSymbol.valueDeclaration && getBindExtraFields(moduleSymbol.valueDeclaration)?.locals; if (locals === undefined) return undefined; const localSymbol = locals.get(token.escapedText); diff --git a/src/services/completions.ts b/src/services/completions.ts index 9bdc38195da54..489f47498b391 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -3193,7 +3193,7 @@ namespace ts.Completions { const localsContainer = findAncestor(namedExports, or(isSourceFile, isModuleDeclaration))!; completionKind = CompletionKind.None; isNewIdentifierLocation = false; - localsContainer.locals?.forEach((symbol, name) => { + getBindExtraFields(localsContainer)?.locals?.forEach((symbol, name) => { symbols.push(symbol); if (localsContainer.symbol?.exports?.has(name)) { symbolToSortTextMap[getSymbolId(symbol)] = SortText.OptionalMember; diff --git a/src/services/jsDoc.ts b/src/services/jsDoc.ts index a8d4c1a4e03fd..4f423f2dde551 100644 --- a/src/services/jsDoc.ts +++ b/src/services/jsDoc.ts @@ -357,7 +357,7 @@ namespace ts.JsDoc { } const { commentOwner, parameters, hasReturn } = commentOwnerInfo; - const commentOwnerJsDoc = hasJSDocNodes(commentOwner) && commentOwner.jsDoc ? commentOwner.jsDoc : undefined; + const commentOwnerJsDoc = hasJSDocNodes(commentOwner) ? getJSDocExtraFields(commentOwner)!.jsDoc : undefined; const lastJsDoc = lastOrUndefined(commentOwnerJsDoc); if (commentOwner.getStart(sourceFile) < position || lastJsDoc diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index ba870fb872037..ff9a1983b8a7c 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -452,7 +452,7 @@ namespace ts.NavigationBar { default: if (hasJSDocNodes(node)) { - forEach(node.jsDoc, jsDoc => { + forEach(getJSDocExtraFields(node)!.jsDoc, jsDoc => { forEach(jsDoc.tags, tag => { if (isJSDocTypeAlias(tag)) { addLeafNode(tag); diff --git a/src/services/services.ts b/src/services/services.ts index 618a274766957..78c9c9d0616e0 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -158,7 +158,7 @@ namespace ts { pos = nodes.end; }; // jsDocComments need to be the first children - forEach((node as JSDocContainer).jsDoc, processNode); + forEach(getJSDocExtraFields(node)?.jsDoc, processNode); // For syntactic classifications, all trivia are classified together, including jsdoc comments. // For that to work, the jsdoc comments should still be the leading trivia of the first child. // Restoring the scanner position ensures that. @@ -269,7 +269,7 @@ namespace ts { } public getChildren(): Node[] { - return this.kind === SyntaxKind.EndOfFileToken ? (this as EndOfFileToken).jsDoc || emptyArray : emptyArray; + return this.kind === SyntaxKind.EndOfFileToken ? getJSDocExtraFields(this as unknown as EndOfFileToken)?.jsDoc || emptyArray : emptyArray; } public getFirstToken(): Node | undefined { @@ -2764,7 +2764,7 @@ namespace ts { forEachChild(node, walk); if (hasJSDocNodes(node)) { - for (const jsDoc of node.jsDoc!) { + for (const jsDoc of getJSDocExtraFields(node)!.jsDoc!) { forEachChild(jsDoc, walk); } } diff --git a/src/services/smartSelection.ts b/src/services/smartSelection.ts index bacaf86f29894..6c4da6a93de36 100644 --- a/src/services/smartSelection.ts +++ b/src/services/smartSelection.ts @@ -60,8 +60,8 @@ namespace ts.SmartSelectionRange { let start = isBetweenMultiLineBookends ? prevNode.getEnd() : node.getStart(); const end = isBetweenMultiLineBookends ? nextNode.getStart() : getEndPos(sourceFile, node); - if (hasJSDocNodes(node) && node.jsDoc?.length) { - pushSelectionRange(first(node.jsDoc).getStart(), end); + if (hasJSDocNodes(node)) { + pushSelectionRange(first(getJSDocExtraFields(node)!.jsDoc!).getStart(), end); } // (#39618 & #49807) @@ -71,8 +71,8 @@ namespace ts.SmartSelectionRange { // covering the JSDoc comment before diving further. if (isSyntaxList(node)) { const firstChild = node.getChildren()[0]; - if (firstChild && hasJSDocNodes(firstChild) && firstChild.jsDoc?.length && firstChild.getStart() !== node.pos) { - start = Math.min(start, first(firstChild.jsDoc).getStart()); + if (firstChild && hasJSDocNodes(firstChild) && firstChild.getStart() !== node.pos) { + start = Math.min(start, first(getJSDocExtraFields(firstChild)!.jsDoc!).getStart()); } } pushSelectionRange(start, end); diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 23f15afe8f8ef..b9f9cdb3a76eb 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -478,8 +478,9 @@ namespace ts.textChanges { public insertJsdocCommentBefore(sourceFile: SourceFile, node: HasJSDoc, tag: JSDoc): void { const fnStart = node.getStart(sourceFile); - if (node.jsDoc) { - for (const jsdoc of node.jsDoc) { + const jsDoc = getJSDocExtraFields(node)?.jsDoc; + if (jsDoc) { + for (const jsdoc of jsDoc) { this.deleteRange(sourceFile, { pos: getLineStartPositionForPosition(jsdoc.getStart(sourceFile), sourceFile), end: getAdjustedEndPosition(sourceFile, jsdoc, /*options*/ {}) @@ -492,9 +493,10 @@ namespace ts.textChanges { } private createJSDocText(sourceFile: SourceFile, node: HasJSDoc) { - const comments = flatMap(node.jsDoc, jsDoc => + const jsDocArray = getJSDocExtraFields(node)?.jsDoc; + const comments = flatMap(jsDocArray, jsDoc => isString(jsDoc.comment) ? factory.createJSDocText(jsDoc.comment) : jsDoc.comment) as JSDocComment[]; - const jsDoc = singleOrUndefined(node.jsDoc); + const jsDoc = singleOrUndefined(jsDocArray); return jsDoc && positionsAreOnSameLine(jsDoc.pos, jsDoc.end, sourceFile) && length(comments) === 0 ? undefined : factory.createNodeArray(intersperse(comments, factory.createJSDocText("\n"))); } @@ -504,7 +506,7 @@ namespace ts.textChanges { } public addJSDocTags(sourceFile: SourceFile, parent: HasJSDoc, newTags: readonly JSDocTag[]): void { - const oldTags = flatMapToMutable(parent.jsDoc, j => j.tags); + const oldTags = flatMapToMutable(getJSDocExtraFields(parent)?.jsDoc, j => j.tags); const unmergedNewTags = newTags.filter(newTag => !oldTags.some((tag, i) => { const merged = tryMergeJsdocTags(tag, newTag); if (merged) oldTags[i] = merged; @@ -514,7 +516,7 @@ namespace ts.textChanges { } public filterJSDocTags(sourceFile: SourceFile, parent: HasJSDoc, predicate: (tag: JSDocTag) => boolean): void { - this.replaceJSDocComment(sourceFile, parent, filter(flatMapToMutable(parent.jsDoc, j => j.tags), predicate)); + this.replaceJSDocComment(sourceFile, parent, filter(flatMapToMutable(getJSDocExtraFields(parent)?.jsDoc, j => j.tags), predicate)); } public replaceRangeWithText(sourceFile: SourceFile, range: TextRange, text: string): void { @@ -964,8 +966,12 @@ namespace ts.textChanges { const jsDocNode = parent.parent.kind === SyntaxKind.PropertyDeclaration ? parent.parent as HasJSDoc : parent.parent.parent as HasJSDoc; - jsDocNode.jsDoc = parent.jsDoc; - jsDocNode.jsDocCache = parent.jsDocCache; + const parentJSDocFields = getJSDocExtraFields(parent); + if (parentJSDocFields) { + const hostJSDocFields = getOrCreateJSDocExtraFields(jsDocNode); + hostJSDocFields.jsDoc = parentJSDocFields.jsDoc; + hostJSDocFields.jsDocCache = parentJSDocFields.jsDocCache; + } return jsDocNode; } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 76d63dd114e97..7d6073a2fc694 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1676,7 +1676,7 @@ namespace ts { function nodeHasTokens(n: Node, sourceFile: SourceFileLike): boolean { // If we have a token or node that has a non-zero width, it must have tokens. // Note: getWidth() does not take trivia into account. - return n.kind === SyntaxKind.EndOfFileToken ? !!(n as EndOfFileToken).jsDoc : n.getWidth(sourceFile) !== 0; + return n.kind === SyntaxKind.EndOfFileToken ? !!getJSDocExtraFields(n as EndOfFileToken)?.jsDoc : n.getWidth(sourceFile) !== 0; } export function getNodeModifiers(node: Node, excludeFlags = ModifierFlags.None): string { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 1e42d9820832d..9e5b783806a66 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -582,6 +582,7 @@ declare namespace ts { readonly parent: Node; } export interface JSDocContainer { + _jsdocContainerBrand: any; } export type HasJSDoc = ParameterDeclaration | CallSignatureDeclaration | ClassStaticBlockDeclaration | ConstructSignatureDeclaration | MethodSignature | PropertySignature | ArrowFunction | ParenthesizedExpression | SpreadAssignment | ShorthandPropertyAssignment | PropertyAssignment | FunctionExpression | EmptyStatement | DebuggerStatement | Block | VariableStatement | ExpressionStatement | IfStatement | DoStatement | WhileStatement | ForStatement | ForInStatement | ForOfStatement | BreakStatement | ContinueStatement | ReturnStatement | WithStatement | SwitchStatement | LabeledStatement | ThrowStatement | TryStatement | FunctionDeclaration | ConstructorDeclaration | MethodDeclaration | VariableDeclaration | PropertyDeclaration | AccessorDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | EnumMember | EnumDeclaration | ModuleDeclaration | ImportEqualsDeclaration | ImportDeclaration | NamespaceExportDeclaration | ExportAssignment | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType | ExportDeclaration | NamedTupleMember | ExportSpecifier | CaseClause | EndOfFileToken; export type HasType = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertySignature | PropertyDeclaration | TypePredicateNode | ParenthesizedTypeNode | TypeOperatorNode | MappedTypeNode | AssertionExpression | TypeAliasDeclaration | JSDocTypeExpression | JSDocNonNullableType | JSDocNullableType | JSDocOptionalType | JSDocVariadicType; @@ -1266,7 +1267,7 @@ declare namespace ts { export interface ObjectLiteralExpressionBase extends PrimaryExpression, Declaration { readonly properties: NodeArray; } - export interface ObjectLiteralExpression extends ObjectLiteralExpressionBase { + export interface ObjectLiteralExpression extends ObjectLiteralExpressionBase, JSDocContainer { readonly kind: SyntaxKind.ObjectLiteralExpression; } export type EntityNameExpression = Identifier | PropertyAccessEntityNameExpression; @@ -1466,7 +1467,7 @@ declare namespace ts { readonly kind: SyntaxKind.Block; readonly statements: NodeArray; } - export interface VariableStatement extends Statement { + export interface VariableStatement extends Statement, JSDocContainer { readonly kind: SyntaxKind.VariableStatement; readonly modifiers?: NodeArray; readonly declarationList: VariableDeclarationList; @@ -3440,7 +3441,8 @@ declare namespace ts { createToken(token: TKind): KeywordTypeNode; createToken(token: TKind): ModifierToken; createToken(token: TKind): KeywordToken; - createToken(token: TKind): Token; + createToken(token: TKind): EndOfFileToken; + createToken(token: TKind): Token; createSuper(): SuperExpression; createThis(): ThisExpression; createNull(): NullLiteral; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index f574fc2325abc..d39d36dcc1948 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -582,6 +582,7 @@ declare namespace ts { readonly parent: Node; } export interface JSDocContainer { + _jsdocContainerBrand: any; } export type HasJSDoc = ParameterDeclaration | CallSignatureDeclaration | ClassStaticBlockDeclaration | ConstructSignatureDeclaration | MethodSignature | PropertySignature | ArrowFunction | ParenthesizedExpression | SpreadAssignment | ShorthandPropertyAssignment | PropertyAssignment | FunctionExpression | EmptyStatement | DebuggerStatement | Block | VariableStatement | ExpressionStatement | IfStatement | DoStatement | WhileStatement | ForStatement | ForInStatement | ForOfStatement | BreakStatement | ContinueStatement | ReturnStatement | WithStatement | SwitchStatement | LabeledStatement | ThrowStatement | TryStatement | FunctionDeclaration | ConstructorDeclaration | MethodDeclaration | VariableDeclaration | PropertyDeclaration | AccessorDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | EnumMember | EnumDeclaration | ModuleDeclaration | ImportEqualsDeclaration | ImportDeclaration | NamespaceExportDeclaration | ExportAssignment | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType | ExportDeclaration | NamedTupleMember | ExportSpecifier | CaseClause | EndOfFileToken; export type HasType = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertySignature | PropertyDeclaration | TypePredicateNode | ParenthesizedTypeNode | TypeOperatorNode | MappedTypeNode | AssertionExpression | TypeAliasDeclaration | JSDocTypeExpression | JSDocNonNullableType | JSDocNullableType | JSDocOptionalType | JSDocVariadicType; @@ -1266,7 +1267,7 @@ declare namespace ts { export interface ObjectLiteralExpressionBase extends PrimaryExpression, Declaration { readonly properties: NodeArray; } - export interface ObjectLiteralExpression extends ObjectLiteralExpressionBase { + export interface ObjectLiteralExpression extends ObjectLiteralExpressionBase, JSDocContainer { readonly kind: SyntaxKind.ObjectLiteralExpression; } export type EntityNameExpression = Identifier | PropertyAccessEntityNameExpression; @@ -1466,7 +1467,7 @@ declare namespace ts { readonly kind: SyntaxKind.Block; readonly statements: NodeArray; } - export interface VariableStatement extends Statement { + export interface VariableStatement extends Statement, JSDocContainer { readonly kind: SyntaxKind.VariableStatement; readonly modifiers?: NodeArray; readonly declarationList: VariableDeclarationList; @@ -3440,7 +3441,8 @@ declare namespace ts { createToken(token: TKind): KeywordTypeNode; createToken(token: TKind): ModifierToken; createToken(token: TKind): KeywordToken; - createToken(token: TKind): Token; + createToken(token: TKind): EndOfFileToken; + createToken(token: TKind): Token; createSuper(): SuperExpression; createThis(): ThisExpression; createNull(): NullLiteral;