Skip to content

Commit 2dfd83e

Browse files
authored
feat: add ignoreDirectives option in no-unused-expressions (#19645)
1 parent 17bae69 commit 2dfd83e

4 files changed

Lines changed: 136 additions & 6 deletions

File tree

docs/src/rules/no-unused-expressions.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function Thing() { nThings += 1; }
2424
new Thing(); // constructed object is unused, but nThings changed as a side effect
2525
```
2626

27-
This rule does not apply to directives (which are in the form of literal string expressions such as `"use strict";` at the beginning of a script, module, or function).
27+
This rule does not apply to directives (which are in the form of literal string expressions such as `"use strict";` at the beginning of a script, module, or function) when using ES5+ environments. In ES3 environments, directives are treated as unused expressions by default, but this behavior can be changed using the `ignoreDirectives` option.
2828

2929
Sequence expressions (those using a comma, such as `a = 1, b = 2`) are always considered unused unless their return value is assigned or used in a condition evaluation, or a function call is made with the sequence expression value.
3030

@@ -36,6 +36,7 @@ This rule, in its default state, does not require any arguments. If you would li
3636
* `allowTernary` set to `true` will enable you to use ternary operators in your expressions similarly to short circuit evaluations (Default: `false`).
3737
* `allowTaggedTemplates` set to `true` will enable you to use tagged template literals in your expressions (Default: `false`).
3838
* `enforceForJSX` set to `true` will flag unused JSX element expressions (Default: `false`).
39+
* `ignoreDirectives` set to `true` will prevent directives from being reported as unused expressions when linting with `ecmaVersion: 3` (Default: `false`).
3940

4041
These options allow unused expressions *only if all* of the code paths either directly change the state (for example, assignment statement) or could have *side effects* (for example, function call).
4142

@@ -277,6 +278,56 @@ const myFragment = <></>;
277278

278279
:::
279280

281+
### ignoreDirectives
282+
283+
When set to `false` (default), this rule reports directives (like `"use strict"`) as unused expressions when linting with `ecmaVersion: 3`. This default behavior exists because ES3 environments do not formally support directives, meaning such strings are effectively unused expressions in that specific context.
284+
285+
Set this option to `true` to prevent directives from being reported as unused, even when `ecmaVersion: 3` is specified. This option is primarily useful for projects that need to maintain a single codebase containing directives while supporting both older ES3 environments and modern (ES5+) environments.
286+
287+
**Note:** In ES5+ environments, directives are always ignored regardless of this setting.
288+
289+
Examples of **incorrect** code for the `{ "ignoreDirectives": false }` option and `ecmaVersion: 3`:
290+
291+
::: incorrect { "ecmaVersion": 3, "sourceType": "script" }
292+
293+
```js
294+
/*eslint no-unused-expressions: ["error", { "ignoreDirectives": false }]*/
295+
296+
"use strict";
297+
"use asm"
298+
"use stricter";
299+
"use babel"
300+
"any other strings like this in the directive prologue";
301+
"this is still the directive prologue";
302+
303+
function foo() {
304+
"bar";
305+
}
306+
```
307+
308+
:::
309+
310+
Examples of **correct** code for the `{ "ignoreDirectives": true }` option and `ecmaVersion: 3`:
311+
312+
::: correct { "ecmaVersion": 3, "sourceType": "script" }
313+
314+
```js
315+
/*eslint no-unused-expressions: ["error", { "ignoreDirectives": true }]*/
316+
317+
"use strict";
318+
"use asm"
319+
"use stricter";
320+
"use babel"
321+
"any other strings like this in the directive prologue";
322+
"this is still the directive prologue";
323+
324+
function foo() {
325+
"bar";
326+
}
327+
```
328+
329+
:::
330+
280331
### TypeScript Support
281332

282333
This rule supports TypeScript-specific expressions and follows these guidelines:

lib/rules/no-unused-expressions.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ module.exports = {
5555
enforceForJSX: {
5656
type: "boolean",
5757
},
58+
ignoreDirectives: {
59+
type: "boolean",
60+
},
5861
},
5962
additionalProperties: false,
6063
},
@@ -66,6 +69,7 @@ module.exports = {
6669
allowTernary: false,
6770
allowTaggedTemplates: false,
6871
enforceForJSX: false,
72+
ignoreDirectives: false,
6973
},
7074
],
7175

@@ -82,6 +86,7 @@ module.exports = {
8286
allowTernary,
8387
allowTaggedTemplates,
8488
enforceForJSX,
89+
ignoreDirectives,
8590
},
8691
] = context.options;
8792

@@ -211,7 +216,8 @@ module.exports = {
211216
ExpressionStatement(node) {
212217
if (
213218
Checker.isDisallowed(node.expression) &&
214-
!isDirective(node)
219+
!astUtils.isDirective(node) &&
220+
!(ignoreDirectives && isDirective(node))
215221
) {
216222
context.report({ node, messageId: "unusedExpression" });
217223
}

lib/types/rules.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3935,6 +3935,10 @@ export interface ESLintRules extends Linter.RulesRecord {
39353935
* @default false
39363936
*/
39373937
enforceForJSX: boolean;
3938+
/**
3939+
* @default false
3940+
*/
3941+
ignoreDirectives: boolean;
39383942
}>,
39393943
]
39403944
>;

tests/lib/rules/no-unused-expressions.js

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,6 @@ ruleTester.run("no-unused-expressions", rule, {
3737
"delete foo.bar",
3838
"void new C",
3939
'"use strict";',
40-
{
41-
code: '"use strict";',
42-
languageOptions: { ecmaVersion: 3, sourceType: "script" },
43-
},
4440
'"directive one"; "directive two"; f();',
4541
'function foo() {"use strict"; return true; }',
4642
{
@@ -117,6 +113,42 @@ ruleTester.run("no-unused-expressions", rule, {
117113
options: [{ enforceForJSX: true }],
118114
languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } },
119115
},
116+
{
117+
code: '"use strict";',
118+
options: [{ ignoreDirectives: true }],
119+
languageOptions: { ecmaVersion: 3, sourceType: "script" },
120+
},
121+
{
122+
code: '"directive one"; "directive two"; f();',
123+
options: [{ ignoreDirectives: true }],
124+
languageOptions: { ecmaVersion: 3, sourceType: "script" },
125+
},
126+
{
127+
code: 'function foo() {"use strict"; return true; }',
128+
options: [{ ignoreDirectives: true }],
129+
languageOptions: { ecmaVersion: 3, sourceType: "script" },
130+
},
131+
{
132+
code: 'function foo() {"directive one"; "directive two"; f(); }',
133+
options: [{ ignoreDirectives: true }],
134+
languageOptions: { ecmaVersion: 3, sourceType: "script" },
135+
},
136+
{
137+
code: '"use strict";',
138+
options: [{ ignoreDirectives: true }],
139+
},
140+
{
141+
code: '"directive one"; "directive two"; f();',
142+
options: [{ ignoreDirectives: true }],
143+
},
144+
{
145+
code: 'function foo() {"use strict"; return true; }',
146+
options: [{ ignoreDirectives: true }],
147+
},
148+
{
149+
code: 'function foo() {"directive one"; "directive two"; f(); }',
150+
options: [{ ignoreDirectives: true }],
151+
},
120152
],
121153
invalid: [
122154
{
@@ -358,6 +390,43 @@ ruleTester.run("no-unused-expressions", rule, {
358390
},
359391
],
360392
},
393+
{
394+
code: "foo;",
395+
options: [{ ignoreDirectives: true }],
396+
errors: [
397+
{ messageId: "unusedExpression", type: "ExpressionStatement" },
398+
],
399+
},
400+
{
401+
code: '"use strict";',
402+
languageOptions: { ecmaVersion: 3, sourceType: "script" },
403+
errors: [
404+
{ messageId: "unusedExpression", type: "ExpressionStatement" },
405+
],
406+
},
407+
{
408+
code: '"directive one"; "directive two"; f();',
409+
languageOptions: { ecmaVersion: 3, sourceType: "script" },
410+
errors: [
411+
{ messageId: "unusedExpression", type: "ExpressionStatement" },
412+
{ messageId: "unusedExpression", type: "ExpressionStatement" },
413+
],
414+
},
415+
{
416+
code: 'function foo() {"use strict"; return true; }',
417+
languageOptions: { ecmaVersion: 3, sourceType: "script" },
418+
errors: [
419+
{ messageId: "unusedExpression", type: "ExpressionStatement" },
420+
],
421+
},
422+
{
423+
code: 'function foo() {"directive one"; "directive two"; f(); }',
424+
languageOptions: { ecmaVersion: 3, sourceType: "script" },
425+
errors: [
426+
{ messageId: "unusedExpression", type: "ExpressionStatement" },
427+
{ messageId: "unusedExpression", type: "ExpressionStatement" },
428+
],
429+
},
361430
],
362431
});
363432

0 commit comments

Comments
 (0)