[ts] Insert export {} when necessary to imply ESM (#13314)
This commit is contained in:
parent
f0b9b25a23
commit
b4c798e754
@ -21,8 +21,12 @@ function isInType(path) {
|
||||
}
|
||||
}
|
||||
|
||||
const PARSED_PARAMS = new WeakSet();
|
||||
const GLOBAL_TYPES = new WeakMap();
|
||||
// Track programs which contain imports/exports of values, so that we can include
|
||||
// empty exports for programs that do not, but were parsed as modules. This allows
|
||||
// tools to infer unamibiguously that results are ESM.
|
||||
const NEEDS_EXPLICIT_ESM = new WeakMap();
|
||||
const PARSED_PARAMS = new WeakSet();
|
||||
|
||||
function isGlobalType(path, name) {
|
||||
const program = path.find(path => path.isProgram()).node;
|
||||
@ -175,7 +179,8 @@ export default declare((api, opts) => {
|
||||
Identifier: visitPattern,
|
||||
RestElement: visitPattern,
|
||||
|
||||
Program(path, state) {
|
||||
Program: {
|
||||
enter(path, state) {
|
||||
const { file } = state;
|
||||
let fileJsxPragma = null;
|
||||
let fileJsxPragmaFrag = null;
|
||||
@ -211,6 +216,10 @@ export default declare((api, opts) => {
|
||||
// remove type imports
|
||||
for (let stmt of path.get("body")) {
|
||||
if (stmt.isImportDeclaration()) {
|
||||
if (!NEEDS_EXPLICIT_ESM.has(state.file.ast.program)) {
|
||||
NEEDS_EXPLICIT_ESM.set(state.file.ast.program, true);
|
||||
}
|
||||
|
||||
if (stmt.node.importKind === "type") {
|
||||
stmt.remove();
|
||||
continue;
|
||||
@ -218,10 +227,13 @@ export default declare((api, opts) => {
|
||||
|
||||
// If onlyRemoveTypeImports is `true`, only remove type-only imports
|
||||
// and exports introduced in TypeScript 3.8.
|
||||
if (!onlyRemoveTypeImports) {
|
||||
if (onlyRemoveTypeImports) {
|
||||
NEEDS_EXPLICIT_ESM.set(path.node, false);
|
||||
} else {
|
||||
// Note: this will allow both `import { } from "m"` and `import "m";`.
|
||||
// In TypeScript, the former would be elided.
|
||||
if (stmt.node.specifiers.length === 0) {
|
||||
NEEDS_EXPLICIT_ESM.set(path.node, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -249,6 +261,7 @@ export default declare((api, opts) => {
|
||||
importsToRemove.push(binding.path);
|
||||
} else {
|
||||
allElided = false;
|
||||
NEEDS_EXPLICIT_ESM.set(path.node, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -285,8 +298,24 @@ export default declare((api, opts) => {
|
||||
}
|
||||
}
|
||||
},
|
||||
exit(path) {
|
||||
if (
|
||||
path.node.sourceType === "module" &&
|
||||
NEEDS_EXPLICIT_ESM.get(path.node)
|
||||
) {
|
||||
// If there are no remaining value exports, this file can no longer
|
||||
// be inferred to be ESM. Leave behind an empty export declaration
|
||||
// so it can be.
|
||||
path.pushContainer("body", t.exportNamedDeclaration());
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
ExportNamedDeclaration(path, state) {
|
||||
if (!NEEDS_EXPLICIT_ESM.has(state.file.ast.program)) {
|
||||
NEEDS_EXPLICIT_ESM.set(state.file.ast.program, true);
|
||||
}
|
||||
|
||||
ExportNamedDeclaration(path) {
|
||||
if (path.node.exportKind === "type") {
|
||||
path.remove();
|
||||
return;
|
||||
@ -307,7 +336,10 @@ export default declare((api, opts) => {
|
||||
)
|
||||
) {
|
||||
path.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
NEEDS_EXPLICIT_ESM.set(state.file.ast.program, false);
|
||||
},
|
||||
|
||||
ExportSpecifier(path) {
|
||||
@ -317,14 +349,22 @@ export default declare((api, opts) => {
|
||||
}
|
||||
},
|
||||
|
||||
ExportDefaultDeclaration(path) {
|
||||
ExportDefaultDeclaration(path, state) {
|
||||
if (!NEEDS_EXPLICIT_ESM.has(state.file.ast.program)) {
|
||||
NEEDS_EXPLICIT_ESM.set(state.file.ast.program, true);
|
||||
}
|
||||
|
||||
// remove whole declaration if it's exporting a TS type
|
||||
if (
|
||||
t.isIdentifier(path.node.declaration) &&
|
||||
isGlobalType(path, path.node.declaration.name)
|
||||
) {
|
||||
path.remove();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
NEEDS_EXPLICIT_ESM.set(state.file.ast.program, false);
|
||||
},
|
||||
|
||||
TSDeclareFunction(path) {
|
||||
|
||||
@ -1 +1,3 @@
|
||||
; // Otherwise-empty file
|
||||
|
||||
export {};
|
||||
|
||||
@ -1 +1,3 @@
|
||||
; // Otherwise-empty file
|
||||
|
||||
export {};
|
||||
|
||||
@ -1 +1,2 @@
|
||||
;
|
||||
export {};
|
||||
|
||||
@ -1 +1,2 @@
|
||||
;
|
||||
export {};
|
||||
|
||||
@ -1 +1,2 @@
|
||||
const x = 0;
|
||||
export {};
|
||||
|
||||
@ -1 +1,2 @@
|
||||
const x = 0;
|
||||
export {};
|
||||
|
||||
@ -1 +1,2 @@
|
||||
const x = 0;
|
||||
export {};
|
||||
|
||||
@ -1 +1,2 @@
|
||||
const x = 0;
|
||||
export {};
|
||||
|
||||
@ -1 +1,2 @@
|
||||
const x = 0;
|
||||
export {};
|
||||
|
||||
@ -5,3 +5,4 @@ var Enum;
|
||||
})(Enum || (Enum = {}));
|
||||
|
||||
;
|
||||
export {};
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
// TODO: This should not be removed
|
||||
;
|
||||
export {};
|
||||
|
||||
@ -1 +1,2 @@
|
||||
;
|
||||
export {};
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
const obj = {
|
||||
A: 'foo'
|
||||
};
|
||||
export {};
|
||||
|
||||
@ -37,3 +37,5 @@ for (let s of strings) {
|
||||
console.log(`"${s}" - ${validators[name].isAcceptable(s) ? "matches" : "does not match"} ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user