Skip to content

Commit

Permalink
[parser] Add TS enum support to the Scope
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Mar 25, 2019
1 parent cb578d9 commit ea374e1
Show file tree
Hide file tree
Showing 24 changed files with 717 additions and 37 deletions.
8 changes: 8 additions & 0 deletions packages/babel-parser/src/plugins/typescript/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
type BindingTypes,
BIND_NONE,
SCOPE_OTHER,
BIND_TS_ENUM,
} from "../../util/scopeflags";
import TypeScriptScopeHandler from "./scope";

Expand Down Expand Up @@ -1141,6 +1142,13 @@ export default (superClass: Class<Parser>): Class<Parser> =>
): N.TsEnumDeclaration {
if (isConst) node.const = true;
node.id = this.parseIdentifier();
this.checkLVal(
node.id,
BIND_TS_ENUM,
undefined,
"typescript enum declaration",
);

this.expect(tt.braceL);
node.members = this.tsParseDelimitedList(
"EnumMembers",
Expand Down
36 changes: 35 additions & 1 deletion packages/babel-parser/src/plugins/typescript/scope.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,45 @@
import ScopeHandler, { Scope } from "../../util/scope";

class TypeScriptScope extends Scope {}
class TypeScriptScope extends Scope {
tsEnum: string = [];
}

export default class TypeScriptScopeHandler extends ScopeHandler {
scopeStack: Array<TypeScriptScope>;

createScope(flags: ScopeFlags): Scope {
return new TypeScriptScope(flags);
}

declareName(name: string, bindingType: ?BindingTypes, pos: number) {
if (bindingType === BIND_TS_ENUM) {
const scope = this.currentScope();
this.checkRedeclarationInScope(scope, name, bindingType, pos);
scope.tsEnum.push(name);
this.maybeExportDefined(scope, name);
} else {
super.declareName(...arguments);
}
}

isRedeclaredInScope(
scope: Scope,
name: string,
bindingType: ?BindingTypes,
): boolean {
if (bindingType === BIND_TS_ENUM) {
// This checks for var/let/function. An enum can redeclare an enum.
return super.isRedeclaredInScope(scope, name, BIND_LEXICAL);
}

return (
scope.tsEnum.indexOf(name) > -1 || super.isRedeclaredInScope(...arguments)
);
}

checkLocalExport(id: N.Identifier) {
if (this.scopeStack[0].tsEnum.indexOf(id.name) === -1) {
super.checkLocalExport(...arguments);
}
}
}
92 changes: 58 additions & 34 deletions packages/babel-parser/src/util/scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,55 +93,79 @@ export default class ScopeHandler {
}

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);
if (this.inModule && scope.flags & SCOPE_PROGRAM) {
this.undefinedExports.delete(name);
}
} else if (bindingType === BIND_SIMPLE_CATCH) {
const scope = this.currentScope();
scope.lexical.push(name);
} else if (bindingType === BIND_FUNCTION) {
if (
bindingType === BIND_LEXICAL ||
bindingType === BIND_SIMPLE_CATCH ||
bindingType === BIND_FUNCTION
) {
const scope = this.currentScope();
if (this.treatFunctionsAsVar) {
redeclared = scope.lexical.indexOf(name) > -1;
this.checkRedeclarationInScope(scope, name, bindingType, pos);

if (bindingType === BIND_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);

if (bindingType === BIND_LEXICAL) this.maybeExportDefined(scope, 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;
}
this.checkRedeclarationInScope(scope, name, bindingType, pos);
scope.var.push(name);

if (this.inModule && scope.flags & SCOPE_PROGRAM) {
this.undefinedExports.delete(name);
}
this.maybeExportDefined(scope, name);

if (scope.flags & SCOPE_VAR) break;
}
}
if (redeclared) {
}

maybeExportDefined(scope: Scope, name: string) {
if (this.inModule && scope.flags & SCOPE_PROGRAM) {
this.undefinedExports.delete(name);
}
}

checkRedeclarationInScope(
scope: Scope,
name: string,
bindingType: ?BindingTypes,
pos: number,
) {
if (this.isRedeclaredInScope(scope, name, bindingType, pos)) {
this.raise(pos, `Identifier '${name}' has already been declared`);
}
}

isRedeclaredInScope(
scope: Scope,
name: string,
bindingType: ?BindingTypes,
): boolean {
if (bindingType === BIND_LEXICAL) {
return (
scope.lexical.indexOf(name) > -1 ||
scope.functions.indexOf(name) > -1 ||
scope.var.indexOf(name) > -1
);
}

if (bindingType === BIND_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 &&
Expand Down
6 changes: 4 additions & 2 deletions packages/babel-parser/src/util/scopeflags.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@ export const BIND_NONE = 0, // Not a 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
BIND_OUTSIDE = 5, // Special case for function names as bound inside the function
BIND_TS_ENUM = 6; // TypeScript enums (block-scoped, but can be re-declared using var/function)

export type BindingTypes =
| typeof BIND_NONE
| typeof BIND_VAR
| typeof BIND_LEXICAL
| typeof BIND_FUNCTION
| typeof BIND_SIMPLE_CATCH
| typeof BIND_OUTSIDE;
| typeof BIND_OUTSIDE
| typeof BIND_TS_ENUM;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{ enum Foo {} }
let Foo;
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
{
"type": "File",
"start": 0,
"end": 24,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 2,
"column": 8
}
},
"program": {
"type": "Program",
"start": 0,
"end": 24,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 2,
"column": 8
}
},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "BlockStatement",
"start": 0,
"end": 15,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 15
}
},
"body": [
{
"type": "TSEnumDeclaration",
"start": 2,
"end": 13,
"loc": {
"start": {
"line": 1,
"column": 2
},
"end": {
"line": 1,
"column": 13
}
},
"id": {
"type": "Identifier",
"start": 7,
"end": 10,
"loc": {
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 10
},
"identifierName": "Foo"
},
"name": "Foo"
},
"members": []
}
],
"directives": []
},
{
"type": "VariableDeclaration",
"start": 16,
"end": 24,
"loc": {
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 2,
"column": 8
}
},
"declarations": [
{
"type": "VariableDeclarator",
"start": 20,
"end": 23,
"loc": {
"start": {
"line": 2,
"column": 4
},
"end": {
"line": 2,
"column": 7
}
},
"id": {
"type": "Identifier",
"start": 20,
"end": 23,
"loc": {
"start": {
"line": 2,
"column": 4
},
"end": {
"line": 2,
"column": 7
},
"identifierName": "Foo"
},
"name": "Foo"
},
"init": null
}
],
"kind": "let"
}
],
"directives": []
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// https://github.com/babel/babel/issues/9763

enum Test {}

export { Test as default }

0 comments on commit ea374e1

Please sign in to comment.