diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 27787a6fa9e..f3e482be32a 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -6592,6 +6592,10 @@ "category": "Error", "code": 8038 }, + "A JSDoc '@template' tag may not follow a '@typedef', '@callback', or '@overload' tag": { + "category": "Error", + "code": 8039 + }, "Declaration emit for this file requires using private name '{0}'. An explicit type annotation may unblock declaration emit.": { "category": "Error", diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index fb31e3dbdc5..a053139789c 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -9140,12 +9140,15 @@ namespace Parser { function parseNestedTypeLiteral(typeExpression: JSDocTypeExpression | undefined, name: EntityName, target: PropertyLikeParse, indent: number) { if (typeExpression && isObjectOrObjectArrayTypeReference(typeExpression.type)) { const pos = getNodePos(); - let child: JSDocPropertyLikeTag | JSDocTypeTag | false; + let child: JSDocPropertyLikeTag | JSDocTypeTag | JSDocTemplateTag | false; let children: JSDocPropertyLikeTag[] | undefined; while (child = tryParse(() => parseChildParameterOrPropertyTag(target, indent, name))) { if (child.kind === SyntaxKind.JSDocParameterTag || child.kind === SyntaxKind.JSDocPropertyTag) { children = append(children, child); } + else if (child.kind === SyntaxKind.JSDocTemplateTag) { + parseErrorAtRange(child.tagName, Diagnostics.A_JSDoc_template_tag_may_not_follow_a_typedef_callback_or_overload_tag); + } } if (children) { const literal = finishNode(factory.createJSDocTypeLiteral(children, typeExpression.type.kind === SyntaxKind.ArrayType), pos); @@ -9291,11 +9294,14 @@ namespace Parser { let end: number | undefined; if (!typeExpression || isObjectOrObjectArrayTypeReference(typeExpression.type)) { - let child: JSDocTypeTag | JSDocPropertyTag | false; + let child: JSDocTypeTag | JSDocPropertyTag | JSDocTemplateTag | false; let childTypeTag: JSDocTypeTag | undefined; let jsDocPropertyTags: JSDocPropertyTag[] | undefined; let hasChildren = false; while (child = tryParse(() => parseChildPropertyTag(indent))) { + if (child.kind === SyntaxKind.JSDocTemplateTag) { + break; + } hasChildren = true; if (child.kind === SyntaxKind.JSDocTypeTag) { if (childTypeTag) { @@ -9359,12 +9365,15 @@ namespace Parser { return typeNameOrNamespaceName; } - function parseCallbackTagParameters(indent: number) { const pos = getNodePos(); - let child: JSDocParameterTag | false; + let child: JSDocParameterTag | JSDocTemplateTag | false; let parameters; - while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.CallbackParameter, indent) as JSDocParameterTag)) { + while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.CallbackParameter, indent) as JSDocParameterTag | JSDocTemplateTag)) { + if (child.kind === SyntaxKind.JSDocTemplateTag) { + parseErrorAtRange(child.tagName, Diagnostics.A_JSDoc_template_tag_may_not_follow_a_typedef_callback_or_overload_tag); + break; + } parameters = append(parameters, child); } return createNodeArray(parameters || [], pos); @@ -9420,10 +9429,10 @@ namespace Parser { } function parseChildPropertyTag(indent: number) { - return parseChildParameterOrPropertyTag(PropertyLikeParse.Property, indent) as JSDocTypeTag | JSDocPropertyTag | false; + return parseChildParameterOrPropertyTag(PropertyLikeParse.Property, indent) as JSDocTypeTag | JSDocPropertyTag | JSDocTemplateTag | false; } - function parseChildParameterOrPropertyTag(target: PropertyLikeParse, indent: number, name?: EntityName): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | false { + function parseChildParameterOrPropertyTag(target: PropertyLikeParse, indent: number, name?: EntityName): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | JSDocTemplateTag | false { let canParseTag = true; let seenAsterisk = false; while (true) { @@ -9459,13 +9468,13 @@ namespace Parser { } } - function tryParseChildTag(target: PropertyLikeParse, indent: number): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | false { + function tryParseChildTag(target: PropertyLikeParse, indent: number): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | JSDocTemplateTag | false { Debug.assert(token() === SyntaxKind.AtToken); const start = scanner.getTokenFullStart(); nextTokenJSDoc(); const tagName = parseJSDocIdentifierName(); - skipWhitespace(); + const indentText = skipWhitespaceOrAsterisk(); let t: PropertyLikeParse; switch (tagName.escapedText) { case "type": @@ -9479,6 +9488,8 @@ namespace Parser { case "param": t = PropertyLikeParse.Parameter | PropertyLikeParse.CallbackParameter; break; + case "template": + return parseTemplateTag(start, tagName, indent, indentText); default: return false; } diff --git a/tests/baselines/reference/templateInsideCallback.errors.txt b/tests/baselines/reference/templateInsideCallback.errors.txt new file mode 100644 index 00000000000..16b1951f31a --- /dev/null +++ b/tests/baselines/reference/templateInsideCallback.errors.txt @@ -0,0 +1,96 @@ +error TS-1: Pre-emit (11) and post-emit (13) diagnostic counts do not match! This can indicate that a semantic _error_ was added by the emit resolver - such an error may not be reflected on the command line or in the editor, but may be captured in a baseline here! +tests/cases/conformance/jsdoc/templateInsideCallback.js(2,13): error TS8021: JSDoc '@typedef' tag should either have a type annotation or be followed by '@property' or '@member' tags. +tests/cases/conformance/jsdoc/templateInsideCallback.js(9,5): error TS8039: A JSDoc '@template' tag may not follow a '@typedef', '@callback', or '@overload' tag +tests/cases/conformance/jsdoc/templateInsideCallback.js(10,12): error TS2304: Cannot find name 'T'. +tests/cases/conformance/jsdoc/templateInsideCallback.js(15,11): error TS2315: Type 'Call' is not generic. +tests/cases/conformance/jsdoc/templateInsideCallback.js(17,18): error TS7006: Parameter 'x' implicitly has an 'any' type. +tests/cases/conformance/jsdoc/templateInsideCallback.js(23,5): error TS8039: A JSDoc '@template' tag may not follow a '@typedef', '@callback', or '@overload' tag +tests/cases/conformance/jsdoc/templateInsideCallback.js(30,5): error TS8039: A JSDoc '@template' tag may not follow a '@typedef', '@callback', or '@overload' tag +tests/cases/conformance/jsdoc/templateInsideCallback.js(32,12): error TS2304: Cannot find name 'T'. +tests/cases/conformance/jsdoc/templateInsideCallback.js(33,16): error TS2304: Cannot find name 'T'. +tests/cases/conformance/jsdoc/templateInsideCallback.js(38,5): error TS8039: A JSDoc '@template' tag may not follow a '@typedef', '@callback', or '@overload' tag +tests/cases/conformance/jsdoc/templateInsideCallback.js(39,12): error TS2304: Cannot find name 'T'. + + +!!! error TS-1: Pre-emit (11) and post-emit (13) diagnostic counts do not match! This can indicate that a semantic _error_ was added by the emit resolver - such an error may not be reflected on the command line or in the editor, but may be captured in a baseline here! +!!! related TS-1: The excess diagnostics are: +!!! related TS7012 tests/cases/conformance/jsdoc/templateInsideCallback.js:29:5: This overload implicitly returns the type 'any' because it lacks a return type annotation. +!!! related TS7012 tests/cases/conformance/jsdoc/templateInsideCallback.js:37:5: This overload implicitly returns the type 'any' because it lacks a return type annotation. +==== tests/cases/conformance/jsdoc/templateInsideCallback.js (11 errors) ==== + /** + * @typedef Oops + ~~~~ +!!! error TS8021: JSDoc '@typedef' tag should either have a type annotation or be followed by '@property' or '@member' tags. + * @template T + * @property {T} a + * @property {T} b + */ + /** + * @callback Call + * @template T + ~~~~~~~~ +!!! error TS8039: A JSDoc '@template' tag may not follow a '@typedef', '@callback', or '@overload' tag + * @param {T} x + ~ +!!! error TS2304: Cannot find name 'T'. + * @returns {T} + */ + /** + * @template T + * @type {Call} + ~~~~~~~ +!!! error TS2315: Type 'Call' is not generic. + */ + const identity = x => x; + ~ +!!! error TS7006: Parameter 'x' implicitly has an 'any' type. + + /** + * @typedef Nested + * @property {Object} oh + * @property {number} oh.no + * @template T + ~~~~~~~~ +!!! error TS8039: A JSDoc '@template' tag may not follow a '@typedef', '@callback', or '@overload' tag + * @property {string} oh.noooooo + */ + + + /** + * @overload + * @template T + ~~~~~~~~ +!!! error TS8039: A JSDoc '@template' tag may not follow a '@typedef', '@callback', or '@overload' tag + * @template U + * @param {T[]} array + ~ +!!! error TS2304: Cannot find name 'T'. + * @param {(x: T) => U[]} iterable + ~ +!!! error TS2304: Cannot find name 'T'. + * @returns {U[]} + */ + /** + * @overload + * @template T + ~~~~~~~~ +!!! error TS8039: A JSDoc '@template' tag may not follow a '@typedef', '@callback', or '@overload' tag + * @param {T[][]} array + ~ +!!! error TS2304: Cannot find name 'T'. + * @returns {T[]} + */ + /** + * @param {unknown[]} array + * @param {(x: unknown) => unknown} iterable + * @returns {unknown[]} + */ + function flatMap(array, iterable = identity) { + /** @type {unknown[]} */ + const result = []; + for (let i = 0; i < array.length; i += 1) { + result.push(.../** @type {unknown[]} */(iterable(array[i]))); + } + return result; + } + \ No newline at end of file diff --git a/tests/baselines/reference/templateInsideCallback.js b/tests/baselines/reference/templateInsideCallback.js new file mode 100644 index 00000000000..41f18826061 --- /dev/null +++ b/tests/baselines/reference/templateInsideCallback.js @@ -0,0 +1,141 @@ +//// [templateInsideCallback.js] +/** + * @typedef Oops + * @template T + * @property {T} a + * @property {T} b + */ +/** + * @callback Call + * @template T + * @param {T} x + * @returns {T} + */ +/** + * @template T + * @type {Call} + */ +const identity = x => x; + +/** + * @typedef Nested + * @property {Object} oh + * @property {number} oh.no + * @template T + * @property {string} oh.noooooo + */ + + +/** + * @overload + * @template T + * @template U + * @param {T[]} array + * @param {(x: T) => U[]} iterable + * @returns {U[]} + */ +/** + * @overload + * @template T + * @param {T[][]} array + * @returns {T[]} + */ +/** + * @param {unknown[]} array + * @param {(x: unknown) => unknown} iterable + * @returns {unknown[]} + */ +function flatMap(array, iterable = identity) { + /** @type {unknown[]} */ + const result = []; + for (let i = 0; i < array.length; i += 1) { + result.push(.../** @type {unknown[]} */(iterable(array[i]))); + } + return result; +} + + +//// [templateInsideCallback.js] +"use strict"; +/** + * @typedef Oops + * @template T + * @property {T} a + * @property {T} b + */ +/** + * @callback Call + * @template T + * @param {T} x + * @returns {T} + */ +/** + * @template T + * @type {Call} + */ +var identity = function (x) { return x; }; +/** + * @typedef Nested + * @property {Object} oh + * @property {number} oh.no + * @template T + * @property {string} oh.noooooo + */ +/** + * @overload + * @template T + * @template U + * @param {T[]} array + * @param {(x: T) => U[]} iterable + * @returns {U[]} + */ +/** + * @overload + * @template T + * @param {T[][]} array + * @returns {T[]} + */ +/** + * @param {unknown[]} array + * @param {(x: unknown) => unknown} iterable + * @returns {unknown[]} + */ +function flatMap(array, iterable) { + if (iterable === void 0) { iterable = identity; } + /** @type {unknown[]} */ + var result = []; + for (var i = 0; i < array.length; i += 1) { + result.push.apply(result, /** @type {unknown[]} */ (iterable(array[i]))); + } + return result; +} + + +//// [templateInsideCallback.d.ts] +declare function flatMap(): any; +declare function flatMap(): any; +/** + * @typedef Oops + * @template T + * @property {T} a + * @property {T} b + */ +/** + * @callback Call + * @template T + * @param {T} x + * @returns {T} + */ +/** + * @template T + * @type {Call} + */ +declare const identity: any; +type Nested = { + oh: { + no: number; + noooooo: string; + }; +}; +type Oops = any; +type Call = () => any; diff --git a/tests/baselines/reference/templateInsideCallback.symbols b/tests/baselines/reference/templateInsideCallback.symbols new file mode 100644 index 00000000000..347c6396a04 --- /dev/null +++ b/tests/baselines/reference/templateInsideCallback.symbols @@ -0,0 +1,80 @@ +=== tests/cases/conformance/jsdoc/templateInsideCallback.js === +/** + * @typedef Oops + * @template T + * @property {T} a + * @property {T} b + */ +/** + * @callback Call + * @template T + * @param {T} x + * @returns {T} + */ +/** + * @template T + * @type {Call} + */ +const identity = x => x; +>identity : Symbol(identity, Decl(templateInsideCallback.js, 16, 5)) +>x : Symbol(x, Decl(templateInsideCallback.js, 16, 16)) +>x : Symbol(x, Decl(templateInsideCallback.js, 16, 16)) + +/** + * @typedef Nested + * @property {Object} oh + * @property {number} oh.no + * @template T + * @property {string} oh.noooooo + */ + + +/** + * @overload + * @template T + * @template U + * @param {T[]} array + * @param {(x: T) => U[]} iterable + * @returns {U[]} + */ +/** + * @overload + * @template T + * @param {T[][]} array + * @returns {T[]} + */ +/** + * @param {unknown[]} array + * @param {(x: unknown) => unknown} iterable + * @returns {unknown[]} + */ +function flatMap(array, iterable = identity) { +>flatMap : Symbol(flatMap, Decl(templateInsideCallback.js, 16, 24)) +>array : Symbol(array, Decl(templateInsideCallback.js, 46, 17)) +>iterable : Symbol(iterable, Decl(templateInsideCallback.js, 46, 23)) +>identity : Symbol(identity, Decl(templateInsideCallback.js, 16, 5)) + + /** @type {unknown[]} */ + const result = []; +>result : Symbol(result, Decl(templateInsideCallback.js, 48, 7)) + + for (let i = 0; i < array.length; i += 1) { +>i : Symbol(i, Decl(templateInsideCallback.js, 49, 10)) +>i : Symbol(i, Decl(templateInsideCallback.js, 49, 10)) +>array.length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --)) +>array : Symbol(array, Decl(templateInsideCallback.js, 46, 17)) +>length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --)) +>i : Symbol(i, Decl(templateInsideCallback.js, 49, 10)) + + result.push(.../** @type {unknown[]} */(iterable(array[i]))); +>result.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) +>result : Symbol(result, Decl(templateInsideCallback.js, 48, 7)) +>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) +>iterable : Symbol(iterable, Decl(templateInsideCallback.js, 46, 23)) +>array : Symbol(array, Decl(templateInsideCallback.js, 46, 17)) +>i : Symbol(i, Decl(templateInsideCallback.js, 49, 10)) + } + return result; +>result : Symbol(result, Decl(templateInsideCallback.js, 48, 7)) +} + diff --git a/tests/baselines/reference/templateInsideCallback.types b/tests/baselines/reference/templateInsideCallback.types new file mode 100644 index 00000000000..e15a9c2f5f7 --- /dev/null +++ b/tests/baselines/reference/templateInsideCallback.types @@ -0,0 +1,91 @@ +=== tests/cases/conformance/jsdoc/templateInsideCallback.js === +/** + * @typedef Oops + * @template T + * @property {T} a + * @property {T} b + */ +/** + * @callback Call + * @template T + * @param {T} x + * @returns {T} + */ +/** + * @template T + * @type {Call} + */ +const identity = x => x; +>identity : any +>x => x : (x: any) => any +>x : any +>x : any + +/** + * @typedef Nested + * @property {Object} oh + * @property {number} oh.no + * @template T + * @property {string} oh.noooooo + */ + + +/** + * @overload + * @template T + * @template U + * @param {T[]} array + * @param {(x: T) => U[]} iterable + * @returns {U[]} + */ +/** + * @overload + * @template T + * @param {T[][]} array + * @returns {T[]} + */ +/** + * @param {unknown[]} array + * @param {(x: unknown) => unknown} iterable + * @returns {unknown[]} + */ +function flatMap(array, iterable = identity) { +>flatMap : { (): any; (): any; } +>array : unknown[] +>iterable : (x: unknown) => unknown +>identity : any + + /** @type {unknown[]} */ + const result = []; +>result : unknown[] +>[] : never[] + + for (let i = 0; i < array.length; i += 1) { +>i : number +>0 : 0 +>i < array.length : boolean +>i : number +>array.length : number +>array : unknown[] +>length : number +>i += 1 : number +>i : number +>1 : 1 + + result.push(.../** @type {unknown[]} */(iterable(array[i]))); +>result.push(.../** @type {unknown[]} */(iterable(array[i]))) : number +>result.push : (...items: unknown[]) => number +>result : unknown[] +>push : (...items: unknown[]) => number +>.../** @type {unknown[]} */(iterable(array[i])) : unknown +>(iterable(array[i])) : unknown[] +>iterable(array[i]) : unknown +>iterable : (x: unknown) => unknown +>array[i] : unknown +>array : unknown[] +>i : number + } + return result; +>result : unknown[] +} + diff --git a/tests/cases/conformance/jsdoc/templateInsideCallback.ts b/tests/cases/conformance/jsdoc/templateInsideCallback.ts new file mode 100644 index 00000000000..226c366adb8 --- /dev/null +++ b/tests/cases/conformance/jsdoc/templateInsideCallback.ts @@ -0,0 +1,59 @@ +// @checkJs: true +// @strict: true +// @outDir: dist/ +// @declaration: true +// @filename: templateInsideCallback.js +/** + * @typedef Oops + * @template T + * @property {T} a + * @property {T} b + */ +/** + * @callback Call + * @template T + * @param {T} x + * @returns {T} + */ +/** + * @template T + * @type {Call} + */ +const identity = x => x; + +/** + * @typedef Nested + * @property {Object} oh + * @property {number} oh.no + * @template T + * @property {string} oh.noooooo + */ + + +/** + * @overload + * @template T + * @template U + * @param {T[]} array + * @param {(x: T) => U[]} iterable + * @returns {U[]} + */ +/** + * @overload + * @template T + * @param {T[][]} array + * @returns {T[]} + */ +/** + * @param {unknown[]} array + * @param {(x: unknown) => unknown} iterable + * @returns {unknown[]} + */ +function flatMap(array, iterable = identity) { + /** @type {unknown[]} */ + const result = []; + for (let i = 0; i < array.length; i += 1) { + result.push(.../** @type {unknown[]} */(iterable(array[i]))); + } + return result; +}