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