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:
Daniel Tschinder 2019-02-25 11:04:52 -08:00 committed by GitHub
parent 918f149a63
commit a7391144b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
284 changed files with 5904 additions and 1842 deletions

View File

@ -24,9 +24,9 @@ declare module B {
} }
declare module "foo" { declare export type * from "bar"; } declare module "foo" { declare export type * from "bar"; }
declare export opaque type Foo; declare export opaque type Foo1;
declare export opaque type Bar<T>; declare export opaque type Bar<T>;
declare export opaque type Baz: Foo; declare export opaque type Baz: Foo;
declare export opaque type Foo<T>: Bar<T>; declare export opaque type Foo3<T>: Bar<T>;
declare export opaque type Foo<T>: Bar; declare export opaque type Foo4<T>: Bar;
declare export opaque type Foo: Bar<T>; declare export opaque type Foo5: Bar<T>;

View File

@ -33,9 +33,9 @@ declare module B {
declare module "foo" { declare module "foo" {
declare export type * from "bar"; declare export type * from "bar";
} }
declare export opaque type Foo; declare export opaque type Foo1;
declare export opaque type Bar<T>; declare export opaque type Bar<T>;
declare export opaque type Baz: Foo; declare export opaque type Baz: Foo;
declare export opaque type Foo<T>: Bar<T>; declare export opaque type Foo3<T>: Bar<T>;
declare export opaque type Foo<T>: Bar; declare export opaque type Foo4<T>: Bar;
declare export opaque type Foo: Bar<T>; declare export opaque type Foo5: Bar<T>;

View File

@ -1,10 +1,10 @@
class C1<+T, -U> {} class C1<+T, -U> {}
function f<+T, -U>() {} function f<+T, -U>() {}
type T<+T, -U> = {}; type T<+T, -U> = {};
type T = { +p: T }; type T2 = { +p: T };
type T = { -p: T }; type T3 = { -p: T };
type T = { +[k:K]: V }; type T4 = { +[k:K]: V };
type T = { -[k:K]: V }; type T5 = { -[k:K]: V };
interface I { +p: T } interface I { +p: T }
interface I { -p: T } interface I { -p: T }
interface I { +[k:K]: V } interface I { +[k:K]: V }

View File

@ -3,16 +3,16 @@ class C1<+T, -U> {}
function f<+T, -U>() {} function f<+T, -U>() {}
type T<+T, -U> = {}; type T<+T, -U> = {};
type T = { type T2 = {
+p: T +p: T
}; };
type T = { type T3 = {
-p: T -p: T
}; };
type T = { type T4 = {
+[k: K]: V +[k: K]: V
}; };
type T = { type T5 = {
-[k: K]: V -[k: K]: V
}; };
interface I { interface I {

View File

@ -1,7 +1,7 @@
declare class C { static [[foo]]: T } declare class C { static [[foo]]: T }
declare class C { [[foo]]: T } declare class C { [[foo]]: T }
interface T { [[foo]]: X } interface I { [[foo]]: X }
interface T { [[foo]](): X } interface I { [[foo]](): X }
type T = { [[foo]]: X } type T1 = { [[foo]]: X }
type T = { [[foo]](): X } type T2 = { [[foo]](): X }
type T = { [[foo]]?: X } type T3 = { [[foo]]?: X }

View File

@ -4,18 +4,18 @@ declare class C {
declare class C { declare class C {
[[foo]]: T [[foo]]: T
} }
interface T { interface I {
[[foo]]: X [[foo]]: X
} }
interface T { interface I {
[[foo]]() => X [[foo]]() => X
} }
type T = { type T1 = {
[[foo]]: X [[foo]]: X
}; };
type T = { type T2 = {
[[foo]]() => X [[foo]]() => X
}; };
type T = { type T3 = {
[[foo]]?: X [[foo]]?: X
}; };

View File

@ -1,11 +1,11 @@
type U = {}; type U = {};
type V = {}; type V = {};
type T = { ...U, }; type T1 = { ...U, };
type T = { ...U, ...V }; type T2 = { ...U, ...V };
type T = { p: V, ...U }; type T3 = { p: V, ...U };
type T = { ...U, p: V, }; type T4 = { ...U, p: V, };
type T = { ...{}|{ p: V, }}; type T5 = { ...{}|{ p: V, }};
type T = { foo(): number } type T6 = { foo(): number }
type T = { foo: () => number } type T7 = { foo: () => number }
type T = { [string]: U }; type T8 = { [string]: U };
type T = { [param: string]: U }; type T9 = { [param: string]: U };

View File

@ -1,30 +1,30 @@
type U = {}; type U = {};
type V = {}; type V = {};
type T = { ...U type T1 = { ...U
}; };
type T = { ...U, type T2 = { ...U,
...V, ...V,
}; };
type T = { type T3 = {
p: V, p: V,
...U, ...U,
}; };
type T = { ...U, type T4 = { ...U,
p: V, p: V,
}; };
type T = { ...{} | { type T5 = { ...{} | {
p: V p: V
} }
}; };
type T = { type T6 = {
foo(): number foo(): number
}; };
type T = { type T7 = {
foo: () => number foo: () => number
}; };
type T = { type T8 = {
[string]: U [string]: U
}; };
type T = { type T9 = {
[param: string]: U [param: string]: U
}; };

View File

@ -1,7 +1,7 @@
opaque type ID = string; opaque type ID = string;
opaque type Foo<T> = Bar<T>; opaque type Foo<T> = Bar<T>;
opaque type Maybe<T> = _Maybe<T, *>; opaque type Maybe<T> = _Maybe<T, *>;
export opaque type Foo = number; export opaque type Foo2 = number;
opaque type union = opaque type union =
| {type: "A"} | {type: "A"}

View File

@ -1,7 +1,7 @@
opaque type ID = string; opaque type ID = string;
opaque type Foo<T> = Bar<T>; opaque type Foo<T> = Bar<T>;
opaque type Maybe<T> = _Maybe<T, *>; opaque type Maybe<T> = _Maybe<T, *>;
export opaque type Foo = number; export opaque type Foo2 = number;
opaque type union = { opaque type union = {
type: "A" type: "A"
} | { } | {

View File

@ -1,7 +1,7 @@
type FBID = number; type FBID = number;
type Foo<T> = Bar<T>; type Foo<T> = Bar<T>;
type Maybe<T> = _Maybe<T, *>; type Maybe<T> = _Maybe<T, *>;
export type Foo = number; export type Foo2 = number;
type union = type union =
| {type: "A"} | {type: "A"}

View File

@ -1,7 +1,7 @@
type FBID = number; type FBID = number;
type Foo<T> = Bar<T>; type Foo<T> = Bar<T>;
type Maybe<T> = _Maybe<T, *>; type Maybe<T> = _Maybe<T, *>;
export type Foo = number; export type Foo2 = number;
type union = { type union = {
type: "A" type: "A"
} | { } | {

View File

@ -3,11 +3,13 @@
import type { Options } from "../options"; import type { Options } from "../options";
import type State from "../tokenizer/state"; import type State from "../tokenizer/state";
import type { PluginsMap } from "./index"; import type { PluginsMap } from "./index";
import type ScopeHandler from "../util/scope";
export default class BaseParser { export default class BaseParser {
// Properties set by constructor in index.js // Properties set by constructor in index.js
options: Options; options: Options;
inModule: boolean; inModule: boolean;
scope: ScopeHandler;
plugins: PluginsMap; plugins: PluginsMap;
filename: ?string; filename: ?string;
sawUnambiguousESM: boolean = false; sawUnambiguousESM: boolean = false;

View File

@ -29,10 +29,23 @@ import {
} from "../util/identifier"; } from "../util/identifier";
import type { Pos, Position } from "../util/location"; import type { Pos, Position } from "../util/location";
import * as charCodes from "charcodes"; 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 { export default class ExpressionParser extends LValParser {
// Forward-declaration: defined in statement.js // Forward-declaration: defined in statement.js
+parseBlock: (allowDirectives?: boolean) => N.BlockStatement; +parseBlock: (
allowDirectives?: boolean,
createNewLexicalScope?: boolean,
) => N.BlockStatement;
+parseClass: ( +parseClass: (
node: N.Class, node: N.Class,
isStatement: boolean, isStatement: boolean,
@ -41,10 +54,9 @@ export default class ExpressionParser extends LValParser {
+parseDecorators: (allowExport?: boolean) => void; +parseDecorators: (allowExport?: boolean) => void;
+parseFunction: <T: N.NormalFunction>( +parseFunction: <T: N.NormalFunction>(
node: T, node: T,
isStatement: boolean, statement?: number,
allowExpressionBody?: boolean, allowExpressionBody?: boolean,
isAsync?: boolean, isAsync?: boolean,
optionalId?: boolean,
) => T; ) => T;
+parseFunctionParams: (node: N.Function, allowModifiers?: boolean) => void; +parseFunctionParams: (node: N.Function, allowModifiers?: boolean) => void;
+takeDecorators: (node: N.HasDecorators) => void; +takeDecorators: (node: N.HasDecorators) => void;
@ -74,6 +86,7 @@ export default class ExpressionParser extends LValParser {
// Convenience method to parse an Expression only // Convenience method to parse an Expression only
getExpression(): N.Expression { getExpression(): N.Expression {
this.scope.enter(SCOPE_PROGRAM);
this.nextToken(); this.nextToken();
const expr = this.parseExpression(); const expr = this.parseExpression();
if (!this.match(tt.eof)) { if (!this.match(tt.eof)) {
@ -128,7 +141,7 @@ export default class ExpressionParser extends LValParser {
const startPos = this.state.start; const startPos = this.state.start;
const startLoc = this.state.startLoc; const startLoc = this.state.startLoc;
if (this.isContextual("yield")) { if (this.isContextual("yield")) {
if (this.state.inGenerator) { if (this.scope.inGenerator) {
let left = this.parseYield(noIn); let left = this.parseYield(noIn);
if (afterLeftParse) { if (afterLeftParse) {
left = afterLeftParse.call(this, left, startPos, startLoc); left = afterLeftParse.call(this, left, startPos, startLoc);
@ -336,7 +349,7 @@ export default class ExpressionParser extends LValParser {
if ( if (
this.match(tt.name) && this.match(tt.name) &&
this.state.value === "await" && this.state.value === "await" &&
this.state.inAsync this.scope.inAsync
) { ) {
throw this.raise( throw this.raise(
this.state.start, this.state.start,
@ -421,8 +434,8 @@ export default class ExpressionParser extends LValParser {
parseMaybeUnary(refShorthandDefaultPos: ?Pos): N.Expression { parseMaybeUnary(refShorthandDefaultPos: ?Pos): N.Expression {
if ( if (
this.isContextual("await") && this.isContextual("await") &&
(this.state.inAsync || (this.scope.inAsync ||
(!this.state.inFunction && this.options.allowAwaitOutsideFunction)) (!this.scope.inFunction && this.options.allowAwaitOutsideFunction))
) { ) {
return this.parseAwait(); return this.parseAwait();
} else if (this.state.type.prefix) { } else if (this.state.type.prefix) {
@ -806,11 +819,7 @@ export default class ExpressionParser extends LValParser {
switch (this.state.type) { switch (this.state.type) {
case tt._super: case tt._super:
if ( if (!this.scope.allowSuper && !this.options.allowSuperOutsideMethod) {
!this.state.inMethod &&
!this.state.inClassProperty &&
!this.options.allowSuperOutsideMethod
) {
this.raise( this.raise(
this.state.start, this.state.start,
"super is only allowed in object methods and classes", "super is only allowed in object methods and classes",
@ -819,6 +828,18 @@ export default class ExpressionParser extends LValParser {
node = this.startNode(); node = this.startNode();
this.next(); 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 ( if (
!this.match(tt.parenL) && !this.match(tt.parenL) &&
!this.match(tt.bracketL) && !this.match(tt.bracketL) &&
@ -826,17 +847,7 @@ export default class ExpressionParser extends LValParser {
) { ) {
this.unexpected(); 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"); return this.finishNode(node, "Super");
case tt._import: case tt._import:
@ -870,20 +881,17 @@ export default class ExpressionParser extends LValParser {
!this.canInsertSemicolon() !this.canInsertSemicolon()
) { ) {
this.next(); this.next();
return this.parseFunction(node, false, false, true); return this.parseFunction(node, undefined, false, true);
} else if ( } else if (
canBeArrow && canBeArrow &&
id.name === "async" && id.name === "async" &&
this.match(tt.name) && this.match(tt.name) &&
!this.canInsertSemicolon() !this.canInsertSemicolon()
) { ) {
const oldInAsync = this.state.inAsync;
this.state.inAsync = true;
const params = [this.parseIdentifier()]; const params = [this.parseIdentifier()];
this.expect(tt.arrow); this.expect(tt.arrow);
// let foo = async bar => {}; // let foo = async bar => {};
this.parseArrowExpression(node, params, true); this.parseArrowExpression(node, params, true);
this.state.inAsync = oldInAsync;
return node; return node;
} }
@ -900,12 +908,9 @@ export default class ExpressionParser extends LValParser {
this.expectPlugin("doExpressions"); this.expectPlugin("doExpressions");
const node = this.startNode(); const node = this.startNode();
this.next(); this.next();
const oldInFunction = this.state.inFunction;
const oldLabels = this.state.labels; const oldLabels = this.state.labels;
this.state.labels = []; this.state.labels = [];
this.state.inFunction = false; node.body = this.parseBlock();
node.body = this.parseBlock(false);
this.state.inFunction = oldInFunction;
this.state.labels = oldLabels; this.state.labels = oldLabels;
return this.finishNode(node, "DoExpression"); return this.finishNode(node, "DoExpression");
} }
@ -1068,10 +1073,10 @@ export default class ExpressionParser extends LValParser {
this.next(); this.next();
meta = this.createIdentifier(meta, "function"); 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.parseMetaProperty(node, meta, "sent");
} }
return this.parseFunction(node, false); return this.parseFunction(node);
} }
parseMetaProperty( parseMetaProperty(
@ -1309,7 +1314,7 @@ export default class ExpressionParser extends LValParser {
if (this.eat(tt.dot)) { if (this.eat(tt.dot)) {
const metaProp = this.parseMetaProperty(node, meta, "target"); 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"; let error = "new.target can only be used in functions";
if (this.hasPlugin("classProperties")) { if (this.hasPlugin("classProperties")) {
@ -1575,6 +1580,7 @@ export default class ExpressionParser extends LValParser {
isGenerator, isGenerator,
isAsync, isAsync,
/* isConstructor */ false, /* isConstructor */ false,
false,
"ObjectMethod", "ObjectMethod",
); );
} }
@ -1588,6 +1594,7 @@ export default class ExpressionParser extends LValParser {
/* isGenerator */ false, /* isGenerator */ false,
/* isAsync */ false, /* isAsync */ false,
/* isConstructor */ false, /* isConstructor */ false,
false,
"ObjectMethod", "ObjectMethod",
); );
this.checkGetterSetterParams(prop); this.checkGetterSetterParams(prop);
@ -1713,32 +1720,28 @@ export default class ExpressionParser extends LValParser {
isGenerator: boolean, isGenerator: boolean,
isAsync: boolean, isAsync: boolean,
isConstructor: boolean, isConstructor: boolean,
allowDirectSuper: boolean,
type: string, type: string,
inClassScope: boolean = false,
): T { ): 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 oldYieldPos = this.state.yieldPos;
const oldAwaitPos = this.state.awaitPos; 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.yieldPos = 0;
this.state.awaitPos = 0; this.state.awaitPos = 0;
this.initFunction(node, isAsync); this.initFunction(node, isAsync);
node.generator = !!isGenerator; node.generator = !!isGenerator;
const allowModifiers = isConstructor; // For TypeScript parameter properties 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.parseFunctionParams((node: any), allowModifiers);
this.checkYieldAwaitInDefaultParams(); this.checkYieldAwaitInDefaultParams();
this.parseFunctionBodyAndFinish(node, type); 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.yieldPos = oldYieldPos;
this.state.awaitPos = oldAwaitPos; this.state.awaitPos = oldAwaitPos;
@ -1753,17 +1756,12 @@ export default class ExpressionParser extends LValParser {
params: ?(N.Expression[]), params: ?(N.Expression[]),
isAsync: boolean, isAsync: boolean,
): N.ArrowFunctionExpression { ): N.ArrowFunctionExpression {
this.scope.enter(functionFlags(isAsync, false) | SCOPE_ARROW);
this.initFunction(node, isAsync); 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 oldMaybeInArrowParameters = this.state.maybeInArrowParameters;
const oldYieldPos = this.state.yieldPos; const oldYieldPos = this.state.yieldPos;
const oldAwaitPos = this.state.awaitPos; const oldAwaitPos = this.state.awaitPos;
this.state.inFunction = true;
this.state.inAsync = isAsync;
this.state.inGenerator = false;
this.state.maybeInArrowParameters = false; this.state.maybeInArrowParameters = false;
this.state.yieldPos = 0; this.state.yieldPos = 0;
this.state.awaitPos = 0; this.state.awaitPos = 0;
@ -1771,9 +1769,6 @@ export default class ExpressionParser extends LValParser {
if (params) this.setArrowFunctionParameters(node, params); if (params) this.setArrowFunctionParameters(node, params);
this.parseFunctionBody(node, true); this.parseFunctionBody(node, true);
this.state.inAsync = oldInAsync;
this.state.inGenerator = oldInGenerator;
this.state.inFunction = oldInFunc;
this.state.maybeInArrowParameters = oldMaybeInArrowParameters; this.state.maybeInArrowParameters = oldMaybeInArrowParameters;
this.state.yieldPos = oldYieldPos; this.state.yieldPos = oldYieldPos;
this.state.awaitPos = oldAwaitPos; this.state.awaitPos = oldAwaitPos;
@ -1819,57 +1814,89 @@ export default class ExpressionParser extends LValParser {
// Parse function body and check parameters. // Parse function body and check parameters.
parseFunctionBody(node: N.Function, allowExpression: ?boolean): void { parseFunctionBody(node: N.Function, allowExpression: ?boolean): void {
const isExpression = allowExpression && !this.match(tt.braceL); const isExpression = allowExpression && !this.match(tt.braceL);
const oldStrict = this.state.strict;
let useStrict = false;
const oldInParameters = this.state.inParameters; const oldInParameters = this.state.inParameters;
this.state.inParameters = false; this.state.inParameters = false;
if (isExpression) { if (isExpression) {
node.body = this.parseMaybeAssign(); node.body = this.parseMaybeAssign();
this.checkParams(node, false, allowExpression);
} else { } 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 // Start a new scope with regard to labels
// flag (restore them to their old value afterwards). // flag (restore them to their old value afterwards).
const oldInFunc = this.state.inFunction;
const oldLabels = this.state.labels; const oldLabels = this.state.labels;
this.state.inFunction = true;
this.state.labels = []; this.state.labels = [];
node.body = this.parseBlock(true); if (useStrict) this.state.strict = true;
this.state.inFunction = oldInFunc; // 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.state.labels = oldLabels;
} }
this.scope.exit();
this.checkFunctionNameAndParams(node, allowExpression);
this.state.inParameters = oldInParameters; this.state.inParameters = oldInParameters;
} // Ensure the function name isn't a forbidden identifier in strict mode, e.g. 'eval'
if (this.state.strict && node.id) {
checkFunctionNameAndParams( this.checkLVal(node.id, BIND_OUTSIDE, undefined, "function name");
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");
}
} }
this.state.strict = oldStrict; 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 // Parses a comma-separated list of expressions, and returns them as
// an array. `close` is the token type that ends the list, and // an array. `close` is the token type that ends the list, and
// `allowEmpty` can be turned on to allow subsequent commas with // `allowEmpty` can be turned on to allow subsequent commas with
@ -1987,22 +2014,21 @@ export default class ExpressionParser extends LValParser {
checkKeywords: boolean, checkKeywords: boolean,
isBinding: boolean, isBinding: boolean,
): void { ): void {
const state = this.state; if (this.scope.inGenerator && word === "yield") {
if (state.inGenerator && word === "yield") {
this.raise( this.raise(
startLoc, startLoc,
"Can not use 'yield' as identifier inside a generator", "Can not use 'yield' as identifier inside a generator",
); );
} }
if (state.inAsync && word === "await") { if (this.scope.inAsync && word === "await") {
this.raise( this.raise(
startLoc, startLoc,
"Can not use 'await' as identifier inside an async function", "Can not use 'await' as identifier inside an async function",
); );
} }
if (state.inClassProperty && word === "arguments") { if (this.state.inClassProperty && word === "arguments") {
this.raise( this.raise(
startLoc, startLoc,
"'arguments' is not allowed in class field initializer", "'arguments' is not allowed in class field initializer",
@ -2012,14 +2038,14 @@ export default class ExpressionParser extends LValParser {
this.raise(startLoc, `Unexpected keyword '${word}'`); this.raise(startLoc, `Unexpected keyword '${word}'`);
} }
const reservedTest = !state.strict const reservedTest = !this.state.strict
? isReservedWord ? isReservedWord
: isBinding : isBinding
? isStrictBindReservedWord ? isStrictBindReservedWord
: isStrictReservedWord; : isStrictReservedWord;
if (reservedTest(word, this.inModule)) { if (reservedTest(word, this.inModule)) {
if (!state.inAsync && word === "await") { if (!this.scope.inAsync && word === "await") {
this.raise( this.raise(
startLoc, startLoc,
"Can not use keyword 'await' outside an async function", "Can not use keyword 'await' outside an async function",

View File

@ -5,6 +5,8 @@ import type { File, JSXOpeningElement } from "../types";
import type { PluginList } from "../plugin-utils"; import type { PluginList } from "../plugin-utils";
import { getOptions } from "../options"; import { getOptions } from "../options";
import StatementParser from "./statement"; import StatementParser from "./statement";
import { SCOPE_PROGRAM } from "../util/scopeflags";
import ScopeHandler from "../util/scope";
export type PluginsMap = Map<string, { [string]: any }>; export type PluginsMap = Map<string, { [string]: any }>;
@ -20,11 +22,13 @@ export default class Parser extends StatementParser {
this.options = options; this.options = options;
this.inModule = this.options.sourceType === "module"; this.inModule = this.options.sourceType === "module";
this.scope = new ScopeHandler(this.raise.bind(this), this.inModule);
this.plugins = pluginsMap(this.options.plugins); this.plugins = pluginsMap(this.options.plugins);
this.filename = options.sourceFilename; this.filename = options.sourceFilename;
} }
parse(): File { parse(): File {
this.scope.enter(SCOPE_PROGRAM);
const file = this.startNode(); const file = this.startNode();
const program = this.startNode(); const program = this.startNode();
this.nextToken(); this.nextToken();

View File

@ -16,6 +16,7 @@ import type {
import type { Pos, Position } from "../util/location"; import type { Pos, Position } from "../util/location";
import { isStrictBindReservedWord } from "../util/identifier"; import { isStrictBindReservedWord } from "../util/identifier";
import { NodeUtils } from "./node"; import { NodeUtils } from "./node";
import { type BindingTypes, BIND_NONE, BIND_OUTSIDE } from "../util/scopeflags";
export default class LValParser extends NodeUtils { export default class LValParser extends NodeUtils {
// Forward-declaration: defined in expression.js // Forward-declaration: defined in expression.js
@ -262,7 +263,7 @@ export default class LValParser extends NodeUtils {
elts.push(this.parseAssignableListItemTypes(this.parseRest())); elts.push(this.parseAssignableListItemTypes(this.parseRest()));
this.checkCommaAfterRest( this.checkCommaAfterRest(
close, close,
this.state.inFunction && this.state.inParameters this.scope.inFunction && this.state.inParameters
? "parameter" ? "parameter"
: "element", : "element",
); );
@ -325,7 +326,7 @@ export default class LValParser extends NodeUtils {
checkLVal( checkLVal(
expr: Expression, expr: Expression,
isBinding: ?boolean, bindingType: ?BindingTypes = BIND_NONE,
checkClashes: ?{ [key: string]: boolean }, checkClashes: ?{ [key: string]: boolean },
contextDescription: string, contextDescription: string,
): void { ): void {
@ -337,7 +338,7 @@ export default class LValParser extends NodeUtils {
) { ) {
this.raise( this.raise(
expr.start, expr.start,
`${isBinding ? "Binding" : "Assigning to"} '${ `${bindingType === BIND_NONE ? "Assigning to" : "Binding"} '${
expr.name expr.name
}' in strict mode`, }' in strict mode`,
); );
@ -358,15 +359,20 @@ export default class LValParser extends NodeUtils {
const key = `_${expr.name}`; const key = `_${expr.name}`;
if (checkClashes[key]) { if (checkClashes[key]) {
this.raise(expr.start, "Argument name clash in strict mode"); this.raise(expr.start, "Argument name clash");
} else { } else {
checkClashes[key] = true; checkClashes[key] = true;
} }
} }
if (bindingType !== BIND_NONE && bindingType !== BIND_OUTSIDE) {
this.scope.declareName(expr.name, bindingType, expr.start);
}
break; break;
case "MemberExpression": case "MemberExpression":
if (isBinding) this.raise(expr.start, "Binding member expression"); if (bindingType !== BIND_NONE) {
this.raise(expr.start, "Binding member expression");
}
break; break;
case "ObjectPattern": case "ObjectPattern":
@ -374,7 +380,7 @@ export default class LValParser extends NodeUtils {
if (prop.type === "ObjectProperty") prop = prop.value; if (prop.type === "ObjectProperty") prop = prop.value;
this.checkLVal( this.checkLVal(
prop, prop,
isBinding, bindingType,
checkClashes, checkClashes,
"object destructuring pattern", "object destructuring pattern",
); );
@ -386,7 +392,7 @@ export default class LValParser extends NodeUtils {
if (elem) { if (elem) {
this.checkLVal( this.checkLVal(
elem, elem,
isBinding, bindingType,
checkClashes, checkClashes,
"array destructuring pattern", "array destructuring pattern",
); );
@ -397,21 +403,26 @@ export default class LValParser extends NodeUtils {
case "AssignmentPattern": case "AssignmentPattern":
this.checkLVal( this.checkLVal(
expr.left, expr.left,
isBinding, bindingType,
checkClashes, checkClashes,
"assignment pattern", "assignment pattern",
); );
break; break;
case "RestElement": case "RestElement":
this.checkLVal(expr.argument, isBinding, checkClashes, "rest element"); this.checkLVal(
expr.argument,
bindingType,
checkClashes,
"rest element",
);
break; break;
default: { default: {
const message = const message =
(isBinding (bindingType === BIND_NONE
? /* istanbul ignore next */ "Binding invalid" ? "Invalid"
: "Invalid") + : /* istanbul ignore next */ "Binding invalid") +
" left-hand side" + " left-hand side" +
(contextDescription (contextDescription
? " in " + contextDescription ? " in " + contextDescription

View File

@ -10,6 +10,17 @@ import {
} from "../util/identifier"; } from "../util/identifier";
import { lineBreak, skipWhiteSpace } from "../util/whitespace"; import { lineBreak, skipWhiteSpace } from "../util/whitespace";
import * as charCodes from "charcodes"; 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. // Reused empty array added for node fields that are always empty.
@ -18,6 +29,11 @@ const empty = [];
const loopLabel = { kind: "loop" }, const loopLabel = { kind: "loop" },
switchLabel = { kind: "switch" }; 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 { export default class StatementParser extends ExpressionParser {
// ### Statement parsing // ### Statement parsing
@ -144,26 +160,24 @@ export default class StatementParser extends ExpressionParser {
return this.parseDoStatement(node); return this.parseDoStatement(node);
case tt._for: case tt._for:
return this.parseForStatement(node); return this.parseForStatement(node);
case tt._function: { case tt._function:
if (this.lookahead().type === tt.dot) break; if (this.lookahead().type === tt.dot) break;
if ( if (context) {
context && if (this.state.strict) {
(this.state.strict || (context !== "if" && context !== "label")) this.raise(
) { this.state.start,
this.raise( "In strict mode code, functions can only be declared at top level or inside a block",
this.state.start, );
"Function declaration not allowed in this context", } 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: case tt._class:
if (context) this.unexpected(); if (context) this.unexpected();
return this.parseClass(node, true); return this.parseClass(node, true);
@ -182,7 +196,12 @@ export default class StatementParser extends ExpressionParser {
case tt._const: case tt._const:
case tt._var: case tt._var:
kind = kind || this.state.value; 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); return this.parseVarStatement(node, kind);
case tt._while: case tt._while:
@ -237,24 +256,19 @@ export default class StatementParser extends ExpressionParser {
return result; return result;
} }
case tt.name:
if (this.isContextual("async")) { default: {
// peek ahead and see if next token is a function if (this.isAsyncFunction()) {
const state = this.state.clone(); if (context) {
this.next(); this.unexpected(
if (this.match(tt._function) && !this.canInsertSemicolon()) { null,
if (context) { "Async functions can only be declared at the top level or inside a block",
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;
} }
this.next();
return this.parseFunctionStatement(node, true, !context);
} }
}
} }
// If the statement does not start with a statement keyword or a // 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; let awaitAt = -1;
if ( if (
(this.state.inAsync || (this.scope.inAsync ||
(!this.state.inFunction && this.options.allowAwaitOutsideFunction)) && (!this.scope.inFunction && this.options.allowAwaitOutsideFunction)) &&
this.eatContextual("await") this.eatContextual("await")
) { ) {
awaitAt = this.state.lastTokStart; awaitAt = this.state.lastTokStart;
} }
this.scope.enter(SCOPE_OTHER);
this.expect(tt.parenL); this.expect(tt.parenL);
if (this.match(tt.semi)) { if (this.match(tt.semi)) {
@ -524,9 +539,18 @@ export default class StatementParser extends ExpressionParser {
return this.parseFor(node, init); return this.parseFor(node, init);
} }
parseFunctionStatement(node: N.FunctionDeclaration): N.FunctionDeclaration { parseFunctionStatement(
node: N.FunctionDeclaration,
isAsync?: boolean,
declarationPosition?: boolean,
): N.FunctionDeclaration {
this.next(); 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 { parseIfStatement(node: N.IfStatement): N.IfStatement {
@ -538,7 +562,7 @@ export default class StatementParser extends ExpressionParser {
} }
parseReturnStatement(node: N.ReturnStatement): N.ReturnStatement { 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"); this.raise(this.state.start, "'return' outside of function");
} }
@ -564,6 +588,7 @@ export default class StatementParser extends ExpressionParser {
const cases = (node.cases = []); const cases = (node.cases = []);
this.expect(tt.braceL); this.expect(tt.braceL);
this.state.labels.push(switchLabel); this.state.labels.push(switchLabel);
this.scope.enter(SCOPE_OTHER);
// Statements under must be grouped (by label) in SwitchCase // Statements under must be grouped (by label) in SwitchCase
// nodes. `cur` is used to keep the node that we are currently // 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"); if (cur) this.finishNode(cur, "SwitchCase");
this.next(); // Closing brace this.next(); // Closing brace
this.state.labels.pop(); this.state.labels.pop();
@ -627,11 +653,18 @@ export default class StatementParser extends ExpressionParser {
if (this.match(tt.parenL)) { if (this.match(tt.parenL)) {
this.expect(tt.parenL); this.expect(tt.parenL);
clause.param = this.parseBindingAtom(); clause.param = this.parseBindingAtom();
const clashes: any = Object.create(null); const simple = clause.param.type === "Identifier";
this.checkLVal(clause.param, true, clashes, "catch clause"); 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); this.expect(tt.parenR);
} else { } else {
clause.param = null; clause.param = null;
this.scope.enter(SCOPE_OTHER);
} }
clause.body = clause.body =
@ -641,8 +674,9 @@ export default class StatementParser extends ExpressionParser {
// outside of the function body. // outside of the function body.
this.withTopicForbiddingContext(() => this.withTopicForbiddingContext(() =>
// Parse the catch clause's body. // Parse the catch clause's body.
this.parseBlock(false), this.parseBlock(false, false),
); );
this.scope.exit();
node.handler = this.finishNode(clause, "CatchClause"); node.handler = this.finishNode(clause, "CatchClause");
} }
@ -769,10 +803,19 @@ export default class StatementParser extends ExpressionParser {
// strict"` declarations when `allowStrict` is true (used for // strict"` declarations when `allowStrict` is true (used for
// function bodies). // function bodies).
parseBlock(allowDirectives?: boolean): N.BlockStatement { parseBlock(
allowDirectives?: boolean = false,
createNewLexicalScope?: boolean = true,
): N.BlockStatement {
const node = this.startNode(); const node = this.startNode();
this.expect(tt.braceL); this.expect(tt.braceL);
if (createNewLexicalScope) {
this.scope.enter(SCOPE_OTHER);
}
this.parseBlockBody(node, allowDirectives, false, tt.braceR); this.parseBlockBody(node, allowDirectives, false, tt.braceR);
if (createNewLexicalScope) {
this.scope.exit();
}
return this.finishNode(node, "BlockStatement"); return this.finishNode(node, "BlockStatement");
} }
@ -858,6 +901,7 @@ export default class StatementParser extends ExpressionParser {
node.update = this.match(tt.parenR) ? null : this.parseExpression(); node.update = this.match(tt.parenR) ? null : this.parseExpression();
this.expect(tt.parenR); this.expect(tt.parenR);
this.scope.exit();
node.body = node.body =
// For the smartPipelines plugin: Disable topic references from outer // For the smartPipelines plugin: Disable topic references from outer
// contexts within the loop body. They are permitted in test expressions, // 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(); node.right = this.parseExpression();
this.expect(tt.parenR); this.expect(tt.parenR);
this.scope.exit();
node.body = node.body =
// For the smartPipelines plugin: // For the smartPipelines plugin:
// Disable topic references from outer contexts within the loop body. // 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"); this.unexpected(null, "let is disallowed as a lexically bound name");
} }
decl.id = this.parseBindingAtom(); 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 // Parse a function declaration or literal (depending on the
@ -962,51 +1012,56 @@ export default class StatementParser extends ExpressionParser {
parseFunction<T: N.NormalFunction>( parseFunction<T: N.NormalFunction>(
node: T, node: T,
isStatement: boolean, statement?: number = FUNC_NO_FLAGS,
allowExpressionBody?: boolean = false, allowExpressionBody?: boolean = false,
isAsync?: boolean = false, isAsync?: boolean = false,
optionalId?: boolean = false,
): T { ): T {
const oldInFunc = this.state.inFunction; const isStatement = statement & FUNC_STATEMENT;
const oldInMethod = this.state.inMethod; const isHangingStatement = statement & FUNC_HANGING_STATEMENT;
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;
this.initFunction(node, isAsync); 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); node.generator = this.eat(tt.star);
if (isStatement && !optionalId && !this.match(tt.name)) { if (isStatement) {
this.unexpected(); 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 const oldInClassProperty = this.state.inClassProperty;
// according to the rules inside the function. const oldYieldPos = this.state.yieldPos;
// e.g. (function* yield() {}) is invalid because "yield" is disallowed in const oldAwaitPos = this.state.awaitPos;
// generators. this.state.inClassProperty = false;
// This isn't the case with function declarations: function* yield() {} is this.state.yieldPos = 0;
// valid because yield is parsed as if it was outside the generator. this.state.awaitPos = 0;
// Therefore, this.state.inGenerator is set before or after parsing the this.scope.enter(functionFlags(node.async, node.generator));
// function id according to the "isStatement" parameter.
// The same applies to await & async functions.
if (!isStatement) { if (!isStatement) {
this.state.inAsync = isAsync; node.id = this.match(tt.name) ? this.parseIdentifier() : null;
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;
} }
this.parseFunctionParams(node); 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.inClassProperty = oldInClassProperty;
this.state.yieldPos = oldYieldPos; this.state.yieldPos = oldYieldPos;
this.state.awaitPos = oldAwaitPos; this.state.awaitPos = oldAwaitPos;
@ -1099,11 +1150,12 @@ export default class StatementParser extends ExpressionParser {
const state = { hadConstructor: false }; const state = { hadConstructor: false };
let decorators: N.Decorator[] = []; let decorators: N.Decorator[] = [];
const classBody: N.ClassBody = this.startNode(); const classBody: N.ClassBody = this.startNode();
classBody.body = []; classBody.body = [];
this.expect(tt.braceL); this.expect(tt.braceL);
const constructorAllowsSuper = node.superClass !== null;
// For the smartPipelines plugin: Disable topic references from outer // For the smartPipelines plugin: Disable topic references from outer
// contexts within the class body. They are permitted in test expressions, // contexts within the class body. They are permitted in test expressions,
// outside of the class body. // outside of the class body.
@ -1133,7 +1185,7 @@ export default class StatementParser extends ExpressionParser {
decorators = []; decorators = [];
} }
this.parseClassMember(classBody, member, state); this.parseClassMember(classBody, member, state, constructorAllowsSuper);
if ( if (
member.kind === "constructor" && member.kind === "constructor" &&
@ -1164,6 +1216,7 @@ export default class StatementParser extends ExpressionParser {
classBody: N.ClassBody, classBody: N.ClassBody,
member: N.ClassMember, member: N.ClassMember,
state: { hadConstructor: boolean }, state: { hadConstructor: boolean },
constructorAllowsSuper: boolean,
): void { ): void {
let isStatic = false; let isStatic = false;
const containsEsc = this.state.containsEsc; const containsEsc = this.state.containsEsc;
@ -1185,6 +1238,7 @@ export default class StatementParser extends ExpressionParser {
false, false,
false, false,
/* isConstructor */ false, /* isConstructor */ false,
false,
); );
return; return;
} else if (this.isClassProperty()) { } else if (this.isClassProperty()) {
@ -1204,7 +1258,13 @@ export default class StatementParser extends ExpressionParser {
isStatic = true; isStatic = true;
} }
this.parseClassMemberWithIsStatic(classBody, member, state, isStatic); this.parseClassMemberWithIsStatic(
classBody,
member,
state,
isStatic,
constructorAllowsSuper,
);
} }
parseClassMemberWithIsStatic( parseClassMemberWithIsStatic(
@ -1212,6 +1272,7 @@ export default class StatementParser extends ExpressionParser {
member: N.ClassMember, member: N.ClassMember,
state: { hadConstructor: boolean }, state: { hadConstructor: boolean },
isStatic: boolean, isStatic: boolean,
constructorAllowsSuper: boolean,
) { ) {
const publicMethod: $FlowSubtype<N.ClassMethod> = member; const publicMethod: $FlowSubtype<N.ClassMethod> = member;
const privateMethod: $FlowSubtype<N.ClassPrivateMethod> = member; const privateMethod: $FlowSubtype<N.ClassPrivateMethod> = member;
@ -1244,6 +1305,7 @@ export default class StatementParser extends ExpressionParser {
true, true,
false, false,
/* isConstructor */ false, /* isConstructor */ false,
false,
); );
return; return;
@ -1266,7 +1328,7 @@ export default class StatementParser extends ExpressionParser {
// a normal method // a normal method
const isConstructor = this.isNonstaticConstructor(publicMethod); const isConstructor = this.isNonstaticConstructor(publicMethod);
let allowsDirectSuper = false;
if (isConstructor) { if (isConstructor) {
publicMethod.kind = "constructor"; publicMethod.kind = "constructor";
@ -1282,6 +1344,7 @@ export default class StatementParser extends ExpressionParser {
this.raise(key.start, "Duplicate constructor in the same class"); this.raise(key.start, "Duplicate constructor in the same class");
} }
state.hadConstructor = true; state.hadConstructor = true;
allowsDirectSuper = constructorAllowsSuper;
} }
this.pushClassMethod( this.pushClassMethod(
@ -1290,6 +1353,7 @@ export default class StatementParser extends ExpressionParser {
false, false,
false, false,
isConstructor, isConstructor,
allowsDirectSuper,
); );
} else if (this.isClassProperty()) { } else if (this.isClassProperty()) {
if (isPrivate) { if (isPrivate) {
@ -1327,6 +1391,7 @@ export default class StatementParser extends ExpressionParser {
isGenerator, isGenerator,
true, true,
/* isConstructor */ false, /* isConstructor */ false,
false,
); );
} }
} else if ( } else if (
@ -1356,6 +1421,7 @@ export default class StatementParser extends ExpressionParser {
false, false,
false, false,
/* isConstructor */ false, /* isConstructor */ false,
false,
); );
} }
@ -1422,6 +1488,7 @@ export default class StatementParser extends ExpressionParser {
isGenerator: boolean, isGenerator: boolean,
isAsync: boolean, isAsync: boolean,
isConstructor: boolean, isConstructor: boolean,
allowsDirectSuper: boolean,
): void { ): void {
classBody.body.push( classBody.body.push(
this.parseMethod( this.parseMethod(
@ -1429,7 +1496,9 @@ export default class StatementParser extends ExpressionParser {
isGenerator, isGenerator,
isAsync, isAsync,
isConstructor, isConstructor,
allowsDirectSuper,
"ClassMethod", "ClassMethod",
true,
), ),
); );
} }
@ -1447,7 +1516,9 @@ export default class StatementParser extends ExpressionParser {
isGenerator, isGenerator,
isAsync, isAsync,
/* isConstructor */ false, /* isConstructor */ false,
false,
"ClassPrivateMethod", "ClassPrivateMethod",
true,
), ),
); );
} }
@ -1466,13 +1537,16 @@ export default class StatementParser extends ExpressionParser {
parseClassPrivateProperty( parseClassPrivateProperty(
node: N.ClassPrivateProperty, node: N.ClassPrivateProperty,
): N.ClassPrivateProperty { ): N.ClassPrivateProperty {
const oldInMethod = this.state.inMethod;
this.state.inMethod = false;
this.state.inClassProperty = true; this.state.inClassProperty = true;
this.scope.enter(SCOPE_CLASS | SCOPE_SUPER);
node.value = this.eat(tt.eq) ? this.parseMaybeAssign() : null; node.value = this.eat(tt.eq) ? this.parseMaybeAssign() : null;
this.semicolon(); this.semicolon();
this.state.inClassProperty = false; this.state.inClassProperty = false;
this.state.inMethod = oldInMethod;
this.scope.exit();
return this.finishNode(node, "ClassPrivateProperty"); return this.finishNode(node, "ClassPrivateProperty");
} }
@ -1481,10 +1555,10 @@ export default class StatementParser extends ExpressionParser {
this.expectPlugin("classProperties"); this.expectPlugin("classProperties");
} }
const oldInMethod = this.state.inMethod;
this.state.inMethod = false;
this.state.inClassProperty = true; this.state.inClassProperty = true;
this.scope.enter(SCOPE_CLASS | SCOPE_SUPER);
if (this.match(tt.eq)) { if (this.match(tt.eq)) {
this.expectPlugin("classProperties"); this.expectPlugin("classProperties");
this.next(); this.next();
@ -1494,7 +1568,8 @@ export default class StatementParser extends ExpressionParser {
} }
this.semicolon(); this.semicolon();
this.state.inClassProperty = false; this.state.inClassProperty = false;
this.state.inMethod = oldInMethod;
this.scope.exit();
return this.finishNode(node, "ClassProperty"); return this.finishNode(node, "ClassProperty");
} }
@ -1506,6 +1581,9 @@ export default class StatementParser extends ExpressionParser {
): void { ): void {
if (this.match(tt.name)) { if (this.match(tt.name)) {
node.id = this.parseIdentifier(); node.id = this.parseIdentifier();
if (isStatement) {
this.checkLVal(node.id, BIND_LEXICAL, undefined, "class name");
}
} else { } else {
if (optionalId || !isStatement) { if (optionalId || !isStatement) {
node.id = null; node.id = null;
@ -1642,7 +1720,7 @@ export default class StatementParser extends ExpressionParser {
return false; return false;
} }
isAsyncFunction() { isAsyncFunction(): boolean {
if (!this.isContextual("async")) return false; if (!this.isContextual("async")) return false;
const { input, pos, length } = this.state; const { input, pos, length } = this.state;
@ -1666,13 +1744,18 @@ export default class StatementParser extends ExpressionParser {
const isAsync = this.isAsyncFunction(); const isAsync = this.isAsyncFunction();
if (this.eat(tt._function) || isAsync) { if (this.match(tt._function) || isAsync) {
this.next();
if (isAsync) { if (isAsync) {
this.eatContextual("async"); this.next();
this.expect(tt._function);
} }
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)) { } else if (this.match(tt._class)) {
return this.parseClass(expr, true, true); return this.parseClass(expr, true, true);
} else if (this.match(tt.at)) { } else if (this.match(tt.at)) {
@ -1927,7 +2010,12 @@ export default class StatementParser extends ExpressionParser {
contextDescription: string, contextDescription: string,
): void { ): void {
specifier.local = this.parseIdentifier(); 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)); node.specifiers.push(this.finishNode(specifier, type));
} }
@ -2000,7 +2088,12 @@ export default class StatementParser extends ExpressionParser {
); );
specifier.local = specifier.imported.__clone(); 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")); node.specifiers.push(this.finishNode(specifier, "ImportSpecifier"));
} }
} }

View File

@ -3,7 +3,9 @@
import { types as tt, type TokenType } from "../tokenizer/types"; import { types as tt, type TokenType } from "../tokenizer/types";
import Tokenizer from "../tokenizer"; import Tokenizer from "../tokenizer";
import type { Node } from "../types"; import type { Node } from "../types";
import { lineBreak } from "../util/whitespace"; import { lineBreak, skipWhiteSpace } from "../util/whitespace";
const literal = /^(?:;|('|")((?:\\?.)*?)\1)/;
// ## Parser utilities // ## 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;
}
} }

View File

@ -4,6 +4,7 @@ import { types as tt, TokenType } from "../tokenizer/types";
import type Parser from "../parser"; import type Parser from "../parser";
import * as N from "../types"; import * as N from "../types";
import type { Pos, Position } from "../util/location"; import type { Pos, Position } from "../util/location";
import { type BindingTypes, BIND_NONE } from "../util/scopeflags";
function isSimpleProperty(node: N.Node): boolean { function isSimpleProperty(node: N.Node): boolean {
return ( return (
@ -104,7 +105,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
checkLVal( checkLVal(
expr: N.Expression, expr: N.Expression,
isBinding: ?boolean, bindingType: ?BindingTypes = BIND_NONE,
checkClashes: ?{ [key: string]: boolean }, checkClashes: ?{ [key: string]: boolean },
contextDescription: string, contextDescription: string,
): void { ): void {
@ -113,14 +114,14 @@ export default (superClass: Class<Parser>): Class<Parser> =>
expr.properties.forEach(prop => { expr.properties.forEach(prop => {
this.checkLVal( this.checkLVal(
prop.type === "Property" ? prop.value : prop, prop.type === "Property" ? prop.value : prop,
isBinding, bindingType,
checkClashes, checkClashes,
"object destructuring pattern", "object destructuring pattern",
); );
}); });
break; break;
default: default:
super.checkLVal(expr, isBinding, checkClashes, contextDescription); super.checkLVal(expr, bindingType, checkClashes, contextDescription);
} }
} }
@ -203,13 +204,16 @@ export default (superClass: Class<Parser>): Class<Parser> =>
isGenerator: boolean, isGenerator: boolean,
isAsync: boolean, isAsync: boolean,
isConstructor: boolean, isConstructor: boolean,
allowsDirectSuper: boolean,
): void { ): void {
this.parseMethod( this.parseMethod(
method, method,
isGenerator, isGenerator,
isAsync, isAsync,
isConstructor, isConstructor,
allowsDirectSuper,
"MethodDefinition", "MethodDefinition",
true,
); );
if (method.typeParameters) { if (method.typeParameters) {
// $FlowIgnore // $FlowIgnore
@ -265,7 +269,9 @@ export default (superClass: Class<Parser>): Class<Parser> =>
isGenerator: boolean, isGenerator: boolean,
isAsync: boolean, isAsync: boolean,
isConstructor: boolean, isConstructor: boolean,
allowDirectSuper: boolean,
type: string, type: string,
inClassScope: boolean = false,
): T { ): T {
let funcNode = this.startNode(); let funcNode = this.startNode();
funcNode.kind = node.kind; // provide kind, so super method correctly sets state funcNode.kind = node.kind; // provide kind, so super method correctly sets state
@ -274,7 +280,9 @@ export default (superClass: Class<Parser>): Class<Parser> =>
isGenerator, isGenerator,
isAsync, isAsync,
isConstructor, isConstructor,
allowDirectSuper,
"FunctionExpression", "FunctionExpression",
inClassScope,
); );
delete funcNode.kind; delete funcNode.kind;
// $FlowIgnore // $FlowIgnore

View File

@ -9,6 +9,12 @@ import type State from "../tokenizer/state";
import { types as tc } from "../tokenizer/context"; import { types as tc } from "../tokenizer/context";
import * as charCodes from "charcodes"; import * as charCodes from "charcodes";
import { isIteratorStart } from "../util/identifier"; import { isIteratorStart } from "../util/identifier";
import {
type BindingTypes,
BIND_NONE,
BIND_LEXICAL,
SCOPE_OTHER,
} from "../util/scopeflags";
const reservedTypes = [ const reservedTypes = [
"any", "any",
@ -257,6 +263,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
flowParseDeclareModule(node: N.FlowDeclareModule): N.FlowDeclareModule { flowParseDeclareModule(node: N.FlowDeclareModule): N.FlowDeclareModule {
this.next(); this.next();
this.scope.enter(SCOPE_OTHER);
if (this.match(tt.string)) { if (this.match(tt.string)) {
node.id = this.parseExprAtom(); node.id = this.parseExprAtom();
} else { } else {
@ -290,6 +298,9 @@ export default (superClass: Class<Parser>): Class<Parser> =>
body.push(bodyNode); body.push(bodyNode);
} }
this.scope.exit();
this.expect(tt.braceR); this.expect(tt.braceR);
this.finishNode(bodyNode, "BlockStatement"); this.finishNode(bodyNode, "BlockStatement");
@ -518,6 +529,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
flowParseTypeAlias(node: N.FlowTypeAlias): N.FlowTypeAlias { flowParseTypeAlias(node: N.FlowTypeAlias): N.FlowTypeAlias {
node.id = this.flowParseRestrictedIdentifier(); node.id = this.flowParseRestrictedIdentifier();
this.scope.declareName(node.id.name, BIND_LEXICAL, node.id.start);
if (this.isRelational("<")) { if (this.isRelational("<")) {
node.typeParameters = this.flowParseTypeParameterDeclaration(); node.typeParameters = this.flowParseTypeParameterDeclaration();
@ -537,6 +549,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
): N.FlowOpaqueType { ): N.FlowOpaqueType {
this.expectContextual("type"); this.expectContextual("type");
node.id = this.flowParseRestrictedIdentifier(/*liberal*/ true); node.id = this.flowParseRestrictedIdentifier(/*liberal*/ true);
this.scope.declareName(node.id.name, BIND_LEXICAL, node.id.start);
if (this.isRelational("<")) { if (this.isRelational("<")) {
node.typeParameters = this.flowParseTypeParameterDeclaration(); node.typeParameters = this.flowParseTypeParameterDeclaration();
@ -1761,7 +1774,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
"arrow function parameters", "arrow function parameters",
); );
// Use super's method to force the parameters to be checked // Use super's method to force the parameters to be checked
super.checkFunctionNameAndParams(node, true); super.checkParams(node, false, true);
} else { } else {
arrows.push(node); arrows.push(node);
} }
@ -1995,14 +2008,14 @@ export default (superClass: Class<Parser>): Class<Parser> =>
checkLVal( checkLVal(
expr: N.Expression, expr: N.Expression,
isBinding: ?boolean, bindingType: ?BindingTypes = BIND_NONE,
checkClashes: ?{ [key: string]: boolean }, checkClashes: ?{ [key: string]: boolean },
contextDescription: string, contextDescription: string,
): void { ): void {
if (expr.type !== "TypeCastExpression") { if (expr.type !== "TypeCastExpression") {
return super.checkLVal( return super.checkLVal(
expr, expr,
isBinding, bindingType,
checkClashes, checkClashes,
contextDescription, contextDescription,
); );
@ -2047,6 +2060,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
isGenerator: boolean, isGenerator: boolean,
isAsync: boolean, isAsync: boolean,
isConstructor: boolean, isConstructor: boolean,
allowsDirectSuper: boolean,
): void { ): void {
if ((method: $FlowFixMe).variance) { if ((method: $FlowFixMe).variance) {
this.unexpected((method: $FlowFixMe).variance.start); this.unexpected((method: $FlowFixMe).variance.start);
@ -2064,6 +2078,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
isGenerator, isGenerator,
isAsync, isAsync,
isConstructor, isConstructor,
allowsDirectSuper,
); );
} }
@ -2217,7 +2232,12 @@ export default (superClass: Class<Parser>): Class<Parser> =>
? this.flowParseRestrictedIdentifier(true) ? this.flowParseRestrictedIdentifier(true)
: this.parseIdentifier(); : this.parseIdentifier();
this.checkLVal(specifier.local, true, undefined, contextDescription); this.checkLVal(
specifier.local,
BIND_LEXICAL,
undefined,
contextDescription,
);
node.specifiers.push(this.finishNode(specifier, type)); node.specifiers.push(this.finishNode(specifier, type));
} }
@ -2327,12 +2347,17 @@ export default (superClass: Class<Parser>): Class<Parser> =>
); );
} }
this.checkLVal(specifier.local, true, undefined, "import specifier"); this.checkLVal(
specifier.local,
BIND_LEXICAL,
undefined,
"import specifier",
);
node.specifiers.push(this.finishNode(specifier, "ImportSpecifier")); node.specifiers.push(this.finishNode(specifier, "ImportSpecifier"));
} }
// parse function type parameters - function foo<T>() {} // parse function type parameters - function foo<T>() {}
parseFunctionParams(node: N.Function): void { parseFunctionParams(node: N.Function, allowModifiers?: boolean): void {
// $FlowFixMe // $FlowFixMe
const kind = node.kind; const kind = node.kind;
if (kind !== "get" && kind !== "set" && this.isRelational("<")) { if (kind !== "get" && kind !== "set" && this.isRelational("<")) {
@ -2340,7 +2365,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
/* allowDefault */ false, /* allowDefault */ false,
); );
} }
super.parseFunctionParams(node); super.parseFunctionParams(node, allowModifiers);
} }
// parse flow type annotations on variable declarator heads - let foo: string = bar // parse flow type annotations on variable declarator heads - let foo: string = bar
@ -2519,8 +2544,9 @@ export default (superClass: Class<Parser>): Class<Parser> =>
} }
} }
checkFunctionNameAndParams( checkParams(
node: N.Function, node: N.Function,
allowDuplicates: boolean,
isArrowFunction: ?boolean, isArrowFunction: ?boolean,
): void { ): void {
if ( if (
@ -2530,7 +2556,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return; return;
} }
return super.checkFunctionNameAndParams(node, isArrowFunction); return super.checkParams(node, allowDuplicates, isArrowFunction);
} }
parseParenAndDistinguishExpression(canBeArrow: boolean): N.Expression { parseParenAndDistinguishExpression(canBeArrow: boolean): N.Expression {

View File

@ -6,6 +6,13 @@ import { types as ct } from "../tokenizer/context";
import * as N from "../types"; import * as N from "../types";
import type { Pos, Position } from "../util/location"; import type { Pos, Position } from "../util/location";
import Parser from "../parser"; import Parser from "../parser";
import {
type BindingTypes,
functionFlags,
BIND_NONE,
SCOPE_ARROW,
SCOPE_OTHER,
} from "../util/scopeflags";
type TsModifier = type TsModifier =
| "readonly" | "readonly"
@ -1071,6 +1078,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
tsParseModuleBlock(): N.TsModuleBlock { tsParseModuleBlock(): N.TsModuleBlock {
const node: N.TsModuleBlock = this.startNode(); const node: N.TsModuleBlock = this.startNode();
this.scope.enter(SCOPE_OTHER);
this.expect(tt.braceL); this.expect(tt.braceL);
// Inside of a module block is considered "top-level", meaning it can have imports and exports. // Inside of a module block is considered "top-level", meaning it can have imports and exports.
this.parseBlockOrModuleBlockBody( this.parseBlockOrModuleBlockBody(
@ -1079,6 +1088,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
/* topLevel */ true, /* topLevel */ true,
/* end */ tt.braceR, /* end */ tt.braceR,
); );
this.scope.exit();
return this.finishNode(node, "TSModuleBlock"); return this.finishNode(node, "TSModuleBlock");
} }
@ -1217,8 +1227,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
switch (starttype) { switch (starttype) {
case tt._function: case tt._function:
this.next(); return this.parseFunctionStatement(nany);
return this.parseFunction(nany, /* isStatement */ true);
case tt._class: case tt._class:
return this.parseClass( return this.parseClass(
nany, nany,
@ -1372,17 +1381,13 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return undefined; return undefined;
} }
const oldInAsync = this.state.inAsync; this.scope.enter(functionFlags(true, false) | SCOPE_ARROW);
const oldInGenerator = this.state.inGenerator;
this.state.inAsync = true;
this.state.inGenerator = false;
res.id = null; res.id = null;
res.generator = false; res.generator = false;
res.expression = true; // May be set again by parseFunctionBody. res.expression = true; // May be set again by parseFunctionBody.
res.async = true; res.async = true;
this.parseFunctionBody(res, true); this.parseFunctionBody(res, true);
this.state.inAsync = oldInAsync;
this.state.inGenerator = oldInGenerator;
return this.finishNode(res, "ArrowFunctionExpression"); return this.finishNode(res, "ArrowFunctionExpression");
} }
@ -1721,11 +1726,12 @@ export default (superClass: Class<Parser>): Class<Parser> =>
classBody: N.ClassBody, classBody: N.ClassBody,
member: any, member: any,
state: { hadConstructor: boolean }, state: { hadConstructor: boolean },
constructorAllowsSuper: boolean,
): void { ): void {
const accessibility = this.parseAccessModifier(); const accessibility = this.parseAccessModifier();
if (accessibility) member.accessibility = accessibility; if (accessibility) member.accessibility = accessibility;
super.parseClassMember(classBody, member, state); super.parseClassMember(classBody, member, state, constructorAllowsSuper);
} }
parseClassMemberWithIsStatic( parseClassMemberWithIsStatic(
@ -1733,6 +1739,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
member: any, member: any,
state: { hadConstructor: boolean }, state: { hadConstructor: boolean },
isStatic: boolean, isStatic: boolean,
constructorAllowsSuper: boolean,
): void { ): void {
const methodOrProp: N.ClassMethod | N.ClassProperty = member; const methodOrProp: N.ClassMethod | N.ClassProperty = member;
const prop: N.ClassProperty = member; const prop: N.ClassProperty = member;
@ -1773,7 +1780,13 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return; return;
} }
super.parseClassMemberWithIsStatic(classBody, member, state, isStatic); super.parseClassMemberWithIsStatic(
classBody,
member,
state,
isStatic,
constructorAllowsSuper,
);
} }
parsePostMemberNameModifiers( parsePostMemberNameModifiers(
@ -1923,6 +1936,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
isGenerator: boolean, isGenerator: boolean,
isAsync: boolean, isAsync: boolean,
isConstructor: boolean, isConstructor: boolean,
allowsDirectSuper: boolean,
): void { ): void {
const typeParameters = this.tsTryParseTypeParameters(); const typeParameters = this.tsTryParseTypeParameters();
if (typeParameters) method.typeParameters = typeParameters; if (typeParameters) method.typeParameters = typeParameters;
@ -1932,6 +1946,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
isGenerator, isGenerator,
isAsync, isAsync,
isConstructor, isConstructor,
allowsDirectSuper,
); );
} }
@ -2154,7 +2169,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
checkLVal( checkLVal(
expr: N.Expression, expr: N.Expression,
isBinding: ?boolean, bindingType: ?BindingTypes = BIND_NONE,
checkClashes: ?{ [key: string]: boolean }, checkClashes: ?{ [key: string]: boolean },
contextDescription: string, contextDescription: string,
): void { ): void {
@ -2167,7 +2182,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
case "TSParameterProperty": case "TSParameterProperty":
this.checkLVal( this.checkLVal(
expr.parameter, expr.parameter,
isBinding, bindingType,
checkClashes, checkClashes,
"parameter property", "parameter property",
); );
@ -2177,13 +2192,13 @@ export default (superClass: Class<Parser>): Class<Parser> =>
case "TSTypeAssertion": case "TSTypeAssertion":
this.checkLVal( this.checkLVal(
expr.expression, expr.expression,
isBinding, bindingType,
checkClashes, checkClashes,
contextDescription, contextDescription,
); );
return; return;
default: default:
super.checkLVal(expr, isBinding, checkClashes, contextDescription); super.checkLVal(expr, bindingType, checkClashes, contextDescription);
return; return;
} }
} }

View File

@ -60,7 +60,7 @@ tt.name.updateContext = function(prevType) {
if (prevType !== tt.dot) { if (prevType !== tt.dot) {
if ( if (
(this.state.value === "of" && !this.state.exprAllowed) || (this.state.value === "of" && !this.state.exprAllowed) ||
(this.state.value === "yield" && this.state.inGenerator) (this.state.value === "yield" && this.scope.inGenerator)
) { ) {
allowed = true; allowed = true;
} }

View File

@ -66,13 +66,9 @@ export default class State {
// and then convert them, we need to track it. // and then convert them, we need to track it.
commaAfterSpreadAt: number = -1; commaAfterSpreadAt: number = -1;
// Flags to track whether we are in a function, a generator. // Flags to track
inFunction: boolean = false;
inParameters: boolean = false; inParameters: boolean = false;
maybeInArrowParameters: boolean = false; maybeInArrowParameters: boolean = false;
inGenerator: boolean = false;
inMethod: boolean | N.MethodKind = false;
inAsync: boolean = false;
inPipeline: boolean = false; inPipeline: boolean = false;
inType: boolean = false; inType: boolean = false;
noAnonFunctionType: boolean = false; noAnonFunctionType: boolean = false;

View File

@ -0,0 +1,160 @@
// @flow
import {
SCOPE_ARROW,
SCOPE_ASYNC,
SCOPE_DIRECT_SUPER,
SCOPE_FUNCTION,
SCOPE_GENERATOR,
SCOPE_SIMPLE_CATCH,
SCOPE_SUPER,
SCOPE_PROGRAM,
SCOPE_VAR,
BIND_SIMPLE_CATCH,
BIND_LEXICAL,
BIND_FUNCTION,
type ScopeFlags,
type BindingTypes,
SCOPE_CLASS,
} from "./scopeflags";
// Start an AST node, attaching a start offset.
class Scope {
flags: ScopeFlags;
// A list of var-declared names in the current lexical scope
var: string[] = [];
// A list of lexically-declared names in the current lexical scope
lexical: string[] = [];
// A list of lexically-declared FunctionDeclaration names in the current lexical scope
functions: string[] = [];
constructor(flags: ScopeFlags) {
this.flags = flags;
}
}
type raiseFunction = (number, string) => void;
// The functions in this module keep track of declared variables in the
// current scope in order to detect duplicate variable names.
export default class ScopeHandler {
scopeStack: Array<Scope> = [];
raise: raiseFunction;
inModule: boolean;
constructor(raise: raiseFunction, inModule: boolean) {
this.raise = raise;
this.inModule = inModule;
}
get inFunction() {
return (this.currentVarScope().flags & SCOPE_FUNCTION) > 0;
}
get inGenerator() {
return (this.currentVarScope().flags & SCOPE_GENERATOR) > 0;
}
get inAsync() {
return (this.currentVarScope().flags & SCOPE_ASYNC) > 0;
}
get allowSuper() {
return (this.currentThisScope().flags & SCOPE_SUPER) > 0;
}
get allowDirectSuper() {
return (this.currentThisScope().flags & SCOPE_DIRECT_SUPER) > 0;
}
get inNonArrowFunction() {
return (this.currentThisScope().flags & SCOPE_FUNCTION) > 0;
}
get treatFunctionsAsVar() {
return this.treatFunctionsAsVarInScope(this.currentScope());
}
enter(flags: ScopeFlags) {
this.scopeStack.push(new Scope(flags));
}
exit() {
this.scopeStack.pop();
}
// The spec says:
// > At the top level of a function, or script, function declarations are
// > treated like var declarations rather than like lexical declarations.
treatFunctionsAsVarInScope(scope: Scope): boolean {
return !!(
scope.flags & SCOPE_FUNCTION ||
(!this.inModule && scope.flags & SCOPE_PROGRAM)
);
}
declareName(name: string, bindingType: ?BindingTypes, pos: number) {
let redeclared = false;
if (bindingType === BIND_LEXICAL) {
const scope = this.currentScope();
redeclared =
scope.lexical.indexOf(name) > -1 ||
scope.functions.indexOf(name) > -1 ||
scope.var.indexOf(name) > -1;
scope.lexical.push(name);
} else if (bindingType === BIND_SIMPLE_CATCH) {
const scope = this.currentScope();
scope.lexical.push(name);
} else if (bindingType === BIND_FUNCTION) {
const scope = this.currentScope();
if (this.treatFunctionsAsVar) {
redeclared = scope.lexical.indexOf(name) > -1;
} else {
redeclared =
scope.lexical.indexOf(name) > -1 || scope.var.indexOf(name) > -1;
}
scope.functions.push(name);
} else {
for (let i = this.scopeStack.length - 1; i >= 0; --i) {
const scope = this.scopeStack[i];
if (
(scope.lexical.indexOf(name) > -1 &&
!(scope.flags & SCOPE_SIMPLE_CATCH) &&
scope.lexical[0] === name) ||
(!this.treatFunctionsAsVarInScope(scope) &&
scope.functions.indexOf(name) > -1)
) {
redeclared = true;
break;
}
scope.var.push(name);
if (scope.flags & SCOPE_VAR) break;
}
}
if (redeclared) {
this.raise(pos, `Identifier '${name}' has already been declared`);
}
}
currentScope(): Scope {
return this.scopeStack[this.scopeStack.length - 1];
}
// $FlowIgnore
currentVarScope(): Scope {
for (let i = this.scopeStack.length - 1; ; i--) {
const scope = this.scopeStack[i];
if (scope.flags & SCOPE_VAR) {
return scope;
}
}
}
// Could be useful for `this`, `new.target`, `super()`, `super.property`, and `super[property]`.
// $FlowIgnore
currentThisScope(): Scope {
for (let i = this.scopeStack.length - 1; ; i--) {
const scope = this.scopeStack[i];
if (
(scope.flags & SCOPE_VAR || scope.flags & SCOPE_CLASS) &&
!(scope.flags & SCOPE_ARROW)
) {
return scope;
}
}
}
}

View File

@ -0,0 +1,52 @@
// @flow
// Each scope gets a bitset that may contain these flags
// prettier-ignore
export const SCOPE_OTHER = 0b000000000,
SCOPE_PROGRAM = 0b000000001,
SCOPE_FUNCTION = 0b000000010,
SCOPE_ASYNC = 0b000000100,
SCOPE_GENERATOR = 0b000001000,
SCOPE_ARROW = 0b000010000,
SCOPE_SIMPLE_CATCH = 0b000100000,
SCOPE_SUPER = 0b001000000,
SCOPE_DIRECT_SUPER = 0b010000000,
SCOPE_CLASS = 0b100000000,
SCOPE_VAR = SCOPE_PROGRAM | SCOPE_FUNCTION;
export type ScopeFlags =
| typeof SCOPE_OTHER
| typeof SCOPE_PROGRAM
| typeof SCOPE_FUNCTION
| typeof SCOPE_VAR
| typeof SCOPE_ASYNC
| typeof SCOPE_GENERATOR
| typeof SCOPE_ARROW
| typeof SCOPE_SIMPLE_CATCH
| typeof SCOPE_SUPER
| typeof SCOPE_DIRECT_SUPER
| typeof SCOPE_CLASS;
export function functionFlags(isAsync: boolean, isGenerator: boolean) {
return (
SCOPE_FUNCTION |
(isAsync ? SCOPE_ASYNC : 0) |
(isGenerator ? SCOPE_GENERATOR : 0)
);
}
// Used in checkLVal and declareName to determine the type of a binding
export const BIND_NONE = 0, // Not a binding
BIND_VAR = 1, // Var-style binding
BIND_LEXICAL = 2, // Let- or const-style binding
BIND_FUNCTION = 3, // Function declaration
BIND_SIMPLE_CATCH = 4, // Simple (identifier pattern) catch binding
BIND_OUTSIDE = 5; // Special case for function names as bound inside the function
export type BindingTypes =
| typeof BIND_NONE
| typeof BIND_VAR
| typeof BIND_LEXICAL
| typeof BIND_FUNCTION
| typeof BIND_SIMPLE_CATCH
| typeof BIND_OUTSIDE;

View File

@ -1,3 +1,3 @@
{ {
"throws": "Non-simple parameter in strict mode (1:10)" "throws": "Illegal 'use strict' directive in function with non-simple parameter list (1:0)"
} }

View File

@ -1,3 +1,3 @@
{ {
"throws": "Function declaration not allowed in this context (1:10)" "throws": "In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement (1:10)"
} }

View File

@ -1,3 +1,3 @@
{ {
"throws": "Function declaration not allowed in this context (1:20)" "throws": "In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement (1:20)"
} }

View File

@ -0,0 +1,3 @@
try {
} catch ([foo, foo]) {
}

View File

@ -0,0 +1,3 @@
{
"throws": "Identifier 'foo' has already been declared (2:15)"
}

View File

@ -0,0 +1 @@
let foo; try {} catch (foo) {} let foo;

View File

@ -0,0 +1,3 @@
{
"throws": "Identifier 'foo' has already been declared (1:35)"
}

View File

@ -0,0 +1,4 @@
try {
} catch (foo) {
function foo() {}
}

View File

@ -0,0 +1,3 @@
{
"throws": "Identifier 'foo' has already been declared (3:11)"
}

View File

@ -0,0 +1,4 @@
try {
} catch (foo) {
if (1) function foo() {}
}

View File

@ -0,0 +1,207 @@
{
"type": "File",
"start": 0,
"end": 50,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"program": {
"type": "Program",
"start": 0,
"end": 50,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "TryStatement",
"start": 0,
"end": 50,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"block": {
"type": "BlockStatement",
"start": 4,
"end": 7,
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 2,
"column": 1
}
},
"body": [],
"directives": []
},
"handler": {
"type": "CatchClause",
"start": 8,
"end": 50,
"loc": {
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 4,
"column": 1
}
},
"param": {
"type": "Identifier",
"start": 15,
"end": 18,
"loc": {
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 12
},
"identifierName": "foo"
},
"name": "foo"
},
"body": {
"type": "BlockStatement",
"start": 20,
"end": 50,
"loc": {
"start": {
"line": 2,
"column": 14
},
"end": {
"line": 4,
"column": 1
}
},
"body": [
{
"type": "IfStatement",
"start": 24,
"end": 48,
"loc": {
"start": {
"line": 3,
"column": 2
},
"end": {
"line": 3,
"column": 26
}
},
"test": {
"type": "NumericLiteral",
"start": 28,
"end": 29,
"loc": {
"start": {
"line": 3,
"column": 6
},
"end": {
"line": 3,
"column": 7
}
},
"extra": {
"rawValue": 1,
"raw": "1"
},
"value": 1
},
"consequent": {
"type": "FunctionDeclaration",
"start": 31,
"end": 48,
"loc": {
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 26
}
},
"id": {
"type": "Identifier",
"start": 40,
"end": 43,
"loc": {
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 21
},
"identifierName": "foo"
},
"name": "foo"
},
"generator": false,
"async": false,
"params": [],
"body": {
"type": "BlockStatement",
"start": 46,
"end": 48,
"loc": {
"start": {
"line": 3,
"column": 24
},
"end": {
"line": 3,
"column": 26
}
},
"body": [],
"directives": []
}
},
"alternate": null
}
],
"directives": []
}
},
"guardedHandlers": [],
"finalizer": null
}
],
"directives": []
}
}

View File

@ -0,0 +1,4 @@
try {
} catch (foo) {
}
let foo;

View File

@ -0,0 +1,169 @@
{
"type": "File",
"start": 0,
"end": 32,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 4,
"column": 8
}
},
"program": {
"type": "Program",
"start": 0,
"end": 32,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 4,
"column": 8
}
},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "TryStatement",
"start": 0,
"end": 23,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"block": {
"type": "BlockStatement",
"start": 4,
"end": 7,
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 2,
"column": 1
}
},
"body": [],
"directives": []
},
"handler": {
"type": "CatchClause",
"start": 8,
"end": 23,
"loc": {
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 3,
"column": 1
}
},
"param": {
"type": "Identifier",
"start": 15,
"end": 18,
"loc": {
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 12
},
"identifierName": "foo"
},
"name": "foo"
},
"body": {
"type": "BlockStatement",
"start": 20,
"end": 23,
"loc": {
"start": {
"line": 2,
"column": 14
},
"end": {
"line": 3,
"column": 1
}
},
"body": [],
"directives": []
}
},
"guardedHandlers": [],
"finalizer": null
},
{
"type": "VariableDeclaration",
"start": 24,
"end": 32,
"loc": {
"start": {
"line": 4,
"column": 0
},
"end": {
"line": 4,
"column": 8
}
},
"declarations": [
{
"type": "VariableDeclarator",
"start": 28,
"end": 31,
"loc": {
"start": {
"line": 4,
"column": 4
},
"end": {
"line": 4,
"column": 7
}
},
"id": {
"type": "Identifier",
"start": 28,
"end": 31,
"loc": {
"start": {
"line": 4,
"column": 4
},
"end": {
"line": 4,
"column": 7
},
"identifierName": "foo"
},
"name": "foo"
},
"init": null
}
],
"kind": "let"
}
],
"directives": []
}
}

View File

@ -0,0 +1,4 @@
try {
} catch (foo) {
let foo;
}

View File

@ -0,0 +1,3 @@
{
"throws": "Identifier 'foo' has already been declared (3:6)"
}

View File

@ -0,0 +1,3 @@
try {
} catch ({ a: foo, b: { c: [foo] } }) {
}

View File

@ -0,0 +1,3 @@
{
"throws": "Identifier 'foo' has already been declared (2:28)"
}

View File

@ -0,0 +1,4 @@
try {
} catch ([foo]) {
var foo;
}

View File

@ -0,0 +1,3 @@
{
"throws": "Identifier 'foo' has already been declared (3:6)"
}

View File

@ -0,0 +1,7 @@
try {
} catch (foo) {
try {
} catch (_) {
var foo;
}
}

View File

@ -0,0 +1,254 @@
{
"type": "File",
"start": 0,
"end": 64,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 7,
"column": 1
}
},
"program": {
"type": "Program",
"start": 0,
"end": 64,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 7,
"column": 1
}
},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "TryStatement",
"start": 0,
"end": 64,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 7,
"column": 1
}
},
"block": {
"type": "BlockStatement",
"start": 4,
"end": 7,
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 2,
"column": 1
}
},
"body": [],
"directives": []
},
"handler": {
"type": "CatchClause",
"start": 8,
"end": 64,
"loc": {
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 7,
"column": 1
}
},
"param": {
"type": "Identifier",
"start": 15,
"end": 18,
"loc": {
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 12
},
"identifierName": "foo"
},
"name": "foo"
},
"body": {
"type": "BlockStatement",
"start": 20,
"end": 64,
"loc": {
"start": {
"line": 2,
"column": 14
},
"end": {
"line": 7,
"column": 1
}
},
"body": [
{
"type": "TryStatement",
"start": 24,
"end": 62,
"loc": {
"start": {
"line": 3,
"column": 2
},
"end": {
"line": 6,
"column": 3
}
},
"block": {
"type": "BlockStatement",
"start": 28,
"end": 33,
"loc": {
"start": {
"line": 3,
"column": 6
},
"end": {
"line": 4,
"column": 3
}
},
"body": [],
"directives": []
},
"handler": {
"type": "CatchClause",
"start": 34,
"end": 62,
"loc": {
"start": {
"line": 4,
"column": 4
},
"end": {
"line": 6,
"column": 3
}
},
"param": {
"type": "Identifier",
"start": 41,
"end": 42,
"loc": {
"start": {
"line": 4,
"column": 11
},
"end": {
"line": 4,
"column": 12
},
"identifierName": "_"
},
"name": "_"
},
"body": {
"type": "BlockStatement",
"start": 44,
"end": 62,
"loc": {
"start": {
"line": 4,
"column": 14
},
"end": {
"line": 6,
"column": 3
}
},
"body": [
{
"type": "VariableDeclaration",
"start": 50,
"end": 58,
"loc": {
"start": {
"line": 5,
"column": 4
},
"end": {
"line": 5,
"column": 12
}
},
"declarations": [
{
"type": "VariableDeclarator",
"start": 54,
"end": 57,
"loc": {
"start": {
"line": 5,
"column": 8
},
"end": {
"line": 5,
"column": 11
}
},
"id": {
"type": "Identifier",
"start": 54,
"end": 57,
"loc": {
"start": {
"line": 5,
"column": 8
},
"end": {
"line": 5,
"column": 11
},
"identifierName": "foo"
},
"name": "foo"
},
"init": null
}
],
"kind": "var"
}
],
"directives": []
}
},
"guardedHandlers": [],
"finalizer": null
}
],
"directives": []
}
},
"guardedHandlers": [],
"finalizer": null
}
],
"directives": []
}
}

View File

@ -0,0 +1,4 @@
try {
} catch ({ foo }) {
var foo;
}

View File

@ -0,0 +1,3 @@
{
"throws": "Identifier 'foo' has already been declared (3:6)"
}

View File

@ -0,0 +1,4 @@
try {
} catch (foo) {
}
var foo;

View File

@ -0,0 +1,169 @@
{
"type": "File",
"start": 0,
"end": 32,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 4,
"column": 8
}
},
"program": {
"type": "Program",
"start": 0,
"end": 32,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 4,
"column": 8
}
},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "TryStatement",
"start": 0,
"end": 23,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"block": {
"type": "BlockStatement",
"start": 4,
"end": 7,
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 2,
"column": 1
}
},
"body": [],
"directives": []
},
"handler": {
"type": "CatchClause",
"start": 8,
"end": 23,
"loc": {
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 3,
"column": 1
}
},
"param": {
"type": "Identifier",
"start": 15,
"end": 18,
"loc": {
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 12
},
"identifierName": "foo"
},
"name": "foo"
},
"body": {
"type": "BlockStatement",
"start": 20,
"end": 23,
"loc": {
"start": {
"line": 2,
"column": 14
},
"end": {
"line": 3,
"column": 1
}
},
"body": [],
"directives": []
}
},
"guardedHandlers": [],
"finalizer": null
},
{
"type": "VariableDeclaration",
"start": 24,
"end": 32,
"loc": {
"start": {
"line": 4,
"column": 0
},
"end": {
"line": 4,
"column": 8
}
},
"declarations": [
{
"type": "VariableDeclarator",
"start": 28,
"end": 31,
"loc": {
"start": {
"line": 4,
"column": 4
},
"end": {
"line": 4,
"column": 7
}
},
"id": {
"type": "Identifier",
"start": 28,
"end": 31,
"loc": {
"start": {
"line": 4,
"column": 4
},
"end": {
"line": 4,
"column": 7
},
"identifierName": "foo"
},
"name": "foo"
},
"init": null
}
],
"kind": "var"
}
],
"directives": []
}
}

View File

@ -0,0 +1,4 @@
try {
} catch (foo) {
var foo;
}

View File

@ -0,0 +1,170 @@
{
"type": "File",
"start": 0,
"end": 34,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"program": {
"type": "Program",
"start": 0,
"end": 34,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "TryStatement",
"start": 0,
"end": 34,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"block": {
"type": "BlockStatement",
"start": 4,
"end": 7,
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 2,
"column": 1
}
},
"body": [],
"directives": []
},
"handler": {
"type": "CatchClause",
"start": 8,
"end": 34,
"loc": {
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 4,
"column": 1
}
},
"param": {
"type": "Identifier",
"start": 15,
"end": 18,
"loc": {
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 12
},
"identifierName": "foo"
},
"name": "foo"
},
"body": {
"type": "BlockStatement",
"start": 20,
"end": 34,
"loc": {
"start": {
"line": 2,
"column": 14
},
"end": {
"line": 4,
"column": 1
}
},
"body": [
{
"type": "VariableDeclaration",
"start": 24,
"end": 32,
"loc": {
"start": {
"line": 3,
"column": 2
},
"end": {
"line": 3,
"column": 10
}
},
"declarations": [
{
"type": "VariableDeclarator",
"start": 28,
"end": 31,
"loc": {
"start": {
"line": 3,
"column": 6
},
"end": {
"line": 3,
"column": 9
}
},
"id": {
"type": "Identifier",
"start": 28,
"end": 31,
"loc": {
"start": {
"line": 3,
"column": 6
},
"end": {
"line": 3,
"column": 9
},
"identifierName": "foo"
},
"name": "foo"
},
"init": null
}
],
"kind": "var"
}
],
"directives": []
}
},
"guardedHandlers": [],
"finalizer": null
}
],
"directives": []
}
}

View File

@ -0,0 +1,2 @@
class foo {};
class foo {};

View File

@ -0,0 +1,3 @@
{
"throws": "Identifier 'foo' has already been declared (2:6)"
}

View File

@ -0,0 +1,2 @@
class foo {};
const foo = 1;

View File

@ -0,0 +1,3 @@
{
"throws": "Identifier 'foo' has already been declared (2:6)"
}

View File

@ -0,0 +1,2 @@
class foo {};
function foo () {};

View File

@ -0,0 +1,3 @@
{
"throws": "Identifier 'foo' has already been declared (2:9)"
}

View File

@ -0,0 +1,2 @@
class foo {};
let foo = 1;

View File

@ -0,0 +1,3 @@
{
"throws": "Identifier 'foo' has already been declared (2:4)"
}

View File

@ -0,0 +1,2 @@
class foo {};
var foo;

View File

@ -0,0 +1,3 @@
{
"throws": "Identifier 'foo' has already been declared (2:4)"
}

View File

@ -0,0 +1 @@
const foo=1, foo=2;

View File

@ -0,0 +1,3 @@
{
"throws": "Identifier 'foo' has already been declared (1:13)"
}

View File

@ -0,0 +1 @@
{ function f() {} function* f() {} }

View File

@ -0,0 +1,3 @@
{
"throws": "Identifier 'f' has already been declared (1:28)"
}

View File

@ -0,0 +1 @@
{ function foo() {} function foo() {} }

View File

@ -0,0 +1,4 @@
{
"sourceType": "module",
"throws": "Identifier 'foo' has already been declared (1:29)"
}

View File

@ -0,0 +1,2 @@
function foo() {}
function foo() {}

View File

@ -0,0 +1,4 @@
{
"sourceType": "module",
"throws":"Identifier 'foo' has already been declared (2:9)"
}

View File

@ -0,0 +1 @@
{ function foo() {} function foo() {} }

View File

@ -0,0 +1,3 @@
{
"sourceType": "script"
}

View File

@ -0,0 +1,157 @@
{
"type": "File",
"start": 0,
"end": 39,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 39
}
},
"program": {
"type": "Program",
"start": 0,
"end": 39,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 39
}
},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "BlockStatement",
"start": 0,
"end": 39,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 39
}
},
"body": [
{
"type": "FunctionDeclaration",
"start": 2,
"end": 19,
"loc": {
"start": {
"line": 1,
"column": 2
},
"end": {
"line": 1,
"column": 19
}
},
"id": {
"type": "Identifier",
"start": 11,
"end": 14,
"loc": {
"start": {
"line": 1,
"column": 11
},
"end": {
"line": 1,
"column": 14
},
"identifierName": "foo"
},
"name": "foo"
},
"generator": false,
"async": false,
"params": [],
"body": {
"type": "BlockStatement",
"start": 17,
"end": 19,
"loc": {
"start": {
"line": 1,
"column": 17
},
"end": {
"line": 1,
"column": 19
}
},
"body": [],
"directives": []
}
},
{
"type": "FunctionDeclaration",
"start": 20,
"end": 37,
"loc": {
"start": {
"line": 1,
"column": 20
},
"end": {
"line": 1,
"column": 37
}
},
"id": {
"type": "Identifier",
"start": 29,
"end": 32,
"loc": {
"start": {
"line": 1,
"column": 29
},
"end": {
"line": 1,
"column": 32
},
"identifierName": "foo"
},
"name": "foo"
},
"generator": false,
"async": false,
"params": [],
"body": {
"type": "BlockStatement",
"start": 35,
"end": 37,
"loc": {
"start": {
"line": 1,
"column": 35
},
"end": {
"line": 1,
"column": 37
}
},
"body": [],
"directives": []
}
}
],
"directives": []
}
],
"directives": []
}
}

View File

@ -0,0 +1,2 @@
function foo() {}
function foo() {}

View File

@ -0,0 +1,3 @@
{
"sourceType": "script"
}

View File

@ -0,0 +1,139 @@
{
"type": "File",
"start": 0,
"end": 35,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 2,
"column": 17
}
},
"program": {
"type": "Program",
"start": 0,
"end": 35,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 2,
"column": 17
}
},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "FunctionDeclaration",
"start": 0,
"end": 17,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 17
}
},
"id": {
"type": "Identifier",
"start": 9,
"end": 12,
"loc": {
"start": {
"line": 1,
"column": 9
},
"end": {
"line": 1,
"column": 12
},
"identifierName": "foo"
},
"name": "foo"
},
"generator": false,
"async": false,
"params": [],
"body": {
"type": "BlockStatement",
"start": 15,
"end": 17,
"loc": {
"start": {
"line": 1,
"column": 15
},
"end": {
"line": 1,
"column": 17
}
},
"body": [],
"directives": []
}
},
{
"type": "FunctionDeclaration",
"start": 18,
"end": 35,
"loc": {
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 2,
"column": 17
}
},
"id": {
"type": "Identifier",
"start": 27,
"end": 30,
"loc": {
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 12
},
"identifierName": "foo"
},
"name": "foo"
},
"generator": false,
"async": false,
"params": [],
"body": {
"type": "BlockStatement",
"start": 33,
"end": 35,
"loc": {
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 2,
"column": 17
}
},
"body": [],
"directives": []
}
}
],
"directives": []
}
}

