diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index f61aa3ab900eb..f77c0669ea300 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -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. diff --git a/tests/baselines/reference/exportedEnumTypeAndValue.symbols b/tests/baselines/reference/exportedEnumTypeAndValue.symbols new file mode 100644 index 0000000000000..16948482ecf8b --- /dev/null +++ b/tests/baselines/reference/exportedEnumTypeAndValue.symbols @@ -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)) + diff --git a/tests/baselines/reference/exportedEnumTypeAndValue.types b/tests/baselines/reference/exportedEnumTypeAndValue.types new file mode 100644 index 0000000000000..d816067e9f476 --- /dev/null +++ b/tests/baselines/reference/exportedEnumTypeAndValue.types @@ -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 + diff --git a/tests/baselines/reference/jsEnumTagOnObjectFrozen.symbols b/tests/baselines/reference/jsEnumTagOnObjectFrozen.symbols index 18610423aeaff..ecdf10f8ea467 100644 --- a/tests/baselines/reference/jsEnumTagOnObjectFrozen.symbols +++ b/tests/baselines/reference/jsEnumTagOnObjectFrozen.symbols @@ -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)) /** diff --git a/tests/cases/conformance/jsdoc/exportedEnumTypeAndValue.ts b/tests/cases/conformance/jsdoc/exportedEnumTypeAndValue.ts new file mode 100644 index 0000000000000..61a7b2614ff9d --- /dev/null +++ b/tests/cases/conformance/jsdoc/exportedEnumTypeAndValue.ts @@ -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; diff --git a/tests/cases/fourslash/quickInfoJSExport.ts b/tests/cases/fourslash/quickInfoJSExport.ts index 24a9bd00fa30b..6b2fbc16dd842 100644 --- a/tests/cases/fourslash/quickInfoJSExport.ts +++ b/tests/cases/fourslash/quickInfoJSExport.ts @@ -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;