]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
1 | /** |
2 | * @fileoverview Rule to flag use of comma operator | |
3 | * @author Brandon Mills | |
4 | */ | |
5 | ||
6 | "use strict"; | |
7 | ||
8 | //------------------------------------------------------------------------------ | |
9 | // Requirements | |
10 | //------------------------------------------------------------------------------ | |
11 | ||
12 | const astUtils = require("./utils/ast-utils"); | |
13 | ||
5422a9cc TL |
14 | //------------------------------------------------------------------------------ |
15 | // Helpers | |
16 | //------------------------------------------------------------------------------ | |
17 | ||
18 | const DEFAULT_OPTIONS = { | |
19 | allowInParentheses: true | |
20 | }; | |
21 | ||
eb39fafa DC |
22 | //------------------------------------------------------------------------------ |
23 | // Rule Definition | |
24 | //------------------------------------------------------------------------------ | |
25 | ||
34eeec05 | 26 | /** @type {import('../shared/types').Rule} */ |
eb39fafa DC |
27 | module.exports = { |
28 | meta: { | |
29 | type: "suggestion", | |
30 | ||
31 | docs: { | |
32 | description: "disallow comma operators", | |
eb39fafa DC |
33 | recommended: false, |
34 | url: "https://eslint.org/docs/rules/no-sequences" | |
35 | }, | |
36 | ||
5422a9cc TL |
37 | schema: [{ |
38 | properties: { | |
39 | allowInParentheses: { | |
40 | type: "boolean", | |
41 | default: true | |
42 | } | |
43 | }, | |
44 | additionalProperties: false | |
45 | }], | |
eb39fafa DC |
46 | |
47 | messages: { | |
48 | unexpectedCommaExpression: "Unexpected use of comma operator." | |
49 | } | |
50 | }, | |
51 | ||
52 | create(context) { | |
5422a9cc | 53 | const options = Object.assign({}, DEFAULT_OPTIONS, context.options[0]); |
eb39fafa DC |
54 | const sourceCode = context.getSourceCode(); |
55 | ||
56 | /** | |
57 | * Parts of the grammar that are required to have parens. | |
58 | */ | |
59 | const parenthesized = { | |
60 | DoWhileStatement: "test", | |
61 | IfStatement: "test", | |
62 | SwitchStatement: "discriminant", | |
63 | WhileStatement: "test", | |
64 | WithStatement: "object", | |
65 | ArrowFunctionExpression: "body" | |
66 | ||
67 | /* | |
68 | * Omitting CallExpression - commas are parsed as argument separators | |
69 | * Omitting NewExpression - commas are parsed as argument separators | |
70 | * Omitting ForInStatement - parts aren't individually parenthesised | |
71 | * Omitting ForStatement - parts aren't individually parenthesised | |
72 | */ | |
73 | }; | |
74 | ||
75 | /** | |
76 | * Determines whether a node is required by the grammar to be wrapped in | |
77 | * parens, e.g. the test of an if statement. | |
78 | * @param {ASTNode} node The AST node | |
79 | * @returns {boolean} True if parens around node belong to parent node. | |
80 | */ | |
81 | function requiresExtraParens(node) { | |
82 | return node.parent && parenthesized[node.parent.type] && | |
83 | node === node.parent[parenthesized[node.parent.type]]; | |
84 | } | |
85 | ||
86 | /** | |
87 | * Check if a node is wrapped in parens. | |
88 | * @param {ASTNode} node The AST node | |
89 | * @returns {boolean} True if the node has a paren on each side. | |
90 | */ | |
91 | function isParenthesised(node) { | |
92 | return astUtils.isParenthesised(sourceCode, node); | |
93 | } | |
94 | ||
95 | /** | |
96 | * Check if a node is wrapped in two levels of parens. | |
97 | * @param {ASTNode} node The AST node | |
98 | * @returns {boolean} True if two parens surround the node on each side. | |
99 | */ | |
100 | function isParenthesisedTwice(node) { | |
101 | const previousToken = sourceCode.getTokenBefore(node, 1), | |
102 | nextToken = sourceCode.getTokenAfter(node, 1); | |
103 | ||
104 | return isParenthesised(node) && previousToken && nextToken && | |
105 | astUtils.isOpeningParenToken(previousToken) && previousToken.range[1] <= node.range[0] && | |
106 | astUtils.isClosingParenToken(nextToken) && nextToken.range[0] >= node.range[1]; | |
107 | } | |
108 | ||
109 | return { | |
110 | SequenceExpression(node) { | |
111 | ||
112 | // Always allow sequences in for statement update | |
113 | if (node.parent.type === "ForStatement" && | |
114 | (node === node.parent.init || node === node.parent.update)) { | |
115 | return; | |
116 | } | |
117 | ||
118 | // Wrapping a sequence in extra parens indicates intent | |
5422a9cc TL |
119 | if (options.allowInParentheses) { |
120 | if (requiresExtraParens(node)) { | |
121 | if (isParenthesisedTwice(node)) { | |
122 | return; | |
123 | } | |
124 | } else { | |
125 | if (isParenthesised(node)) { | |
126 | return; | |
127 | } | |
eb39fafa DC |
128 | } |
129 | } | |
130 | ||
131 | const firstCommaToken = sourceCode.getTokenAfter(node.expressions[0], astUtils.isCommaToken); | |
132 | ||
133 | context.report({ node, loc: firstCommaToken.loc, messageId: "unexpectedCommaExpression" }); | |
134 | } | |
135 | }; | |
136 | ||
137 | } | |
138 | }; |