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

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