Skip to content

Commit d5280d0

Browse files
committed
Reuse input nodes where possible when serializing jsdoc implements clauses
1 parent 9a957e7 commit d5280d0

6 files changed

+236
-24
lines changed

src/compiler/checker.ts

+64-20
Original file line numberDiff line numberDiff line change
@@ -5860,6 +5860,32 @@ namespace ts {
58605860
return typeToTypeNodeHelper(type, context);
58615861
}
58625862

5863+
function trackExistingEntityName<T extends EntityNameOrEntityNameExpression>(node: T, context: NodeBuilderContext, includePrivateSymbol?: (s: Symbol) => void) {
5864+
let introducesError = false;
5865+
const leftmost = getFirstIdentifier(node);
5866+
if (isInJSFile(node) && (isExportsIdentifier(leftmost) || isModuleExportsAccessExpression(leftmost.parent) || (isQualifiedName(leftmost.parent) && isModuleIdentifier(leftmost.parent.left) && isExportsIdentifier(leftmost.parent.right)))) {
5867+
introducesError = true;
5868+
return { introducesError, node };
5869+
}
5870+
const sym = resolveEntityName(leftmost, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveALias*/ true);
5871+
if (sym) {
5872+
if (isSymbolAccessible(sym, context.enclosingDeclaration, SymbolFlags.All, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible) {
5873+
introducesError = true;
5874+
}
5875+
else {
5876+
context.tracker?.trackSymbol?.(sym, context.enclosingDeclaration, SymbolFlags.All);
5877+
includePrivateSymbol?.(sym);
5878+
}
5879+
if (isIdentifier(node)) {
5880+
const name = sym.flags & SymbolFlags.TypeParameter ? typeParameterToName(getDeclaredTypeOfSymbol(sym), context) : factory.cloneNode(node);
5881+
name.symbol = sym; // for quickinfo, which uses identifier symbol information
5882+
return { introducesError, node: setEmitFlags(setOriginalNode(name, node), EmitFlags.NoAsciiEscaping) };
5883+
}
5884+
}
5885+
5886+
return { introducesError, node };
5887+
}
5888+
58635889
function serializeExistingTypeNode(context: NodeBuilderContext, existing: TypeNode, includePrivateSymbol?: (s: Symbol) => void, bundled?: boolean) {
58645890
if (cancellationToken && cancellationToken.throwIfCancellationRequested) {
58655891
cancellationToken.throwIfCancellationRequested();
@@ -5971,25 +5997,10 @@ namespace ts {
59715997
}
59725998

59735999
if (isEntityName(node) || isEntityNameExpression(node)) {
5974-
const leftmost = getFirstIdentifier(node);
5975-
if (isInJSFile(node) && (isExportsIdentifier(leftmost) || isModuleExportsAccessExpression(leftmost.parent) || (isQualifiedName(leftmost.parent) && isModuleIdentifier(leftmost.parent.left) && isExportsIdentifier(leftmost.parent.right)))) {
5976-
hadError = true;
5977-
return node;
5978-
}
5979-
const sym = resolveEntityName(leftmost, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveALias*/ true);
5980-
if (sym) {
5981-
if (isSymbolAccessible(sym, context.enclosingDeclaration, SymbolFlags.All, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible) {
5982-
hadError = true;
5983-
}
5984-
else {
5985-
context.tracker?.trackSymbol?.(sym, context.enclosingDeclaration, SymbolFlags.All);
5986-
includePrivateSymbol?.(sym);
5987-
}
5988-
if (isIdentifier(node)) {
5989-
const name = sym.flags & SymbolFlags.TypeParameter ? typeParameterToName(getDeclaredTypeOfSymbol(sym), context) : factory.cloneNode(node);
5990-
name.symbol = sym; // for quickinfo, which uses identifier symbol information
5991-
return setEmitFlags(setOriginalNode(name, node), EmitFlags.NoAsciiEscaping);
5992-
}
6000+
const { introducesError, node: result } = trackExistingEntityName(node, context, includePrivateSymbol);
6001+
hadError = hadError || introducesError;
6002+
if (result !== node) {
6003+
return result;
59936004
}
59946005
}
59956006

@@ -6716,12 +6727,44 @@ namespace ts {
67166727
!(p.flags & SymbolFlags.Prototype || p.escapedName === "prototype" || p.valueDeclaration && getEffectiveModifierFlags(p.valueDeclaration) & ModifierFlags.Static && isClassLike(p.valueDeclaration.parent));
67176728
}
67186729

6730+
function sanitizeJSDocImplements(clauses: readonly ExpressionWithTypeArguments[]): ExpressionWithTypeArguments[] | undefined {
6731+
const result = mapDefined(clauses, e => {
6732+
const oldEnclosing = context.enclosingDeclaration;
6733+
context.enclosingDeclaration = e;
6734+
let expr = e.expression;
6735+
if (isEntityNameExpression(expr)) {
6736+
if (isIdentifier(expr) && idText(expr) === "") {
6737+
return cleanup(/*result*/ undefined); // Empty heritage clause, should be an error, but prefer emitting no heritage clauses to reemitting the empty one
6738+
}
6739+
let introducesError: boolean;
6740+
({ introducesError, node: expr } = trackExistingEntityName(expr, context, includePrivateSymbol));
6741+
if (introducesError) {
6742+
return cleanup(/*result*/ undefined);
6743+
}
6744+
}
6745+
return cleanup(factory.createExpressionWithTypeArguments(expr, map(e.typeArguments, a => serializeExistingTypeNode(context, a, includePrivateSymbol, bundled) || typeToTypeNodeHelper(getTypeFromTypeNode(a), context))));
6746+
6747+
function cleanup<T>(result: T): T {
6748+
context.enclosingDeclaration = oldEnclosing;
6749+
return result;
6750+
}
6751+
});
6752+
if (result.length === clauses.length) {
6753+
return result;
6754+
}
6755+
return undefined;
6756+
}
6757+
67196758
function serializeAsClass(symbol: Symbol, localName: string, modifierFlags: ModifierFlags) {
6759+
const originalDecl = find(symbol.declarations, isClassLike);
6760+
const oldEnclosing = context.enclosingDeclaration;
6761+
context.enclosingDeclaration = originalDecl || oldEnclosing;
67206762
const localParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol);
67216763
const typeParamDecls = map(localParams, p => typeParameterToDeclaration(p, context));
67226764
const classType = getDeclaredTypeOfClassOrInterface(symbol);
67236765
const baseTypes = getBaseTypes(classType);
6724-
const implementsExpressions = mapDefined(getImplementsTypes(classType), serializeImplementedType);
6766+
const originalImplements = originalDecl && getEffectiveImplementsTypeNodes(originalDecl);
6767+
const implementsExpressions = originalImplements && sanitizeJSDocImplements(originalImplements) || mapDefined(getImplementsTypes(classType), serializeImplementedType);
67256768
const staticType = getTypeOfSymbol(symbol);
67266769
const isClass = !!staticType.symbol?.valueDeclaration && isClassLike(staticType.symbol.valueDeclaration);
67276770
const staticBaseType = isClass
@@ -6774,6 +6817,7 @@ namespace ts {
67746817
[factory.createConstructorDeclaration(/*decorators*/ undefined, factory.createModifiersFromModifierFlags(ModifierFlags.Private), [], /*body*/ undefined)] :
67756818
serializeSignatures(SignatureKind.Construct, staticType, baseTypes[0], SyntaxKind.Constructor) as ConstructorDeclaration[];
67766819
const indexSignatures = serializeIndexSignatures(classType, baseTypes[0]);
6820+
context.enclosingDeclaration = oldEnclosing;
67776821
addResult(setTextRange(factory.createClassDeclaration(
67786822
/*decorators*/ undefined,
67796823
/*modifiers*/ undefined,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//// [tests/cases/conformance/jsdoc/declarations/jsDeclarationsClassImplementsGenericsSerialization.ts] ////
2+
3+
//// [interface.ts]
4+
export interface Encoder<T> {
5+
encode(value: T): Uint8Array
6+
}
7+
//// [lib.js]
8+
/**
9+
* @template T
10+
* @implements {IEncoder<T>}
11+
*/
12+
export class Encoder {
13+
/**
14+
* @param {T} value
15+
*/
16+
encode(value) {
17+
return new Uint8Array(0)
18+
}
19+
}
20+
21+
22+
/**
23+
* @template T
24+
* @typedef {import('./interface').Encoder<T>} IEncoder
25+
*/
26+
27+
//// [interface.js]
28+
"use strict";
29+
Object.defineProperty(exports, "__esModule", { value: true });
30+
//// [lib.js]
31+
"use strict";
32+
Object.defineProperty(exports, "__esModule", { value: true });
33+
exports.Encoder = void 0;
34+
/**
35+
* @template T
36+
* @implements {IEncoder<T>}
37+
*/
38+
var Encoder = /** @class */ (function () {
39+
function Encoder() {
40+
}
41+
/**
42+
* @param {T} value
43+
*/
44+
Encoder.prototype.encode = function (value) {
45+
return new Uint8Array(0);
46+
};
47+
return Encoder;
48+
}());
49+
exports.Encoder = Encoder;
50+
/**
51+
* @template T
52+
* @typedef {import('./interface').Encoder<T>} IEncoder
53+
*/
54+
55+
56+
//// [interface.d.ts]
57+
export interface Encoder<T> {
58+
encode(value: T): Uint8Array;
59+
}
60+
//// [lib.d.ts]
61+
/**
62+
* @template T
63+
* @implements {IEncoder<T>}
64+
*/
65+
export class Encoder<T> implements IEncoder<T> {
66+
/**
67+
* @param {T} value
68+
*/
69+
encode(value: T): Uint8Array;
70+
}
71+
export type IEncoder<T> = import("./interface").Encoder<T>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
=== tests/cases/conformance/jsdoc/declarations/interface.ts ===
2+
export interface Encoder<T> {
3+
>Encoder : Symbol(Encoder, Decl(interface.ts, 0, 0))
4+
>T : Symbol(T, Decl(interface.ts, 0, 25))
5+
6+
encode(value: T): Uint8Array
7+
>encode : Symbol(Encoder.encode, Decl(interface.ts, 0, 29))
8+
>value : Symbol(value, Decl(interface.ts, 1, 11))
9+
>T : Symbol(T, Decl(interface.ts, 0, 25))
10+
>Uint8Array : Symbol(Uint8Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
11+
}
12+
=== tests/cases/conformance/jsdoc/declarations/lib.js ===
13+
/**
14+
* @template T
15+
* @implements {IEncoder<T>}
16+
*/
17+
export class Encoder {
18+
>Encoder : Symbol(Encoder, Decl(lib.js, 0, 0))
19+
20+
/**
21+
* @param {T} value
22+
*/
23+
encode(value) {
24+
>encode : Symbol(Encoder.encode, Decl(lib.js, 4, 22))
25+
>value : Symbol(value, Decl(lib.js, 8, 11))
26+
27+
return new Uint8Array(0)
28+
>Uint8Array : Symbol(Uint8Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
29+
}
30+
}
31+
32+
33+
/**
34+
* @template T
35+
* @typedef {import('./interface').Encoder<T>} IEncoder
36+
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
=== tests/cases/conformance/jsdoc/declarations/interface.ts ===
2+
export interface Encoder<T> {
3+
encode(value: T): Uint8Array
4+
>encode : (value: T) => Uint8Array
5+
>value : T
6+
}
7+
=== tests/cases/conformance/jsdoc/declarations/lib.js ===
8+
/**
9+
* @template T
10+
* @implements {IEncoder<T>}
11+
*/
12+
export class Encoder {
13+
>Encoder : Encoder<T>
14+
15+
/**
16+
* @param {T} value
17+
*/
18+
encode(value) {
19+
>encode : (value: T) => Uint8Array
20+
>value : T
21+
22+
return new Uint8Array(0)
23+
>new Uint8Array(0) : Uint8Array
24+
>Uint8Array : Uint8ArrayConstructor
25+
>0 : 0
26+
}
27+
}
28+
29+
30+
/**
31+
* @template T
32+
* @typedef {import('./interface').Encoder<T>} IEncoder
33+
*/

tests/baselines/reference/jsDeclarationsClassesErr.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -222,11 +222,11 @@ exports.CC = CC;
222222

223223

224224
//// [index.d.ts]
225-
export class M<T_1> {
226-
field: T_1;
225+
export class M<T> {
226+
field: T;
227227
}
228-
export class N<U_1> extends M<U_1> {
229-
other: U_1;
228+
export class N<U> extends M<U> {
229+
other: U;
230230
}
231231
export class O {
232232
[idx: string]: string;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// @allowJs: true
2+
// @checkJs: true
3+
// @target: es5
4+
// @outDir: ./out
5+
// @declaration: true
6+
// @filename: interface.ts
7+
export interface Encoder<T> {
8+
encode(value: T): Uint8Array
9+
}
10+
// @filename: lib.js
11+
/**
12+
* @template T
13+
* @implements {IEncoder<T>}
14+
*/
15+
export class Encoder {
16+
/**
17+
* @param {T} value
18+
*/
19+
encode(value) {
20+
return new Uint8Array(0)
21+
}
22+
}
23+
24+
25+
/**
26+
* @template T
27+
* @typedef {import('./interface').Encoder<T>} IEncoder
28+
*/

0 commit comments

Comments
 (0)