Skip to content

Issue an error on cross-file merges we can't emit #38148

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
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
7 changes: 6 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6029,14 +6029,19 @@ namespace ts {
serializeAsNamespaceDeclaration(realMembers, localName, modifierFlags, !!(symbol.flags & (SymbolFlags.Function | SymbolFlags.Assignment)));
}
if (length(mergedMembers)) {
const containingFile = getSourceFileOfNode(context.enclosingDeclaration);
const localName = getInternalSymbolName(symbol, symbolName);
const nsBody = createModuleBlock([createExportDeclaration(
/*decorators*/ undefined,
/*modifiers*/ undefined,
createNamedExports(map(filter(mergedMembers, n => n.escapedName !== InternalSymbolName.ExportEquals), s => {
createNamedExports(mapDefined(filter(mergedMembers, n => n.escapedName !== InternalSymbolName.ExportEquals), s => {
const name = unescapeLeadingUnderscores(s.escapedName);
const localName = getInternalSymbolName(s, name);
const aliasDecl = s.declarations && getDeclarationOfAliasSymbol(s);
if (containingFile && (aliasDecl ? containingFile !== getSourceFileOfNode(aliasDecl) : !some(s.declarations, d => getSourceFileOfNode(d) === containingFile))) {
context.tracker?.reportNonlocalAugmentation?.(containingFile, symbol, s);
Copy link
Member

Choose a reason for hiding this comment

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

that is an impressive number of ?. there

return undefined;
}
const target = aliasDecl && getTargetOfAliasDeclaration(aliasDecl, /*dontRecursivelyResolve*/ true);
includePrivateSymbol(target || s);
const targetName = target ? getInternalSymbolName(target, unescapeLeadingUnderscores(target.escapedName)) : localName;
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -4388,6 +4388,14 @@
"category": "Error",
"code": 6231
},
"Declaration augments declaration in another file. This cannot be serialized.": {
"category": "Error",
"code": 6232
},
"This is the declaration being augmented. Consider moving the augmenting declaration into the same file.": {
"category": "Error",
"code": 6233
},

"Projects to reference": {
"category": "Message",
Expand Down
14 changes: 13 additions & 1 deletion src/compiler/transformers/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ namespace ts {
reportLikelyUnsafeImportRequiredError,
moduleResolverHost: host,
trackReferencedAmbientModule,
trackExternalModuleSymbolOfImportTypeNode
trackExternalModuleSymbolOfImportTypeNode,
reportNonlocalAugmentation
};
let errorNameNode: DeclarationName | undefined;

Expand Down Expand Up @@ -190,6 +191,17 @@ namespace ts {
}
}

function reportNonlocalAugmentation(containingFile: SourceFile, parentSymbol: Symbol, symbol: Symbol) {
const primaryDeclaration = find(parentSymbol.declarations, d => getSourceFileOfNode(d) === containingFile)!;
const augmentingDeclarations = filter(symbol.declarations, d => getSourceFileOfNode(d) !== containingFile);
for (const augmentations of augmentingDeclarations) {
context.addDiagnostic(addRelatedInfo(
createDiagnosticForNode(augmentations, Diagnostics.Declaration_augments_declaration_in_another_file_This_cannot_be_serialized),
createDiagnosticForNode(primaryDeclaration, Diagnostics.This_is_the_declaration_being_augmented_Consider_moving_the_augmenting_declaration_into_the_same_file)
));
}
}

function transformDeclarationsForJS(sourceFile: SourceFile, bundled?: boolean) {
const oldDiag = getSymbolAccessibilityDiagnostic;
getSymbolAccessibilityDiagnostic = (s) => ({
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6468,6 +6468,7 @@ namespace ts {
moduleResolverHost?: ModuleSpecifierResolutionHost & { getCommonSourceDirectory(): string };
trackReferencedAmbientModule?(decl: ModuleDeclaration, symbol: Symbol): void;
trackExternalModuleSymbolOfImportTypeNode?(symbol: Symbol): void;
reportNonlocalAugmentation?(containingFile: SourceFile, parentSymbol: Symbol, augmentingSymbol: Symbol): void;
}

export interface TextSpan {
Expand Down
17 changes: 17 additions & 0 deletions tests/baselines/reference/jsDeclarationsCrossfileMerge.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
tests/cases/conformance/jsdoc/declarations/index.js(4,1): error TS6232: Declaration augments declaration in another file. This cannot be serialized.


==== tests/cases/conformance/jsdoc/declarations/index.js (1 errors) ====
const m = require("./exporter");

module.exports = m.default;
module.exports.memberName = "thing";
~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS6232: Declaration augments declaration in another file. This cannot be serialized.
!!! related TS6233 /.src/tests/cases/conformance/jsdoc/declarations/exporter.js:1:10: This is the declaration being augmented. Consider moving the augmenting declaration into the same file.

==== tests/cases/conformance/jsdoc/declarations/exporter.js (0 errors) ====
function validate() {}

export default validate;

28 changes: 28 additions & 0 deletions tests/baselines/reference/jsDeclarationsCrossfileMerge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//// [tests/cases/conformance/jsdoc/declarations/jsDeclarationsCrossfileMerge.ts] ////

//// [index.js]
const m = require("./exporter");

module.exports = m.default;
module.exports.memberName = "thing";

//// [exporter.js]
function validate() {}

export default validate;


//// [exporter.js]
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function validate() { }
exports.default = validate;
//// [index.js]
var m = require("./exporter");
module.exports = m.default;
module.exports.memberName = "thing";


//// [index.d.ts]
declare const _exports: typeof import("./exporter").default;
export = _exports;
28 changes: 28 additions & 0 deletions tests/baselines/reference/jsDeclarationsCrossfileMerge.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
=== tests/cases/conformance/jsdoc/declarations/index.js ===
const m = require("./exporter");
>m : Symbol(m, Decl(index.js, 0, 5))
>require : Symbol(require)
>"./exporter" : Symbol("tests/cases/conformance/jsdoc/declarations/exporter", Decl(exporter.js, 0, 0))

module.exports = m.default;
>module.exports : Symbol("tests/cases/conformance/jsdoc/declarations/index", Decl(index.js, 0, 0))
>module : Symbol(export=, Decl(index.js, 0, 32))
>exports : Symbol(export=, Decl(index.js, 0, 32))
>m.default : Symbol(default, Decl(exporter.js, 0, 22))
>m : Symbol(m, Decl(index.js, 0, 5))
>default : Symbol(default, Decl(exporter.js, 0, 22))

module.exports.memberName = "thing";
>module.exports.memberName : Symbol(memberName)
>module.exports : Symbol(memberName, Decl(index.js, 2, 27))
>module : Symbol(module, Decl(index.js, 0, 32))
>exports : Symbol("tests/cases/conformance/jsdoc/declarations/index", Decl(index.js, 0, 0))
>memberName : Symbol(memberName, Decl(index.js, 2, 27))

=== tests/cases/conformance/jsdoc/declarations/exporter.js ===
function validate() {}
>validate : Symbol(validate, Decl(exporter.js, 0, 0))

export default validate;
>validate : Symbol(validate, Decl(exporter.js, 0, 0))

32 changes: 32 additions & 0 deletions tests/baselines/reference/jsDeclarationsCrossfileMerge.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
=== tests/cases/conformance/jsdoc/declarations/index.js ===
const m = require("./exporter");
>m : typeof import("tests/cases/conformance/jsdoc/declarations/exporter")
>require("./exporter") : typeof import("tests/cases/conformance/jsdoc/declarations/exporter")
>require : any
>"./exporter" : "./exporter"

module.exports = m.default;
>module.exports = m.default : typeof import("tests/cases/conformance/jsdoc/declarations/exporter").default
>module.exports : typeof import("tests/cases/conformance/jsdoc/declarations/exporter").default
>module : { "\"tests/cases/conformance/jsdoc/declarations/index\"": typeof import("tests/cases/conformance/jsdoc/declarations/exporter").default; }
>exports : typeof import("tests/cases/conformance/jsdoc/declarations/exporter").default
>m.default : { (): void; memberName: string; }
>m : typeof import("tests/cases/conformance/jsdoc/declarations/exporter")
>default : { (): void; memberName: string; }

module.exports.memberName = "thing";
>module.exports.memberName = "thing" : "thing"
>module.exports.memberName : string
>module.exports : typeof import("tests/cases/conformance/jsdoc/declarations/exporter").default
>module : { "\"tests/cases/conformance/jsdoc/declarations/index\"": typeof import("tests/cases/conformance/jsdoc/declarations/exporter").default; }
>exports : typeof import("tests/cases/conformance/jsdoc/declarations/exporter").default
>memberName : string
>"thing" : "thing"

=== tests/cases/conformance/jsdoc/declarations/exporter.js ===
function validate() {}
>validate : typeof validate

export default validate;
>validate : typeof validate

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// @allowJs: true
// @checkJs: true
// @target: es5
// @lib: es6
// @outDir: ./out
// @declaration: true
// @filename: index.js
const m = require("./exporter");

module.exports = m.default;
module.exports.memberName = "thing";

// @filename: exporter.js
function validate() {}

export default validate;