*/
function createReferenceMap(scope, outReferenceMap = new Map()) {
for (const reference of scope.references) {
+ if (reference.resolved === null) {
+ continue;
+ }
+
outReferenceMap.set(reference.identifier, reference);
}
for (const childScope of scope.childScopes) {
reference.from.variableScope === functionScope);
}
+/**
+ * Represents segment information.
+ */
class SegmentInfo {
constructor() {
this.info = new WeakMap();
* @returns {void}
*/
initialize(segment) {
- const outdatedReadVariableNames = new Set();
- const freshReadVariableNames = new Set();
+ const outdatedReadVariables = new Set();
+ const freshReadVariables = new Set();
for (const prevSegment of segment.prevSegments) {
const info = this.info.get(prevSegment);
if (info) {
- info.outdatedReadVariableNames.forEach(Set.prototype.add, outdatedReadVariableNames);
- info.freshReadVariableNames.forEach(Set.prototype.add, freshReadVariableNames);
+ info.outdatedReadVariables.forEach(Set.prototype.add, outdatedReadVariables);
+ info.freshReadVariables.forEach(Set.prototype.add, freshReadVariables);
}
}
- this.info.set(segment, { outdatedReadVariableNames, freshReadVariableNames });
+ this.info.set(segment, { outdatedReadVariables, freshReadVariables });
}
/**
* Mark a given variable as read on given segments.
* @param {PathSegment[]} segments The segments that it read the variable on.
- * @param {string} variableName The variable name to be read.
+ * @param {Variable} variable The variable to be read.
* @returns {void}
*/
- markAsRead(segments, variableName) {
+ markAsRead(segments, variable) {
for (const segment of segments) {
const info = this.info.get(segment);
if (info) {
- info.freshReadVariableNames.add(variableName);
+ info.freshReadVariables.add(variable);
+
+ // If a variable is freshly read again, then it's no more out-dated.
+ info.outdatedReadVariables.delete(variable);
}
}
}
/**
- * Move `freshReadVariableNames` to `outdatedReadVariableNames`.
+ * Move `freshReadVariables` to `outdatedReadVariables`.
* @param {PathSegment[]} segments The segments to process.
* @returns {void}
*/
const info = this.info.get(segment);
if (info) {
- info.freshReadVariableNames.forEach(Set.prototype.add, info.outdatedReadVariableNames);
- info.freshReadVariableNames.clear();
+ info.freshReadVariables.forEach(Set.prototype.add, info.outdatedReadVariables);
+ info.freshReadVariables.clear();
}
}
}
/**
* Check if a given variable is outdated on the current segments.
* @param {PathSegment[]} segments The current segments.
- * @param {string} variableName The variable name to check.
+ * @param {Variable} variable The variable to check.
* @returns {boolean} `true` if the variable is outdated on the segments.
*/
- isOutdated(segments, variableName) {
+ isOutdated(segments, variable) {
for (const segment of segments) {
const info = this.info.get(segment);
- if (info && info.outdatedReadVariableNames.has(variableName)) {
+ if (info && info.outdatedReadVariables.has(variable)) {
return true;
}
}
// Rule Definition
//------------------------------------------------------------------------------
+/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "problem",
docs: {
- description: "disallow assignments that can lead to race conditions due to usage of `await` or `yield`",
- category: "Possible Errors",
+ description: "Disallow assignments that can lead to race conditions due to usage of `await` or `yield`",
recommended: false,
- url: "https://eslint.org/docs/rules/require-atomic-updates"
+ url: "https://eslint.org/docs/latest/rules/require-atomic-updates"
},
fixable: null,
- schema: [],
+
+ schema: [{
+ type: "object",
+ properties: {
+ allowProperties: {
+ type: "boolean",
+ default: false
+ }
+ },
+ additionalProperties: false
+ }],
messages: {
- nonAtomicUpdate: "Possible race condition: `{{value}}` might be reassigned based on an outdated value of `{{value}}`."
+ nonAtomicUpdate: "Possible race condition: `{{value}}` might be reassigned based on an outdated value of `{{value}}`.",
+ nonAtomicObjectUpdate: "Possible race condition: `{{value}}` might be assigned based on an outdated state of `{{object}}`."
}
},
create(context) {
- const sourceCode = context.getSourceCode();
+ const allowProperties = !!context.options[0] && context.options[0].allowProperties;
+
+ const sourceCode = context.sourceCode;
const assignmentReferences = new Map();
const segmentInfo = new SegmentInfo();
let stack = null;
return {
- onCodePathStart(codePath) {
- const scope = context.getScope();
+ onCodePathStart(codePath, node) {
+ const scope = sourceCode.getScope(node);
const shouldVerify =
scope.type === "function" &&
(scope.block.async || scope.block.generator);
if (!reference) {
return;
}
- const name = reference.identifier.name;
const variable = reference.resolved;
const writeExpr = getWriteExpr(reference);
const isMemberAccess = reference.identifier.parent.type === "MemberExpression";
// Add a fresh read variable.
if (reference.isRead() && !(writeExpr && writeExpr.parent.operator === "=")) {
- segmentInfo.markAsRead(codePath.currentSegments, name);
+ segmentInfo.markAsRead(codePath.currentSegments, variable);
}
/*
/*
* Verify assignments.
- * If the reference exists in `outdatedReadVariableNames` list, report it.
+ * If the reference exists in `outdatedReadVariables` list, report it.
*/
":expression:exit"(node) {
const { codePath, referenceMap } = stack;
assignmentReferences.delete(node);
for (const reference of references) {
- const name = reference.identifier.name;
-
- if (segmentInfo.isOutdated(codePath.currentSegments, name)) {
- context.report({
- node: node.parent,
- messageId: "nonAtomicUpdate",
- data: {
- value: sourceCode.getText(node.parent.left)
- }
- });
+ const variable = reference.resolved;
+
+ if (segmentInfo.isOutdated(codePath.currentSegments, variable)) {
+ if (node.parent.left === reference.identifier) {
+ context.report({
+ node: node.parent,
+ messageId: "nonAtomicUpdate",
+ data: {
+ value: variable.name
+ }
+ });
+ } else if (!allowProperties) {
+ context.report({
+ node: node.parent,
+ messageId: "nonAtomicObjectUpdate",
+ data: {
+ value: sourceCode.getText(node.parent.left),
+ object: variable.name
+ }
+ });
+ }
+
}
}
}