Skip to content

Commit 3d7ec8a

Browse files
authored
Improve @template lookup and resilience (microsoft#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 microsoft#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
1 parent c3d7a56 commit 3d7ec8a

13 files changed

+269
-5
lines changed

src/compiler/checker.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -9185,7 +9185,6 @@ namespace ts {
91859185
return undefined;
91869186
}
91879187
switch (node.kind) {
9188-
case SyntaxKind.VariableStatement:
91899188
case SyntaxKind.ClassDeclaration:
91909189
case SyntaxKind.ClassExpression:
91919190
case SyntaxKind.InterfaceDeclaration:
@@ -9205,28 +9204,32 @@ namespace ts {
92059204
case SyntaxKind.JSDocEnumTag:
92069205
case SyntaxKind.JSDocCallbackTag:
92079206
case SyntaxKind.MappedType:
9208-
case SyntaxKind.ConditionalType:
9207+
case SyntaxKind.ConditionalType: {
92099208
const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes);
92109209
if (node.kind === SyntaxKind.MappedType) {
92119210
return append(outerTypeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode((<MappedTypeNode>node).typeParameter)));
92129211
}
92139212
else if (node.kind === SyntaxKind.ConditionalType) {
92149213
return concatenate(outerTypeParameters, getInferTypeParameters(<ConditionalTypeNode>node));
92159214
}
9216-
else if (node.kind === SyntaxKind.VariableStatement && !isInJSFile(node)) {
9217-
break;
9218-
}
92199215
const outerAndOwnTypeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(<DeclarationWithTypeParameters>node));
92209216
const thisType = includeThisTypes &&
92219217
(node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.InterfaceDeclaration || isJSConstructor(node)) &&
92229218
getDeclaredTypeOfClassOrInterface(getSymbolOfNode(node as ClassLikeDeclaration | InterfaceDeclaration)).thisType;
92239219
return thisType ? append(outerAndOwnTypeParameters, thisType) : outerAndOwnTypeParameters;
9220+
}
92249221
case SyntaxKind.JSDocParameterTag:
92259222
const paramSymbol = getParameterSymbolFromJSDoc(node as JSDocParameterTag);
92269223
if (paramSymbol) {
92279224
node = paramSymbol.valueDeclaration;
92289225
}
92299226
break;
9227+
case SyntaxKind.JSDocComment: {
9228+
const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes);
9229+
return (node as JSDoc).tags
9230+
? appendTypeParameters(outerTypeParameters, flatMap((node as JSDoc).tags, t => isJSDocTemplateTag(t) ? t.typeParameters : undefined))
9231+
: outerTypeParameters;
9232+
}
92309233
}
92319234
}
92329235
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
error TS5055: Cannot write file 'tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js' because it would overwrite input file.
2+
Adding a tsconfig.json file will help organize projects that contain both TypeScript and JavaScript files. Learn more at https://aka.ms/tsconfig.
3+
tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js(1,14): error TS2304: Cannot find name 'T'.
4+
tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js(4,17): error TS2304: Cannot find name 'T'.
5+
tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js(4,19): error TS1069: Unexpected token. A type parameter name was expected without curly braces.
6+
tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js(7,35): error TS2339: Property 'foo' does not exist on type 'Bar'.
7+
8+
9+
!!! error TS5055: Cannot write file 'tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js' because it would overwrite input file.
10+
!!! 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.
11+
==== tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js (4 errors) ====
12+
/** @return {T} */
13+
~
14+
!!! error TS2304: Cannot find name 'T'.
15+
const dedupingMixin = function(mixin) {};
16+
17+
/** @template {T} */
18+
~
19+
!!! error TS2304: Cannot find name 'T'.
20+
~
21+
!!! error TS1069: Unexpected token. A type parameter name was expected without curly braces.
22+
const PropertyAccessors = dedupingMixin(() => {
23+
class Bar {
24+
static bar() { this.prototype.foo(); }
25+
~~~
26+
!!! error TS2339: Property 'foo' does not exist on type 'Bar'.
27+
}
28+
});
29+
30+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
=== tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js ===
2+
/** @return {T} */
3+
const dedupingMixin = function(mixin) {};
4+
>dedupingMixin : Symbol(dedupingMixin, Decl(jsdocOuterTypeParameters1.js, 1, 5))
5+
>mixin : Symbol(mixin, Decl(jsdocOuterTypeParameters1.js, 1, 31))
6+
7+
/** @template {T} */
8+
const PropertyAccessors = dedupingMixin(() => {
9+
>PropertyAccessors : Symbol(PropertyAccessors, Decl(jsdocOuterTypeParameters1.js, 4, 5))
10+
>dedupingMixin : Symbol(dedupingMixin, Decl(jsdocOuterTypeParameters1.js, 1, 5))
11+
12+
class Bar {
13+
>Bar : Symbol(Bar, Decl(jsdocOuterTypeParameters1.js, 4, 47))
14+
15+
static bar() { this.prototype.foo(); }
16+
>bar : Symbol(Bar.bar, Decl(jsdocOuterTypeParameters1.js, 5, 13))
17+
>this.prototype : Symbol(Bar.prototype)
18+
>this : Symbol(Bar, Decl(jsdocOuterTypeParameters1.js, 4, 47))
19+
>prototype : Symbol(Bar.prototype)
20+
}
21+
});
22+
23+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
=== tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js ===
2+
/** @return {T} */
3+
const dedupingMixin = function(mixin) {};
4+
>dedupingMixin : (mixin: any) => any
5+
>function(mixin) {} : (mixin: any) => any
6+
>mixin : any
7+
8+
/** @template {T} */
9+
const PropertyAccessors = dedupingMixin(() => {
10+
>PropertyAccessors : any
11+
>dedupingMixin(() => { class Bar { static bar() { this.prototype.foo(); } }}) : any
12+
>dedupingMixin : (mixin: any) => any
13+
>() => { class Bar { static bar() { this.prototype.foo(); } }} : () => void
14+
15+
class Bar {
16+
>Bar : Bar
17+
18+
static bar() { this.prototype.foo(); }
19+
>bar : () => void
20+
>this.prototype.foo() : any
21+
>this.prototype.foo : any
22+
>this.prototype : Bar
23+
>this : typeof Bar
24+
>prototype : Bar
25+
>foo : any
26+
}
27+
});
28+
29+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
error TS5055: Cannot write file 'tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js' because it would overwrite input file.
2+
Adding a tsconfig.json file will help organize projects that contain both TypeScript and JavaScript files. Learn more at https://aka.ms/tsconfig.
3+
tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js(1,14): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
4+
tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js(7,35): error TS2339: Property 'foo' does not exist on type 'Bar'.
5+
6+
7+
!!! error TS5055: Cannot write file 'tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js' because it would overwrite input file.
8+
!!! 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.
9+
==== tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js (2 errors) ====
10+
/** @return {T} */
11+
~
12+
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
13+
const dedupingMixin = function(mixin) {};
14+
15+
/** @template T */
16+
const PropertyAccessors = dedupingMixin(() => {
17+
class Bar {
18+
static bar() { this.prototype.foo(); }
19+
~~~
20+
!!! error TS2339: Property 'foo' does not exist on type 'Bar'.
21+
}
22+
});
23+
24+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
=== tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js ===
2+
/** @return {T} */
3+
const dedupingMixin = function(mixin) {};
4+
>dedupingMixin : Symbol(dedupingMixin, Decl(jsdocOuterTypeParameters1.js, 1, 5))
5+
>mixin : Symbol(mixin, Decl(jsdocOuterTypeParameters1.js, 1, 31))
6+
7+
/** @template T */
8+
const PropertyAccessors = dedupingMixin(() => {
9+
>PropertyAccessors : Symbol(PropertyAccessors, Decl(jsdocOuterTypeParameters1.js, 4, 5))
10+
>dedupingMixin : Symbol(dedupingMixin, Decl(jsdocOuterTypeParameters1.js, 1, 5))
11+
12+
class Bar {
13+
>Bar : Symbol(Bar, Decl(jsdocOuterTypeParameters1.js, 4, 47))
14+
15+
static bar() { this.prototype.foo(); }
16+
>bar : Symbol(Bar.bar, Decl(jsdocOuterTypeParameters1.js, 5, 13))
17+
>this.prototype : Symbol(Bar.prototype)
18+
>this : Symbol(Bar, Decl(jsdocOuterTypeParameters1.js, 4, 47))
19+
>prototype : Symbol(Bar.prototype)
20+
}
21+
});
22+
23+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
=== tests/cases/conformance/jsdoc/jsdocOuterTypeParameters1.js ===
2+
/** @return {T} */
3+
const dedupingMixin = function(mixin) {};
4+
>dedupingMixin : (mixin: any) => T
5+
>function(mixin) {} : (mixin: any) => T
6+
>mixin : any
7+
8+
/** @template T */
9+
const PropertyAccessors = dedupingMixin(() => {
10+
>PropertyAccessors : T
11+
>dedupingMixin(() => { class Bar { static bar() { this.prototype.foo(); } }}) : T
12+
>dedupingMixin : (mixin: any) => T
13+
>() => { class Bar { static bar() { this.prototype.foo(); } }} : () => void
14+
15+
class Bar {
16+
>Bar : Bar
17+
18+
static bar() { this.prototype.foo(); }
19+
>bar : () => void
20+
>this.prototype.foo() : any
21+
>this.prototype.foo : any
22+
>this.prototype : Bar
23+
>this : typeof Bar
24+
>prototype : Bar
25+
>foo : any
26+
}
27+
});
28+
29+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
error TS5055: Cannot write file 'tests/cases/conformance/jsdoc/jsdocOuterTypeParameters3.js' because it would overwrite input file.
2+
Adding a tsconfig.json file will help organize projects that contain both TypeScript and JavaScript files. Learn more at https://aka.ms/tsconfig.
3+
tests/cases/conformance/jsdoc/jsdocOuterTypeParameters3.js(1,16): error TS2304: Cannot find name 'T'.
4+
tests/cases/conformance/jsdoc/jsdocOuterTypeParameters3.js(1,18): error TS1069: Unexpected token. A type parameter name was expected without curly braces.
5+
tests/cases/conformance/jsdoc/jsdocOuterTypeParameters3.js(5,43): error TS2339: Property 'foo' does not exist on type 'Bar'.
6+
7+
8+
!!! error TS5055: Cannot write file 'tests/cases/conformance/jsdoc/jsdocOuterTypeParameters3.js' because it would overwrite input file.
9+
!!! 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.
10+
==== tests/cases/conformance/jsdoc/jsdocOuterTypeParameters3.js (3 errors) ====
11+
/** @template {T} */
12+
~
13+
!!! error TS2304: Cannot find name 'T'.
14+
~
15+
!!! error TS1069: Unexpected token. A type parameter name was expected without curly braces.
16+
class Baz {
17+
m() {
18+
class Bar {
19+
static bar() { this.prototype.foo(); }
20+
~~~
21+
!!! error TS2339: Property 'foo' does not exist on type 'Bar'.
22+
}
23+
}
24+
}
25+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
=== tests/cases/conformance/jsdoc/jsdocOuterTypeParameters3.js ===
2+
/** @template {T} */
3+
class Baz {
4+
>Baz : Symbol(Baz, Decl(jsdocOuterTypeParameters3.js, 0, 0))
5+
6+
m() {
7+
>m : Symbol(Baz.m, Decl(jsdocOuterTypeParameters3.js, 1, 11))
8+
9+
class Bar {
10+
>Bar : Symbol(Bar, Decl(jsdocOuterTypeParameters3.js, 2, 9))
11+
12+
static bar() { this.prototype.foo(); }
13+
>bar : Symbol(Bar.bar, Decl(jsdocOuterTypeParameters3.js, 3, 19))
14+
>this.prototype : Symbol(Bar.prototype)
15+
>this : Symbol(Bar, Decl(jsdocOuterTypeParameters3.js, 2, 9))
16+
>prototype : Symbol(Bar.prototype)
17+
}
18+
}
19+
}
20+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
=== tests/cases/conformance/jsdoc/jsdocOuterTypeParameters3.js ===
2+
/** @template {T} */
3+
class Baz {
4+
>Baz : Baz
5+
6+
m() {
7+
>m : () => void
8+
9+
class Bar {
10+
>Bar : Bar
11+
12+
static bar() { this.prototype.foo(); }
13+
>bar : () => void
14+
>this.prototype.foo() : any
15+
>this.prototype.foo : any
16+
>this.prototype : Bar
17+
>this : typeof Bar
18+
>prototype : Bar
19+
>foo : any
20+
}
21+
}
22+
}
23+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// @checkjs: true
2+
// @filename: jsdocOuterTypeParameters1.js
3+
/** @return {T} */
4+
const dedupingMixin = function(mixin) {};
5+
6+
/** @template {T} */
7+
const PropertyAccessors = dedupingMixin(() => {
8+
class Bar {
9+
static bar() { this.prototype.foo(); }
10+
}
11+
});
12+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// @checkjs: true
2+
// @filename: jsdocOuterTypeParameters1.js
3+
/** @return {T} */
4+
const dedupingMixin = function(mixin) {};
5+
6+
/** @template T */
7+
const PropertyAccessors = dedupingMixin(() => {
8+
class Bar {
9+
static bar() { this.prototype.foo(); }
10+
}
11+
});
12+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// @checkjs: true
2+
// @filename: jsdocOuterTypeParameters3.js
3+
4+
/** @template {T} */
5+
class Baz {
6+
m() {
7+
class Bar {
8+
static bar() { this.prototype.foo(); }
9+
}
10+
}
11+
}

0 commit comments

Comments
 (0)