From cb8400de53cc4043774616f2ac226ebb15cc5d71 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 27 Jul 2018 14:27:44 -0700 Subject: [PATCH 1/2] Support the JSDoc @enum tag `@enum` is used on a variable declaration with an object literal initializer. It does a number of things: 1. The object literal has a closed set of properties, unlike other object literals in Javascript. 2. The variable's name is resolvable as a type, but it just has the declared type of the enum tag. 3. Each property's type must be assignable to the enum tag's declared type, which can be any type. For example, ```js /** @enum {string} */ const Target = { START: "START", END: "END", MISTAKE: 0, // error 'number' is not assignable to 'string' -- see (3) } Target.THIS_IS_AN_ERROR; // See (1) /** @type {Target} See (2) */ var target = Target.START; ``` --- src/compiler/checker.ts | 13 +- src/compiler/parser.ts | 14 +++ src/compiler/types.ts | 9 +- src/compiler/utilities.ts | 9 ++ tests/baselines/reference/enumTag.errors.txt | 52 ++++++++ tests/baselines/reference/enumTag.symbols | 98 +++++++++++++++ tests/baselines/reference/enumTag.types | 120 +++++++++++++++++++ tests/cases/conformance/jsdoc/enumTag.ts | 43 +++++++ 8 files changed, 355 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/enumTag.errors.txt create mode 100644 tests/baselines/reference/enumTag.symbols create mode 100644 tests/baselines/reference/enumTag.types create mode 100644 tests/cases/conformance/jsdoc/enumTag.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2b255da7ae34d..3babc5788e824 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8128,6 +8128,10 @@ namespace ts { // TODO: GH#18217 (should the `|| assignedType` be at a lower precedence?) return (referenceType && assignedType ? getIntersectionType([assignedType, referenceType]) : referenceType || assignedType)!; } + const enumTag = getJSDocEnumTag(symbol.valueDeclaration); + if (enumTag && enumTag.typeExpression) { + return getTypeFromTypeNode(enumTag.typeExpression); + } } function getTypeReferenceTypeWorker(node: NodeWithTypeArguments, symbol: Symbol, typeArguments: Type[] | undefined): Type | undefined { @@ -16563,7 +16567,8 @@ namespace ts { const contextualTypeHasPattern = contextualType && contextualType.pattern && (contextualType.pattern.kind === SyntaxKind.ObjectBindingPattern || contextualType.pattern.kind === SyntaxKind.ObjectLiteralExpression); const isInJSFile = isInJavaScriptFile(node) && !isInJsonFile(node); - const isJSObjectLiteral = !contextualType && isInJSFile; + const enumTag = getJSDocEnumTag(node); + const isJSObjectLiteral = !contextualType && isInJSFile && !enumTag; let typeFlags: TypeFlags = 0; let patternWithComputedProperties = false; let hasComputedStringProperty = false; @@ -16588,6 +16593,9 @@ namespace ts { checkTypeAssignableTo(type, jsDocType, memberDecl); type = jsDocType; } + else if (enumTag && enumTag.typeExpression) { + checkTypeAssignableTo(type, getTypeFromTypeNode(enumTag.typeExpression), memberDecl); + } } typeFlags |= type.flags; const nameType = computedNameType && computedNameType.flags & TypeFlags.StringOrNumberLiteralOrUnique ? @@ -16622,6 +16630,7 @@ namespace ts { symbolToString(member), typeToString(contextualType!)); } } + prop.declarations = member.declarations; prop.parent = member.parent; if (member.valueDeclaration) { @@ -21641,7 +21650,7 @@ namespace ts { checkComputedPropertyName(node.name); } - return checkExpressionForMutableLocation(node.initializer, checkMode); + return checkExpressionForMutableLocation(node.initializer, checkMode) } function checkObjectLiteralMethod(node: MethodDeclaration, checkMode?: CheckMode): Type { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 4fd7f77978a45..1f52a1cc6e89f 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -491,6 +491,8 @@ namespace ts { visitNode(cbNode, (node as JSDocCallbackTag).typeExpression); case SyntaxKind.JSDocThisTag: return visitNode(cbNode, (node as JSDocThisTag).typeExpression); + case SyntaxKind.JSDocEnumTag: + return visitNode(cbNode, (node as JSDocEnumTag).typeExpression); case SyntaxKind.JSDocSignature: return visitNodes(cbNode, cbNodes, node.decorators) || visitNodes(cbNode, cbNodes, node.modifiers) || @@ -6527,6 +6529,9 @@ namespace ts { case "this": tag = parseThisTag(atToken, tagName); break; + case "enum": + tag = parseEnumTag(atToken, tagName); + break; case "arg": case "argument": case "param": @@ -6817,6 +6822,15 @@ namespace ts { return finishNode(tag); } + function parseEnumTag(atToken: AtToken, tagName: Identifier): JSDocEnumTag { + const tag = createNode(SyntaxKind.JSDocEnumTag, atToken.pos); + tag.atToken = atToken; + tag.tagName = tagName; + tag.typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); + skipWhitespace(); + return finishNode(tag); + } + function parseTypedefTag(atToken: AtToken, tagName: Identifier, indent: number): JSDocTypedefTag { const typeExpression = tryParseTypeExpression(); skipWhitespace(); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 99d2291e89847..f8468203575c4 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -372,6 +372,7 @@ namespace ts { JSDocAugmentsTag, JSDocClassTag, JSDocCallbackTag, + JSDocEnumTag, JSDocParameterTag, JSDocReturnTag, JSDocThisTag, @@ -581,6 +582,7 @@ namespace ts { | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType + | ExportDeclaration | EndOfFileToken; export type HasType = @@ -2202,7 +2204,7 @@ namespace ts { name: Identifier; } - export interface ExportDeclaration extends DeclarationStatement { + export interface ExportDeclaration extends DeclarationStatement, JSDocContainer { kind: SyntaxKind.ExportDeclaration; parent: SourceFile | ModuleBlock; /** Will not be assigned in the case of `export * from "foo";` */ @@ -2348,6 +2350,11 @@ namespace ts { kind: SyntaxKind.JSDocClassTag; } + export interface JSDocEnumTag extends JSDocTag { + kind: SyntaxKind.JSDocEnumTag; + typeExpression?: JSDocTypeExpression; + } + export interface JSDocThisTag extends JSDocTag { kind: SyntaxKind.JSDocThisTag; typeExpression?: JSDocTypeExpression; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 689e5cd2fef51..5c1ecac31b871 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4985,6 +4985,11 @@ namespace ts { return getFirstJSDocTag(node, isJSDocClassTag); } + /** Gets the JSDoc enum tag for the node if present */ + export function getJSDocEnumTag(node: Node): JSDocEnumTag | undefined { + return getFirstJSDocTag(node, isJSDocEnumTag); + } + /** Gets the JSDoc this tag for the node if present */ export function getJSDocThisTag(node: Node): JSDocThisTag | undefined { return getFirstJSDocTag(node, isJSDocThisTag); @@ -5749,6 +5754,10 @@ namespace ts { return node.kind === SyntaxKind.JSDocClassTag; } + export function isJSDocEnumTag(node: Node): node is JSDocEnumTag { + return node.kind === SyntaxKind.JSDocEnumTag; + } + export function isJSDocThisTag(node: Node): node is JSDocThisTag { return node.kind === SyntaxKind.JSDocThisTag; } diff --git a/tests/baselines/reference/enumTag.errors.txt b/tests/baselines/reference/enumTag.errors.txt new file mode 100644 index 0000000000000..89faa3cbf8d79 --- /dev/null +++ b/tests/baselines/reference/enumTag.errors.txt @@ -0,0 +1,52 @@ +tests/cases/conformance/jsdoc/a.js(6,5): error TS2322: Type 'number' is not assignable to type 'string'. +tests/cases/conformance/jsdoc/a.js(12,5): error TS2322: Type 'string' is not assignable to type 'number'. +tests/cases/conformance/jsdoc/a.js(37,16): error TS2339: Property 'UNKNOWN' does not exist on type '{ START: string; MIDDLE: string; END: string; MISTAKE: number; OK_I_GUESS: number; }'. + + +==== tests/cases/conformance/jsdoc/a.js (3 errors) ==== + /** @enum {string} */ + const Target = { + START: "start", + MIDDLE: "middle", + END: "end", + MISTAKE: 1, + ~~~~~~~~~~ +!!! error TS2322: Type 'number' is not assignable to type 'string'. + /** @type {number} */ + OK_I_GUESS: 2 + } + /** @enum {number} */ + const Second = { + MISTAKE: "end", + ~~~~~~~~~~~~~~ +!!! error TS2322: Type 'string' is not assignable to type 'number'. + OK: 1, + /** @type {number} */ + FINE: 2, + } + /** @enum {function(number): number} */ + const Fs = { + ADD1: n => n + 1, + ID: n => n, + SUB1: n => n - 1 + } + + /** @param {Target} t + * @param {Second} s + * @param {Fs} f + */ + function consume(t,s,f) { + /** @type {string} */ + var str = t + /** @type {number} */ + var num = s + /** @type {(n: number) => number} */ + var fun = f + /** @type {Target} */ + var v = Target.START + v = Target.UNKNOWN // error, can't find 'UNKNOWN' + ~~~~~~~ +!!! error TS2339: Property 'UNKNOWN' does not exist on type '{ START: string; MIDDLE: string; END: string; MISTAKE: number; OK_I_GUESS: number; }'. + v = Second.MISTAKE // meh..ok, I guess? + } + \ No newline at end of file diff --git a/tests/baselines/reference/enumTag.symbols b/tests/baselines/reference/enumTag.symbols new file mode 100644 index 0000000000000..9515f9031d6de --- /dev/null +++ b/tests/baselines/reference/enumTag.symbols @@ -0,0 +1,98 @@ +=== tests/cases/conformance/jsdoc/a.js === +/** @enum {string} */ +const Target = { +>Target : Symbol(Target, Decl(a.js, 1, 5)) + + START: "start", +>START : Symbol(START, Decl(a.js, 1, 16)) + + MIDDLE: "middle", +>MIDDLE : Symbol(MIDDLE, Decl(a.js, 2, 19)) + + END: "end", +>END : Symbol(END, Decl(a.js, 3, 21)) + + MISTAKE: 1, +>MISTAKE : Symbol(MISTAKE, Decl(a.js, 4, 15)) + + /** @type {number} */ + OK_I_GUESS: 2 +>OK_I_GUESS : Symbol(OK_I_GUESS, Decl(a.js, 5, 15)) +} +/** @enum {number} */ +const Second = { +>Second : Symbol(Second, Decl(a.js, 10, 5)) + + MISTAKE: "end", +>MISTAKE : Symbol(MISTAKE, Decl(a.js, 10, 16)) + + OK: 1, +>OK : Symbol(OK, Decl(a.js, 11, 19)) + + /** @type {number} */ + FINE: 2, +>FINE : Symbol(FINE, Decl(a.js, 12, 10)) +} +/** @enum {function(number): number} */ +const Fs = { +>Fs : Symbol(Fs, Decl(a.js, 17, 5)) + + ADD1: n => n + 1, +>ADD1 : Symbol(ADD1, Decl(a.js, 17, 12)) +>n : Symbol(n, Decl(a.js, 18, 9)) +>n : Symbol(n, Decl(a.js, 18, 9)) + + ID: n => n, +>ID : Symbol(ID, Decl(a.js, 18, 21)) +>n : Symbol(n, Decl(a.js, 19, 7)) +>n : Symbol(n, Decl(a.js, 19, 7)) + + SUB1: n => n - 1 +>SUB1 : Symbol(SUB1, Decl(a.js, 19, 15)) +>n : Symbol(n, Decl(a.js, 20, 9)) +>n : Symbol(n, Decl(a.js, 20, 9)) +} + +/** @param {Target} t + * @param {Second} s + * @param {Fs} f + */ +function consume(t,s,f) { +>consume : Symbol(consume, Decl(a.js, 21, 1)) +>t : Symbol(t, Decl(a.js, 27, 17)) +>s : Symbol(s, Decl(a.js, 27, 19)) +>f : Symbol(f, Decl(a.js, 27, 21)) + + /** @type {string} */ + var str = t +>str : Symbol(str, Decl(a.js, 29, 7)) +>t : Symbol(t, Decl(a.js, 27, 17)) + + /** @type {number} */ + var num = s +>num : Symbol(num, Decl(a.js, 31, 7)) +>s : Symbol(s, Decl(a.js, 27, 19)) + + /** @type {(n: number) => number} */ + var fun = f +>fun : Symbol(fun, Decl(a.js, 33, 7)) +>f : Symbol(f, Decl(a.js, 27, 21)) + + /** @type {Target} */ + var v = Target.START +>v : Symbol(v, Decl(a.js, 35, 7)) +>Target.START : Symbol(START, Decl(a.js, 1, 16)) +>Target : Symbol(Target, Decl(a.js, 1, 5)) +>START : Symbol(START, Decl(a.js, 1, 16)) + + v = Target.UNKNOWN // error, can't find 'UNKNOWN' +>v : Symbol(v, Decl(a.js, 35, 7)) +>Target : Symbol(Target, Decl(a.js, 1, 5)) + + v = Second.MISTAKE // meh..ok, I guess? +>v : Symbol(v, Decl(a.js, 35, 7)) +>Second.MISTAKE : Symbol(MISTAKE, Decl(a.js, 10, 16)) +>Second : Symbol(Second, Decl(a.js, 10, 5)) +>MISTAKE : Symbol(MISTAKE, Decl(a.js, 10, 16)) +} + diff --git a/tests/baselines/reference/enumTag.types b/tests/baselines/reference/enumTag.types new file mode 100644 index 0000000000000..d7b649a676fdc --- /dev/null +++ b/tests/baselines/reference/enumTag.types @@ -0,0 +1,120 @@ +=== tests/cases/conformance/jsdoc/a.js === +/** @enum {string} */ +const Target = { +>Target : { START: string; MIDDLE: string; END: string; MISTAKE: number; OK_I_GUESS: number; } +>{ START: "start", MIDDLE: "middle", END: "end", MISTAKE: 1, /** @type {number} */ OK_I_GUESS: 2} : { START: string; MIDDLE: string; END: string; MISTAKE: number; OK_I_GUESS: number; } + + START: "start", +>START : string +>"start" : "start" + + MIDDLE: "middle", +>MIDDLE : string +>"middle" : "middle" + + END: "end", +>END : string +>"end" : "end" + + MISTAKE: 1, +>MISTAKE : number +>1 : 1 + + /** @type {number} */ + OK_I_GUESS: 2 +>OK_I_GUESS : number +>2 : 2 +} +/** @enum {number} */ +const Second = { +>Second : { MISTAKE: string; OK: number; FINE: number; } +>{ MISTAKE: "end", OK: 1, /** @type {number} */ FINE: 2,} : { MISTAKE: string; OK: number; FINE: number; } + + MISTAKE: "end", +>MISTAKE : string +>"end" : "end" + + OK: 1, +>OK : number +>1 : 1 + + /** @type {number} */ + FINE: 2, +>FINE : number +>2 : 2 +} +/** @enum {function(number): number} */ +const Fs = { +>Fs : { ADD1: (n: any) => any; ID: (n: any) => any; SUB1: (n: any) => number; } +>{ ADD1: n => n + 1, ID: n => n, SUB1: n => n - 1} : { ADD1: (n: any) => any; ID: (n: any) => any; SUB1: (n: any) => number; } + + ADD1: n => n + 1, +>ADD1 : (n: any) => any +>n => n + 1 : (n: any) => any +>n : any +>n + 1 : any +>n : any +>1 : 1 + + ID: n => n, +>ID : (n: any) => any +>n => n : (n: any) => any +>n : any +>n : any + + SUB1: n => n - 1 +>SUB1 : (n: any) => number +>n => n - 1 : (n: any) => number +>n : any +>n - 1 : number +>n : any +>1 : 1 +} + +/** @param {Target} t + * @param {Second} s + * @param {Fs} f + */ +function consume(t,s,f) { +>consume : (t: string, s: number, f: (arg0: number) => number) => void +>t : string +>s : number +>f : (arg0: number) => number + + /** @type {string} */ + var str = t +>str : string +>t : string + + /** @type {number} */ + var num = s +>num : number +>s : number + + /** @type {(n: number) => number} */ + var fun = f +>fun : (n: number) => number +>f : (arg0: number) => number + + /** @type {Target} */ + var v = Target.START +>v : string +>Target.START : string +>Target : { START: string; MIDDLE: string; END: string; MISTAKE: number; OK_I_GUESS: number; } +>START : string + + v = Target.UNKNOWN // error, can't find 'UNKNOWN' +>v = Target.UNKNOWN : any +>v : string +>Target.UNKNOWN : any +>Target : { START: string; MIDDLE: string; END: string; MISTAKE: number; OK_I_GUESS: number; } +>UNKNOWN : any + + v = Second.MISTAKE // meh..ok, I guess? +>v = Second.MISTAKE : string +>v : string +>Second.MISTAKE : string +>Second : { MISTAKE: string; OK: number; FINE: number; } +>MISTAKE : string +} + diff --git a/tests/cases/conformance/jsdoc/enumTag.ts b/tests/cases/conformance/jsdoc/enumTag.ts new file mode 100644 index 0000000000000..8e1b97787189d --- /dev/null +++ b/tests/cases/conformance/jsdoc/enumTag.ts @@ -0,0 +1,43 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @Filename: a.js +/** @enum {string} */ +const Target = { + START: "start", + MIDDLE: "middle", + END: "end", + MISTAKE: 1, + /** @type {number} */ + OK_I_GUESS: 2 +} +/** @enum {number} */ +const Second = { + MISTAKE: "end", + OK: 1, + /** @type {number} */ + FINE: 2, +} +/** @enum {function(number): number} */ +const Fs = { + ADD1: n => n + 1, + ID: n => n, + SUB1: n => n - 1 +} + +/** @param {Target} t + * @param {Second} s + * @param {Fs} f + */ +function consume(t,s,f) { + /** @type {string} */ + var str = t + /** @type {number} */ + var num = s + /** @type {(n: number) => number} */ + var fun = f + /** @type {Target} */ + var v = Target.START + v = Target.UNKNOWN // error, can't find 'UNKNOWN' + v = Second.MISTAKE // meh..ok, I guess? +} From 1b341cfcea73a617566da44ef949a102fd876a63 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 27 Jul 2018 14:48:31 -0700 Subject: [PATCH 2/2] Fix lint, add new test case, update API baselines --- src/compiler/checker.ts | 2 +- .../reference/api/tsserverlibrary.d.ts | 44 +++++++++++-------- tests/baselines/reference/api/typescript.d.ts | 44 +++++++++++-------- tests/baselines/reference/enumTag.errors.txt | 13 ++++++ tests/baselines/reference/enumTag.symbols | 23 ++++++++++ tests/baselines/reference/enumTag.types | 29 ++++++++++++ tests/cases/conformance/jsdoc/enumTag.ts | 13 ++++++ 7 files changed, 131 insertions(+), 37 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3babc5788e824..e1c63442823dd 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -21650,7 +21650,7 @@ namespace ts { checkComputedPropertyName(node.name); } - return checkExpressionForMutableLocation(node.initializer, checkMode) + return checkExpressionForMutableLocation(node.initializer, checkMode); } function checkObjectLiteralMethod(node: MethodDeclaration, checkMode?: CheckMode): Type { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 99a9c3b610098..859bda59af1c6 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -368,20 +368,21 @@ declare namespace ts { JSDocAugmentsTag = 293, JSDocClassTag = 294, JSDocCallbackTag = 295, - JSDocParameterTag = 296, - JSDocReturnTag = 297, - JSDocThisTag = 298, - JSDocTypeTag = 299, - JSDocTemplateTag = 300, - JSDocTypedefTag = 301, - JSDocPropertyTag = 302, - SyntaxList = 303, - NotEmittedStatement = 304, - PartiallyEmittedExpression = 305, - CommaListExpression = 306, - MergeDeclarationMarker = 307, - EndOfDeclarationMarker = 308, - Count = 309, + JSDocEnumTag = 296, + JSDocParameterTag = 297, + JSDocReturnTag = 298, + JSDocThisTag = 299, + JSDocTypeTag = 300, + JSDocTemplateTag = 301, + JSDocTypedefTag = 302, + JSDocPropertyTag = 303, + SyntaxList = 304, + NotEmittedStatement = 305, + PartiallyEmittedExpression = 306, + CommaListExpression = 307, + MergeDeclarationMarker = 308, + EndOfDeclarationMarker = 309, + Count = 310, FirstAssignment = 58, LastAssignment = 70, FirstCompoundAssignment = 59, @@ -408,9 +409,9 @@ declare namespace ts { LastBinaryOperator = 70, FirstNode = 146, FirstJSDocNode = 281, - LastJSDocNode = 302, + LastJSDocNode = 303, FirstJSDocTagNode = 292, - LastJSDocTagNode = 302 + LastJSDocTagNode = 303 } enum NodeFlags { None = 0, @@ -479,7 +480,7 @@ declare namespace ts { } interface JSDocContainer { } - type HasJSDoc = ParameterDeclaration | CallSignatureDeclaration | ConstructSignatureDeclaration | MethodSignature | PropertySignature | ArrowFunction | ParenthesizedExpression | SpreadAssignment | ShorthandPropertyAssignment | PropertyAssignment | FunctionExpression | LabeledStatement | ExpressionStatement | VariableStatement | FunctionDeclaration | ConstructorDeclaration | MethodDeclaration | PropertyDeclaration | AccessorDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | EnumMember | EnumDeclaration | ModuleDeclaration | ImportEqualsDeclaration | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType | EndOfFileToken; + type HasJSDoc = ParameterDeclaration | CallSignatureDeclaration | ConstructSignatureDeclaration | MethodSignature | PropertySignature | ArrowFunction | ParenthesizedExpression | SpreadAssignment | ShorthandPropertyAssignment | PropertyAssignment | FunctionExpression | LabeledStatement | ExpressionStatement | VariableStatement | FunctionDeclaration | ConstructorDeclaration | MethodDeclaration | PropertyDeclaration | AccessorDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | EnumMember | EnumDeclaration | ModuleDeclaration | ImportEqualsDeclaration | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType | ExportDeclaration | EndOfFileToken; type HasType = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertySignature | PropertyDeclaration | TypePredicateNode | ParenthesizedTypeNode | TypeOperatorNode | MappedTypeNode | AssertionExpression | TypeAliasDeclaration | JSDocTypeExpression | JSDocNonNullableType | JSDocNullableType | JSDocOptionalType | JSDocVariadicType; type HasInitializer = HasExpressionInitializer | ForStatement | ForInStatement | ForOfStatement | JsxAttribute; type HasExpressionInitializer = VariableDeclaration | ParameterDeclaration | BindingElement | PropertySignature | PropertyDeclaration | PropertyAssignment | EnumMember; @@ -1437,7 +1438,7 @@ declare namespace ts { kind: SyntaxKind.NamespaceExportDeclaration; name: Identifier; } - interface ExportDeclaration extends DeclarationStatement { + interface ExportDeclaration extends DeclarationStatement, JSDocContainer { kind: SyntaxKind.ExportDeclaration; parent: SourceFile | ModuleBlock; /** Will not be assigned in the case of `export * from "foo";` */ @@ -1556,6 +1557,10 @@ declare namespace ts { interface JSDocClassTag extends JSDocTag { kind: SyntaxKind.JSDocClassTag; } + interface JSDocEnumTag extends JSDocTag { + kind: SyntaxKind.JSDocEnumTag; + typeExpression?: JSDocTypeExpression; + } interface JSDocThisTag extends JSDocTag { kind: SyntaxKind.JSDocThisTag; typeExpression?: JSDocTypeExpression; @@ -3220,6 +3225,8 @@ declare namespace ts { function getJSDocAugmentsTag(node: Node): JSDocAugmentsTag | undefined; /** Gets the JSDoc class tag for the node if present */ function getJSDocClassTag(node: Node): JSDocClassTag | undefined; + /** Gets the JSDoc enum tag for the node if present */ + function getJSDocEnumTag(node: Node): JSDocEnumTag | undefined; /** Gets the JSDoc this tag for the node if present */ function getJSDocThisTag(node: Node): JSDocThisTag | undefined; /** Gets the JSDoc return tag for the node if present */ @@ -3412,6 +3419,7 @@ declare namespace ts { function isJSDoc(node: Node): node is JSDoc; function isJSDocAugmentsTag(node: Node): node is JSDocAugmentsTag; function isJSDocClassTag(node: Node): node is JSDocClassTag; + function isJSDocEnumTag(node: Node): node is JSDocEnumTag; function isJSDocThisTag(node: Node): node is JSDocThisTag; function isJSDocParameterTag(node: Node): node is JSDocParameterTag; function isJSDocReturnTag(node: Node): node is JSDocReturnTag; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 41510d5192bda..31a7c8abbabaf 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -368,20 +368,21 @@ declare namespace ts { JSDocAugmentsTag = 293, JSDocClassTag = 294, JSDocCallbackTag = 295, - JSDocParameterTag = 296, - JSDocReturnTag = 297, - JSDocThisTag = 298, - JSDocTypeTag = 299, - JSDocTemplateTag = 300, - JSDocTypedefTag = 301, - JSDocPropertyTag = 302, - SyntaxList = 303, - NotEmittedStatement = 304, - PartiallyEmittedExpression = 305, - CommaListExpression = 306, - MergeDeclarationMarker = 307, - EndOfDeclarationMarker = 308, - Count = 309, + JSDocEnumTag = 296, + JSDocParameterTag = 297, + JSDocReturnTag = 298, + JSDocThisTag = 299, + JSDocTypeTag = 300, + JSDocTemplateTag = 301, + JSDocTypedefTag = 302, + JSDocPropertyTag = 303, + SyntaxList = 304, + NotEmittedStatement = 305, + PartiallyEmittedExpression = 306, + CommaListExpression = 307, + MergeDeclarationMarker = 308, + EndOfDeclarationMarker = 309, + Count = 310, FirstAssignment = 58, LastAssignment = 70, FirstCompoundAssignment = 59, @@ -408,9 +409,9 @@ declare namespace ts { LastBinaryOperator = 70, FirstNode = 146, FirstJSDocNode = 281, - LastJSDocNode = 302, + LastJSDocNode = 303, FirstJSDocTagNode = 292, - LastJSDocTagNode = 302 + LastJSDocTagNode = 303 } enum NodeFlags { None = 0, @@ -479,7 +480,7 @@ declare namespace ts { } interface JSDocContainer { } - type HasJSDoc = ParameterDeclaration | CallSignatureDeclaration | ConstructSignatureDeclaration | MethodSignature | PropertySignature | ArrowFunction | ParenthesizedExpression | SpreadAssignment | ShorthandPropertyAssignment | PropertyAssignment | FunctionExpression | LabeledStatement | ExpressionStatement | VariableStatement | FunctionDeclaration | ConstructorDeclaration | MethodDeclaration | PropertyDeclaration | AccessorDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | EnumMember | EnumDeclaration | ModuleDeclaration | ImportEqualsDeclaration | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType | EndOfFileToken; + type HasJSDoc = ParameterDeclaration | CallSignatureDeclaration | ConstructSignatureDeclaration | MethodSignature | PropertySignature | ArrowFunction | ParenthesizedExpression | SpreadAssignment | ShorthandPropertyAssignment | PropertyAssignment | FunctionExpression | LabeledStatement | ExpressionStatement | VariableStatement | FunctionDeclaration | ConstructorDeclaration | MethodDeclaration | PropertyDeclaration | AccessorDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | EnumMember | EnumDeclaration | ModuleDeclaration | ImportEqualsDeclaration | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType | ExportDeclaration | EndOfFileToken; type HasType = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertySignature | PropertyDeclaration | TypePredicateNode | ParenthesizedTypeNode | TypeOperatorNode | MappedTypeNode | AssertionExpression | TypeAliasDeclaration | JSDocTypeExpression | JSDocNonNullableType | JSDocNullableType | JSDocOptionalType | JSDocVariadicType; type HasInitializer = HasExpressionInitializer | ForStatement | ForInStatement | ForOfStatement | JsxAttribute; type HasExpressionInitializer = VariableDeclaration | ParameterDeclaration | BindingElement | PropertySignature | PropertyDeclaration | PropertyAssignment | EnumMember; @@ -1437,7 +1438,7 @@ declare namespace ts { kind: SyntaxKind.NamespaceExportDeclaration; name: Identifier; } - interface ExportDeclaration extends DeclarationStatement { + interface ExportDeclaration extends DeclarationStatement, JSDocContainer { kind: SyntaxKind.ExportDeclaration; parent: SourceFile | ModuleBlock; /** Will not be assigned in the case of `export * from "foo";` */ @@ -1556,6 +1557,10 @@ declare namespace ts { interface JSDocClassTag extends JSDocTag { kind: SyntaxKind.JSDocClassTag; } + interface JSDocEnumTag extends JSDocTag { + kind: SyntaxKind.JSDocEnumTag; + typeExpression?: JSDocTypeExpression; + } interface JSDocThisTag extends JSDocTag { kind: SyntaxKind.JSDocThisTag; typeExpression?: JSDocTypeExpression; @@ -3220,6 +3225,8 @@ declare namespace ts { function getJSDocAugmentsTag(node: Node): JSDocAugmentsTag | undefined; /** Gets the JSDoc class tag for the node if present */ function getJSDocClassTag(node: Node): JSDocClassTag | undefined; + /** Gets the JSDoc enum tag for the node if present */ + function getJSDocEnumTag(node: Node): JSDocEnumTag | undefined; /** Gets the JSDoc this tag for the node if present */ function getJSDocThisTag(node: Node): JSDocThisTag | undefined; /** Gets the JSDoc return tag for the node if present */ @@ -3412,6 +3419,7 @@ declare namespace ts { function isJSDoc(node: Node): node is JSDoc; function isJSDocAugmentsTag(node: Node): node is JSDocAugmentsTag; function isJSDocClassTag(node: Node): node is JSDocClassTag; + function isJSDocEnumTag(node: Node): node is JSDocEnumTag; function isJSDocThisTag(node: Node): node is JSDocThisTag; function isJSDocParameterTag(node: Node): node is JSDocParameterTag; function isJSDocReturnTag(node: Node): node is JSDocReturnTag; diff --git a/tests/baselines/reference/enumTag.errors.txt b/tests/baselines/reference/enumTag.errors.txt index 89faa3cbf8d79..0c2524a1dc1a9 100644 --- a/tests/baselines/reference/enumTag.errors.txt +++ b/tests/baselines/reference/enumTag.errors.txt @@ -48,5 +48,18 @@ tests/cases/conformance/jsdoc/a.js(37,16): error TS2339: Property 'UNKNOWN' does ~~~~~~~ !!! error TS2339: Property 'UNKNOWN' does not exist on type '{ START: string; MIDDLE: string; END: string; MISTAKE: number; OK_I_GUESS: number; }'. v = Second.MISTAKE // meh..ok, I guess? + v = 'something else' // allowed, like Typescript's classic enums and unlike its string enums } + /** @param {string} s */ + function ff(s) { + // element access with arbitrary string is an error only with noImplicitAny + if (!Target[s]) { + return null + } + else { + return Target[s] + } + } + + \ No newline at end of file diff --git a/tests/baselines/reference/enumTag.symbols b/tests/baselines/reference/enumTag.symbols index 9515f9031d6de..ed0c11522f427 100644 --- a/tests/baselines/reference/enumTag.symbols +++ b/tests/baselines/reference/enumTag.symbols @@ -94,5 +94,28 @@ function consume(t,s,f) { >Second.MISTAKE : Symbol(MISTAKE, Decl(a.js, 10, 16)) >Second : Symbol(Second, Decl(a.js, 10, 5)) >MISTAKE : Symbol(MISTAKE, Decl(a.js, 10, 16)) + + v = 'something else' // allowed, like Typescript's classic enums and unlike its string enums +>v : Symbol(v, Decl(a.js, 35, 7)) } +/** @param {string} s */ +function ff(s) { +>ff : Symbol(ff, Decl(a.js, 39, 1)) +>s : Symbol(s, Decl(a.js, 41, 12)) + + // element access with arbitrary string is an error only with noImplicitAny + if (!Target[s]) { +>Target : Symbol(Target, Decl(a.js, 1, 5)) +>s : Symbol(s, Decl(a.js, 41, 12)) + + return null + } + else { + return Target[s] +>Target : Symbol(Target, Decl(a.js, 1, 5)) +>s : Symbol(s, Decl(a.js, 41, 12)) + } +} + + diff --git a/tests/baselines/reference/enumTag.types b/tests/baselines/reference/enumTag.types index d7b649a676fdc..fa8e537b6f583 100644 --- a/tests/baselines/reference/enumTag.types +++ b/tests/baselines/reference/enumTag.types @@ -116,5 +116,34 @@ function consume(t,s,f) { >Second.MISTAKE : string >Second : { MISTAKE: string; OK: number; FINE: number; } >MISTAKE : string + + v = 'something else' // allowed, like Typescript's classic enums and unlike its string enums +>v = 'something else' : "something else" +>v : string +>'something else' : "something else" } +/** @param {string} s */ +function ff(s) { +>ff : (s: string) => any +>s : string + + // element access with arbitrary string is an error only with noImplicitAny + if (!Target[s]) { +>!Target[s] : boolean +>Target[s] : any +>Target : { START: string; MIDDLE: string; END: string; MISTAKE: number; OK_I_GUESS: number; } +>s : string + + return null +>null : null + } + else { + return Target[s] +>Target[s] : any +>Target : { START: string; MIDDLE: string; END: string; MISTAKE: number; OK_I_GUESS: number; } +>s : string + } +} + + diff --git a/tests/cases/conformance/jsdoc/enumTag.ts b/tests/cases/conformance/jsdoc/enumTag.ts index 8e1b97787189d..bd740d879a18c 100644 --- a/tests/cases/conformance/jsdoc/enumTag.ts +++ b/tests/cases/conformance/jsdoc/enumTag.ts @@ -40,4 +40,17 @@ function consume(t,s,f) { var v = Target.START v = Target.UNKNOWN // error, can't find 'UNKNOWN' v = Second.MISTAKE // meh..ok, I guess? + v = 'something else' // allowed, like Typescript's classic enums and unlike its string enums } +/** @param {string} s */ +function ff(s) { + // element access with arbitrary string is an error only with noImplicitAny + if (!Target[s]) { + return null + } + else { + return Target[s] + } +} + +