-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
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
Parse JS Module Blocks proposal #12469
Conversation
Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/41538/ |
Can you submit a PR to ESTree on the AST shapes? See also https://github.com/babel/babel/blob/main/CONTRIBUTING.md#creating-a-new-plugin-spec-new |
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 e53f5fe:
|
const oldExportedIdentifiers = this.state.exportedIdentifiers; | ||
this.state.exportedIdentifiers = []; | ||
this.eat(tt.braceL); | ||
this.scope.enter(SCOPE_OTHER); |
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 should also enter a new production parameter scope with [Await]
parameter when topLevelAwait
is enabled, e.g.
module { await 42 }
See
this.prodParam.enter(PARAM); |
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.
189bc49 👍
|
||
// https://github.com/tc39/proposal-js-module-blocks | ||
parseModuleExpression(): N.ModuleExpression { | ||
this.expectPlugin("jsModuleBlocks"); |
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 personally prefer moduleBlocks
for brevity.
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.
5dd532b 👍
defineType("ModuleExpression", { | ||
visitor: ["body"], | ||
fields: { | ||
body: { |
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 think we should wrap the module blocks within a BlockStatement
. Like we did for DoExpression
: https://github.com/babel/babel/blob/main/packages/babel-parser/ast/spec.md#doexpression
The BlockStatement
is of Scopeable
alias, and many more. So when traversing the module body statements in a module block, path.scope
can be well defined, currently it points to the scope when the ModuleExpression
defines since it is not Scopeable
.
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 could also consider Program
instead of BlockStatement
.
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.
Program
allows to include Directive
in its body. But Module Blocks doesn't seem to allow that. Also current BlockStatement
body cannot have ModuleDeclaration
.
Should we add new node type? or extends BlockStatement
?
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 would be really surprised if module blocks don't allow directives, since they are currently also allowed in blocks and functions.
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.
Oh sorry, I misunderstood about directives in ECMAScript spec. Module blocks allows directives. As you said, we can use Program
.
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.
Btw, the original reason I proposed Program
is that in many transforms we need to inject things to the top-level.
We find the top-level with path.findParent(p => p.isProgram())
, and this would nicely work out of the box with module blocks.
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.
Fixed at e381be0
this.state.exportedIdentifiers = []; | ||
this.eat(tt.braceL); | ||
this.scope.enter(SCOPE_OTHER); | ||
this.parseBlockOrModuleBlockBody(node.body, undefined, true, tt.braceR); |
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 a test case about the locality of this.inModule
? We should throw for
// sourceType: "script"
module { await: 1 }
because "await" as a label identifier is forbidden when the goal syntax is Module: https://tc39.es/ecma262/#sec-identifiers-static-semantics-early-errors
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.
b4adac3 👍
aebc37b
to
b4adac3
Compare
const oldInModule = this.inModule; | ||
this.inModule = true; | ||
const program = this.startNode<N.Program>(); | ||
node.body = this.parseProgram(program, tt.braceR, "module"); |
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 should also construct new classScope
like we did in
this.classScope = new ClassScopeHandler(this.raise.bind(this)); |
otherwise it will mess up with upper class definition:
class B {
#p() {
module {
class C { [this.#p]; } // it should not have access to `#p` defined across the module block
}
}
}
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 wonder if it would be better to introduce a new initializeScopes
function, so htat if we have to initialize more things we only have to update one place.
Maybe something like
const oldScopes = this.initializeScopes();
node.body = this.parseProgram(program, tt.braceR, "module"));
Object.assign(this, oldScopes);
and then we re-use initializeScopes
in Parser
's constructor
.
8645fbc
to
fa763c2
Compare
Update: Stage 2! |
/*:: | ||
import type { SourceType } from "../options"; | ||
*/ |
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.
Nit: no need to comment this. It was only needed for class fields because Flow didn't support the declare
keyword.
/*:: | |
import type { SourceType } from "../options"; | |
*/ | |
import type { SourceType } from "../options"; |
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.
If remove this comment, ESLint warns:
error: 'SourceType' is defined but never used (no-unused-vars) at packages/babel-parser/src/parser/expression.js:58:15:
56 | } from "../util/expression-scope";
57 | import { Errors } from "./error";
> 58 | import type { SourceType } from "../options";
| ^
59 |
60 | export default class ExpressionParser extends LValParser {
61 | // Forward-declaration: defined in statement.js
Sorry I'm not familiar with Flow, what should we do?
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.
It's ok like this then.
if (this.isContextual("module")) { | ||
const lookahead = this.lookahead(); | ||
if ( | ||
lookahead.type === tt.braceL && |
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 this.lookaheadCharCode() === charCodes.rightCurlyBrace
and avoid the expensive this.lokahead()
.
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.
Also we can avoid lookahead via using new hasFollowingLineBreak
, thank you. 370953a
const oldInModule = this.inModule; | ||
this.inModule = true; | ||
const program = this.startNode<N.Program>(); | ||
node.body = this.parseProgram(program, tt.braceR, "module"); |
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 wonder if it would be better to introduce a new initializeScopes
function, so htat if we have to initialize more things we only have to update one place.
Maybe something like
const oldScopes = this.initializeScopes();
node.body = this.parseProgram(program, tt.braceR, "module"));
Object.assign(this, oldScopes);
and then we re-use initializeScopes
in Parser
's constructor
.
@@ -170,13 +170,16 @@ export default class ScopeHandler<IScope: Scope = Scope> { | |||
} | |||
|
|||
checkLocalExport(id: N.Identifier) { | |||
const currentScope = this.currentScope(); | |||
const scope = | |||
currentScope.flags & SCOPE_PROGRAM ? currentScope : this.scopeStack[0]; |
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 currentScope.flags & SCOPE_PROGRAM
ever be false?
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 seem to have made a mistake when I added this... 15c8306
module { | ||
export { foo } | ||
} |
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.
Another test:
let foo;
module {
export { foo };
}
fa763c2
to
15c8306
Compare
What do you think about the interface as below? f651952 const revertScopnes = this.initializeScopes();
node.body = this.parseProgram(program, tt.braceR, "module"));
revertScopes(); |
We should use EDIT: I was suggesting to use babel/packages/babel-parser/src/parser/index.js Lines 31 to 38 in 10978bb
|
0ebce22
to
96c07c5
Compare
Just wanted to thank y’all for being on top of my proposal! This is amazing to see. Thanks to this PR, I was able to prototype a babel transform to shim Module Blocks. ❤️ |
@sosukesuzuki Can we mark this PR as ready? |
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.
Parsing algorithm and AST LGTM.
Nit: Consider calling the AST node "ModuleBlock" instead of "ModuleExpression"? (and ditto parseModuleBlock, etc)
Currently all the expression node names end with "Expression" (both in Babel and in the spec), so I'd prefer to keep it as |
Add parsing support for JS Module Blocks proposal.
/cc @littledan @surma