Skip to content

Commit

Permalink
Disallow redeclaration of enum with const enum
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Apr 26, 2019
1 parent 7274ca1 commit 89fc433
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 25 deletions.
3 changes: 2 additions & 1 deletion packages/babel-parser/src/plugins/typescript/index.js
Expand Up @@ -11,6 +11,7 @@ import {
BIND_NONE,
SCOPE_OTHER,
BIND_TS_ENUM,
BIND_TS_CONST_ENUM,
BIND_TS_TYPE,
BIND_TS_INTERFACE,
BIND_TS_FN_TYPE,
Expand Down Expand Up @@ -1156,7 +1157,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
node.id = this.parseIdentifier();
this.checkLVal(
node.id,
BIND_TS_ENUM,
isConst ? BIND_TS_CONST_ENUM : BIND_TS_ENUM,
undefined,
"typescript enum declaration",
);
Expand Down
15 changes: 13 additions & 2 deletions packages/babel-parser/src/plugins/typescript/scope.js
Expand Up @@ -4,6 +4,7 @@ import ScopeHandler, { Scope } from "../../util/scope";
import {
BIND_KIND_TYPE,
BIND_FLAGS_TS_ENUM,
BIND_FLAGS_TS_CONST_ENUM,
BIND_FLAGS_TS_EXPORT_ONLY,
BIND_KIND_VALUE,
BIND_FLAGS_CLASS,
Expand All @@ -18,6 +19,9 @@ class TypeScriptScope extends Scope {
// enums (which are also in .types)
enums: string[] = [];

// const enums (which are also in .enums and .types)
constEnums: string[] = [];

// classes (which are also in .lexical) and interface (which are also in .types)
classes: string[] = [];

Expand Down Expand Up @@ -55,6 +59,7 @@ export default class TypeScriptScopeHandler extends ScopeHandler<TypeScriptScope
scope.types.push(name);
}
if (bindingType & BIND_FLAGS_TS_ENUM) scope.enums.push(name);
if (bindingType & BIND_FLAGS_TS_CONST_ENUM) scope.constEnums.push(name);
if (bindingType & BIND_FLAGS_CLASS) scope.classes.push(name);
}

Expand All @@ -64,8 +69,14 @@ export default class TypeScriptScopeHandler extends ScopeHandler<TypeScriptScope
bindingType: BindingTypes,
): boolean {
if (scope.enums.indexOf(name) > -1) {
// Enums can be merged with other enums
return !(bindingType & BIND_FLAGS_TS_ENUM);
if (bindingType & BIND_FLAGS_TS_ENUM) {
// Enums can be merged with other enums if they are both
// const or both non-const.
const isConst = !!(bindingType & BIND_FLAGS_TS_CONST_ENUM);
const wasConst = scope.constEnums.indexOf(name) > -1;
return isConst !== wasConst;
}
return true;
}
if (bindingType & BIND_FLAGS_CLASS && scope.classes.indexOf(name) > -1) {
if (scope.lexical.indexOf(name) > -1) {
Expand Down
46 changes: 24 additions & 22 deletions packages/babel-parser/src/util/scopeflags.js
Expand Up @@ -37,37 +37,39 @@ export function functionFlags(isAsync: boolean, isGenerator: boolean) {

// These flags are meant to be _only_ used inside the Scope class (or subclasses).
// prettier-ignore
export const BIND_KIND_VALUE = 0b0000_0000_01,
BIND_KIND_TYPE = 0b0000_0000_10,
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 = 0b0000_0001_00, // Var-style binding
BIND_SCOPE_LEXICAL = 0b0000_0010_00, // Let- or const-style binding
BIND_SCOPE_FUNCTION = 0b0000_0100_00, // Function declaration
BIND_SCOPE_OUTSIDE = 0b0000_1000_00, // Special case for function names as
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 = 0b0001_0000_00,
BIND_FLAGS_CLASS = 0b0010_0000_00,
BIND_FLAGS_TS_ENUM = 0b0100_0000_00,
BIND_FLAGS_TS_EXPORT_ONLY = 0b1000_0000_00;
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,
/* = 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_NONE = 0 | 0 | 0 | BIND_FLAGS_NONE ,
BIND_OUTSIDE = BIND_KIND_VALUE | 0 | 0 | BIND_FLAGS_NONE ,

BIND_TS_NAMESPACE = BIND_TS_FN_TYPE;
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
Expand Down
@@ -0,0 +1,2 @@
const enum Foo {}
const enum Foo {}
@@ -0,0 +1,103 @@
{
"type": "File",
"start": 0,
"end": 35,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 2,
"column": 17
}
},
"program": {
"type": "Program",
"start": 0,
"end": 35,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 2,
"column": 17
}
},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "TSEnumDeclaration",
"start": 0,
"end": 17,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 17
}
},
"const": true,
"id": {
"type": "Identifier",
"start": 11,
"end": 14,
"loc": {
"start": {
"line": 1,
"column": 11
},
"end": {
"line": 1,
"column": 14
},
"identifierName": "Foo"
},
"name": "Foo"
},
"members": []
},
{
"type": "TSEnumDeclaration",
"start": 18,
"end": 35,
"loc": {
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 2,
"column": 17
}
},
"const": true,
"id": {
"type": "Identifier",
"start": 29,
"end": 32,
"loc": {
"start": {
"line": 2,
"column": 11
},
"end": {
"line": 2,
"column": 14
},
"identifierName": "Foo"
},
"name": "Foo"
},
"members": []
}
],
"directives": []
}
}
@@ -0,0 +1,2 @@
const enum X {}
enum X {}
@@ -0,0 +1,3 @@
{
"throws": "Identifier 'X' has already been declared (2:5)"
}
@@ -0,0 +1,2 @@
enum X {}
const enum X {}
@@ -0,0 +1,3 @@
{
"throws": "Identifier 'X' has already been declared (2:11)"
}

0 comments on commit 89fc433

Please sign in to comment.