@crescware/eslint-plugin-crescware-iteration-var-names 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +275 -20
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ const TARGET_METHODS = new Set([
|
|
|
13
13
|
"reduceRight",
|
|
14
14
|
"sort",
|
|
15
15
|
]);
|
|
16
|
+
const FOR_ALLOWED_INNER = new Set(["k", "v", "i"]);
|
|
16
17
|
const expectedNamesFor = (kind) => {
|
|
17
18
|
switch (kind) {
|
|
18
19
|
case "reduce-sync":
|
|
@@ -71,19 +72,195 @@ const isIdentifier = (node) => {
|
|
|
71
72
|
node.type === "Identifier" &&
|
|
72
73
|
typeof node.name === "string");
|
|
73
74
|
};
|
|
75
|
+
const collectPatternIdentifiers = (node) => {
|
|
76
|
+
if (node === null) {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
switch (node.type) {
|
|
80
|
+
case "Identifier":
|
|
81
|
+
if (typeof node.name !== "string") {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
return [node];
|
|
85
|
+
case "ArrayPattern":
|
|
86
|
+
return (node.elements ?? []).flatMap((el) => collectPatternIdentifiers(el));
|
|
87
|
+
case "ObjectPattern":
|
|
88
|
+
return (node.properties ?? []).flatMap((v) => {
|
|
89
|
+
if (v.type === "Property") {
|
|
90
|
+
return collectPatternIdentifiers(v.value);
|
|
91
|
+
}
|
|
92
|
+
if (v.type === "RestElement") {
|
|
93
|
+
return collectPatternIdentifiers(v.argument);
|
|
94
|
+
}
|
|
95
|
+
return [];
|
|
96
|
+
});
|
|
97
|
+
case "RestElement":
|
|
98
|
+
return collectPatternIdentifiers(node.argument ?? null);
|
|
99
|
+
case "AssignmentPattern":
|
|
100
|
+
return collectPatternIdentifiers(node.left ?? null);
|
|
101
|
+
default:
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const collectForLoopVarIdentifiers = (node) => {
|
|
106
|
+
if (node.type === "ForStatement") {
|
|
107
|
+
const init = node.init;
|
|
108
|
+
if (init === null || init === undefined) {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
if (init.type !== "VariableDeclaration") {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
const decl = init;
|
|
115
|
+
return decl.declarations.flatMap((v) => collectPatternIdentifiers(v.id));
|
|
116
|
+
}
|
|
117
|
+
const left = node.left;
|
|
118
|
+
if (left.type === "VariableDeclaration") {
|
|
119
|
+
const decl = left;
|
|
120
|
+
return decl.declarations.flatMap((v) => collectPatternIdentifiers(v.id));
|
|
121
|
+
}
|
|
122
|
+
return [];
|
|
123
|
+
};
|
|
124
|
+
const forKindOf = (node) => {
|
|
125
|
+
if (node.type === "ForOfStatement") {
|
|
126
|
+
return "for-of";
|
|
127
|
+
}
|
|
128
|
+
if (node.type === "ForInStatement") {
|
|
129
|
+
return "for-in";
|
|
130
|
+
}
|
|
131
|
+
return "for";
|
|
132
|
+
};
|
|
133
|
+
const DISCARD_NAME = "_";
|
|
134
|
+
const hasIdentifierUseIn = (root, name) => {
|
|
135
|
+
if (root === null || root === undefined) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
if (typeof root !== "object") {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
if (Array.isArray(root)) {
|
|
142
|
+
for (const item of root) {
|
|
143
|
+
if (hasIdentifierUseIn(item, name)) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
const node = root;
|
|
150
|
+
const type = node.type;
|
|
151
|
+
if (type === "Identifier" && node.name === name) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
if (type === "VariableDeclarator") {
|
|
155
|
+
return hasIdentifierUseIn(node.init, name);
|
|
156
|
+
}
|
|
157
|
+
if (type === "FunctionDeclaration" ||
|
|
158
|
+
type === "FunctionExpression" ||
|
|
159
|
+
type === "ArrowFunctionExpression") {
|
|
160
|
+
return hasIdentifierUseIn(node.body, name);
|
|
161
|
+
}
|
|
162
|
+
if (type === "MemberExpression") {
|
|
163
|
+
if (hasIdentifierUseIn(node.object, name)) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
if (node.computed === true) {
|
|
167
|
+
return hasIdentifierUseIn(node.property, name);
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
if (type === "Property") {
|
|
172
|
+
if (node.computed === true && hasIdentifierUseIn(node.key, name)) {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
return hasIdentifierUseIn(node.value, name);
|
|
176
|
+
}
|
|
177
|
+
if (type === "ForStatement") {
|
|
178
|
+
const init = node.init;
|
|
179
|
+
if (init !== null &&
|
|
180
|
+
init !== undefined &&
|
|
181
|
+
init.type !== "VariableDeclaration") {
|
|
182
|
+
if (hasIdentifierUseIn(init, name)) {
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (hasIdentifierUseIn(node.test, name)) {
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
if (hasIdentifierUseIn(node.update, name)) {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
return hasIdentifierUseIn(node.body, name);
|
|
193
|
+
}
|
|
194
|
+
if (type === "ForOfStatement" || type === "ForInStatement") {
|
|
195
|
+
const left = node.left;
|
|
196
|
+
if (left !== null &&
|
|
197
|
+
left !== undefined &&
|
|
198
|
+
left.type !== "VariableDeclaration") {
|
|
199
|
+
if (hasIdentifierUseIn(left, name)) {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (hasIdentifierUseIn(node.right, name)) {
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
return hasIdentifierUseIn(node.body, name);
|
|
207
|
+
}
|
|
208
|
+
if (type === "CatchClause") {
|
|
209
|
+
return hasIdentifierUseIn(node.body, name);
|
|
210
|
+
}
|
|
211
|
+
if (type === "ImportSpecifier" ||
|
|
212
|
+
type === "ImportDefaultSpecifier" ||
|
|
213
|
+
type === "ImportNamespaceSpecifier" ||
|
|
214
|
+
type === "LabeledStatement" ||
|
|
215
|
+
type === "BreakStatement" ||
|
|
216
|
+
type === "ContinueStatement") {
|
|
217
|
+
return hasIdentifierUseIn(node.body, name);
|
|
218
|
+
}
|
|
219
|
+
if (type === "ClassDeclaration" || type === "ClassExpression") {
|
|
220
|
+
if (hasIdentifierUseIn(node.superClass, name)) {
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
return hasIdentifierUseIn(node.body, name);
|
|
224
|
+
}
|
|
225
|
+
const indexed = node;
|
|
226
|
+
for (const key of Object.keys(indexed)) {
|
|
227
|
+
if (key === "loc" ||
|
|
228
|
+
key === "range" ||
|
|
229
|
+
key === "parent" ||
|
|
230
|
+
key === "type") {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (hasIdentifierUseIn(indexed[key], name)) {
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return false;
|
|
238
|
+
};
|
|
239
|
+
const outerDescriptorOf = (frame) => {
|
|
240
|
+
if (frame.kind === "array-callback") {
|
|
241
|
+
return `Array.prototype.${frame.methodName} callback`;
|
|
242
|
+
}
|
|
243
|
+
return `${frame.kind} loop`;
|
|
244
|
+
};
|
|
74
245
|
const rule = {
|
|
75
246
|
meta: {
|
|
76
247
|
type: "suggestion",
|
|
77
248
|
docs: {
|
|
78
|
-
description: "
|
|
249
|
+
description: "Ban thoughtless single-character variable names in iteration contexts (Array.prototype callbacks, for / for-of / for-in).",
|
|
79
250
|
},
|
|
80
251
|
schema: [],
|
|
81
252
|
},
|
|
82
253
|
create(context) {
|
|
83
|
-
const
|
|
254
|
+
const frameStack = [];
|
|
84
255
|
const callbackToFrame = new WeakMap();
|
|
85
|
-
const
|
|
86
|
-
|
|
256
|
+
const forNodeToFrame = new WeakMap();
|
|
257
|
+
const isReduceFirstPosition = (frame, paramIndex) => {
|
|
258
|
+
return (paramIndex === 0 &&
|
|
259
|
+
(frame.methodKind === "reduce-sync" ||
|
|
260
|
+
frame.methodKind === "reduce-async"));
|
|
261
|
+
};
|
|
262
|
+
const reportInnerArrayCallback = (param, frame, paramIndex, body) => {
|
|
263
|
+
const expected = expectedNamesFor(frame.methodKind)[paramIndex];
|
|
87
264
|
if (expected === undefined) {
|
|
88
265
|
return;
|
|
89
266
|
}
|
|
@@ -93,20 +270,60 @@ const rule = {
|
|
|
93
270
|
if (param.name === expected) {
|
|
94
271
|
return;
|
|
95
272
|
}
|
|
273
|
+
if (param.name === DISCARD_NAME &&
|
|
274
|
+
!isReduceFirstPosition(frame, paramIndex)) {
|
|
275
|
+
if (!hasIdentifierUseIn(body, DISCARD_NAME)) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
context.report({
|
|
279
|
+
message: `Discard parameter '_' is referenced inside the body; rename it to a meaningful name.`,
|
|
280
|
+
node: param,
|
|
281
|
+
});
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
96
284
|
context.report({
|
|
97
|
-
message: `Array.prototype.${methodName} expects '${expected}' for argument ${paramIndex + 1} (got: '${param.name}').`,
|
|
285
|
+
message: `Array.prototype.${frame.methodName} expects '${expected}' for argument ${paramIndex + 1} (got: '${param.name}').`,
|
|
98
286
|
node: param,
|
|
99
287
|
});
|
|
100
288
|
};
|
|
101
|
-
const
|
|
289
|
+
const reportInnerFor = (param, frame, body) => {
|
|
102
290
|
if (param.name.length >= 2) {
|
|
103
291
|
return;
|
|
104
292
|
}
|
|
293
|
+
if (FOR_ALLOWED_INNER.has(param.name)) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (param.name === DISCARD_NAME) {
|
|
297
|
+
if (!hasIdentifierUseIn(body, DISCARD_NAME)) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
context.report({
|
|
301
|
+
message: `Discard parameter '_' is referenced inside the body; rename it to a meaningful name.`,
|
|
302
|
+
node: param,
|
|
303
|
+
});
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
105
306
|
context.report({
|
|
106
|
-
message:
|
|
307
|
+
message: `${frame.kind} loop variable '${param.name}' is not allowed; use 'k', 'v', 'i' or a meaningful name with 2 or more characters.`,
|
|
107
308
|
node: param,
|
|
108
309
|
});
|
|
109
310
|
};
|
|
311
|
+
const reportOuter = (param, frame) => {
|
|
312
|
+
if (param.name.length >= 2) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const descriptor = outerDescriptorOf(frame);
|
|
316
|
+
context.report({
|
|
317
|
+
message: `Avoid the single-character name '${param.name}' on an outer ${descriptor}; use a meaningful name with 2 or more characters.`,
|
|
318
|
+
node: param,
|
|
319
|
+
});
|
|
320
|
+
};
|
|
321
|
+
const markParentHasNested = () => {
|
|
322
|
+
const top = frameStack[frameStack.length - 1];
|
|
323
|
+
if (top !== undefined) {
|
|
324
|
+
top.hasNested = true;
|
|
325
|
+
}
|
|
326
|
+
};
|
|
110
327
|
const onCallEnter = (node) => {
|
|
111
328
|
const methodName = getTargetMethodName(node);
|
|
112
329
|
if (methodName === null) {
|
|
@@ -116,46 +333,78 @@ const rule = {
|
|
|
116
333
|
if (callback === null) {
|
|
117
334
|
return;
|
|
118
335
|
}
|
|
119
|
-
const
|
|
120
|
-
if (
|
|
336
|
+
const methodKind = methodKindOf(methodName, callback.async === true);
|
|
337
|
+
if (methodKind === null) {
|
|
121
338
|
return;
|
|
122
339
|
}
|
|
123
|
-
const frame = {
|
|
340
|
+
const frame = {
|
|
341
|
+
kind: "array-callback",
|
|
342
|
+
methodKind,
|
|
343
|
+
methodName,
|
|
344
|
+
hasNested: false,
|
|
345
|
+
};
|
|
124
346
|
callbackToFrame.set(callback, frame);
|
|
125
|
-
|
|
126
|
-
if (top !== undefined) {
|
|
127
|
-
top.hasNested = true;
|
|
128
|
-
}
|
|
347
|
+
markParentHasNested();
|
|
129
348
|
};
|
|
130
349
|
const onFunctionEnter = (node) => {
|
|
131
350
|
const frame = callbackToFrame.get(node);
|
|
132
351
|
if (frame === undefined) {
|
|
133
352
|
return;
|
|
134
353
|
}
|
|
135
|
-
|
|
354
|
+
frameStack.push(frame);
|
|
136
355
|
};
|
|
137
356
|
const onFunctionExit = (node) => {
|
|
138
357
|
const frame = callbackToFrame.get(node);
|
|
139
358
|
if (frame === undefined) {
|
|
140
359
|
return;
|
|
141
360
|
}
|
|
142
|
-
const popped =
|
|
361
|
+
const popped = frameStack.pop();
|
|
143
362
|
if (popped !== frame) {
|
|
144
363
|
return;
|
|
145
364
|
}
|
|
146
|
-
const expected = expectedNamesFor(frame.
|
|
365
|
+
const expected = expectedNamesFor(frame.methodKind);
|
|
147
366
|
const params = node.params;
|
|
148
367
|
const limit = Math.min(params.length, expected.length);
|
|
368
|
+
const body = node.body;
|
|
149
369
|
for (let i = 0; i < limit; i++) {
|
|
150
370
|
const param = params[i];
|
|
151
371
|
if (!isIdentifier(param)) {
|
|
152
372
|
continue;
|
|
153
373
|
}
|
|
154
374
|
if (frame.hasNested) {
|
|
155
|
-
reportOuter(param, frame
|
|
375
|
+
reportOuter(param, frame);
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
reportInnerArrayCallback(param, frame, i, body);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
const onForEnter = (node) => {
|
|
383
|
+
const frame = {
|
|
384
|
+
kind: forKindOf(node),
|
|
385
|
+
hasNested: false,
|
|
386
|
+
};
|
|
387
|
+
markParentHasNested();
|
|
388
|
+
frameStack.push(frame);
|
|
389
|
+
forNodeToFrame.set(node, frame);
|
|
390
|
+
};
|
|
391
|
+
const onForExit = (node) => {
|
|
392
|
+
const frame = forNodeToFrame.get(node);
|
|
393
|
+
if (frame === undefined) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
const popped = frameStack.pop();
|
|
397
|
+
if (popped !== frame) {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const params = collectForLoopVarIdentifiers(node);
|
|
401
|
+
const body = node.body;
|
|
402
|
+
for (const param of params) {
|
|
403
|
+
if (frame.hasNested) {
|
|
404
|
+
reportOuter(param, frame);
|
|
156
405
|
}
|
|
157
406
|
else {
|
|
158
|
-
|
|
407
|
+
reportInnerFor(param, frame, body);
|
|
159
408
|
}
|
|
160
409
|
}
|
|
161
410
|
};
|
|
@@ -165,13 +414,19 @@ const rule = {
|
|
|
165
414
|
"ArrowFunctionExpression:exit": onFunctionExit,
|
|
166
415
|
FunctionExpression: onFunctionEnter,
|
|
167
416
|
"FunctionExpression:exit": onFunctionExit,
|
|
417
|
+
ForStatement: onForEnter,
|
|
418
|
+
"ForStatement:exit": onForExit,
|
|
419
|
+
ForOfStatement: onForEnter,
|
|
420
|
+
"ForOfStatement:exit": onForExit,
|
|
421
|
+
ForInStatement: onForEnter,
|
|
422
|
+
"ForInStatement:exit": onForExit,
|
|
168
423
|
};
|
|
169
424
|
},
|
|
170
425
|
};
|
|
171
426
|
const plugin = {
|
|
172
427
|
meta: { name: "crescware-iteration-var-names" },
|
|
173
428
|
rules: {
|
|
174
|
-
"
|
|
429
|
+
"iteration-var-names": rule,
|
|
175
430
|
},
|
|
176
431
|
};
|
|
177
432
|
export default plugin;
|