View File

@ -0,0 +1,4 @@
{
function foo() {}
var foo = 1;
}

View File

@ -0,0 +1,3 @@
{
"throws": "Identifier 'foo' has already been declared (3:6)"
}

View File

@ -0,0 +1,2 @@
function foo() {}
var foo = 1;

View File

@ -0,0 +1,157 @@
{
"type": "File",
"start": 0,
"end": 30,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 2,
"column": 12
}
},
"program": {
"type": "Program",
"start": 0,
"end": 30,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 2,
"column": 12
}
},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "FunctionDeclaration",
"start": 0,
"end": 17,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 17
}
},
"id": {
"type": "Identifier",
"start": 9,
"end": 12,
"loc": {
"start": {
"line": 1,
"column": 9
},
"end": {
"line": 1,
"column": 12
},
"identifierName": "foo"
},
"name": "foo"
},
"generator": false,
"async": false,
"params": [],
"body": {
"type": "BlockStatement",
"start": 15,
"end": 17,
"loc": {
"start": {
"line": 1,
"column": 15
},
"end": {
"line": 1,
"column": 17
}
},
"body": [],
"directives": []
}
},
{
"type": "VariableDeclaration",
"start": 18,
"end": 30,
"loc": {
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 2,
"column": 12
}
},
"declarations": [
{
"type": "VariableDeclarator",
"start": 22,
"end": 29,
"loc": {
"start": {
"line": 2,
"column": 4
},
"end": {
"line": 2,
"column": 11
}
},
"id": {
"type": "Identifier",
"start": 22,
"end": 25,
"loc": {
"start": {
"line": 2,
"column": 4
},
"end": {
"line": 2,
"column": 7
},
"identifierName": "foo"
},
"name": "foo"
},
"init": {
"type": "NumericLiteral",
"start": 28,
"end": 29,
"loc": {
"start": {
"line": 2,
"column": 10
},
"end": {
"line": 2,
"column": 11
}
},
"extra": {
"rawValue": 1,
"raw": "1"
},
"value": 1
}
}
],
"kind": "var"
}
],
"directives": []
}
}

