Skip to content

Commit

Permalink
feat(eslint-plugin): [no-use-before-define] opt to ignore enum (#1242)
Browse files Browse the repository at this point in the history
  • Loading branch information
a-tarasyuk authored and bradzacher committed Dec 19, 2019
1 parent 2b16019 commit 6edd911
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 25 deletions.
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',
},
],
},
],
});

0 comments on commit 6edd911

Please sign in to comment.