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
…45483)

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

* Inline parseBracketNameInTemplateTag

* Update baselines

* Add js declaration emit tests
  • Loading branch information
rbuckton authored Sep 9, 2021
1 parent 8610ff5 commit cf787e9
Show file tree
Hide file tree
Showing 14 changed files with 649 additions and 5 deletions.
2 changes: 1 addition & 1 deletion src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
9 changes: 6 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -33673,7 +33675,7 @@ namespace ts {
}
}

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

forEach(node.parameters, checkParameter);

Expand Down Expand Up @@ -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 {
Expand Down
15 changes: 14 additions & 1 deletion src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
12 changes: 12 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
90 changes: 90 additions & 0 deletions tests/baselines/reference/jsdocTemplateTagDefault.errors.txt
Original file line number Diff line number Diff line change
@@ -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) {}

186 changes: 186 additions & 0 deletions tests/baselines/reference/jsdocTemplateTagDefault.js
Original file line number Diff line number Diff line change
@@ -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];
Loading

0 comments on commit cf787e9

Please sign in to comment.