View File

@ -0,0 +1 @@
{ function* f() {} function f() {} }

View File

@ -0,0 +1,3 @@
{
"throws": "Identifier 'f' has already been declared (1:28)"
}

View File

@ -0,0 +1,2 @@
function *foo() {};
function *foo() {};

View File

@ -0,0 +1,3 @@
{
"sourceType": "script"
}

View File

@ -0,0 +1,169 @@
{
"type": "File",
"start": 0,
"end": 39,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 2,
"column": 19
}
},
"program": {
"type": "Program",
"start": 0,
"end": 39,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 2,
"column": 19
}
},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "FunctionDeclaration",
"start": 0,
"end": 18,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 18
}
},
"id": {
"type": "Identifier",
"start": 10,
"end": 13,
"loc": {
"start": {
"line": 1,
"column": 10
},
"end": {
"line": 1,
"column": 13
},
"identifierName": "foo"
},
"name": "foo"
},
"generator": true,
"async": false,
"params": [],
"body": {
"type": "BlockStatement",
"start": 16,
"end": 18,
"loc": {
"start": {
"line": 1,
"column": 16
},
"end": {
"line": 1,
"column": 18
}
},
"body": [],
"directives": []
}
},
{
"type": "EmptyStatement",
"start": 18,
"end": 19,
"loc": {
"start": {
"line": 1,
"column": 18
},
"end": {
"line": 1,
"column": 19
}
}
},
{
"type": "FunctionDeclaration",
"start": 20,
"end": 38,
"loc": {
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 2,
"column": 18
}
},
"id": {
"type": "Identifier",
"start": 30,
"end": 33,
"loc": {
"start": {
"line": 2,
"column": 10
},
"end": {
"line": 2,
"column": 13
},
"identifierName": "foo"
},
"name": "foo"
},
"generator": true,
"async": false,
"params": [],
"body": {
"type": "BlockStatement",
"start": 36,
"end": 38,
"loc": {
"start": {
"line": 2,
"column": 16
},
"end": {
"line": 2,
"column": 18
}
},
"body": [],
"directives": []
}
},
{
"type": "EmptyStatement",
"start": 38,
"end": 39,
"loc": {
"start": {
"line": 2,
"column": 18
},
"end": {
"line": 2,
"column": 19
}
}
}
],
"directives": []
}
}

