Actualizacion maquina principal
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / require-atomic-updates.js
1 /**
2  * @fileoverview disallow assignments that can lead to race conditions due to usage of `await` or `yield`
3  * @author Teddy Katz
4  * @author Toru Nagashima
5  */
6 "use strict";
7
8 /**
9  * Make the map from identifiers to each reference.
10  * @param {escope.Scope} scope The scope to get references.
11  * @param {Map<Identifier, escope.Reference>} [outReferenceMap] The map from identifier nodes to each reference object.
12  * @returns {Map<Identifier, escope.Reference>} `referenceMap`.
13  */
14 function createReferenceMap(scope, outReferenceMap = new Map()) {
15     for (const reference of scope.references) {
16         outReferenceMap.set(reference.identifier, reference);
17     }
18     for (const childScope of scope.childScopes) {
19         if (childScope.type !== "function") {
20             createReferenceMap(childScope, outReferenceMap);
21         }
22     }
23
24     return outReferenceMap;
25 }
26
27 /**
28  * Get `reference.writeExpr` of a given reference.
29  * If it's the read reference of MemberExpression in LHS, returns RHS in order to address `a.b = await a`
30  * @param {escope.Reference} reference The reference to get.
31  * @returns {Expression|null} The `reference.writeExpr`.
32  */
33 function getWriteExpr(reference) {
34     if (reference.writeExpr) {
35         return reference.writeExpr;
36     }
37     let node = reference.identifier;
38
39     while (node) {
40         const t = node.parent.type;
41
42         if (t === "AssignmentExpression" && node.parent.left === node) {
43             return node.parent.right;
44         }
45         if (t === "MemberExpression" && node.parent.object === node) {
46             node = node.parent;
47             continue;
48         }
49
50         break;
51     }
52
53     return null;
54 }
55
56 /**
57  * Checks if an expression is a variable that can only be observed within the given function.
58  * @param {Variable|null} variable The variable to check
59  * @param {boolean} isMemberAccess If `true` then this is a member access.
60  * @returns {boolean} `true` if the variable is local to the given function, and is never referenced in a closure.
61  */
62 function isLocalVariableWithoutEscape(variable, isMemberAccess) {
63     if (!variable) {
64         return false; // A global variable which was not defined.
65     }
66
67     // If the reference is a property access and the variable is a parameter, it handles the variable is not local.
68     if (isMemberAccess && variable.defs.some(d => d.type === "Parameter")) {
69         return false;
70     }
71
72     const functionScope = variable.scope.variableScope;
73
74     return variable.references.every(reference =>
75         reference.from.variableScope === functionScope);
76 }
77
78 class SegmentInfo {
79     constructor() {
80         this.info = new WeakMap();
81     }
82
83     /**
84      * Initialize the segment information.
85      * @param {PathSegment} segment The segment to initialize.
86      * @returns {void}
87      */
88     initialize(segment) {
89         const outdatedReadVariableNames = new Set();
90         const freshReadVariableNames = new Set();
91
92         for (const prevSegment of segment.prevSegments) {
93             const info = this.info.get(prevSegment);
94
95             if (info) {
96                 info.outdatedReadVariableNames.forEach(Set.prototype.add, outdatedReadVariableNames);
97                 info.freshReadVariableNames.forEach(Set.prototype.add, freshReadVariableNames);
98             }
99         }
100
101         this.info.set(segment, { outdatedReadVariableNames, freshReadVariableNames });
102     }
103
104     /**
105      * Mark a given variable as read on given segments.
106      * @param {PathSegment[]} segments The segments that it read the variable on.
107      * @param {string} variableName The variable name to be read.
108      * @returns {void}
109      */
110     markAsRead(segments, variableName) {
111         for (const segment of segments) {
112             const info = this.info.get(segment);
113
114             if (info) {
115                 info.freshReadVariableNames.add(variableName);
116             }
117         }
118     }
119
120     /**
121      * Move `freshReadVariableNames` to `outdatedReadVariableNames`.
122      * @param {PathSegment[]} segments The segments to process.
123      * @returns {void}
124      */
125     makeOutdated(segments) {
126         for (const segment of segments) {
127             const info = this.info.get(segment);
128
129             if (info) {
130                 info.freshReadVariableNames.forEach(Set.prototype.add, info.outdatedReadVariableNames);
131                 info.freshReadVariableNames.clear();
132             }
133         }
134     }
135
136     /**
137      * Check if a given variable is outdated on the current segments.
138      * @param {PathSegment[]} segments The current segments.
139      * @param {string} variableName The variable name to check.
140      * @returns {boolean} `true` if the variable is outdated on the segments.
141      */
142     isOutdated(segments, variableName) {
143         for (const segment of segments) {
144             const info = this.info.get(segment);
145
146             if (info && info.outdatedReadVariableNames.has(variableName)) {
147                 return true;
148             }
149         }
150         return false;
151     }
152 }
153
154 //------------------------------------------------------------------------------
155 // Rule Definition
156 //------------------------------------------------------------------------------
157
158 module.exports = {
159     meta: {
160         type: "problem",
161
162         docs: {
163             description: "disallow assignments that can lead to race conditions due to usage of `await` or `yield`",
164             category: "Possible Errors",
165             recommended: false,
166             url: "https://eslint.org/docs/rules/require-atomic-updates"
167         },
168
169         fixable: null,
170         schema: [],
171
172         messages: {
173             nonAtomicUpdate: "Possible race condition: `{{value}}` might be reassigned based on an outdated value of `{{value}}`."
174         }
175     },
176
177     create(context) {
178         const sourceCode = context.getSourceCode();
179         const assignmentReferences = new Map();
180         const segmentInfo = new SegmentInfo();
181         let stack = null;
182
183         return {
184             onCodePathStart(codePath) {
185                 const scope = context.getScope();
186                 const shouldVerify =
187                     scope.type === "function" &&
188                     (scope.block.async || scope.block.generator);
189
190                 stack = {
191                     upper: stack,
192                     codePath,
193                     referenceMap: shouldVerify ? createReferenceMap(scope) : null
194                 };
195             },
196             onCodePathEnd() {
197                 stack = stack.upper;
198             },
199
200             // Initialize the segment information.
201             onCodePathSegmentStart(segment) {
202                 segmentInfo.initialize(segment);
203             },
204
205             // Handle references to prepare verification.
206             Identifier(node) {
207                 const { codePath, referenceMap } = stack;
208                 const reference = referenceMap && referenceMap.get(node);
209
210                 // Ignore if this is not a valid variable reference.
211                 if (!reference) {
212                     return;
213                 }
214                 const name = reference.identifier.name;
215                 const variable = reference.resolved;
216                 const writeExpr = getWriteExpr(reference);
217                 const isMemberAccess = reference.identifier.parent.type === "MemberExpression";
218
219                 // Add a fresh read variable.
220                 if (reference.isRead() && !(writeExpr && writeExpr.parent.operator === "=")) {
221                     segmentInfo.markAsRead(codePath.currentSegments, name);
222                 }
223
224                 /*
225                  * Register the variable to verify after ESLint traversed the `writeExpr` node
226                  * if this reference is an assignment to a variable which is referred from other clausure.
227                  */
228                 if (writeExpr &&
229                     writeExpr.parent.right === writeExpr && // ← exclude variable declarations.
230                     !isLocalVariableWithoutEscape(variable, isMemberAccess)
231                 ) {
232                     let refs = assignmentReferences.get(writeExpr);
233
234                     if (!refs) {
235                         refs = [];
236                         assignmentReferences.set(writeExpr, refs);
237                     }
238
239                     refs.push(reference);
240                 }
241             },
242
243             /*
244              * Verify assignments.
245              * If the reference exists in `outdatedReadVariableNames` list, report it.
246              */
247             ":expression:exit"(node) {
248                 const { codePath, referenceMap } = stack;
249
250                 // referenceMap exists if this is in a resumable function scope.
251                 if (!referenceMap) {
252                     return;
253                 }
254
255                 // Mark the read variables on this code path as outdated.
256                 if (node.type === "AwaitExpression" || node.type === "YieldExpression") {
257                     segmentInfo.makeOutdated(codePath.currentSegments);
258                 }
259
260                 // Verify.
261                 const references = assignmentReferences.get(node);
262
263                 if (references) {
264                     assignmentReferences.delete(node);
265
266                     for (const reference of references) {
267                         const name = reference.identifier.name;
268
269                         if (segmentInfo.isOutdated(codePath.currentSegments, name)) {
270                             context.report({
271                                 node: node.parent,
272                                 messageId: "nonAtomicUpdate",
273                                 data: {
274                                     value: sourceCode.getText(node.parent.left)
275                                 }
276                             });
277                         }
278                     }
279                 }
280             }
281         };
282     }
283 };