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 b93bbc7 commit be8e163
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, BIND_NONE,
SCOPE_OTHER, SCOPE_OTHER,
BIND_TS_ENUM, BIND_TS_ENUM,
BIND_TS_CONST_ENUM,
BIND_TS_TYPE, BIND_TS_TYPE,
BIND_TS_INTERFACE, BIND_TS_INTERFACE,
BIND_TS_FN_TYPE, BIND_TS_FN_TYPE,
Expand Down Expand Up @@ -1123,7 +1124,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
node.id = this.parseIdentifier(); node.id = this.parseIdentifier();
this.checkLVal( this.checkLVal(
node.id, node.id,
BIND_TS_ENUM, isConst ? BIND_TS_CONST_ENUM : BIND_TS_ENUM,
undefined, undefined,
"typescript enum declaration", "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 { import {
BIND_KIND_TYPE, BIND_KIND_TYPE,
BIND_FLAGS_TS_ENUM, BIND_FLAGS_TS_ENUM,
BIND_FLAGS_TS_CONST_ENUM,
BIND_FLAGS_TS_EXPORT_ONLY, BIND_FLAGS_TS_EXPORT_ONLY,
BIND_KIND_VALUE, BIND_KIND_VALUE,
BIND_FLAGS_CLASS, BIND_FLAGS_CLASS,
Expand All @@ -18,6 +19,9 @@ class TypeScriptScope extends Scope {
// enums (which are also in .types) // enums (which are also in .types)
enums: string[] = []; 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 (which are also in .lexical) and interface (which are also in .types)
classes: string[] = []; classes: string[] = [];


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


Expand All @@ -64,8 +69,14 @@ export default class TypeScriptScopeHandler extends ScopeHandler<TypeScriptScope
bindingType: BindingTypes, bindingType: BindingTypes,
): boolean { ): boolean {
if (scope.enums.indexOf(name) > -1) { if (scope.enums.indexOf(name) > -1) {
// Enums can be merged with other enums if (bindingType & BIND_FLAGS_TS_ENUM) {
return !(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 (bindingType & BIND_FLAGS_CLASS && scope.classes.indexOf(name) > -1) {
if (scope.lexical.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). // These flags are meant to be _only_ used inside the Scope class (or subclasses).
// prettier-ignore // prettier-ignore
export const BIND_KIND_VALUE = 0b0000_0000_01, export const BIND_KIND_VALUE = 0b00000_0000_01,
BIND_KIND_TYPE = 0b0000_0000_10, BIND_KIND_TYPE = 0b00000_0000_10,
// Used in checkLVal and declareName to determine the type of a binding // Used in checkLVal and declareName to determine the type of a binding
BIND_SCOPE_VAR = 0b0000_0001_00, // Var-style binding BIND_SCOPE_VAR = 0b00000_0001_00, // Var-style binding
BIND_SCOPE_LEXICAL = 0b0000_0010_00, // Let- or const-style binding BIND_SCOPE_LEXICAL = 0b00000_0010_00, // Let- or const-style binding
BIND_SCOPE_FUNCTION = 0b0000_0100_00, // Function declaration BIND_SCOPE_FUNCTION = 0b00000_0100_00, // Function declaration
BIND_SCOPE_OUTSIDE = 0b0000_1000_00, // Special case for function names as BIND_SCOPE_OUTSIDE = 0b00000_1000_00, // Special case for function names as
// bound inside the function // bound inside the function
// Misc flags // Misc flags
BIND_FLAGS_NONE = 0b0001_0000_00, BIND_FLAGS_NONE = 0b00001_0000_00,
BIND_FLAGS_CLASS = 0b0010_0000_00, BIND_FLAGS_CLASS = 0b00010_0000_00,
BIND_FLAGS_TS_ENUM = 0b0100_0000_00, BIND_FLAGS_TS_ENUM = 0b00100_0000_00,
BIND_FLAGS_TS_EXPORT_ONLY = 0b1000_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 // These flags are meant to be _only_ used by Scope consumers
// prettier-ignore // prettier-ignore
/* = is value? | is type? | scope | misc flags */ /* = is value? | is type? | scope | misc flags */
export const BIND_CLASS = BIND_KIND_VALUE | BIND_KIND_TYPE | BIND_SCOPE_LEXICAL | BIND_FLAGS_CLASS , 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_LEXICAL = BIND_KIND_VALUE | 0 | BIND_SCOPE_LEXICAL | 0 ,
BIND_VAR = BIND_KIND_VALUE | 0 | BIND_SCOPE_VAR | 0 , BIND_VAR = BIND_KIND_VALUE | 0 | BIND_SCOPE_VAR | 0 ,
BIND_FUNCTION = BIND_KIND_VALUE | 0 | BIND_SCOPE_FUNCTION | 0 , BIND_FUNCTION = BIND_KIND_VALUE | 0 | BIND_SCOPE_FUNCTION | 0 ,
BIND_TS_INTERFACE = 0 | BIND_KIND_TYPE | 0 | BIND_FLAGS_CLASS , BIND_TS_INTERFACE = 0 | BIND_KIND_TYPE | 0 | BIND_FLAGS_CLASS ,
BIND_TS_TYPE = 0 | BIND_KIND_TYPE | 0 | 0 , 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_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, 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 // These bindings don't introduce anything in the scope. They are used for assignments and
// function expressions IDs. // function expressions IDs.
BIND_NONE = 0 | 0 | 0 | BIND_FLAGS_NONE , BIND_NONE = 0 | 0 | 0 | BIND_FLAGS_NONE ,
BIND_OUTSIDE = BIND_KIND_VALUE | 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 = export type BindingTypes =
| typeof BIND_NONE | 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 be8e163

Please sign in to comment.