View File

@ -0,0 +1 @@
let foo, foo;

View File

@ -0,0 +1,3 @@
{
"throws": "Identifier 'foo' has already been declared (1:9)"
}

View File

@ -0,0 +1,4 @@
{
let a;
{ var a; }
}

View File

@ -0,0 +1,3 @@
{
"throws": "Identifier 'a' has already been declared (3:8)"
}

View File

@ -0,0 +1,4 @@
{
var a;
{ let a; }
}

View File

@ -0,0 +1,173 @@
{
"type": "File",
"start": 0,
"end": 25,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"program": {
"type": "Program",
"start": 0,
"end": 25,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "BlockStatement",
"start": 0,
"end": 25,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"body": [
{
"type": "VariableDeclaration",
"start": 4,
"end": 10,
"loc": {
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 2,
"column": 8
}
},
"declarations": [
{
"type": "VariableDeclarator",
"start": 8,
"end": 9,
"loc": {
"start": {
"line": 2,
"column": 6
},
"end": {
"line": 2,
"column": 7
}
},
"id": {
"type": "Identifier",
"start": 8,
"end": 9,
"loc": {
"start": {
"line": 2,
"column": 6
},
"end": {
"line": 2,
"column": 7
},
"identifierName": "a"
},
"name": "a"
},
"init": null
}
],
"kind": "var"
},
{
"type": "BlockStatement",
"start": 13,
"end": 23,
"loc": {
"start": {
"line": 3,
"column": 2
},
"end": {
"line": 3,
"column": 12
}
},
"body": [
{
"type": "VariableDeclaration",
"start": 15,
"end": 21,
"loc": {
"start": {
"line": 3,
"column": 4
},
"end": {
"line": 3,
"column": 10
}
},
"declarations": [
{
"type": "VariableDeclarator",
"start": 19,
"end": 20,
"loc": {
"start": {
"line": 3,
"column": 8
},
"end": {
"line": 3,
"column": 9
}
},
"id": {
"type": "Identifier",
"start": 19,
"end": 20,
"loc": {
"start": {
"line": 3,
"column": 8
},
"end": {
"line": 3,
"column": 9
},
"identifierName": "a"
},
"name": "a"
},
"init": null
}
],
"kind": "let"
}
],
"directives": []
}
],
"directives": []
}
],
"directives": []
}
}

