• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
12const astUtils = require("./utils/ast-utils");
13
14//------------------------------------------------------------------------------
15// Rule Definition
16//------------------------------------------------------------------------------
17
18module.exports = {
19    meta: {
20        type: "suggestion",
21
22        docs: {
23            description: "disallow comma operators",
24            category: "Best Practices",
25            recommended: false,
26            url: "https://eslint.org/docs/rules/no-sequences"
27        },
28
29        schema: [],
30
31        messages: {
32            unexpectedCommaExpression: "Unexpected use of comma operator."
33        }
34    },
35
36    create(context) {
37        const sourceCode = context.getSourceCode();
38
39        /**
40         * Parts of the grammar that are required to have parens.
41         */
42        const parenthesized = {
43            DoWhileStatement: "test",
44            IfStatement: "test",
45            SwitchStatement: "discriminant",
46            WhileStatement: "test",
47            WithStatement: "object",
48            ArrowFunctionExpression: "body"
49
50            /*
51             * Omitting CallExpression - commas are parsed as argument separators
52             * Omitting NewExpression - commas are parsed as argument separators
53             * Omitting ForInStatement - parts aren't individually parenthesised
54             * Omitting ForStatement - parts aren't individually parenthesised
55             */
56        };
57
58        /**
59         * Determines whether a node is required by the grammar to be wrapped in
60         * parens, e.g. the test of an if statement.
61         * @param {ASTNode} node The AST node
62         * @returns {boolean} True if parens around node belong to parent node.
63         */
64        function requiresExtraParens(node) {
65            return node.parent && parenthesized[node.parent.type] &&
66                    node === node.parent[parenthesized[node.parent.type]];
67        }
68
69        /**
70         * Check if a node is wrapped in parens.
71         * @param {ASTNode} node The AST node
72         * @returns {boolean} True if the node has a paren on each side.
73         */
74        function isParenthesised(node) {
75            return astUtils.isParenthesised(sourceCode, node);
76        }
77
78        /**
79         * Check if a node is wrapped in two levels of parens.
80         * @param {ASTNode} node The AST node
81         * @returns {boolean} True if two parens surround the node on each side.
82         */
83        function isParenthesisedTwice(node) {
84            const previousToken = sourceCode.getTokenBefore(node, 1),
85                nextToken = sourceCode.getTokenAfter(node, 1);
86
87            return isParenthesised(node) && previousToken && nextToken &&
88                astUtils.isOpeningParenToken(previousToken) && previousToken.range[1] <= node.range[0] &&
89                astUtils.isClosingParenToken(nextToken) && nextToken.range[0] >= node.range[1];
90        }
91
92        return {
93            SequenceExpression(node) {
94
95                // Always allow sequences in for statement update
96                if (node.parent.type === "ForStatement" &&
97                        (node === node.parent.init || node === node.parent.update)) {
98                    return;
99                }
100
101                // Wrapping a sequence in extra parens indicates intent
102                if (requiresExtraParens(node)) {
103                    if (isParenthesisedTwice(node)) {
104                        return;
105                    }
106                } else {
107                    if (isParenthesised(node)) {
108                        return;
109                    }
110                }
111
112                const firstCommaToken = sourceCode.getTokenAfter(node.expressions[0], astUtils.isCommaToken);
113
114                context.report({ node, loc: firstCommaToken.loc, messageId: "unexpectedCommaExpression" });
115            }
116        };
117
118    }
119};
120