From 8d969a23cba2cec9ce3baa5e1cfaafda2c7c0301 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 19 Apr 2018 15:58:43 -0700 Subject: [PATCH] In JS, class supports `@template` tag for declaring type parameters (#23511) * Support @template as a class type parameter Still need to do the following: 1. Correctly get jsdoc host in predicate. 2. Make this work for constructor functions too. 3. Scan rest of codebase for other usages of the type parameters property that should be calls to getEffectiveTypeParameterDeclarations. 4. Rename tp to something more readable, like typar or ts'. * Use jsdoc host declaration to find container * Longer names for type parameters * Fix renaming operation * Update fourslash test * Support @template for JS constructors * Look for both outer and tag type parameters * Improve naming to improve code clarity --- src/compiler/checker.ts | 72 +++++++++++------- src/compiler/utilities.ts | 4 +- .../reference/jsdocTemplateClass.errors.txt | 31 ++++++++ .../reference/jsdocTemplateClass.symbols | 54 +++++++++++++ .../reference/jsdocTemplateClass.types | 62 +++++++++++++++ ...sdocTemplateConstructorFunction.errors.txt | 28 +++++++ .../jsdocTemplateConstructorFunction.symbols | 57 ++++++++++++++ .../jsdocTemplateConstructorFunction.types | 76 +++++++++++++++++++ .../conformance/jsdoc/jsdocTemplateClass.ts | 29 +++++++ .../jsdoc/jsdocTemplateConstructorFunction.ts | 26 +++++++ .../findAllRefsJsDocTemplateTag_class_js.ts | 5 +- 11 files changed, 413 insertions(+), 31 deletions(-) create mode 100644 tests/baselines/reference/jsdocTemplateClass.errors.txt create mode 100644 tests/baselines/reference/jsdocTemplateClass.symbols create mode 100644 tests/baselines/reference/jsdocTemplateClass.types create mode 100644 tests/baselines/reference/jsdocTemplateConstructorFunction.errors.txt create mode 100644 tests/baselines/reference/jsdocTemplateConstructorFunction.symbols create mode 100644 tests/baselines/reference/jsdocTemplateConstructorFunction.types create mode 100644 tests/cases/conformance/jsdoc/jsdocTemplateClass.ts create mode 100644 tests/cases/conformance/jsdoc/jsdocTemplateConstructorFunction.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6c8a79daf5f..bfc9c1fd550 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1554,7 +1554,8 @@ namespace ts { function isTypeParameterSymbolDeclaredInContainer(symbol: Symbol, container: Node) { for (const decl of symbol.declarations) { - if (decl.kind === SyntaxKind.TypeParameter && decl.parent === container) { + const parent = isJSDocTemplateTag(decl.parent) ? getJSDocHost(decl.parent) : decl.parent; + if (decl.kind === SyntaxKind.TypeParameter && parent === container) { return true; } } @@ -2060,10 +2061,10 @@ namespace ts { let symbol: Symbol; if (name.kind === SyntaxKind.Identifier) { const message = meaning === namespaceMeaning ? Diagnostics.Cannot_find_namespace_0 : Diagnostics.Cannot_find_name_0; - - symbol = resolveName(location || name, name.escapedText, meaning, ignoreErrors ? undefined : message, name, /*isUse*/ true); + const symbolFromJSPrototype = isInJavaScriptFile(name) && resolveEntityNameFromJSPrototype(name, meaning); + symbol = resolveName(location || name, name.escapedText, meaning, ignoreErrors || symbolFromJSPrototype ? undefined : message, name, /*isUse*/ true); if (!symbol) { - return undefined; + return symbolFromJSPrototype; } } else if (name.kind === SyntaxKind.QualifiedName || name.kind === SyntaxKind.PropertyAccessExpression) { @@ -2114,6 +2115,18 @@ namespace ts { return (symbol.flags & meaning) || dontResolveAlias ? symbol : resolveAlias(symbol); } + function resolveEntityNameFromJSPrototype(name: Identifier, meaning: SymbolFlags) { + if (isJSDocTypeReference(name.parent) && isJSDocTag(name.parent.parent.parent)) { + const host = getJSDocHost(name.parent.parent.parent as JSDocTag); + if (isExpressionStatement(host) && + isBinaryExpression(host.expression) && + getSpecialPropertyAssignmentKind(host.expression) === SpecialPropertyAssignmentKind.PrototypeProperty) { + const secondaryLocation = getSymbolOfNode(host.expression.left).parent.valueDeclaration; + return resolveName(secondaryLocation, name.escapedText, meaning, /*nameNotFoundMessage*/ undefined, name, /*isUse*/ true); + } + } + } + function resolveExternalModuleName(location: Node, moduleReferenceExpression: Expression): Symbol { return resolveExternalModuleNameWorker(location, moduleReferenceExpression, Diagnostics.Cannot_find_module_0); } @@ -4897,8 +4910,7 @@ namespace ts { // in-place and returns the same array. function appendTypeParameters(typeParameters: TypeParameter[], declarations: ReadonlyArray): TypeParameter[] { for (const declaration of declarations) { - const tp = getDeclaredTypeOfTypeParameter(getSymbolOfNode(declaration)); - typeParameters = appendIfUnique(typeParameters, tp); + typeParameters = appendIfUnique(typeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode(declaration))); } return typeParameters; } @@ -4958,8 +4970,9 @@ namespace ts { if (node.kind === SyntaxKind.InterfaceDeclaration || node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.TypeAliasDeclaration) { const declaration = node; - if (declaration.typeParameters) { - result = appendTypeParameters(result, declaration.typeParameters); + const typeParameters = getEffectiveTypeParameterDeclarations(declaration); + if (typeParameters) { + result = appendTypeParameters(result, typeParameters); } } } @@ -5455,9 +5468,10 @@ namespace ts { */ function isThislessFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean { const returnType = getEffectiveReturnTypeNode(node); + const typeParameters = getEffectiveTypeParameterDeclarations(node); return (node.kind === SyntaxKind.Constructor || (returnType && isThislessType(returnType))) && node.parameters.every(isThislessVariableLikeDeclaration) && - (!node.typeParameters || node.typeParameters.every(isThislessTypeParameter)); + (!typeParameters || typeParameters.every(isThislessTypeParameter)); } /** @@ -6735,8 +6749,7 @@ namespace ts { function getTypeParametersFromDeclaration(declaration: DeclarationWithTypeParameters): TypeParameter[] { let result: TypeParameter[]; forEach(getEffectiveTypeParameterDeclarations(declaration), node => { - const tp = getDeclaredTypeOfTypeParameter(node.symbol); - result = appendIfUnique(result, tp); + result = appendIfUnique(result, getDeclaredTypeOfTypeParameter(node.symbol)); }); return result; } @@ -7547,7 +7560,7 @@ namespace ts { return constraints ? getSubstitutionType(typeVariable, getIntersectionType(append(constraints, typeVariable))) : typeVariable; } - function isJSDocTypeReference(node: NodeWithTypeArguments): node is TypeReferenceNode { + function isJSDocTypeReference(node: Node): node is TypeReferenceNode { return node.flags & NodeFlags.JSDoc && node.kind === SyntaxKind.TypeReference; } @@ -9170,10 +9183,15 @@ namespace ts { // aren't the right hand side of a generic type alias declaration we optimize by reducing the // set of type parameters to those that are possibly referenced in the literal. const declaration = symbol.declarations[0]; - const outerTypeParameters = getOuterTypeParameters(declaration, /*includeThisTypes*/ true) || emptyArray; + let outerTypeParameters = getOuterTypeParameters(declaration, /*includeThisTypes*/ true); + if (isJavaScriptConstructor(declaration)) { + const templateTagParameters = getTypeParametersFromDeclaration(declaration as DeclarationWithTypeParameters); + outerTypeParameters = addRange(outerTypeParameters, templateTagParameters); + } + typeParameters = outerTypeParameters || emptyArray; typeParameters = symbol.flags & SymbolFlags.TypeLiteral && !target.aliasTypeArguments ? - filter(outerTypeParameters, tp => isTypeParameterPossiblyReferenced(tp, declaration)) : - outerTypeParameters; + filter(typeParameters, tp => isTypeParameterPossiblyReferenced(tp, declaration)) : + typeParameters; links.outerTypeParameters = typeParameters; if (typeParameters.length) { links.instantiations = createMap(); @@ -18533,7 +18551,7 @@ namespace ts { } const type = funcSymbol && getJavaScriptClassType(funcSymbol); if (type) { - return type; + return signature.target ? instantiateType(type, signature.mapper) : type; } if (noImplicitAny) { error(node, Diagnostics.new_expression_whose_target_lacks_a_construct_signature_implicitly_has_an_any_type); @@ -22161,8 +22179,9 @@ namespace ts { ): void { // Only report errors on the last declaration for the type parameter container; // this ensures that all uses have been accounted for. - if (!(node.flags & NodeFlags.Ambient) && node.typeParameters && last(getSymbolOfNode(node)!.declarations) === node) { - for (const typeParameter of node.typeParameters) { + const typeParameters = getEffectiveTypeParameterDeclarations(node); + if (!(node.flags & NodeFlags.Ambient) && typeParameters && last(getSymbolOfNode(node)!.declarations) === node) { + for (const typeParameter of typeParameters) { if (!(getMergedSymbol(typeParameter.symbol).isReferenced & SymbolFlags.TypeParameter) && !isIdentifierThatStartsWithUnderScore(typeParameter.name)) { addDiagnostic(UnusedKind.Parameter, createDiagnosticForNode(typeParameter.name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(typeParameter.symbol))); } @@ -23536,20 +23555,21 @@ namespace ts { } } - function areTypeParametersIdentical(declarations: ReadonlyArray, typeParameters: TypeParameter[]) { - const maxTypeArgumentCount = length(typeParameters); - const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters); + function areTypeParametersIdentical(declarations: ReadonlyArray, targetParameters: TypeParameter[]) { + const maxTypeArgumentCount = length(targetParameters); + const minTypeArgumentCount = getMinTypeArgumentCount(targetParameters); for (const declaration of declarations) { // If this declaration has too few or too many type parameters, we report an error - const numTypeParameters = length(declaration.typeParameters); + const sourceParameters = getEffectiveTypeParameterDeclarations(declaration); + const numTypeParameters = length(sourceParameters); if (numTypeParameters < minTypeArgumentCount || numTypeParameters > maxTypeArgumentCount) { return false; } for (let i = 0; i < numTypeParameters; i++) { - const source = declaration.typeParameters[i]; - const target = typeParameters[i]; + const source = sourceParameters[i]; + const target = targetParameters[i]; // If the type parameter node does not have the same as the resolved type // parameter at this position, we report an error. @@ -23610,7 +23630,7 @@ namespace ts { checkCollisionWithRequireExportsInGeneratedCode(node, node.name); checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name); } - checkTypeParameters(node.typeParameters); + checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); checkExportsOnMergedDeclarations(node); const symbol = getSymbolOfNode(node); const type = getDeclaredTypeOfSymbol(symbol); @@ -26846,7 +26866,7 @@ namespace ts { function checkGrammarClassLikeDeclaration(node: ClassLikeDeclaration): boolean { const file = getSourceFileOfNode(node); - return checkGrammarClassDeclarationHeritageClauses(node) || checkGrammarTypeParameterList(node.typeParameters, file); + return checkGrammarClassDeclarationHeritageClauses(node) || checkGrammarTypeParameterList(getEffectiveTypeParameterDeclarations(node), file); } function checkGrammarArrowFunction(node: Node, file: SourceFile): boolean { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 3fe255fb612..730c3fc51d4 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3055,11 +3055,11 @@ namespace ts { * Gets the effective type parameters. If the node was parsed in a * JavaScript file, gets the type parameters from the `@template` tag from JSDoc. */ - export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters): ReadonlyArray | undefined { + export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters) { return node.typeParameters || (isInJavaScriptFile(node) ? getJSDocTypeParameterDeclarations(node) : undefined); } - export function getJSDocTypeParameterDeclarations(node: DeclarationWithTypeParameters): ReadonlyArray { + export function getJSDocTypeParameterDeclarations(node: DeclarationWithTypeParameters) { const templateTag = getJSDocTemplateTag(node); return templateTag && templateTag.typeParameters; } diff --git a/tests/baselines/reference/jsdocTemplateClass.errors.txt b/tests/baselines/reference/jsdocTemplateClass.errors.txt new file mode 100644 index 00000000000..4598e9f320f --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateClass.errors.txt @@ -0,0 +1,31 @@ +tests/cases/conformance/jsdoc/templateTagOnClasses.js(24,1): error TS2322: Type 'boolean' is not assignable to type 'number'. + + +==== tests/cases/conformance/jsdoc/templateTagOnClasses.js (1 errors) ==== + /** + * @template {T} + * @typedef {(t: T) => T} Id + */ + class Foo { + /** @typedef {(t: T) => T} Id2 */ + /** @param {T} x */ + constructor (x) { + this.a = x + } + /** + * + * @param {T} x + * @param {Id} y + * @param {Id2} alpha + * @return {T} + */ + foo(x, y, alpha) { + return alpha(y(x)) + } + } + var f = new Foo(1) + var g = new Foo(false) + f.a = g.a + ~~~ +!!! error TS2322: Type 'boolean' is not assignable to type 'number'. + \ No newline at end of file diff --git a/tests/baselines/reference/jsdocTemplateClass.symbols b/tests/baselines/reference/jsdocTemplateClass.symbols new file mode 100644 index 00000000000..d80b73bc4ac --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateClass.symbols @@ -0,0 +1,54 @@ +=== tests/cases/conformance/jsdoc/templateTagOnClasses.js === +/** + * @template {T} + * @typedef {(t: T) => T} Id + */ +class Foo { +>Foo : Symbol(Foo, Decl(templateTagOnClasses.js, 0, 0)) + + /** @typedef {(t: T) => T} Id2 */ + /** @param {T} x */ + constructor (x) { +>x : Symbol(x, Decl(templateTagOnClasses.js, 7, 17)) + + this.a = x +>this.a : Symbol(Foo.a, Decl(templateTagOnClasses.js, 7, 21)) +>this : Symbol(Foo, Decl(templateTagOnClasses.js, 0, 0)) +>a : Symbol(Foo.a, Decl(templateTagOnClasses.js, 7, 21)) +>x : Symbol(x, Decl(templateTagOnClasses.js, 7, 17)) + } + /** + * + * @param {T} x + * @param {Id} y + * @param {Id2} alpha + * @return {T} + */ + foo(x, y, alpha) { +>foo : Symbol(Foo.foo, Decl(templateTagOnClasses.js, 9, 5)) +>x : Symbol(x, Decl(templateTagOnClasses.js, 17, 8)) +>y : Symbol(y, Decl(templateTagOnClasses.js, 17, 10)) +>alpha : Symbol(alpha, Decl(templateTagOnClasses.js, 17, 13)) + + return alpha(y(x)) +>alpha : Symbol(alpha, Decl(templateTagOnClasses.js, 17, 13)) +>y : Symbol(y, Decl(templateTagOnClasses.js, 17, 10)) +>x : Symbol(x, Decl(templateTagOnClasses.js, 17, 8)) + } +} +var f = new Foo(1) +>f : Symbol(f, Decl(templateTagOnClasses.js, 21, 3)) +>Foo : Symbol(Foo, Decl(templateTagOnClasses.js, 0, 0)) + +var g = new Foo(false) +>g : Symbol(g, Decl(templateTagOnClasses.js, 22, 3)) +>Foo : Symbol(Foo, Decl(templateTagOnClasses.js, 0, 0)) + +f.a = g.a +>f.a : Symbol(Foo.a, Decl(templateTagOnClasses.js, 7, 21)) +>f : Symbol(f, Decl(templateTagOnClasses.js, 21, 3)) +>a : Symbol(Foo.a, Decl(templateTagOnClasses.js, 7, 21)) +>g.a : Symbol(Foo.a, Decl(templateTagOnClasses.js, 7, 21)) +>g : Symbol(g, Decl(templateTagOnClasses.js, 22, 3)) +>a : Symbol(Foo.a, Decl(templateTagOnClasses.js, 7, 21)) + diff --git a/tests/baselines/reference/jsdocTemplateClass.types b/tests/baselines/reference/jsdocTemplateClass.types new file mode 100644 index 00000000000..aa6a685e34f --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateClass.types @@ -0,0 +1,62 @@ +=== tests/cases/conformance/jsdoc/templateTagOnClasses.js === +/** + * @template {T} + * @typedef {(t: T) => T} Id + */ +class Foo { +>Foo : Foo + + /** @typedef {(t: T) => T} Id2 */ + /** @param {T} x */ + constructor (x) { +>x : T + + this.a = x +>this.a = x : T +>this.a : T +>this : this +>a : T +>x : T + } + /** + * + * @param {T} x + * @param {Id} y + * @param {Id2} alpha + * @return {T} + */ + foo(x, y, alpha) { +>foo : (x: T, y: (t: T) => T, alpha: (t: T) => T) => T +>x : T +>y : (t: T) => T +>alpha : (t: T) => T + + return alpha(y(x)) +>alpha(y(x)) : T +>alpha : (t: T) => T +>y(x) : T +>y : (t: T) => T +>x : T + } +} +var f = new Foo(1) +>f : Foo +>new Foo(1) : Foo +>Foo : typeof Foo +>1 : 1 + +var g = new Foo(false) +>g : Foo +>new Foo(false) : Foo +>Foo : typeof Foo +>false : false + +f.a = g.a +>f.a = g.a : boolean +>f.a : number +>f : Foo +>a : number +>g.a : boolean +>g : Foo +>a : boolean + diff --git a/tests/baselines/reference/jsdocTemplateConstructorFunction.errors.txt b/tests/baselines/reference/jsdocTemplateConstructorFunction.errors.txt new file mode 100644 index 00000000000..225d277c4ff --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateConstructorFunction.errors.txt @@ -0,0 +1,28 @@ +tests/cases/conformance/jsdoc/templateTagOnConstructorFunctions.js(21,1): error TS2322: Type 'false' is not assignable to type 'number'. + + +==== tests/cases/conformance/jsdoc/templateTagOnConstructorFunctions.js (1 errors) ==== + /** + * @template {T} + * @typedef {(t: T) => T} Id + * @param {T} t + */ + function Zet(t) { + /** @type {T} */ + this.u + this.t = t + } + /** + * @param {T} v + * @param {Id} id + */ + Zet.prototype.add = function(v, id) { + this.u = v || this.t + return id(this.u) + } + var z = new Zet(1) + z.t = 2 + z.u = false + ~~~ +!!! error TS2322: Type 'false' is not assignable to type 'number'. + \ No newline at end of file diff --git a/tests/baselines/reference/jsdocTemplateConstructorFunction.symbols b/tests/baselines/reference/jsdocTemplateConstructorFunction.symbols new file mode 100644 index 00000000000..8cf2ee99e57 --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateConstructorFunction.symbols @@ -0,0 +1,57 @@ +=== tests/cases/conformance/jsdoc/templateTagOnConstructorFunctions.js === +/** + * @template {T} + * @typedef {(t: T) => T} Id + * @param {T} t + */ +function Zet(t) { +>Zet : Symbol(Zet, Decl(templateTagOnConstructorFunctions.js, 0, 0)) +>t : Symbol(t, Decl(templateTagOnConstructorFunctions.js, 5, 13)) + + /** @type {T} */ + this.u + this.t = t +>t : Symbol(Zet.t, Decl(templateTagOnConstructorFunctions.js, 7, 10)) +>t : Symbol(t, Decl(templateTagOnConstructorFunctions.js, 5, 13)) +} +/** + * @param {T} v + * @param {Id} id + */ +Zet.prototype.add = function(v, id) { +>Zet.prototype : Symbol(Zet.add, Decl(templateTagOnConstructorFunctions.js, 9, 1)) +>Zet : Symbol(Zet, Decl(templateTagOnConstructorFunctions.js, 0, 0)) +>prototype : Symbol(Function.prototype, Decl(lib.d.ts, --, --)) +>add : Symbol(Zet.add, Decl(templateTagOnConstructorFunctions.js, 9, 1)) +>v : Symbol(v, Decl(templateTagOnConstructorFunctions.js, 14, 29)) +>id : Symbol(id, Decl(templateTagOnConstructorFunctions.js, 14, 31)) + + this.u = v || this.t +>this.u : Symbol(Zet.u, Decl(templateTagOnConstructorFunctions.js, 5, 17), Decl(templateTagOnConstructorFunctions.js, 14, 37)) +>this : Symbol(Zet, Decl(templateTagOnConstructorFunctions.js, 0, 0)) +>u : Symbol(Zet.u, Decl(templateTagOnConstructorFunctions.js, 5, 17), Decl(templateTagOnConstructorFunctions.js, 14, 37)) +>v : Symbol(v, Decl(templateTagOnConstructorFunctions.js, 14, 29)) +>this.t : Symbol(Zet.t, Decl(templateTagOnConstructorFunctions.js, 7, 10)) +>this : Symbol(Zet, Decl(templateTagOnConstructorFunctions.js, 0, 0)) +>t : Symbol(Zet.t, Decl(templateTagOnConstructorFunctions.js, 7, 10)) + + return id(this.u) +>id : Symbol(id, Decl(templateTagOnConstructorFunctions.js, 14, 31)) +>this.u : Symbol(Zet.u, Decl(templateTagOnConstructorFunctions.js, 5, 17), Decl(templateTagOnConstructorFunctions.js, 14, 37)) +>this : Symbol(Zet, Decl(templateTagOnConstructorFunctions.js, 0, 0)) +>u : Symbol(Zet.u, Decl(templateTagOnConstructorFunctions.js, 5, 17), Decl(templateTagOnConstructorFunctions.js, 14, 37)) +} +var z = new Zet(1) +>z : Symbol(z, Decl(templateTagOnConstructorFunctions.js, 18, 3)) +>Zet : Symbol(Zet, Decl(templateTagOnConstructorFunctions.js, 0, 0)) + +z.t = 2 +>z.t : Symbol(Zet.t, Decl(templateTagOnConstructorFunctions.js, 7, 10)) +>z : Symbol(z, Decl(templateTagOnConstructorFunctions.js, 18, 3)) +>t : Symbol(Zet.t, Decl(templateTagOnConstructorFunctions.js, 7, 10)) + +z.u = false +>z.u : Symbol(Zet.u, Decl(templateTagOnConstructorFunctions.js, 5, 17), Decl(templateTagOnConstructorFunctions.js, 14, 37)) +>z : Symbol(z, Decl(templateTagOnConstructorFunctions.js, 18, 3)) +>u : Symbol(Zet.u, Decl(templateTagOnConstructorFunctions.js, 5, 17), Decl(templateTagOnConstructorFunctions.js, 14, 37)) + diff --git a/tests/baselines/reference/jsdocTemplateConstructorFunction.types b/tests/baselines/reference/jsdocTemplateConstructorFunction.types new file mode 100644 index 00000000000..f838534aa33 --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateConstructorFunction.types @@ -0,0 +1,76 @@ +=== tests/cases/conformance/jsdoc/templateTagOnConstructorFunctions.js === +/** + * @template {T} + * @typedef {(t: T) => T} Id + * @param {T} t + */ +function Zet(t) { +>Zet : typeof Zet +>t : T + + /** @type {T} */ + this.u +>this.u : any +>this : any +>u : any + + this.t = t +>this.t = t : T +>this.t : any +>this : any +>t : any +>t : T +} +/** + * @param {T} v + * @param {Id} id + */ +Zet.prototype.add = function(v, id) { +>Zet.prototype.add = function(v, id) { this.u = v || this.t return id(this.u)} : (v: T, id: (t: T) => T) => T +>Zet.prototype.add : any +>Zet.prototype : any +>Zet : typeof Zet +>prototype : any +>add : any +>function(v, id) { this.u = v || this.t return id(this.u)} : (v: T, id: (t: T) => T) => T +>v : T +>id : (t: T) => T + + this.u = v || this.t +>this.u = v || this.t : T +>this.u : T +>this : Zet +>u : T +>v || this.t : T +>v : T +>this.t : T +>this : Zet +>t : T + + return id(this.u) +>id(this.u) : T +>id : (t: T) => T +>this.u : T +>this : Zet +>u : T +} +var z = new Zet(1) +>z : typeof Zet +>new Zet(1) : typeof Zet +>Zet : typeof Zet +>1 : 1 + +z.t = 2 +>z.t = 2 : 2 +>z.t : number +>z : typeof Zet +>t : number +>2 : 2 + +z.u = false +>z.u = false : false +>z.u : number +>z : typeof Zet +>u : number +>false : false + diff --git a/tests/cases/conformance/jsdoc/jsdocTemplateClass.ts b/tests/cases/conformance/jsdoc/jsdocTemplateClass.ts new file mode 100644 index 00000000000..5cd0cde89be --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocTemplateClass.ts @@ -0,0 +1,29 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @Filename: templateTagOnClasses.js + +/** + * @template {T} + * @typedef {(t: T) => T} Id + */ +class Foo { + /** @typedef {(t: T) => T} Id2 */ + /** @param {T} x */ + constructor (x) { + this.a = x + } + /** + * + * @param {T} x + * @param {Id} y + * @param {Id2} alpha + * @return {T} + */ + foo(x, y, alpha) { + return alpha(y(x)) + } +} +var f = new Foo(1) +var g = new Foo(false) +f.a = g.a diff --git a/tests/cases/conformance/jsdoc/jsdocTemplateConstructorFunction.ts b/tests/cases/conformance/jsdoc/jsdocTemplateConstructorFunction.ts new file mode 100644 index 00000000000..dc44afe040a --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocTemplateConstructorFunction.ts @@ -0,0 +1,26 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @Filename: templateTagOnConstructorFunctions.js + +/** + * @template {T} + * @typedef {(t: T) => T} Id + * @param {T} t + */ +function Zet(t) { + /** @type {T} */ + this.u + this.t = t +} +/** + * @param {T} v + * @param {Id} id + */ +Zet.prototype.add = function(v, id) { + this.u = v || this.t + return id(this.u) +} +var z = new Zet(1) +z.t = 2 +z.u = false diff --git a/tests/cases/fourslash/findAllRefsJsDocTemplateTag_class_js.ts b/tests/cases/fourslash/findAllRefsJsDocTemplateTag_class_js.ts index 6577cfce15d..8ec2e86fadb 100644 --- a/tests/cases/fourslash/findAllRefsJsDocTemplateTag_class_js.ts +++ b/tests/cases/fourslash/findAllRefsJsDocTemplateTag_class_js.ts @@ -3,15 +3,14 @@ // @allowJs: true // @Filename: /a.js -// TODO: https://github.com/Microsoft/TypeScript/issues/16411 // Both uses of T should be referenced. /////** @template [|{| "isWriteAccess": true, "isDefinition": true |}T|] */ ////class C { //// constructor() { -//// /** @type {T} */ +//// /** @type {[|T|]} */ //// this.x = null; //// } ////} -verify.singleReferenceGroup("(type parameter) T in C"); +verify.singleReferenceGroup("(type parameter) T in C", test.ranges());