Skip to content

Commit

Permalink
Fix name resolution in typedef and allow defaults for template tags
Browse files Browse the repository at this point in the history
  • Loading branch information
rbuckton committed Aug 17, 2021
1 parent 339ad92 commit 80fa780
Show file tree
Hide file tree
Showing 13 changed files with 457 additions and 5 deletions.
10 changes: 9 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2045,6 +2045,13 @@ namespace ts {
lastSelfReferenceLocation = location;
}
lastLocation = location;
if (isJSDocTemplateTag(location)) {
const container = location.parent.tags && find(location.parent.tags, isJSDocTypeAlias) || getHostSignatureFromJSDoc(location);
if (container) {
location = container;
continue;
}
}
location = location.parent;
}

Expand Down Expand Up @@ -33458,7 +33465,7 @@ namespace ts {
}
}

checkTypeParameters(node.typeParameters);
checkTypeParameters(getEffectiveTypeParameterDeclarations(node));

forEach(node.parameters, checkParameter);

Expand Down Expand Up @@ -35089,6 +35096,7 @@ namespace ts {
checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0);
}
checkSourceElement(node.typeExpression);
checkTypeParameters(getEffectiveTypeParameterDeclarations(node));
}

function checkJSDocTemplateTag(node: JSDocTemplateTag): void {
Expand Down
20 changes: 18 additions & 2 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8053,6 +8053,22 @@ namespace ts {
return token() === SyntaxKind.OpenBraceToken ? parseJSDocTypeExpression() : undefined;
}

function parseBracketNameInTemplateTag(): { name: Identifier, defaultType?: TypeNode } {
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);
}
return { name, defaultType };
}

