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:
@@ -4,6 +4,7 @@ import { types as tt, TokenType } from "../tokenizer/types";
|
||||
import type Parser from "../parser";
|
||||
import * as N from "../types";
|
||||
import type { Pos, Position } from "../util/location";
|
||||
import { type BindingTypes, BIND_NONE } from "../util/scopeflags";
|
||||
|
||||
function isSimpleProperty(node: N.Node): boolean {
|
||||
return (
|
||||
@@ -104,7 +105,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
|
||||
checkLVal(
|
||||
expr: N.Expression,
|
||||
isBinding: ?boolean,
|
||||
bindingType: ?BindingTypes = BIND_NONE,
|
||||
checkClashes: ?{ [key: string]: boolean },
|
||||
contextDescription: string,
|
||||
): void {
|
||||
@@ -113,14 +114,14 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
expr.properties.forEach(prop => {
|
||||
this.checkLVal(
|
||||
prop.type === "Property" ? prop.value : prop,
|
||||
isBinding,
|
||||
bindingType,
|
||||
checkClashes,
|
||||
"object destructuring pattern",
|
||||
);
|
||||
});
|
||||
break;
|
||||
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,
|
||||
isAsync: boolean,
|
||||
isConstructor: boolean,
|
||||
allowsDirectSuper: boolean,
|
||||
): void {
|
||||
this.parseMethod(
|
||||
method,
|
||||
isGenerator,
|
||||
isAsync,
|
||||
isConstructor,
|
||||
allowsDirectSuper,
|
||||
"MethodDefinition",
|
||||
true,
|
||||
);
|
||||
if (method.typeParameters) {
|
||||
// $FlowIgnore
|
||||
@@ -265,7 +269,9 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
isGenerator: boolean,
|
||||
isAsync: boolean,
|
||||
isConstructor: boolean,
|
||||
allowDirectSuper: boolean,
|
||||
type: string,
|
||||
inClassScope: boolean = false,
|
||||
): T {
|
||||
let funcNode = this.startNode();
|
||||
funcNode.kind = node.kind; // provide kind, so super method correctly sets state
|
||||
@@ -274,7 +280,9 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
isGenerator,
|
||||
isAsync,
|
||||
isConstructor,
|
||||
allowDirectSuper,
|
||||
"FunctionExpression",
|
||||
inClassScope,
|
||||
);
|
||||
delete funcNode.kind;
|
||||
// $FlowIgnore
|
||||
|
||||
@@ -9,6 +9,12 @@ import type State from "../tokenizer/state";
|
||||
import { types as tc } from "../tokenizer/context";
|
||||
import * as charCodes from "charcodes";
|
||||
import { isIteratorStart } from "../util/identifier";
|
||||
import {
|
||||
type BindingTypes,
|
||||
BIND_NONE,
|
||||
BIND_LEXICAL,
|
||||
SCOPE_OTHER,
|
||||
} from "../util/scopeflags";
|
||||
|
||||
const reservedTypes = [
|
||||
"any",
|
||||
@@ -257,6 +263,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
flowParseDeclareModule(node: N.FlowDeclareModule): N.FlowDeclareModule {
|
||||
this.next();
|
||||
|
||||
this.scope.enter(SCOPE_OTHER);
|
||||
|
||||
if (this.match(tt.string)) {
|
||||
node.id = this.parseExprAtom();
|
||||
} else {
|
||||
@@ -290,6 +298,9 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
|
||||
body.push(bodyNode);
|
||||
}
|
||||
|
||||
this.scope.exit();
|
||||
|
||||
this.expect(tt.braceR);
|
||||
|
||||
this.finishNode(bodyNode, "BlockStatement");
|
||||
@@ -518,6 +529,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
|
||||
flowParseTypeAlias(node: N.FlowTypeAlias): N.FlowTypeAlias {
|
||||
node.id = this.flowParseRestrictedIdentifier();
|
||||
this.scope.declareName(node.id.name, BIND_LEXICAL, node.id.start);
|
||||
|
||||
if (this.isRelational("<")) {
|
||||
node.typeParameters = this.flowParseTypeParameterDeclaration();
|
||||
@@ -537,6 +549,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
): N.FlowOpaqueType {
|
||||
this.expectContextual("type");
|
||||
node.id = this.flowParseRestrictedIdentifier(/*liberal*/ true);
|
||||
this.scope.declareName(node.id.name, BIND_LEXICAL, node.id.start);
|
||||
|
||||
if (this.isRelational("<")) {
|
||||
node.typeParameters = this.flowParseTypeParameterDeclaration();
|
||||
@@ -1761,7 +1774,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
"arrow function parameters",
|
||||
);
|
||||
// Use super's method to force the parameters to be checked
|
||||
super.checkFunctionNameAndParams(node, true);
|
||||
super.checkParams(node, false, true);
|
||||
} else {
|
||||
arrows.push(node);
|
||||
}
|
||||
@@ -1995,14 +2008,14 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
|
||||
checkLVal(
|
||||
expr: N.Expression,
|
||||
isBinding: ?boolean,
|
||||
bindingType: ?BindingTypes = BIND_NONE,
|
||||
checkClashes: ?{ [key: string]: boolean },
|
||||
contextDescription: string,
|
||||
): void {
|
||||
if (expr.type !== "TypeCastExpression") {
|
||||
return super.checkLVal(
|
||||
expr,
|
||||
isBinding,
|
||||
bindingType,
|
||||
checkClashes,
|
||||
contextDescription,
|
||||
);
|
||||
@@ -2047,6 +2060,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
isGenerator: boolean,
|
||||
isAsync: boolean,
|
||||
isConstructor: boolean,
|
||||
allowsDirectSuper: boolean,
|
||||
): void {
|
||||
if ((method: $FlowFixMe).variance) {
|
||||
this.unexpected((method: $FlowFixMe).variance.start);
|
||||
@@ -2064,6 +2078,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
isGenerator,
|
||||
isAsync,
|
||||
isConstructor,
|
||||
allowsDirectSuper,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2217,7 +2232,12 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
? this.flowParseRestrictedIdentifier(true)
|
||||
: this.parseIdentifier();
|
||||
|
||||
this.checkLVal(specifier.local, true, undefined, contextDescription);
|
||||
this.checkLVal(
|
||||
specifier.local,
|
||||
BIND_LEXICAL,
|
||||
undefined,
|
||||
contextDescription,
|
||||
);
|
||||
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"));
|
||||
}
|
||||
|
||||
// parse function type parameters - function foo<T>() {}
|
||||
parseFunctionParams(node: N.Function): void {
|
||||
parseFunctionParams(node: N.Function, allowModifiers?: boolean): void {
|
||||
// $FlowFixMe
|
||||
const kind = node.kind;
|
||||
if (kind !== "get" && kind !== "set" && this.isRelational("<")) {
|
||||
@@ -2340,7 +2365,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
/* allowDefault */ false,
|
||||
);
|
||||
}
|
||||
super.parseFunctionParams(node);
|
||||
super.parseFunctionParams(node, allowModifiers);
|
||||
}
|
||||
|
||||
// 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,
|
||||
allowDuplicates: boolean,
|
||||
isArrowFunction: ?boolean,
|
||||
): void {
|
||||
if (
|
||||
@@ -2530,7 +2556,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
return;
|
||||
}
|
||||
|
||||
return super.checkFunctionNameAndParams(node, isArrowFunction);
|
||||
return super.checkParams(node, allowDuplicates, isArrowFunction);
|
||||
}
|
||||
|
||||
parseParenAndDistinguishExpression(canBeArrow: boolean): N.Expression {
|
||||
|
||||
@@ -6,6 +6,13 @@ import { types as ct } from "../tokenizer/context";
|
||||
import * as N from "../types";
|
||||
import type { Pos, Position } from "../util/location";
|
||||
import Parser from "../parser";
|
||||
import {
|
||||
type BindingTypes,
|
||||
functionFlags,
|
||||
BIND_NONE,
|
||||
SCOPE_ARROW,
|
||||
SCOPE_OTHER,
|
||||
} from "../util/scopeflags";
|
||||
|
||||
type TsModifier =
|
||||
| "readonly"
|
||||
@@ -1071,6 +1078,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
|
||||
tsParseModuleBlock(): N.TsModuleBlock {
|
||||
const node: N.TsModuleBlock = this.startNode();
|
||||
this.scope.enter(SCOPE_OTHER);
|
||||
|
||||
this.expect(tt.braceL);
|
||||
// Inside of a module block is considered "top-level", meaning it can have imports and exports.
|
||||
this.parseBlockOrModuleBlockBody(
|
||||
@@ -1079,6 +1088,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
/* topLevel */ true,
|
||||
/* end */ tt.braceR,
|
||||
);
|
||||
this.scope.exit();
|
||||
return this.finishNode(node, "TSModuleBlock");
|
||||
}
|
||||
|
||||
@@ -1217,8 +1227,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
|
||||
switch (starttype) {
|
||||
case tt._function:
|
||||
this.next();
|
||||
return this.parseFunction(nany, /* isStatement */ true);
|
||||
return this.parseFunctionStatement(nany);
|
||||
case tt._class:
|
||||
return this.parseClass(
|
||||
nany,
|
||||
@@ -1372,17 +1381,13 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const oldInAsync = this.state.inAsync;
|
||||
const oldInGenerator = this.state.inGenerator;
|
||||
this.state.inAsync = true;
|
||||
this.state.inGenerator = false;
|
||||
this.scope.enter(functionFlags(true, false) | SCOPE_ARROW);
|
||||
|
||||
res.id = null;
|
||||
res.generator = false;
|
||||
res.expression = true; // May be set again by parseFunctionBody.
|
||||
res.async = true;
|
||||
this.parseFunctionBody(res, true);
|
||||
this.state.inAsync = oldInAsync;
|
||||
this.state.inGenerator = oldInGenerator;
|
||||
return this.finishNode(res, "ArrowFunctionExpression");
|
||||
}
|
||||
|
||||
@@ -1721,11 +1726,12 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
classBody: N.ClassBody,
|
||||
member: any,
|
||||
state: { hadConstructor: boolean },
|
||||
constructorAllowsSuper: boolean,
|
||||
): void {
|
||||
const accessibility = this.parseAccessModifier();
|
||||
if (accessibility) member.accessibility = accessibility;
|
||||
|
||||
super.parseClassMember(classBody, member, state);
|
||||
super.parseClassMember(classBody, member, state, constructorAllowsSuper);
|
||||
}
|
||||
|
||||
parseClassMemberWithIsStatic(
|
||||
@@ -1733,6 +1739,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
member: any,
|
||||
state: { hadConstructor: boolean },
|
||||
isStatic: boolean,
|
||||
constructorAllowsSuper: boolean,
|
||||
): void {
|
||||
const methodOrProp: N.ClassMethod | N.ClassProperty = member;
|
||||
const prop: N.ClassProperty = member;
|
||||
@@ -1773,7 +1780,13 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
return;
|
||||
}
|
||||
|
||||
super.parseClassMemberWithIsStatic(classBody, member, state, isStatic);
|
||||
super.parseClassMemberWithIsStatic(
|
||||
classBody,
|
||||
member,
|
||||
state,
|
||||
isStatic,
|
||||
constructorAllowsSuper,
|
||||
);
|
||||
}
|
||||
|
||||
parsePostMemberNameModifiers(
|
||||
@@ -1923,6 +1936,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
isGenerator: boolean,
|
||||
isAsync: boolean,
|
||||
isConstructor: boolean,
|
||||
allowsDirectSuper: boolean,
|
||||
): void {
|
||||
const typeParameters = this.tsTryParseTypeParameters();
|
||||
if (typeParameters) method.typeParameters = typeParameters;
|
||||
@@ -1932,6 +1946,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
isGenerator,
|
||||
isAsync,
|
||||
isConstructor,
|
||||
allowsDirectSuper,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2154,7 +2169,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
|
||||
checkLVal(
|
||||
expr: N.Expression,
|
||||
isBinding: ?boolean,
|
||||
bindingType: ?BindingTypes = BIND_NONE,
|
||||
checkClashes: ?{ [key: string]: boolean },
|
||||
contextDescription: string,
|
||||
): void {
|
||||
@@ -2167,7 +2182,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
case "TSParameterProperty":
|
||||
this.checkLVal(
|
||||
expr.parameter,
|
||||
isBinding,
|
||||
bindingType,
|
||||
checkClashes,
|
||||
"parameter property",
|
||||
);
|
||||
@@ -2177,13 +2192,13 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
case "TSTypeAssertion":
|
||||
this.checkLVal(
|
||||
expr.expression,
|
||||
isBinding,
|
||||
bindingType,
|
||||
checkClashes,
|
||||
contextDescription,
|
||||
);
|
||||
return;
|
||||
default:
|
||||
super.checkLVal(expr, isBinding, checkClashes, contextDescription);
|
||||
super.checkLVal(expr, bindingType, checkClashes, contextDescription);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user