Avoid the double-symbol trick for enums

Nameless jsdoc typedefs have their exportedness controlled by the
exportedness of the location they pull their name from.

Fixes #33575.
This commit is contained in:
Eli Barzilay 2020-08-07 15:29:58 -04:00
parent 1f5caf554c
commit 620e260576
6 changed files with 93 additions and 7 deletions

View File

@ -541,7 +541,7 @@ namespace ts {
}
function declareModuleMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags): Symbol {
const hasExportModifier = getCombinedModifierFlags(node) & ModifierFlags.Export;
const hasExportModifier = !!(getCombinedModifierFlags(node) & ModifierFlags.Export) || jsdocTreatAsExported(node);
if (symbolFlags & SymbolFlags.Alias) {
if (node.kind === SyntaxKind.ExportSpecifier || (node.kind === SyntaxKind.ImportEqualsDeclaration && hasExportModifier)) {
return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes);
@ -567,7 +567,7 @@ namespace ts {
// and this case is specially handled. Module augmentations should only be merged with original module definition
// and should never be merged directly with other augmentation, and the latter case would be possible if automatic merge is allowed.
if (isJSDocTypeAlias(node)) Debug.assert(isInJSFile(node)); // We shouldn't add symbols for JSDoc nodes if not in a JS file.
if ((!isAmbientModule(node) && (hasExportModifier || container.flags & NodeFlags.ExportContext)) || isJSDocTypeAlias(node)) {
if (!isAmbientModule(node) && (hasExportModifier || container.flags & NodeFlags.ExportContext)) {
if (!container.locals || (hasSyntacticModifier(node, ModifierFlags.Default) && !getDeclarationName(node))) {
return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); // No local symbol for an unnamed default!
}
@ -583,6 +583,21 @@ namespace ts {
}
}
function jsdocTreatAsExported(node: Node) {
if (!isJSDocTypeAlias(node)) return false;
// jsdoc typedef handling is a bit of a doozy, but to summarize, treat the typedef as exported if:
// 1. It has an explicit name (since by default typedefs are always directly exported, either at the top level or in a container), or
if (!isJSDocEnumTag(node) && !!node.fullName) return true;
// 2. The thing a nameless typedef pulls its name from is implicitly a direct export (either by assignment or actual export flag).
const declName = getNameOfDeclaration(node);
if (!declName) return false;
if (isPropertyAccessEntityNameExpression(declName.parent) && isTopLevelNamespaceAssignment(declName.parent)) return true;
if (isDeclaration(declName.parent) && getCombinedModifierFlags(declName.parent) & ModifierFlags.Export) return true;
// This could potentially be simplified by having `delayedBindJSDocTypedefTag` pass in an override for `hasExportModifier`, since it should
// already have calculated and branched on most of this.
return false;
}
// All container nodes are kept on a linked list in declaration order. This list is used by
// the getLocalNameOfContainer function in the type checker to validate that the local name
// used for a container is unique.

View File

@ -0,0 +1,26 @@
=== tests/cases/conformance/jsdoc/def.js ===
/** @enum {number} */
const MyEnum = {
>MyEnum : Symbol(MyEnum, Decl(def.js, 1, 5), Decl(def.js, 0, 4))
a: 1,
>a : Symbol(a, Decl(def.js, 1, 16))
b: 2
>b : Symbol(b, Decl(def.js, 2, 7))
};
export default MyEnum;
>MyEnum : Symbol(MyEnum, Decl(def.js, 1, 5), Decl(def.js, 0, 4))
=== tests/cases/conformance/jsdoc/use.js ===
import MyEnum from "./def";
>MyEnum : Symbol(MyEnum, Decl(use.js, 0, 6))
/** @type {MyEnum} */
const v = MyEnum.b;
>v : Symbol(v, Decl(use.js, 3, 5))
>MyEnum.b : Symbol(b, Decl(def.js, 2, 7))
>MyEnum : Symbol(MyEnum, Decl(use.js, 0, 6))
>b : Symbol(b, Decl(def.js, 2, 7))

View File

@ -0,0 +1,29 @@
=== tests/cases/conformance/jsdoc/def.js ===
/** @enum {number} */
const MyEnum = {
>MyEnum : { a: number; b: number; }
>{ a: 1, b: 2} : { a: number; b: number; }
a: 1,
>a : number
>1 : 1
b: 2
>b : number
>2 : 2
};
export default MyEnum;
>MyEnum : number
=== tests/cases/conformance/jsdoc/use.js ===
import MyEnum from "./def";
>MyEnum : { a: number; b: number; }
/** @type {MyEnum} */
const v = MyEnum.b;
>v : number
>MyEnum.b : number
>MyEnum : { a: number; b: number; }
>b : number

View File

@ -55,9 +55,9 @@ const Thing = Object.freeze({
});
exports.Thing = Thing;
>exports.Thing : Symbol(Thing, Decl(index.js, 4, 3), Decl(index.js, 0, 4))
>exports : Symbol(Thing, Decl(index.js, 4, 3), Decl(index.js, 0, 4))
>Thing : Symbol(Thing, Decl(index.js, 4, 3), Decl(index.js, 0, 4))
>exports.Thing : Symbol(Thing, Decl(index.js, 4, 3))
>exports : Symbol(Thing, Decl(index.js, 4, 3))
>Thing : Symbol(Thing, Decl(index.js, 4, 3))
>Thing : Symbol(Thing, Decl(index.js, 1, 5), Decl(index.js, 0, 4))
/**

View File

@ -0,0 +1,17 @@
// @noEmit: true
// @allowJs: true
// @checkJs: true
// @Filename: def.js
/** @enum {number} */
const MyEnum = {
a: 1,
b: 2
};
export default MyEnum;
// @Filename: use.js
import MyEnum from "./def";
/** @type {MyEnum} */
const v = MyEnum.b;

View File

@ -15,8 +15,7 @@
//// export { test/**/String };
verify.quickInfoAt("",
`type testString = string
(alias) type testString = any
`(alias) type testString = string
(alias) const testString: {
one: string;
two: string;