Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(eslint-plugin): [no-use-before-define] opt to ignore enum #1242

42 changes: 42 additions & 0 deletions packages/eslint-plugin/docs/rules/no-use-before-define.md
Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand Down
73 changes: 48 additions & 25 deletions packages/eslint-plugin/src/rules/no-use-before-define.ts
Expand Up @@ -13,6 +13,7 @@ const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFun
function parseOptions(options: string | Config | null): Required<Config> {
let functions = true;
let classes = true;
let enums = true;
let variables = true;
let typedefs = true;

Expand All @@ -21,11 +22,12 @@ function parseOptions(options: string | Config | null): Required<Config> {
} 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 };
}

/**
Expand All @@ -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.
*/
Expand All @@ -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)
);
}

/**
Expand All @@ -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)
);
}

/**
Expand Down Expand Up @@ -147,6 +163,7 @@ function isInInitializer(
interface Config {
functions?: boolean;
classes?: boolean;
enums?: boolean;
variables?: boolean;
typedefs?: boolean;
}
Expand Down Expand Up @@ -176,6 +193,7 @@ export default util.createRule<Options, MessageIds>({
properties: {
functions: { type: 'boolean' },
classes: { type: 'boolean' },
enums: { type: 'boolean' },
variables: { type: 'boolean' },
typedefs: { type: 'boolean' },
},
Expand All @@ -189,6 +207,7 @@ export default util.createRule<Options, MessageIds>({
{
functions: true,
classes: true,
enums: true,
variables: true,
typedefs: true,
},
Expand All @@ -214,6 +233,10 @@ export default util.createRule<Options, MessageIds>({
if (isOuterVariable(variable, reference)) {
return !!options.variables;
}
if (isOuterEnum(variable, reference)) {
return !!options.enums;
}

return true;
}

Expand Down
111 changes: 111 additions & 0 deletions packages/eslint-plugin/tests/rules/no-use-before-define.test.ts
Expand Up @@ -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: [
{
Expand Down Expand Up @@ -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',
},
],
},
],
});