Skip to content

Commit 66456c5

Browse files
committed
New: isParenthesized function
1 parent 6123e16 commit 66456c5

File tree

5 files changed

+386
-0
lines changed

5 files changed

+386
-0
lines changed

docs/api/ast-utils.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,72 @@ function getStringIfConstant(node, initialScope) {
350350

351351
----
352352

353+
## isParenthesized
354+
355+
```js
356+
const ret = utils.isParenthesized(node, sourceCode)
357+
```
358+
359+
Check whether a given node is parenthesized or not.
360+
361+
This function detects it correctly even if it's parenthesized by specific syntax.
362+
363+
```js
364+
f(a); //→ this `a` is not parenthesized.
365+
f((b)); //→ this `b` is parenthesized.
366+
367+
new C(a); //→ this `a` is not parenthesized.
368+
new C((b)); //→ this `b` is parenthesized.
369+
370+
if (a) {} //→ this `a` is not parenthesized.
371+
if ((b)) {} //→ this `b` is parenthesized.
372+
373+
switch (a) {} //→ this `a` is not parenthesized.
374+
switch ((b)) {} //→ this `b` is parenthesized.
375+
376+
while (a) {} //→ this `a` is not parenthesized.
377+
while ((b)) {} //→ this `b` is parenthesized.
378+
379+
do {} while (a); //→ this `a` is not parenthesized.
380+
do {} while ((b)); //→ this `b` is parenthesized.
381+
382+
with (a) {} //→ this `a` is not parenthesized.
383+
with ((b)) {} //→ this `b` is parenthesized.
384+
```
385+
386+
### Parameters
387+
388+
Name | Type | Description
389+
:-----|:-----|:------------
390+
node | Node | The node to check.
391+
sourceCode | SourceCode | The source code object to get tokens.
392+
393+
### Return value
394+
395+
`true` if the node is parenthesized.
396+
397+
### Example
398+
399+
```js{9}
400+
const { isParenthesized } = require("eslint-utils")
401+
402+
module.exports = {
403+
meta: {},
404+
create(context) {
405+
const sourceCode = context.getSourceCode()
406+
return {
407+
":expression"(node) {
408+
if (isParenthesized(node, sourceCode)) {
409+
// ...
410+
}
411+
},
412+
}
413+
},
414+
}
415+
```
416+
417+
----
418+
353419
## PatternMatcher class
354420

355421
```js

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"devDependencies": {
1414
"@mysticatea/eslint-plugin": "^5.0.1",
1515
"codecov": "^3.0.2",
16+
"dot-prop": "^4.2.0",
1617
"eslint": "^5.0.1",
1718
"esm": "^3.0.55",
1819
"espree": "^4.0.0",

src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { getInnermostScope } from "./get-innermost-scope"
55
import { getPropertyName } from "./get-property-name"
66
import { getStaticValue } from "./get-static-value"
77
import { getStringIfConstant } from "./get-string-if-constant"
8+
import { isParenthesized } from "./is-parenthesized"
89
import { PatternMatcher } from "./pattern-matcher"
910
import {
1011
CALL,
@@ -70,6 +71,7 @@ export default {
7071
isOpeningBraceToken,
7172
isOpeningBracketToken,
7273
isOpeningParenToken,
74+
isParenthesized,
7375
isSemicolonToken,
7476
PatternMatcher,
7577
READ,
@@ -107,6 +109,7 @@ export {
107109
isOpeningBraceToken,
108110
isOpeningBracketToken,
109111
isOpeningParenToken,
112+
isParenthesized,
110113
isSemicolonToken,
111114
PatternMatcher,
112115
READ,

src/is-parenthesized.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { isClosingParenToken, isOpeningParenToken } from "./token-predicate"
2+
3+
/**
4+
* Get the left parenthesis of the parent node syntax if it exists.
5+
* E.g., `if (a) {}` then the `(`.
6+
* @param {Node} node The AST node to check.
7+
* @param {SourceCode} sourceCode The source code object to get tokens.
8+
* @returns {Token|null} The left parenthesis of the parent node syntax
9+
*/
10+
function getParentSyntaxParen(node, sourceCode) {
11+
const parent = node.parent
12+
13+
switch (parent.type) {
14+
case "CallExpression":
15+
case "NewExpression":
16+
if (parent.arguments.length === 1 && parent.arguments[0] === node) {
17+
return sourceCode.getTokenAfter(
18+
parent.callee,
19+
isOpeningParenToken
20+
)
21+
}
22+
return null
23+
24+
case "DoWhileStatement":
25+
if (parent.test === node) {
26+
return sourceCode.getTokenAfter(
27+
parent.body,
28+
isOpeningParenToken
29+
)
30+
}
31+
return null
32+
33+
case "IfStatement":
34+
case "WhileStatement":
35+
if (parent.test === node) {
36+
return sourceCode.getFirstToken(parent, 1)
37+
}
38+
return null
39+
40+
case "SwitchStatement":
41+
if (parent.discriminant === node) {
42+
return sourceCode.getFirstToken(parent, 1)
43+
}
44+
return null
45+
46+
case "WithStatement":
47+
if (parent.object === node) {
48+
return sourceCode.getFirstToken(parent, 1)
49+
}
50+
return null
51+
52+
default:
53+
return null
54+
}
55+
}
56+
57+
/**
58+
* Check whether a given node is parenthesized or not.
59+
* @param {Node} node The AST node to check.
60+
* @param {SourceCode} sourceCode The source code object to get tokens.
61+
* @returns {boolean} `true` if the node is parenthesized.
62+
*/
63+
export function isParenthesized(node, sourceCode) {
64+
if (node == null) {
65+
return false
66+
}
67+
68+
const maybeLeftParen = sourceCode.getTokenBefore(node)
69+
const maybeRightParen = sourceCode.getTokenAfter(node)
70+
71+
return (
72+
maybeLeftParen != null &&
73+
maybeRightParen != null &&
74+
isOpeningParenToken(maybeLeftParen) &&
75+
isClosingParenToken(maybeRightParen) &&
76+
// Avoid false positive such as `if (a) {}`
77+
maybeLeftParen !== getParentSyntaxParen(node, sourceCode)
78+
)
79+
}

0 commit comments

Comments
 (0)