Skip to content

Commit 57d63b7

Browse files
Nizariusbradzacher
authored andcommitted
feat(eslint-plugin): [no-unused-expressions] extend for optional chaining (typescript-eslint#1175)
1 parent 026ceb9 commit 57d63b7

File tree

7 files changed

+264
-2
lines changed

7 files changed

+264
-2
lines changed

packages/eslint-plugin/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e
145145
| [`@typescript-eslint/await-thenable`](./docs/rules/await-thenable.md) | Disallows awaiting a value that is not a Thenable | :heavy_check_mark: | | :thought_balloon: |
146146
| [`@typescript-eslint/ban-ts-ignore`](./docs/rules/ban-ts-ignore.md) | Bans “// @ts-ignore” comments from being used | :heavy_check_mark: | | |
147147
| [`@typescript-eslint/ban-types`](./docs/rules/ban-types.md) | Bans specific types from being used | :heavy_check_mark: | :wrench: | |
148-
| [`@typescript-eslint/brace-style`](./docs/rules/brace-style.md) | Enforce consistent brace style for blocks | | :wrench: | |
148+
| [`@typescript-eslint/brace-style`](./docs/rules/brace-style.md) | Enforce consistent brace style for blocks | | :wrench: | |
149149
| [`@typescript-eslint/camelcase`](./docs/rules/camelcase.md) | Enforce camelCase naming convention | :heavy_check_mark: | | |
150150
| [`@typescript-eslint/class-name-casing`](./docs/rules/class-name-casing.md) | Require PascalCased class and interface names | :heavy_check_mark: | | |
151151
| [`@typescript-eslint/consistent-type-assertions`](./docs/rules/consistent-type-assertions.md) | Enforces consistent usage of type assertions. | :heavy_check_mark: | | |
@@ -181,6 +181,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e
181181
| [`@typescript-eslint/no-unnecessary-qualifier`](./docs/rules/no-unnecessary-qualifier.md) | Warns when a namespace qualifier is unnecessary | | :wrench: | :thought_balloon: |
182182
| [`@typescript-eslint/no-unnecessary-type-arguments`](./docs/rules/no-unnecessary-type-arguments.md) | Warns if an explicitly specified type argument is the default for that type parameter | | :wrench: | :thought_balloon: |
183183
| [`@typescript-eslint/no-unnecessary-type-assertion`](./docs/rules/no-unnecessary-type-assertion.md) | Warns if a type assertion does not change the type of an expression | :heavy_check_mark: | :wrench: | :thought_balloon: |
184+
| [`@typescript-eslint/no-unused-expressions`](./docs/rules/no-unused-expressions.md) | Disallow unused expressions | | | |
184185
| [`@typescript-eslint/no-unused-vars`](./docs/rules/no-unused-vars.md) | Disallow unused variables | :heavy_check_mark: | | |
185186
| [`@typescript-eslint/no-use-before-define`](./docs/rules/no-use-before-define.md) | Disallow the use of variables before they are defined | :heavy_check_mark: | | |
186187
| [`@typescript-eslint/no-useless-constructor`](./docs/rules/no-useless-constructor.md) | Disallow unnecessary constructors | | | |
@@ -193,7 +194,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e
193194
| [`@typescript-eslint/prefer-regexp-exec`](./docs/rules/prefer-regexp-exec.md) | Prefer RegExp#exec() over String#match() if no global flag is provided | :heavy_check_mark: | | :thought_balloon: |
194195
| [`@typescript-eslint/prefer-string-starts-ends-with`](./docs/rules/prefer-string-starts-ends-with.md) | Enforce the use of `String#startsWith` and `String#endsWith` instead of other equivalent methods of checking substrings | :heavy_check_mark: | :wrench: | :thought_balloon: |
195196
| [`@typescript-eslint/promise-function-async`](./docs/rules/promise-function-async.md) | Requires any function or method that returns a Promise to be marked async | | | :thought_balloon: |
196-
| [`@typescript-eslint/quotes`](./docs/rules/quotes.md) | Enforce the consistent use of either backticks, double, or single quotes | | :wrench: | |
197+
| [`@typescript-eslint/quotes`](./docs/rules/quotes.md) | Enforce the consistent use of either backticks, double, or single quotes | | :wrench: | |
197198
| [`@typescript-eslint/require-array-sort-compare`](./docs/rules/require-array-sort-compare.md) | Enforce giving `compare` argument to `Array#sort` | | | :thought_balloon: |
198199
| [`@typescript-eslint/require-await`](./docs/rules/require-await.md) | Disallow async functions which have no `await` expression | :heavy_check_mark: | | :thought_balloon: |
199200
| [`@typescript-eslint/restrict-plus-operands`](./docs/rules/restrict-plus-operands.md) | When adding two variables, operands must both be of type number or of type string | | | :thought_balloon: |
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# require or disallow semicolons instead of ASI (semi)
2+
3+
This rule aims to eliminate unused expressions which have no effect on the state of the program.
4+
5+
## Rule Details
6+
7+
This rule extends the base [eslint/no-unused-expressions](https://eslint.org/docs/rules/no-unused-expressions) rule.
8+
It supports all options and features of the base rule.
9+
This version adds support for numerous typescript features.
10+
11+
## How to use
12+
13+
```cjson
14+
{
15+
// note you must disable the base rule as it can report incorrect errors
16+
"no-unused-expressions": "off",
17+
"@typescript-eslint/no-unused-expressions": ["error"]
18+
}
19+
```
20+
21+
## Options
22+
23+
See [eslint/no-unused-expressions options](https://eslint.org/docs/rules/no-unused-expressions#options).
24+
25+
<sup>Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-unused-expressions.md)</sup>

packages/eslint-plugin/src/configs/all.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"@typescript-eslint/no-unnecessary-qualifier": "error",
5151
"@typescript-eslint/no-unnecessary-type-arguments": "error",
5252
"@typescript-eslint/no-unnecessary-type-assertion": "error",
53+
"@typescript-eslint/no-unused-expressions": "error",
5354
"no-unused-vars": "off",
5455
"@typescript-eslint/no-unused-vars": "error",
5556
"no-use-before-define": "off",

packages/eslint-plugin/src/rules/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import noUnnecessaryCondition from './no-unnecessary-condition';
3939
import noUnnecessaryQualifier from './no-unnecessary-qualifier';
4040
import noUnnecessaryTypeAssertion from './no-unnecessary-type-assertion';
4141
import noUnusedVars from './no-unused-vars';
42+
import noUnusedExpressions from './no-unused-expressions';
4243
import noUseBeforeDefine from './no-use-before-define';
4344
import noUselessConstructor from './no-useless-constructor';
4445
import noVarRequires from './no-var-requires';
@@ -106,6 +107,7 @@ export default {
106107
'no-unnecessary-type-arguments': useDefaultTypeParameter,
107108
'no-unnecessary-type-assertion': noUnnecessaryTypeAssertion,
108109
'no-unused-vars': noUnusedVars,
110+
'no-unused-expressions': noUnusedExpressions,
109111
'no-use-before-define': noUseBeforeDefine,
110112
'no-useless-constructor': noUselessConstructor,
111113
'no-var-requires': noVarRequires,
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils';
2+
import baseRule from 'eslint/lib/rules/no-unused-expressions';
3+
import * as util from '../util';
4+
5+
export default util.createRule({
6+
name: 'no-unused-expressions',
7+
meta: {
8+
type: 'suggestion',
9+
docs: {
10+
description: 'Disallow unused expressions',
11+
category: 'Best Practices',
12+
recommended: false,
13+
},
14+
schema: baseRule.meta.schema,
15+
messages: {
16+
expected:
17+
'Expected an assignment or function call and instead saw an expression.',
18+
},
19+
},
20+
defaultOptions: [],
21+
create(context) {
22+
const rules = baseRule.create(context);
23+
24+
return {
25+
ExpressionStatement(node): void {
26+
if (node.expression.type === AST_NODE_TYPES.OptionalCallExpression) {
27+
return;
28+
}
29+
rules.ExpressionStatement(node);
30+
},
31+
};
32+
},
33+
});
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import rule from '../../src/rules/no-unused-expressions';
2+
import { RuleTester } from '../RuleTester';
3+
4+
const ruleTester = new RuleTester({
5+
parserOptions: {
6+
ecmaVersion: 6,
7+
sourceType: 'module',
8+
ecmaFeatures: {},
9+
},
10+
parser: '@typescript-eslint/parser',
11+
});
12+
13+
// the base rule doesn't have messageIds
14+
function error(
15+
messages: { line: number; column: number }[],
16+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
17+
): any[] {
18+
return messages.map(message => ({
19+
...message,
20+
message:
21+
'Expected an assignment or function call and instead saw an expression.',
22+
}));
23+
}
24+
25+
ruleTester.run('no-unused-expressions', rule, {
26+
valid: [
27+
`
28+
test.age?.toLocaleString();
29+
`,
30+
`
31+
let a = (a?.b).c;
32+
`,
33+
`
34+
let b = a?.['b'];
35+
`,
36+
`
37+
let c = one[2]?.[3][4];
38+
`,
39+
`
40+
one[2]?.[3][4]?.();
41+
`,
42+
`
43+
a?.['b']?.c();
44+
`,
45+
],
46+
invalid: [
47+
{
48+
code: `
49+
if(0) 0
50+
`,
51+
errors: error([
52+
{
53+
line: 2,
54+
column: 7,
55+
},
56+
]),
57+
},
58+
{
59+
code: `
60+
f(0), {}
61+
`,
62+
errors: error([
63+
{
64+
line: 2,
65+
column: 1,
66+
},
67+
]),
68+
},
69+
{
70+
code: `
71+
a, b()
72+
`,
73+
errors: error([
74+
{
75+
line: 2,
76+
column: 1,
77+
},
78+
]),
79+
},
80+
{
81+
code: `
82+
a() && function namedFunctionInExpressionContext () {f();}
83+
`,
84+
errors: error([
85+
{
86+
line: 2,
87+
column: 1,
88+
},
89+
]),
90+
},
91+
{
92+
code: `
93+
a?.b
94+
`,
95+
errors: error([
96+
{
97+
line: 2,
98+
column: 1,
99+
},
100+
]),
101+
},
102+
{
103+
code: `
104+
(a?.b).c
105+
`,
106+
errors: error([
107+
{
108+
line: 2,
109+
column: 1,
110+
},
111+
]),
112+
},
113+
{
114+
code: `
115+
a?.['b']
116+
`,
117+
errors: error([
118+
{
119+
line: 2,
120+
column: 1,
121+
},
122+
]),
123+
},
124+
{
125+
code: `
126+
(a?.['b']).c
127+
`,
128+
errors: error([
129+
{
130+
line: 2,
131+
column: 1,
132+
},
133+
]),
134+
},
135+
{
136+
code: `
137+
a?.b()?.c
138+
`,
139+
errors: error([
140+
{
141+
line: 2,
142+
column: 1,
143+
},
144+
]),
145+
},
146+
{
147+
code: `
148+
(a?.b()).c
149+
`,
150+
errors: error([
151+
{
152+
line: 2,
153+
column: 1,
154+
},
155+
]),
156+
},
157+
{
158+
code: `
159+
one[2]?.[3][4];
160+
`,
161+
errors: error([
162+
{
163+
line: 2,
164+
column: 1,
165+
},
166+
]),
167+
},
168+
{
169+
code: `
170+
one.two?.three.four;
171+
`,
172+
errors: error([
173+
{
174+
line: 2,
175+
column: 1,
176+
},
177+
]),
178+
},
179+
],
180+
});

packages/eslint-plugin/typings/eslint-rules.d.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,26 @@ declare module 'eslint/lib/rules/no-unused-vars' {
304304
export = rule;
305305
}
306306

307+
declare module 'eslint/lib/rules/no-unused-expressions' {
308+
import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';
309+
310+
const rule: TSESLint.RuleModule<
311+
'expected',
312+
(
313+
| 'all'
314+
| 'local'
315+
| {
316+
allowShortCircuit?: boolean;
317+
allowTernary?: boolean;
318+
allowTaggedTemplates?: boolean;
319+
})[],
320+
{
321+
ExpressionStatement(node: TSESTree.ExpressionStatement): void;
322+
}
323+
>;
324+
export = rule;
325+
}
326+
307327
declare module 'eslint/lib/rules/no-use-before-define' {
308328
import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';
309329

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy