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
Support TypeScript 4.5 type-only import/export specifiers #13802
Support TypeScript 4.5 type-only import/export specifiers #13802
Conversation
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 1f933af:
|
Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/49458/ |
I'd personally have preferred if the AST was just |
3cc6c3d
to
15392c3
Compare
@@ -229,9 +229,23 @@ export default declare((api, opts) => { | |||
continue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you rebase on latest main
? We should also register global type for type-only import specifiers so they can be removed in the following case:
import { type A } from "module";
export { A }
15392c3
to
4929209
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a test for import { type A } from "x"
with onlyRemoveTypeImports
? import "x"
should not be removed.
@@ -40,6 +40,11 @@ export function ExportDefaultSpecifier( | |||
} | |||
|
|||
export function ExportSpecifier(this: Printer, node: t.ExportSpecifier) { | |||
if (node.exportKind === "type") { | |||
this.word(node.exportKind); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this.word(node.exportKind); | |
this.word("type"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
d337f69 👍
} else { | ||
allElided = false; | ||
NEEDS_EXPLICIT_ESM.set(path.node, false); | ||
if (!importsToRemove.includes(binding.path)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use a Set
for importsToRemove
, since .has()
is O(1) while .includes
is O(n).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
8cb6dc0 👍
@nicolo-ribaudo Thank you for your review! I've addressed your comments. 4d1cde6 |
@@ -288,7 +288,7 @@ export default declare((api, opts) => { | |||
} | |||
} | |||
|
|||
if (isAllSpecifiersElided()) { | |||
if (!onlyRemoveTypeImports && isAllSpecifiersElided()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why should we keep import statements when onlyRemoveTypeImports
is true
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nicolo-ribaudo You said #13802 (review). What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, I assumed that import { type X } from "x"
would have been equivalent to import {} from "x"
and thus not removed. I'm sorry!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, interesting. TS removes import {} from "x"
, too. It preserves only import "x"
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to behavior of TypeScript, it is better to remove the Import Statement in such cases. 6b79fd1
const local = this.parseModuleExportName(); | ||
node.local = local; | ||
if (this.eatContextual(tt._as)) { | ||
node.local = this.parseModuleExportName(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to avoid mixing TS/flow-specific code in the main parser, when possible.
Would it be possible to move type
handling in parseModuleExportName()
? Or maybe we reorganize it like this:
parseExportSpecifiers() {
while () { // loop to parse all the specifiers
const local = this.parseModuleExportName();
nodes.push(this.parseExportSpecifier(local));
}
}
parseExportSpecifier(local) {
// ...
}
and in typescript/index.js
parseExportSpecifier(local) {
if (local.type === "Identifier" && local.name === "type") {
const node = this.startNodeAtNode(local);
// if this is a type export, parse it as such
return node;
}
return super.parseExprtSpecifier(local);
}
This is similar to what we do for imports.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
node[rightOfAsKey] = rightOfAs; | ||
|
||
const kindKey = isImport ? "importKind" : "exportKind"; | ||
node[kindKey] = hasTypeSpecifier ? "type" : "value"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you also update packages/babel-parser/src/types.js
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
c44171a 👍
// { type as as something } | ||
hasTypeSpecifier = true; | ||
leftOfAs = firstAs; | ||
rightOfAs = this.parseIdentifier(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When we are parsing export, the right-of-as is a liberal identifier (should be parsed by parseIdentifier(true)
), otherwise it should throw unexpected reserved word:
export { type a as if } from "x"; // valid
import { type a as if } from "x"; // invalid
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use parseIdentifier(true/false)
for those specifiers. But Babel doesn't check reserved word for TypeScript. Should we still fix for that?
babel/packages/babel-parser/src/plugins/typescript/index.js
Lines 2237 to 2247 in 248aa9d
checkReservedWord( | |
word: string, // eslint-disable-line no-unused-vars | |
startLoc: number, // eslint-disable-line no-unused-vars | |
checkKeywords: boolean, // eslint-disable-line no-unused-vars | |
// eslint-disable-next-line no-unused-vars | |
isBinding: boolean, | |
): void { | |
// Don't bother checking for TypeScript code. | |
// Strict mode words may be allowed as in `declare namespace N { const static: number; }`. | |
// And we have a type checker anyway, so don't bother having the parser do it. | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, let's fix that in another PR, then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome work!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
Support type-only import/export specifiers will be landed in TS 4.5.
Todo:
AST change:
/cc @bradzacher What do you think?
importKind: "value" | "type"
toImportSpecifier
.exportKind: "value" | "type"
toExportSpecifier
References: