Introduce scope tracking in the parser (#9493)
* Introduce scope tracking * Fix tests * Add new tests * Remove constructor-super check from transform as it is now in parser * Correctly handle class properties and class scope * Fix duplicate name check * Convert scope identifier storage to array * Enter a new scope in typescript module blocks * Add test for duplicate declaration * Rename error for duplicate exports * Treat class declarations as lexical declaration * Update whitelist * Add tests * Fix scope tracking for function declarations * Migrate try-catch duplicate error * Fix test * More tests * One more test * Make scope a separate class and fix review comments * Do not allow new.target in top scope arrow function * Correctly enter new scope for declare module and treat type aliases as lexical declarations * Tests for typescript scope tracking to not mark type aliases as duplicate * Fix flow scope tracking * Remove ident from test names as redundant * Add test case for var and function * Improve error messages * Improve literal regex
This commit is contained in:
@@ -3,11 +3,13 @@
|
||||
import type { Options } from "../options";
|
||||
import type State from "../tokenizer/state";
|
||||
import type { PluginsMap } from "./index";
|
||||
import type ScopeHandler from "../util/scope";
|
||||
|
||||
export default class BaseParser {
|
||||
// Properties set by constructor in index.js
|
||||
options: Options;
|
||||
inModule: boolean;
|
||||
scope: ScopeHandler;
|
||||
plugins: PluginsMap;
|
||||
filename: ?string;
|
||||
sawUnambiguousESM: boolean = false;
|
||||
|
||||
@@ -29,10 +29,23 @@ import {
|
||||
} from "../util/identifier";
|
||||
import type { Pos, Position } from "../util/location";
|
||||
import * as charCodes from "charcodes";
|
||||
import {
|
||||
BIND_OUTSIDE,
|
||||
BIND_VAR,
|
||||
functionFlags,
|
||||
SCOPE_ARROW,
|
||||
SCOPE_CLASS,
|
||||
SCOPE_DIRECT_SUPER,
|
||||
SCOPE_SUPER,
|
||||
SCOPE_PROGRAM,
|
||||
} from "../util/scopeflags";
|
||||
|
||||
export default class ExpressionParser extends LValParser {
|
||||
// Forward-declaration: defined in statement.js
|
||||
+parseBlock: (allowDirectives?: boolean) => N.BlockStatement;
|
||||
+parseBlock: (
|
||||
allowDirectives?: boolean,
|
||||
createNewLexicalScope?: boolean,
|
||||
) => N.BlockStatement;
|
||||
+parseClass: (
|
||||
node: N.Class,
|
||||
isStatement: boolean,
|
||||
@@ -41,10 +54,9 @@ export default class ExpressionParser extends LValParser {
|
||||
+parseDecorators: (allowExport?: boolean) => void;
|
||||
+parseFunction: <T: N.NormalFunction>(
|
||||
node: T,
|
||||
isStatement: boolean,
|
||||
statement?: number,
|
||||
allowExpressionBody?: boolean,
|
||||
isAsync?: boolean,
|
||||
optionalId?: boolean,
|
||||
) => T;
|
||||
+parseFunctionParams: (node: N.Function, allowModifiers?: boolean) => void;
|
||||
+takeDecorators: (node: N.HasDecorators) => void;
|
||||
@@ -74,6 +86,7 @@ export default class ExpressionParser extends LValParser {
|
||||
|
||||
// Convenience method to parse an Expression only
|
||||
getExpression(): N.Expression {
|
||||
this.scope.enter(SCOPE_PROGRAM);
|
||||
this.nextToken();
|
||||
const expr = this.parseExpression();
|
||||
if (!this.match(tt.eof)) {
|
||||
@@ -128,7 +141,7 @@ export default class ExpressionParser extends LValParser {
|
||||
const startPos = this.state.start;
|
||||
const startLoc = this.state.startLoc;
|
||||
if (this.isContextual("yield")) {
|
||||
if (this.state.inGenerator) {
|
||||
if (this.scope.inGenerator) {
|
||||
let left = this.parseYield(noIn);
|
||||
if (afterLeftParse) {
|
||||
left = afterLeftParse.call(this, left, startPos, startLoc);
|
||||
@@ -336,7 +349,7 @@ export default class ExpressionParser extends LValParser {
|
||||
if (
|
||||
this.match(tt.name) &&
|
||||
this.state.value === "await" &&
|
||||
this.state.inAsync
|
||||
this.scope.inAsync
|
||||
) {
|
||||
throw this.raise(
|
||||
this.state.start,
|
||||
@@ -421,8 +434,8 @@ export default class ExpressionParser extends LValParser {
|
||||
parseMaybeUnary(refShorthandDefaultPos: ?Pos): N.Expression {
|
||||
if (
|
||||
this.isContextual("await") &&
|
||||
(this.state.inAsync ||
|
||||
(!this.state.inFunction && this.options.allowAwaitOutsideFunction))
|
||||
(this.scope.inAsync ||
|
||||
(!this.scope.inFunction && this.options.allowAwaitOutsideFunction))
|
||||
) {
|
||||
return this.parseAwait();
|
||||
} else if (this.state.type.prefix) {
|
||||
@@ -806,11 +819,7 @@ export default class ExpressionParser extends LValParser {
|
||||
|
||||
switch (this.state.type) {
|
||||
case tt._super:
|
||||
if (
|
||||
!this.state.inMethod &&
|
||||
!this.state.inClassProperty &&
|
||||
!this.options.allowSuperOutsideMethod
|
||||
) {
|
||||
if (!this.scope.allowSuper && !this.options.allowSuperOutsideMethod) {
|
||||
this.raise(
|
||||
this.state.start,
|
||||
"super is only allowed in object methods and classes",
|
||||
@@ -819,6 +828,18 @@ export default class ExpressionParser extends LValParser {
|
||||
|
||||
node = this.startNode();
|
||||
this.next();
|
||||
if (
|
||||
this.match(tt.parenL) &&
|
||||
!this.scope.allowDirectSuper &&
|
||||
!this.options.allowSuperOutsideMethod
|
||||
) {
|
||||
this.raise(
|
||||
node.start,
|
||||
"super() is only valid inside a class constructor of a subclass. " +
|
||||
"Maybe a typo in the method name ('constructor') or not extending another class?",
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!this.match(tt.parenL) &&
|
||||
!this.match(tt.bracketL) &&
|
||||
@@ -826,17 +847,7 @@ export default class ExpressionParser extends LValParser {
|
||||
) {
|
||||
this.unexpected();
|
||||
}
|
||||
if (
|
||||
this.match(tt.parenL) &&
|
||||
this.state.inMethod !== "constructor" &&
|
||||
!this.options.allowSuperOutsideMethod
|
||||
) {
|
||||
this.raise(
|
||||
node.start,
|
||||
"super() is only valid inside a class constructor. " +
|
||||
"Make sure the method name is spelled exactly as 'constructor'.",
|
||||
);
|
||||
}
|
||||
|
||||
return this.finishNode(node, "Super");
|
||||
|
||||
case tt._import:
|
||||
@@ -870,20 +881,17 @@ export default class ExpressionParser extends LValParser {
|
||||
!this.canInsertSemicolon()
|
||||
) {
|
||||
this.next();
|
||||
return this.parseFunction(node, false, false, true);
|
||||
return this.parseFunction(node, undefined, false, true);
|
||||
} else if (
|
||||
canBeArrow &&
|
||||
id.name === "async" &&
|
||||
this.match(tt.name) &&
|
||||
!this.canInsertSemicolon()
|
||||
) {
|
||||
const oldInAsync = this.state.inAsync;
|
||||
this.state.inAsync = true;
|
||||
const params = [this.parseIdentifier()];
|
||||
this.expect(tt.arrow);
|
||||
// let foo = async bar => {};
|
||||
this.parseArrowExpression(node, params, true);
|
||||
this.state.inAsync = oldInAsync;
|
||||
return node;
|
||||
}
|
||||
|
||||
@@ -900,12 +908,9 @@ export default class ExpressionParser extends LValParser {
|
||||
this.expectPlugin("doExpressions");
|
||||
const node = this.startNode();
|
||||
this.next();
|
||||
const oldInFunction = this.state.inFunction;
|
||||
const oldLabels = this.state.labels;
|
||||
this.state.labels = [];
|
||||
this.state.inFunction = false;
|
||||
node.body = this.parseBlock(false);
|
||||
this.state.inFunction = oldInFunction;
|
||||
node.body = this.parseBlock();
|
||||
this.state.labels = oldLabels;
|
||||
return this.finishNode(node, "DoExpression");
|
||||
}
|
||||
@@ -1068,10 +1073,10 @@ export default class ExpressionParser extends LValParser {
|
||||
this.next();
|
||||
meta = this.createIdentifier(meta, "function");
|
||||
|
||||
if (this.state.inGenerator && this.eat(tt.dot)) {
|
||||
if (this.scope.inGenerator && this.eat(tt.dot)) {
|
||||
return this.parseMetaProperty(node, meta, "sent");
|
||||
}
|
||||
return this.parseFunction(node, false);
|
||||
return this.parseFunction(node);
|
||||
}
|
||||
|
||||
parseMetaProperty(
|
||||
@@ -1309,7 +1314,7 @@ export default class ExpressionParser extends LValParser {
|
||||
if (this.eat(tt.dot)) {
|
||||
const metaProp = this.parseMetaProperty(node, meta, "target");
|
||||
|
||||
if (!this.state.inFunction && !this.state.inClassProperty) {
|
||||
if (!this.scope.inNonArrowFunction && !this.state.inClassProperty) {
|
||||
let error = "new.target can only be used in functions";
|
||||
|
||||
if (this.hasPlugin("classProperties")) {
|
||||
@@ -1575,6 +1580,7 @@ export default class ExpressionParser extends LValParser {
|
||||
isGenerator,
|
||||
isAsync,
|
||||
/* isConstructor */ false,
|
||||
false,
|
||||
"ObjectMethod",
|
||||
);
|
||||
}
|
||||
@@ -1588,6 +1594,7 @@ export default class ExpressionParser extends LValParser {
|
||||
/* isGenerator */ false,
|
||||
/* isAsync */ false,
|
||||
/* isConstructor */ false,
|
||||
false,
|
||||
"ObjectMethod",
|
||||
);
|
||||
this.checkGetterSetterParams(prop);
|
||||
@@ -1713,32 +1720,28 @@ export default class ExpressionParser extends LValParser {
|
||||
isGenerator: boolean,
|
||||
isAsync: boolean,
|
||||
isConstructor: boolean,
|
||||
allowDirectSuper: boolean,
|
||||
type: string,
|
||||
inClassScope: boolean = false,
|
||||
): T {
|
||||
const oldInFunc = this.state.inFunction;
|
||||
const oldInMethod = this.state.inMethod;
|
||||
const oldInAsync = this.state.inAsync;
|
||||
const oldInGenerator = this.state.inGenerator;
|
||||
const oldYieldPos = this.state.yieldPos;
|
||||
const oldAwaitPos = this.state.awaitPos;
|
||||
this.state.inFunction = true;
|
||||
this.state.inMethod = node.kind || true;
|
||||
this.state.inAsync = isAsync;
|
||||
this.state.inGenerator = isGenerator;
|
||||
this.state.yieldPos = 0;
|
||||
this.state.awaitPos = 0;
|
||||
|
||||
this.initFunction(node, isAsync);
|
||||
node.generator = !!isGenerator;
|
||||
const allowModifiers = isConstructor; // For TypeScript parameter properties
|
||||
this.scope.enter(
|
||||
functionFlags(isAsync, node.generator) |
|
||||
SCOPE_SUPER |
|
||||
(inClassScope ? SCOPE_CLASS : 0) |
|
||||
(allowDirectSuper ? SCOPE_DIRECT_SUPER : 0),
|
||||
);
|
||||
this.parseFunctionParams((node: any), allowModifiers);
|
||||
this.checkYieldAwaitInDefaultParams();
|
||||
this.parseFunctionBodyAndFinish(node, type);
|
||||
|
||||
this.state.inFunction = oldInFunc;
|
||||
this.state.inMethod = oldInMethod;
|
||||
this.state.inAsync = oldInAsync;
|
||||
this.state.inGenerator = oldInGenerator;
|
||||
this.state.yieldPos = oldYieldPos;
|
||||
this.state.awaitPos = oldAwaitPos;
|
||||
|
||||
@@ -1753,17 +1756,12 @@ export default class ExpressionParser extends LValParser {
|
||||
params: ?(N.Expression[]),
|
||||
isAsync: boolean,
|
||||
): N.ArrowFunctionExpression {
|
||||
this.scope.enter(functionFlags(isAsync, false) | SCOPE_ARROW);
|
||||
this.initFunction(node, isAsync);
|
||||
|
||||
const oldInFunc = this.state.inFunction;
|
||||
const oldInAsync = this.state.inAsync;
|
||||
const oldInGenerator = this.state.inGenerator;
|
||||
const oldMaybeInArrowParameters = this.state.maybeInArrowParameters;
|
||||
const oldYieldPos = this.state.yieldPos;
|
||||
const oldAwaitPos = this.state.awaitPos;
|
||||
this.state.inFunction = true;
|
||||
this.state.inAsync = isAsync;
|
||||
this.state.inGenerator = false;
|
||||
this.state.maybeInArrowParameters = false;
|
||||
this.state.yieldPos = 0;
|
||||
this.state.awaitPos = 0;
|
||||
@@ -1771,9 +1769,6 @@ export default class ExpressionParser extends LValParser {
|
||||
if (params) this.setArrowFunctionParameters(node, params);
|
||||
this.parseFunctionBody(node, true);
|
||||
|
||||
this.state.inAsync = oldInAsync;
|
||||
this.state.inGenerator = oldInGenerator;
|
||||
this.state.inFunction = oldInFunc;
|
||||
this.state.maybeInArrowParameters = oldMaybeInArrowParameters;
|
||||
this.state.yieldPos = oldYieldPos;
|
||||
this.state.awaitPos = oldAwaitPos;
|
||||
@@ -1819,57 +1814,89 @@ export default class ExpressionParser extends LValParser {
|
||||
// Parse function body and check parameters.
|
||||
parseFunctionBody(node: N.Function, allowExpression: ?boolean): void {
|
||||
const isExpression = allowExpression && !this.match(tt.braceL);
|
||||
const oldStrict = this.state.strict;
|
||||
let useStrict = false;
|
||||
|
||||
const oldInParameters = this.state.inParameters;
|
||||
this.state.inParameters = false;
|
||||
|
||||
if (isExpression) {
|
||||
node.body = this.parseMaybeAssign();
|
||||
this.checkParams(node, false, allowExpression);
|
||||
} else {
|
||||
const nonSimple = !this.isSimpleParamList(node.params);
|
||||
if (!oldStrict || nonSimple) {
|
||||
useStrict = this.strictDirective(this.state.end);
|
||||
// If this is a strict mode function, verify that argument names
|
||||
// are not repeated, and it does not try to bind the words `eval`
|
||||
// or `arguments`.
|
||||
if (useStrict && nonSimple) {
|
||||
// This logic is here to align the error location with the estree plugin
|
||||
const errorPos =
|
||||
// $FlowIgnore
|
||||
(node.kind === "method" || node.kind === "constructor") &&
|
||||
// $FlowIgnore
|
||||
!!node.key
|
||||
? node.key.end
|
||||
: node.start;
|
||||
this.raise(
|
||||
errorPos,
|
||||
"Illegal 'use strict' directive in function with non-simple parameter list",
|
||||
);
|
||||
}
|
||||
}
|
||||
// Start a new scope with regard to labels
|
||||
// flag (restore them to their old value afterwards).
|
||||
const oldInFunc = this.state.inFunction;
|
||||
const oldLabels = this.state.labels;
|
||||
this.state.inFunction = true;
|
||||
this.state.labels = [];
|
||||
node.body = this.parseBlock(true);
|
||||
this.state.inFunction = oldInFunc;
|
||||
if (useStrict) this.state.strict = true;
|
||||
// Add the params to varDeclaredNames to ensure that an error is thrown
|
||||
// if a let/const declaration in the function clashes with one of the params.
|
||||
this.checkParams(
|
||||
node,
|
||||
!oldStrict && !useStrict && !allowExpression && !nonSimple,
|
||||
allowExpression,
|
||||
);
|
||||
node.body = this.parseBlock(true, false);
|
||||
this.state.labels = oldLabels;
|
||||
}
|
||||
this.scope.exit();
|
||||
|
||||
this.checkFunctionNameAndParams(node, allowExpression);
|
||||
this.state.inParameters = oldInParameters;
|
||||
}
|
||||
|
||||
checkFunctionNameAndParams(
|
||||
node: N.Function,
|
||||
isArrowFunction: ?boolean,
|
||||
): void {
|
||||
// If this is a strict mode function, verify that argument names
|
||||
// are not repeated, and it does not try to bind the words `eval`
|
||||
// or `arguments`.
|
||||
const isStrict = this.isStrictBody(node);
|
||||
// Also check for arrow functions
|
||||
const checkLVal = this.state.strict || isStrict || isArrowFunction;
|
||||
|
||||
const oldStrict = this.state.strict;
|
||||
if (isStrict) this.state.strict = isStrict;
|
||||
|
||||
if (checkLVal) {
|
||||
const nameHash: any = Object.create(null);
|
||||
if (node.id) {
|
||||
this.checkLVal(node.id, true, undefined, "function name");
|
||||
}
|
||||
for (const param of node.params) {
|
||||
if (isStrict && param.type !== "Identifier") {
|
||||
this.raise(param.start, "Non-simple parameter in strict mode");
|
||||
}
|
||||
this.checkLVal(param, true, nameHash, "function parameter list");
|
||||
}
|
||||
// Ensure the function name isn't a forbidden identifier in strict mode, e.g. 'eval'
|
||||
if (this.state.strict && node.id) {
|
||||
this.checkLVal(node.id, BIND_OUTSIDE, undefined, "function name");
|
||||
}
|
||||
this.state.strict = oldStrict;
|
||||
}
|
||||
|
||||
isSimpleParamList(
|
||||
params: $ReadOnlyArray<N.Pattern | N.TSParameterProperty>,
|
||||
): boolean {
|
||||
for (let i = 0, len = params.length; i < len; i++) {
|
||||
if (params[i].type !== "Identifier") return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
checkParams(
|
||||
node: N.Function,
|
||||
allowDuplicates: boolean,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
isArrowFunction: ?boolean,
|
||||
): void {
|
||||
// $FlowIssue
|
||||
const nameHash: {} = Object.create(null);
|
||||
for (let i = 0; i < node.params.length; i++) {
|
||||
this.checkLVal(
|
||||
node.params[i],
|
||||
BIND_VAR,
|
||||
allowDuplicates ? null : nameHash,
|
||||
"function paramter list",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Parses a comma-separated list of expressions, and returns them as
|
||||
// an array. `close` is the token type that ends the list, and
|
||||
// `allowEmpty` can be turned on to allow subsequent commas with
|
||||
@@ -1987,22 +2014,21 @@ export default class ExpressionParser extends LValParser {
|
||||
checkKeywords: boolean,
|
||||
isBinding: boolean,
|
||||
): void {
|
||||
const state = this.state;
|
||||
if (state.inGenerator && word === "yield") {
|
||||
if (this.scope.inGenerator && word === "yield") {
|
||||
this.raise(
|
||||
startLoc,
|
||||
"Can not use 'yield' as identifier inside a generator",
|
||||
);
|
||||
}
|
||||
|
||||
if (state.inAsync && word === "await") {
|
||||
if (this.scope.inAsync && word === "await") {
|
||||
this.raise(
|
||||
startLoc,
|
||||
"Can not use 'await' as identifier inside an async function",
|
||||
);
|
||||
}
|
||||
|
||||
if (state.inClassProperty && word === "arguments") {
|
||||
if (this.state.inClassProperty && word === "arguments") {
|
||||
this.raise(
|
||||
startLoc,
|
||||
"'arguments' is not allowed in class field initializer",
|
||||
@@ -2012,14 +2038,14 @@ export default class ExpressionParser extends LValParser {
|
||||
this.raise(startLoc, `Unexpected keyword '${word}'`);
|
||||
}
|
||||
|
||||
const reservedTest = !state.strict
|
||||
const reservedTest = !this.state.strict
|
||||
? isReservedWord
|
||||
: isBinding
|
||||
? isStrictBindReservedWord
|
||||
: isStrictReservedWord;
|
||||
|
||||
if (reservedTest(word, this.inModule)) {
|
||||
if (!state.inAsync && word === "await") {
|
||||
if (!this.scope.inAsync && word === "await") {
|
||||
this.raise(
|
||||
startLoc,
|
||||
"Can not use keyword 'await' outside an async function",
|
||||
|
||||
@@ -5,6 +5,8 @@ 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";
|
||||
|
||||
export type PluginsMap = Map<string, { [string]: any }>;
|
||||
|
||||
@@ -20,11 +22,13 @@ export default class Parser extends StatementParser {
|
||||
|
||||
this.options = options;
|
||||
this.inModule = this.options.sourceType === "module";
|
||||
this.scope = new ScopeHandler(this.raise.bind(this), this.inModule);
|
||||
this.plugins = pluginsMap(this.options.plugins);
|
||||
this.filename = options.sourceFilename;
|
||||
}
|
||||
|
||||
parse(): File {
|
||||
this.scope.enter(SCOPE_PROGRAM);
|
||||
const file = this.startNode();
|
||||
const program = this.startNode();
|
||||
this.nextToken();
|
||||
|
||||
@@ -16,6 +16,7 @@ import type {
|
||||
import type { Pos, Position } from "../util/location";
|
||||
import { isStrictBindReservedWord } from "../util/identifier";
|
||||
import { NodeUtils } from "./node";
|
||||
import { type BindingTypes, BIND_NONE, BIND_OUTSIDE } from "../util/scopeflags";
|
||||
|
||||
export default class LValParser extends NodeUtils {
|
||||
// Forward-declaration: defined in expression.js
|
||||
@@ -262,7 +263,7 @@ export default class LValParser extends NodeUtils {
|
||||
elts.push(this.parseAssignableListItemTypes(this.parseRest()));
|
||||
this.checkCommaAfterRest(
|
||||
close,
|
||||
this.state.inFunction && this.state.inParameters
|
||||
this.scope.inFunction && this.state.inParameters
|
||||
? "parameter"
|
||||
: "element",
|
||||
);
|
||||
@@ -325,7 +326,7 @@ export default class LValParser extends NodeUtils {
|
||||
|
||||
checkLVal(
|
||||
expr: Expression,
|
||||
isBinding: ?boolean,
|
||||
bindingType: ?BindingTypes = BIND_NONE,
|
||||
checkClashes: ?{ [key: string]: boolean },
|
||||
contextDescription: string,
|
||||
): void {
|
||||
@@ -337,7 +338,7 @@ export default class LValParser extends NodeUtils {
|
||||
) {
|
||||
this.raise(
|
||||
expr.start,
|
||||
`${isBinding ? "Binding" : "Assigning to"} '${
|
||||
`${bindingType === BIND_NONE ? "Assigning to" : "Binding"} '${
|
||||
expr.name
|
||||
}' in strict mode`,
|
||||
);
|
||||
@@ -358,15 +359,20 @@ export default class LValParser extends NodeUtils {
|
||||
const key = `_${expr.name}`;
|
||||
|
||||
if (checkClashes[key]) {
|
||||
this.raise(expr.start, "Argument name clash in strict mode");
|
||||
this.raise(expr.start, "Argument name clash");
|
||||
} else {
|
||||
checkClashes[key] = true;
|
||||
}
|
||||
}
|
||||
if (bindingType !== BIND_NONE && bindingType !== BIND_OUTSIDE) {
|
||||
this.scope.declareName(expr.name, bindingType, expr.start);
|
||||
}
|
||||
break;
|
||||
|
||||
case "MemberExpression":
|
||||
if (isBinding) this.raise(expr.start, "Binding member expression");
|
||||
if (bindingType !== BIND_NONE) {
|
||||
this.raise(expr.start, "Binding member expression");
|
||||
}
|
||||
break;
|
||||
|
||||
case "ObjectPattern":
|
||||
@@ -374,7 +380,7 @@ export default class LValParser extends NodeUtils {
|
||||
if (prop.type === "ObjectProperty") prop = prop.value;
|
||||
this.checkLVal(
|
||||
prop,
|
||||
isBinding,
|
||||
bindingType,
|
||||
checkClashes,
|
||||
"object destructuring pattern",
|
||||
);
|
||||
@@ -386,7 +392,7 @@ export default class LValParser extends NodeUtils {
|
||||
if (elem) {
|
||||
this.checkLVal(
|
||||
elem,
|
||||
isBinding,
|
||||
bindingType,
|
||||
checkClashes,
|
||||
"array destructuring pattern",
|
||||
);
|
||||
@@ -397,21 +403,26 @@ export default class LValParser extends NodeUtils {
|
||||
case "AssignmentPattern":
|
||||
this.checkLVal(
|
||||
expr.left,
|
||||
isBinding,
|
||||
bindingType,
|
||||
checkClashes,
|
||||
"assignment pattern",
|
||||
);
|
||||
break;
|
||||
|
||||
case "RestElement":
|
||||
this.checkLVal(expr.argument, isBinding, checkClashes, "rest element");
|
||||
this.checkLVal(
|
||||
expr.argument,
|
||||
bindingType,
|
||||
checkClashes,
|
||||
"rest element",
|
||||
);
|
||||
break;
|
||||
|
||||
default: {
|
||||
const message =
|
||||
(isBinding
|
||||
? /* istanbul ignore next */ "Binding invalid"
|
||||
: "Invalid") +
|
||||
(bindingType === BIND_NONE
|
||||
? "Invalid"
|
||||
: /* istanbul ignore next */ "Binding invalid") +
|
||||
" left-hand side" +
|
||||
(contextDescription
|
||||
? " in " + contextDescription
|
||||
|
||||
@@ -10,6 +10,17 @@ import {
|
||||
} from "../util/identifier";
|
||||
import { lineBreak, skipWhiteSpace } from "../util/whitespace";
|
||||
import * as charCodes from "charcodes";
|
||||
import {
|
||||
BIND_SIMPLE_CATCH,
|
||||
BIND_LEXICAL,
|
||||
BIND_VAR,
|
||||
BIND_FUNCTION,
|
||||
functionFlags,
|
||||
SCOPE_CLASS,
|
||||
SCOPE_OTHER,
|
||||
SCOPE_SIMPLE_CATCH,
|
||||
SCOPE_SUPER,
|
||||
} from "../util/scopeflags";
|
||||
|
||||
// Reused empty array added for node fields that are always empty.
|
||||
|
||||
@@ -18,6 +29,11 @@ const empty = [];
|
||||
const loopLabel = { kind: "loop" },
|
||||
switchLabel = { kind: "switch" };
|
||||
|
||||
const FUNC_NO_FLAGS = 0b000,
|
||||
FUNC_STATEMENT = 0b001,
|
||||
FUNC_HANGING_STATEMENT = 0b010,
|
||||
FUNC_NULLABLE_ID = 0b100;
|
||||
|
||||
export default class StatementParser extends ExpressionParser {
|
||||
// ### Statement parsing
|
||||
|
||||
@@ -144,26 +160,24 @@ export default class StatementParser extends ExpressionParser {
|
||||
return this.parseDoStatement(node);
|
||||
case tt._for:
|
||||
return this.parseForStatement(node);
|
||||
case tt._function: {
|
||||
case tt._function:
|
||||
if (this.lookahead().type === tt.dot) break;
|
||||
if (
|
||||
context &&
|
||||
(this.state.strict || (context !== "if" && context !== "label"))
|
||||
) {
|
||||
this.raise(
|
||||
this.state.start,
|
||||
"Function declaration not allowed in this context",
|
||||
);
|
||||
if (context) {
|
||||
if (this.state.strict) {
|
||||
this.raise(
|
||||
this.state.start,
|
||||
"In strict mode code, functions can only be declared at top level or inside a block",
|
||||
);
|
||||
} else if (context !== "if" && context !== "label") {
|
||||
this.raise(
|
||||
this.state.start,
|
||||
"In non-strict mode code, functions can only be declared at top level, " +
|
||||
"inside a block, or as the body of an if statement",
|
||||
);
|
||||
}
|
||||
}
|
||||
const result = this.parseFunctionStatement(node);
|
||||
return this.parseFunctionStatement(node, false, !context);
|
||||
|
||||
// TODO: Remove this once we have proper scope tracking in place.
|
||||
if (context && result.generator) {
|
||||
this.unexpected(node.start);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
case tt._class:
|
||||
if (context) this.unexpected();
|
||||
return this.parseClass(node, true);
|
||||
@@ -182,7 +196,12 @@ export default class StatementParser extends ExpressionParser {
|
||||
case tt._const:
|
||||
case tt._var:
|
||||
kind = kind || this.state.value;
|
||||
if (context && kind !== "var") this.unexpected();
|
||||
if (context && kind !== "var") {
|
||||
this.unexpected(
|
||||
this.state.start,
|
||||
"Lexical declaration cannot appear in a single-statement context",
|
||||
);
|
||||
}
|
||||
return this.parseVarStatement(node, kind);
|
||||
|
||||
case tt._while:
|
||||
@@ -237,24 +256,19 @@ export default class StatementParser extends ExpressionParser {
|
||||
|
||||
return result;
|
||||
}
|
||||
case tt.name:
|
||||
if (this.isContextual("async")) {
|
||||
// peek ahead and see if next token is a function
|
||||
const state = this.state.clone();
|
||||
this.next();
|
||||
if (this.match(tt._function) && !this.canInsertSemicolon()) {
|
||||
if (context) {
|
||||
this.raise(
|
||||
this.state.lastTokStart,
|
||||
"Function declaration not allowed in this context",
|
||||
);
|
||||
}
|
||||
this.next();
|
||||
return this.parseFunction(node, true, false, true);
|
||||
} else {
|
||||
this.state = state;
|
||||
|
||||
default: {
|
||||
if (this.isAsyncFunction()) {
|
||||
if (context) {
|
||||
this.unexpected(
|
||||
null,
|
||||
"Async functions can only be declared at the top level or inside a block",
|
||||
);
|
||||
}
|
||||
this.next();
|
||||
return this.parseFunctionStatement(node, true, !context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the statement does not start with a statement keyword or a
|
||||
@@ -461,12 +475,13 @@ export default class StatementParser extends ExpressionParser {
|
||||
|
||||
let awaitAt = -1;
|
||||
if (
|
||||
(this.state.inAsync ||
|
||||
(!this.state.inFunction && this.options.allowAwaitOutsideFunction)) &&
|
||||
(this.scope.inAsync ||
|
||||
(!this.scope.inFunction && this.options.allowAwaitOutsideFunction)) &&
|
||||
this.eatContextual("await")
|
||||
) {
|
||||
awaitAt = this.state.lastTokStart;
|
||||
}
|
||||
this.scope.enter(SCOPE_OTHER);
|
||||
this.expect(tt.parenL);
|
||||
|
||||
if (this.match(tt.semi)) {
|
||||
@@ -524,9 +539,18 @@ export default class StatementParser extends ExpressionParser {
|
||||
return this.parseFor(node, init);
|
||||
}
|
||||
|
||||
parseFunctionStatement(node: N.FunctionDeclaration): N.FunctionDeclaration {
|
||||
parseFunctionStatement(
|
||||
node: N.FunctionDeclaration,
|
||||
isAsync?: boolean,
|
||||
declarationPosition?: boolean,
|
||||
): N.FunctionDeclaration {
|
||||
this.next();
|
||||
return this.parseFunction(node, true);
|
||||
return this.parseFunction(
|
||||
node,
|
||||
FUNC_STATEMENT | (declarationPosition ? 0 : FUNC_HANGING_STATEMENT),
|
||||
false,
|
||||
isAsync,
|
||||
);
|
||||
}
|
||||
|
||||
parseIfStatement(node: N.IfStatement): N.IfStatement {
|
||||
@@ -538,7 +562,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
}
|
||||
|
||||
parseReturnStatement(node: N.ReturnStatement): N.ReturnStatement {
|
||||
if (!this.state.inFunction && !this.options.allowReturnOutsideFunction) {
|
||||
if (!this.scope.inFunction && !this.options.allowReturnOutsideFunction) {
|
||||
this.raise(this.state.start, "'return' outside of function");
|
||||
}
|
||||
|
||||
@@ -564,6 +588,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
const cases = (node.cases = []);
|
||||
this.expect(tt.braceL);
|
||||
this.state.labels.push(switchLabel);
|
||||
this.scope.enter(SCOPE_OTHER);
|
||||
|
||||
// Statements under must be grouped (by label) in SwitchCase
|
||||
// nodes. `cur` is used to keep the node that we are currently
|
||||
@@ -595,6 +620,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.scope.exit();
|
||||
if (cur) this.finishNode(cur, "SwitchCase");
|
||||
this.next(); // Closing brace
|
||||
this.state.labels.pop();
|
||||
@@ -627,11 +653,18 @@ export default class StatementParser extends ExpressionParser {
|
||||
if (this.match(tt.parenL)) {
|
||||
this.expect(tt.parenL);
|
||||
clause.param = this.parseBindingAtom();
|
||||
const clashes: any = Object.create(null);
|
||||
this.checkLVal(clause.param, true, clashes, "catch clause");
|
||||
const simple = clause.param.type === "Identifier";
|
||||
this.scope.enter(simple ? SCOPE_SIMPLE_CATCH : 0);
|
||||
this.checkLVal(
|
||||
clause.param,
|
||||
simple ? BIND_SIMPLE_CATCH : BIND_LEXICAL,
|
||||
null,
|
||||
"catch clause",
|
||||
);
|
||||
this.expect(tt.parenR);
|
||||
} else {
|
||||
clause.param = null;
|
||||
this.scope.enter(SCOPE_OTHER);
|
||||
}
|
||||
|
||||
clause.body =
|
||||
@@ -641,8 +674,9 @@ export default class StatementParser extends ExpressionParser {
|
||||
// outside of the function body.
|
||||
this.withTopicForbiddingContext(() =>
|
||||
// Parse the catch clause's body.
|
||||
this.parseBlock(false),
|
||||
this.parseBlock(false, false),
|
||||
);
|
||||
this.scope.exit();
|
||||
|
||||
node.handler = this.finishNode(clause, "CatchClause");
|
||||
}
|
||||
@@ -769,10 +803,19 @@ export default class StatementParser extends ExpressionParser {
|
||||
// strict"` declarations when `allowStrict` is true (used for
|
||||
// function bodies).
|
||||
|
||||
parseBlock(allowDirectives?: boolean): N.BlockStatement {
|
||||
parseBlock(
|
||||
allowDirectives?: boolean = false,
|
||||
createNewLexicalScope?: boolean = true,
|
||||
): N.BlockStatement {
|
||||
const node = this.startNode();
|
||||
this.expect(tt.braceL);
|
||||
if (createNewLexicalScope) {
|
||||
this.scope.enter(SCOPE_OTHER);
|
||||
}
|
||||
this.parseBlockBody(node, allowDirectives, false, tt.braceR);
|
||||
if (createNewLexicalScope) {
|
||||
this.scope.exit();
|
||||
}
|
||||
return this.finishNode(node, "BlockStatement");
|
||||
}
|
||||
|
||||
@@ -858,6 +901,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
node.update = this.match(tt.parenR) ? null : this.parseExpression();
|
||||
this.expect(tt.parenR);
|
||||
|
||||
this.scope.exit();
|
||||
node.body =
|
||||
// For the smartPipelines plugin: Disable topic references from outer
|
||||
// contexts within the loop body. They are permitted in test expressions,
|
||||
@@ -893,6 +937,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
node.right = this.parseExpression();
|
||||
this.expect(tt.parenR);
|
||||
|
||||
this.scope.exit();
|
||||
node.body =
|
||||
// For the smartPipelines plugin:
|
||||
// Disable topic references from outer contexts within the loop body.
|
||||
@@ -954,7 +999,12 @@ export default class StatementParser extends ExpressionParser {
|
||||
this.unexpected(null, "let is disallowed as a lexically bound name");
|
||||
}
|
||||
decl.id = this.parseBindingAtom();
|
||||
this.checkLVal(decl.id, true, undefined, "variable declaration");
|
||||
this.checkLVal(
|
||||
decl.id,
|
||||
kind === "var" ? BIND_VAR : BIND_LEXICAL,
|
||||
undefined,
|
||||
"variable declaration",
|
||||
);
|
||||
}
|
||||
|
||||
// Parse a function declaration or literal (depending on the
|
||||
@@ -962,51 +1012,56 @@ export default class StatementParser extends ExpressionParser {
|
||||
|
||||
parseFunction<T: N.NormalFunction>(
|
||||
node: T,
|
||||
isStatement: boolean,
|
||||
statement?: number = FUNC_NO_FLAGS,
|
||||
allowExpressionBody?: boolean = false,
|
||||
isAsync?: boolean = false,
|
||||
optionalId?: boolean = false,
|
||||
): T {
|
||||
const oldInFunc = this.state.inFunction;
|
||||
const oldInMethod = this.state.inMethod;
|
||||
const oldInAsync = this.state.inAsync;
|
||||
const oldInGenerator = this.state.inGenerator;
|
||||
const oldInClassProperty = this.state.inClassProperty;
|
||||
const oldYieldPos = this.state.yieldPos;
|
||||
const oldAwaitPos = this.state.awaitPos;
|
||||
this.state.inFunction = true;
|
||||
this.state.inMethod = false;
|
||||
this.state.inClassProperty = false;
|
||||
this.state.yieldPos = 0;
|
||||
this.state.awaitPos = 0;
|
||||
const isStatement = statement & FUNC_STATEMENT;
|
||||
const isHangingStatement = statement & FUNC_HANGING_STATEMENT;
|
||||
|
||||
this.initFunction(node, isAsync);
|
||||
|
||||
if (this.match(tt.star) && isHangingStatement) {
|
||||
this.unexpected(
|
||||
this.state.start,
|
||||
"Generators can only be declared at the top level or inside a block",
|
||||
);
|
||||
}
|
||||
node.generator = this.eat(tt.star);
|
||||
|
||||
if (isStatement && !optionalId && !this.match(tt.name)) {
|
||||
this.unexpected();
|
||||
if (isStatement) {
|
||||
node.id =
|
||||
statement & FUNC_NULLABLE_ID && !this.match(tt.name)
|
||||
? null
|
||||
: this.parseIdentifier();
|
||||
if (node.id && !isHangingStatement) {
|
||||
// If it is a regular function declaration in sloppy mode, then it is
|
||||
// subject to Annex B semantics (BIND_FUNCTION). Otherwise, the binding
|
||||
// mode depends on properties of the current scope (see
|
||||
// treatFunctionsAsVar).
|
||||
this.checkLVal(
|
||||
node.id,
|
||||
this.state.strict || node.generator || node.async
|
||||
? this.scope.treatFunctionsAsVar
|
||||
? BIND_VAR
|
||||
: BIND_LEXICAL
|
||||
: BIND_FUNCTION,
|
||||
null,
|
||||
"function name",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// When parsing function expression, the binding identifier is parsed
|
||||
// according to the rules inside the function.
|
||||
// e.g. (function* yield() {}) is invalid because "yield" is disallowed in
|
||||
// generators.
|
||||
// This isn't the case with function declarations: function* yield() {} is
|
||||
// valid because yield is parsed as if it was outside the generator.
|
||||
// Therefore, this.state.inGenerator is set before or after parsing the
|
||||
// function id according to the "isStatement" parameter.
|
||||
// The same applies to await & async functions.
|
||||
const oldInClassProperty = this.state.inClassProperty;
|
||||
const oldYieldPos = this.state.yieldPos;
|
||||
const oldAwaitPos = this.state.awaitPos;
|
||||
this.state.inClassProperty = false;
|
||||
this.state.yieldPos = 0;
|
||||
this.state.awaitPos = 0;
|
||||
this.scope.enter(functionFlags(node.async, node.generator));
|
||||
|
||||
if (!isStatement) {
|
||||
this.state.inAsync = isAsync;
|
||||
this.state.inGenerator = node.generator;
|
||||
}
|
||||
if (this.match(tt.name)) {
|
||||
node.id = this.parseIdentifier();
|
||||
}
|
||||
if (isStatement) {
|
||||
this.state.inAsync = isAsync;
|
||||
this.state.inGenerator = node.generator;
|
||||
node.id = this.match(tt.name) ? this.parseIdentifier() : null;
|
||||
}
|
||||
|
||||
this.parseFunctionParams(node);
|
||||
@@ -1023,10 +1078,6 @@ export default class StatementParser extends ExpressionParser {
|
||||
);
|
||||
});
|
||||
|
||||
this.state.inFunction = oldInFunc;
|
||||
this.state.inMethod = oldInMethod;
|
||||
this.state.inAsync = oldInAsync;
|
||||
this.state.inGenerator = oldInGenerator;
|
||||
this.state.inClassProperty = oldInClassProperty;
|
||||
this.state.yieldPos = oldYieldPos;
|
||||
this.state.awaitPos = oldAwaitPos;
|
||||
@@ -1099,11 +1150,12 @@ export default class StatementParser extends ExpressionParser {
|
||||
const state = { hadConstructor: false };
|
||||
let decorators: N.Decorator[] = [];
|
||||
const classBody: N.ClassBody = this.startNode();
|
||||
|
||||
classBody.body = [];
|
||||
|
||||
this.expect(tt.braceL);
|
||||
|
||||
const constructorAllowsSuper = node.superClass !== null;
|
||||
|
||||
// For the smartPipelines plugin: Disable topic references from outer
|
||||
// contexts within the class body. They are permitted in test expressions,
|
||||
// outside of the class body.
|
||||
@@ -1133,7 +1185,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
decorators = [];
|
||||
}
|
||||
|
||||
this.parseClassMember(classBody, member, state);
|
||||
this.parseClassMember(classBody, member, state, constructorAllowsSuper);
|
||||
|
||||
if (
|
||||
member.kind === "constructor" &&
|
||||
@@ -1164,6 +1216,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
classBody: N.ClassBody,
|
||||
member: N.ClassMember,
|
||||
state: { hadConstructor: boolean },
|
||||
constructorAllowsSuper: boolean,
|
||||
): void {
|
||||
let isStatic = false;
|
||||
const containsEsc = this.state.containsEsc;
|
||||
@@ -1185,6 +1238,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
false,
|
||||
false,
|
||||
/* isConstructor */ false,
|
||||
false,
|
||||
);
|
||||
return;
|
||||
} else if (this.isClassProperty()) {
|
||||
@@ -1204,7 +1258,13 @@ export default class StatementParser extends ExpressionParser {
|
||||
isStatic = true;
|
||||
}
|
||||
|
||||
this.parseClassMemberWithIsStatic(classBody, member, state, isStatic);
|
||||
this.parseClassMemberWithIsStatic(
|
||||
classBody,
|
||||
member,
|
||||
state,
|
||||
isStatic,
|
||||
constructorAllowsSuper,
|
||||
);
|
||||
}
|
||||
|
||||
parseClassMemberWithIsStatic(
|
||||
@@ -1212,6 +1272,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
member: N.ClassMember,
|
||||
state: { hadConstructor: boolean },
|
||||
isStatic: boolean,
|
||||
constructorAllowsSuper: boolean,
|
||||
) {
|
||||
const publicMethod: $FlowSubtype<N.ClassMethod> = member;
|
||||
const privateMethod: $FlowSubtype<N.ClassPrivateMethod> = member;
|
||||
@@ -1244,6 +1305,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
true,
|
||||
false,
|
||||
/* isConstructor */ false,
|
||||
false,
|
||||
);
|
||||
|
||||
return;
|
||||
@@ -1266,7 +1328,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
|
||||
// a normal method
|
||||
const isConstructor = this.isNonstaticConstructor(publicMethod);
|
||||
|
||||
let allowsDirectSuper = false;
|
||||
if (isConstructor) {
|
||||
publicMethod.kind = "constructor";
|
||||
|
||||
@@ -1282,6 +1344,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
this.raise(key.start, "Duplicate constructor in the same class");
|
||||
}
|
||||
state.hadConstructor = true;
|
||||
allowsDirectSuper = constructorAllowsSuper;
|
||||
}
|
||||
|
||||
this.pushClassMethod(
|
||||
@@ -1290,6 +1353,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
false,
|
||||
false,
|
||||
isConstructor,
|
||||
allowsDirectSuper,
|
||||
);
|
||||
} else if (this.isClassProperty()) {
|
||||
if (isPrivate) {
|
||||
@@ -1327,6 +1391,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
isGenerator,
|
||||
true,
|
||||
/* isConstructor */ false,
|
||||
false,
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
@@ -1356,6 +1421,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
false,
|
||||
false,
|
||||
/* isConstructor */ false,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1422,6 +1488,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
isGenerator: boolean,
|
||||
isAsync: boolean,
|
||||
isConstructor: boolean,
|
||||
allowsDirectSuper: boolean,
|
||||
): void {
|
||||
classBody.body.push(
|
||||
this.parseMethod(
|
||||
@@ -1429,7 +1496,9 @@ export default class StatementParser extends ExpressionParser {
|
||||
isGenerator,
|
||||
isAsync,
|
||||
isConstructor,
|
||||
allowsDirectSuper,
|
||||
"ClassMethod",
|
||||
true,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1447,7 +1516,9 @@ export default class StatementParser extends ExpressionParser {
|
||||
isGenerator,
|
||||
isAsync,
|
||||
/* isConstructor */ false,
|
||||
false,
|
||||
"ClassPrivateMethod",
|
||||
true,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1466,13 +1537,16 @@ export default class StatementParser extends ExpressionParser {
|
||||
parseClassPrivateProperty(
|
||||
node: N.ClassPrivateProperty,
|
||||
): N.ClassPrivateProperty {
|
||||
const oldInMethod = this.state.inMethod;
|
||||
this.state.inMethod = false;
|
||||
this.state.inClassProperty = true;
|
||||
|
||||
this.scope.enter(SCOPE_CLASS | SCOPE_SUPER);
|
||||
|
||||
node.value = this.eat(tt.eq) ? this.parseMaybeAssign() : null;
|
||||
this.semicolon();
|
||||
this.state.inClassProperty = false;
|
||||
this.state.inMethod = oldInMethod;
|
||||
|
||||
this.scope.exit();
|
||||
|
||||
return this.finishNode(node, "ClassPrivateProperty");
|
||||
}
|
||||
|
||||
@@ -1481,10 +1555,10 @@ export default class StatementParser extends ExpressionParser {
|
||||
this.expectPlugin("classProperties");
|
||||
}
|
||||
|
||||
const oldInMethod = this.state.inMethod;
|
||||
this.state.inMethod = false;
|
||||
this.state.inClassProperty = true;
|
||||
|
||||
this.scope.enter(SCOPE_CLASS | SCOPE_SUPER);
|
||||
|
||||
if (this.match(tt.eq)) {
|
||||
this.expectPlugin("classProperties");
|
||||
this.next();
|
||||
@@ -1494,7 +1568,8 @@ export default class StatementParser extends ExpressionParser {
|
||||
}
|
||||
this.semicolon();
|
||||
this.state.inClassProperty = false;
|
||||
this.state.inMethod = oldInMethod;
|
||||
|
||||
this.scope.exit();
|
||||
|
||||
return this.finishNode(node, "ClassProperty");
|
||||
}
|
||||
@@ -1506,6 +1581,9 @@ export default class StatementParser extends ExpressionParser {
|
||||
): void {
|
||||
if (this.match(tt.name)) {
|
||||
node.id = this.parseIdentifier();
|
||||
if (isStatement) {
|
||||
this.checkLVal(node.id, BIND_LEXICAL, undefined, "class name");
|
||||
}
|
||||
} else {
|
||||
if (optionalId || !isStatement) {
|
||||
node.id = null;
|
||||
@@ -1642,7 +1720,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
return false;
|
||||
}
|
||||
|
||||
isAsyncFunction() {
|
||||
isAsyncFunction(): boolean {
|
||||
if (!this.isContextual("async")) return false;
|
||||
|
||||
const { input, pos, length } = this.state;
|
||||
@@ -1666,13 +1744,18 @@ export default class StatementParser extends ExpressionParser {
|
||||
|
||||
const isAsync = this.isAsyncFunction();
|
||||
|
||||
if (this.eat(tt._function) || isAsync) {
|
||||
if (this.match(tt._function) || isAsync) {
|
||||
this.next();
|
||||
if (isAsync) {
|
||||
this.eatContextual("async");
|
||||
this.expect(tt._function);
|
||||
this.next();
|
||||
}
|
||||
|
||||
return this.parseFunction(expr, true, false, isAsync, true);
|
||||
return this.parseFunction(
|
||||
expr,
|
||||
FUNC_STATEMENT | FUNC_NULLABLE_ID,
|
||||
false,
|
||||
isAsync,
|
||||
);
|
||||
} else if (this.match(tt._class)) {
|
||||
return this.parseClass(expr, true, true);
|
||||
} else if (this.match(tt.at)) {
|
||||
@@ -1927,7 +2010,12 @@ export default class StatementParser extends ExpressionParser {
|
||||
contextDescription: string,
|
||||
): void {
|
||||
specifier.local = this.parseIdentifier();
|
||||
this.checkLVal(specifier.local, true, undefined, contextDescription);
|
||||
this.checkLVal(
|
||||
specifier.local,
|
||||
BIND_LEXICAL,
|
||||
undefined,
|
||||
contextDescription,
|
||||
);
|
||||
node.specifiers.push(this.finishNode(specifier, type));
|
||||
}
|
||||
|
||||
@@ -2000,7 +2088,12 @@ export default class StatementParser extends ExpressionParser {
|
||||
);
|
||||
specifier.local = specifier.imported.__clone();
|
||||
}
|
||||
this.checkLVal(specifier.local, true, undefined, "import specifier");
|
||||
this.checkLVal(
|
||||
specifier.local,
|
||||
BIND_LEXICAL,
|
||||
undefined,
|
||||
"import specifier",
|
||||
);
|
||||
node.specifiers.push(this.finishNode(specifier, "ImportSpecifier"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
import { types as tt, type TokenType } from "../tokenizer/types";
|
||||
import Tokenizer from "../tokenizer";
|
||||
import type { Node } from "../types";
|
||||
import { lineBreak } from "../util/whitespace";
|
||||
import { lineBreak, skipWhiteSpace } from "../util/whitespace";
|
||||
|
||||
const literal = /^(?:;|('|")((?:\\?.)*?)\1)/;
|
||||
|
||||
// ## Parser utilities
|
||||
|
||||
@@ -165,4 +167,18 @@ export default class UtilParser extends Tokenizer {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
strictDirective(start: number): boolean {
|
||||
for (;;) {
|
||||
skipWhiteSpace.lastIndex = start;
|
||||
// $FlowIgnore
|
||||
start += skipWhiteSpace.exec(this.state.input)[0].length;
|
||||
const match = literal.exec(this.state.input.slice(start));
|
||||
if (!match) break;
|
||||
if (match[2] === "use strict") return true;
|
||||
start += match[0].length;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user