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: add allowNamedExports option to no-use-before-define #15953

Merged
merged 1 commit into from Jun 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/src/_data/further_reading_links.json
Expand Up @@ -685,4 +685,4 @@
"title": "Coding in Style",
"description": "Thomas M. Tuerke topical weblog"
}
}
}
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" }
}]
}
]
});