Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions docs/src/rules/no-use-before-define.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,18 @@ export { foo };
These references are safe even if the variables are declared later in the code.
Default is `false`.

This rule additionally supports TypeScript type syntax. The following options enable checking for the references to `type`, `interface` and `enum` declarations:

* `enums` (`boolean`) -
If it is `true`, the rule warns every reference to an `enum` before it is defined.
Defult is `true`.
* `typedefs` (`boolean`) -
If it is `true`, this rule warns every reference to a type `alias` or `interface` before its declaration. If `false`, the rule allows using type `alias`es and `interface`s before they are defined.
Default is `true`.
* `ignoreTypeReferences` (`boolean`) -
If it is `true`, rule will ignore all type references, such as in type annotations and assertions.
Default is `true`.

This rule accepts `"nofunc"` string as an option.
`"nofunc"` is the same as `{ "functions": false, "classes": true, "variables": true, "allowNamedExports": false }`.

Expand Down Expand Up @@ -350,3 +362,106 @@ const d = 1;
```

:::

### enums (TypeScript only)

Examples of **incorrect** code for the `{ "enums": true }` option:

::: incorrect

```ts
/*eslint no-use-before-define: ["error", { "enums": true }]*/

const x = Foo.FOO;

enum Foo {
FOO,
}
```

:::

Examples of **correct** code for the `{ "enums": true }` option:

::: correct

```ts
/*eslint no-use-before-define: ["error", { "enums": true }]*/

enum Foo {
FOO,
}

const x = Foo.FOO;
```

:::

### typedefs (TypeScript only)

Examples of **incorrect** code for the `{ "enums": true }` option:

::: incorrect

```ts
/*eslint no-use-before-define: ["error", { "typedefs": true }]*/

let myVar: StringOrNumber;

type StringOrNumber = string | number;

```

:::

Examples of **correct** code for the `{ "typedefs": true }` option:

::: correct

```ts
/*eslint no-use-before-define: ["error", { "typedefs": true }]*/

type StringOrNumber = string | number;

let myVar: StringOrNumber;
```

:::

### ignoreTypeReferences (TypeScript only)

Examples of **incorrect** code for the `{ "ignoreTypeReferences": false }` option:

::: incorrect

```ts
/*eslint no-use-before-define: ["error", { "ignoreTypeReferences": false }]*/

let var1: StringOrNumber;

type StringOrNumber = string | number;

let var2: Enum;

enum Enum {}
```

:::

Examples of **correct** code for the `{ "ignoreTypeReferences": false }` option:

::: correct

```ts
/*eslint no-use-before-define: ["error", { "ignoreTypeReferences": false }]*/

type StringOrNumber = string | number;

let myVar: StringOrNumber;

enum Enum {}

let var2: Enum;
```

:::
95 changes: 94 additions & 1 deletion lib/rules/no-use-before-define.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ function parseOptions(options) {
classes: true,
variables: true,
allowNamedExports: false,
enums: true,
typedefs: true,
ignoreTypeReferences: true,
};
}

Expand Down Expand Up @@ -208,6 +211,54 @@ function isEvaluatedDuringInitialization(reference) {
return false;
}

/**
* check whether the reference contains a type query.
* @param {ASTNode} node Identifier node to check.
* @returns {boolean} true if reference contains type query.
*/
function referenceContainsTypeQuery(node) {
switch (node.type) {
case "TSTypeQuery":
return true;

case "TSQualifiedName":
case "Identifier":
return referenceContainsTypeQuery(node.parent);

default:
// if we find a different node, there's no chance that we're in a TSTypeQuery
return false;
}
}

/**
* Decorators are transpiled such that the decorator is placed after the class declaration
* So it is considered safe
* @param {Variable} variable The variable to check.
* @param {Reference} reference The reference to check.
* @returns {boolean} `true` if the reference is in a class decorator.
*/
function isClassRefInClassDecorator(variable, reference) {
if (
variable.defs[0].type !== "ClassName" ||
variable.defs[0].node?.decorators?.length === 0 ||
!Array.isArray(variable.defs[0].node?.decorators)
) {
return false;
}

for (const deco of variable.defs[0].node.decorators) {
if (
reference.identifier.range[0] >= deco.range[0] &&
reference.identifier.range[1] <= deco.range[1]
) {
return true;
}
}

return false;
}

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
Expand Down Expand Up @@ -237,6 +288,9 @@ module.exports = {
classes: { type: "boolean" },
variables: { type: "boolean" },
allowNamedExports: { type: "boolean" },
enums: { type: "boolean" },
typedefs: { type: "boolean" },
ignoreTypeReferences: { type: "boolean" },
Comment on lines +294 to +296
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also update the types for this rule?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

},
additionalProperties: false,
},
Expand All @@ -250,6 +304,9 @@ module.exports = {
functions: true,
variables: true,
allowNamedExports: false,
enums: true,
typedefs: true,
ignoreTypeReferences: true,
},
],

Expand Down Expand Up @@ -310,6 +367,41 @@ module.exports = {
return false;
}

if (!options.enums && definitionType === "TSEnumName") {
return false;
}

if (!options.typedefs && definitionType === "Type") {
return false;
}
Comment on lines +377 to +379
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Testing] Removing this if statement doesn't fail any unit tests. Looks like there are missing cases for having typedefs: true without ignoreTypeReferences: false?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems when ignoreTypeReferences is true, typedefs does not report any case whether its true or false. So i add this case:

/*eslint no-use-before-define: ["error", { "ignoreTypeReferences": false, "typedefs": false, }]*/

var x: Foo = {};
interface Foo {}

please let me know if i am missing anything here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks good to me. I think there should also be testing for type aliases before a type and before a value:

type Before = typeof after;
const after = {};
type Before = After;
interface After {}


if (
options.ignoreTypeReferences &&
(referenceContainsTypeQuery(identifier) ||
identifier.parent.type === "TSTypeReference")
) {
return false;
}

// skip nested namespace aliases as variable references
if (identifier.parent.type === "TSQualifiedName") {
let currentNode = identifier.parent;

while (currentNode.type === "TSQualifiedName") {
currentNode = currentNode.left;
}

if (currentNode === identifier) {
return true;
}

return false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Testing] Switching this to return true; doesn't fail any unit tests. I think there'll need to be more testing around nested namespaces with content inside them.

}

if (isClassRefInClassDecorator(variable, reference)) {
return false;
}

return true;
}

Expand All @@ -326,7 +418,8 @@ module.exports = {
if (
reference.identifier.range[1] <
definitionIdentifier.range[1] ||
isEvaluatedDuringInitialization(reference)
(isEvaluatedDuringInitialization(reference) &&
reference.identifier.parent.type !== "TSTypeReference")
) {
context.report({
node: reference.identifier,
Expand Down
Loading
Loading