Add TS support to @babel/parser's Scope (#9766)
* [parser] Allow plugins to extend ScopeHandler
* Directly extend Scope
* Don't use new.target to get the ScopeHandler
* [parser] Add TS enum support to the Scope
* Remove duplicated options in tests
* Fix
* Fix flow
* Rename tests
* Add tests
* Full typescript support in scope
* Remove BIND_SIMPLE_CATCH
SCOPE_SIMPLE_CATCH was used instead
* Export TS types
* Register function declarations
* Fix body-less functions and namespaces
1) Move this.scope.exit() for functions from parseFunctionBody to the callers.
Otherwise the scope of body-less functions was never closed.
Also, it is easier to track scope.exit() if it is near to scope.enter()
2) Register namespace ids for export
* Disallow redeclaration of enum with const enum
This commit is contained in:
@@ -9,7 +9,7 @@ export default class BaseParser {
|
||||
// Properties set by constructor in index.js
|
||||
options: Options;
|
||||
inModule: boolean;
|
||||
scope: ScopeHandler;
|
||||
scope: ScopeHandler<*>;
|
||||
plugins: PluginsMap;
|
||||
filename: ?string;
|
||||
sawUnambiguousESM: boolean = false;
|
||||
|
||||
@@ -1767,6 +1767,7 @@ export default class ExpressionParser extends LValParser {
|
||||
this.parseFunctionParams((node: any), allowModifiers);
|
||||
this.checkYieldAwaitInDefaultParams();
|
||||
this.parseFunctionBodyAndFinish(node, type, true);
|
||||
this.scope.exit();
|
||||
|
||||
this.state.yieldPos = oldYieldPos;
|
||||
this.state.awaitPos = oldAwaitPos;
|
||||
@@ -1795,6 +1796,7 @@ export default class ExpressionParser extends LValParser {
|
||||
if (params) this.setArrowFunctionParameters(node, params);
|
||||
this.parseFunctionBody(node, true);
|
||||
|
||||
this.scope.exit();
|
||||
this.state.maybeInArrowParameters = oldMaybeInArrowParameters;
|
||||
this.state.yieldPos = oldYieldPos;
|
||||
this.state.awaitPos = oldAwaitPos;
|
||||
@@ -1890,7 +1892,6 @@ export default class ExpressionParser extends LValParser {
|
||||
node.body = this.parseBlock(true, false);
|
||||
this.state.labels = oldLabels;
|
||||
}
|
||||
this.scope.exit();
|
||||
|
||||
this.state.inParameters = oldInParameters;
|
||||
// Ensure the function name isn't a forbidden identifier in strict mode, e.g. 'eval'
|
||||
|
||||
@@ -20,6 +20,8 @@ export default class Parser extends StatementParser {
|
||||
options = getOptions(options);
|
||||
super(options, input);
|
||||
|
||||
const ScopeHandler = this.getScopeHandler();
|
||||
|
||||
this.options = options;
|
||||
this.inModule = this.options.sourceType === "module";
|
||||
this.scope = new ScopeHandler(this.raise.bind(this), this.inModule);
|
||||
@@ -27,6 +29,11 @@ export default class Parser extends StatementParser {
|
||||
this.filename = options.sourceFilename;
|
||||
}
|
||||
|
||||
// This can be overwritten, for example, by the TypeScript plugin.
|
||||
getScopeHandler(): Class<ScopeHandler<*>> {
|
||||
return ScopeHandler;
|
||||
}
|
||||
|
||||
parse(): File {
|
||||
this.scope.enter(SCOPE_PROGRAM);
|
||||
const file = this.startNode();
|
||||
|
||||
@@ -16,7 +16,7 @@ import type {
|
||||
import type { Pos, Position } from "../util/location";
|
||||
import { isStrictBindReservedWord } from "../util/identifier";
|
||||
import { NodeUtils } from "./node";
|
||||
import { type BindingTypes, BIND_NONE, BIND_OUTSIDE } from "../util/scopeflags";
|
||||
import { type BindingTypes, BIND_NONE } from "../util/scopeflags";
|
||||
|
||||
export default class LValParser extends NodeUtils {
|
||||
// Forward-declaration: defined in expression.js
|
||||
@@ -325,7 +325,7 @@ export default class LValParser extends NodeUtils {
|
||||
|
||||
checkLVal(
|
||||
expr: Expression,
|
||||
bindingType: ?BindingTypes = BIND_NONE,
|
||||
bindingType: BindingTypes = BIND_NONE,
|
||||
checkClashes: ?{ [key: string]: boolean },
|
||||
contextDescription: string,
|
||||
): void {
|
||||
@@ -363,7 +363,7 @@ export default class LValParser extends NodeUtils {
|
||||
checkClashes[key] = true;
|
||||
}
|
||||
}
|
||||
if (bindingType !== BIND_NONE && bindingType !== BIND_OUTSIDE) {
|
||||
if (!(bindingType & BIND_NONE)) {
|
||||
this.scope.declareName(expr.name, bindingType, expr.start);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { lineBreak, skipWhiteSpace } from "../util/whitespace";
|
||||
import * as charCodes from "charcodes";
|
||||
import {
|
||||
BIND_SIMPLE_CATCH,
|
||||
BIND_CLASS,
|
||||
BIND_LEXICAL,
|
||||
BIND_VAR,
|
||||
BIND_FUNCTION,
|
||||
@@ -662,12 +662,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
clause.param = this.parseBindingAtom();
|
||||
const simple = clause.param.type === "Identifier";
|
||||
this.scope.enter(simple ? SCOPE_SIMPLE_CATCH : 0);
|
||||
this.checkLVal(
|
||||
clause.param,
|
||||
simple ? BIND_SIMPLE_CATCH : BIND_LEXICAL,
|
||||
null,
|
||||
"catch clause",
|
||||
);
|
||||
this.checkLVal(clause.param, BIND_LEXICAL, null, "catch clause");
|
||||
this.expect(tt.parenR);
|
||||
} else {
|
||||
clause.param = null;
|
||||
@@ -1056,22 +1051,6 @@ export default class StatementParser extends ExpressionParser {
|
||||
|
||||
if (isStatement) {
|
||||
node.id = this.parseFunctionId(requireId);
|
||||
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",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const oldInClassProperty = this.state.inClassProperty;
|
||||
@@ -1099,6 +1078,15 @@ export default class StatementParser extends ExpressionParser {
|
||||
);
|
||||
});
|
||||
|
||||
this.scope.exit();
|
||||
|
||||
if (isStatement && !isHangingStatement) {
|
||||
// We need to validate this _after_ parsing the function body
|
||||
// because of TypeScript body-less function declarations,
|
||||
// which shouldn't be added to the scope.
|
||||
this.checkFunctionStatementId(node);
|
||||
}
|
||||
|
||||
this.state.inClassProperty = oldInClassProperty;
|
||||
this.state.yieldPos = oldYieldPos;
|
||||
this.state.awaitPos = oldAwaitPos;
|
||||
@@ -1125,6 +1113,25 @@ export default class StatementParser extends ExpressionParser {
|
||||
this.checkYieldAwaitInDefaultParams();
|
||||
}
|
||||
|
||||
checkFunctionStatementId(node: N.Function): void {
|
||||
if (!node.id) return;
|
||||
|
||||
// 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",
|
||||
);
|
||||
}
|
||||
|
||||
// Parse a class declaration or literal (depending on the
|
||||
// `isStatement` parameter).
|
||||
|
||||
@@ -1612,7 +1619,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
if (this.match(tt.name)) {
|
||||
node.id = this.parseIdentifier();
|
||||
if (isStatement) {
|
||||
this.checkLVal(node.id, BIND_LEXICAL, undefined, "class name");
|
||||
this.checkLVal(node.id, BIND_CLASS, undefined, "class name");
|
||||
}
|
||||
} else {
|
||||
if (optionalId || !isStatement) {
|
||||
|
||||
Reference in New Issue
Block a user