View File

@ -0,0 +1 @@
var foo, foo;

View File

@ -0,0 +1,119 @@
{
"type": "File",
"start": 0,
"end": 13,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 13
}
},
"program": {
"type": "Program",
"start": 0,
"end": 13,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 13
}
},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "VariableDeclaration",
"start": 0,
"end": 13,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 13
}
},
"declarations": [
{
"type": "VariableDeclarator",
"start": 4,
"end": 7,
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 1,
"column": 7
}
},
"id": {
"type": "Identifier",
"start": 4,
"end": 7,
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 1,
"column": 7
},
"identifierName": "foo"
},
"name": "foo"
},
"init": null
},
{
"type": "VariableDeclarator",
"start": 9,
"end": 12,
"loc": {
"start": {
"line": 1,
"column": 9
},
"end": {
"line": 1,
"column": 12
}
},
"id": {
"type": "Identifier",
"start": 9,
"end": 12,
"loc": {
"start": {
"line": 1,
"column": 9
},
"end": {
"line": 1,
"column": 12
},
"identifierName": "foo"
},
"name": "foo"
},
"init": null
}
],
"kind": "var"
}
],
"directives": []
}
}

View File

@ -0,0 +1 @@
let x = 1; x = 2;

View File

@ -0,0 +1,173 @@
{
"type": "File",
"start": 0,
"end": 17,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 17
}
},
"program": {
"type": "Program",
"start": 0,
"end": 17,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 17
}
},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "VariableDeclaration",
"start": 0,
"end": 10,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 10
}
},
"declarations": [
{
"type": "VariableDeclarator",
"start": 4,
"end": 9,
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 1,
"column": 9
}
},
"id": {
"type": "Identifier",
"start": 4,
"end": 5,
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 1,
"column": 5
},
"identifierName": "x"
},
"name": "x"
},
"init": {
"type": "NumericLiteral",
"start": 8,
"end": 9,
"loc": {
"start": {
"line": 1,
"column": 8
},
"end": {
"line": 1,
"column": 9
}
},
"extra": {
"rawValue": 1,
"raw": "1"
},
"value": 1
}
}
],
"kind": "let"
},
{
"type": "ExpressionStatement",
"start": 11,
"end": 17,
"loc": {
"start": {
"line": 1,
"column": 11
},
"end": {
"line": 1,
"column": 17
}
},
"expression": {
"type": "AssignmentExpression",
"start": 11,
"end": 16,
"loc": {
"start": {
"line": 1,
"column": 11
},
"end": {
"line": 1,
"column": 16
}
},
"operator": "=",
"left": {
"type": "Identifier",
"start": 11,
"end": 12,
"loc": {
"start": {
"line": 1,
"column": 11
},
"end": {
"line": 1,
"column": 12
},
"identifierName": "x"
},
"name": "x"
},
"right": {
"type": "NumericLiteral",
"start": 15,
"end": 16,
"loc": {
"start": {
"line": 1,
"column": 15
},
"end": {
"line": 1,
"column": 16
}
},
"extra": {
"rawValue": 2,
"raw": "2"
},
"value": 2
}
}
}
],
"directives": []
}
}

View File

@ -1,3 +1,3 @@
{ {
"throws": "Unexpected token (1:9)" "throws": "Unexpected keyword 'null' (1:9)"
} }

View File

@ -1,3 +1,3 @@
{ {
"throws": "Unexpected token (1:9)" "throws": "Unexpected keyword 'true' (1:9)"
} }

View File

@ -1,3 +1,3 @@
{ {
"throws": "Unexpected token (1:9)" "throws": "Unexpected keyword 'false' (1:9)"
} }

View File

@ -1,3 +1,3 @@
{ {
"throws": "Unexpected token (1:9)" "throws": "Unexpected keyword 'if' (1:9)"
} }

View File

@ -1,3 +1,3 @@
{ {
"throws": "Argument name clash in strict mode (1:14)" "throws": "Argument name clash (1:14)"
} }

Some files were not shown because too many files have changed in this diff Show More