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:
160
packages/babel-parser/src/util/scope.js
Normal file
160
packages/babel-parser/src/util/scope.js
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
52
packages/babel-parser/src/util/scopeflags.js
Normal file
52
packages/babel-parser/src/util/scopeflags.js
Normal 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;
|
||||
Reference in New Issue
Block a user