Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat: add allowParensAfterCommentPattern option to no-extra-parens (#…
…16561)

* feat: add new `allowParensAfterComment` option (`no-extra-parens`)

* feat: add new `allowParensAfterCommentPattern` option to the `no-extra-parens` rule

* docs: add new `allowParensAfterCommentPattern` option to the `no-extra-parens` rule

* docs: add new `allowParensAfterCommentPattern` option to the `no-extra-parens` rule

* docs: add more information for `allowParensAfterCommentPattern`

* test: add another invalid case

* refactor: remove extra condition

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>

* test: add more invalid cases

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>
  • Loading branch information
snitin315 and mdjermanovic committed Nov 29, 2022
1 parent 6380c87 commit 49a07c5
Show file tree
Hide file tree
Showing 3 changed files with 232 additions and 3 deletions.
29 changes: 29 additions & 0 deletions docs/src/rules/no-extra-parens.md
Expand Up @@ -38,6 +38,7 @@ This rule has an object option for exceptions to the `"all"` option:
* `"enforceForSequenceExpressions": false` allows extra parentheses around sequence expressions
* `"enforceForNewInMemberExpressions": false` allows extra parentheses around `new` expressions in member expressions
* `"enforceForFunctionPrototypeMethods": false` allows extra parentheses around immediate `.call` and `.apply` method calls on function expressions and around function expressions in the same context.
* `"allowParensAfterCommentPattern": "any-string-pattern"` allows extra parentheses preceded by a comment that matches a regular expression.

### all

Expand Down Expand Up @@ -322,6 +323,34 @@ const quux = (function () {}.apply());

:::

### allowParensAfterCommentPattern

To make this rule allow extra parentheses preceded by specific comments, set this option to a string pattern that will be passed to the [`RegExp` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/RegExp).

Examples of **correct** code for this rule with the `"all"` and `{ "allowParensAfterCommentPattern": "@type" }` options:

::: correct

```js
/* eslint no-extra-parens: ["error", "all", { "allowParensAfterCommentPattern": "@type" }] */

const span = /**@type {HTMLSpanElement}*/(event.currentTarget);

if (/** @type {Foo | Bar} */(options).baz) console.log('Lint free');

foo(/** @type {Bar} */ (bar), options, {
name: "name",
path: "path",
});

if (foo) {
/** @type {Bar} */
(bar).prop = false;
}
```

:::

### functions

Examples of **incorrect** code for this rule with the `"functions"` option:
Expand Down
17 changes: 16 additions & 1 deletion lib/rules/no-extra-parens.js
Expand Up @@ -52,7 +52,8 @@ module.exports = {
enforceForArrowConditionals: { type: "boolean" },
enforceForSequenceExpressions: { type: "boolean" },
enforceForNewInMemberExpressions: { type: "boolean" },
enforceForFunctionPrototypeMethods: { type: "boolean" }
enforceForFunctionPrototypeMethods: { type: "boolean" },
allowParensAfterCommentPattern: { type: "string" }
},
additionalProperties: false
}
Expand Down Expand Up @@ -86,6 +87,7 @@ module.exports = {
context.options[1].enforceForNewInMemberExpressions === false;
const IGNORE_FUNCTION_PROTOTYPE_METHODS = ALL_NODES && context.options[1] &&
context.options[1].enforceForFunctionPrototypeMethods === false;
const ALLOW_PARENS_AFTER_COMMENT_PATTERN = ALL_NODES && context.options[1] && context.options[1].allowParensAfterCommentPattern;

const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" });
const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" });
Expand Down Expand Up @@ -402,6 +404,19 @@ module.exports = {
if (isIIFE(node) && !isParenthesised(node.callee)) {
return;
}

if (ALLOW_PARENS_AFTER_COMMENT_PATTERN) {
const commentsBeforeLeftParenToken = sourceCode.getCommentsBefore(leftParenToken);
const totalCommentsBeforeLeftParenTokenCount = commentsBeforeLeftParenToken.length;
const ignorePattern = new RegExp(ALLOW_PARENS_AFTER_COMMENT_PATTERN, "u");

if (
totalCommentsBeforeLeftParenTokenCount > 0 &&
ignorePattern.test(commentsBeforeLeftParenToken[totalCommentsBeforeLeftParenTokenCount - 1].value)
) {
return;
}
}
}

/**
Expand Down
189 changes: 187 additions & 2 deletions tests/lib/rules/no-extra-parens.js
Expand Up @@ -738,8 +738,39 @@ ruleTester.run("no-extra-parens", rule, {
},
{
code: "(Object.prototype.toString.call())",
options: ["functions"],
parserOptions: { ecmaVersion: 2020 }
options: ["functions"]
},

// "allowParensAfterCommentPattern" option
{
code: "const span = /**@type {HTMLSpanElement}*/(event.currentTarget);",
options: ["all", { allowParensAfterCommentPattern: "@type" }]
},
{
code: "if (/** @type {Compiler | MultiCompiler} */(options).hooks) console.log('good');",
options: ["all", { allowParensAfterCommentPattern: "@type" }]
},
{
code: `
validate(/** @type {Schema} */ (schema), options, {
name: "Dev Server",
baseDataPath: "options",
});
`,
options: ["all", { allowParensAfterCommentPattern: "@type" }]
},
{
code: `
if (condition) {
/** @type {ServerOptions} */
(options.server.options).requestCert = false;
}
`,
options: ["all", { allowParensAfterCommentPattern: "@type" }]
},
{
code: "const net = ipaddr.parseCIDR(/** @type {string} */ (cidr));",
options: ["all", { allowParensAfterCommentPattern: "@type" }]
}
],

