Skip to content

Bind a jsdoc enum as SymbolFlags.TypeAlias and not SymbolFlags.Enum #32520

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 10 additions & 11 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ namespace ts {
let thisParentContainer: Node; // Container one level up
let blockScopeContainer: Node;
let lastContainer: Node;
let delayedTypeAliases: (JSDocTypedefTag | JSDocCallbackTag)[];
let delayedTypeAliases: (JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag)[];
let seenThisKeyword: boolean;

// state used by control flow analysis
Expand Down Expand Up @@ -732,7 +732,8 @@ namespace ts {
break;
case SyntaxKind.JSDocTypedefTag:
case SyntaxKind.JSDocCallbackTag:
bindJSDocTypeAlias(node as JSDocTypedefTag | JSDocCallbackTag);
case SyntaxKind.JSDocEnumTag:
bindJSDocTypeAlias(node as JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag);
break;
// In source files and blocks, bind functions first to match hoisting that occurs at runtime
case SyntaxKind.SourceFile: {
Expand Down Expand Up @@ -1436,9 +1437,9 @@ namespace ts {
}
}

function bindJSDocTypeAlias(node: JSDocTypedefTag | JSDocCallbackTag) {
function bindJSDocTypeAlias(node: JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag) {
node.tagName.parent = node;
if (node.fullName) {
if (node.kind !== SyntaxKind.JSDocEnumTag && node.fullName) {
setParentPointers(node, node.fullName);
}
}
Expand Down Expand Up @@ -1805,7 +1806,7 @@ namespace ts {
currentFlow = { flags: FlowFlags.Start };
parent = typeAlias;
bind(typeAlias.typeExpression);
if (!typeAlias.fullName || typeAlias.fullName.kind === SyntaxKind.Identifier) {
if (isJSDocEnumTag(typeAlias) || !typeAlias.fullName || typeAlias.fullName.kind === SyntaxKind.Identifier) {
parent = typeAlias.parent;
bindBlockScopedDeclaration(typeAlias, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes);
}
Expand Down Expand Up @@ -2319,7 +2320,8 @@ namespace ts {
return declareSymbolAndAddToSymbolTable(propTag, flags, SymbolFlags.PropertyExcludes);
case SyntaxKind.JSDocTypedefTag:
case SyntaxKind.JSDocCallbackTag:
return (delayedTypeAliases || (delayedTypeAliases = [])).push(node as JSDocTypedefTag | JSDocCallbackTag);
case SyntaxKind.JSDocEnumTag:
return (delayedTypeAliases || (delayedTypeAliases = [])).push(node as JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag);
}
}

Expand Down Expand Up @@ -2766,11 +2768,8 @@ namespace ts {
}

if (!isBindingPattern(node.name)) {
const isEnum = isInJSFile(node) && !!getJSDocEnumTag(node);
const enumFlags = (isEnum ? SymbolFlags.RegularEnum : SymbolFlags.None);
const enumExcludes = (isEnum ? SymbolFlags.RegularEnumExcludes : SymbolFlags.None);
if (isBlockOrCatchScoped(node)) {
bindBlockScopedDeclaration(node, SymbolFlags.BlockScopedVariable | enumFlags, SymbolFlags.BlockScopedVariableExcludes | enumExcludes);
bindBlockScopedDeclaration(node, SymbolFlags.BlockScopedVariable, SymbolFlags.BlockScopedVariableExcludes);
}
else if (isParameterDeclaration(node)) {
// It is safe to walk up parent chain to find whether the node is a destructuring parameter declaration
Expand All @@ -2785,7 +2784,7 @@ namespace ts {
declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes);
}
else {
declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable | enumFlags, SymbolFlags.FunctionScopedVariableExcludes | enumExcludes);
declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.FunctionScopedVariableExcludes);
}
}
}
Expand Down
30 changes: 11 additions & 19 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1680,6 +1680,7 @@ namespace ts {
break;
case SyntaxKind.JSDocTypedefTag:
case SyntaxKind.JSDocCallbackTag:
case SyntaxKind.JSDocEnumTag:
// js type aliases do not resolve names from their host, so skip past it
location = getJSDocHost(location);
break;
Expand Down Expand Up @@ -2025,7 +2026,7 @@ namespace ts {
// Block-scoped variables cannot be used before their definition
const declaration = find(
result.declarations,
d => isBlockOrCatchScoped(d) || isClassLike(d) || (d.kind === SyntaxKind.EnumDeclaration) || isInJSFile(d) && !!getJSDocEnumTag(d));
d => isBlockOrCatchScoped(d) || isClassLike(d) || (d.kind === SyntaxKind.EnumDeclaration));

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

Expand Down Expand Up @@ -4851,6 +4852,7 @@ namespace ts {
switch (node.kind) {
case SyntaxKind.JSDocCallbackTag:
case SyntaxKind.JSDocTypedefTag:
case SyntaxKind.JSDocEnumTag:
// Top-level jsdoc type aliases are considered exported
// 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
return !!(node.parent && node.parent.parent && node.parent.parent.parent && isSourceFile(node.parent.parent.parent));
Expand Down Expand Up @@ -6149,6 +6151,7 @@ namespace ts {
case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.JSDocTemplateTag:
case SyntaxKind.JSDocTypedefTag:
case SyntaxKind.JSDocEnumTag:
case SyntaxKind.JSDocCallbackTag:
case SyntaxKind.MappedType:
case SyntaxKind.ConditionalType:
Expand Down Expand Up @@ -6482,8 +6485,10 @@ namespace ts {
return errorType;
}

const declaration = <JSDocTypedefTag | JSDocCallbackTag | TypeAliasDeclaration>find(symbol.declarations, d =>
isJSDocTypeAlias(d) || d.kind === SyntaxKind.TypeAliasDeclaration);
const declaration = find(symbol.declarations, isTypeAlias);
if (!declaration) {
return Debug.fail("Type alias symbol with no valid declaration found");
}
const typeNode = isJSDocTypeAlias(declaration) ? declaration.typeExpression : declaration.type;
// If typeNode is missing, we will error in checkJSDocTypedefTag.
let type = typeNode ? getTypeFromTypeNode(typeNode) : errorType;
Expand All @@ -6500,7 +6505,7 @@ namespace ts {
}
else {
type = errorType;
error(declaration.name, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol));
error(isJSDocEnumTag(declaration) ? declaration : declaration.name || declaration, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol));
}
links.declaredType = type;
}
Expand Down Expand Up @@ -9152,21 +9157,6 @@ namespace ts {
return type;
}

// JS enums are 'string' or 'number', not an enum type.
const enumTag = isInJSFile(node) && symbol.valueDeclaration && getJSDocEnumTag(symbol.valueDeclaration);
if (enumTag) {
const links = getNodeLinks(enumTag);
if (!pushTypeResolution(enumTag, TypeSystemPropertyName.EnumTagType)) {
return errorType;
}
let type = enumTag.typeExpression ? getTypeFromTypeNode(enumTag.typeExpression) : errorType;
if (!popTypeResolution()) {
type = errorType;
error(node, Diagnostics.Enum_type_0_circularly_references_itself, symbolToString(symbol));
}
return (links.resolvedEnumType = type);
}

// Get type from reference to named type that cannot be generic (enum or type parameter)
const res = tryGetDeclaredTypeOfSymbol(symbol);
if (res) {
Expand Down Expand Up @@ -26329,6 +26319,7 @@ namespace ts {
// A jsdoc typedef and callback are, by definition, type aliases
case SyntaxKind.JSDocTypedefTag:
case SyntaxKind.JSDocCallbackTag:
case SyntaxKind.JSDocEnumTag:
return DeclarationSpaces.ExportType;
case SyntaxKind.ModuleDeclaration:
return isAmbientModule(d as ModuleDeclaration) || getModuleInstanceState(d as ModuleDeclaration) !== ModuleInstanceState.NonInstantiated
Expand Down Expand Up @@ -30245,6 +30236,7 @@ namespace ts {
return checkJSDocAugmentsTag(node as JSDocAugmentsTag);
case SyntaxKind.JSDocTypedefTag:
case SyntaxKind.JSDocCallbackTag:
case SyntaxKind.JSDocEnumTag:
return checkJSDocTypeAliasTag(node as JSDocTypedefTag);
case SyntaxKind.JSDocTemplateTag:
return checkJSDocTemplateTag(node as JSDocTemplateTag);
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2466,7 +2466,8 @@ namespace ts {
kind: SyntaxKind.JSDocClassTag;
}

export interface JSDocEnumTag extends JSDocTag {
export interface JSDocEnumTag extends JSDocTag, Declaration {
parent: JSDoc;
kind: SyntaxKind.JSDocEnumTag;
typeExpression?: JSDocTypeExpression;
}
Expand Down
10 changes: 6 additions & 4 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2147,11 +2147,11 @@ namespace ts {
return !!name && name.escapedText === "new";
}

export function isJSDocTypeAlias(node: Node): node is JSDocTypedefTag | JSDocCallbackTag {
return node.kind === SyntaxKind.JSDocTypedefTag || node.kind === SyntaxKind.JSDocCallbackTag;
export function isJSDocTypeAlias(node: Node): node is JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag {
return node.kind === SyntaxKind.JSDocTypedefTag || node.kind === SyntaxKind.JSDocCallbackTag || node.kind === SyntaxKind.JSDocEnumTag;
}

export function isTypeAlias(node: Node): node is JSDocTypedefTag | JSDocCallbackTag | TypeAliasDeclaration {
export function isTypeAlias(node: Node): node is JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag | TypeAliasDeclaration {
return isJSDocTypeAlias(node) || isTypeAliasDeclaration(node);
}

Expand Down Expand Up @@ -5091,7 +5091,7 @@ namespace ts {
* attempt to draw the name from the node the declaration is on (as that declaration is what its' symbol
* will be merged with)
*/
function nameForNamelessJSDocTypedef(declaration: JSDocTypedefTag): Identifier | undefined {
function nameForNamelessJSDocTypedef(declaration: JSDocTypedefTag | JSDocEnumTag): Identifier | undefined {
const hostNode = declaration.parent.parent;
if (!hostNode) {
return undefined;
Expand Down Expand Up @@ -5177,6 +5177,8 @@ namespace ts {
}
case SyntaxKind.JSDocTypedefTag:
return getNameOfJSDocTypedef(declaration as JSDocTypedefTag);
case SyntaxKind.JSDocEnumTag:
return nameForNamelessJSDocTypedef(declaration as JSDocEnumTag);
case SyntaxKind.ExportAssignment: {
const { expression } = declaration as ExportAssignment;
return isIdentifier(expression) ? expression : undefined;
Expand Down
2 changes: 1 addition & 1 deletion src/harness/typeWriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ class TypeWriterWalker {
if (!isSymbolWalk) {
// Don't try to get the type of something that's already a type.
// Exception for `T` in `type T = something` because that may evaluate to some interesting type.
if (ts.isPartOfTypeNode(node) || ts.isIdentifier(node) && !(ts.getMeaningFromDeclaration(node.parent) & ts.SemanticMeaning.Value) && !(ts.isTypeAlias(node.parent) && node.parent.name === node)) {
if (ts.isPartOfTypeNode(node) || ts.isIdentifier(node) && !(ts.getMeaningFromDeclaration(node.parent) & ts.SemanticMeaning.Value) && !(ts.isTypeAliasDeclaration(node.parent) && node.parent.name === node)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me see if I can summarise what this means: we now bail for @callback, @typedef and @enum and some (which?) type aliases. Before it looks like there was a check that @callback and @typedef would usually fail, but sometimes incorrectly pass. But I'm not sure in what circumstances.

Copy link
Member Author

@weswigham weswigham Jul 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getMeaningFromDeclaration on a JSDoc (as is the parent of all of these) isn't handled afaik, so those cases are already unreachable, as it's defaulted to All.

return undefined;
}

Expand Down
3 changes: 2 additions & 1 deletion tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1589,7 +1589,8 @@ declare namespace ts {
interface JSDocClassTag extends JSDocTag {
kind: SyntaxKind.JSDocClassTag;
}
interface JSDocEnumTag extends JSDocTag {
interface JSDocEnumTag extends JSDocTag, Declaration {
parent: JSDoc;
kind: SyntaxKind.JSDocEnumTag;
typeExpression?: JSDocTypeExpression;
}
Expand Down
3 changes: 2 additions & 1 deletion tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1589,7 +1589,8 @@ declare namespace ts {
interface JSDocClassTag extends JSDocTag {
kind: SyntaxKind.JSDocClassTag;
}
interface JSDocEnumTag extends JSDocTag {
interface JSDocEnumTag extends JSDocTag, Declaration {
parent: JSDoc;
kind: SyntaxKind.JSDocEnumTag;
typeExpression?: JSDocTypeExpression;
}
Expand Down
16 changes: 8 additions & 8 deletions tests/baselines/reference/enumTag.symbols
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
=== tests/cases/conformance/jsdoc/a.js ===
/** @enum {string} */
const Target = {
>Target : Symbol(Target, Decl(a.js, 1, 5))
>Target : Symbol(Target, Decl(a.js, 1, 5), Decl(a.js, 0, 4))

START: "start",
>START : Symbol(START, Decl(a.js, 1, 16))
Expand All @@ -21,7 +21,7 @@ const Target = {
}
/** @enum number */
const Second = {
>Second : Symbol(Second, Decl(a.js, 10, 5))
>Second : Symbol(Second, Decl(a.js, 10, 5), Decl(a.js, 9, 4))

MISTAKE: "end",
>MISTAKE : Symbol(MISTAKE, Decl(a.js, 10, 16))
Expand All @@ -35,7 +35,7 @@ const Second = {
}
/** @enum {function(number): number} */
const Fs = {
>Fs : Symbol(Fs, Decl(a.js, 17, 5))
>Fs : Symbol(Fs, Decl(a.js, 17, 5), Decl(a.js, 16, 4))

ADD1: n => n + 1,
>ADD1 : Symbol(ADD1, Decl(a.js, 17, 12))
Expand Down Expand Up @@ -82,17 +82,17 @@ function consume(t,s,f) {
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))
>Target : Symbol(Target, Decl(a.js, 1, 5), Decl(a.js, 0, 4))
>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))
>Target : Symbol(Target, Decl(a.js, 1, 5), Decl(a.js, 0, 4))

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))
>Second : Symbol(Second, Decl(a.js, 10, 5), Decl(a.js, 9, 4))
>MISTAKE : Symbol(MISTAKE, Decl(a.js, 10, 16))

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

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

return null
}
else {
return Target[s]
>Target : Symbol(Target, Decl(a.js, 1, 5))
>Target : Symbol(Target, Decl(a.js, 1, 5), Decl(a.js, 0, 4))
>s : Symbol(s, Decl(a.js, 41, 12))
}
}
Expand Down
6 changes: 3 additions & 3 deletions tests/baselines/reference/enumTagCircularReference.errors.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
tests/cases/conformance/jsdoc/bug27142.js(1,12): error TS2586: Enum type 'E' circularly references itself.
tests/cases/conformance/jsdoc/bug27142.js(1,5): error TS2456: Type alias 'E' circularly references itself.


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

2 changes: 1 addition & 1 deletion tests/baselines/reference/enumTagCircularReference.symbols
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
=== tests/cases/conformance/jsdoc/bug27142.js ===
/** @enum {E} */
const E = { x: 0 };
>E : Symbol(E, Decl(bug27142.js, 1, 5))
>E : Symbol(E, Decl(bug27142.js, 1, 5), Decl(bug27142.js, 0, 4))
>x : Symbol(x, Decl(bug27142.js, 1, 11))

2 changes: 1 addition & 1 deletion tests/baselines/reference/enumTagImported.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const tist = TestEnum.ADD
=== tests/cases/conformance/jsdoc/mod1.js ===
/** @enum {string} */
export const TestEnum = {
>TestEnum : Symbol(TestEnum, Decl(mod1.js, 1, 12))
>TestEnum : Symbol(TestEnum, Decl(mod1.js, 1, 12), Decl(mod1.js, 0, 4))

ADD: 'add',
>ADD : Symbol(ADD, Decl(mod1.js, 1, 25))
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/enumTagUseBeforeDefCrash.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* @enum {number}
*/
var foo = { };
>foo : Symbol(foo, Decl(bug27134.js, 3, 3))
>foo : Symbol(foo, Decl(bug27134.js, 3, 3), Decl(bug27134.js, 1, 3))

/**
* @type {foo}
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/enumTagUseBeforeDefCrash.types
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* @enum {number}
*/
var foo = { };
>foo : typeof foo
>foo : {}
>{ } : {}

/**
Expand Down
2 changes: 1 addition & 1 deletion tests/cases/fourslash/findAllRefs_jsEnum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
////const e = [|E|].A;

verify.singleReferenceGroup(
`enum E
`type E = string
const E: {
A: string;
}`, "E");
5 changes: 2 additions & 3 deletions tests/cases/fourslash/quickInfoJsdocEnum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@
verify.noErrors();

verify.quickInfoAt("type",
`enum E`,
`type E = number`,
"Doc");
verify.quickInfoAt("value",
`enum E
const E: {
`const E: {
A: number;
}`,
"Doc");
2 changes: 1 addition & 1 deletion tests/cases/user/prettier/prettier
Submodule prettier updated 174 files