diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bebda41082c3b..9f85b552378ca 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5860,6 +5860,32 @@ namespace ts { return typeToTypeNodeHelper(type, context); } + function trackExistingEntityName(node: T, context: NodeBuilderContext, includePrivateSymbol?: (s: Symbol) => void) { + let introducesError = false; + const leftmost = getFirstIdentifier(node); + if (isInJSFile(node) && (isExportsIdentifier(leftmost) || isModuleExportsAccessExpression(leftmost.parent) || (isQualifiedName(leftmost.parent) && isModuleIdentifier(leftmost.parent.left) && isExportsIdentifier(leftmost.parent.right)))) { + introducesError = true; + return { introducesError, node }; + } + const sym = resolveEntityName(leftmost, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveALias*/ true); + if (sym) { + if (isSymbolAccessible(sym, context.enclosingDeclaration, SymbolFlags.All, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible) { + introducesError = true; + } + else { + context.tracker?.trackSymbol?.(sym, context.enclosingDeclaration, SymbolFlags.All); + includePrivateSymbol?.(sym); + } + if (isIdentifier(node)) { + const name = sym.flags & SymbolFlags.TypeParameter ? typeParameterToName(getDeclaredTypeOfSymbol(sym), context) : factory.cloneNode(node); + name.symbol = sym; // for quickinfo, which uses identifier symbol information + return { introducesError, node: setEmitFlags(setOriginalNode(name, node), EmitFlags.NoAsciiEscaping) }; + } + } + + return { introducesError, node }; + } + function serializeExistingTypeNode(context: NodeBuilderContext, existing: TypeNode, includePrivateSymbol?: (s: Symbol) => void, bundled?: boolean) { if (cancellationToken && cancellationToken.throwIfCancellationRequested) { cancellationToken.throwIfCancellationRequested(); @@ -5983,25 +6009,10 @@ namespace ts { } if (isEntityName(node) || isEntityNameExpression(node)) { - const leftmost = getFirstIdentifier(node); - if (isInJSFile(node) && (isExportsIdentifier(leftmost) || isModuleExportsAccessExpression(leftmost.parent) || (isQualifiedName(leftmost.parent) && isModuleIdentifier(leftmost.parent.left) && isExportsIdentifier(leftmost.parent.right)))) { - hadError = true; - return node; - } - const sym = resolveEntityName(leftmost, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveALias*/ true); - if (sym) { - if (isSymbolAccessible(sym, context.enclosingDeclaration, SymbolFlags.All, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible) { - hadError = true; - } - else { - context.tracker?.trackSymbol?.(sym, context.enclosingDeclaration, SymbolFlags.All); - includePrivateSymbol?.(sym); - } - if (isIdentifier(node)) { - const name = sym.flags & SymbolFlags.TypeParameter ? typeParameterToName(getDeclaredTypeOfSymbol(sym), context) : factory.cloneNode(node); - name.symbol = sym; // for quickinfo, which uses identifier symbol information - return setEmitFlags(setOriginalNode(name, node), EmitFlags.NoAsciiEscaping); - } + const { introducesError, node: result } = trackExistingEntityName(node, context, includePrivateSymbol); + hadError = hadError || introducesError; + if (result !== node) { + return result; } } @@ -6732,12 +6743,50 @@ namespace ts { !(p.flags & SymbolFlags.Prototype || p.escapedName === "prototype" || p.valueDeclaration && getEffectiveModifierFlags(p.valueDeclaration) & ModifierFlags.Static && isClassLike(p.valueDeclaration.parent)); } + function sanitizeJSDocImplements(clauses: readonly ExpressionWithTypeArguments[]): ExpressionWithTypeArguments[] | undefined { + const result = mapDefined(clauses, e => { + const oldEnclosing = context.enclosingDeclaration; + context.enclosingDeclaration = e; + let expr = e.expression; + if (isEntityNameExpression(expr)) { + if (isIdentifier(expr) && idText(expr) === "") { + return cleanup(/*result*/ undefined); // Empty heritage clause, should be an error, but prefer emitting no heritage clauses to reemitting the empty one + } + let introducesError: boolean; + ({ introducesError, node: expr } = trackExistingEntityName(expr, context, includePrivateSymbol)); + if (introducesError) { + return cleanup(/*result*/ undefined); + } + } + return cleanup(factory.createExpressionWithTypeArguments(expr, + map(e.typeArguments, a => + serializeExistingTypeNode(context, a, includePrivateSymbol, bundled) + || typeToTypeNodeHelper(getTypeFromTypeNode(a), context) + ) + )); + + function cleanup(result: T): T { + context.enclosingDeclaration = oldEnclosing; + return result; + } + }); + if (result.length === clauses.length) { + return result; + } + return undefined; + } + function serializeAsClass(symbol: Symbol, localName: string, modifierFlags: ModifierFlags) { + const originalDecl = find(symbol.declarations, isClassLike); + const oldEnclosing = context.enclosingDeclaration; + context.enclosingDeclaration = originalDecl || oldEnclosing; const localParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); const typeParamDecls = map(localParams, p => typeParameterToDeclaration(p, context)); const classType = getDeclaredTypeOfClassOrInterface(symbol); const baseTypes = getBaseTypes(classType); - const implementsExpressions = mapDefined(getImplementsTypes(classType), serializeImplementedType); + const originalImplements = originalDecl && getEffectiveImplementsTypeNodes(originalDecl); + const implementsExpressions = originalImplements && sanitizeJSDocImplements(originalImplements) + || mapDefined(getImplementsTypes(classType), serializeImplementedType); const staticType = getTypeOfSymbol(symbol); const isClass = !!staticType.symbol?.valueDeclaration && isClassLike(staticType.symbol.valueDeclaration); const staticBaseType = isClass @@ -6790,6 +6839,7 @@ namespace ts { [factory.createConstructorDeclaration(/*decorators*/ undefined, factory.createModifiersFromModifierFlags(ModifierFlags.Private), [], /*body*/ undefined)] : serializeSignatures(SignatureKind.Construct, staticType, staticBaseType, SyntaxKind.Constructor) as ConstructorDeclaration[]; const indexSignatures = serializeIndexSignatures(classType, baseTypes[0]); + context.enclosingDeclaration = oldEnclosing; addResult(setTextRange(factory.createClassDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, diff --git a/tests/baselines/reference/jsDeclarationsClassImplementsGenericsSerialization.js b/tests/baselines/reference/jsDeclarationsClassImplementsGenericsSerialization.js new file mode 100644 index 0000000000000..04fbe98c54625 --- /dev/null +++ b/tests/baselines/reference/jsDeclarationsClassImplementsGenericsSerialization.js @@ -0,0 +1,71 @@ +//// [tests/cases/conformance/jsdoc/declarations/jsDeclarationsClassImplementsGenericsSerialization.ts] //// + +//// [interface.ts] +export interface Encoder { + encode(value: T): Uint8Array +} +//// [lib.js] +/** + * @template T + * @implements {IEncoder} + */ +export class Encoder { + /** + * @param {T} value + */ + encode(value) { + return new Uint8Array(0) + } +} + + +/** + * @template T + * @typedef {import('./interface').Encoder} IEncoder + */ + +//// [interface.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//// [lib.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Encoder = void 0; +/** + * @template T + * @implements {IEncoder} + */ +var Encoder = /** @class */ (function () { + function Encoder() { + } + /** + * @param {T} value + */ + Encoder.prototype.encode = function (value) { + return new Uint8Array(0); + }; + return Encoder; +}()); +exports.Encoder = Encoder; +/** + * @template T + * @typedef {import('./interface').Encoder} IEncoder + */ + + +//// [interface.d.ts] +export interface Encoder { + encode(value: T): Uint8Array; +} +//// [lib.d.ts] +/** + * @template T + * @implements {IEncoder} + */ +export class Encoder implements IEncoder { + /** + * @param {T} value + */ + encode(value: T): Uint8Array; +} +export type IEncoder = import("./interface").Encoder; diff --git a/tests/baselines/reference/jsDeclarationsClassImplementsGenericsSerialization.symbols b/tests/baselines/reference/jsDeclarationsClassImplementsGenericsSerialization.symbols new file mode 100644 index 0000000000000..ac297390e27d5 --- /dev/null +++ b/tests/baselines/reference/jsDeclarationsClassImplementsGenericsSerialization.symbols @@ -0,0 +1,36 @@ +=== tests/cases/conformance/jsdoc/declarations/interface.ts === +export interface Encoder { +>Encoder : Symbol(Encoder, Decl(interface.ts, 0, 0)) +>T : Symbol(T, Decl(interface.ts, 0, 25)) + + encode(value: T): Uint8Array +>encode : Symbol(Encoder.encode, Decl(interface.ts, 0, 29)) +>value : Symbol(value, Decl(interface.ts, 1, 11)) +>T : Symbol(T, Decl(interface.ts, 0, 25)) +>Uint8Array : Symbol(Uint8Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +} +=== tests/cases/conformance/jsdoc/declarations/lib.js === +/** + * @template T + * @implements {IEncoder} + */ +export class Encoder { +>Encoder : Symbol(Encoder, Decl(lib.js, 0, 0)) + + /** + * @param {T} value + */ + encode(value) { +>encode : Symbol(Encoder.encode, Decl(lib.js, 4, 22)) +>value : Symbol(value, Decl(lib.js, 8, 11)) + + return new Uint8Array(0) +>Uint8Array : Symbol(Uint8Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } +} + + +/** + * @template T + * @typedef {import('./interface').Encoder} IEncoder + */ diff --git a/tests/baselines/reference/jsDeclarationsClassImplementsGenericsSerialization.types b/tests/baselines/reference/jsDeclarationsClassImplementsGenericsSerialization.types new file mode 100644 index 0000000000000..96a3c6c55f61c --- /dev/null +++ b/tests/baselines/reference/jsDeclarationsClassImplementsGenericsSerialization.types @@ -0,0 +1,33 @@ +=== tests/cases/conformance/jsdoc/declarations/interface.ts === +export interface Encoder { + encode(value: T): Uint8Array +>encode : (value: T) => Uint8Array +>value : T +} +=== tests/cases/conformance/jsdoc/declarations/lib.js === +/** + * @template T + * @implements {IEncoder} + */ +export class Encoder { +>Encoder : Encoder + + /** + * @param {T} value + */ + encode(value) { +>encode : (value: T) => Uint8Array +>value : T + + return new Uint8Array(0) +>new Uint8Array(0) : Uint8Array +>Uint8Array : Uint8ArrayConstructor +>0 : 0 + } +} + + +/** + * @template T + * @typedef {import('./interface').Encoder} IEncoder + */ diff --git a/tests/baselines/reference/jsDeclarationsClassesErr.js b/tests/baselines/reference/jsDeclarationsClassesErr.js index bb3006e9c6908..50f6099701dc4 100644 --- a/tests/baselines/reference/jsDeclarationsClassesErr.js +++ b/tests/baselines/reference/jsDeclarationsClassesErr.js @@ -222,11 +222,11 @@ exports.CC = CC; //// [index.d.ts] -export class M { - field: T_1; +export class M { + field: T; } -export class N extends M { - other: U_1; +export class N extends M { + other: U; } export class O { [idx: string]: string; diff --git a/tests/cases/conformance/jsdoc/declarations/jsDeclarationsClassImplementsGenericsSerialization.ts b/tests/cases/conformance/jsdoc/declarations/jsDeclarationsClassImplementsGenericsSerialization.ts new file mode 100644 index 0000000000000..3aa94654c0dca --- /dev/null +++ b/tests/cases/conformance/jsdoc/declarations/jsDeclarationsClassImplementsGenericsSerialization.ts @@ -0,0 +1,28 @@ +// @allowJs: true +// @checkJs: true +// @target: es5 +// @outDir: ./out +// @declaration: true +// @filename: interface.ts +export interface Encoder { + encode(value: T): Uint8Array +} +// @filename: lib.js +/** + * @template T + * @implements {IEncoder} + */ +export class Encoder { + /** + * @param {T} value + */ + encode(value) { + return new Uint8Array(0) + } +} + + +/** + * @template T + * @typedef {import('./interface').Encoder} IEncoder + */ \ No newline at end of file