Skip to content

Commit

Permalink
feat(eslint-plugin): support type assertions in no-extra-parens rule (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
webschik authored and bradzacher committed Apr 11, 2019
1 parent 5592a2c commit 116ca75
Show file tree
Hide file tree
Showing 6 changed files with 351 additions and 2 deletions.
22 changes: 22 additions & 0 deletions packages/eslint-plugin/docs/rules/no-extra-parens.md
@@ -0,0 +1,22 @@
# disallow unnecessary parentheses (no-extra-parens)

This rule restricts the use of parentheses to only where they are necessary.

## Rule Details

This rule extends the base [eslint/no-extra-parens](https://eslint.org/docs/rules/no-extra-parens) rule.
It supports all options and features of the base rule plus TS type assertions.

## How to use

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

## Options

See [eslint/no-extra-parens options](https://eslint.org/docs/rules/no-extra-parens#options).
33 changes: 33 additions & 0 deletions packages/eslint-plugin/src/rules/no-extra-parens.ts
@@ -0,0 +1,33 @@
import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree';
import baseRule from 'eslint/lib/rules/no-extra-parens';
import * as util from '../util';

type Options = util.InferOptionsTypeFromRule<typeof baseRule>;
type MessageIds = util.InferMessageIdsTypeFromRule<typeof baseRule>;

export default util.createRule<Options, MessageIds>({
name: 'no-extra-parens',
meta: {
type: 'layout',
docs: {
description: 'disallow unnecessary parentheses',
category: 'Possible Errors',
recommended: false,
},
fixable: 'code',
schema: baseRule.meta.schema,
messages: baseRule.meta.messages,
},
defaultOptions: ['all'],
create(context) {
const rules = baseRule.create(context);

return Object.assign({}, rules, {
MemberExpression(node: TSESTree.MemberExpression) {
if (node.object.type !== AST_NODE_TYPES.TSAsExpression) {
return rules.MemberExpression(node);
}
},
});
},
});
266 changes: 266 additions & 0 deletions packages/eslint-plugin/tests/rules/no-extra-parens.test.ts
@@ -0,0 +1,266 @@
import rule from '../../src/rules/no-extra-parens';
import { RuleTester } from '../RuleTester';

const ruleTester = new RuleTester({
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
parser: '@typescript-eslint/parser',
});

ruleTester.run('no-extra-parens', rule, {
valid: [
{
code: `
(0).toString();
(function(){}) ? a() : b();
(/^a$/).test(x);
for (a of (b, c));
for (a of b);
for (a in b, c);
for (a in b);
`,
},
{
code: `t.true((me.get as SinonStub).calledWithExactly('/foo', other));`,
},
{
code: `while ((foo = bar())) {}`,
options: ['all', { conditionalAssign: false }],
},
{
code: `if ((foo = bar())) {}`,
options: ['all', { conditionalAssign: false }],
},
{
code: `do; while ((foo = bar()))`,
options: ['all', { conditionalAssign: false }],
},
{
code: `for (;(a = b););`,
options: ['all', { conditionalAssign: false }],
},
{
code: `
function a(b) {
return (b = 1);
}
`,
options: ['all', { returnAssign: false }],
},
{
code: `
function a(b) {
return b ? (c = d) : (c = e);
}
`,
options: ['all', { returnAssign: false }],
},
{
code: `b => (b = 1);`,
options: ['all', { returnAssign: false }],
},
{
code: `b => b ? (c = d) : (c = e);`,
options: ['all', { returnAssign: false }],
},
{
code: `x = a || (b && c);`,
options: ['all', { nestedBinaryExpressions: false }],
},
{
code: `x = a + (b * c);`,
options: ['all', { nestedBinaryExpressions: false }],
},
{
code: `x = (a * b) / c;`,
options: ['all', { nestedBinaryExpressions: false }],
},
{
code: `
const Component = (<div />)
const Component = (
<div
prop={true}
/>
)
`,
options: ['all', { ignoreJSX: 'all' }],
},
{
code: `
const Component = (
<div>
<p />
</div>
)
const Component = (
<div
prop={true}
/>
)
`,
options: ['all', { ignoreJSX: 'multi-line' }],
},
{
code: `
const Component = (<div />)
const Component = (<div><p /></div>)
`,
options: ['all', { ignoreJSX: 'single-line' }],
},
{
code: `
const b = a => 1 ? 2 : 3;
const d = c => (1 ? 2 : 3);
`,
options: ['all', { enforceForArrowConditionals: false }],
},
{
code: `
(0).toString();
(Object.prototype.toString.call());
({}.toString.call());
(function(){} ? a() : b());
(/^a$/).test(x);
a = (b * c);
(a * b) + c;
typeof (a);
`,
options: ['functions'],
},
],

invalid: [
{
code: `a = (b * c);`,
errors: [
{
messageId: 'unexpected',
line: 1,
column: 5,
},
],
},
{
code: `(a * b) + c;`,
errors: [
{
messageId: 'unexpected',
line: 1,
column: 1,
},
],
},
{
code: `for (a in (b, c));`,
errors: [
{
messageId: 'unexpected',
line: 1,
column: 11,
},
],
},
{
code: `for (a in (b));`,
errors: [
{
messageId: 'unexpected',
line: 1,
column: 11,
},
],
},
{
code: `for (a of (b));`,
errors: [
{
messageId: 'unexpected',
line: 1,
column: 11,
},
],
},
{
code: `typeof (a);`,
errors: [
{
messageId: 'unexpected',
line: 1,
column: 8,
},
],
},
{
code: `
const Component = (<div />)
const Component = (<div><p /></div>)
`,
options: ['all', { ignoreJSX: 'multi-line' }],
errors: [
{
messageId: 'unexpected',
line: 2,
column: 27,
},
{
messageId: 'unexpected',
line: 3,
column: 27,
},
],
},
{
code: `
const Component = (
<div>
<p />
</div>
)
const Component = (
<div
prop={true}
/>
)
`,
options: ['all', { ignoreJSX: 'single-line' }],
errors: [
{
messageId: 'unexpected',
line: 2,
column: 27,
},
{
messageId: 'unexpected',
line: 7,
column: 27,
},
],
},
{
code: `((function foo() {}))();`,
options: ['functions'],
errors: [
{
messageId: 'unexpected',
line: 1,
column: 2,
},
],
},
{
code: `var y = (function () {return 1;});`,
options: ['functions'],
errors: [
{
messageId: 'unexpected',
line: 1,
column: 9,
},
],
},
],
});
23 changes: 23 additions & 0 deletions packages/eslint-plugin/typings/eslint-rules.d.ts
Expand Up @@ -330,3 +330,26 @@ declare module 'eslint/lib/rules/no-useless-constructor' {
>;
export = rule;
}

declare module 'eslint/lib/rules/no-extra-parens' {
import { TSESTree } from '@typescript-eslint/typescript-estree';
import RuleModule from 'ts-eslint';

const rule: RuleModule<
'unexpected',
(
| 'all'
| 'functions'
| {
conditionalAssign?: boolean;
returnAssign?: boolean;
nestedBinaryExpressions?: boolean;
ignoreJSX?: 'none' | 'all' | 'multi-line' | 'single-line';
enforceForArrowConditionals?: boolean;
})[],
{
MemberExpression(node: TSESTree.MemberExpression): void;
}
>;
export = rule;
}
6 changes: 5 additions & 1 deletion packages/eslint-plugin/typings/ts-eslint.d.ts
Expand Up @@ -209,7 +209,11 @@ declare module 'ts-eslint' {
/**
* The general category the rule falls within
*/
category: 'Best Practices' | 'Stylistic Issues' | 'Variables';
category:
| 'Best Practices'
| 'Stylistic Issues'
| 'Variables'
| 'Possible Errors';
/**
* Concise description of the rule
*/
Expand Down
3 changes: 2 additions & 1 deletion packages/typescript-estree/src/ts-estree/ts-estree.ts
Expand Up @@ -323,7 +323,8 @@ export type LeftHandSideExpression =
| MemberExpression
| PrimaryExpression
| TaggedTemplateExpression
| TSNonNullExpression;
| TSNonNullExpression
| TSAsExpression;
export type LiteralExpression = BigIntLiteral | Literal | TemplateLiteral;
export type Modifier =
| TSAbstractKeyword
Expand Down

0 comments on commit 116ca75

Please sign in to comment.