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
284 changed files with 5904 additions and 1842 deletions

View File

@@ -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

View File

@@ -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 {

View File

@@ -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;
}
}