Parse JS Module Blocks proposal (#12469)

This commit is contained in:
Sosuke Suzuki
2021-02-22 04:08:20 +09:00
committed by GitHub
parent e4588bed22
commit 9c567baa9b
64 changed files with 1704 additions and 32 deletions

View File

@@ -56,6 +56,10 @@ import {
} from "../util/expression-scope";
import { Errors } from "./error";
/*::
import type { SourceType } from "../options";
*/
export default class ExpressionParser extends LValParser {
// Forward-declaration: defined in statement.js
/*::
@@ -78,6 +82,16 @@ export default class ExpressionParser extends LValParser {
) => T;
+parseFunctionParams: (node: N.Function, allowModifiers?: boolean) => void;
+takeDecorators: (node: N.HasDecorators) => void;
+parseBlockOrModuleBlockBody: (
body: N.Statement[],
directives: ?(N.Directive[]),
topLevel: boolean,
end: TokenType,
afterBlockParse?: (hasStrictModeDirective: boolean) => void
) => void
+parseProgram: (
program: N.Program, end: TokenType, sourceType?: SourceType
) => N.Program
*/
// For object literal, check if property __proto__ has been used more than once.
@@ -500,7 +514,13 @@ export default class ExpressionParser extends LValParser {
this.next();
return this.parseAwait(startPos, startLoc);
}
if (
this.isContextual("module") &&
this.lookaheadCharCode() === charCodes.leftCurlyBrace &&
!this.hasFollowingLineBreak()
) {
return this.parseModuleExpression();
}
const update = this.match(tt.incDec);
const node = this.startNode();
if (this.state.type.prefix) {
@@ -2630,4 +2650,24 @@ export default class ExpressionParser extends LValParser {
return ret;
}
// https://github.com/tc39/proposal-js-module-blocks
parseModuleExpression(): N.ModuleExpression {
this.expectPlugin("moduleBlocks");
const node = this.startNode<N.ModuleExpression>();
this.next(); // eat "module"
this.eat(tt.braceL);
const revertScopes = this.initializeScopes(/** inModule */ true);
this.enterInitialScopes();
const program = this.startNode<N.Program>();
try {
node.body = this.parseProgram(program, tt.braceR, "module");
} finally {
revertScopes();
}
this.eat(tt.braceR);
return this.finishNode<N.ModuleExpression>(node, "ModuleExpression");
}
}

View File

@@ -5,14 +5,7 @@ import type { File /*::, JSXOpeningElement */ } from "../types";
import type { PluginList } from "../plugin-utils";
import { getOptions } from "../options";
import StatementParser from "./statement";
import { SCOPE_PROGRAM } from "../util/scopeflags";
import ScopeHandler from "../util/scope";
import ClassScopeHandler from "../util/class-scope";
import ExpressionScopeHandler from "../util/expression-scope";
import ProductionParameterHandler, {
PARAM_AWAIT,
PARAM,
} from "../util/production-parameter";
export type PluginsMap = Map<string, { [string]: any }>;
@@ -28,14 +21,8 @@ export default class Parser extends StatementParser {
options = getOptions(options);
super(options, input);
const ScopeHandler = this.getScopeHandler();
this.options = options;
this.inModule = this.options.sourceType === "module";
this.scope = new ScopeHandler(this.raise.bind(this), this.inModule);
this.prodParam = new ProductionParameterHandler();
this.classScope = new ClassScopeHandler(this.raise.bind(this));
this.expressionScope = new ExpressionScopeHandler(this.raise.bind(this));
this.initializeScopes();
this.plugins = pluginsMap(this.options.plugins);
this.filename = options.sourceFilename;
}
@@ -46,12 +33,7 @@ export default class Parser extends StatementParser {
}
parse(): File {
let paramFlags = PARAM;
if (this.hasPlugin("topLevelAwait") && this.inModule) {
paramFlags |= PARAM_AWAIT;
}
this.scope.enter(SCOPE_PROGRAM);
this.prodParam.enter(paramFlags);
this.enterInitialScopes();
const file = this.startNode();
const program = this.startNode();
this.nextToken();

View File

@@ -35,6 +35,7 @@ import {
newExpressionScope,
newParameterDeclarationScope,
} from "../util/expression-scope";
import type { SourceType } from "../options";
const loopLabel = { kind: "loop" },
switchLabel = { kind: "switch" };
@@ -55,12 +56,22 @@ export default class StatementParser extends ExpressionParser {
// to its body instead of creating a new node.
parseTopLevel(file: N.File, program: N.Program): N.File {
program.sourceType = this.options.sourceType;
file.program = this.parseProgram(program);
file.comments = this.state.comments;
if (this.options.tokens) file.tokens = this.tokens;
return this.finishNode(file, "File");
}
parseProgram(
program: N.Program,
end: TokenType = tt.eof,
sourceType: SourceType = this.options.sourceType,
): N.Program {
program.sourceType = sourceType;
program.interpreter = this.parseInterpreterDirective();
this.parseBlockBody(program, true, true, tt.eof);
this.parseBlockBody(program, true, true, end);
if (
this.inModule &&
!this.options.allowUndeclaredExports &&
@@ -72,13 +83,7 @@ export default class StatementParser extends ExpressionParser {
this.raise(pos, Errors.ModuleExportUndefined, name);
}
}
file.program = this.finishNode(program, "Program");
file.comments = this.state.comments;
if (this.options.tokens) file.tokens = this.tokens;
return this.finishNode(file, "File");
return this.finishNode<N.Program>(program, "Program");
}
// TODO

View File

@@ -6,7 +6,17 @@ import State from "../tokenizer/state";
import type { Node } from "../types";
import { lineBreak } from "../util/whitespace";
import { isIdentifierChar } from "../util/identifier";
import ClassScopeHandler from "../util/class-scope";
import ExpressionScopeHandler from "../util/expression-scope";
import { SCOPE_PROGRAM } from "../util/scopeflags";
import ProductionParameterHandler, {
PARAM_AWAIT,
PARAM,
} from "../util/production-parameter";
import { Errors } from "./error";
/*::
import type ScopeHandler from "../util/scope";
*/
type TryParse<Node, Error, Thrown, Aborted, FailState> = {
node: Node,
@@ -19,6 +29,11 @@ type TryParse<Node, Error, Thrown, Aborted, FailState> = {
// ## Parser utilities
export default class UtilParser extends Tokenizer {
// Forward-declaration: defined in parser/index.js
/*::
+getScopeHandler: () => Class<ScopeHandler<*>>;
*/
// TODO
addExtra(node: Node, key: string, val: any): void {
@@ -304,6 +319,56 @@ export default class UtilParser extends Tokenizer {
isObjectMethod(node: Node): boolean {
return node.type === "ObjectMethod";
}
initializeScopes(
inModule: boolean = this.options.sourceType === "module",
): () => void {
// Initialize state
const oldLabels = this.state.labels;
this.state.labels = [];
const oldExportedIdentifiers = this.state.exportedIdentifiers;
this.state.exportedIdentifiers = [];
// initialize scopes
const oldInModule = this.inModule;
this.inModule = inModule;
const oldScope = this.scope;
const ScopeHandler = this.getScopeHandler();
this.scope = new ScopeHandler(this.raise.bind(this), this.inModule);
const oldProdParam = this.prodParam;
this.prodParam = new ProductionParameterHandler();
const oldClassScope = this.classScope;
this.classScope = new ClassScopeHandler(this.raise.bind(this));
const oldExpressionScope = this.expressionScope;
this.expressionScope = new ExpressionScopeHandler(this.raise.bind(this));
return () => {
// Revert state
this.state.labels = oldLabels;
this.state.exportedIdentifiers = oldExportedIdentifiers;
// Revert scopes
this.inModule = oldInModule;
this.scope = oldScope;
this.prodParam = oldProdParam;
this.classScope = oldClassScope;
this.expressionScope = oldExpressionScope;
};
}
enterInitialScopes() {
let paramFlags = PARAM;
if (this.hasPlugin("topLevelAwait") && this.inModule) {
paramFlags |= PARAM_AWAIT;
}
this.scope.enter(SCOPE_PROGRAM);
this.prodParam.enter(paramFlags);
}
}
/**

View File

@@ -655,6 +655,11 @@ export type TemplateElement = NodeBase & {
},
};
export type ModuleExpression = NodeBase & {
type: "ModuleExpression",
body: Program,
};
// Patterns
// TypeScript access modifiers