Fix name resolution in typedef and allow defaults for template tags (#45483)

* Fix name resolution in typedef and allow defaults for template tags

* Inline parseBracketNameInTemplateTag

* Update baselines

* Add js declaration emit tests
This commit is contained in:
Ron Buckton 2021-09-08 17:05:07 -07:00 committed by GitHub
parent 8610ff5ebe
commit cf787e9bcf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 649 additions and 5 deletions

View File

@ -3388,7 +3388,7 @@ namespace ts {
function bindTypeParameter(node: TypeParameterDeclaration) {
if (isJSDocTemplateTag(node.parent)) {
const container = find((node.parent.parent as JSDoc).tags!, isJSDocTypeAlias) || getHostSignatureFromJSDoc(node.parent); // TODO: GH#18217
const container = getEffectiveContainerForJSDocTemplateTag(node.parent);
if (container) {
if (!container.locals) {
container.locals = createSymbolTable();

View File

@ -2052,7 +2052,9 @@ namespace ts {
lastSelfReferenceLocation = location;
}
lastLocation = location;
location = location.parent;
location = isJSDocTemplateTag(location) ?
getEffectiveContainerForJSDocTemplateTag(location) || location.parent :
location.parent;
}
// We just climbed up parents looking for the name, meaning that we started in a descendant node of `lastLocation`.
@ -12901,7 +12903,7 @@ namespace ts {
function getParentSymbolOfTypeParameter(typeParameter: TypeParameter): Symbol | undefined {
const tp = getDeclarationOfKind<TypeParameterDeclaration>(typeParameter.symbol, SyntaxKind.TypeParameter)!;
const host = isJSDocTemplateTag(tp.parent) ? getHostSignatureFromJSDoc(tp.parent) : tp.parent;
const host = isJSDocTemplateTag(tp.parent) ? getEffectiveContainerForJSDocTemplateTag(tp.parent) : tp.parent;
return host && getSymbolOfNode(host);
}
@ -33673,7 +33675,7 @@ namespace ts {
}
}
checkTypeParameters(node.typeParameters);
checkTypeParameters(getEffectiveTypeParameterDeclarations(node));
forEach(node.parameters, checkParameter);
@ -35306,6 +35308,7 @@ namespace ts {
checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0);
}
checkSourceElement(node.typeExpression);
checkTypeParameters(getEffectiveTypeParameterDeclarations(node));
}
function checkJSDocTemplateTag(node: JSDocTemplateTag): void {

View File

@ -8441,11 +8441,24 @@ namespace ts {
function parseTemplateTagTypeParameter() {
const typeParameterPos = getNodePos();
const isBracketed = parseOptionalJsdoc(SyntaxKind.OpenBracketToken);
if (isBracketed) {
skipWhitespace();
}
const name = parseJSDocIdentifierName(Diagnostics.Unexpected_token_A_type_parameter_name_was_expected_without_curly_braces);
let defaultType: TypeNode | undefined;
if (isBracketed) {
skipWhitespace();
parseExpected(SyntaxKind.EqualsToken);
defaultType = doInsideOfContext(NodeFlags.JSDoc, parseJSDocType);
parseExpected(SyntaxKind.CloseBracketToken);
}
if (nodeIsMissing(name)) {
return undefined;
}
return finishNode(factory.createTypeParameterDeclaration(name, /*constraint*/ undefined, /*defaultType*/ undefined), typeParameterPos);
return finishNode(factory.createTypeParameterDeclaration(name, /*constraint*/ undefined, defaultType), typeParameterPos);
}
function parseTemplateTagTypeParameters() {

View File

@ -2723,6 +2723,18 @@ namespace ts {
return parameter && parameter.symbol;
}
export function getEffectiveContainerForJSDocTemplateTag(node: JSDocTemplateTag) {
if (isJSDoc(node.parent) && node.parent.tags) {
// A @template tag belongs to any @typedef, @callback, or @enum tags in the same comment block, if they exist.
const typeAlias = find(node.parent.tags, isJSDocTypeAlias);
if (typeAlias) {
return typeAlias;
}
}
// otherwise it belongs to the host it annotates
return getHostSignatureFromJSDoc(node);
}
export function getHostSignatureFromJSDoc(node: Node): SignatureDeclaration | undefined {
const host = getEffectiveJSDocHost(node);
return host && isFunctionLike(host) ? host : undefined;

View File

@ -0,0 +1,90 @@
tests/cases/conformance/jsdoc/file.js(9,20): error TS2322: Type 'number' is not assignable to type 'string'.
tests/cases/conformance/jsdoc/file.js(22,34): error TS1005: '=' expected.
tests/cases/conformance/jsdoc/file.js(27,35): error TS1110: Type expected.
tests/cases/conformance/jsdoc/file.js(33,14): error TS2706: Required type parameters may not follow optional type parameters.
tests/cases/conformance/jsdoc/file.js(38,17): error TS2744: Type parameter defaults can only reference previously declared type parameters.
tests/cases/conformance/jsdoc/file.js(53,14): error TS2706: Required type parameters may not follow optional type parameters.
tests/cases/conformance/jsdoc/file.js(60,17): error TS2744: Type parameter defaults can only reference previously declared type parameters.
==== tests/cases/conformance/jsdoc/file.js (7 errors) ====
/**
* @template {string | number} [T=string] - ok: defaults are permitted
* @typedef {[T]} A
*/
/** @type {A} */ // ok, default for `T` in `A` is `string`
const aDefault1 = [""];
/** @type {A} */ // error: `number` is not assignable to string`
const aDefault2 = [0];
~
!!! error TS2322: Type 'number' is not assignable to type 'string'.
/** @type {A<string>} */ // ok, `T` is provided for `A`
const aString = [""];
/** @type {A<number>} */ // ok, `T` is provided for `A`
const aNumber = [0];
/**
* @template T
* @template [U=T] - ok: default can reference earlier type parameter
* @typedef {[T, U]} B
*/
/**
* @template {string | number} [T] - error: default requires an `=type`
~
!!! error TS1005: '=' expected.
* @typedef {[T]} C
*/
/**
* @template {string | number} [T=] - error: default requires a `type`
~
!!! error TS1110: Type expected.
* @typedef {[T]} D
*/
/**
* @template {string | number} [T=string]
* @template U - error: Required type parameters cannot follow optional type parameters
~
!!! error TS2706: Required type parameters may not follow optional type parameters.
* @typedef {[T, U]} E
*/
/**
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
~
!!! error TS2744: Type parameter defaults can only reference previously declared type parameters.
* @template [U=T]
* @typedef {[T, U]} G
*/
/**
* @template T
* @template [U=T] - ok: default can reference earlier type parameter
* @param {T} a
* @param {U} b
*/
function f1(a, b) {}
/**
* @template {string | number} [T=string]
* @template U - error: Required type parameters cannot follow optional type parameters
~
!!! error TS2706: Required type parameters may not follow optional type parameters.
* @param {T} a
* @param {U} b
*/
function f2(a, b) {}
/**
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
~
!!! error TS2744: Type parameter defaults can only reference previously declared type parameters.
* @template [U=T]
* @param {T} a
* @param {U} b
*/
function f3(a, b) {}

View File

@ -0,0 +1,186 @@
//// [file.js]
/**
* @template {string | number} [T=string] - ok: defaults are permitted
* @typedef {[T]} A
*/
/** @type {A} */ // ok, default for `T` in `A` is `string`
const aDefault1 = [""];
/** @type {A} */ // error: `number` is not assignable to string`
const aDefault2 = [0];
/** @type {A<string>} */ // ok, `T` is provided for `A`
const aString = [""];
/** @type {A<number>} */ // ok, `T` is provided for `A`
const aNumber = [0];
/**
* @template T
* @template [U=T] - ok: default can reference earlier type parameter
* @typedef {[T, U]} B
*/
/**
* @template {string | number} [T] - error: default requires an `=type`
* @typedef {[T]} C
*/
/**
* @template {string | number} [T=] - error: default requires a `type`
* @typedef {[T]} D
*/
/**
* @template {string | number} [T=string]
* @template U - error: Required type parameters cannot follow optional type parameters
* @typedef {[T, U]} E
*/
/**
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
* @template [U=T]
* @typedef {[T, U]} G
*/
/**
* @template T
* @template [U=T] - ok: default can reference earlier type parameter
* @param {T} a
* @param {U} b
*/
function f1(a, b) {}
/**
* @template {string | number} [T=string]
* @template U - error: Required type parameters cannot follow optional type parameters
* @param {T} a
* @param {U} b
*/
function f2(a, b) {}
/**
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
* @template [U=T]
* @param {T} a
* @param {U} b
*/
function f3(a, b) {}
//// [file.js]
/**
* @template {string | number} [T=string] - ok: defaults are permitted
* @typedef {[T]} A
*/
/** @type {A} */ // ok, default for `T` in `A` is `string`
var aDefault1 = [""];
/** @type {A} */ // error: `number` is not assignable to string`
var aDefault2 = [0];
/** @type {A<string>} */ // ok, `T` is provided for `A`
var aString = [""];
/** @type {A<number>} */ // ok, `T` is provided for `A`
var aNumber = [0];
/**
* @template T
* @template [U=T] - ok: default can reference earlier type parameter
* @typedef {[T, U]} B
*/
/**
* @template {string | number} [T] - error: default requires an `=type`
* @typedef {[T]} C
*/
/**
* @template {string | number} [T=] - error: default requires a `type`
* @typedef {[T]} D
*/
/**
* @template {string | number} [T=string]
* @template U - error: Required type parameters cannot follow optional type parameters
* @typedef {[T, U]} E
*/
/**
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
* @template [U=T]
* @typedef {[T, U]} G
*/
/**
* @template T
* @template [U=T] - ok: default can reference earlier type parameter
* @param {T} a
* @param {U} b
*/
function f1(a, b) { }
/**
* @template {string | number} [T=string]
* @template U - error: Required type parameters cannot follow optional type parameters
* @param {T} a
* @param {U} b
*/
function f2(a, b) { }
/**
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
* @template [U=T]
* @param {T} a
* @param {U} b
*/
function f3(a, b) { }
//// [file.d.ts]
/**
* @template T
* @template [U=T] - ok: default can reference earlier type parameter
* @typedef {[T, U]} B
*/
/**
* @template {string | number} [T] - error: default requires an `=type`
* @typedef {[T]} C
*/
/**
* @template {string | number} [T=] - error: default requires a `type`
* @typedef {[T]} D
*/
/**
* @template {string | number} [T=string]
* @template U - error: Required type parameters cannot follow optional type parameters
* @typedef {[T, U]} E
*/
/**
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
* @template [U=T]
* @typedef {[T, U]} G
*/
/**
* @template T
* @template [U=T] - ok: default can reference earlier type parameter
* @param {T} a
* @param {U} b
*/
declare function f1<T, U = T>(a: T, b: U): void;
/**
* @template {string | number} [T=string]
* @template U - error: Required type parameters cannot follow optional type parameters
* @param {T} a
* @param {U} b
*/
declare function f2<T extends string | number = string, U>(a: T, b: U): void;
/**
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
* @template [U=T]
* @param {T} a
* @param {U} b
*/
declare function f3<T = U, U = T>(a: T, b: U): void;
/**
* @template {string | number} [T=string] - ok: defaults are permitted
* @typedef {[T]} A
*/
/** @type {A} */ declare const aDefault1: A<string>;
/** @type {A} */ declare const aDefault2: A<string>;
/** @type {A<string>} */ declare const aString: A<string>;
/** @type {A<number>} */ declare const aNumber: A<number>;
type B<T, U = T> = [T, U];
type C<T extends string | number = any> = [T];
type D<T extends string | number = any> = [T];
type E<T extends string | number = string, U> = [T, U];
type G<T = U, U = T> = [T, U];
type A<T extends string | number = string> = [T];

View File

@ -0,0 +1,83 @@
=== tests/cases/conformance/jsdoc/file.js ===
/**
* @template {string | number} [T=string] - ok: defaults are permitted
* @typedef {[T]} A
*/
/** @type {A} */ // ok, default for `T` in `A` is `string`
const aDefault1 = [""];
>aDefault1 : Symbol(aDefault1, Decl(file.js, 6, 5))
/** @type {A} */ // error: `number` is not assignable to string`
const aDefault2 = [0];
>aDefault2 : Symbol(aDefault2, Decl(file.js, 8, 5))
/** @type {A<string>} */ // ok, `T` is provided for `A`
const aString = [""];
>aString : Symbol(aString, Decl(file.js, 10, 5))
/** @type {A<number>} */ // ok, `T` is provided for `A`
const aNumber = [0];
>aNumber : Symbol(aNumber, Decl(file.js, 12, 5))
/**
* @template T
* @template [U=T] - ok: default can reference earlier type parameter
* @typedef {[T, U]} B
*/
/**
* @template {string | number} [T] - error: default requires an `=type`
* @typedef {[T]} C
*/
/**
* @template {string | number} [T=] - error: default requires a `type`
* @typedef {[T]} D
*/
/**
* @template {string | number} [T=string]
* @template U - error: Required type parameters cannot follow optional type parameters
* @typedef {[T, U]} E
*/
/**
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
* @template [U=T]
* @typedef {[T, U]} G
*/
/**
* @template T
* @template [U=T] - ok: default can reference earlier type parameter
* @param {T} a
* @param {U} b
*/
function f1(a, b) {}
>f1 : Symbol(f1, Decl(file.js, 12, 20))
>a : Symbol(a, Decl(file.js, 48, 12))
>b : Symbol(b, Decl(file.js, 48, 14))
/**
* @template {string | number} [T=string]
* @template U - error: Required type parameters cannot follow optional type parameters
* @param {T} a
* @param {U} b
*/
function f2(a, b) {}
>f2 : Symbol(f2, Decl(file.js, 48, 20))
>a : Symbol(a, Decl(file.js, 56, 12))
>b : Symbol(b, Decl(file.js, 56, 14))
/**
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
* @template [U=T]
* @param {T} a
* @param {U} b
*/
function f3(a, b) {}
>f3 : Symbol(f3, Decl(file.js, 56, 20))
>a : Symbol(a, Decl(file.js, 64, 12))
>b : Symbol(b, Decl(file.js, 64, 14))

View File

@ -0,0 +1,91 @@
=== tests/cases/conformance/jsdoc/file.js ===
/**
* @template {string | number} [T=string] - ok: defaults are permitted
* @typedef {[T]} A
*/
/** @type {A} */ // ok, default for `T` in `A` is `string`
const aDefault1 = [""];
>aDefault1 : A<string>
>[""] : [string]
>"" : ""
/** @type {A} */ // error: `number` is not assignable to string`
const aDefault2 = [0];
>aDefault2 : A<string>
>[0] : [number]
>0 : 0
/** @type {A<string>} */ // ok, `T` is provided for `A`
const aString = [""];
>aString : A<string>
>[""] : [string]
>"" : ""
/** @type {A<number>} */ // ok, `T` is provided for `A`
const aNumber = [0];
>aNumber : A<number>
>[0] : [number]
>0 : 0
/**
* @template T
* @template [U=T] - ok: default can reference earlier type parameter
* @typedef {[T, U]} B
*/
/**
* @template {string | number} [T] - error: default requires an `=type`
* @typedef {[T]} C
*/
/**
* @template {string | number} [T=] - error: default requires a `type`
* @typedef {[T]} D
*/
/**
* @template {string | number} [T=string]
* @template U - error: Required type parameters cannot follow optional type parameters
* @typedef {[T, U]} E
*/
/**
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
* @template [U=T]
* @typedef {[T, U]} G
*/
/**
* @template T
* @template [U=T] - ok: default can reference earlier type parameter
* @param {T} a
* @param {U} b
*/
function f1(a, b) {}
>f1 : <T, U = T>(a: T, b: U) => void
>a : T
>b : U
/**
* @template {string | number} [T=string]
* @template U - error: Required type parameters cannot follow optional type parameters
* @param {T} a
* @param {U} b
*/
function f2(a, b) {}
>f2 : <T extends string | number = string, U>(a: T, b: U) => void
>a : T
>b : U
/**
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
* @template [U=T]
* @param {T} a
* @param {U} b
*/
function f3(a, b) {}
>f3 : <T = U, U = T>(a: T, b: U) => void
>a : T
>b : U

View File

@ -0,0 +1,16 @@
tests/cases/conformance/jsdoc/file.js(10,7): error TS2322: Type 'string' is not assignable to type 'number'.
==== tests/cases/conformance/jsdoc/file.js (1 errors) ====
/**
* @template T
* @template {keyof T} K
* @typedef {T[K]} Foo
*/
const x = { a: 1 };
/** @type {Foo<typeof x, "a">} */
const y = "a";
~
!!! error TS2322: Type 'string' is not assignable to type 'number'.

View File

@ -0,0 +1,30 @@
//// [file.js]
/**
* @template T
* @template {keyof T} K
* @typedef {T[K]} Foo
*/
const x = { a: 1 };
/** @type {Foo<typeof x, "a">} */
const y = "a";
//// [file.js]
/**
* @template T
* @template {keyof T} K
* @typedef {T[K]} Foo
*/
var x = { a: 1 };
/** @type {Foo<typeof x, "a">} */
var y = "a";
//// [file.d.ts]
declare namespace x {
const a: number;
}
/** @type {Foo<typeof x, "a">} */
declare const y: Foo<typeof x, "a">;
type Foo<T, K extends keyof T> = T[K];

View File

@ -0,0 +1,15 @@
=== tests/cases/conformance/jsdoc/file.js ===
/**
* @template T
* @template {keyof T} K
* @typedef {T[K]} Foo
*/
const x = { a: 1 };
>x : Symbol(x, Decl(file.js, 6, 5))
>a : Symbol(a, Decl(file.js, 6, 11))
/** @type {Foo<typeof x, "a">} */
const y = "a";
>y : Symbol(y, Decl(file.js, 9, 5))

View File

@ -0,0 +1,18 @@
=== tests/cases/conformance/jsdoc/file.js ===
/**
* @template T
* @template {keyof T} K
* @typedef {T[K]} Foo
*/
const x = { a: 1 };
>x : { a: number; }
>{ a: 1 } : { a: number; }
>a : number
>1 : 1
/** @type {Foo<typeof x, "a">} */
const y = "a";
>y : number
>"a" : "a"

View File

@ -0,0 +1,71 @@
// @allowJs: true
// @checkJs: true
// @declaration: true
// @outDir: out
// @Filename: file.js
/**
* @template {string | number} [T=string] - ok: defaults are permitted
* @typedef {[T]} A
*/
/** @type {A} */ // ok, default for `T` in `A` is `string`
const aDefault1 = [""];
/** @type {A} */ // error: `number` is not assignable to string`
const aDefault2 = [0];
/** @type {A<string>} */ // ok, `T` is provided for `A`
const aString = [""];
/** @type {A<number>} */ // ok, `T` is provided for `A`
const aNumber = [0];
/**
* @template T
* @template [U=T] - ok: default can reference earlier type parameter
* @typedef {[T, U]} B
*/
/**
* @template {string | number} [T] - error: default requires an `=type`
* @typedef {[T]} C
*/
/**
* @template {string | number} [T=] - error: default requires a `type`
* @typedef {[T]} D
*/
/**
* @template {string | number} [T=string]
* @template U - error: Required type parameters cannot follow optional type parameters
* @typedef {[T, U]} E
*/
/**
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
* @template [U=T]
* @typedef {[T, U]} G
*/
/**
* @template T
* @template [U=T] - ok: default can reference earlier type parameter
* @param {T} a
* @param {U} b
*/
function f1(a, b) {}
/**
* @template {string | number} [T=string]
* @template U - error: Required type parameters cannot follow optional type parameters
* @param {T} a
* @param {U} b
*/
function f2(a, b) {}
/**
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
* @template [U=T]
* @param {T} a
* @param {U} b
*/
function f3(a, b) {}

View File

@ -0,0 +1,16 @@
// @allowJs: true
// @checkJs: true
// @outDir: out
// @declaration: true
// @Filename: file.js
/**
* @template T
* @template {keyof T} K
* @typedef {T[K]} Foo
*/
const x = { a: 1 };
/** @type {Foo<typeof x, "a">} */
const y = "a";