Template tag allows specification of constraints (#24600)

* Parse (and mostly support) template tag constraints

A bunch of tests hit the asserts I added though.

* Messy version is finished. Need to add a few tests

* Refactor to be smaller

* Small refactor + Add one test

* Another test

* Minor cleanup

* Fix error reporting on type parameters on ctors

* Simplify syntax of `@template` tag

This is a breaking change, but in my sample, nobody except webpack used the
erroneous syntax. I need to improve the error message, so
jsdocTemplateTag3 currently fails to remind me of that.

* Better error message for template tag

* Fix fourslash baselines

* Another fourslash update

* Address PR comments

* Simplify getEffectiveTypeParameterDeclarations

Make checkGrammarConstructorTypeParameters do a little more work
This commit is contained in:
Nathan Shively-Sanders 2018-06-04 11:42:46 -07:00 committed by GitHub
parent 2ce7e5f0cd
commit 34e68efdae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 412 additions and 57 deletions

View File

@ -28421,9 +28421,9 @@ namespace ts {
}
function checkGrammarConstructorTypeParameters(node: ConstructorDeclaration) {
const typeParameters = getEffectiveTypeParameterDeclarations(node);
if (isNodeArray(typeParameters)) {
const { pos, end } = typeParameters;
const jsdocTypeParameters = isInJavaScriptFile(node) && getJSDocTypeParameterDeclarations(node);
if (node.typeParameters || jsdocTypeParameters && jsdocTypeParameters.length) {
const { pos, end } = node.typeParameters || jsdocTypeParameters && jsdocTypeParameters[0] || node;
return grammarErrorAtPos(node, pos, end - pos, Diagnostics.Type_parameters_cannot_appear_on_a_constructor_declaration);
}
}

View File

@ -219,6 +219,10 @@
"category": "Error",
"code": 1068
},
"Unexpected token. A type parameter name was expected without curly braces.": {
"category": "Error",
"code": 1069
},
"'{0}' modifier cannot appear on a type member.": {
"category": "Error",
"code": 1070

View File

@ -6993,29 +6993,27 @@ namespace ts {
}
function parseTemplateTag(atToken: AtToken, tagName: Identifier): JSDocTemplateTag | undefined {
if (some(tags, isJSDocTemplateTag)) {
parseErrorAt(tagName.pos, scanner.getTokenPos(), Diagnostics._0_tag_already_specified, tagName.escapedText);
// the template tag looks like '@template {Constraint} T,U,V'
let constraint: JSDocTypeExpression | undefined;
if (token() === SyntaxKind.OpenBraceToken) {
constraint = parseJSDocTypeExpression();
skipWhitespace();
}
// Type parameter list looks like '@template T,U,V'
const typeParameters = [];
const typeParametersPos = getNodePos();
while (true) {
const typeParameter = <TypeParameterDeclaration>createNode(SyntaxKind.TypeParameter);
const name = parseJSDocIdentifierNameWithOptionalBraces();
skipWhitespace();
if (!name) {
parseErrorAtPosition(scanner.getStartPos(), 0, Diagnostics.Identifier_expected);
if (!tokenIsIdentifierOrKeyword(token())) {
parseErrorAtCurrentToken(Diagnostics.Unexpected_token_A_type_parameter_name_was_expected_without_curly_braces);
return undefined;
}
typeParameter.name = name;
typeParameter.name = parseJSDocIdentifierName()!;
skipWhitespace();
finishNode(typeParameter);
typeParameters.push(typeParameter);
if (token() === SyntaxKind.CommaToken) {
// need to look for more type parameters
nextJSDocToken();
skipWhitespace();
}
@ -7024,6 +7022,10 @@ namespace ts {
}
}
if (constraint) {
first(typeParameters).constraint = constraint.type;
}
const result = <JSDocTemplateTag>createNode(SyntaxKind.JSDocTemplateTag, atToken.pos);
result.atToken = atToken;
result.tagName = tagName;
@ -7032,15 +7034,6 @@ namespace ts {
return result;
}
function parseJSDocIdentifierNameWithOptionalBraces(): Identifier | undefined {
const parsedBrace = parseOptional(SyntaxKind.OpenBraceToken);
const res = parseJSDocIdentifierName();
if (parsedBrace) {
parseExpected(SyntaxKind.CloseBraceToken);
}
return res;
}
function nextJSDocToken(): JsDocSyntaxKind {
return currentToken = scanner.scanJSDocToken();
}

View File

@ -3164,21 +3164,18 @@ namespace ts {
}
if (isJSDocTypeAlias(node)) {
Debug.assert(node.parent.kind === SyntaxKind.JSDocComment);
const templateTags = flatMap(filter(node.parent.tags, isJSDocTemplateTag), tag => tag.typeParameters) as ReadonlyArray<TypeParameterDeclaration>;
const templateTagNodes = templateTags as NodeArray<TypeParameterDeclaration>;
templateTagNodes.pos = templateTagNodes.length > 0 ? first(templateTagNodes).pos : node.pos;
templateTagNodes.end = templateTagNodes.length > 0 ? last(templateTagNodes).end : node.end;
templateTagNodes.hasTrailingComma = false;
return templateTagNodes;
return flatMap(node.parent.tags, tag => isJSDocTemplateTag(tag) ? tag.typeParameters : undefined) as ReadonlyArray<TypeParameterDeclaration>;
}
return node.typeParameters || (isInJavaScriptFile(node) ? getJSDocTypeParameterDeclarations(node) : emptyArray);
}
export function getJSDocTypeParameterDeclarations(node: DeclarationWithTypeParameters): ReadonlyArray<TypeParameterDeclaration> {
// template tags are only available when a typedef isn't already using them
const tag = find(getJSDocTags(node), (tag): tag is JSDocTemplateTag =>
isJSDocTemplateTag(tag) && !(tag.parent.kind === SyntaxKind.JSDocComment && tag.parent.tags!.some(isJSDocTypeAlias)));
return (tag && tag.typeParameters) || emptyArray;
return flatMap(getJSDocTags(node), tag => isNonTypeAliasTemplate(tag) ? tag.typeParameters : undefined);
}
/** template tags are only available when a typedef isn't already using them */
function isNonTypeAliasTemplate(tag: JSDocTag): tag is JSDocTemplateTag {
return isJSDocTemplateTag(tag) && !(tag.parent.kind === SyntaxKind.JSDocComment && tag.parent.tags!.some(isJSDocTypeAlias));
}
/**

View File

@ -3,7 +3,7 @@ tests/cases/conformance/jsdoc/templateTagOnClasses.js(25,1): error TS2322: Type
==== tests/cases/conformance/jsdoc/templateTagOnClasses.js (1 errors) ====
/**
* @template {T}
* @template T
* @typedef {(t: T) => T} Id
*/
/** @template T */

View File

@ -1,6 +1,6 @@
=== tests/cases/conformance/jsdoc/templateTagOnClasses.js ===
/**
* @template {T}
* @template T
* @typedef {(t: T) => T} Id
*/
/** @template T */

View File

@ -1,6 +1,6 @@
=== tests/cases/conformance/jsdoc/templateTagOnClasses.js ===
/**
* @template {T}
* @template T
* @typedef {(t: T) => T} Id
*/
/** @template T */

View File

@ -3,12 +3,12 @@ tests/cases/conformance/jsdoc/templateTagOnConstructorFunctions.js(24,1): error
==== tests/cases/conformance/jsdoc/templateTagOnConstructorFunctions.js (1 errors) ====
/**
* @template {U}
* @template U
* @typedef {(u: U) => U} Id
*/
/**
* @param {T} t
* @template {T}
* @template T
*/
function Zet(t) {
/** @type {T} */

View File

@ -1,11 +1,11 @@
=== tests/cases/conformance/jsdoc/templateTagOnConstructorFunctions.js ===
/**
* @template {U}
* @template U
* @typedef {(u: U) => U} Id
*/
/**
* @param {T} t
* @template {T}
* @template T
*/
function Zet(t) {
>Zet : Symbol(Zet, Decl(templateTagOnConstructorFunctions.js, 0, 0))

View File

@ -1,11 +1,11 @@
=== tests/cases/conformance/jsdoc/templateTagOnConstructorFunctions.js ===
/**
* @template {U}
* @template U
* @typedef {(u: U) => U} Id
*/
/**
* @param {T} t
* @template {T}
* @template T
*/
function Zet(t) {
>Zet : typeof Zet

View File

@ -5,7 +5,7 @@ tests/cases/conformance/jsdoc/templateTagWithNestedTypeLiteral.js(26,15): error
==== tests/cases/conformance/jsdoc/templateTagWithNestedTypeLiteral.js (2 errors) ====
/**
* @param {T} t
* @template {T}
* @template T
*/
function Zet(t) {
/** @type {T} */

View File

@ -1,7 +1,7 @@
=== tests/cases/conformance/jsdoc/templateTagWithNestedTypeLiteral.js ===
/**
* @param {T} t
* @template {T}
* @template T
*/
function Zet(t) {
>Zet : Symbol(Zet, Decl(templateTagWithNestedTypeLiteral.js, 0, 0))

View File

@ -1,7 +1,7 @@
=== tests/cases/conformance/jsdoc/templateTagWithNestedTypeLiteral.js ===
/**
* @param {T} t
* @template {T}
* @template T
*/
function Zet(t) {
>Zet : typeof Zet

View File

@ -0,0 +1,47 @@
tests/cases/conformance/jsdoc/a.js(14,29): error TS2339: Property 'a' does not exist on type 'U'.
tests/cases/conformance/jsdoc/a.js(14,35): error TS2339: Property 'b' does not exist on type 'U'.
tests/cases/conformance/jsdoc/a.js(21,3): error TS2345: Argument of type '{ a: number; }' is not assignable to parameter of type '{ a: number; b: string; }'.
Property 'b' is missing in type '{ a: number; }'.
tests/cases/conformance/jsdoc/a.js(25,2): error TS1069: Unexpected token. A type parameter name was expected without curly braces.
==== tests/cases/conformance/jsdoc/a.js (4 errors) ====
/**
* @template {{ a: number, b: string }} T,U A Comment
* @template {{ c: boolean }} V uh ... are comments even supported??
* @template W
* @template X That last one had no comment
* @param {T} t
* @param {U} u
* @param {V} v
* @param {W} w
* @param {X} x
* @return {W | X}
*/
function f(t, u, v, w, x) {
if(t.a + t.b.length > u.a - u.b.length && v.c) {
~
!!! error TS2339: Property 'a' does not exist on type 'U'.
~
!!! error TS2339: Property 'b' does not exist on type 'U'.
return w;
}
return x;
}
f({ a: 12, b: 'hi', c: null }, undefined, { c: false, d: 12, b: undefined }, 101, 'nope');
f({ a: 12 }, undefined, undefined, 101, 'nope');
~~~~~~~~~~
!!! error TS2345: Argument of type '{ a: number; }' is not assignable to parameter of type '{ a: number; b: string; }'.
!!! error TS2345: Property 'b' is missing in type '{ a: number; }'.
/**
* @template {NoLongerAllowed}
* @template T preceding line's syntax is no longer allowed
~
!!! error TS1069: Unexpected token. A type parameter name was expected without curly braces.
* @param {T} x
*/
function g(x) { }

View File

@ -0,0 +1,70 @@
=== tests/cases/conformance/jsdoc/a.js ===
/**
* @template {{ a: number, b: string }} T,U A Comment
* @template {{ c: boolean }} V uh ... are comments even supported??
* @template W
* @template X That last one had no comment
* @param {T} t
* @param {U} u
* @param {V} v
* @param {W} w
* @param {X} x
* @return {W | X}
*/
function f(t, u, v, w, x) {
>f : Symbol(f, Decl(a.js, 0, 0))
>t : Symbol(t, Decl(a.js, 12, 11))
>u : Symbol(u, Decl(a.js, 12, 13))
>v : Symbol(v, Decl(a.js, 12, 16))
>w : Symbol(w, Decl(a.js, 12, 19))
>x : Symbol(x, Decl(a.js, 12, 22))
if(t.a + t.b.length > u.a - u.b.length && v.c) {
>t.a : Symbol(a, Decl(a.js, 1, 15))
>t : Symbol(t, Decl(a.js, 12, 11))
>a : Symbol(a, Decl(a.js, 1, 15))
>t.b.length : Symbol(String.length, Decl(lib.d.ts, --, --))
>t.b : Symbol(b, Decl(a.js, 1, 26))
>t : Symbol(t, Decl(a.js, 12, 11))
>b : Symbol(b, Decl(a.js, 1, 26))
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
>u : Symbol(u, Decl(a.js, 12, 13))
>u : Symbol(u, Decl(a.js, 12, 13))
>v.c : Symbol(c, Decl(a.js, 2, 15))
>v : Symbol(v, Decl(a.js, 12, 16))
>c : Symbol(c, Decl(a.js, 2, 15))
return w;
>w : Symbol(w, Decl(a.js, 12, 19))
}
return x;
>x : Symbol(x, Decl(a.js, 12, 22))
}
f({ a: 12, b: 'hi', c: null }, undefined, { c: false, d: 12, b: undefined }, 101, 'nope');
>f : Symbol(f, Decl(a.js, 0, 0))
>a : Symbol(a, Decl(a.js, 19, 3))
>b : Symbol(b, Decl(a.js, 19, 10))
>c : Symbol(c, Decl(a.js, 19, 19))
>undefined : Symbol(undefined)
>c : Symbol(c, Decl(a.js, 19, 43))
>d : Symbol(d, Decl(a.js, 19, 53))
>b : Symbol(b, Decl(a.js, 19, 60))
>undefined : Symbol(undefined)
f({ a: 12 }, undefined, undefined, 101, 'nope');
>f : Symbol(f, Decl(a.js, 0, 0))
>a : Symbol(a, Decl(a.js, 20, 3))
>undefined : Symbol(undefined)
>undefined : Symbol(undefined)
/**
* @template {NoLongerAllowed}
* @template T preceding line's syntax is no longer allowed
* @param {T} x
*/
function g(x) { }
>g : Symbol(g, Decl(a.js, 20, 49))
>x : Symbol(x, Decl(a.js, 27, 11))

View File

@ -0,0 +1,95 @@
=== tests/cases/conformance/jsdoc/a.js ===
/**
* @template {{ a: number, b: string }} T,U A Comment
* @template {{ c: boolean }} V uh ... are comments even supported??
* @template W
* @template X That last one had no comment
* @param {T} t
* @param {U} u
* @param {V} v
* @param {W} w
* @param {X} x
* @return {W | X}
*/
function f(t, u, v, w, x) {
>f : <T extends { a: number; b: string; }, U, V extends { c: boolean; }, W, X>(t: T, u: U, v: V, w: W, x: X) => W | X
>t : T
>u : U
>v : V
>w : W
>x : X
if(t.a + t.b.length > u.a - u.b.length && v.c) {
>t.a + t.b.length > u.a - u.b.length && v.c : boolean
>t.a + t.b.length > u.a - u.b.length : boolean
>t.a + t.b.length : number
>t.a : number
>t : T
>a : number
>t.b.length : number
>t.b : string
>t : T
>b : string
>length : number
>u.a - u.b.length : number
>u.a : any
>u : U
>a : any
>u.b.length : any
>u.b : any
>u : U
>b : any
>length : any
>v.c : boolean
>v : V
>c : boolean
return w;
>w : W
}
return x;
>x : X
}
f({ a: 12, b: 'hi', c: null }, undefined, { c: false, d: 12, b: undefined }, 101, 'nope');
>f({ a: 12, b: 'hi', c: null }, undefined, { c: false, d: 12, b: undefined }, 101, 'nope') : string | number
>f : <T extends { a: number; b: string; }, U, V extends { c: boolean; }, W, X>(t: T, u: U, v: V, w: W, x: X) => W | X
>{ a: 12, b: 'hi', c: null } : { a: number; b: string; c: null; }
>a : number
>12 : 12
>b : string
>'hi' : "hi"
>c : null
>null : null
>undefined : undefined
>{ c: false, d: 12, b: undefined } : { c: false; d: number; b: undefined; }
>c : false
>false : false
>d : number
>12 : 12
>b : undefined
>undefined : undefined
>101 : 101
>'nope' : "nope"
f({ a: 12 }, undefined, undefined, 101, 'nope');
>f({ a: 12 }, undefined, undefined, 101, 'nope') : any
>f : <T extends { a: number; b: string; }, U, V extends { c: boolean; }, W, X>(t: T, u: U, v: V, w: W, x: X) => W | X
>{ a: 12 } : { a: number; }
>a : number
>12 : 12
>undefined : undefined
>undefined : undefined
>101 : 101
>'nope' : "nope"
/**
* @template {NoLongerAllowed}
* @template T preceding line's syntax is no longer allowed
* @param {T} x
*/
function g(x) { }
>g : <T>(x: T) => void
>x : T

View File

@ -2,13 +2,13 @@
{
"marker": {
"fileName": "/tests/cases/fourslash/quickInfoJsDocTags.ts",
"position": 258
"position": 256
},
"quickInfo": {
"kind": "function",
"kindModifiers": "",
"textSpan": {
"start": 258,
"start": 256,
"length": 3
},
"displayParts": [
@ -78,7 +78,7 @@
},
{
"name": "template",
"text": "{T} A template"
"text": "T A template"
},
{
"name": "type",

View File

@ -0,0 +1,32 @@
tests/cases/conformance/jsdoc/a.js(16,12): error TS2314: Generic type 'Everything' requires 5 type argument(s).
tests/cases/conformance/jsdoc/test.ts(1,34): error TS2344: Type '{ a: number; }' does not satisfy the constraint '{ a: number; b: string; }'.
Property 'b' is missing in type '{ a: number; }'.
==== tests/cases/conformance/jsdoc/a.js (1 errors) ====
/**
* @template {{ a: number, b: string }} T,U A Comment
* @template {{ c: boolean }} V uh ... are comments even supported??
* @template W
* @template X That last one had no comment
* @typedef {{ t: T, u: U, v: V, w: W, x: X }} Everything
*/
/** @type {Everything<{ a: number, b: 'hi', c: never }, undefined, { c: true, d: 1 }, number, string>} */
var tuvwx;
// TODO: will error when #24592 is fixed
/** @type {Everything<{ a: number }, undefined, { c: 1, d: 1 }, number, string>} */
var wrong;
/** @type {Everything<{ a: number }>} */
~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2314: Generic type 'Everything' requires 5 type argument(s).
var insufficient;
==== tests/cases/conformance/jsdoc/test.ts (1 errors) ====
declare var actually: Everything<{ a: number }, undefined, { c: 1, d: 1 }, number, string>;
~~~~~~~~~~~~~~
!!! error TS2344: Type '{ a: number; }' does not satisfy the constraint '{ a: number; b: string; }'.
!!! error TS2344: Property 'b' is missing in type '{ a: number; }'.

View File

@ -0,0 +1,30 @@
=== tests/cases/conformance/jsdoc/a.js ===
/**
* @template {{ a: number, b: string }} T,U A Comment
* @template {{ c: boolean }} V uh ... are comments even supported??
* @template W
* @template X That last one had no comment
* @typedef {{ t: T, u: U, v: V, w: W, x: X }} Everything
*/
/** @type {Everything<{ a: number, b: 'hi', c: never }, undefined, { c: true, d: 1 }, number, string>} */
var tuvwx;
>tuvwx : Symbol(tuvwx, Decl(a.js, 9, 3))
// TODO: will error when #24592 is fixed
/** @type {Everything<{ a: number }, undefined, { c: 1, d: 1 }, number, string>} */
var wrong;
>wrong : Symbol(wrong, Decl(a.js, 13, 3))
/** @type {Everything<{ a: number }>} */
var insufficient;
>insufficient : Symbol(insufficient, Decl(a.js, 16, 3))
=== tests/cases/conformance/jsdoc/test.ts ===
declare var actually: Everything<{ a: number }, undefined, { c: 1, d: 1 }, number, string>;
>actually : Symbol(actually, Decl(test.ts, 0, 11))
>Everything : Symbol(Everything, Decl(a.js, 5, 3))
>a : Symbol(a, Decl(test.ts, 0, 34))
>c : Symbol(c, Decl(test.ts, 0, 61))
>d : Symbol(d, Decl(test.ts, 0, 67))

View File

@ -0,0 +1,30 @@
=== tests/cases/conformance/jsdoc/a.js ===
/**
* @template {{ a: number, b: string }} T,U A Comment
* @template {{ c: boolean }} V uh ... are comments even supported??
* @template W
* @template X That last one had no comment
* @typedef {{ t: T, u: U, v: V, w: W, x: X }} Everything
*/
/** @type {Everything<{ a: number, b: 'hi', c: never }, undefined, { c: true, d: 1 }, number, string>} */
var tuvwx;
>tuvwx : { t: { a: number; b: "hi"; c: never; }; u: undefined; v: { c: true; d: 1; }; w: number; x: string; }
// TODO: will error when #24592 is fixed
/** @type {Everything<{ a: number }, undefined, { c: 1, d: 1 }, number, string>} */
var wrong;
>wrong : { t: { a: number; }; u: undefined; v: { c: 1; d: 1; }; w: number; x: string; }
/** @type {Everything<{ a: number }>} */
var insufficient;
>insufficient : any
=== tests/cases/conformance/jsdoc/test.ts ===
declare var actually: Everything<{ a: number }, undefined, { c: 1, d: 1 }, number, string>;
>actually : { t: { a: number; }; u: undefined; v: { c: 1; d: 1; }; w: number; x: string; }
>Everything : { t: T; u: U; v: V; w: W; x: X; }
>a : number
>c : 1
>d : 1

View File

@ -4,7 +4,7 @@
// @Filename: templateTagOnClasses.js
/**
* @template {T}
* @template T
* @typedef {(t: T) => T} Id
*/
/** @template T */

View File

@ -4,12 +4,12 @@
// @Filename: templateTagOnConstructorFunctions.js
/**
* @template {U}
* @template U
* @typedef {(u: U) => U} Id
*/
/**
* @param {T} t
* @template {T}
* @template T
*/
function Zet(t) {
/** @type {T} */

View File

@ -5,7 +5,7 @@
/**
* @param {T} t
* @template {T}
* @template T
*/
function Zet(t) {
/** @type {T} */

View File

@ -0,0 +1,33 @@
// @allowJs: true
// @checkJs: true
// @noEmit: true
// @Filename: a.js
/**
* @template {{ a: number, b: string }} T,U A Comment
* @template {{ c: boolean }} V uh ... are comments even supported??
* @template W
* @template X That last one had no comment
* @param {T} t
* @param {U} u
* @param {V} v
* @param {W} w
* @param {X} x
* @return {W | X}
*/
function f(t, u, v, w, x) {
if(t.a + t.b.length > u.a - u.b.length && v.c) {
return w;
}
return x;
}
f({ a: 12, b: 'hi', c: null }, undefined, { c: false, d: 12, b: undefined }, 101, 'nope');
f({ a: 12 }, undefined, undefined, 101, 'nope');
/**
* @template {NoLongerAllowed}
* @template T preceding line's syntax is no longer allowed
* @param {T} x
*/
function g(x) { }

View File

@ -0,0 +1,24 @@
// @allowJs: true
// @checkJs: true
// @noEmit: true
// @Filename: a.js
/**
* @template {{ a: number, b: string }} T,U A Comment
* @template {{ c: boolean }} V uh ... are comments even supported??
* @template W
* @template X That last one had no comment
* @typedef {{ t: T, u: U, v: V, w: W, x: X }} Everything
*/
/** @type {Everything<{ a: number, b: 'hi', c: never }, undefined, { c: true, d: 1 }, number, string>} */
var tuvwx;
// TODO: will error when #24592 is fixed
/** @type {Everything<{ a: number }, undefined, { c: 1, d: 1 }, number, string>} */
var wrong;
/** @type {Everything<{ a: number }>} */
var insufficient;
// @Filename: test.ts
declare var actually: Everything<{ a: number }, undefined, { c: 1, d: 1 }, number, string>;

View File

@ -1,7 +1,7 @@
/// <reference path='fourslash.ts' />
/////**
//// * @template {T}
//// * @template T
//// * @param {T} x
//// * @returns {T}
//// */
@ -11,7 +11,7 @@ verify.codeFix({
description: "Annotate with type from JSDoc",
newFileContent:
`/**
* @template {T}
* @template T
* @param {T} x
* @returns {T}
*/

View File

@ -17,7 +17,7 @@ verify.signatureHelp({
text: "find<T>(l: T[], x: T): T",
docComment: "Find an item",
tags: [
// TODO: GH#24130
// TODO: GH#24130 (see PR #24600's commits for potential fix)
{ name: "template", text: "T\n " },
{ name: "param", text: "l" },
{ name: "param", text: "x" },

View File

@ -5,7 +5,7 @@
//// * Doc
//// * @author Me
//// * @augments {C<T>} Augments it
//// * @template {T} A template
//// * @template T A template
//// * @type {number | string} A type
//// * @typedef {number | string} NumOrStr
//// * @property {number} x The prop