Skip to content

Commit f6d7920

Browse files
authored
feat: add allowNamedExports option to no-use-before-define (#15953)
Fixes #15710
1 parent f0bb609 commit f6d7920

4 files changed

Lines changed: 220 additions & 6 deletions

File tree

docs/src/_data/further_reading_links.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -692,4 +692,4 @@
692692
"title": "Exponentiation (**) - JavaScript | MDN",
693693
"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."
694694
}
695-
}
695+
}

docs/src/rules/no-use-before-define.md

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ var b = 1;
6060
}
6161
}
6262
}
63+
64+
export { foo };
65+
const foo = 1;
6366
```
6467

6568
Examples of **correct** code for this rule:
@@ -109,13 +112,21 @@ function g() {
109112
}
110113
}
111114
}
115+
116+
const foo = 1;
117+
export { foo };
112118
```
113119

114120
## Options
115121

116122
```json
117123
{
118-
"no-use-before-define": ["error", { "functions": true, "classes": true, "variables": true }]
124+
"no-use-before-define": ["error", {
125+
"functions": true,
126+
"classes": true,
127+
"variables": true,
128+
"allowNamedExports": false
129+
}]
119130
}
120131
```
121132

@@ -136,9 +147,13 @@ function g() {
136147
If this is `true`, the rule warns every reference to a variable before the variable declaration.
137148
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.
138149
Default is `true`.
150+
* `allowNamedExports` (`boolean`) -
151+
If this flag is set to `true`, the rule always allows references in `export {};` declarations.
152+
These references are safe even if the variables are declared later in the code.
153+
Default is `false`.
139154

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

143158
### functions
144159

@@ -267,3 +282,38 @@ const g = function() {}
267282
const foo = 1;
268283
}
269284
```
285+
286+
### allowNamedExports
287+
288+
Examples of **correct** code for the `{ "allowNamedExports": true }` option:
289+
290+
```js
291+
/*eslint no-use-before-define: ["error", { "allowNamedExports": true }]*/
292+
293+
export { a, b, f, C };
294+
295+
const a = 1;
296+
297+
let b;
298+
299+
function f () {}
300+
301+
class C {}
302+
```
303+
304+
Examples of **incorrect** code for the `{ "allowNamedExports": true }` option:
305+
306+
```js
307+
/*eslint no-use-before-define: ["error", { "allowNamedExports": true }]*/
308+
309+
export default a;
310+
const a = 1;
311+
312+
const b = c;
313+
export const c = 1;
314+
315+
export function foo() {
316+
return d;
317+
}
318+
const d = 1;
319+
```

lib/rules/no-use-before-define.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,18 @@ function parseOptions(options) {
2121
let functions = true;
2222
let classes = true;
2323
let variables = true;
24+
let allowNamedExports = false;
2425

2526
if (typeof options === "string") {
2627
functions = (options !== "nofunc");
2728
} else if (typeof options === "object" && options !== null) {
2829
functions = options.functions !== false;
2930
classes = options.classes !== false;
3031
variables = options.variables !== false;
32+
allowNamedExports = !!options.allowNamedExports;
3133
}
3234

33-
return { functions, classes, variables };
35+
return { functions, classes, variables, allowNamedExports };
3436
}
3537

3638
/**
@@ -240,7 +242,8 @@ module.exports = {
240242
properties: {
241243
functions: { type: "boolean" },
242244
classes: { type: "boolean" },
243-
variables: { type: "boolean" }
245+
variables: { type: "boolean" },
246+
allowNamedExports: { type: "boolean" }
244247
},
245248
additionalProperties: false
246249
}
@@ -273,6 +276,16 @@ module.exports = {
273276
return false;
274277
}
275278

279+
const { identifier } = reference;
280+
281+
if (
282+
options.allowNamedExports &&
283+
identifier.parent.type === "ExportSpecifier" &&
284+
identifier.parent.local === identifier
285+
) {
286+
return false;
287+
}
288+
276289
const variable = reference.resolved;
277290

278291
if (!variable || variable.defs.length === 0) {

tests/lib/rules/no-use-before-define.js

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,38 @@ ruleTester.run("no-use-before-define", rule, {
209209
{
210210
code: "const C = class C { static { C.x; } }",
211211
parserOptions: { ecmaVersion: 2022 }
212+
},
213+
214+
// "allowNamedExports" option
215+
{
216+
code: "export { a }; const a = 1;",
217+
options: [{ allowNamedExports: true }],
218+
parserOptions: { ecmaVersion: 2015, sourceType: "module" }
219+
},
220+
{
221+
code: "export { a as b }; const a = 1;",
222+
options: [{ allowNamedExports: true }],
223+
parserOptions: { ecmaVersion: 2015, sourceType: "module" }
224+
},
225+
{
226+
code: "export { a, b }; let a, b;",
227+
options: [{ allowNamedExports: true }],
228+
parserOptions: { ecmaVersion: 2015, sourceType: "module" }
229+
},
230+
{
231+
code: "export { a }; var a;",
232+
options: [{ allowNamedExports: true }],
233+
parserOptions: { ecmaVersion: 2015, sourceType: "module" }
234+
},
235+
{
236+
code: "export { f }; function f() {}",
237+
options: [{ allowNamedExports: true }],
238+
parserOptions: { ecmaVersion: 2015, sourceType: "module" }
239+
},
240+
{
241+
code: "export { C }; class C {}",
242+
options: [{ allowNamedExports: true }],
243+
parserOptions: { ecmaVersion: 2015, sourceType: "module" }
212244
}
213245
],
214246
invalid: [
@@ -1091,7 +1123,7 @@ ruleTester.run("no-use-before-define", rule, {
10911123
messageId: "usedBeforeDefined",
10921124
data: { name: "a" }
10931125
}]
1094-
}
1126+
},
10951127

10961128
/*
10971129
* TODO(mdjermanovic): Add the following test cases once https://github.com/eslint/eslint-scope/issues/59 gets fixed:
@@ -1123,5 +1155,124 @@ ruleTester.run("no-use-before-define", rule, {
11231155
* }]
11241156
* }
11251157
*/
1158+
1159+
// "allowNamedExports" option
1160+
{
1161+
code: "export { a }; const a = 1;",
1162+
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
1163+
errors: [{
1164+
messageId: "usedBeforeDefined",
1165+
data: { name: "a" }
1166+
}]
1167+
},
1168+
{
1169+
code: "export { a }; const a = 1;",
1170+
options: [{}],
1171+
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
1172+
errors: [{
1173+
messageId: "usedBeforeDefined",
1174+
data: { name: "a" }
1175+
}]
1176+
},
1177+
{
1178+
code: "export { a }; const a = 1;",
1179+
options: [{ allowNamedExports: false }],
1180+
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
1181+
errors: [{
1182+
messageId: "usedBeforeDefined",
1183+
data: { name: "a" }
1184+
}]
1185+
},
1186+
{
1187+
code: "export { a }; const a = 1;",
1188+
options: ["nofunc"],
1189+
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
1190+
errors: [{
1191+
messageId: "usedBeforeDefined",
1192+
data: { name: "a" }
1193+
}]
1194+
},
1195+
{
1196+
code: "export { a as b }; const a = 1;",
1197+
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
1198+
errors: [{
1199+
messageId: "usedBeforeDefined",
1200+
data: { name: "a" }
1201+
}]
1202+
},
1203+
{
1204+
code: "export { a, b }; let a, b;",
1205+
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
1206+
errors: [
1207+
{
1208+
messageId: "usedBeforeDefined",
1209+
data: { name: "a" }
1210+
},
1211+
{
1212+
messageId: "usedBeforeDefined",
1213+
data: { name: "b" }
1214+
}
1215+
]
1216+
},
1217+
{
1218+
code: "export { a }; var a;",
1219+
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
1220+
errors: [{
1221+
messageId: "usedBeforeDefined",
1222+
data: { name: "a" }
1223+
}]
1224+
},
1225+
{
1226+
code: "export { f }; function f() {}",
1227+
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
1228+
errors: [{
1229+
messageId: "usedBeforeDefined",
1230+
data: { name: "f" }
1231+
}]
1232+
},
1233+
{
1234+
code: "export { C }; class C {}",
1235+
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
1236+
errors: [{
1237+
messageId: "usedBeforeDefined",
1238+
data: { name: "C" }
1239+
}]
1240+
},
1241+
{
1242+
code: "export const foo = a; const a = 1;",
1243+
options: [{ allowNamedExports: true }],
1244+
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
1245+
errors: [{
1246+
messageId: "usedBeforeDefined",
1247+
data: { name: "a" }
1248+
}]
1249+
},
1250+
{
1251+
code: "export default a; const a = 1;",
1252+
options: [{ allowNamedExports: true }],
1253+
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
1254+
errors: [{
1255+
messageId: "usedBeforeDefined",
1256+
data: { name: "a" }
1257+
}]
1258+
},
1259+
{
1260+
code: "export function foo() { return a; }; const a = 1;",
1261+
options: [{ allowNamedExports: true }],
1262+
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
1263+
errors: [{
1264+
messageId: "usedBeforeDefined",
1265+
data: { name: "a" }
1266+
}]
1267+
},
1268+
{
1269+
code: "export class C { foo() { return a; } }; const a = 1;",
1270+
options: [{ allowNamedExports: true }],
1271+
parserOptions: { ecmaVersion: 2015, sourceType: "module" },
1272+
errors: [{
1273+
messageId: "usedBeforeDefined",
1274+
data: { name: "a" }
1275+
}]
1276+
}
11261277
]
11271278
});

0 commit comments

Comments
 (0)