Skip to content

Commit 13b7d17

Browse files
author
Andy
authored
Don't bind JSDoc type parameter in a TS file (#16413)
* Don't bind JSDoc type parameter in a TS file * Fix tests * Remove unnecessary non-null assertions
1 parent a757e84 commit 13b7d17

13 files changed

+104
-11
lines changed

src/compiler/binder.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,7 +1521,7 @@ namespace ts {
15211521
// All the children of these container types are never visible through another
15221522
// symbol (i.e. through another symbol's 'exports' or 'members'). Instead,
15231523
// they're only accessed 'lexically' (i.e. from code that exists underneath
1524-
// their container in the tree. To accomplish this, we simply add their declared
1524+
// their container in the tree). To accomplish this, we simply add their declared
15251525
// symbol to the 'locals' of the container. These symbols can then be found as
15261526
// the type checker walks up the containers, checking them for matching names.
15271527
return declareSymbol(container.locals, /*parent*/ undefined, node, symbolFlags, symbolExcludes);
@@ -2053,7 +2053,10 @@ namespace ts {
20532053
case SyntaxKind.TypePredicate:
20542054
return checkTypePredicate(node as TypePredicateNode);
20552055
case SyntaxKind.TypeParameter:
2056-
return declareSymbolAndAddToSymbolTable(<Declaration>node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes);
2056+
if (node.parent.kind !== ts.SyntaxKind.JSDocTemplateTag || isInJavaScriptFile(node)) {
2057+
return declareSymbolAndAddToSymbolTable(<Declaration>node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes);
2058+
}
2059+
return;
20572060
case SyntaxKind.Parameter:
20582061
return bindParameter(<ParameterDeclaration>node);
20592062
case SyntaxKind.VariableDeclaration:

src/compiler/checker.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22554,10 +22554,16 @@ namespace ts {
2255422554
}
2255522555

2255622556
if (entityName.parent!.kind === SyntaxKind.JSDocParameterTag) {
22557-
const parameter = ts.getParameterFromJSDoc(entityName.parent as JSDocParameterTag);
22557+
const parameter = getParameterFromJSDoc(entityName.parent as JSDocParameterTag);
2255822558
return parameter && parameter.symbol;
2255922559
}
2256022560

22561+
if (entityName.parent.kind === SyntaxKind.TypeParameter && entityName.parent.parent.kind === SyntaxKind.JSDocTemplateTag) {
22562+
Debug.assert(!isInJavaScriptFile(entityName)); // Otherwise `isDeclarationName` would have been true.
22563+
const typeParameter = getTypeParameterFromJsDoc(entityName.parent as TypeParameterDeclaration & { parent: JSDocTemplateTag });
22564+
return typeParameter && typeParameter.symbol;
22565+
}
22566+
2256122567
if (isPartOfExpression(entityName)) {
2256222568
if (nodeIsMissing(entityName)) {
2256322569
// Missing entity name.
@@ -24824,7 +24830,6 @@ namespace ts {
2482424830
// falls through
2482524831
default:
2482624832
return isDeclarationName(name);
24827-
2482824833
}
2482924834
}
2483024835
}

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1797,7 +1797,7 @@ namespace ts {
17971797
block: Block;
17981798
}
17991799

1800-
export type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration;
1800+
export type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag;
18011801

18021802
export interface ClassLikeDeclaration extends NamedDeclaration {
18031803
name?: Identifier;

src/compiler/utilities.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1546,6 +1546,12 @@ namespace ts {
15461546
p.name.kind === SyntaxKind.Identifier && p.name.text === name);
15471547
}
15481548

1549+
export function getTypeParameterFromJsDoc(node: TypeParameterDeclaration & { parent: JSDocTemplateTag }): TypeParameterDeclaration | undefined {
1550+
const name = node.name.text;
1551+
const { typeParameters } = (node.parent.parent.parent as ts.SignatureDeclaration | ts.InterfaceDeclaration | ts.ClassDeclaration);
1552+
return find(typeParameters, p => p.name.text === name);
1553+
}
1554+
15491555
export function getJSDocType(node: Node): JSDocType {
15501556
let tag: JSDocTypeTag | JSDocParameterTag = getFirstJSDocTag(node, SyntaxKind.JSDocTypeTag) as JSDocTypeTag;
15511557
if (!tag && node.kind === SyntaxKind.Parameter) {
@@ -5274,6 +5280,10 @@ namespace ts {
52745280

52755281
/* @internal */
52765282
export function isDeclaration(node: Node): node is NamedDeclaration {
5283+
if (node.kind === SyntaxKind.TypeParameter) {
5284+
return node.parent.kind !== SyntaxKind.JSDocTemplateTag || isInJavaScriptFile(node);
5285+
}
5286+
52775287
return isDeclarationKind(node.kind);
52785288
}
52795289

src/services/findAllReferences.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -784,8 +784,8 @@ namespace ts.FindAllReferences.Core {
784784
return;
785785
}
786786

787-
const fullStart = state.options.findInComments || container.jsDoc !== undefined || forEach(search.symbol.declarations, d => d.kind === ts.SyntaxKind.JSDocTypedefTag);
788-
for (const position of getPossibleSymbolReferencePositions(sourceFile, search.text, container, fullStart)) {
787+
// Need to search in the full start of the node in case there is a reference inside JSDoc.
788+
for (const position of getPossibleSymbolReferencePositions(sourceFile, search.text, container, /*fullStart*/ true)) {
789789
getReferencesAtLocation(sourceFile, position, search, state);
790790
}
791791
}

src/services/utilities.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ namespace ts {
9494
else if (isNamespaceReference(node)) {
9595
return SemanticMeaning.Namespace;
9696
}
97+
else if (isTypeParameter(node.parent)) {
98+
Debug.assert(isJSDocTemplateTag(node.parent.parent)); // Else would be handled by isDeclarationName
99+
return SemanticMeaning.Type;
100+
}
97101
else {
98102
return SemanticMeaning.Value;
99103
}

tests/baselines/reference/jsdocInTypeScript.errors.txt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,16 @@ tests/cases/compiler/jsdocInTypeScript.ts(30,3): error TS2339: Property 'x' does
4343
// @type has no effect either.
4444
/** @type {{ x?: number }} */
4545
const z = {};
46-
z.x = 1;
46+
z.x = 1; // Error
4747
~
4848
!!! error TS2339: Property 'x' does not exist on type '{}'.
49+
50+
// @template tag should not interfere with constraint or default.
51+
/** @template T */
52+
interface I<T extends number = 0> {}
53+
54+
/** @template T */
55+
function tem<T extends number>(t: T): I<T> { return {}; }
56+
57+
let i: I; // Should succeed thanks to type parameter default
4958

tests/baselines/reference/jsdocInTypeScript.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,16 @@ f(1); f(true).length;
2828
// @type has no effect either.
2929
/** @type {{ x?: number }} */
3030
const z = {};
31-
z.x = 1;
31+
z.x = 1; // Error
32+
33+
// @template tag should not interfere with constraint or default.
34+
/** @template T */
35+
interface I<T extends number = 0> {}
36+
37+
/** @template T */
38+
function tem<T extends number>(t: T): I<T> { return {}; }
39+
40+
let i: I; // Should succeed thanks to type parameter default
3241

3342

3443
//// [jsdocInTypeScript.js]
@@ -50,4 +59,7 @@ f(true).length;
5059
// @type has no effect either.
5160
/** @type {{ x?: number }} */
5261
var z = {};
53-
z.x = 1;
62+
z.x = 1; // Error
63+
/** @template T */
64+
function tem(t) { return {}; }
65+
var i; // Should succeed thanks to type parameter default

tests/cases/compiler/jsdocInTypeScript.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,13 @@ f(1); f(true).length;
2727
// @type has no effect either.
2828
/** @type {{ x?: number }} */
2929
const z = {};
30-
z.x = 1;
30+
z.x = 1; // Error
31+
32+
// @template tag should not interfere with constraint or default.
33+
/** @template T */
34+
interface I<T extends number = 0> {}
35+
36+
/** @template T */
37+
function tem<T extends number>(t: T): I<T> { return {}; }
38+
39+
let i: I; // Should succeed thanks to type parameter default
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
/////** @template [|T|] */
4+
////class C<[|{| "isWriteAccess": true, "isDefinition": true |}T|]> {}
5+
6+
verify.singleReferenceGroup("(type parameter) T in C<T>");
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
// @allowJs: true
4+
// @Filename: /a.js
5+
6+
// TODO: https://github.com/Microsoft/TypeScript/issues/16411
7+
// Both uses of T should be referenced.
8+
9+
/////** @template [|{| "isWriteAccess": true, "isDefinition": true |}T|] */
10+
////class C {
11+
//// constructor() {
12+
//// /** @type {T} */
13+
//// this.x = null;
14+
//// }
15+
////}
16+
17+
verify.singleReferenceGroup("(type parameter) T in C");
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
/////** @template [|{| "isWriteAccess": false, "isDefinition": false |}T|] */
4+
////function f<[|{| "isWriteAccess": true, "isDefinition": true |}T|]>() {}
5+
6+
verify.singleReferenceGroup("(type parameter) T in f<T>(): void");
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
// @allowJs: true
4+
// @Filename: /a.js
5+
6+
/////**
7+
//// * @template [|{| "isWriteAccess": true, "isDefinition": true |}T|]
8+
//// * @return {[|T|]}
9+
//// */
10+
////function f() {}
11+
12+
verify.singleReferenceGroup("(type parameter) T"); // TODO:GH#??? should be "(type parameter) T in f<T>(): void"

0 commit comments

Comments
 (0)