@blumintinc/eslint-plugin-blumint 1.20.151 → 1.20.152
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/lib/index.js +1 -1
- package/lib/rules/enforce-querykey-ts.js +31 -31
- package/lib/rules/prefer-clone-deep.js +38 -0
- package/package.json +1 -1
- package/release-manifest.json +22 -0
package/lib/index.js
CHANGED
|
@@ -123,20 +123,19 @@ exports.enforceQueryKeyTs = (0, createRule_1.createRule)({
|
|
|
123
123
|
]);
|
|
124
124
|
const sourceCode = context.getSourceCode();
|
|
125
125
|
/**
|
|
126
|
-
* Reports are buffered until the whole file has been walked so
|
|
127
|
-
*
|
|
128
|
-
*
|
|
126
|
+
* Reports are buffered until the whole file has been walked so each fix is
|
|
127
|
+
* planned against the file's complete import picture: what a substituted
|
|
128
|
+
* constant resolves to, and whether the fix has to bring an import with it,
|
|
129
|
+
* are questions about the whole file rather than about the text above the
|
|
130
|
+
* violation.
|
|
129
131
|
*/
|
|
130
132
|
const pendingReports = [];
|
|
131
133
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
* Resolving suppression here keeps a suppressed violation out of the plan
|
|
138
|
-
* entirely: it neither claims the carrier slot nor contributes a specifier
|
|
139
|
-
* to the import, which would otherwise be imported and never used.
|
|
134
|
+
* A violation the author has waived with an inline disable directive is
|
|
135
|
+
* left out of the fix plan, so no constant is resolved for it and no import
|
|
136
|
+
* is written for it (#1410). This is the decision ESLint reaches anyway
|
|
137
|
+
* when it discards a suppressed message together with its fix, taken one
|
|
138
|
+
* step earlier where the rule can see it.
|
|
140
139
|
*/
|
|
141
140
|
const isReportSuppressed = (0, disableDirectives_1.createSuppressionChecker)(context);
|
|
142
141
|
/**
|
|
@@ -264,10 +263,13 @@ exports.enforceQueryKeyTs = (0, createRule_1.createRule)({
|
|
|
264
263
|
: queryKeysSpecifier;
|
|
265
264
|
}
|
|
266
265
|
/**
|
|
267
|
-
* Make
|
|
266
|
+
* Make a substituted constant resolve: extend the file's queryKeys import
|
|
268
267
|
* when there is one to extend, otherwise add a fresh import statement.
|
|
268
|
+
*
|
|
269
|
+
* The caller asks for this only where the constant binds to nothing, so the
|
|
270
|
+
* emitted specifier is never a duplicate of one the file already has.
|
|
269
271
|
*/
|
|
270
|
-
function buildImportFix(fixer,
|
|
272
|
+
function buildImportFix(fixer, constant) {
|
|
271
273
|
const importDeclarations = importDeclarationsOf();
|
|
272
274
|
const queryKeysDeclarations = queryKeysDeclarationsOf();
|
|
273
275
|
const reusable = queryKeysDeclarations.find((declaration) => declaration.importKind !== 'type' &&
|
|
@@ -275,7 +277,7 @@ exports.enforceQueryKeyTs = (0, createRule_1.createRule)({
|
|
|
275
277
|
if (reusable) {
|
|
276
278
|
const namedSpecifiers = reusable.specifiers.filter(isValueImportSpecifier);
|
|
277
279
|
const lastSpecifier = namedSpecifiers[namedSpecifiers.length - 1];
|
|
278
|
-
return fixer.insertTextAfter(lastSpecifier,
|
|
280
|
+
return fixer.insertTextAfter(lastSpecifier, `, ${constant}`);
|
|
279
281
|
}
|
|
280
282
|
// A namespace or type-only queryKeys import cannot take named value
|
|
281
283
|
// specifiers, but its path is proof of how this file reaches the module.
|
|
@@ -283,7 +285,7 @@ exports.enforceQueryKeyTs = (0, createRule_1.createRule)({
|
|
|
283
285
|
if (source === null) {
|
|
284
286
|
return null;
|
|
285
287
|
}
|
|
286
|
-
const importText = `import { ${
|
|
288
|
+
const importText = `import { ${constant} } from '${source}';\n`;
|
|
287
289
|
const anchor = (0, importInsertion_1.importInsertionAnchor)(sourceCode);
|
|
288
290
|
if (importDeclarations.length) {
|
|
289
291
|
// The statement joins an import block, and the anchor is that block's
|
|
@@ -310,7 +312,6 @@ exports.enforceQueryKeyTs = (0, createRule_1.createRule)({
|
|
|
310
312
|
}
|
|
311
313
|
function flushReports() {
|
|
312
314
|
const resolutions = new Map();
|
|
313
|
-
const missingConstants = [];
|
|
314
315
|
const canImport = importSourceOf() !== null;
|
|
315
316
|
// The location handed to `context.report` below is what ESLint matches a
|
|
316
317
|
// directive against, so suppression is resolved from exactly that node.
|
|
@@ -329,20 +330,7 @@ exports.enforceQueryKeyTs = (0, createRule_1.createRule)({
|
|
|
329
330
|
continue;
|
|
330
331
|
}
|
|
331
332
|
resolutions.set(report, { name, state });
|
|
332
|
-
if (state === 'missing' && !missingConstants.includes(name)) {
|
|
333
|
-
missingConstants.push(name);
|
|
334
|
-
}
|
|
335
333
|
}
|
|
336
|
-
// The first applied substitution carries the import for every other one:
|
|
337
|
-
// its fix range then starts at the top of the file and ends before the
|
|
338
|
-
// remaining literals, so no two fixes of this rule overlap in a pass.
|
|
339
|
-
// Suppressed reports are skipped so the slot falls to a survivor.
|
|
340
|
-
const importCarrier = pendingReports.find((report) => {
|
|
341
|
-
const resolution = resolutions.get(report);
|
|
342
|
-
return (!suppressed.has(report) &&
|
|
343
|
-
resolution !== undefined &&
|
|
344
|
-
resolution.state !== 'conflict');
|
|
345
|
-
});
|
|
346
334
|
// Suppressed violations are still reported: ESLint discards them, and
|
|
347
335
|
// reporting keeps the user's directive "used" so that
|
|
348
336
|
// `--report-unused-disable-directives` does not flag it.
|
|
@@ -363,8 +351,20 @@ exports.enforceQueryKeyTs = (0, createRule_1.createRule)({
|
|
|
363
351
|
const fixes = [
|
|
364
352
|
fixer.replaceText(substitution.keyNode, resolution.name),
|
|
365
353
|
];
|
|
366
|
-
|
|
367
|
-
|
|
354
|
+
// Every fix stands on its own: the one that writes a constant is
|
|
355
|
+
// the one that imports it. Concentrating the file's imports into a
|
|
356
|
+
// single carrier fix made every other fix depend on that one being
|
|
357
|
+
// applied, and ESLint drops a fix whose range overlaps a fix it has
|
|
358
|
+
// already taken from another rule — so the carrier lost that race
|
|
359
|
+
// while the literal-only fixes still landed, leaving the file
|
|
360
|
+
// naming an identifier nothing imports (#2012).
|
|
361
|
+
//
|
|
362
|
+
// Two such fixes both reach for the import declaration, so within a
|
|
363
|
+
// pass they overlap and ESLint applies one of them. That converges:
|
|
364
|
+
// the unapplied violation is re-reported on the next pass and its
|
|
365
|
+
// constant then binds to, or extends, the import that landed.
|
|
366
|
+
if (resolution.state === 'missing') {
|
|
367
|
+
const importFix = buildImportFix(fixer, resolution.name);
|
|
368
368
|
if (!importFix) {
|
|
369
369
|
return null;
|
|
370
370
|
}
|
|
@@ -25,6 +25,41 @@ function isCloneDeepModule(source) {
|
|
|
25
25
|
source.startsWith('@/') ||
|
|
26
26
|
source.endsWith('util/cloneDeep'));
|
|
27
27
|
}
|
|
28
|
+
function isConstAssertion(node) {
|
|
29
|
+
return (node.type === utils_1.AST_NODE_TYPES.TSAsExpression &&
|
|
30
|
+
node.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
|
|
31
|
+
node.typeAnnotation.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
32
|
+
node.typeAnnotation.typeName.name === 'const');
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A `const` assertion is legal only on a literal, so leaving one wrapped around
|
|
36
|
+
* the emitted `cloneDeep(...)` call yields TS1355 and turns a compiling file
|
|
37
|
+
* into a broken one (#2011). The fix is declined there rather than absorbing
|
|
38
|
+
* the assertion, which is the conservative reading of a `const` the author
|
|
39
|
+
* asked for on a value this rule replaces.
|
|
40
|
+
*
|
|
41
|
+
* The whole assertion chain is walked because each of its links still applies
|
|
42
|
+
* to the emitted call: `as Foo as const`, `satisfies Foo as const` and
|
|
43
|
+
* `! as const` are TS1355 just the same. The walk stops at the first parent
|
|
44
|
+
* that is not an assertion, which keeps `as const` on an ENCLOSING literal
|
|
45
|
+
* fixable — that assertion still has a literal to apply to.
|
|
46
|
+
*
|
|
47
|
+
* Only a `const` assertion is disqualifying: `as Foo` and `satisfies Foo` are
|
|
48
|
+
* legal on a call expression and keep their fix.
|
|
49
|
+
*/
|
|
50
|
+
function isConstAsserted(node) {
|
|
51
|
+
let current = node.parent;
|
|
52
|
+
while (current &&
|
|
53
|
+
(current.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
|
|
54
|
+
current.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression ||
|
|
55
|
+
current.type === utils_1.AST_NODE_TYPES.TSNonNullExpression)) {
|
|
56
|
+
if (isConstAssertion(current)) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
current = current.parent;
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
28
63
|
exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
29
64
|
name: 'prefer-clone-deep',
|
|
30
65
|
meta: {
|
|
@@ -444,6 +479,9 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
|
444
479
|
return null;
|
|
445
480
|
}
|
|
446
481
|
for (const target of targets) {
|
|
482
|
+
if (isConstAsserted(target)) {
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
447
485
|
const call = buildCloneDeepCall(target);
|
|
448
486
|
if (call === null) {
|
|
449
487
|
return null;
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.20.152",
|
|
4
|
+
"date": "2026-08-14T10:28:08.765Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "enforce-querykey-ts",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
2012
|
|
11
|
+
],
|
|
12
|
+
"summary": "carry each key's import on its own fix (closes #2012)"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "prefer-clone-deep",
|
|
16
|
+
"changeType": "fix",
|
|
17
|
+
"issues": [
|
|
18
|
+
2011
|
|
19
|
+
],
|
|
20
|
+
"summary": "decline the fix under a const assertion (closes #2011)"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
},
|
|
2
24
|
{
|
|
3
25
|
"version": "1.20.151",
|
|
4
26
|
"date": "2026-08-14T02:27:13.012Z",
|