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
This commit is contained in:
Wesley Wigham 2019-07-26 13:57:22 -07:00 committed by GitHub
parent 2fe3c1b3b7
commit 2a4930f4ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 52 additions and 57 deletions

View File

@ -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
@ -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: {
@ -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);
}
}
@ -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);
}
@ -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);
}
}
@ -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
@ -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);
}
}
}

View File

@ -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;
@ -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");
@ -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));
@ -6156,6 +6158,7 @@ namespace ts {
case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.JSDocTemplateTag:
case SyntaxKind.JSDocTypedefTag:
case SyntaxKind.JSDocEnumTag:
case SyntaxKind.JSDocCallbackTag:
case SyntaxKind.MappedType:
case SyntaxKind.ConditionalType:
@ -6489,8 +6492,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;
@ -6507,7 +6512,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;
}
@ -9159,21 +9164,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) {
@ -26409,6 +26399,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
@ -30325,6 +30316,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);

View File

@ -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;
}

View File

@ -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);
}
@ -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;
@ -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;

View File

@ -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)) {
return undefined;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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))
@ -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))
@ -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))
@ -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
@ -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))
}
}

View File

@ -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 };

View File

@ -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))

View File

@ -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))

View File

@ -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}

View File

@ -3,7 +3,7 @@
* @enum {number}
*/
var foo = { };
>foo : typeof foo
>foo : {}
>{ } : {}
/**

View File

@ -10,7 +10,7 @@
////const e = [|E|].A;
verify.singleReferenceGroup(
`enum E
`type E = string
const E: {
A: string;
}`, "E");

View File

@ -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");