Skip to content

Commit

Permalink
feat: add allowNamedExports option to no-use-before-define (#15953)
Browse files Browse the repository at this point in the history
Fixes #15710
  • Loading branch information
mdjermanovic committed Jun 3, 2022
1 parent f0bb609 commit f6d7920
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 6 deletions.
2 changes: 1 addition & 1 deletion docs/src/_data/further_reading_links.json
Expand Up @@ -692,4 +692,4 @@
"title": "Exponentiation (**) - JavaScript | MDN",
"description": "The exponentiation operator (**) returns the result of raising the first operand to the power of the second operand. It is equivalent to Math.pow, except it also accepts BigInts as operands."
}
}
}
54 changes: 52 additions & 2 deletions docs/src/rules/no-use-before-define.md
Expand Up @@ -60,6 +60,9 @@ var b = 1;
}
}
}

export { foo };
const foo = 1;
```

Examples of **correct** code for this rule:
Expand Down Expand Up @@ -109,13 +112,21 @@ function g() {
}
}
}

const foo = 1;
export { foo };
```

## Options

```json
{
"no-use-before-define": ["error", { "functions": true, "classes": true, "variables": true }]
"no-use-before-define": ["error", {
"functions": true,
"classes": true,
"variables": true,
"allowNamedExports": false
}]
}
```

Expand All @@ -136,9 +147,13 @@ function g() {
If this is `true`, the rule warns every reference to a variable before the variable declaration.
Otherwise, the rule ignores a reference if the declaration is in an upper scope, while still reporting the reference if it's in the same scope as the declaration.
Default is `true`.
* `allowNamedExports` (`boolean`) -
If this flag is set to `true`, the rule always allows references in `export {};` declarations.
These references are safe even if the variables are declared later in the code.
Default is `false`.

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

### functions

Expand Down Expand Up @@ -267,3 +282,38 @@ const g = function() {}
const foo = 1;
}
```

### allowNamedExports

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

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

export { a, b, f, C };

const a = 1;

let b;

function f () {}

class C {}
```

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

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

export default a;
const a = 1;

const b = c;
export const c = 1;

export function foo() {
return d;
}
const d = 1;
```
17 changes: 15 additions & 2 deletions lib/rules/no-use-before-define.js
Expand Up @@ -21,16 +21,18 @@ function parseOptions(options) {
let functions = true;
let classes = true;
let variables = true;
let allowNamedExports = false;

if (typeof options === "string") {
functions = (options !== "nofunc");
} else if (typeof options === "object" && options !== null) {
functions = options.functions !== false;
classes = options.classes !== false;
variables = options.variables !== false;
allowNamedExports = !!options.allowNamedExports;
}

return { functions, classes, variables };
return { functions, classes, variables, allowNamedExports };
}

/**
Expand Down Expand Up @@ -240,7 +242,8 @@ module.exports = {
properties: {
functions: { type: "boolean" },
classes: { type: "boolean" },
variables: { type: "boolean" }
variables: { type: "boolean" },
allowNamedExports: { type: "boolean" }
},
additionalProperties: false
}
Expand Down Expand Up @@ -273,6 +276,16 @@ module.exports = {
return false;
}

const { identifier } = reference;

if (
options.allowNamedExports &&
identifier.parent.type === "ExportSpecifier" &&
identifier.parent.local === identifier
) {
return false;
}

const variable = reference.resolved;

if (!variable || variable.defs.length === 0) {
Expand Down
153 changes: 152 additions & 1 deletion tests/lib/rules/no-use-before-define.js
Expand Up @@ -209,6 +209,38 @@ ruleTester.run("no-use-before-define", rule, {
{
code: "const C = class C { static { C.x; } }",
parserOptions: { ecmaVersion: 2022 }
},

// "allowNamedExports" option
{
code: "export { a }; const a = 1;",
options: [{ allowNamedExports: true }],
parserOptions: { ecmaVersion: 2015, sourceType: "module" }
},
{
code: "export { a as b }; const a = 1;",
options: [{ allowNamedExports: true }],
parserOptions: { ecmaVersion: 2015, sourceType: "module" }
},
{
code: "export { a, b }; let a, b;",
options: [{ allowNamedExports: true }],
parserOptions: { ecmaVersion: 2015, sourceType: "module" }
},
{
code: "export { a }; var a;",
options: [{ allowNamedExports: true }],
parserOptions: { ecmaVersion: 2015, sourceType: "module" }
},
{
code: "export { f }; function f() {}",
options: [{ allowNamedExports: true }],
parserOptions: { ecmaVersion: 2015, sourceType: "module" }
},
{
code: "export { C }; class C {}",
options: [{ allowNamedExports: true }],
parserOptions: { ecmaVersion: 2015, sourceType: "module" }
}
],
invalid: [
Expand Down Expand Up @@ -1091,7 +1123,7 @@ ruleTester.run("no-use-before-define", rule, {
messageId: "usedBeforeDefined",
data: { name: "a" }
}]
}
},

/*
* TODO(mdjermanovic): Add the following test cases once https://github.com/eslint/eslint-scope/issues/59 gets fixed:
Expand Down Expand Up @@ -1123,5 +1155,124 @@ ruleTester.run("no-use-before-define", rule, {
* }]
* }
*/

// "allowNamedExports" option
{
code: "export { a }; const a = 1;",
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
errors: [{
messageId: "usedBeforeDefined",
data: { name: "a" }
}]
},
{
code: "export { a }; const a = 1;",
options: [{}],
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
errors: [{
messageId: "usedBeforeDefined",
data: { name: "a" }
}]
},
{
code: "export { a }; const a = 1;",
options: [{ allowNamedExports: false }],
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
errors: [{
messageId: "usedBeforeDefined",
data: { name: "a" }
}]
},
{
code: "export { a }; const a = 1;",
options: ["nofunc"],
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
errors: [{
messageId: "usedBeforeDefined",
data: { name: "a" }
}]
},
{
code: "export { a as b }; const a = 1;",
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
errors: [{
messageId: "usedBeforeDefined",
data: { name: "a" }
}]
},
{
code: "export { a, b }; let a, b;",
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
errors: [
{
messageId: "usedBeforeDefined",
data: { name: "a" }
},
{
messageId: "usedBeforeDefined",
data: { name: "b" }
}
]
},
{
code: "export { a }; var a;",
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
errors: [{
messageId: "usedBeforeDefined",
data: { name: "a" }
}]
},
{
code: "export { f }; function f() {}",
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
errors: [{
messageId: "usedBeforeDefined",
data: { name: "f" }
}]
},
{
code: "export { C }; class C {}",
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
errors: [{
messageId: "usedBeforeDefined",
data: { name: "C" }
}]
},
{
code: "export const foo = a; const a = 1;",
options: [{ allowNamedExports: true }],
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
errors: [{
messageId: "usedBeforeDefined",
data: { name: "a" }
}]
},
{
code: "export default a; const a = 1;",
options: [{ allowNamedExports: true }],
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
errors: [{
messageId: "usedBeforeDefined",
data: { name: "a" }
}]
},
{
code: "export function foo() { return a; }; const a = 1;",
options: [{ allowNamedExports: true }],
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
errors: [{
messageId: "usedBeforeDefined",
data: { name: "a" }
}]
},
{
code: "export class C { foo() { return a; } }; const a = 1;",
options: [{ allowNamedExports: true }],
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
errors: [{
messageId: "usedBeforeDefined",
data: { name: "a" }
}]
}
]
});

0 comments on commit f6d7920

Please sign in to comment.