From 806de6d69c3e6f63291d818f197c2e885b136911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sat, 28 Oct 2023 18:37:55 +0200 Subject: [PATCH 1/2] Fxied declaration emit for JS files when `module.exports` is assigned a non-alias value and when it has extra type members --- src/compiler/checker.ts | 10 ++++-- ...ssignedFunctionWithExtraTypedefsMembers.js | 35 +++++++++++++++++++ ...edFunctionWithExtraTypedefsMembers.symbols | 18 ++++++++++ ...gnedFunctionWithExtraTypedefsMembers.types | 20 +++++++++++ ...ssignedFunctionWithExtraTypedefsMembers.ts | 16 +++++++++ 5 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.js create mode 100644 tests/baselines/reference/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.symbols create mode 100644 tests/baselines/reference/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.types create mode 100644 tests/cases/compiler/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 254dbf5753248..fe73a5fa18a5f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8664,7 +8664,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { }); let addingDeclare = !bundled; const exportEquals = symbolTable.get(InternalSymbolName.ExportEquals); - if (exportEquals && symbolTable.size > 1 && exportEquals.flags & SymbolFlags.Alias) { + if (exportEquals && symbolTable.size > 1 && exportEquals.flags & (SymbolFlags.Alias | SymbolFlags.Module)) { symbolTable = createSymbolTable(); // Remove extraneous elements from root symbol table (they'll be mixed back in when the target of the `export=` is looked up) symbolTable.set(InternalSymbolName.ExportEquals, exportEquals); @@ -9197,8 +9197,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getNamespaceMembersForSerialization(symbol: Symbol) { - const exports = getExportsOfSymbol(symbol); - return !exports ? [] : filter(arrayFrom(exports.values()), m => isNamespaceMember(m) && isIdentifierText(m.escapedName as string, ScriptTarget.ESNext)); + let exports = arrayFrom(getExportsOfSymbol(symbol).values()); + const merged = getMergedSymbol(symbol); + if (merged !== symbol) { + exports = concatenate(exports, filter(arrayFrom(getExportsOfSymbol(merged).values()), m => !(getSymbolFlags(resolveSymbol(m)) & SymbolFlags.Value))); + } + return filter(exports, m => isNamespaceMember(m) && isIdentifierText(m.escapedName as string, ScriptTarget.ESNext)); } function isTypeOnlyNamespace(symbol: Symbol) { diff --git a/tests/baselines/reference/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.js b/tests/baselines/reference/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.js new file mode 100644 index 0000000000000..1e02c358656d6 --- /dev/null +++ b/tests/baselines/reference/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.js @@ -0,0 +1,35 @@ +//// [tests/cases/compiler/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.ts] //// + +//// [index.js] +/** + * @typedef Options + * @property {string} opt + */ + +/** + * @param {Options} options + */ +module.exports = function loader(options) {} + + +//// [index.js] +"use strict"; +/** + * @typedef Options + * @property {string} opt + */ +/** + * @param {Options} options + */ +module.exports = function loader(options) { }; + + +//// [index.d.ts] +declare namespace _exports { + export { Options }; +} +declare function _exports(options: Options): void; +export = _exports; +type Options = { + opt: string; +}; diff --git a/tests/baselines/reference/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.symbols b/tests/baselines/reference/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.symbols new file mode 100644 index 0000000000000..dc72dbfb0b17d --- /dev/null +++ b/tests/baselines/reference/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.symbols @@ -0,0 +1,18 @@ +//// [tests/cases/compiler/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.ts] //// + +=== index.js === +/** + * @typedef Options + * @property {string} opt + */ + +/** + * @param {Options} options + */ +module.exports = function loader(options) {} +>module.exports : Symbol(module.exports, Decl(index.js, 0, 0)) +>module : Symbol(export=, Decl(index.js, 0, 0)) +>exports : Symbol(export=, Decl(index.js, 0, 0)) +>loader : Symbol(loader, Decl(index.js, 8, 16)) +>options : Symbol(options, Decl(index.js, 8, 33)) + diff --git a/tests/baselines/reference/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.types b/tests/baselines/reference/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.types new file mode 100644 index 0000000000000..4343d7b088e5b --- /dev/null +++ b/tests/baselines/reference/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.types @@ -0,0 +1,20 @@ +//// [tests/cases/compiler/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.ts] //// + +=== index.js === +/** + * @typedef Options + * @property {string} opt + */ + +/** + * @param {Options} options + */ +module.exports = function loader(options) {} +>module.exports = function loader(options) {} : (options: Options) => void +>module.exports : (options: Options) => void +>module : { exports: (options: Options) => void; } +>exports : (options: Options) => void +>function loader(options) {} : (options: Options) => void +>loader : (options: Options) => void +>options : Options + diff --git a/tests/cases/compiler/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.ts b/tests/cases/compiler/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.ts new file mode 100644 index 0000000000000..2a2a69bd22e70 --- /dev/null +++ b/tests/cases/compiler/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.ts @@ -0,0 +1,16 @@ +// @strict: true +// @checkJs: true +// @declaration: true +// @outDir: out + +// @filename: index.js + +/** + * @typedef Options + * @property {string} opt + */ + +/** + * @param {Options} options + */ +module.exports = function loader(options) {} From 4bba29b6c27f79fbc0cca94df3b562cb5e23bea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Wed, 10 Jan 2024 20:48:06 +0100 Subject: [PATCH 2/2] use `Set` before converting to an array to avoid potential duplicates --- src/compiler/checker.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6ab6160af691e..ee832c67f0fc2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9268,7 +9268,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let exports = arrayFrom(getExportsOfSymbol(symbol).values()); const merged = getMergedSymbol(symbol); if (merged !== symbol) { - exports = concatenate(exports, filter(arrayFrom(getExportsOfSymbol(merged).values()), m => !(getSymbolFlags(resolveSymbol(m)) & SymbolFlags.Value))); + const membersSet = new Set(exports); + for (const exported of getExportsOfSymbol(merged).values()) { + if (!(getSymbolFlags(resolveSymbol(exported)) & SymbolFlags.Value)) { + membersSet.add(exported); + } + } + exports = arrayFrom(membersSet); } return filter(exports, m => isNamespaceMember(m) && isIdentifierText(m.escapedName as string, ScriptTarget.ESNext)); }