From 3d7ec8aab2fd53e81fee066ea9f07c3a88a8d51e Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 17 Feb 2021 17:21:17 -0800 Subject: [PATCH] Improve @template lookup and resilience (#42851) * Improve @template lookup and resilience 1. @template parsing may produce a template tag with a type parameter whose name is the missing identifier. These tags should be skipped in the checker because they receive an error in the parser. 2. The fix in #37819 was incorrect; there's no such thing as a type parameter declared on a variable declaration. Instead, there needs to be a type parameter declared on a jsdoc comment, because that's the scope for tags like `@return` and `@typedef`. There are 3 tests because either fix (1) and (2) fix the first test's failure, but both are required to fix the last two tests' failures. * remove containsParseError call --- src/compiler/checker.ts | 13 ++++---- .../jsdocOuterTypeParameters1.errors.txt | 30 +++++++++++++++++++ .../jsdocOuterTypeParameters1.symbols | 23 ++++++++++++++ .../reference/jsdocOuterTypeParameters1.types | 29 ++++++++++++++++++ .../jsdocOuterTypeParameters2.errors.txt | 24 +++++++++++++++ .../jsdocOuterTypeParameters2.symbols | 23 ++++++++++++++ .../reference/jsdocOuterTypeParameters2.types | 29 ++++++++++++++++++ .../jsdocOuterTypeParameters3.errors.txt | 25 ++++++++++++++++ .../jsdocOuterTypeParameters3.symbols | 20 +++++++++++++ .../reference/jsdocOuterTypeParameters3.types | 23 ++++++++++++++ .../jsdoc/jsdocOuterTypeParameters1.ts | 12 ++++++++ .../jsdoc/jsdocOuterTypeParameters2.ts | 12 ++++++++ .../jsdoc/jsdocOuterTypeParameters3.ts | 11 +++++++ 13 files changed, 269 insertions(+), 5 deletions(-) create mode 100644 tests/baselines/reference/jsdocOuterTypeParameters1.errors.txt create mode 100644 tests/baselines/reference/jsdocOuterTypeParameters1.symbols create mode 100644 tests/baselines/reference/jsdocOuterTypeParameters1.types create mode 100644 tests/baselines/reference/jsdocOuterTypeParameters2.errors.txt create mode 100644 tests/baselines/reference/jsdocOuterTypeParameters2.symbols create mode 100644 tests/baselines/reference/jsdocOuterTypeParameters2.types create mode 100644 tests/baselines/reference/jsdocOuterTypeParameters3.errors.txt create mode 100644 tests/baselines/reference/jsdocOuterTypeParameters3.symbols create mode 100644 tests/baselines/reference/jsdocOuterTypeParameters3.types create mode 100644 tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.ts create mode 100644 tests/cases/conformance/jsdoc/jsdocOuterTypeParameters2.ts create mode 100644 tests/cases/conformance/jsdoc/jsdocOuterTypeParameters3.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 03e696e48a9..7b70751a4ad 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9185,7 +9185,6 @@ namespace ts { return undefined; } switch (node.kind) { - case SyntaxKind.VariableStatement: case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: case SyntaxKind.InterfaceDeclaration: @@ -9205,7 +9204,7 @@ namespace ts { case SyntaxKind.JSDocEnumTag: case SyntaxKind.JSDocCallbackTag: case SyntaxKind.MappedType: - case SyntaxKind.ConditionalType: + case SyntaxKind.ConditionalType: { const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes); if (node.kind === SyntaxKind.MappedType) { return append(outerTypeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode((node).typeParameter))); @@ -9213,20 +9212,24 @@ namespace ts { else if (node.kind === SyntaxKind.ConditionalType) { return concatenate(outerTypeParameters, getInferTypeParameters(node)); } - else if (node.kind === SyntaxKind.VariableStatement && !isInJSFile(node)) { - break; - } const outerAndOwnTypeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(node)); const thisType = includeThisTypes && (node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.InterfaceDeclaration || isJSConstructor(node)) && getDeclaredTypeOfClassOrInterface(getSymbolOfNode(node as ClassLikeDeclaration | InterfaceDeclaration)).thisType; return thisType ? append(outerAndOwnTypeParameters, thisType) : outerAndOwnTypeParameters; + } case SyntaxKind.JSDocParameterTag: const paramSymbol = getParameterSymbolFromJSDoc(node as JSDocParameterTag); if (paramSymbol) { node = paramSymbol.valueDeclaration; } break; + case SyntaxKind.JSDocComment: { + const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes); + return (node as JSDoc).tags + ? appendTypeParameters(outerTypeParameters, flatMap((node as JSDoc).tags, t => isJSDocTemplateTag(t) ? t.typeParameters : undefined)) + : outerTypeParameters; + } } } } diff --git a/tests/baselines/reference/jsdocOuterTypeParameters1.errors.txt b/tests/baselines/reference/jsdocOuterTypeParameters1.errors.txt new file mode 100644 index 00000000000..83a2cd427b2 --- /dev/null +++ b/tests/baselines/reference/jsdocOuterTypeParameters1.errors.txt @@ -0,0 +1,30 @@ +error TS5055: Cannot write file 'tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js' because it would overwrite input file. + Adding a tsconfig.json file will help organize projects that contain both TypeScript and JavaScript files. Learn more at https://aka.ms/tsconfig. +tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js(1,14): error TS2304: Cannot find name 'T'. +tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js(4,17): error TS2304: Cannot find name 'T'. +tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js(4,19): error TS1069: Unexpected token. A type parameter name was expected without curly braces. +tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js(7,35): error TS2339: Property 'foo' does not exist on type 'Bar'. + + +!!! error TS5055: Cannot write file 'tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js' because it would overwrite input file. +!!! error TS5055: Adding a tsconfig.json file will help organize projects that contain both TypeScript and JavaScript files. Learn more at https://aka.ms/tsconfig. +==== tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js (4 errors) ==== + /** @return {T} */ + ~ +!!! error TS2304: Cannot find name 'T'. + const dedupingMixin = function(mixin) {}; + + /** @template {T} */ + ~ +!!! error TS2304: Cannot find name 'T'. + ~ +!!! error TS1069: Unexpected token. A type parameter name was expected without curly braces. + const PropertyAccessors = dedupingMixin(() => { + class Bar { + static bar() { this.prototype.foo(); } + ~~~ +!!! error TS2339: Property 'foo' does not exist on type 'Bar'. + } + }); + + \ No newline at end of file diff --git a/tests/baselines/reference/jsdocOuterTypeParameters1.symbols b/tests/baselines/reference/jsdocOuterTypeParameters1.symbols new file mode 100644 index 00000000000..75d7dac9860 --- /dev/null +++ b/tests/baselines/reference/jsdocOuterTypeParameters1.symbols @@ -0,0 +1,23 @@ +=== tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js === +/** @return {T} */ +const dedupingMixin = function(mixin) {}; +>dedupingMixin : Symbol(dedupingMixin, Decl(jsdocOuterTypeParameters1.js, 1, 5)) +>mixin : Symbol(mixin, Decl(jsdocOuterTypeParameters1.js, 1, 31)) + + /** @template {T} */ +const PropertyAccessors = dedupingMixin(() => { +>PropertyAccessors : Symbol(PropertyAccessors, Decl(jsdocOuterTypeParameters1.js, 4, 5)) +>dedupingMixin : Symbol(dedupingMixin, Decl(jsdocOuterTypeParameters1.js, 1, 5)) + + class Bar { +>Bar : Symbol(Bar, Decl(jsdocOuterTypeParameters1.js, 4, 47)) + + static bar() { this.prototype.foo(); } +>bar : Symbol(Bar.bar, Decl(jsdocOuterTypeParameters1.js, 5, 13)) +>this.prototype : Symbol(Bar.prototype) +>this : Symbol(Bar, Decl(jsdocOuterTypeParameters1.js, 4, 47)) +>prototype : Symbol(Bar.prototype) + } +}); + + diff --git a/tests/baselines/reference/jsdocOuterTypeParameters1.types b/tests/baselines/reference/jsdocOuterTypeParameters1.types new file mode 100644 index 00000000000..cf50a32b841 --- /dev/null +++ b/tests/baselines/reference/jsdocOuterTypeParameters1.types @@ -0,0 +1,29 @@ +=== tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js === +/** @return {T} */ +const dedupingMixin = function(mixin) {}; +>dedupingMixin : (mixin: any) => any +>function(mixin) {} : (mixin: any) => any +>mixin : any + + /** @template {T} */ +const PropertyAccessors = dedupingMixin(() => { +>PropertyAccessors : any +>dedupingMixin(() => { class Bar { static bar() { this.prototype.foo(); } }}) : any +>dedupingMixin : (mixin: any) => any +>() => { class Bar { static bar() { this.prototype.foo(); } }} : () => void + + class Bar { +>Bar : Bar + + static bar() { this.prototype.foo(); } +>bar : () => void +>this.prototype.foo() : any +>this.prototype.foo : any +>this.prototype : Bar +>this : typeof Bar +>prototype : Bar +>foo : any + } +}); + + diff --git a/tests/baselines/reference/jsdocOuterTypeParameters2.errors.txt b/tests/baselines/reference/jsdocOuterTypeParameters2.errors.txt new file mode 100644 index 00000000000..a0a53680b66 --- /dev/null +++ b/tests/baselines/reference/jsdocOuterTypeParameters2.errors.txt @@ -0,0 +1,24 @@ +error TS5055: Cannot write file 'tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js' because it would overwrite input file. + Adding a tsconfig.json file will help organize projects that contain both TypeScript and JavaScript files. Learn more at https://aka.ms/tsconfig. +tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js(1,14): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value. +tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js(7,35): error TS2339: Property 'foo' does not exist on type 'Bar'. + + +!!! error TS5055: Cannot write file 'tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js' because it would overwrite input file. +!!! error TS5055: Adding a tsconfig.json file will help organize projects that contain both TypeScript and JavaScript files. Learn more at https://aka.ms/tsconfig. +==== tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js (2 errors) ==== + /** @return {T} */ + ~ +!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value. + const dedupingMixin = function(mixin) {}; + + /** @template T */ + const PropertyAccessors = dedupingMixin(() => { + class Bar { + static bar() { this.prototype.foo(); } + ~~~ +!!! error TS2339: Property 'foo' does not exist on type 'Bar'. + } + }); + + \ No newline at end of file diff --git a/tests/baselines/reference/jsdocOuterTypeParameters2.symbols b/tests/baselines/reference/jsdocOuterTypeParameters2.symbols new file mode 100644 index 00000000000..32760417a98 --- /dev/null +++ b/tests/baselines/reference/jsdocOuterTypeParameters2.symbols @@ -0,0 +1,23 @@ +=== tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js === +/** @return {T} */ +const dedupingMixin = function(mixin) {}; +>dedupingMixin : Symbol(dedupingMixin, Decl(jsdocOuterTypeParameters1.js, 1, 5)) +>mixin : Symbol(mixin, Decl(jsdocOuterTypeParameters1.js, 1, 31)) + + /** @template T */ +const PropertyAccessors = dedupingMixin(() => { +>PropertyAccessors : Symbol(PropertyAccessors, Decl(jsdocOuterTypeParameters1.js, 4, 5)) +>dedupingMixin : Symbol(dedupingMixin, Decl(jsdocOuterTypeParameters1.js, 1, 5)) + + class Bar { +>Bar : Symbol(Bar, Decl(jsdocOuterTypeParameters1.js, 4, 47)) + + static bar() { this.prototype.foo(); } +>bar : Symbol(Bar.bar, Decl(jsdocOuterTypeParameters1.js, 5, 13)) +>this.prototype : Symbol(Bar.prototype) +>this : Symbol(Bar, Decl(jsdocOuterTypeParameters1.js, 4, 47)) +>prototype : Symbol(Bar.prototype) + } +}); + + diff --git a/tests/baselines/reference/jsdocOuterTypeParameters2.types b/tests/baselines/reference/jsdocOuterTypeParameters2.types new file mode 100644 index 00000000000..0870d2184c9 --- /dev/null +++ b/tests/baselines/reference/jsdocOuterTypeParameters2.types @@ -0,0 +1,29 @@ +=== tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js === +/** @return {T} */ +const dedupingMixin = function(mixin) {}; +>dedupingMixin : (mixin: any) => T +>function(mixin) {} : (mixin: any) => T +>mixin : any + + /** @template T */ +const PropertyAccessors = dedupingMixin(() => { +>PropertyAccessors : T +>dedupingMixin(() => { class Bar { static bar() { this.prototype.foo(); } }}) : T +>dedupingMixin : (mixin: any) => T +>() => { class Bar { static bar() { this.prototype.foo(); } }} : () => void + + class Bar { +>Bar : Bar + + static bar() { this.prototype.foo(); } +>bar : () => void +>this.prototype.foo() : any +>this.prototype.foo : any +>this.prototype : Bar +>this : typeof Bar +>prototype : Bar +>foo : any + } +}); + + diff --git a/tests/baselines/reference/jsdocOuterTypeParameters3.errors.txt b/tests/baselines/reference/jsdocOuterTypeParameters3.errors.txt new file mode 100644 index 00000000000..d7cb5089247 --- /dev/null +++ b/tests/baselines/reference/jsdocOuterTypeParameters3.errors.txt @@ -0,0 +1,25 @@ +error TS5055: Cannot write file 'tests/cases/conformance/jsdoc/jsdocOuterTypeParameters3.js' because it would overwrite input file. + Adding a tsconfig.json file will help organize projects that contain both TypeScript and JavaScript files. Learn more at https://aka.ms/tsconfig. +tests/cases/conformance/jsdoc/jsdocOuterTypeParameters3.js(1,16): error TS2304: Cannot find name 'T'. +tests/cases/conformance/jsdoc/jsdocOuterTypeParameters3.js(1,18): error TS1069: Unexpected token. A type parameter name was expected without curly braces. +tests/cases/conformance/jsdoc/jsdocOuterTypeParameters3.js(5,43): error TS2339: Property 'foo' does not exist on type 'Bar'. + + +!!! error TS5055: Cannot write file 'tests/cases/conformance/jsdoc/jsdocOuterTypeParameters3.js' because it would overwrite input file. +!!! error TS5055: Adding a tsconfig.json file will help organize projects that contain both TypeScript and JavaScript files. Learn more at https://aka.ms/tsconfig. +==== tests/cases/conformance/jsdoc/jsdocOuterTypeParameters3.js (3 errors) ==== + /** @template {T} */ + ~ +!!! error TS2304: Cannot find name 'T'. + ~ +!!! error TS1069: Unexpected token. A type parameter name was expected without curly braces. + class Baz { + m() { + class Bar { + static bar() { this.prototype.foo(); } + ~~~ +!!! error TS2339: Property 'foo' does not exist on type 'Bar'. + } + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/jsdocOuterTypeParameters3.symbols b/tests/baselines/reference/jsdocOuterTypeParameters3.symbols new file mode 100644 index 00000000000..965182d48e7 --- /dev/null +++ b/tests/baselines/reference/jsdocOuterTypeParameters3.symbols @@ -0,0 +1,20 @@ +=== tests/cases/conformance/jsdoc/jsdocOuterTypeParameters3.js === +/** @template {T} */ +class Baz { +>Baz : Symbol(Baz, Decl(jsdocOuterTypeParameters3.js, 0, 0)) + + m() { +>m : Symbol(Baz.m, Decl(jsdocOuterTypeParameters3.js, 1, 11)) + + class Bar { +>Bar : Symbol(Bar, Decl(jsdocOuterTypeParameters3.js, 2, 9)) + + static bar() { this.prototype.foo(); } +>bar : Symbol(Bar.bar, Decl(jsdocOuterTypeParameters3.js, 3, 19)) +>this.prototype : Symbol(Bar.prototype) +>this : Symbol(Bar, Decl(jsdocOuterTypeParameters3.js, 2, 9)) +>prototype : Symbol(Bar.prototype) + } + } +} + diff --git a/tests/baselines/reference/jsdocOuterTypeParameters3.types b/tests/baselines/reference/jsdocOuterTypeParameters3.types new file mode 100644 index 00000000000..7056caa66ab --- /dev/null +++ b/tests/baselines/reference/jsdocOuterTypeParameters3.types @@ -0,0 +1,23 @@ +=== tests/cases/conformance/jsdoc/jsdocOuterTypeParameters3.js === +/** @template {T} */ +class Baz { +>Baz : Baz + + m() { +>m : () => void + + class Bar { +>Bar : Bar + + static bar() { this.prototype.foo(); } +>bar : () => void +>this.prototype.foo() : any +>this.prototype.foo : any +>this.prototype : Bar +>this : typeof Bar +>prototype : Bar +>foo : any + } + } +} + diff --git a/tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.ts b/tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.ts new file mode 100644 index 00000000000..c16a87a57fc --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.ts @@ -0,0 +1,12 @@ +// @checkjs: true +// @filename: jsdocOuterTypeParameters1.js +/** @return {T} */ +const dedupingMixin = function(mixin) {}; + + /** @template {T} */ +const PropertyAccessors = dedupingMixin(() => { + class Bar { + static bar() { this.prototype.foo(); } + } +}); + diff --git a/tests/cases/conformance/jsdoc/jsdocOuterTypeParameters2.ts b/tests/cases/conformance/jsdoc/jsdocOuterTypeParameters2.ts new file mode 100644 index 00000000000..ed814d315ca --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocOuterTypeParameters2.ts @@ -0,0 +1,12 @@ +// @checkjs: true +// @filename: jsdocOuterTypeParameters1.js +/** @return {T} */ +const dedupingMixin = function(mixin) {}; + + /** @template T */ +const PropertyAccessors = dedupingMixin(() => { + class Bar { + static bar() { this.prototype.foo(); } + } +}); + diff --git a/tests/cases/conformance/jsdoc/jsdocOuterTypeParameters3.ts b/tests/cases/conformance/jsdoc/jsdocOuterTypeParameters3.ts new file mode 100644 index 00000000000..ef666b796d8 --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocOuterTypeParameters3.ts @@ -0,0 +1,11 @@ +// @checkjs: true +// @filename: jsdocOuterTypeParameters3.js + +/** @template {T} */ +class Baz { + m() { + class Bar { + static bar() { this.prototype.foo(); } + } + } +}