first commit
This commit is contained in:
360
app_vue/node_modules/eslint/lib/rules/no-unmodified-loop-condition.js
generated
vendored
Normal file
360
app_vue/node_modules/eslint/lib/rules/no-unmodified-loop-condition.js
generated
vendored
Normal file
@ -0,0 +1,360 @@
|
||||
/**
|
||||
* @fileoverview Rule to disallow use of unmodified expressions in loop conditions
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const Traverser = require("../shared/traverser"),
|
||||
astUtils = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const SENTINEL_PATTERN = /(?:(?:Call|Class|Function|Member|New|Yield)Expression|Statement|Declaration)$/u;
|
||||
const LOOP_PATTERN = /^(?:DoWhile|For|While)Statement$/u; // for-in/of statements don't have `test` property.
|
||||
const GROUP_PATTERN = /^(?:BinaryExpression|ConditionalExpression)$/u;
|
||||
const SKIP_PATTERN = /^(?:ArrowFunction|Class|Function)Expression$/u;
|
||||
const DYNAMIC_PATTERN = /^(?:Call|Member|New|TaggedTemplate|Yield)Expression$/u;
|
||||
|
||||
/**
|
||||
* @typedef {Object} LoopConditionInfo
|
||||
* @property {eslint-scope.Reference} reference - The reference.
|
||||
* @property {ASTNode} group - BinaryExpression or ConditionalExpression nodes
|
||||
* that the reference is belonging to.
|
||||
* @property {Function} isInLoop - The predicate which checks a given reference
|
||||
* is in this loop.
|
||||
* @property {boolean} modified - The flag that the reference is modified in
|
||||
* this loop.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks whether or not a given reference is a write reference.
|
||||
* @param {eslint-scope.Reference} reference A reference to check.
|
||||
* @returns {boolean} `true` if the reference is a write reference.
|
||||
*/
|
||||
function isWriteReference(reference) {
|
||||
if (reference.init) {
|
||||
const def = reference.resolved && reference.resolved.defs[0];
|
||||
|
||||
if (!def || def.type !== "Variable" || def.parent.kind !== "var") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return reference.isWrite();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a given loop condition info does not have the modified
|
||||
* flag.
|
||||
* @param {LoopConditionInfo} condition A loop condition info to check.
|
||||
* @returns {boolean} `true` if the loop condition info is "unmodified".
|
||||
*/
|
||||
function isUnmodified(condition) {
|
||||
return !condition.modified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a given loop condition info does not have the modified
|
||||
* flag and does not have the group this condition belongs to.
|
||||
* @param {LoopConditionInfo} condition A loop condition info to check.
|
||||
* @returns {boolean} `true` if the loop condition info is "unmodified".
|
||||
*/
|
||||
function isUnmodifiedAndNotBelongToGroup(condition) {
|
||||
return !(condition.modified || condition.group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a given reference is inside of a given node.
|
||||
* @param {ASTNode} node A node to check.
|
||||
* @param {eslint-scope.Reference} reference A reference to check.
|
||||
* @returns {boolean} `true` if the reference is inside of the node.
|
||||
*/
|
||||
function isInRange(node, reference) {
|
||||
const or = node.range;
|
||||
const ir = reference.identifier.range;
|
||||
|
||||
return or[0] <= ir[0] && ir[1] <= or[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a given reference is inside of a loop node's condition.
|
||||
* @param {ASTNode} node A node to check.
|
||||
* @param {eslint-scope.Reference} reference A reference to check.
|
||||
* @returns {boolean} `true` if the reference is inside of the loop node's
|
||||
* condition.
|
||||
*/
|
||||
const isInLoop = {
|
||||
WhileStatement: isInRange,
|
||||
DoWhileStatement: isInRange,
|
||||
ForStatement(node, reference) {
|
||||
return (
|
||||
isInRange(node, reference) &&
|
||||
!(node.init && isInRange(node.init, reference))
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the function which encloses a given reference.
|
||||
* This supports only FunctionDeclaration.
|
||||
* @param {eslint-scope.Reference} reference A reference to get.
|
||||
* @returns {ASTNode|null} The function node or null.
|
||||
*/
|
||||
function getEncloseFunctionDeclaration(reference) {
|
||||
let node = reference.identifier;
|
||||
|
||||
while (node) {
|
||||
if (node.type === "FunctionDeclaration") {
|
||||
return node.id ? node : null;
|
||||
}
|
||||
|
||||
node = node.parent;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the "modified" flags of given loop conditions with given modifiers.
|
||||
* @param {LoopConditionInfo[]} conditions The loop conditions to be updated.
|
||||
* @param {eslint-scope.Reference[]} modifiers The references to update.
|
||||
* @returns {void}
|
||||
*/
|
||||
function updateModifiedFlag(conditions, modifiers) {
|
||||
|
||||
for (let i = 0; i < conditions.length; ++i) {
|
||||
const condition = conditions[i];
|
||||
|
||||
for (let j = 0; !condition.modified && j < modifiers.length; ++j) {
|
||||
const modifier = modifiers[j];
|
||||
let funcNode, funcVar;
|
||||
|
||||
/*
|
||||
* Besides checking for the condition being in the loop, we want to
|
||||
* check the function that this modifier is belonging to is called
|
||||
* in the loop.
|
||||
* FIXME: This should probably be extracted to a function.
|
||||
*/
|
||||
const inLoop = condition.isInLoop(modifier) || Boolean(
|
||||
(funcNode = getEncloseFunctionDeclaration(modifier)) &&
|
||||
(funcVar = astUtils.getVariableByName(modifier.from.upper, funcNode.id.name)) &&
|
||||
funcVar.references.some(condition.isInLoop)
|
||||
);
|
||||
|
||||
condition.modified = inLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "problem",
|
||||
|
||||
docs: {
|
||||
description: "disallow unmodified loop conditions",
|
||||
category: "Best Practices",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/rules/no-unmodified-loop-condition"
|
||||
},
|
||||
|
||||
schema: [],
|
||||
|
||||
messages: {
|
||||
loopConditionNotModified: "'{{name}}' is not modified in this loop."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const sourceCode = context.getSourceCode();
|
||||
let groupMap = null;
|
||||
|
||||
/**
|
||||
* Reports a given condition info.
|
||||
* @param {LoopConditionInfo} condition A loop condition info to report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function report(condition) {
|
||||
const node = condition.reference.identifier;
|
||||
|
||||
context.report({
|
||||
node,
|
||||
messageId: "loopConditionNotModified",
|
||||
data: node
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers given conditions to the group the condition belongs to.
|
||||
* @param {LoopConditionInfo[]} conditions A loop condition info to
|
||||
* register.
|
||||
* @returns {void}
|
||||
*/
|
||||
function registerConditionsToGroup(conditions) {
|
||||
for (let i = 0; i < conditions.length; ++i) {
|
||||
const condition = conditions[i];
|
||||
|
||||
if (condition.group) {
|
||||
let group = groupMap.get(condition.group);
|
||||
|
||||
if (!group) {
|
||||
group = [];
|
||||
groupMap.set(condition.group, group);
|
||||
}
|
||||
group.push(condition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports references which are inside of unmodified groups.
|
||||
* @param {LoopConditionInfo[]} conditions A loop condition info to report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkConditionsInGroup(conditions) {
|
||||
if (conditions.every(isUnmodified)) {
|
||||
conditions.forEach(report);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a given group node has any dynamic elements.
|
||||
* @param {ASTNode} root A node to check.
|
||||
* This node is one of BinaryExpression or ConditionalExpression.
|
||||
* @returns {boolean} `true` if the node is dynamic.
|
||||
*/
|
||||
function hasDynamicExpressions(root) {
|
||||
let retv = false;
|
||||
|
||||
Traverser.traverse(root, {
|
||||
visitorKeys: sourceCode.visitorKeys,
|
||||
enter(node) {
|
||||
if (DYNAMIC_PATTERN.test(node.type)) {
|
||||
retv = true;
|
||||
this.break();
|
||||
} else if (SKIP_PATTERN.test(node.type)) {
|
||||
this.skip();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return retv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the loop condition information from a given reference.
|
||||
* @param {eslint-scope.Reference} reference A reference to create.
|
||||
* @returns {LoopConditionInfo|null} Created loop condition info, or null.
|
||||
*/
|
||||
function toLoopCondition(reference) {
|
||||
if (reference.init) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let group = null;
|
||||
let child = reference.identifier;
|
||||
let node = child.parent;
|
||||
|
||||
while (node) {
|
||||
if (SENTINEL_PATTERN.test(node.type)) {
|
||||
if (LOOP_PATTERN.test(node.type) && node.test === child) {
|
||||
|
||||
// This reference is inside of a loop condition.
|
||||
return {
|
||||
reference,
|
||||
group,
|
||||
isInLoop: isInLoop[node.type].bind(null, node),
|
||||
modified: false
|
||||
};
|
||||
}
|
||||
|
||||
// This reference is outside of a loop condition.
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* If it's inside of a group, OK if either operand is modified.
|
||||
* So stores the group this reference belongs to.
|
||||
*/
|
||||
if (GROUP_PATTERN.test(node.type)) {
|
||||
|
||||
// If this expression is dynamic, no need to check.
|
||||
if (hasDynamicExpressions(node)) {
|
||||
break;
|
||||
} else {
|
||||
group = node;
|
||||
}
|
||||
}
|
||||
|
||||
child = node;
|
||||
node = node.parent;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds unmodified references which are inside of a loop condition.
|
||||
* Then reports the references which are outside of groups.
|
||||
* @param {eslint-scope.Variable} variable A variable to report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkReferences(variable) {
|
||||
|
||||
// Gets references that exist in loop conditions.
|
||||
const conditions = variable
|
||||
.references
|
||||
.map(toLoopCondition)
|
||||
.filter(Boolean);
|
||||
|
||||
if (conditions.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Registers the conditions to belonging groups.
|
||||
registerConditionsToGroup(conditions);
|
||||
|
||||
// Check the conditions are modified.
|
||||
const modifiers = variable.references.filter(isWriteReference);
|
||||
|
||||
if (modifiers.length > 0) {
|
||||
updateModifiedFlag(conditions, modifiers);
|
||||
}
|
||||
|
||||
/*
|
||||
* Reports the conditions which are not belonging to groups.
|
||||
* Others will be reported after all variables are done.
|
||||
*/
|
||||
conditions
|
||||
.filter(isUnmodifiedAndNotBelongToGroup)
|
||||
.forEach(report);
|
||||
}
|
||||
|
||||
return {
|
||||
"Program:exit"() {
|
||||
const queue = [context.getScope()];
|
||||
|
||||
groupMap = new Map();
|
||||
|
||||
let scope;
|
||||
|
||||
while ((scope = queue.pop())) {
|
||||
queue.push(...scope.childScopes);
|
||||
scope.variables.forEach(checkReferences);
|
||||
}
|
||||
|
||||
groupMap.forEach(checkConditionsInGroup);
|
||||
groupMap = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user