Permalink
Cannot retrieve contributors at this time
159 lines (131 sloc)
5.52 KB
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
codeql-action/node_modules/eslint/lib/rules/no-cond-assign.js
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @fileoverview Rule to flag assignment in a conditional statement's test expression | |
* @author Stephen Murray <spmurrayzzz> | |
*/ | |
"use strict"; | |
//------------------------------------------------------------------------------ | |
// Requirements | |
//------------------------------------------------------------------------------ | |
const astUtils = require("./utils/ast-utils"); | |
//------------------------------------------------------------------------------ | |
// Helpers | |
//------------------------------------------------------------------------------ | |
const TEST_CONDITION_PARENT_TYPES = new Set(["IfStatement", "WhileStatement", "DoWhileStatement", "ForStatement", "ConditionalExpression"]); | |
const NODE_DESCRIPTIONS = { | |
DoWhileStatement: "a 'do...while' statement", | |
ForStatement: "a 'for' statement", | |
IfStatement: "an 'if' statement", | |
WhileStatement: "a 'while' statement" | |
}; | |
//------------------------------------------------------------------------------ | |
// Rule Definition | |
//------------------------------------------------------------------------------ | |
/** @type {import('../shared/types').Rule} */ | |
module.exports = { | |
meta: { | |
type: "problem", | |
docs: { | |
description: "Disallow assignment operators in conditional expressions", | |
recommended: true, | |
url: "https://eslint.org/docs/latest/rules/no-cond-assign" | |
}, | |
schema: [ | |
{ | |
enum: ["except-parens", "always"] | |
} | |
], | |
messages: { | |
unexpected: "Unexpected assignment within {{type}}.", | |
// must match JSHint's error message | |
missing: "Expected a conditional expression and instead saw an assignment." | |
} | |
}, | |
create(context) { | |
const prohibitAssign = (context.options[0] || "except-parens"); | |
const sourceCode = context.sourceCode; | |
/** | |
* Check whether an AST node is the test expression for a conditional statement. | |
* @param {!Object} node The node to test. | |
* @returns {boolean} `true` if the node is the text expression for a conditional statement; otherwise, `false`. | |
*/ | |
function isConditionalTestExpression(node) { | |
return node.parent && | |
TEST_CONDITION_PARENT_TYPES.has(node.parent.type) && | |
node === node.parent.test; | |
} | |
/** | |
* Given an AST node, perform a bottom-up search for the first ancestor that represents a conditional statement. | |
* @param {!Object} node The node to use at the start of the search. | |
* @returns {?Object} The closest ancestor node that represents a conditional statement. | |
*/ | |
function findConditionalAncestor(node) { | |
let currentAncestor = node; | |
do { | |
if (isConditionalTestExpression(currentAncestor)) { | |
return currentAncestor.parent; | |
} | |
} while ((currentAncestor = currentAncestor.parent) && !astUtils.isFunction(currentAncestor)); | |
return null; | |
} | |
/** | |
* Check whether the code represented by an AST node is enclosed in two sets of parentheses. | |
* @param {!Object} node The node to test. | |
* @returns {boolean} `true` if the code is enclosed in two sets of parentheses; otherwise, `false`. | |
*/ | |
function isParenthesisedTwice(node) { | |
const previousToken = sourceCode.getTokenBefore(node, 1), | |
nextToken = sourceCode.getTokenAfter(node, 1); | |
return astUtils.isParenthesised(sourceCode, node) && | |
previousToken && astUtils.isOpeningParenToken(previousToken) && previousToken.range[1] <= node.range[0] && | |
astUtils.isClosingParenToken(nextToken) && nextToken.range[0] >= node.range[1]; | |
} | |
/** | |
* Check a conditional statement's test expression for top-level assignments that are not enclosed in parentheses. | |
* @param {!Object} node The node for the conditional statement. | |
* @returns {void} | |
*/ | |
function testForAssign(node) { | |
if (node.test && | |
(node.test.type === "AssignmentExpression") && | |
(node.type === "ForStatement" | |
? !astUtils.isParenthesised(sourceCode, node.test) | |
: !isParenthesisedTwice(node.test) | |
) | |
) { | |
context.report({ | |
node: node.test, | |
messageId: "missing" | |
}); | |
} | |
} | |
/** | |
* Check whether an assignment expression is descended from a conditional statement's test expression. | |
* @param {!Object} node The node for the assignment expression. | |
* @returns {void} | |
*/ | |
function testForConditionalAncestor(node) { | |
const ancestor = findConditionalAncestor(node); | |
if (ancestor) { | |
context.report({ | |
node, | |
messageId: "unexpected", | |
data: { | |
type: NODE_DESCRIPTIONS[ancestor.type] || ancestor.type | |
} | |
}); | |
} | |
} | |
if (prohibitAssign === "always") { | |
return { | |
AssignmentExpression: testForConditionalAncestor | |
}; | |
} | |
return { | |
DoWhileStatement: testForAssign, | |
ForStatement: testForAssign, | |
IfStatement: testForAssign, | |
WhileStatement: testForAssign, | |
ConditionalExpression: testForAssign | |
}; | |
} | |
}; |