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,17 +9,18 @@ import {
|
||||
SCOPE_SUPER,
|
||||
SCOPE_PROGRAM,
|
||||
SCOPE_VAR,
|
||||
BIND_SIMPLE_CATCH,
|
||||
BIND_LEXICAL,
|
||||
BIND_FUNCTION,
|
||||
SCOPE_CLASS,
|
||||
BIND_SCOPE_FUNCTION,
|
||||
BIND_SCOPE_VAR,
|
||||
BIND_SCOPE_LEXICAL,
|
||||
BIND_KIND_VALUE,
|
||||
type ScopeFlags,
|
||||
type BindingTypes,
|
||||
SCOPE_CLASS,
|
||||
} from "./scopeflags";
|
||||
import * as N from "../types";
|
||||
|
||||
// Start an AST node, attaching a start offset.
|
||||
class Scope {
|
||||
export class Scope {
|
||||
flags: ScopeFlags;
|
||||
// A list of var-declared names in the current lexical scope
|
||||
var: string[] = [];
|
||||
@@ -37,8 +38,8 @@ 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> = [];
|
||||
export default class ScopeHandler<IScope: Scope = Scope> {
|
||||
scopeStack: Array<IScope> = [];
|
||||
raise: raiseFunction;
|
||||
inModule: boolean;
|
||||
undefinedExports: Map<string, number> = new Map();
|
||||
@@ -70,8 +71,14 @@ export default class ScopeHandler {
|
||||
return this.treatFunctionsAsVarInScope(this.currentScope());
|
||||
}
|
||||
|
||||
createScope(flags: ScopeFlags): Scope {
|
||||
return new Scope(flags);
|
||||
}
|
||||
// This method will be overwritten by subclasses
|
||||
+createScope: (flags: ScopeFlags) => IScope;
|
||||
|
||||
enter(flags: ScopeFlags) {
|
||||
this.scopeStack.push(new Scope(flags));
|
||||
this.scopeStack.push(this.createScope(flags));
|
||||
}
|
||||
|
||||
exit() {
|
||||
@@ -81,46 +88,33 @@ export default class ScopeHandler {
|
||||
// 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 {
|
||||
treatFunctionsAsVarInScope(scope: IScope): boolean {
|
||||
return !!(
|
||||
scope.flags & SCOPE_FUNCTION ||
|
||||
(!this.inModule && scope.flags & SCOPE_PROGRAM)
|
||||
);
|
||||
}
|
||||
|
||||
declareName(name: string, bindingType: ?BindingTypes, pos: number) {
|
||||
let redeclared = false;
|
||||
declareName(name: string, bindingType: BindingTypes, pos: number) {
|
||||
let scope = this.currentScope();
|
||||
if (bindingType & BIND_SCOPE_LEXICAL || bindingType & BIND_SCOPE_FUNCTION) {
|
||||
this.checkRedeclarationInScope(scope, name, bindingType, pos);
|
||||
|
||||
if (bindingType === BIND_LEXICAL) {
|
||||
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) {
|
||||
scope.lexical.push(name);
|
||||
} else if (bindingType === BIND_FUNCTION) {
|
||||
if (this.treatFunctionsAsVar) {
|
||||
redeclared = scope.lexical.indexOf(name) > -1;
|
||||
if (bindingType & BIND_SCOPE_FUNCTION) {
|
||||
scope.functions.push(name);
|
||||
} else {
|
||||
redeclared =
|
||||
scope.lexical.indexOf(name) > -1 || scope.var.indexOf(name) > -1;
|
||||
scope.lexical.push(name);
|
||||
}
|
||||
scope.functions.push(name);
|
||||
} else {
|
||||
|
||||
if (bindingType & BIND_SCOPE_LEXICAL) {
|
||||
this.maybeExportDefined(scope, name);
|
||||
}
|
||||
} else if (bindingType & BIND_SCOPE_VAR) {
|
||||
for (let i = this.scopeStack.length - 1; i >= 0; --i) {
|
||||
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;
|
||||
}
|
||||
this.checkRedeclarationInScope(scope, name, bindingType, pos);
|
||||
scope.var.push(name);
|
||||
this.maybeExportDefined(scope, name);
|
||||
|
||||
if (scope.flags & SCOPE_VAR) break;
|
||||
}
|
||||
@@ -128,11 +122,56 @@ export default class ScopeHandler {
|
||||
if (this.inModule && scope.flags & SCOPE_PROGRAM) {
|
||||
this.undefinedExports.delete(name);
|
||||
}
|
||||
if (redeclared) {
|
||||
}
|
||||
|
||||
maybeExportDefined(scope: IScope, name: string) {
|
||||
if (this.inModule && scope.flags & SCOPE_PROGRAM) {
|
||||
this.undefinedExports.delete(name);
|
||||
}
|
||||
}
|
||||
|
||||
checkRedeclarationInScope(
|
||||
scope: IScope,
|
||||
name: string,
|
||||
bindingType: BindingTypes,
|
||||
pos: number,
|
||||
) {
|
||||
if (this.isRedeclaredInScope(scope, name, bindingType)) {
|
||||
this.raise(pos, `Identifier '${name}' has already been declared`);
|
||||
}
|
||||
}
|
||||
|
||||
isRedeclaredInScope(
|
||||
scope: IScope,
|
||||
name: string,
|
||||
bindingType: BindingTypes,
|
||||
): boolean {
|
||||
if (!(bindingType & BIND_KIND_VALUE)) return false;
|
||||
|
||||
if (bindingType & BIND_SCOPE_LEXICAL) {
|
||||
return (
|
||||
scope.lexical.indexOf(name) > -1 ||
|
||||
scope.functions.indexOf(name) > -1 ||
|
||||
scope.var.indexOf(name) > -1
|
||||
);
|
||||
}
|
||||
|
||||
if (bindingType & BIND_SCOPE_FUNCTION) {
|
||||
return (
|
||||
scope.lexical.indexOf(name) > -1 ||
|
||||
(!this.treatFunctionsAsVarInScope(scope) &&
|
||||
scope.var.indexOf(name) > -1)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
(scope.lexical.indexOf(name) > -1 &&
|
||||
!(scope.flags & SCOPE_SIMPLE_CATCH && scope.lexical[0] === name)) ||
|
||||
(!this.treatFunctionsAsVarInScope(scope) &&
|
||||
scope.functions.indexOf(name) > -1)
|
||||
);
|
||||
}
|
||||
|
||||
checkLocalExport(id: N.Identifier) {
|
||||
if (
|
||||
this.scopeStack[0].lexical.indexOf(id.name) === -1 &&
|
||||
@@ -146,12 +185,12 @@ export default class ScopeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
currentScope(): Scope {
|
||||
currentScope(): IScope {
|
||||
return this.scopeStack[this.scopeStack.length - 1];
|
||||
}
|
||||
|
||||
// $FlowIgnore
|
||||
currentVarScope(): Scope {
|
||||
currentVarScope(): IScope {
|
||||
for (let i = this.scopeStack.length - 1; ; i--) {
|
||||
const scope = this.scopeStack[i];
|
||||
if (scope.flags & SCOPE_VAR) {
|
||||
@@ -162,7 +201,7 @@ export default class ScopeHandler {
|
||||
|
||||
// Could be useful for `this`, `new.target`, `super()`, `super.property`, and `super[property]`.
|
||||
// $FlowIgnore
|
||||
currentThisScope(): Scope {
|
||||
currentThisScope(): IScope {
|
||||
for (let i = this.scopeStack.length - 1; ; i--) {
|
||||
const scope = this.scopeStack[i];
|
||||
if (
|
||||
|
||||
@@ -35,18 +35,51 @@ export function functionFlags(isAsync: boolean, isGenerator: boolean) {
|
||||
);
|
||||
}
|
||||
|
||||
// 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
|
||||
// These flags are meant to be _only_ used inside the Scope class (or subclasses).
|
||||
// prettier-ignore
|
||||
export const BIND_KIND_VALUE = 0b00000_0000_01,
|
||||
BIND_KIND_TYPE = 0b00000_0000_10,
|
||||
// Used in checkLVal and declareName to determine the type of a binding
|
||||
BIND_SCOPE_VAR = 0b00000_0001_00, // Var-style binding
|
||||
BIND_SCOPE_LEXICAL = 0b00000_0010_00, // Let- or const-style binding
|
||||
BIND_SCOPE_FUNCTION = 0b00000_0100_00, // Function declaration
|
||||
BIND_SCOPE_OUTSIDE = 0b00000_1000_00, // Special case for function names as
|
||||
// bound inside the function
|
||||
// Misc flags
|
||||
BIND_FLAGS_NONE = 0b00001_0000_00,
|
||||
BIND_FLAGS_CLASS = 0b00010_0000_00,
|
||||
BIND_FLAGS_TS_ENUM = 0b00100_0000_00,
|
||||
BIND_FLAGS_TS_CONST_ENUM = 0b01000_0000_00,
|
||||
BIND_FLAGS_TS_EXPORT_ONLY = 0b10000_0000_00;
|
||||
|
||||
// These flags are meant to be _only_ used by Scope consumers
|
||||
// prettier-ignore
|
||||
/* = is value? | is type? | scope | misc flags */
|
||||
export const BIND_CLASS = BIND_KIND_VALUE | BIND_KIND_TYPE | BIND_SCOPE_LEXICAL | BIND_FLAGS_CLASS ,
|
||||
BIND_LEXICAL = BIND_KIND_VALUE | 0 | BIND_SCOPE_LEXICAL | 0 ,
|
||||
BIND_VAR = BIND_KIND_VALUE | 0 | BIND_SCOPE_VAR | 0 ,
|
||||
BIND_FUNCTION = BIND_KIND_VALUE | 0 | BIND_SCOPE_FUNCTION | 0 ,
|
||||
BIND_TS_INTERFACE = 0 | BIND_KIND_TYPE | 0 | BIND_FLAGS_CLASS ,
|
||||
BIND_TS_TYPE = 0 | BIND_KIND_TYPE | 0 | 0 ,
|
||||
BIND_TS_ENUM = BIND_KIND_VALUE | BIND_KIND_TYPE | BIND_SCOPE_LEXICAL | BIND_FLAGS_TS_ENUM,
|
||||
BIND_TS_FN_TYPE = 0 | 0 | 0 | BIND_FLAGS_TS_EXPORT_ONLY,
|
||||
// These bindings don't introduce anything in the scope. They are used for assignments and
|
||||
// function expressions IDs.
|
||||
BIND_NONE = 0 | 0 | 0 | BIND_FLAGS_NONE ,
|
||||
BIND_OUTSIDE = BIND_KIND_VALUE | 0 | 0 | BIND_FLAGS_NONE ,
|
||||
|
||||
BIND_TS_CONST_ENUM = BIND_TS_ENUM | BIND_FLAGS_TS_CONST_ENUM,
|
||||
BIND_TS_NAMESPACE = BIND_TS_FN_TYPE;
|
||||
|
||||
export type BindingTypes =
|
||||
| typeof BIND_NONE
|
||||
| typeof BIND_OUTSIDE
|
||||
| typeof BIND_VAR
|
||||
| typeof BIND_LEXICAL
|
||||
| typeof BIND_CLASS
|
||||
| typeof BIND_FUNCTION
|
||||
| typeof BIND_SIMPLE_CATCH
|
||||
| typeof BIND_OUTSIDE;
|
||||
| typeof BIND_TS_INTERFACE
|
||||
| typeof BIND_TS_TYPE
|
||||
| typeof BIND_TS_ENUM
|
||||
| typeof BIND_TS_FN_TYPE
|
||||
| typeof BIND_TS_NAMESPACE;
|
||||
|
||||
Reference in New Issue
Block a user