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(eslint-plugin): [no-unused-expressions] extend for optional chaining #1175

Merged
merged 11 commits into from
Nov 11, 2019
5 changes: 3 additions & 2 deletions packages/eslint-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e
| [`@typescript-eslint/await-thenable`](./docs/rules/await-thenable.md) | Disallows awaiting a value that is not a Thenable | :heavy_check_mark: | | :thought_balloon: |
| [`@typescript-eslint/ban-ts-ignore`](./docs/rules/ban-ts-ignore.md) | Bans “// @ts-ignore” comments from being used | :heavy_check_mark: | | |
| [`@typescript-eslint/ban-types`](./docs/rules/ban-types.md) | Bans specific types from being used | :heavy_check_mark: | :wrench: | |
| [`@typescript-eslint/brace-style`](./docs/rules/brace-style.md) | Enforce consistent brace style for blocks | | :wrench: | |
| [`@typescript-eslint/brace-style`](./docs/rules/brace-style.md) | Enforce consistent brace style for blocks | | :wrench: | |
| [`@typescript-eslint/camelcase`](./docs/rules/camelcase.md) | Enforce camelCase naming convention | :heavy_check_mark: | | |
| [`@typescript-eslint/class-name-casing`](./docs/rules/class-name-casing.md) | Require PascalCased class and interface names | :heavy_check_mark: | | |
| [`@typescript-eslint/consistent-type-assertions`](./docs/rules/consistent-type-assertions.md) | Enforces consistent usage of type assertions. | :heavy_check_mark: | | |
Expand Down Expand Up @@ -181,6 +181,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e
| [`@typescript-eslint/no-unnecessary-qualifier`](./docs/rules/no-unnecessary-qualifier.md) | Warns when a namespace qualifier is unnecessary | | :wrench: | :thought_balloon: |
| [`@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: |
| [`@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: |
| [`@typescript-eslint/no-unused-expressions`](./docs/rules/no-unused-expressions.md) | Disallow unused expressions | | | |
| [`@typescript-eslint/no-unused-vars`](./docs/rules/no-unused-vars.md) | Disallow unused variables | :heavy_check_mark: | | |
| [`@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: | | |
| [`@typescript-eslint/no-useless-constructor`](./docs/rules/no-useless-constructor.md) | Disallow unnecessary constructors | | | |
Expand All @@ -193,7 +194,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e
| [`@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: |
| [`@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: |
| [`@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: |
| [`@typescript-eslint/quotes`](./docs/rules/quotes.md) | Enforce the consistent use of either backticks, double, or single quotes | | :wrench: | |
| [`@typescript-eslint/quotes`](./docs/rules/quotes.md) | Enforce the consistent use of either backticks, double, or single quotes | | :wrench: | |
| [`@typescript-eslint/require-array-sort-compare`](./docs/rules/require-array-sort-compare.md) | Enforce giving `compare` argument to `Array#sort` | | | :thought_balloon: |
| [`@typescript-eslint/require-await`](./docs/rules/require-await.md) | Disallow async functions which have no `await` expression | :heavy_check_mark: | | :thought_balloon: |
| [`@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: |
Expand Down
25 changes: 25 additions & 0 deletions packages/eslint-plugin/docs/rules/no-unused-expressions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# require or disallow semicolons instead of ASI (semi)

This rule aims to eliminate unused expressions which have no effect on the state of the program.

## Rule Details

This rule extends the base [eslint/no-unused-expressions](https://eslint.org/docs/rules/no-unused-expressions) rule.
It supports all options and features of the base rule.
This version adds support for numerous typescript features.

## How to use

```cjson
{
// note you must disable the base rule as it can report incorrect errors
"no-unused-expressions": "off",
"@typescript-eslint/no-unused-expressions": ["error"]
}
```

## Options

See [eslint/no-unused-expressions options](https://eslint.org/docs/rules/no-unused-expressions#options).

<sup>Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-unused-expressions.md)</sup>
1 change: 1 addition & 0 deletions packages/eslint-plugin/src/configs/all.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"@typescript-eslint/no-unnecessary-qualifier": "error",
"@typescript-eslint/no-unnecessary-type-arguments": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-unused-expressions": "error",
bradzacher marked this conversation as resolved.
Show resolved Hide resolved
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error",
"no-use-before-define": "off",
Expand Down
2 changes: 2 additions & 0 deletions packages/eslint-plugin/src/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import noUnnecessaryCondition from './no-unnecessary-condition';
import noUnnecessaryQualifier from './no-unnecessary-qualifier';
import noUnnecessaryTypeAssertion from './no-unnecessary-type-assertion';
import noUnusedVars from './no-unused-vars';
import noUnusedExpressions from './no-unused-expressions';
import noUseBeforeDefine from './no-use-before-define';
import noUselessConstructor from './no-useless-constructor';
import noVarRequires from './no-var-requires';
Expand Down Expand Up @@ -106,6 +107,7 @@ export default {
'no-unnecessary-type-arguments': useDefaultTypeParameter,
'no-unnecessary-type-assertion': noUnnecessaryTypeAssertion,
'no-unused-vars': noUnusedVars,
'no-unused-expressions': noUnusedExpressions,
'no-use-before-define': noUseBeforeDefine,
'no-useless-constructor': noUselessConstructor,
'no-var-requires': noVarRequires,
Expand Down
33 changes: 33 additions & 0 deletions packages/eslint-plugin/src/rules/no-unused-expressions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils';
import baseRule from 'eslint/lib/rules/no-unused-expressions';
import * as util from '../util';

export default util.createRule({
name: 'no-unused-expressions',
meta: {
type: 'suggestion',
docs: {
description: 'Disallow unused expressions',
category: 'Best Practices',
recommended: false,
},
schema: baseRule.meta.schema,
messages: {
expected:
'Expected an assignment or function call and instead saw an expression.',
},
},
defaultOptions: [],
create(context) {
const rules = baseRule.create(context);

return {
ExpressionStatement(node): void {
if (node.expression.type === AST_NODE_TYPES.OptionalCallExpression) {
return;
}
rules.ExpressionStatement(node);
},
};
},
});
180 changes: 180 additions & 0 deletions packages/eslint-plugin/tests/rules/no-unused-expressions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import rule from '../../src/rules/no-unused-expressions';
import { RuleTester } from '../RuleTester';

const ruleTester = new RuleTester({
parserOptions: {
ecmaVersion: 6,
sourceType: 'module',
ecmaFeatures: {},
},
parser: '@typescript-eslint/parser',
});

// the base rule doesn't have messageIds
function error(
messages: { line: number; column: number }[],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): any[] {
return messages.map(message => ({
...message,
message:
'Expected an assignment or function call and instead saw an expression.',
}));
}

ruleTester.run('no-unused-expressions', rule, {
valid: [
`
test.age?.toLocaleString();
`,
`
let a = (a?.b).c;
`,
`
let b = a?.['b'];
`,
`
let c = one[2]?.[3][4];
`,
`
one[2]?.[3][4]?.();
`,
`
a?.['b']?.c();
`,
],
invalid: [
{
code: `
if(0) 0
`,
errors: error([
{
line: 2,
column: 7,
},
]),
},
{
code: `
f(0), {}
`,
errors: error([
{
line: 2,
column: 1,
},
]),
},
{
code: `
a, b()
`,
errors: error([
{
line: 2,
column: 1,
},
]),
},
{
code: `
a() && function namedFunctionInExpressionContext () {f();}
`,
errors: error([
{
line: 2,
column: 1,
},
]),
},
{
code: `
a?.b
`,
errors: error([
{
line: 2,
column: 1,
},
]),
},
{
code: `
(a?.b).c
`,
errors: error([
{
line: 2,
column: 1,
},
]),
},
{
code: `
a?.['b']
`,
errors: error([
{
line: 2,
column: 1,
},
]),
},
{
code: `
(a?.['b']).c
`,
errors: error([
{
line: 2,
column: 1,
},
]),
},
{
code: `
a?.b()?.c
`,
errors: error([
{
line: 2,
column: 1,
},
]),
},
{
code: `
(a?.b()).c
`,
errors: error([
{
line: 2,
column: 1,
},
]),
},
{
code: `
one[2]?.[3][4];
`,
errors: error([
{
line: 2,
column: 1,
},
]),
},
{
code: `
one.two?.three.four;
`,
errors: error([
{
line: 2,
column: 1,
},
]),
},
],
});
20 changes: 20 additions & 0 deletions packages/eslint-plugin/typings/eslint-rules.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,26 @@ declare module 'eslint/lib/rules/no-unused-vars' {
export = rule;
}

declare module 'eslint/lib/rules/no-unused-expressions' {
import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';

const rule: TSESLint.RuleModule<
'expected',
(
| 'all'
| 'local'
| {
allowShortCircuit?: boolean;
allowTernary?: boolean;
allowTaggedTemplates?: boolean;
})[],
{
ExpressionStatement(node: TSESTree.ExpressionStatement): void;
}
>;
export = rule;
}

declare module 'eslint/lib/rules/no-use-before-define' {
import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';

Expand Down