@@ -5996,6 +5996,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
59965996 if (!nodeIsSynthesized(range) && !(range.flags & NodeFlags.Synthesized) && (!context.enclosingFile || context.enclosingFile !== getSourceFileOfNode(range))) {
59975997 range = factory.cloneNode(range);
59985998 }
5999+ if (range === location) return range;
59996000 if (!location) {
60006001 return range;
60016002 }
@@ -8340,6 +8341,62 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
83408341 }
83418342 }
83428343
8344+ function serializeTypeName(context: NodeBuilderContext, node: EntityName, isTypeOf?: boolean, typeArguments?: readonly TypeNode[]) {
8345+ const meaning = isTypeOf ? SymbolFlags.Value : SymbolFlags.Type;
8346+ const symbol = resolveEntityName(node, meaning, /*ignoreErrors*/ true);
8347+ if (!symbol) return undefined;
8348+ const resolvedSymbol = symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol;
8349+ if (isSymbolAccessible(symbol, context.enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible) return undefined;
8350+ return symbolToTypeNode(resolvedSymbol, context, meaning, typeArguments);
8351+ }
8352+
8353+ function canReuseTypeNode(context: NodeBuilderContext, existing: TypeNode) {
8354+ if (isInJSFile(existing)) {
8355+ if (isLiteralImportTypeNode(existing)) {
8356+ // Ensure resolvedSymbol is present
8357+ void getTypeFromImportTypeNode(existing);
8358+ const nodeSymbol = getNodeLinks(existing).resolvedSymbol;
8359+ return (
8360+ !nodeSymbol ||
8361+ !(
8362+ // The import type resolved using jsdoc fallback logic
8363+ (!existing.isTypeOf && !(nodeSymbol.flags & SymbolFlags.Type)) ||
8364+ // The import type had type arguments autofilled by js fallback logic
8365+ !(length(existing.typeArguments) >= getMinTypeArgumentCount(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(nodeSymbol)))
8366+ )
8367+ );
8368+ }
8369+ }
8370+ if (isTypeReferenceNode(existing)) {
8371+ if (isConstTypeReference(existing)) return false;
8372+ const type = getTypeFromTypeReference(existing);
8373+ const symbol = getNodeLinks(existing).resolvedSymbol;
8374+ if (!symbol) return false;
8375+ if (symbol.flags & SymbolFlags.TypeParameter) {
8376+ return true;
8377+ }
8378+ if (isInJSDoc(existing)) {
8379+ return existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing, type)
8380+ && !getIntendedTypeFromJSDocTypeReference(existing) // We should probably allow the reuse of JSDoc reference types such as String Number etc
8381+ && (symbol.flags & SymbolFlags.Type); // JSDoc type annotations can reference values (meaning typeof value) as well as types. We only reuse type nodes
8382+ }
8383+ }
8384+ if (
8385+ isTypeOperatorNode(existing) &&
8386+ existing.operator === SyntaxKind.UniqueKeyword &&
8387+ existing.type.kind === SyntaxKind.SymbolKeyword
8388+ ) {
8389+ const effectiveEnclosingContext = context.enclosingDeclaration && getEnclosingDeclarationIgnoringFakeScope(context.enclosingDeclaration);
8390+ return !!findAncestor(existing, n => n === effectiveEnclosingContext);
8391+ }
8392+ return true;
8393+ }
8394+
8395+ function serializeExistingTypeNode(context: NodeBuilderContext, typeNode: TypeNode) {
8396+ const type = getTypeFromTypeNode(typeNode);
8397+ return typeToTypeNodeHelper(type, context);
8398+ }
8399+
83438400 /**
83448401 * Do you mean to call this directly? You probably should use `tryReuseExistingTypeNode` instead,
83458402 * which performs sanity checking on the type before doing this.
@@ -8353,6 +8410,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
83538410 if (hadError) {
83548411 return undefined;
83558412 }
8413+ context.approximateLength += existing.end - existing.pos;
83568414 return transformed;
83578415
83588416 function visitExistingNodeTreeSymbols(node: Node): Node | undefined {
@@ -8456,8 +8514,27 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
84568514 );
84578515 }
84588516 }
8459- if (isTypeReferenceNode(node) && isInJSDoc(node) && (!existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(node, getTypeFromTypeNode(node)) || getIntendedTypeFromJSDocTypeReference(node) || unknownSymbol === resolveTypeReferenceName(node, SymbolFlags.Type, /*ignoreErrors*/ true))) {
8460- return setOriginalNode(typeToTypeNodeHelper(getTypeFromTypeNode(node), context), node);
8517+ if (isTypeReferenceNode(node)) {
8518+ if (canReuseTypeNode(context, node)) {
8519+ const { introducesError, node: newName } = trackExistingEntityName(node.typeName, context);
8520+ const typeArguments = visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode);
8521+
8522+ if (!introducesError) {
8523+ const updated = factory.updateTypeReferenceNode(
8524+ node,
8525+ newName,
8526+ typeArguments,
8527+ );
8528+ return setTextRange(context, updated, node);
8529+ }
8530+ else {
8531+ const serializedName = serializeTypeName(context, node.typeName, /*isTypeOf*/ false, typeArguments);
8532+ if (serializedName) {
8533+ return setTextRange(context, serializedName, node.typeName);
8534+ }
8535+ }
8536+ }
8537+ return serializeExistingTypeNode(context, node);
84618538 }
84628539 if (isLiteralImportTypeNode(node)) {
84638540 const nodeSymbol = getNodeLinks(node).resolvedSymbol;
@@ -8471,7 +8548,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
84718548 !(length(node.typeArguments) >= getMinTypeArgumentCount(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(nodeSymbol)))
84728549 )
84738550 ) {
8474- return setOriginalNode( typeToTypeNodeHelper(getTypeFromTypeNode(node), context), node);
8551+ return setTextRange(context, typeToTypeNodeHelper(getTypeFromTypeNode(node), context), node);
84758552 }
84768553 return factory.updateImportTypeNode(
84778554 node,
@@ -8503,15 +8580,47 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
85038580 }
85048581 return visited;
85058582 }
8506-
8507- if (isEntityName(node) || isEntityNameExpression(node)) {
8508- if (isDeclarationName(node)) {
8509- return node;
8583+ if (isTypeQueryNode(node)) {
8584+ const { introducesError, node: exprName } = trackExistingEntityName(node.exprName, context);
8585+ if (introducesError) {
8586+ const serializedName = serializeTypeName(context, node.exprName, /*isTypeOf*/ true);
8587+ if (serializedName) {
8588+ return setTextRange(context, serializedName, node.exprName);
8589+ }
8590+ return serializeExistingTypeNode(context, node);
85108591 }
8511- const { introducesError, node: result } = trackExistingEntityName(node, context);
8592+ return factory.updateTypeQueryNode(
8593+ node,
8594+ exprName,
8595+ visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode),
8596+ );
8597+ }
8598+ if (isComputedPropertyName(node) && isEntityNameExpression(node.expression)) {
8599+ const { node: result, introducesError } = trackExistingEntityName(node.expression, context);
8600+ if (!introducesError) {
8601+ return factory.updateComputedPropertyName(node, result);
8602+ }
8603+ else {
8604+ const type = getWidenedType(getRegularTypeOfExpression(node.expression));
8605+ const computedPropertyNameType = typeToTypeNodeHelper(type, context);
8606+ Debug.assertNode(computedPropertyNameType, isLiteralTypeNode);
8607+ const literal = computedPropertyNameType.literal;
8608+ if (literal.kind === SyntaxKind.StringLiteral && isIdentifierText(literal.text, getEmitScriptTarget(compilerOptions))) {
8609+ return factory.createIdentifier(literal.text);
8610+ }
8611+ if (literal.kind === SyntaxKind.NumericLiteral && !literal.text.startsWith("-")) {
8612+ return literal;
8613+ }
8614+ return factory.updateComputedPropertyName(node, literal);
8615+ }
8616+ }
8617+ if (isTypePredicateNode(node) && isIdentifier(node.parameterName)) {
8618+ const { node: result, introducesError } = trackExistingEntityName(node.parameterName, context);
8619+ // Should not usually happen the only case is when a type predicate comes from a JSDoc type annotation with it's own parameter symbol definition.
8620+ // /** @type {(v: unknown) => v is undefined} */
8621+ // const isUndef = v => v === undefined;
85128622 hadError = hadError || introducesError;
8513- // We should not go to child nodes of the entity name, they will not be accessible
8514- return result;
8623+ return factory.updateTypePredicateNode(node, node.assertsModifier, result, visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode));
85158624 }
85168625
85178626 if (isTupleTypeNode(node) || isTypeLiteralNode(node) || isMappedTypeNode(node)) {
@@ -8543,6 +8652,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
85438652 );
85448653 }
85458654
8655+ if (isTypeOperatorNode(node) && node.operator === SyntaxKind.UniqueKeyword && node.type.kind === SyntaxKind.SymbolKeyword) {
8656+ if (!canReuseTypeNode(context, node)) {
8657+ return serializeExistingTypeNode(context, node);
8658+ }
8659+ }
8660+
85468661 return visitEachChild(node, visitExistingNodeTreeSymbols, /*context*/ undefined);
85478662
85488663 function getEffectiveDotDotDotForParameter(p: ParameterDeclaration) {
0 commit comments