Expand Down Expand Up @@ -3192,6 +3223,160 @@ ruleTester.run("no-extra-parens", rule, {
errors: [{ messageId: "unexpected" }]
},

// "allowParensAfterCommentPattern" option (off by default)
{
code: "const span = /**@type {HTMLSpanElement}*/(event.currentTarget);",
output: "const span = /**@type {HTMLSpanElement}*/event.currentTarget;",
options: ["all"],
errors: [{ messageId: "unexpected" }]
},
{
code: "if (/** @type {Compiler | MultiCompiler} */(options).hooks) console.log('good');",
output: "if (/** @type {Compiler | MultiCompiler} */options.hooks) console.log('good');",
options: ["all"],
errors: [{ messageId: "unexpected" }]
},
{
code: `
validate(/** @type {Schema} */ (schema), options, {
name: "Dev Server",
baseDataPath: "options",
});
`,
output: `
validate(/** @type {Schema} */ schema, options, {
name: "Dev Server",
baseDataPath: "options",
});
`,
options: ["all"],
errors: [{ messageId: "unexpected" }]
},
{
code: `
if (condition) {
/** @type {ServerOptions} */
(options.server.options).requestCert = false;
}
`,
output: `
if (condition) {
/** @type {ServerOptions} */
options.server.options.requestCert = false;
}
`,
options: ["all"],
errors: [{ messageId: "unexpected" }]
},
{
code: "const net = ipaddr.parseCIDR(/** @type {string} */ (cidr));",
output: "const net = ipaddr.parseCIDR(/** @type {string} */ cidr);",
options: ["all"],
errors: [{ messageId: "unexpected" }]
},
{
code: "const span = /**@type {HTMLSpanElement}*/(event.currentTarget);",
output: "const span = /**@type {HTMLSpanElement}*/event.currentTarget;",
options: ["all", { allowParensAfterCommentPattern: "invalid" }],
errors: [{ messageId: "unexpected" }]
},
{
code: "if (/** @type {Compiler | MultiCompiler} */(options).hooks) console.log('good');",
output: "if (/** @type {Compiler | MultiCompiler} */options.hooks) console.log('good');",
options: ["all", { allowParensAfterCommentPattern: "invalid" }],
errors: [{ messageId: "unexpected" }]
},
{
code: `
validate(/** @type {Schema} */ (schema), options, {
name: "Dev Server",
baseDataPath: "options",
});
`,
output: `
validate(/** @type {Schema} */ schema, options, {
name: "Dev Server",
baseDataPath: "options",
});
`,
options: ["all", { allowParensAfterCommentPattern: "invalid" }],
errors: [{ messageId: "unexpected" }]
},
{
code: `
if (condition) {
/** @type {ServerOptions} */
(options.server.options).requestCert = false;
}
`,
output: `
if (condition) {
/** @type {ServerOptions} */
options.server.options.requestCert = false;
}
`,
options: ["all", { allowParensAfterCommentPattern: "invalid" }],
errors: [{ messageId: "unexpected" }]
},
{
code: `
if (condition) {
/** @type {ServerOptions} */
/** extra coment */
(options.server.options).requestCert = false;
}
`,
output: `
if (condition) {
/** @type {ServerOptions} */
/** extra coment */
options.server.options.requestCert = false;
}
`,
options: ["all", { allowParensAfterCommentPattern: "@type" }],
errors: [{ messageId: "unexpected" }]
},
{
code: `
if (condition) {
/** @type {ServerOptions} */
((options.server.options)).requestCert = false;
}
`,
output: `
if (condition) {
/** @type {ServerOptions} */
(options.server.options).requestCert = false;
}
`,
options: ["all", { allowParensAfterCommentPattern: "@type" }],
errors: [{ messageId: "unexpected" }]
},
{
code: `
if (condition) {
/** @type {ServerOptions} */
let foo = "bar";
(options.server.options).requestCert = false;
}
`,
output: `
if (condition) {
/** @type {ServerOptions} */
let foo = "bar";
options.server.options.requestCert = false;
}
`,
options: ["all", { allowParensAfterCommentPattern: "@type" }],
errors: [{ messageId: "unexpected" }]
},
{
code: "const net = ipaddr.parseCIDR(/** @type {string} */ (cidr));",
output: "const net = ipaddr.parseCIDR(/** @type {string} */ cidr);",
options: ["all", { allowParensAfterCommentPattern: "invalid" }],
errors: [{ messageId: "unexpected" }]
},

// Optional chaining
{
code: "var v = (obj?.aaa)?.aaa",
Expand Down

0 comments on commit 49a07c5

Please sign in to comment.