function parseBracketNameInPropertyAndParamTag(): { name: EntityName, isBracketed: boolean } {
// Looking for something like '[foo]', 'foo', '[foo.bar]' or 'foo.bar'
const isBracketed = parseOptionalJsdoc(SyntaxKind.OpenBracketToken);
Expand Down Expand Up @@ -8441,11 +8457,11 @@ namespace ts {

function parseTemplateTagTypeParameter() {
const typeParameterPos = getNodePos();
const name = parseJSDocIdentifierName(Diagnostics.Unexpected_token_A_type_parameter_name_was_expected_without_curly_braces);
const { name, defaultType } = parseBracketNameInTemplateTag();
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() {
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3304,6 +3304,7 @@ namespace ts {

export interface JSDocTemplateTag extends JSDocTag {
readonly kind: SyntaxKind.JSDocTemplateTag;
readonly parent: JSDoc;
readonly constraint: JSDocTypeExpression | undefined;
readonly typeParameters: NodeArray<TypeParameterDeclaration>;
}
Expand Down
3 changes: 2 additions & 1 deletion tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1847,6 +1847,7 @@ declare namespace ts {
}
export interface JSDocTemplateTag extends JSDocTag {
readonly kind: SyntaxKind.JSDocTemplateTag;
readonly parent: JSDoc;
readonly constraint: JSDocTypeExpression | undefined;
readonly typeParameters: NodeArray<TypeParameterDeclaration>;
}
Expand Down Expand Up @@ -10993,7 +10994,7 @@ declare namespace ts {
/** @deprecated Use `factory.createJSDocTemplateTag` or the factory supplied by your transformation context instead. */
const createJSDocTemplateTag: (tagName: Identifier | undefined, constraint: JSDocTypeExpression | undefined, typeParameters: readonly TypeParameterDeclaration[], comment?: string | NodeArray<JSDocComment> | undefined) => JSDocTemplateTag;
/** @deprecated Use `factory.createJSDocTypedefTag` or the factory supplied by your transformation context instead. */
const createJSDocTypedefTag: (tagName: Identifier | undefined, typeExpression?: JSDocTypeLiteral | JSDocTypeExpression | undefined, fullName?: Identifier | JSDocNamespaceDeclaration | undefined, comment?: string | NodeArray<JSDocComment> | undefined) => JSDocTypedefTag;
const createJSDocTypedefTag: (tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression | JSDocTypeLiteral | undefined, fullName?: Identifier | JSDocNamespaceDeclaration | undefined, comment?: string | NodeArray<JSDocComment> | undefined) => JSDocTypedefTag;
/** @deprecated Use `factory.createJSDocCallbackTag` or the factory supplied by your transformation context instead. */
const createJSDocCallbackTag: (tagName: Identifier | undefined, typeExpression: JSDocSignature, fullName?: Identifier | JSDocNamespaceDeclaration | undefined, comment?: string | NodeArray<JSDocComment> | undefined) => JSDocCallbackTag;
/** @deprecated Use `factory.createJSDocSignature` or the factory supplied by your transformation context instead. */
Expand Down
3 changes: 2 additions & 1 deletion tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1847,6 +1847,7 @@ declare namespace ts {
}
export interface JSDocTemplateTag extends JSDocTag {
readonly kind: SyntaxKind.JSDocTemplateTag;
readonly parent: JSDoc;
readonly constraint: JSDocTypeExpression | undefined;
readonly typeParameters: NodeArray<TypeParameterDeclaration>;
}
Expand Down Expand Up @@ -7189,7 +7190,7 @@ declare namespace ts {
/** @deprecated Use `factory.createJSDocTemplateTag` or the factory supplied by your transformation context instead. */
const createJSDocTemplateTag: (tagName: Identifier | undefined, constraint: JSDocTypeExpression | undefined, typeParameters: readonly TypeParameterDeclaration[], comment?: string | NodeArray<JSDocComment> | undefined) => JSDocTemplateTag;
/** @deprecated Use `factory.createJSDocTypedefTag` or the factory supplied by your transformation context instead. */
const createJSDocTypedefTag: (tagName: Identifier | undefined, typeExpression?: JSDocTypeLiteral | JSDocTypeExpression | undefined, fullName?: Identifier | JSDocNamespaceDeclaration | undefined, comment?: string | NodeArray<JSDocComment> | undefined) => JSDocTypedefTag;
const createJSDocTypedefTag: (tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression | JSDocTypeLiteral | undefined, fullName?: Identifier | JSDocNamespaceDeclaration | undefined, comment?: string | NodeArray<JSDocComment> | undefined) => JSDocTypedefTag;
/** @deprecated Use `factory.createJSDocCallbackTag` or the factory supplied by your transformation context instead. */
const createJSDocCallbackTag: (tagName: Identifier | undefined, typeExpression: JSDocSignature, fullName?: Identifier | JSDocNamespaceDeclaration | undefined, comment?: string | NodeArray<JSDocComment> | undefined) => JSDocCallbackTag;
/** @deprecated Use `factory.createJSDocSignature` or the factory supplied by your transformation context instead. */
Expand Down
99 changes: 99 additions & 0 deletions tests/baselines/reference/jsdocTemplateTagDefault.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
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(39,14): error TS2706: Required type parameters may not follow optional type parameters.
tests/cases/conformance/jsdoc/file.js(44,17): error TS2744: Type parameter defaults can only reference previously declared type parameters.
tests/cases/conformance/jsdoc/file.js(59,14): error TS2706: Required type parameters may not follow optional type parameters.
tests/cases/conformance/jsdoc/file.js(66,17): error TS2744: Type parameter defaults can only reference previously declared type parameters.


==== tests/cases/conformance/jsdoc/file.js (8 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 {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]} F
*/

/**
* @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) {}

89 changes: 89 additions & 0 deletions tests/baselines/reference/jsdocTemplateTagDefault.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
=== 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 {string | number} [T=string]
* @template U - error: Required type parameters cannot follow optional type parameters
* @typedef {[T, U]} F
*/

/**
* @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, 54, 12))
>b : Symbol(b, Decl(file.js, 54, 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, 54, 20))
>a : Symbol(a, Decl(file.js, 62, 12))
>b : Symbol(b, Decl(file.js, 62, 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, 62, 20))
>a : Symbol(a, Decl(file.js, 70, 12))
>b : Symbol(b, Decl(file.js, 70, 14))

97 changes: 97 additions & 0 deletions tests/baselines/reference/jsdocTemplateTagDefault.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
=== 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 {string | number} [T=string]
* @template U - error: Required type parameters cannot follow optional type parameters
* @typedef {[T, U]} F
*/

/**
* @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

Loading

0 comments on commit 80fa780

Please sign in to comment.