From b208b4fe2b1683a4fcc5f1086005f8fde2b5ee81 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 31 May 2018 16:24:37 -0700 Subject: [PATCH 01/13] Parse (and mostly support) template tag constraints A bunch of tests hit the asserts I added though. --- src/compiler/parser.ts | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index ea5a2a339372a..c59829a673691 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -7014,14 +7014,33 @@ namespace ts { function parseTemplateTag(atToken: AtToken, tagName: Identifier): JSDocTemplateTag | undefined { if (some(tags, isJSDocTemplateTag)) { + // TODO: Get rid of this restriction after making sure it's ok elsewhere parseErrorAt(tagName.pos, scanner.getTokenPos(), Diagnostics._0_tag_already_specified, tagName.escapedText); } - // Type parameter list looks like '@template T,U,V' + // Type parameter list looks like '@template {Constraint} T,U,V' const typeParameters = []; const typeParametersPos = getNodePos(); - - while (true) { + const nameOrConstraint = parseJSDocTypeExpression(/*mayOmitBraces*/ true); + skipWhitespace(); + let parseMore = true; + const isConstraint = token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.Identifier; + if (!isConstraint) { + const typeParameter = createNode(SyntaxKind.TypeParameter); + if (!ts.isIdentifier(nameOrConstraint.type)) { return Debug.fail(); } + typeParameter.name = nameOrConstraint.type; + finishNode(typeParameter); + typeParameters.push(typeParameter); + if (token() === SyntaxKind.CommaToken) { + // need to look for more type parameters + nextJSDocToken(); + skipWhitespace(); + } + else { + parseMore = false; + } + } + while (parseMore) { const typeParameter = createNode(SyntaxKind.TypeParameter); const name = parseJSDocIdentifierNameWithOptionalBraces(); skipWhitespace(); @@ -7044,6 +7063,11 @@ namespace ts { } } + if (isConstraint) { + Debug.assert(!!typeParameters.length); + typeParameters[0].constraint = nameOrConstraint.type; + } + const result = createNode(SyntaxKind.JSDocTemplateTag, atToken.pos); result.atToken = atToken; result.tagName = tagName; From e986333d6fd53e2d9f09fbd76ffbdfdf3be64aad Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 1 Jun 2018 10:03:21 -0700 Subject: [PATCH 02/13] Messy version is finished. Need to add a few tests --- src/compiler/parser.ts | 24 ++++++++++++------- ...Comments.parsesCorrectly.templateTag3.json | 2 +- ...Comments.parsesCorrectly.templateTag5.json | 2 +- ...Comments.parsesCorrectly.templateTag6.json | 2 +- .../reference/quickInfoJsDocTags.baseline | 6 ++--- tests/cases/fourslash/jsdocReturnsTag.ts | 2 +- tests/cases/fourslash/quickInfoJsDocTags.ts | 2 +- 7 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index c59829a673691..d2a8a3b0b6529 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -7019,16 +7019,24 @@ namespace ts { } // Type parameter list looks like '@template {Constraint} T,U,V' + let typeParameter = createNode(SyntaxKind.TypeParameter); + let typeParametersPos = getNodePos(); const typeParameters = []; - const typeParametersPos = getNodePos(); + const firstBrace = token() === SyntaxKind.OpenBraceToken; const nameOrConstraint = parseJSDocTypeExpression(/*mayOmitBraces*/ true); skipWhitespace(); let parseMore = true; - const isConstraint = token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.Identifier; - if (!isConstraint) { - const typeParameter = createNode(SyntaxKind.TypeParameter); - if (!ts.isIdentifier(nameOrConstraint.type)) { return Debug.fail(); } - typeParameter.name = nameOrConstraint.type; + // nameOrConstraint is a constraint if (1) it started with a brace and (2) the next token does too, or is an identifier + const hasConstraint = firstBrace && (token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.Identifier); + if (hasConstraint) { + typeParametersPos = getNodePos(); + } + else { + if (!isTypeReferenceNode(nameOrConstraint.type) && !ts.isIdentifier((nameOrConstraint.type as TypeReferenceNode).typeName)) { + parseErrorAtPosition(scanner.getStartPos(), 0, Diagnostics.Identifier_expected); + return undefined; + } + typeParameter.name = (nameOrConstraint.type as TypeReferenceNode).typeName as Identifier; finishNode(typeParameter); typeParameters.push(typeParameter); if (token() === SyntaxKind.CommaToken) { @@ -7041,7 +7049,7 @@ namespace ts { } } while (parseMore) { - const typeParameter = createNode(SyntaxKind.TypeParameter); + typeParameter = createNode(SyntaxKind.TypeParameter); const name = parseJSDocIdentifierNameWithOptionalBraces(); skipWhitespace(); if (!name) { @@ -7063,7 +7071,7 @@ namespace ts { } } - if (isConstraint) { + if (hasConstraint) { Debug.assert(!!typeParameters.length); typeParameters[0].constraint = nameOrConstraint.type; } diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag3.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag3.json index ab1b9db87822e..193c5c0eb0141 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag3.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag3.json @@ -22,7 +22,7 @@ "0": { "kind": "TypeParameter", "pos": 18, - "end": 20, + "end": 19, "name": { "kind": "Identifier", "pos": 18, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag5.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag5.json index 80e127b07592f..fca64bcb43044 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag5.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag5.json @@ -22,7 +22,7 @@ "0": { "kind": "TypeParameter", "pos": 18, - "end": 20, + "end": 19, "name": { "kind": "Identifier", "pos": 18, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag6.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag6.json index d097b42a0b0ca..5dd0043f36e30 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag6.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag6.json @@ -22,7 +22,7 @@ "0": { "kind": "TypeParameter", "pos": 18, - "end": 20, + "end": 19, "name": { "kind": "Identifier", "pos": 18, diff --git a/tests/baselines/reference/quickInfoJsDocTags.baseline b/tests/baselines/reference/quickInfoJsDocTags.baseline index 6019419e48945..d3ed4ad5ef1de 100644 --- a/tests/baselines/reference/quickInfoJsDocTags.baseline +++ b/tests/baselines/reference/quickInfoJsDocTags.baseline @@ -2,13 +2,13 @@ { "marker": { "fileName": "/tests/cases/fourslash/quickInfoJsDocTags.ts", - "position": 258 + "position": 256 }, "quickInfo": { "kind": "function", "kindModifiers": "", "textSpan": { - "start": 258, + "start": 256, "length": 3 }, "displayParts": [ @@ -78,7 +78,7 @@ }, { "name": "template", - "text": "{T} A template" + "text": "T A template" }, { "name": "type", diff --git a/tests/cases/fourslash/jsdocReturnsTag.ts b/tests/cases/fourslash/jsdocReturnsTag.ts index e91d350922278..176e0bd8bba79 100644 --- a/tests/cases/fourslash/jsdocReturnsTag.ts +++ b/tests/cases/fourslash/jsdocReturnsTag.ts @@ -18,7 +18,7 @@ verify.signatureHelp({ docComment: "Find an item", tags: [ // TODO: GH#24130 - { name: "template", text: "T\n " }, + { name: "template", text: "T" }, { name: "param", text: "l" }, { name: "param", text: "x" }, { name: "returns", text: "The names of the found item(s)." }, diff --git a/tests/cases/fourslash/quickInfoJsDocTags.ts b/tests/cases/fourslash/quickInfoJsDocTags.ts index ce045ab29400f..798a850a310fe 100644 --- a/tests/cases/fourslash/quickInfoJsDocTags.ts +++ b/tests/cases/fourslash/quickInfoJsDocTags.ts @@ -5,7 +5,7 @@ //// * Doc //// * @author Me //// * @augments {C} Augments it -//// * @template {T} A template +//// * @template T A template //// * @type {number | string} A type //// * @typedef {number | string} NumOrStr //// * @property {number} x The prop From e629fc9e5d0b1783d7ecb3c06162ce80c772dc91 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 1 Jun 2018 10:43:49 -0700 Subject: [PATCH 03/13] Refactor to be smaller --- src/compiler/parser.ts | 71 ++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 40 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index d2a8a3b0b6529..8e0a416786e8d 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -7013,62 +7013,32 @@ namespace ts { } function parseTemplateTag(atToken: AtToken, tagName: Identifier): JSDocTemplateTag | undefined { - if (some(tags, isJSDocTemplateTag)) { - // TODO: Get rid of this restriction after making sure it's ok elsewhere - parseErrorAt(tagName.pos, scanner.getTokenPos(), Diagnostics._0_tag_already_specified, tagName.escapedText); - } - // Type parameter list looks like '@template {Constraint} T,U,V' let typeParameter = createNode(SyntaxKind.TypeParameter); let typeParametersPos = getNodePos(); - const typeParameters = []; + const typeParameters: TypeParameterDeclaration[] = []; const firstBrace = token() === SyntaxKind.OpenBraceToken; const nameOrConstraint = parseJSDocTypeExpression(/*mayOmitBraces*/ true); skipWhitespace(); - let parseMore = true; + let parseAnotherConstraint: boolean | undefined = true; + // nameOrConstraint is a constraint if (1) it started with a brace and (2) the next token does too, or is an identifier const hasConstraint = firstBrace && (token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.Identifier); if (hasConstraint) { typeParametersPos = getNodePos(); } else { - if (!isTypeReferenceNode(nameOrConstraint.type) && !ts.isIdentifier((nameOrConstraint.type as TypeReferenceNode).typeName)) { - parseErrorAtPosition(scanner.getStartPos(), 0, Diagnostics.Identifier_expected); - return undefined; - } - typeParameter.name = (nameOrConstraint.type as TypeReferenceNode).typeName as Identifier; - finishNode(typeParameter); - typeParameters.push(typeParameter); - if (token() === SyntaxKind.CommaToken) { - // need to look for more type parameters - nextJSDocToken(); - skipWhitespace(); - } - else { - parseMore = false; - } + const name = isTypeReferenceNode(nameOrConstraint.type) && ts.isIdentifier(nameOrConstraint.type.typeName) && nameOrConstraint.type.typeName; + parseAnotherConstraint = finishTypeParameter(typeParameters, typeParameter, name); } - while (parseMore) { + while (parseAnotherConstraint) { typeParameter = createNode(SyntaxKind.TypeParameter); const name = parseJSDocIdentifierNameWithOptionalBraces(); skipWhitespace(); - if (!name) { - parseErrorAtPosition(scanner.getStartPos(), 0, Diagnostics.Identifier_expected); - return undefined; - } - - typeParameter.name = name; - finishNode(typeParameter); - - typeParameters.push(typeParameter); - - if (token() === SyntaxKind.CommaToken) { - nextJSDocToken(); - skipWhitespace(); - } - else { - break; - } + parseAnotherConstraint = finishTypeParameter(typeParameters, typeParameter, name); + } + if (parseAnotherConstraint === undefined) { + return undefined; } if (hasConstraint) { @@ -7084,6 +7054,27 @@ namespace ts { return result; } + function finishTypeParameter(typeParameters: TypeParameterDeclaration[], typeParameter: TypeParameterDeclaration, name: Identifier | false | undefined): boolean | undefined { + if (name) { + typeParameter.name = name as Identifier; + } + else { + parseErrorAtPosition(scanner.getStartPos(), 0, Diagnostics.Identifier_expected); + return undefined; + } + finishNode(typeParameter); + typeParameters.push(typeParameter); + if (token() === SyntaxKind.CommaToken) { + // need to look for more type parameters + nextJSDocToken(); + skipWhitespace(); + return true; + } + else { + return false; + } + } + function parseJSDocIdentifierNameWithOptionalBraces(): Identifier | undefined { const parsedBrace = parseOptional(SyntaxKind.OpenBraceToken); const res = parseJSDocIdentifierName(); From da9f937eced818d503e53d5dfb7f61787666176a Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 1 Jun 2018 13:11:01 -0700 Subject: [PATCH 04/13] Small refactor + Add one test --- src/compiler/utilities.ts | 10 ++- .../reference/jsdocTemplateTag3.errors.txt | 36 ++++++++ .../reference/jsdocTemplateTag3.symbols | 60 +++++++++++++ .../reference/jsdocTemplateTag3.types | 85 +++++++++++++++++++ .../conformance/jsdoc/jsdocTemplateTag3.ts | 25 ++++++ 5 files changed, 212 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/jsdocTemplateTag3.errors.txt create mode 100644 tests/baselines/reference/jsdocTemplateTag3.symbols create mode 100644 tests/baselines/reference/jsdocTemplateTag3.types create mode 100644 tests/cases/conformance/jsdoc/jsdocTemplateTag3.ts diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 8b4e12dc2220b..53973d70d816a 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3177,10 +3177,12 @@ namespace ts { } export function getJSDocTypeParameterDeclarations(node: DeclarationWithTypeParameters): ReadonlyArray { - // template tags are only available when a typedef isn't already using them - const tag = find(getJSDocTags(node), (tag): tag is JSDocTemplateTag => - isJSDocTemplateTag(tag) && !(tag.parent.kind === SyntaxKind.JSDocComment && tag.parent.tags!.some(isJSDocTypeAlias))); - return (tag && tag.typeParameters) || emptyArray; + return flatMap(filter(getJSDocTags(node), isNonTypeAliasTemplate), tag => tag.typeParameters); + } + + /** template tags are only available when a typedef isn't already using them */ + function isNonTypeAliasTemplate(tag: JSDocTag): tag is JSDocTemplateTag { + return isJSDocTemplateTag(tag) && !(tag.parent.kind === SyntaxKind.JSDocComment && tag.parent.tags!.some(isJSDocTypeAlias)); } /** diff --git a/tests/baselines/reference/jsdocTemplateTag3.errors.txt b/tests/baselines/reference/jsdocTemplateTag3.errors.txt new file mode 100644 index 0000000000000..894a0a0978250 --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateTag3.errors.txt @@ -0,0 +1,36 @@ +tests/cases/conformance/jsdoc/a.js(14,29): error TS2339: Property 'a' does not exist on type 'U'. +tests/cases/conformance/jsdoc/a.js(14,35): error TS2339: Property 'b' does not exist on type 'U'. +tests/cases/conformance/jsdoc/a.js(21,3): error TS2345: Argument of type '{ a: number; }' is not assignable to parameter of type '{ a: number; b: string; }'. + Property 'b' is missing in type '{ a: number; }'. + + +==== tests/cases/conformance/jsdoc/a.js (3 errors) ==== + /** + * @template {{ a: number, b: string }} T,U A Comment + * @template {{ c: boolean }} V uh ... are comments even supported?? + * @template {W} + * @template X That last one had no comment + * @param {T} t + * @param {U} u + * @param {V} v + * @param {W} w + * @param {X} x + * @return {W | X} + */ + function f(t, u, v, w, x) { + if(t.a + t.b.length > u.a - u.b.length && v.c) { + ~ +!!! error TS2339: Property 'a' does not exist on type 'U'. + ~ +!!! error TS2339: Property 'b' does not exist on type 'U'. + return w; + } + return x; + } + + f({ a: 12, b: 'hi', c: null }, undefined, { c: false, d: 12, b: undefined }, 101, 'nope'); + f({ a: 12 }, undefined, undefined, 101, 'nope'); + ~~~~~~~~~~ +!!! error TS2345: Argument of type '{ a: number; }' is not assignable to parameter of type '{ a: number; b: string; }'. +!!! error TS2345: Property 'b' is missing in type '{ a: number; }'. + \ No newline at end of file diff --git a/tests/baselines/reference/jsdocTemplateTag3.symbols b/tests/baselines/reference/jsdocTemplateTag3.symbols new file mode 100644 index 0000000000000..71464b0cb6fd0 --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateTag3.symbols @@ -0,0 +1,60 @@ +=== tests/cases/conformance/jsdoc/a.js === +/** + * @template {{ a: number, b: string }} T,U A Comment + * @template {{ c: boolean }} V uh ... are comments even supported?? + * @template {W} + * @template X That last one had no comment + * @param {T} t + * @param {U} u + * @param {V} v + * @param {W} w + * @param {X} x + * @return {W | X} + */ +function f(t, u, v, w, x) { +>f : Symbol(f, Decl(a.js, 0, 0)) +>t : Symbol(t, Decl(a.js, 12, 11)) +>u : Symbol(u, Decl(a.js, 12, 13)) +>v : Symbol(v, Decl(a.js, 12, 16)) +>w : Symbol(w, Decl(a.js, 12, 19)) +>x : Symbol(x, Decl(a.js, 12, 22)) + + if(t.a + t.b.length > u.a - u.b.length && v.c) { +>t.a : Symbol(a, Decl(a.js, 1, 15)) +>t : Symbol(t, Decl(a.js, 12, 11)) +>a : Symbol(a, Decl(a.js, 1, 15)) +>t.b.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>t.b : Symbol(b, Decl(a.js, 1, 26)) +>t : Symbol(t, Decl(a.js, 12, 11)) +>b : Symbol(b, Decl(a.js, 1, 26)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>u : Symbol(u, Decl(a.js, 12, 13)) +>u : Symbol(u, Decl(a.js, 12, 13)) +>v.c : Symbol(c, Decl(a.js, 2, 15)) +>v : Symbol(v, Decl(a.js, 12, 16)) +>c : Symbol(c, Decl(a.js, 2, 15)) + + return w; +>w : Symbol(w, Decl(a.js, 12, 19)) + } + return x; +>x : Symbol(x, Decl(a.js, 12, 22)) +} + +f({ a: 12, b: 'hi', c: null }, undefined, { c: false, d: 12, b: undefined }, 101, 'nope'); +>f : Symbol(f, Decl(a.js, 0, 0)) +>a : Symbol(a, Decl(a.js, 19, 3)) +>b : Symbol(b, Decl(a.js, 19, 10)) +>c : Symbol(c, Decl(a.js, 19, 19)) +>undefined : Symbol(undefined) +>c : Symbol(c, Decl(a.js, 19, 43)) +>d : Symbol(d, Decl(a.js, 19, 53)) +>b : Symbol(b, Decl(a.js, 19, 60)) +>undefined : Symbol(undefined) + +f({ a: 12 }, undefined, undefined, 101, 'nope'); +>f : Symbol(f, Decl(a.js, 0, 0)) +>a : Symbol(a, Decl(a.js, 20, 3)) +>undefined : Symbol(undefined) +>undefined : Symbol(undefined) + diff --git a/tests/baselines/reference/jsdocTemplateTag3.types b/tests/baselines/reference/jsdocTemplateTag3.types new file mode 100644 index 0000000000000..618c10b198e0e --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateTag3.types @@ -0,0 +1,85 @@ +=== tests/cases/conformance/jsdoc/a.js === +/** + * @template {{ a: number, b: string }} T,U A Comment + * @template {{ c: boolean }} V uh ... are comments even supported?? + * @template {W} + * @template X That last one had no comment + * @param {T} t + * @param {U} u + * @param {V} v + * @param {W} w + * @param {X} x + * @return {W | X} + */ +function f(t, u, v, w, x) { +>f : (t: T, u: U, v: V, w: W, x: X) => W | X +>t : T +>u : U +>v : V +>w : W +>x : X + + if(t.a + t.b.length > u.a - u.b.length && v.c) { +>t.a + t.b.length > u.a - u.b.length && v.c : boolean +>t.a + t.b.length > u.a - u.b.length : boolean +>t.a + t.b.length : number +>t.a : number +>t : T +>a : number +>t.b.length : number +>t.b : string +>t : T +>b : string +>length : number +>u.a - u.b.length : number +>u.a : any +>u : U +>a : any +>u.b.length : any +>u.b : any +>u : U +>b : any +>length : any +>v.c : boolean +>v : V +>c : boolean + + return w; +>w : W + } + return x; +>x : X +} + +f({ a: 12, b: 'hi', c: null }, undefined, { c: false, d: 12, b: undefined }, 101, 'nope'); +>f({ a: 12, b: 'hi', c: null }, undefined, { c: false, d: 12, b: undefined }, 101, 'nope') : string | number +>f : (t: T, u: U, v: V, w: W, x: X) => W | X +>{ a: 12, b: 'hi', c: null } : { a: number; b: string; c: null; } +>a : number +>12 : 12 +>b : string +>'hi' : "hi" +>c : null +>null : null +>undefined : undefined +>{ c: false, d: 12, b: undefined } : { c: false; d: number; b: undefined; } +>c : false +>false : false +>d : number +>12 : 12 +>b : undefined +>undefined : undefined +>101 : 101 +>'nope' : "nope" + +f({ a: 12 }, undefined, undefined, 101, 'nope'); +>f({ a: 12 }, undefined, undefined, 101, 'nope') : any +>f : (t: T, u: U, v: V, w: W, x: X) => W | X +>{ a: 12 } : { a: number; } +>a : number +>12 : 12 +>undefined : undefined +>undefined : undefined +>101 : 101 +>'nope' : "nope" + diff --git a/tests/cases/conformance/jsdoc/jsdocTemplateTag3.ts b/tests/cases/conformance/jsdoc/jsdocTemplateTag3.ts new file mode 100644 index 0000000000000..3ba37a969ac76 --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocTemplateTag3.ts @@ -0,0 +1,25 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @Filename: a.js +/** + * @template {{ a: number, b: string }} T,U A Comment + * @template {{ c: boolean }} V uh ... are comments even supported?? + * @template {W} + * @template X That last one had no comment + * @param {T} t + * @param {U} u + * @param {V} v + * @param {W} w + * @param {X} x + * @return {W | X} + */ +function f(t, u, v, w, x) { + if(t.a + t.b.length > u.a - u.b.length && v.c) { + return w; + } + return x; +} + +f({ a: 12, b: 'hi', c: null }, undefined, { c: false, d: 12, b: undefined }, 101, 'nope'); +f({ a: 12 }, undefined, undefined, 101, 'nope'); From 24fdef6a1d5720352b19362a977f081d8a0f3563 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 1 Jun 2018 13:48:33 -0700 Subject: [PATCH 05/13] Another test --- .../typedefMultipleTypeParameters.errors.txt | 32 +++++++++++++++++++ .../typedefMultipleTypeParameters.symbols | 30 +++++++++++++++++ .../typedefMultipleTypeParameters.types | 30 +++++++++++++++++ .../jsdoc/typedefMultipleTypeParameters.ts | 24 ++++++++++++++ 4 files changed, 116 insertions(+) create mode 100644 tests/baselines/reference/typedefMultipleTypeParameters.errors.txt create mode 100644 tests/baselines/reference/typedefMultipleTypeParameters.symbols create mode 100644 tests/baselines/reference/typedefMultipleTypeParameters.types create mode 100644 tests/cases/conformance/jsdoc/typedefMultipleTypeParameters.ts diff --git a/tests/baselines/reference/typedefMultipleTypeParameters.errors.txt b/tests/baselines/reference/typedefMultipleTypeParameters.errors.txt new file mode 100644 index 0000000000000..fbed490c29b5b --- /dev/null +++ b/tests/baselines/reference/typedefMultipleTypeParameters.errors.txt @@ -0,0 +1,32 @@ +tests/cases/conformance/jsdoc/a.js(16,12): error TS2314: Generic type 'Everything' requires 5 type argument(s). +tests/cases/conformance/jsdoc/test.ts(1,34): error TS2344: Type '{ a: number; }' does not satisfy the constraint '{ a: number; b: string; }'. + Property 'b' is missing in type '{ a: number; }'. + + +==== tests/cases/conformance/jsdoc/a.js (1 errors) ==== + /** + * @template {{ a: number, b: string }} T,U A Comment + * @template {{ c: boolean }} V uh ... are comments even supported?? + * @template {W} + * @template X That last one had no comment + * @typedef {{ t: T, u: U, v: V, w: W, x: X }} Everything + */ + + /** @type {Everything<{ a: number, b: 'hi', c: never }, undefined, { c: true, d: 1 }, number, string>} */ + var tuvwx; + + // TODO: will error when #24592 is fixed + /** @type {Everything<{ a: number }, undefined, { c: 1, d: 1 }, number, string>} */ + var wrong; + + /** @type {Everything<{ a: number }>} */ + ~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2314: Generic type 'Everything' requires 5 type argument(s). + var insufficient; + +==== tests/cases/conformance/jsdoc/test.ts (1 errors) ==== + declare var actually: Everything<{ a: number }, undefined, { c: 1, d: 1 }, number, string>; + ~~~~~~~~~~~~~~ +!!! error TS2344: Type '{ a: number; }' does not satisfy the constraint '{ a: number; b: string; }'. +!!! error TS2344: Property 'b' is missing in type '{ a: number; }'. + \ No newline at end of file diff --git a/tests/baselines/reference/typedefMultipleTypeParameters.symbols b/tests/baselines/reference/typedefMultipleTypeParameters.symbols new file mode 100644 index 0000000000000..9ab04dcf8e192 --- /dev/null +++ b/tests/baselines/reference/typedefMultipleTypeParameters.symbols @@ -0,0 +1,30 @@ +=== tests/cases/conformance/jsdoc/a.js === +/** + * @template {{ a: number, b: string }} T,U A Comment + * @template {{ c: boolean }} V uh ... are comments even supported?? + * @template {W} + * @template X That last one had no comment + * @typedef {{ t: T, u: U, v: V, w: W, x: X }} Everything + */ + +/** @type {Everything<{ a: number, b: 'hi', c: never }, undefined, { c: true, d: 1 }, number, string>} */ +var tuvwx; +>tuvwx : Symbol(tuvwx, Decl(a.js, 9, 3)) + +// TODO: will error when #24592 is fixed +/** @type {Everything<{ a: number }, undefined, { c: 1, d: 1 }, number, string>} */ +var wrong; +>wrong : Symbol(wrong, Decl(a.js, 13, 3)) + +/** @type {Everything<{ a: number }>} */ +var insufficient; +>insufficient : Symbol(insufficient, Decl(a.js, 16, 3)) + +=== tests/cases/conformance/jsdoc/test.ts === +declare var actually: Everything<{ a: number }, undefined, { c: 1, d: 1 }, number, string>; +>actually : Symbol(actually, Decl(test.ts, 0, 11)) +>Everything : Symbol(Everything, Decl(a.js, 5, 3)) +>a : Symbol(a, Decl(test.ts, 0, 34)) +>c : Symbol(c, Decl(test.ts, 0, 61)) +>d : Symbol(d, Decl(test.ts, 0, 67)) + diff --git a/tests/baselines/reference/typedefMultipleTypeParameters.types b/tests/baselines/reference/typedefMultipleTypeParameters.types new file mode 100644 index 0000000000000..53ed2203ef160 --- /dev/null +++ b/tests/baselines/reference/typedefMultipleTypeParameters.types @@ -0,0 +1,30 @@ +=== tests/cases/conformance/jsdoc/a.js === +/** + * @template {{ a: number, b: string }} T,U A Comment + * @template {{ c: boolean }} V uh ... are comments even supported?? + * @template {W} + * @template X That last one had no comment + * @typedef {{ t: T, u: U, v: V, w: W, x: X }} Everything + */ + +/** @type {Everything<{ a: number, b: 'hi', c: never }, undefined, { c: true, d: 1 }, number, string>} */ +var tuvwx; +>tuvwx : { t: { a: number; b: "hi"; c: never; }; u: undefined; v: { c: true; d: 1; }; w: number; x: string; } + +// TODO: will error when #24592 is fixed +/** @type {Everything<{ a: number }, undefined, { c: 1, d: 1 }, number, string>} */ +var wrong; +>wrong : { t: { a: number; }; u: undefined; v: { c: 1; d: 1; }; w: number; x: string; } + +/** @type {Everything<{ a: number }>} */ +var insufficient; +>insufficient : any + +=== tests/cases/conformance/jsdoc/test.ts === +declare var actually: Everything<{ a: number }, undefined, { c: 1, d: 1 }, number, string>; +>actually : { t: { a: number; }; u: undefined; v: { c: 1; d: 1; }; w: number; x: string; } +>Everything : { t: T; u: U; v: V; w: W; x: X; } +>a : number +>c : 1 +>d : 1 + diff --git a/tests/cases/conformance/jsdoc/typedefMultipleTypeParameters.ts b/tests/cases/conformance/jsdoc/typedefMultipleTypeParameters.ts new file mode 100644 index 0000000000000..117ac3d673c80 --- /dev/null +++ b/tests/cases/conformance/jsdoc/typedefMultipleTypeParameters.ts @@ -0,0 +1,24 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @Filename: a.js +/** + * @template {{ a: number, b: string }} T,U A Comment + * @template {{ c: boolean }} V uh ... are comments even supported?? + * @template {W} + * @template X That last one had no comment + * @typedef {{ t: T, u: U, v: V, w: W, x: X }} Everything + */ + +/** @type {Everything<{ a: number, b: 'hi', c: never }, undefined, { c: true, d: 1 }, number, string>} */ +var tuvwx; + +// TODO: will error when #24592 is fixed +/** @type {Everything<{ a: number }, undefined, { c: 1, d: 1 }, number, string>} */ +var wrong; + +/** @type {Everything<{ a: number }>} */ +var insufficient; + +// @Filename: test.ts +declare var actually: Everything<{ a: number }, undefined, { c: 1, d: 1 }, number, string>; From efd8a4f6eec1c1455d84e2e8567e035ba54d8b0c Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 1 Jun 2018 14:15:29 -0700 Subject: [PATCH 06/13] Minor cleanup --- src/compiler/parser.ts | 2 +- tests/cases/fourslash/jsdocReturnsTag.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 8e0a416786e8d..d7c4bc6663685 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -7013,7 +7013,7 @@ namespace ts { } function parseTemplateTag(atToken: AtToken, tagName: Identifier): JSDocTemplateTag | undefined { - // Type parameter list looks like '@template {Constraint} T,U,V' + // the template tag looks like '@template {Constraint} T,U,V' let typeParameter = createNode(SyntaxKind.TypeParameter); let typeParametersPos = getNodePos(); const typeParameters: TypeParameterDeclaration[] = []; diff --git a/tests/cases/fourslash/jsdocReturnsTag.ts b/tests/cases/fourslash/jsdocReturnsTag.ts index 176e0bd8bba79..58edfdbff9151 100644 --- a/tests/cases/fourslash/jsdocReturnsTag.ts +++ b/tests/cases/fourslash/jsdocReturnsTag.ts @@ -17,7 +17,6 @@ verify.signatureHelp({ text: "find(l: T[], x: T): T", docComment: "Find an item", tags: [ - // TODO: GH#24130 { name: "template", text: "T" }, { name: "param", text: "l" }, { name: "param", text: "x" }, From d499fde8ac911888602723338a980ad428206d67 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 1 Jun 2018 15:02:59 -0700 Subject: [PATCH 07/13] Fix error reporting on type parameters on ctors --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0f4db171ab0fb..377f3c27efd95 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -28417,8 +28417,8 @@ namespace ts { function checkGrammarConstructorTypeParameters(node: ConstructorDeclaration) { const typeParameters = getEffectiveTypeParameterDeclarations(node); - if (isNodeArray(typeParameters)) { - const { pos, end } = typeParameters; + if (isNodeArray(typeParameters) || typeParameters.length) { + const { pos, end } = isNodeArray(typeParameters) ? typeParameters : typeParameters[0]; return grammarErrorAtPos(node, pos, end - pos, Diagnostics.Type_parameters_cannot_appear_on_a_constructor_declaration); } } From 781bc65958e37219c4ae6e87537a66d96ca870e8 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 1 Jun 2018 16:03:37 -0700 Subject: [PATCH 08/13] Simplify syntax of `@template` tag This is a breaking change, but in my sample, nobody except webpack used the erroneous syntax. I need to improve the error message, so jsdocTemplateTag3 currently fails to remind me of that. --- src/compiler/parser.ts | 82 ++++++------------- ...Comments.parsesCorrectly.templateTag3.json | 2 +- ...Comments.parsesCorrectly.templateTag5.json | 2 +- ...Comments.parsesCorrectly.templateTag6.json | 2 +- .../reference/jsdocTemplateClass.errors.txt | 2 +- .../reference/jsdocTemplateClass.symbols | 2 +- .../reference/jsdocTemplateClass.types | 2 +- ...sdocTemplateConstructorFunction.errors.txt | 4 +- .../jsdocTemplateConstructorFunction.symbols | 4 +- .../jsdocTemplateConstructorFunction.types | 4 +- ...docTemplateConstructorFunction2.errors.txt | 2 +- .../jsdocTemplateConstructorFunction2.symbols | 2 +- .../jsdocTemplateConstructorFunction2.types | 2 +- .../reference/quickInfoJsDocTags.baseline | 2 +- .../typedefMultipleTypeParameters.errors.txt | 2 +- .../typedefMultipleTypeParameters.symbols | 2 +- .../typedefMultipleTypeParameters.types | 2 +- .../conformance/jsdoc/jsdocTemplateClass.ts | 2 +- .../jsdoc/jsdocTemplateConstructorFunction.ts | 4 +- .../jsdocTemplateConstructorFunction2.ts | 2 +- .../conformance/jsdoc/jsdocTemplateTag3.ts | 10 ++- .../jsdoc/typedefMultipleTypeParameters.ts | 2 +- 22 files changed, 60 insertions(+), 80 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index d7c4bc6663685..d07bc81b99849 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -7014,36 +7014,38 @@ namespace ts { function parseTemplateTag(atToken: AtToken, tagName: Identifier): JSDocTemplateTag | undefined { // the template tag looks like '@template {Constraint} T,U,V' - let typeParameter = createNode(SyntaxKind.TypeParameter); - let typeParametersPos = getNodePos(); - const typeParameters: TypeParameterDeclaration[] = []; - const firstBrace = token() === SyntaxKind.OpenBraceToken; - const nameOrConstraint = parseJSDocTypeExpression(/*mayOmitBraces*/ true); - skipWhitespace(); - let parseAnotherConstraint: boolean | undefined = true; - - // nameOrConstraint is a constraint if (1) it started with a brace and (2) the next token does too, or is an identifier - const hasConstraint = firstBrace && (token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.Identifier); - if (hasConstraint) { - typeParametersPos = getNodePos(); - } - else { - const name = isTypeReferenceNode(nameOrConstraint.type) && ts.isIdentifier(nameOrConstraint.type.typeName) && nameOrConstraint.type.typeName; - parseAnotherConstraint = finishTypeParameter(typeParameters, typeParameter, name); - } - while (parseAnotherConstraint) { - typeParameter = createNode(SyntaxKind.TypeParameter); - const name = parseJSDocIdentifierNameWithOptionalBraces(); + let constraint: JSDocTypeExpression | undefined; + if (token() === SyntaxKind.OpenBraceToken) { + constraint = parseJSDocTypeExpression(); skipWhitespace(); - parseAnotherConstraint = finishTypeParameter(typeParameters, typeParameter, name); } - if (parseAnotherConstraint === undefined) { - return undefined; + + const typeParameters = []; + const typeParametersPos = getNodePos(); + while (true) { + const typeParameter = createNode(SyntaxKind.TypeParameter); + const name = parseJSDocIdentifierName(); + skipWhitespace(); + if (!name) { + parseErrorAtPosition(scanner.getStartPos(), 0, Diagnostics.Identifier_expected); + return undefined; + } + typeParameter.name = name; + finishNode(typeParameter); + typeParameters.push(typeParameter); + if (token() === SyntaxKind.CommaToken) { + // need to look for more type parameters + nextJSDocToken(); + skipWhitespace(); + } + else { + break; + } } - if (hasConstraint) { + if (constraint) { Debug.assert(!!typeParameters.length); - typeParameters[0].constraint = nameOrConstraint.type; + typeParameters[0].constraint = constraint.type; } const result = createNode(SyntaxKind.JSDocTemplateTag, atToken.pos); @@ -7054,36 +7056,6 @@ namespace ts { return result; } - function finishTypeParameter(typeParameters: TypeParameterDeclaration[], typeParameter: TypeParameterDeclaration, name: Identifier | false | undefined): boolean | undefined { - if (name) { - typeParameter.name = name as Identifier; - } - else { - parseErrorAtPosition(scanner.getStartPos(), 0, Diagnostics.Identifier_expected); - return undefined; - } - finishNode(typeParameter); - typeParameters.push(typeParameter); - if (token() === SyntaxKind.CommaToken) { - // need to look for more type parameters - nextJSDocToken(); - skipWhitespace(); - return true; - } - else { - return false; - } - } - - function parseJSDocIdentifierNameWithOptionalBraces(): Identifier | undefined { - const parsedBrace = parseOptional(SyntaxKind.OpenBraceToken); - const res = parseJSDocIdentifierName(); - if (parsedBrace) { - parseExpected(SyntaxKind.CloseBraceToken); - } - return res; - } - function nextJSDocToken(): JsDocSyntaxKind { return currentToken = scanner.scanJSDocToken(); } diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag3.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag3.json index 193c5c0eb0141..ab1b9db87822e 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag3.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag3.json @@ -22,7 +22,7 @@ "0": { "kind": "TypeParameter", "pos": 18, - "end": 19, + "end": 20, "name": { "kind": "Identifier", "pos": 18, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag5.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag5.json index fca64bcb43044..80e127b07592f 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag5.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag5.json @@ -22,7 +22,7 @@ "0": { "kind": "TypeParameter", "pos": 18, - "end": 19, + "end": 20, "name": { "kind": "Identifier", "pos": 18, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag6.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag6.json index 5dd0043f36e30..d097b42a0b0ca 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag6.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag6.json @@ -22,7 +22,7 @@ "0": { "kind": "TypeParameter", "pos": 18, - "end": 19, + "end": 20, "name": { "kind": "Identifier", "pos": 18, diff --git a/tests/baselines/reference/jsdocTemplateClass.errors.txt b/tests/baselines/reference/jsdocTemplateClass.errors.txt index 402992c34bdc9..372fee6fd5a81 100644 --- a/tests/baselines/reference/jsdocTemplateClass.errors.txt +++ b/tests/baselines/reference/jsdocTemplateClass.errors.txt @@ -3,7 +3,7 @@ tests/cases/conformance/jsdoc/templateTagOnClasses.js(25,1): error TS2322: Type ==== tests/cases/conformance/jsdoc/templateTagOnClasses.js (1 errors) ==== /** - * @template {T} + * @template T * @typedef {(t: T) => T} Id */ /** @template T */ diff --git a/tests/baselines/reference/jsdocTemplateClass.symbols b/tests/baselines/reference/jsdocTemplateClass.symbols index b82cd8038e50b..a2da2f051cd3e 100644 --- a/tests/baselines/reference/jsdocTemplateClass.symbols +++ b/tests/baselines/reference/jsdocTemplateClass.symbols @@ -1,6 +1,6 @@ === tests/cases/conformance/jsdoc/templateTagOnClasses.js === /** - * @template {T} + * @template T * @typedef {(t: T) => T} Id */ /** @template T */ diff --git a/tests/baselines/reference/jsdocTemplateClass.types b/tests/baselines/reference/jsdocTemplateClass.types index 9c028a96ff170..40b73bc700266 100644 --- a/tests/baselines/reference/jsdocTemplateClass.types +++ b/tests/baselines/reference/jsdocTemplateClass.types @@ -1,6 +1,6 @@ === tests/cases/conformance/jsdoc/templateTagOnClasses.js === /** - * @template {T} + * @template T * @typedef {(t: T) => T} Id */ /** @template T */ diff --git a/tests/baselines/reference/jsdocTemplateConstructorFunction.errors.txt b/tests/baselines/reference/jsdocTemplateConstructorFunction.errors.txt index 9e735b780a0fc..1153424c8db61 100644 --- a/tests/baselines/reference/jsdocTemplateConstructorFunction.errors.txt +++ b/tests/baselines/reference/jsdocTemplateConstructorFunction.errors.txt @@ -3,12 +3,12 @@ tests/cases/conformance/jsdoc/templateTagOnConstructorFunctions.js(24,1): error ==== tests/cases/conformance/jsdoc/templateTagOnConstructorFunctions.js (1 errors) ==== /** - * @template {U} + * @template U * @typedef {(u: U) => U} Id */ /** * @param {T} t - * @template {T} + * @template T */ function Zet(t) { /** @type {T} */ diff --git a/tests/baselines/reference/jsdocTemplateConstructorFunction.symbols b/tests/baselines/reference/jsdocTemplateConstructorFunction.symbols index b640d3c1e21e4..86b80ca08b620 100644 --- a/tests/baselines/reference/jsdocTemplateConstructorFunction.symbols +++ b/tests/baselines/reference/jsdocTemplateConstructorFunction.symbols @@ -1,11 +1,11 @@ === tests/cases/conformance/jsdoc/templateTagOnConstructorFunctions.js === /** - * @template {U} + * @template U * @typedef {(u: U) => U} Id */ /** * @param {T} t - * @template {T} + * @template T */ function Zet(t) { >Zet : Symbol(Zet, Decl(templateTagOnConstructorFunctions.js, 0, 0)) diff --git a/tests/baselines/reference/jsdocTemplateConstructorFunction.types b/tests/baselines/reference/jsdocTemplateConstructorFunction.types index 121cf846a0e39..4c0237aedebe6 100644 --- a/tests/baselines/reference/jsdocTemplateConstructorFunction.types +++ b/tests/baselines/reference/jsdocTemplateConstructorFunction.types @@ -1,11 +1,11 @@ === tests/cases/conformance/jsdoc/templateTagOnConstructorFunctions.js === /** - * @template {U} + * @template U * @typedef {(u: U) => U} Id */ /** * @param {T} t - * @template {T} + * @template T */ function Zet(t) { >Zet : typeof Zet diff --git a/tests/baselines/reference/jsdocTemplateConstructorFunction2.errors.txt b/tests/baselines/reference/jsdocTemplateConstructorFunction2.errors.txt index cdcc9a056ebab..d7222f11ee28a 100644 --- a/tests/baselines/reference/jsdocTemplateConstructorFunction2.errors.txt +++ b/tests/baselines/reference/jsdocTemplateConstructorFunction2.errors.txt @@ -5,7 +5,7 @@ tests/cases/conformance/jsdoc/templateTagWithNestedTypeLiteral.js(26,15): error ==== tests/cases/conformance/jsdoc/templateTagWithNestedTypeLiteral.js (2 errors) ==== /** * @param {T} t - * @template {T} + * @template T */ function Zet(t) { /** @type {T} */ diff --git a/tests/baselines/reference/jsdocTemplateConstructorFunction2.symbols b/tests/baselines/reference/jsdocTemplateConstructorFunction2.symbols index 32573a373985b..ebc2e5eba58a5 100644 --- a/tests/baselines/reference/jsdocTemplateConstructorFunction2.symbols +++ b/tests/baselines/reference/jsdocTemplateConstructorFunction2.symbols @@ -1,7 +1,7 @@ === tests/cases/conformance/jsdoc/templateTagWithNestedTypeLiteral.js === /** * @param {T} t - * @template {T} + * @template T */ function Zet(t) { >Zet : Symbol(Zet, Decl(templateTagWithNestedTypeLiteral.js, 0, 0)) diff --git a/tests/baselines/reference/jsdocTemplateConstructorFunction2.types b/tests/baselines/reference/jsdocTemplateConstructorFunction2.types index 14407a5a0c9d4..91c820ce0947e 100644 --- a/tests/baselines/reference/jsdocTemplateConstructorFunction2.types +++ b/tests/baselines/reference/jsdocTemplateConstructorFunction2.types @@ -1,7 +1,7 @@ === tests/cases/conformance/jsdoc/templateTagWithNestedTypeLiteral.js === /** * @param {T} t - * @template {T} + * @template T */ function Zet(t) { >Zet : typeof Zet diff --git a/tests/baselines/reference/quickInfoJsDocTags.baseline b/tests/baselines/reference/quickInfoJsDocTags.baseline index d3ed4ad5ef1de..b022204dd69a2 100644 --- a/tests/baselines/reference/quickInfoJsDocTags.baseline +++ b/tests/baselines/reference/quickInfoJsDocTags.baseline @@ -78,7 +78,7 @@ }, { "name": "template", - "text": "T A template" + "text": "T A template" }, { "name": "type", diff --git a/tests/baselines/reference/typedefMultipleTypeParameters.errors.txt b/tests/baselines/reference/typedefMultipleTypeParameters.errors.txt index fbed490c29b5b..e8d0e52ba0f7f 100644 --- a/tests/baselines/reference/typedefMultipleTypeParameters.errors.txt +++ b/tests/baselines/reference/typedefMultipleTypeParameters.errors.txt @@ -7,7 +7,7 @@ tests/cases/conformance/jsdoc/test.ts(1,34): error TS2344: Type '{ a: number; }' /** * @template {{ a: number, b: string }} T,U A Comment * @template {{ c: boolean }} V uh ... are comments even supported?? - * @template {W} + * @template W * @template X That last one had no comment * @typedef {{ t: T, u: U, v: V, w: W, x: X }} Everything */ diff --git a/tests/baselines/reference/typedefMultipleTypeParameters.symbols b/tests/baselines/reference/typedefMultipleTypeParameters.symbols index 9ab04dcf8e192..9acd30fea1be4 100644 --- a/tests/baselines/reference/typedefMultipleTypeParameters.symbols +++ b/tests/baselines/reference/typedefMultipleTypeParameters.symbols @@ -2,7 +2,7 @@ /** * @template {{ a: number, b: string }} T,U A Comment * @template {{ c: boolean }} V uh ... are comments even supported?? - * @template {W} + * @template W * @template X That last one had no comment * @typedef {{ t: T, u: U, v: V, w: W, x: X }} Everything */ diff --git a/tests/baselines/reference/typedefMultipleTypeParameters.types b/tests/baselines/reference/typedefMultipleTypeParameters.types index 53ed2203ef160..5bc38bb835bf8 100644 --- a/tests/baselines/reference/typedefMultipleTypeParameters.types +++ b/tests/baselines/reference/typedefMultipleTypeParameters.types @@ -2,7 +2,7 @@ /** * @template {{ a: number, b: string }} T,U A Comment * @template {{ c: boolean }} V uh ... are comments even supported?? - * @template {W} + * @template W * @template X That last one had no comment * @typedef {{ t: T, u: U, v: V, w: W, x: X }} Everything */ diff --git a/tests/cases/conformance/jsdoc/jsdocTemplateClass.ts b/tests/cases/conformance/jsdoc/jsdocTemplateClass.ts index b13303d6def4f..08297262ccade 100644 --- a/tests/cases/conformance/jsdoc/jsdocTemplateClass.ts +++ b/tests/cases/conformance/jsdoc/jsdocTemplateClass.ts @@ -4,7 +4,7 @@ // @Filename: templateTagOnClasses.js /** - * @template {T} + * @template T * @typedef {(t: T) => T} Id */ /** @template T */ diff --git a/tests/cases/conformance/jsdoc/jsdocTemplateConstructorFunction.ts b/tests/cases/conformance/jsdoc/jsdocTemplateConstructorFunction.ts index a2a7a48c1cb7c..016607490a8f9 100644 --- a/tests/cases/conformance/jsdoc/jsdocTemplateConstructorFunction.ts +++ b/tests/cases/conformance/jsdoc/jsdocTemplateConstructorFunction.ts @@ -4,12 +4,12 @@ // @Filename: templateTagOnConstructorFunctions.js /** - * @template {U} + * @template U * @typedef {(u: U) => U} Id */ /** * @param {T} t - * @template {T} + * @template T */ function Zet(t) { /** @type {T} */ diff --git a/tests/cases/conformance/jsdoc/jsdocTemplateConstructorFunction2.ts b/tests/cases/conformance/jsdoc/jsdocTemplateConstructorFunction2.ts index c736962de3974..d0418661bb712 100644 --- a/tests/cases/conformance/jsdoc/jsdocTemplateConstructorFunction2.ts +++ b/tests/cases/conformance/jsdoc/jsdocTemplateConstructorFunction2.ts @@ -5,7 +5,7 @@ /** * @param {T} t - * @template {T} + * @template T */ function Zet(t) { /** @type {T} */ diff --git a/tests/cases/conformance/jsdoc/jsdocTemplateTag3.ts b/tests/cases/conformance/jsdoc/jsdocTemplateTag3.ts index 3ba37a969ac76..e05438d304de7 100644 --- a/tests/cases/conformance/jsdoc/jsdocTemplateTag3.ts +++ b/tests/cases/conformance/jsdoc/jsdocTemplateTag3.ts @@ -5,7 +5,7 @@ /** * @template {{ a: number, b: string }} T,U A Comment * @template {{ c: boolean }} V uh ... are comments even supported?? - * @template {W} + * @template W * @template X That last one had no comment * @param {T} t * @param {U} u @@ -23,3 +23,11 @@ function f(t, u, v, w, x) { f({ a: 12, b: 'hi', c: null }, undefined, { c: false, d: 12, b: undefined }, 101, 'nope'); f({ a: 12 }, undefined, undefined, 101, 'nope'); + +/** + * @template {NoLongerAllowed} + * @template T preceding line's syntax is no longer allowed + * @param {T} x + */ +function g(x) { } + diff --git a/tests/cases/conformance/jsdoc/typedefMultipleTypeParameters.ts b/tests/cases/conformance/jsdoc/typedefMultipleTypeParameters.ts index 117ac3d673c80..9fc7c0d0d4175 100644 --- a/tests/cases/conformance/jsdoc/typedefMultipleTypeParameters.ts +++ b/tests/cases/conformance/jsdoc/typedefMultipleTypeParameters.ts @@ -5,7 +5,7 @@ /** * @template {{ a: number, b: string }} T,U A Comment * @template {{ c: boolean }} V uh ... are comments even supported?? - * @template {W} + * @template W * @template X That last one had no comment * @typedef {{ t: T, u: U, v: V, w: W, x: X }} Everything */ From e5e9e4347f2aeda561f33076f7023aeaac2427fd Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Sat, 2 Jun 2018 09:06:21 -0700 Subject: [PATCH 09/13] Better error message for template tag --- src/compiler/diagnosticMessages.json | 4 ++++ src/compiler/parser.ts | 9 ++++----- .../reference/jsdocTemplateTag3.errors.txt | 15 +++++++++++++-- .../baselines/reference/jsdocTemplateTag3.symbols | 12 +++++++++++- tests/baselines/reference/jsdocTemplateTag3.types | 12 +++++++++++- 5 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 259c1ee4ea5f4..79d91f7637941 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -219,6 +219,10 @@ "category": "Error", "code": 1068 }, + "Unexpected token. A type parameter name was expected without curly braces.": { + "category": "Error", + "code": 1069 + }, "'{0}' modifier cannot appear on a type member.": { "category": "Error", "code": 1070 diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index d07bc81b99849..1a8c01853257d 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -7024,13 +7024,12 @@ namespace ts { const typeParametersPos = getNodePos(); while (true) { const typeParameter = createNode(SyntaxKind.TypeParameter); - const name = parseJSDocIdentifierName(); - skipWhitespace(); - if (!name) { - parseErrorAtPosition(scanner.getStartPos(), 0, Diagnostics.Identifier_expected); + if (!tokenIsIdentifierOrKeyword(token())) { + parseErrorAtCurrentToken(Diagnostics.Unexpected_token_A_type_parameter_name_was_expected_without_curly_braces); return undefined; } - typeParameter.name = name; + typeParameter.name = parseJSDocIdentifierName()!; + skipWhitespace(); finishNode(typeParameter); typeParameters.push(typeParameter); if (token() === SyntaxKind.CommaToken) { diff --git a/tests/baselines/reference/jsdocTemplateTag3.errors.txt b/tests/baselines/reference/jsdocTemplateTag3.errors.txt index 894a0a0978250..a85024e1499d1 100644 --- a/tests/baselines/reference/jsdocTemplateTag3.errors.txt +++ b/tests/baselines/reference/jsdocTemplateTag3.errors.txt @@ -2,13 +2,14 @@ tests/cases/conformance/jsdoc/a.js(14,29): error TS2339: Property 'a' does not e tests/cases/conformance/jsdoc/a.js(14,35): error TS2339: Property 'b' does not exist on type 'U'. tests/cases/conformance/jsdoc/a.js(21,3): error TS2345: Argument of type '{ a: number; }' is not assignable to parameter of type '{ a: number; b: string; }'. Property 'b' is missing in type '{ a: number; }'. +tests/cases/conformance/jsdoc/a.js(25,2): error TS1069: Unexpected token. A type parameter name was expected without curly braces. -==== tests/cases/conformance/jsdoc/a.js (3 errors) ==== +==== tests/cases/conformance/jsdoc/a.js (4 errors) ==== /** * @template {{ a: number, b: string }} T,U A Comment * @template {{ c: boolean }} V uh ... are comments even supported?? - * @template {W} + * @template W * @template X That last one had no comment * @param {T} t * @param {U} u @@ -33,4 +34,14 @@ tests/cases/conformance/jsdoc/a.js(21,3): error TS2345: Argument of type '{ a: n ~~~~~~~~~~ !!! error TS2345: Argument of type '{ a: number; }' is not assignable to parameter of type '{ a: number; b: string; }'. !!! error TS2345: Property 'b' is missing in type '{ a: number; }'. + + /** + * @template {NoLongerAllowed} + * @template T preceding line's syntax is no longer allowed + ~ +!!! error TS1069: Unexpected token. A type parameter name was expected without curly braces. + * @param {T} x + */ + function g(x) { } + \ No newline at end of file diff --git a/tests/baselines/reference/jsdocTemplateTag3.symbols b/tests/baselines/reference/jsdocTemplateTag3.symbols index 71464b0cb6fd0..f9257d40e6fc8 100644 --- a/tests/baselines/reference/jsdocTemplateTag3.symbols +++ b/tests/baselines/reference/jsdocTemplateTag3.symbols @@ -2,7 +2,7 @@ /** * @template {{ a: number, b: string }} T,U A Comment * @template {{ c: boolean }} V uh ... are comments even supported?? - * @template {W} + * @template W * @template X That last one had no comment * @param {T} t * @param {U} u @@ -58,3 +58,13 @@ f({ a: 12 }, undefined, undefined, 101, 'nope'); >undefined : Symbol(undefined) >undefined : Symbol(undefined) +/** + * @template {NoLongerAllowed} + * @template T preceding line's syntax is no longer allowed + * @param {T} x + */ +function g(x) { } +>g : Symbol(g, Decl(a.js, 20, 49)) +>x : Symbol(x, Decl(a.js, 27, 11)) + + diff --git a/tests/baselines/reference/jsdocTemplateTag3.types b/tests/baselines/reference/jsdocTemplateTag3.types index 618c10b198e0e..63e8498d84aac 100644 --- a/tests/baselines/reference/jsdocTemplateTag3.types +++ b/tests/baselines/reference/jsdocTemplateTag3.types @@ -2,7 +2,7 @@ /** * @template {{ a: number, b: string }} T,U A Comment * @template {{ c: boolean }} V uh ... are comments even supported?? - * @template {W} + * @template W * @template X That last one had no comment * @param {T} t * @param {U} u @@ -83,3 +83,13 @@ f({ a: 12 }, undefined, undefined, 101, 'nope'); >101 : 101 >'nope' : "nope" +/** + * @template {NoLongerAllowed} + * @template T preceding line's syntax is no longer allowed + * @param {T} x + */ +function g(x) { } +>g : (x: T) => void +>x : T + + From f6d56f366ed002dad99433047b0598fb9aa176b4 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Sat, 2 Jun 2018 13:46:45 -0700 Subject: [PATCH 10/13] Fix fourslash baselines --- tests/cases/fourslash/annotateWithTypeFromJSDoc9.5.ts | 2 +- tests/cases/fourslash/jsdocReturnsTag.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc9.5.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc9.5.ts index 33750e4f3e04b..f696273c3386a 100644 --- a/tests/cases/fourslash/annotateWithTypeFromJSDoc9.5.ts +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc9.5.ts @@ -11,7 +11,7 @@ verify.codeFix({ description: "Annotate with type from JSDoc", newFileContent: `/** - * @template {T} + * @template T * @param {T} x * @returns {T} */ diff --git a/tests/cases/fourslash/jsdocReturnsTag.ts b/tests/cases/fourslash/jsdocReturnsTag.ts index 58edfdbff9151..dac10e16fbb65 100644 --- a/tests/cases/fourslash/jsdocReturnsTag.ts +++ b/tests/cases/fourslash/jsdocReturnsTag.ts @@ -17,7 +17,7 @@ verify.signatureHelp({ text: "find(l: T[], x: T): T", docComment: "Find an item", tags: [ - { name: "template", text: "T" }, + { name: "template", text: "T\n " }, { name: "param", text: "l" }, { name: "param", text: "x" }, { name: "returns", text: "The names of the found item(s)." }, From 0019f69c56794d3b55537274b9a762a01e4d4f0f Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Sun, 3 Jun 2018 16:23:02 -0700 Subject: [PATCH 11/13] Another fourslash update --- tests/cases/fourslash/annotateWithTypeFromJSDoc9.5.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc9.5.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc9.5.ts index f696273c3386a..d0319bee614c1 100644 --- a/tests/cases/fourslash/annotateWithTypeFromJSDoc9.5.ts +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc9.5.ts @@ -1,7 +1,7 @@ /// /////** -//// * @template {T} +//// * @template T //// * @param {T} x //// * @returns {T} //// */ From 3ce4b24f596ebcb35484cc9df34132b333b82a59 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 4 Jun 2018 10:12:05 -0700 Subject: [PATCH 12/13] Address PR comments --- src/compiler/parser.ts | 3 +-- src/compiler/utilities.ts | 2 +- tests/cases/fourslash/jsdocReturnsTag.ts | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 1a8c01853257d..40ec484afb862 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -7043,8 +7043,7 @@ namespace ts { } if (constraint) { - Debug.assert(!!typeParameters.length); - typeParameters[0].constraint = constraint.type; + first(typeParameters).constraint = constraint.type; } const result = createNode(SyntaxKind.JSDocTemplateTag, atToken.pos); diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 53973d70d816a..c383ed8b2bab2 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3177,7 +3177,7 @@ namespace ts { } export function getJSDocTypeParameterDeclarations(node: DeclarationWithTypeParameters): ReadonlyArray { - return flatMap(filter(getJSDocTags(node), isNonTypeAliasTemplate), tag => tag.typeParameters); + return flatMap(getJSDocTags(node), tag => isNonTypeAliasTemplate(tag) ? tag.typeParameters : undefined); } /** template tags are only available when a typedef isn't already using them */ diff --git a/tests/cases/fourslash/jsdocReturnsTag.ts b/tests/cases/fourslash/jsdocReturnsTag.ts index dac10e16fbb65..3f78e275e93fe 100644 --- a/tests/cases/fourslash/jsdocReturnsTag.ts +++ b/tests/cases/fourslash/jsdocReturnsTag.ts @@ -17,6 +17,7 @@ verify.signatureHelp({ text: "find(l: T[], x: T): T", docComment: "Find an item", tags: [ + // TODO: GH#24130 (see PR #24600's commits for potential fix) { name: "template", text: "T\n " }, { name: "param", text: "l" }, { name: "param", text: "x" }, From e85d472a79a561255034ecbd5ba744525f5c5a51 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 4 Jun 2018 11:20:50 -0700 Subject: [PATCH 13/13] Simplify getEffectiveTypeParameterDeclarations Make checkGrammarConstructorTypeParameters do a little more work --- src/compiler/checker.ts | 6 +++--- src/compiler/utilities.ts | 7 +------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 377f3c27efd95..0c7d4044a4177 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -28416,9 +28416,9 @@ namespace ts { } function checkGrammarConstructorTypeParameters(node: ConstructorDeclaration) { - const typeParameters = getEffectiveTypeParameterDeclarations(node); - if (isNodeArray(typeParameters) || typeParameters.length) { - const { pos, end } = isNodeArray(typeParameters) ? typeParameters : typeParameters[0]; + const jsdocTypeParameters = isInJavaScriptFile(node) && getJSDocTypeParameterDeclarations(node); + if (node.typeParameters || jsdocTypeParameters && jsdocTypeParameters.length) { + const { pos, end } = node.typeParameters || jsdocTypeParameters && jsdocTypeParameters[0] || node; return grammarErrorAtPos(node, pos, end - pos, Diagnostics.Type_parameters_cannot_appear_on_a_constructor_declaration); } } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index c383ed8b2bab2..65984a5cedb4b 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3166,12 +3166,7 @@ namespace ts { } if (isJSDocTypeAlias(node)) { Debug.assert(node.parent.kind === SyntaxKind.JSDocComment); - const templateTags = flatMap(filter(node.parent.tags, isJSDocTemplateTag), tag => tag.typeParameters) as ReadonlyArray; - const templateTagNodes = templateTags as NodeArray; - templateTagNodes.pos = templateTagNodes.length > 0 ? first(templateTagNodes).pos : node.pos; - templateTagNodes.end = templateTagNodes.length > 0 ? last(templateTagNodes).end : node.end; - templateTagNodes.hasTrailingComma = false; - return templateTagNodes; + return flatMap(node.parent.tags, tag => isJSDocTemplateTag(tag) ? tag.typeParameters : undefined) as ReadonlyArray; } return node.typeParameters || (isInJavaScriptFile(node) ? getJSDocTypeParameterDeclarations(node) : emptyArray); }