diff --git a/packages/eslint-plugin/docs/rules/no-use-before-define.md b/packages/eslint-plugin/docs/rules/no-use-before-define.md index 5c2c9fd47ff..64e7ee73230 100644 --- a/packages/eslint-plugin/docs/rules/no-use-before-define.md +++ b/packages/eslint-plugin/docs/rules/no-use-before-define.md @@ -83,6 +83,11 @@ let myVar: StringOrNumber; Otherwise, ignores those references if the declaration is in upper function scopes. Class declarations are not hoisted, so it might be danger. Default is `true`. +- `enums` (`boolean`) - + The flag which shows whether or not this rule checks enum declarations of upper scopes. + If this is `true`, this rule warns every reference to a enum before the enum declaration. + Otherwise, ignores those references. + Default is `true`. - `variables` (`boolean`) - This flag determines whether or not the rule checks variable declarations in upper scopes. If this is `true`, the rule warns every reference to a variable before the variable declaration. @@ -134,6 +139,43 @@ function foo() { class A {} ``` +### `enums` + +Examples of **incorrect** code for the `{ "enums": true }` option: + +```ts +/*eslint no-use-before-define: ["error", { "enums": true }]*/ + +function foo() { + return Foo.FOO; +} + +class Test { + foo() { + return Foo.FOO; + } +} + +enum Foo { + FOO, + BAR, +} +``` + +Examples of **correct** code for the `{ "enums": false }` option: + +```ts +/*eslint no-use-before-define: ["error", { "enums": false }]*/ + +function foo() { + return Foo.FOO; +} + +enum Foo { + FOO, +} +``` + ### `variables` Examples of **incorrect** code for the `{ "variables": false }` option: diff --git a/packages/eslint-plugin/src/rules/no-use-before-define.ts b/packages/eslint-plugin/src/rules/no-use-before-define.ts index e40da703893..5dad2a490d9 100644 --- a/packages/eslint-plugin/src/rules/no-use-before-define.ts +++ b/packages/eslint-plugin/src/rules/no-use-before-define.ts @@ -13,6 +13,7 @@ const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFun function parseOptions(options: string | Config | null): Required { let functions = true; let classes = true; + let enums = true; let variables = true; let typedefs = true; @@ -21,11 +22,12 @@ function parseOptions(options: string | Config | null): Required { } else if (typeof options === 'object' && options !== null) { functions = options.functions !== false; classes = options.classes !== false; + enums = options.enums !== false; variables = options.variables !== false; typedefs = options.typedefs !== false; } - return { functions, classes, variables, typedefs }; + return { functions, classes, enums, variables, typedefs }; } /** @@ -35,6 +37,23 @@ function isTopLevelScope(scope: TSESLint.Scope.Scope): boolean { return scope.type === 'module' || scope.type === 'global'; } +/** + * Checks whether or not a given variable declaration in an upper scope. + */ +function isOuterScope( + variable: TSESLint.Scope.Variable, + reference: TSESLint.Scope.Reference, +): boolean { + if (variable.scope.variableScope === reference.from.variableScope) { + // allow the same scope only if it's the top level global/module scope + if (!isTopLevelScope(variable.scope.variableScope)) { + return false; + } + } + + return true; +} + /** * Checks whether or not a given variable is a function declaration. */ @@ -43,24 +62,30 @@ function isFunction(variable: TSESLint.Scope.Variable): boolean { } /** - * Checks whether or not a given variable is a class declaration in an upper function scope. + * Checks whether or not a given variable is a enum declaration in an upper function scope. */ -function isOuterClass( +function isOuterEnum( variable: TSESLint.Scope.Variable, reference: TSESLint.Scope.Reference, ): boolean { - if (variable.defs[0].type !== 'ClassName') { - return false; - } + const node = variable.defs[0].node as TSESTree.Node; - if (variable.scope.variableScope === reference.from.variableScope) { - // allow the same scope only if it's the top level global/module scope - if (!isTopLevelScope(variable.scope.variableScope)) { - return false; - } - } + return ( + node.type === AST_NODE_TYPES.TSEnumDeclaration && + isOuterScope(variable, reference) + ); +} - return true; +/** + * Checks whether or not a given variable is a class declaration in an upper function scope. + */ +function isOuterClass( + variable: TSESLint.Scope.Variable, + reference: TSESLint.Scope.Reference, +): boolean { + return ( + variable.defs[0].type === 'ClassName' && isOuterScope(variable, reference) + ); } /** @@ -70,18 +95,9 @@ function isOuterVariable( variable: TSESLint.Scope.Variable, reference: TSESLint.Scope.Reference, ): boolean { - if (variable.defs[0].type !== 'Variable') { - return false; - } - - if (variable.scope.variableScope === reference.from.variableScope) { - // allow the same scope only if it's the top level global/module scope - if (!isTopLevelScope(variable.scope.variableScope)) { - return false; - } - } - - return true; + return ( + variable.defs[0].type === 'Variable' && isOuterScope(variable, reference) + ); } /** @@ -147,6 +163,7 @@ function isInInitializer( interface Config { functions?: boolean; classes?: boolean; + enums?: boolean; variables?: boolean; typedefs?: boolean; } @@ -176,6 +193,7 @@ export default util.createRule({ properties: { functions: { type: 'boolean' }, classes: { type: 'boolean' }, + enums: { type: 'boolean' }, variables: { type: 'boolean' }, typedefs: { type: 'boolean' }, }, @@ -189,6 +207,7 @@ export default util.createRule({ { functions: true, classes: true, + enums: true, variables: true, typedefs: true, }, @@ -214,6 +233,10 @@ export default util.createRule({ if (isOuterVariable(variable, reference)) { return !!options.variables; } + if (isOuterEnum(variable, reference)) { + return !!options.enums; + } + return true; } diff --git a/packages/eslint-plugin/tests/rules/no-use-before-define.test.ts b/packages/eslint-plugin/tests/rules/no-use-before-define.test.ts index a96364d2366..8b375aef075 100644 --- a/packages/eslint-plugin/tests/rules/no-use-before-define.test.ts +++ b/packages/eslint-plugin/tests/rules/no-use-before-define.test.ts @@ -229,6 +229,54 @@ interface Foo { } const bar = 'blah' `, + { + code: ` +function foo(): Foo { + return Foo.FOO; +} + +enum Foo { + FOO, +} + `, + options: [ + { + enums: false, + }, + ], + }, + { + code: ` +let foo: Foo; + +enum Foo { + FOO, +} + `, + options: [ + { + enums: false, + }, + ], + }, + { + code: ` +class Test { + foo(args: Foo): Foo { + return Foo.FOO; + } +} + +enum Foo { + FOO, +} + `, + options: [ + { + enums: false, + }, + ], + }, ], invalid: [ { @@ -840,5 +888,68 @@ var bar; }, ], }, + { + code: ` +class Test { + foo(args: Foo): Foo { + return Foo.FOO; + } +} + +enum Foo { + FOO, +} + `, + options: [ + { + enums: true, + }, + ], + errors: [ + { + messageId: 'noUseBeforeDefine', + }, + ], + }, + { + code: ` +function foo(): Foo { + return Foo.FOO; +} + +enum Foo { + FOO, +} + `, + options: [ + { + enums: true, + }, + ], + errors: [ + { + messageId: 'noUseBeforeDefine', + }, + ], + }, + { + code: ` +const foo = Foo.Foo; + +enum Foo { + FOO, +} + `, + options: [ + { + enums: true, + }, + ], + errors: [ + { + messageId: 'noUseBeforeDefine', + }, + ], + }, ], });