Skip to content

Commit 701f1af

Browse files
feat!: no-inner-declaration new default behaviour and option (#17885)
* feat: no-inner-declaration new default behaviour and option * feat!: removed no-inner-declarations from recommended * docs: added legacy option * feat!: updated the legacy option * feat!: added more test and updated docs * feat: no-inner-declaration new default behaviour and option * feat!: removed no-inner-declarations from recommended * docs: added legacy option * feat!: updated the legacy option * feat!: added more test and updated docs * added test for module * remove non-strict report for variablDeclarations * change legacy option to blockScopedFunctions * check upper scope * update docs and tests * update docs * add functions option in docs
1 parent b4e0503 commit 701f1af

File tree

3 files changed

+334
-23
lines changed

3 files changed

+334
-23
lines changed

docs/src/rules/no-inner-declarations.md

+149-10
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,20 @@ function anotherThing() {
3030
}
3131
```
3232

33+
In ES6, [block-level functions](https://leanpub.com/understandinges6/read#leanpub-auto-block-level-functions) (functions declared inside a block) are limited to the scope of the block they are declared in and outside of the block scope they can't be accessed and called, but only when the code is in strict mode (code with `"use strict"` tag or ESM modules). In non-strict mode, they can be accessed and called outside of the block scope.
34+
35+
```js
36+
"use strict";
37+
38+
if (test) {
39+
function doSomething () { }
40+
41+
doSomething(); // no error
42+
}
43+
44+
doSomething(); // error
45+
```
46+
3347
A variable declaration is permitted anywhere a statement can go, even nested deeply inside other blocks. This is often undesirable due to variable hoisting, and moving declarations to the root of the program or function body can increase clarity. Note that [block bindings](https://leanpub.com/understandinges6/read#leanpub-auto-block-bindings) (`let`, `const`) are not hoisted and therefore they are not affected by this rule.
3448

3549
```js
@@ -65,10 +79,11 @@ This rule requires that function declarations and, optionally, variable declarat
6579

6680
## Options
6781

68-
This rule has a string option:
82+
This rule has a string and an object option:
6983

7084
* `"functions"` (default) disallows `function` declarations in nested blocks
7185
* `"both"` disallows `function` and `var` declarations in nested blocks
86+
* `{ blockScopedFunctions: "allow" }` (default) this option allows `function` declarations in nested blocks when code is in strict mode (code with `"use strict"` tag or ESM modules) and `languageOptions.ecmaVersion` is set to `2015` or above. This option can be disabled by setting it to `"disallow"`.
7287

7388
### functions
7489

@@ -79,6 +94,8 @@ Examples of **incorrect** code for this rule with the default `"functions"` opti
7994
```js
8095
/*eslint no-inner-declarations: "error"*/
8196

97+
// script, non-strict code
98+
8299
if (test) {
83100
function doSomething() { }
84101
}
@@ -90,14 +107,6 @@ function doSomethingElse() {
90107
}
91108

92109
if (foo) function f(){}
93-
94-
class C {
95-
static {
96-
if (test) {
97-
function doSomething() { }
98-
}
99-
}
100-
}
101110
```
102111

103112
:::
@@ -115,6 +124,14 @@ function doSomethingElse() {
115124
function doAnotherThing() { }
116125
}
117126

127+
function doSomethingElse() {
128+
"use strict";
129+
130+
if (test) {
131+
function doAnotherThing() { }
132+
}
133+
}
134+
118135
class C {
119136
static {
120137
function doSomething() { }
@@ -195,6 +212,128 @@ class C {
195212

196213
:::
197214

215+
### blockScopedFunctions
216+
217+
Example of **incorrect** code for this rule with `{ blockScopedFunctions: "disallow" }` option with `ecmaVersion: 2015`:
218+
219+
::: incorrect { "sourceType": "script", "ecmaVersion": 2015 }
220+
221+
```js
222+
/*eslint no-inner-declarations: ["error", "functions", { blockScopedFunctions: "disallow" }]*/
223+
224+
// non-strict code
225+
226+
if (test) {
227+
function doSomething() { }
228+
}
229+
230+
function doSomething() {
231+
if (test) {
232+
function doSomethingElse() { }
233+
}
234+
}
235+
236+
// strict code
237+
238+
function foo() {
239+
"use strict";
240+
241+
if (test) {
242+
function bar() { }
243+
}
244+
}
245+
```
246+
247+
:::
248+
249+
Example of **correct** code for this rule with `{ blockScopedFunctions: "disallow" }` option with `ecmaVersion: 2015`:
250+
251+
::: correct { "sourceType": "script", "ecmaVersion": 2015 }
252+
253+
```js
254+
/*eslint no-inner-declarations: ["error", "functions", { blockScopedFunctions: "disallow" }]*/
255+
256+
function doSomething() { }
257+
258+
function doSomething() {
259+
function doSomethingElse() { }
260+
}
261+
```
262+
263+
:::
264+
265+
Example of **correct** code for this rule with `{ blockScopedFunctions: "allow" }` option with `ecmaVersion: 2015`:
266+
267+
::: correct { "sourceType": "script", "ecmaVersion": 2015 }
268+
269+
```js
270+
/*eslint no-inner-declarations: ["error", "functions", { blockScopedFunctions: "allow" }]*/
271+
272+
"use strict";
273+
274+
if (test) {
275+
function doSomething() { }
276+
}
277+
278+
function doSomething() {
279+
if (test) {
280+
function doSomethingElse() { }
281+
}
282+
}
283+
284+
// OR
285+
286+
function foo() {
287+
"use strict";
288+
289+
if (test) {
290+
function bar() { }
291+
}
292+
}
293+
```
294+
295+
:::
296+
297+
`ESM modules` and both `class` declarations and expressions are always in strict mode.
298+
299+
::: correct { "sourceType": "module" }
300+
301+
```js
302+
/*eslint no-inner-declarations: ["error", "functions", { blockScopedFunctions: "allow" }]*/
303+
304+
if (test) {
305+
function doSomething() { }
306+
}
307+
308+
function doSomethingElse() {
309+
if (test) {
310+
function doAnotherThing() { }
311+
}
312+
}
313+
314+
class Some {
315+
static {
316+
if (test) {
317+
function doSomething() { }
318+
}
319+
}
320+
}
321+
322+
const C = class {
323+
static {
324+
if (test) {
325+
function doSomething() { }
326+
}
327+
}
328+
}
329+
```
330+
331+
:::
332+
198333
## When Not To Use It
199334

200-
The function declaration portion rule will be rendered obsolete when [block-scoped functions](https://bugzilla.mozilla.org/show_bug.cgi?id=585536) land in ES6, but until then, it should be left on to enforce valid constructions. Disable checking variable declarations when using [block-scoped-var](block-scoped-var) or if declaring variables in nested blocks is acceptable despite hoisting.
335+
By default, this rule disallows inner function declarations only in contexts where their behavior is unspecified and thus inconsistent (pre-ES6 environments) or legacy semantics apply (non-strict mode code). If your code targets pre-ES6 environments or is not in strict mode, you should enable this rule to prevent unexpected behavior.
336+
337+
In ES6+ environments, in strict mode code, the behavior of inner function declarations is well-defined and consistent - they are always block-scoped. If your code targets only ES6+ environments and is in strict mode (ES modules, or code with `"use strict"` directives) then there is no need to enable this rule unless you want to disallow inner functions as a stylistic choice, in which case you should enable this rule with the option `blockScopedFunctions: "disallow"`.
338+
339+
Disable checking variable declarations when using [block-scoped-var](block-scoped-var) or if declaring variables in nested blocks is acceptable despite hoisting.

lib/rules/no-inner-declarations.js

+22-1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ module.exports = {
5656
schema: [
5757
{
5858
enum: ["functions", "both"]
59+
},
60+
{
61+
type: "object",
62+
properties: {
63+
blockScopedFunctions: {
64+
enum: ["allow", "disallow"]
65+
}
66+
},
67+
additionalProperties: false
5968
}
6069
],
6170

@@ -66,6 +75,10 @@ module.exports = {
6675

6776
create(context) {
6877

78+
const sourceCode = context.sourceCode;
79+
const ecmaVersion = context.languageOptions.ecmaVersion;
80+
const blockScopedFunctions = context.options[1]?.blockScopedFunctions ?? "allow";
81+
6982
/**
7083
* Ensure that a given node is at a program or function body's root.
7184
* @param {ASTNode} node Declaration node to check.
@@ -97,7 +110,15 @@ module.exports = {
97110

98111
return {
99112

100-
FunctionDeclaration: check,
113+
FunctionDeclaration(node) {
114+
const isInStrictCode = sourceCode.getScope(node).upper.isStrict;
115+
116+
if (blockScopedFunctions === "allow" && ecmaVersion >= 2015 && isInStrictCode) {
117+
return;
118+
}
119+
120+
check(node);
121+
},
101122
VariableDeclaration(node) {
102123
if (context.options[0] === "both" && node.kind === "var") {
103124
check(node);

0 commit comments

Comments
 (0)