Skip to content

Commit 2a4930f

Browse files
authored
Bind a jsdoc enum as SymbolFlags.TypeAlias and not SymbolFlags.Enum (#32520)
* Bind a jsdoc enum as SymbolFlags.TypeAlias and not SymbolFlags.Enum * Actually include an @enum tag as a declaration * Add enum tag refs into a couple more syntax kind lists * accept symbol baseline update
1 parent 2fe3c1b commit 2a4930f

15 files changed

+52
-57
lines changed

src/compiler/binder.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ namespace ts {
120120
let thisParentContainer: Node; // Container one level up
121121
let blockScopeContainer: Node;
122122
let lastContainer: Node;
123-
let delayedTypeAliases: (JSDocTypedefTag | JSDocCallbackTag)[];
123+
let delayedTypeAliases: (JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag)[];
124124
let seenThisKeyword: boolean;
125125

126126
// state used by control flow analysis
@@ -732,7 +732,8 @@ namespace ts {
732732
break;
733733
case SyntaxKind.JSDocTypedefTag:
734734
case SyntaxKind.JSDocCallbackTag:
735-
bindJSDocTypeAlias(node as JSDocTypedefTag | JSDocCallbackTag);
735+
case SyntaxKind.JSDocEnumTag:
736+
bindJSDocTypeAlias(node as JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag);
736737
break;
737738
// In source files and blocks, bind functions first to match hoisting that occurs at runtime
738739
case SyntaxKind.SourceFile: {
@@ -1436,9 +1437,9 @@ namespace ts {
14361437
}
14371438
}
14381439

1439-
function bindJSDocTypeAlias(node: JSDocTypedefTag | JSDocCallbackTag) {
1440+
function bindJSDocTypeAlias(node: JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag) {
14401441
node.tagName.parent = node;
1441-
if (node.fullName) {
1442+
if (node.kind !== SyntaxKind.JSDocEnumTag && node.fullName) {
14421443
setParentPointers(node, node.fullName);
14431444
}
14441445
}
@@ -1805,7 +1806,7 @@ namespace ts {
18051806
currentFlow = { flags: FlowFlags.Start };
18061807
parent = typeAlias;
18071808
bind(typeAlias.typeExpression);
1808-
if (!typeAlias.fullName || typeAlias.fullName.kind === SyntaxKind.Identifier) {
1809+
if (isJSDocEnumTag(typeAlias) || !typeAlias.fullName || typeAlias.fullName.kind === SyntaxKind.Identifier) {
18091810
parent = typeAlias.parent;
18101811
bindBlockScopedDeclaration(typeAlias, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes);
18111812
}
@@ -2319,7 +2320,8 @@ namespace ts {
23192320
return declareSymbolAndAddToSymbolTable(propTag, flags, SymbolFlags.PropertyExcludes);
23202321
case SyntaxKind.JSDocTypedefTag:
23212322
case SyntaxKind.JSDocCallbackTag:
2322-
return (delayedTypeAliases || (delayedTypeAliases = [])).push(node as JSDocTypedefTag | JSDocCallbackTag);
2323+
case SyntaxKind.JSDocEnumTag:
2324+
return (delayedTypeAliases || (delayedTypeAliases = [])).push(node as JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag);
23232325
}
23242326
}
23252327

@@ -2766,11 +2768,8 @@ namespace ts {
27662768
}
27672769

27682770
if (!isBindingPattern(node.name)) {
2769-
const isEnum = isInJSFile(node) && !!getJSDocEnumTag(node);
2770-
const enumFlags = (isEnum ? SymbolFlags.RegularEnum : SymbolFlags.None);
2771-
const enumExcludes = (isEnum ? SymbolFlags.RegularEnumExcludes : SymbolFlags.None);
27722771
if (isBlockOrCatchScoped(node)) {
2773-
bindBlockScopedDeclaration(node, SymbolFlags.BlockScopedVariable | enumFlags, SymbolFlags.BlockScopedVariableExcludes | enumExcludes);
2772+
bindBlockScopedDeclaration(node, SymbolFlags.BlockScopedVariable, SymbolFlags.BlockScopedVariableExcludes);
27742773
}
27752774
else if (isParameterDeclaration(node)) {
27762775
// It is safe to walk up parent chain to find whether the node is a destructuring parameter declaration
@@ -2785,7 +2784,7 @@ namespace ts {
27852784
declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes);
27862785
}
27872786
else {
2788-
declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable | enumFlags, SymbolFlags.FunctionScopedVariableExcludes | enumExcludes);
2787+
declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.FunctionScopedVariableExcludes);
27892788
}
27902789
}
27912790
}

src/compiler/checker.ts

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1680,6 +1680,7 @@ namespace ts {
16801680
break;
16811681
case SyntaxKind.JSDocTypedefTag:
16821682
case SyntaxKind.JSDocCallbackTag:
1683+
case SyntaxKind.JSDocEnumTag:
16831684
// js type aliases do not resolve names from their host, so skip past it
16841685
location = getJSDocHost(location);
16851686
break;
@@ -2025,7 +2026,7 @@ namespace ts {
20252026
// Block-scoped variables cannot be used before their definition
20262027
const declaration = find(
20272028
result.declarations,
2028-
d => isBlockOrCatchScoped(d) || isClassLike(d) || (d.kind === SyntaxKind.EnumDeclaration) || isInJSFile(d) && !!getJSDocEnumTag(d));
2029+
d => isBlockOrCatchScoped(d) || isClassLike(d) || (d.kind === SyntaxKind.EnumDeclaration));
20292030

20302031
if (declaration === undefined) return Debug.fail("Declaration to checkResolvedBlockScopedVariable is undefined");
20312032

@@ -4851,6 +4852,7 @@ namespace ts {
48514852
switch (node.kind) {
48524853
case SyntaxKind.JSDocCallbackTag:
48534854
case SyntaxKind.JSDocTypedefTag:
4855+
case SyntaxKind.JSDocEnumTag:
48544856
// Top-level jsdoc type aliases are considered exported
48554857
// First parent is comment node, second is hosting declaration or token; we only care about those tokens or declarations whose parent is a source file
48564858
return !!(node.parent && node.parent.parent && node.parent.parent.parent && isSourceFile(node.parent.parent.parent));
@@ -6156,6 +6158,7 @@ namespace ts {
61566158
case SyntaxKind.TypeAliasDeclaration:
61576159
case SyntaxKind.JSDocTemplateTag:
61586160
case SyntaxKind.JSDocTypedefTag:
6161+
case SyntaxKind.JSDocEnumTag:
61596162
case SyntaxKind.JSDocCallbackTag:
61606163
case SyntaxKind.MappedType:
61616164
case SyntaxKind.ConditionalType:
@@ -6489,8 +6492,10 @@ namespace ts {
64896492
return errorType;
64906493
}
64916494

6492-
const declaration = <JSDocTypedefTag | JSDocCallbackTag | TypeAliasDeclaration>find(symbol.declarations, d =>
6493-
isJSDocTypeAlias(d) || d.kind === SyntaxKind.TypeAliasDeclaration);
6495+
const declaration = find(symbol.declarations, isTypeAlias);
6496+
if (!declaration) {
6497+
return Debug.fail("Type alias symbol with no valid declaration found");
6498+
}
64946499
const typeNode = isJSDocTypeAlias(declaration) ? declaration.typeExpression : declaration.type;
64956500
// If typeNode is missing, we will error in checkJSDocTypedefTag.
64966501
let type = typeNode ? getTypeFromTypeNode(typeNode) : errorType;
@@ -6507,7 +6512,7 @@ namespace ts {
65076512
}
65086513
else {
65096514
type = errorType;
6510-
error(declaration.name, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol));
6515+
error(isJSDocEnumTag(declaration) ? declaration : declaration.name || declaration, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol));
65116516
}
65126517
links.declaredType = type;
65136518
}
@@ -9159,21 +9164,6 @@ namespace ts {
91599164
return type;
91609165
}
91619166

9162-
// JS enums are 'string' or 'number', not an enum type.
9163-
const enumTag = isInJSFile(node) && symbol.valueDeclaration && getJSDocEnumTag(symbol.valueDeclaration);
9164-
if (enumTag) {
9165-
const links = getNodeLinks(enumTag);
9166-
if (!pushTypeResolution(enumTag, TypeSystemPropertyName.EnumTagType)) {
9167-
return errorType;
9168-
}
9169-
let type = enumTag.typeExpression ? getTypeFromTypeNode(enumTag.typeExpression) : errorType;
9170-
if (!popTypeResolution()) {
9171-
type = errorType;
9172-
error(node, Diagnostics.Enum_type_0_circularly_references_itself, symbolToString(symbol));
9173-
}
9174-
return (links.resolvedEnumType = type);
9175-
}
9176-
91779167
// Get type from reference to named type that cannot be generic (enum or type parameter)
91789168
const res = tryGetDeclaredTypeOfSymbol(symbol);
91799169
if (res) {
@@ -26409,6 +26399,7 @@ namespace ts {
2640926399
// A jsdoc typedef and callback are, by definition, type aliases
2641026400
case SyntaxKind.JSDocTypedefTag:
2641126401
case SyntaxKind.JSDocCallbackTag:
26402+
case SyntaxKind.JSDocEnumTag:
2641226403
return DeclarationSpaces.ExportType;
2641326404
case SyntaxKind.ModuleDeclaration:
2641426405
return isAmbientModule(d as ModuleDeclaration) || getModuleInstanceState(d as ModuleDeclaration) !== ModuleInstanceState.NonInstantiated
@@ -30325,6 +30316,7 @@ namespace ts {
3032530316
return checkJSDocAugmentsTag(node as JSDocAugmentsTag);
3032630317
case SyntaxKind.JSDocTypedefTag:
3032730318
case SyntaxKind.JSDocCallbackTag:
30319+
case SyntaxKind.JSDocEnumTag:
3032830320
return checkJSDocTypeAliasTag(node as JSDocTypedefTag);
3032930321
case SyntaxKind.JSDocTemplateTag:
3033030322
return checkJSDocTemplateTag(node as JSDocTemplateTag);

src/compiler/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2466,7 +2466,8 @@ namespace ts {
24662466
kind: SyntaxKind.JSDocClassTag;
24672467
}
24682468

2469-
export interface JSDocEnumTag extends JSDocTag {
2469+
export interface JSDocEnumTag extends JSDocTag, Declaration {
2470+
parent: JSDoc;
24702471
kind: SyntaxKind.JSDocEnumTag;
24712472
typeExpression?: JSDocTypeExpression;
24722473
}

src/compiler/utilities.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2147,11 +2147,11 @@ namespace ts {
21472147
return !!name && name.escapedText === "new";
21482148
}
21492149

2150-
export function isJSDocTypeAlias(node: Node): node is JSDocTypedefTag | JSDocCallbackTag {
2151-
return node.kind === SyntaxKind.JSDocTypedefTag || node.kind === SyntaxKind.JSDocCallbackTag;
2150+
export function isJSDocTypeAlias(node: Node): node is JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag {
2151+
return node.kind === SyntaxKind.JSDocTypedefTag || node.kind === SyntaxKind.JSDocCallbackTag || node.kind === SyntaxKind.JSDocEnumTag;
21522152
}
21532153

2154-
export function isTypeAlias(node: Node): node is JSDocTypedefTag | JSDocCallbackTag | TypeAliasDeclaration {
2154+
export function isTypeAlias(node: Node): node is JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag | TypeAliasDeclaration {
21552155
return isJSDocTypeAlias(node) || isTypeAliasDeclaration(node);
21562156
}
21572157

@@ -5091,7 +5091,7 @@ namespace ts {
50915091
* attempt to draw the name from the node the declaration is on (as that declaration is what its' symbol
50925092
* will be merged with)
50935093
*/
5094-
function nameForNamelessJSDocTypedef(declaration: JSDocTypedefTag): Identifier | undefined {
5094+
function nameForNamelessJSDocTypedef(declaration: JSDocTypedefTag | JSDocEnumTag): Identifier | undefined {
50955095
const hostNode = declaration.parent.parent;
50965096
if (!hostNode) {
50975097
return undefined;
@@ -5177,6 +5177,8 @@ namespace ts {
51775177
}
51785178
case SyntaxKind.JSDocTypedefTag:
51795179
return getNameOfJSDocTypedef(declaration as JSDocTypedefTag);
5180+
case SyntaxKind.JSDocEnumTag:
5181+
return nameForNamelessJSDocTypedef(declaration as JSDocEnumTag);
51805182
case SyntaxKind.ExportAssignment: {
51815183
const { expression } = declaration as ExportAssignment;
51825184
return isIdentifier(expression) ? expression : undefined;

src/harness/typeWriter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ class TypeWriterWalker {
9797
if (!isSymbolWalk) {
9898
// Don't try to get the type of something that's already a type.
9999
// Exception for `T` in `type T = something` because that may evaluate to some interesting type.
100-
if (ts.isPartOfTypeNode(node) || ts.isIdentifier(node) && !(ts.getMeaningFromDeclaration(node.parent) & ts.SemanticMeaning.Value) && !(ts.isTypeAlias(node.parent) && node.parent.name === node)) {
100+
if (ts.isPartOfTypeNode(node) || ts.isIdentifier(node) && !(ts.getMeaningFromDeclaration(node.parent) & ts.SemanticMeaning.Value) && !(ts.isTypeAliasDeclaration(node.parent) && node.parent.name === node)) {
101101
return undefined;
102102
}
103103

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1589,7 +1589,8 @@ declare namespace ts {
15891589
interface JSDocClassTag extends JSDocTag {
15901590
kind: SyntaxKind.JSDocClassTag;
15911591
}
1592-
interface JSDocEnumTag extends JSDocTag {
1592+
interface JSDocEnumTag extends JSDocTag, Declaration {
1593+
parent: JSDoc;
15931594
kind: SyntaxKind.JSDocEnumTag;
15941595
typeExpression?: JSDocTypeExpression;
15951596
}

tests/baselines/reference/api/typescript.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1589,7 +1589,8 @@ declare namespace ts {
15891589
interface JSDocClassTag extends JSDocTag {
15901590
kind: SyntaxKind.JSDocClassTag;
15911591
}
1592-
interface JSDocEnumTag extends JSDocTag {
1592+
interface JSDocEnumTag extends JSDocTag, Declaration {
1593+
parent: JSDoc;
15931594
kind: SyntaxKind.JSDocEnumTag;
15941595
typeExpression?: JSDocTypeExpression;
15951596
}

tests/baselines/reference/enumTag.symbols

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
=== tests/cases/conformance/jsdoc/a.js ===
22
/** @enum {string} */
33
const Target = {
4-
>Target : Symbol(Target, Decl(a.js, 1, 5))
4+
>Target : Symbol(Target, Decl(a.js, 1, 5), Decl(a.js, 0, 4))
55

66
START: "start",
77
>START : Symbol(START, Decl(a.js, 1, 16))
@@ -21,7 +21,7 @@ const Target = {
2121
}
2222
/** @enum number */
2323
const Second = {
24-
>Second : Symbol(Second, Decl(a.js, 10, 5))
24+
>Second : Symbol(Second, Decl(a.js, 10, 5), Decl(a.js, 9, 4))
2525

2626
MISTAKE: "end",
2727
>MISTAKE : Symbol(MISTAKE, Decl(a.js, 10, 16))
@@ -35,7 +35,7 @@ const Second = {
3535
}
3636
/** @enum {function(number): number} */
3737
const Fs = {
38-
>Fs : Symbol(Fs, Decl(a.js, 17, 5))
38+
>Fs : Symbol(Fs, Decl(a.js, 17, 5), Decl(a.js, 16, 4))
3939

4040
ADD1: n => n + 1,
4141
>ADD1 : Symbol(ADD1, Decl(a.js, 17, 12))
@@ -82,17 +82,17 @@ function consume(t,s,f) {
8282
var v = Target.START
8383
>v : Symbol(v, Decl(a.js, 35, 7))
8484
>Target.START : Symbol(START, Decl(a.js, 1, 16))
85-
>Target : Symbol(Target, Decl(a.js, 1, 5))
85+
>Target : Symbol(Target, Decl(a.js, 1, 5), Decl(a.js, 0, 4))
8686
>START : Symbol(START, Decl(a.js, 1, 16))
8787

8888
v = Target.UNKNOWN // error, can't find 'UNKNOWN'
8989
>v : Symbol(v, Decl(a.js, 35, 7))
90-
>Target : Symbol(Target, Decl(a.js, 1, 5))
90+
>Target : Symbol(Target, Decl(a.js, 1, 5), Decl(a.js, 0, 4))
9191

9292
v = Second.MISTAKE // meh..ok, I guess?
9393
>v : Symbol(v, Decl(a.js, 35, 7))
9494
>Second.MISTAKE : Symbol(MISTAKE, Decl(a.js, 10, 16))
95-
>Second : Symbol(Second, Decl(a.js, 10, 5))
95+
>Second : Symbol(Second, Decl(a.js, 10, 5), Decl(a.js, 9, 4))
9696
>MISTAKE : Symbol(MISTAKE, Decl(a.js, 10, 16))
9797

9898
v = 'something else' // allowed, like Typescript's classic enums and unlike its string enums
@@ -105,14 +105,14 @@ function ff(s) {
105105

106106
// element access with arbitrary string is an error only with noImplicitAny
107107
if (!Target[s]) {
108-
>Target : Symbol(Target, Decl(a.js, 1, 5))
108+
>Target : Symbol(Target, Decl(a.js, 1, 5), Decl(a.js, 0, 4))
109109
>s : Symbol(s, Decl(a.js, 41, 12))
110110

111111
return null
112112
}
113113
else {
114114
return Target[s]
115-
>Target : Symbol(Target, Decl(a.js, 1, 5))
115+
>Target : Symbol(Target, Decl(a.js, 1, 5), Decl(a.js, 0, 4))
116116
>s : Symbol(s, Decl(a.js, 41, 12))
117117
}
118118
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
tests/cases/conformance/jsdoc/bug27142.js(1,12): error TS2586: Enum type 'E' circularly references itself.
1+
tests/cases/conformance/jsdoc/bug27142.js(1,5): error TS2456: Type alias 'E' circularly references itself.
22

33

44
==== tests/cases/conformance/jsdoc/bug27142.js (1 errors) ====
55
/** @enum {E} */
6-
~
7-
!!! error TS2586: Enum type 'E' circularly references itself.
6+
~~~~~~~~~
7+
!!! error TS2456: Type alias 'E' circularly references itself.
88
const E = { x: 0 };
99

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
=== tests/cases/conformance/jsdoc/bug27142.js ===
22
/** @enum {E} */
33
const E = { x: 0 };
4-
>E : Symbol(E, Decl(bug27142.js, 1, 5))
4+
>E : Symbol(E, Decl(bug27142.js, 1, 5), Decl(bug27142.js, 0, 4))
55
>x : Symbol(x, Decl(bug27142.js, 1, 11))
66

tests/baselines/reference/enumTagImported.symbols

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const tist = TestEnum.ADD
2323
=== tests/cases/conformance/jsdoc/mod1.js ===
2424
/** @enum {string} */
2525
export const TestEnum = {
26-
>TestEnum : Symbol(TestEnum, Decl(mod1.js, 1, 12))
26+
>TestEnum : Symbol(TestEnum, Decl(mod1.js, 1, 12), Decl(mod1.js, 0, 4))
2727

2828
ADD: 'add',
2929
>ADD : Symbol(ADD, Decl(mod1.js, 1, 25))

tests/baselines/reference/enumTagUseBeforeDefCrash.symbols

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @enum {number}
44
*/
55
var foo = { };
6-
>foo : Symbol(foo, Decl(bug27134.js, 3, 3))
6+
>foo : Symbol(foo, Decl(bug27134.js, 3, 3), Decl(bug27134.js, 1, 3))
77

88
/**
99
* @type {foo}

tests/baselines/reference/enumTagUseBeforeDefCrash.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @enum {number}
44
*/
55
var foo = { };
6-
>foo : typeof foo
6+
>foo : {}
77
>{ } : {}
88

99
/**

tests/cases/fourslash/findAllRefs_jsEnum.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
////const e = [|E|].A;
1111

1212
verify.singleReferenceGroup(
13-
`enum E
13+
`type E = string
1414
const E: {
1515
A: string;
1616
}`, "E");

tests/cases/fourslash/quickInfoJsdocEnum.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@
1818
verify.noErrors();
1919

2020
verify.quickInfoAt("type",
21-
`enum E`,
21+
`type E = number`,
2222
"Doc");
2323
verify.quickInfoAt("value",
24-
`enum E
25-
const E: {
24+
`const E: {
2625
A: number;
2726
}`,
2827
"Doc");

0 commit comments

Comments
 (0)