@blumintinc/eslint-plugin-blumint 1.20.140 → 1.20.141
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-centralized-mock-firestore.js +6 -11
- package/lib/rules/enforce-early-destructuring.js +12 -3
- package/lib/rules/enforce-memoize-async.js +1 -1
- package/lib/rules/enforce-memoize-getters.js +1 -1
- package/lib/rules/prefer-use-deep-compare-memo.js +6 -3
- package/lib/utils/importInsertion.d.ts +17 -3
- package/lib/utils/importInsertion.js +24 -7
- package/package.json +1 -1
- package/release-manifest.json +38 -0
package/lib/index.js
CHANGED
|
@@ -218,19 +218,14 @@ function editOffset(anchor) {
|
|
|
218
218
|
*
|
|
219
219
|
* Widening to the anchor's line start is what lets the emitted
|
|
220
220
|
* `${indent}import …\n` leave the displaced statement on the indentation it
|
|
221
|
-
* already had
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
* retirement
|
|
225
|
-
*
|
|
226
|
-
* to that edit's start, since ESLint rejects a fix nested inside another.
|
|
221
|
+
* already had; `importAnchorLineStartIfOwned` restricts that to the anchors it
|
|
222
|
+
* is sound for. When the resulting position still falls inside a retirement —
|
|
223
|
+
* the anchor statement is itself the declaration being retired, and the
|
|
224
|
+
* retirement claims the indentation ahead of it — the insertion moves to that
|
|
225
|
+
* edit's start, since ESLint rejects a fix nested inside another.
|
|
227
226
|
*/
|
|
228
227
|
function importPlacement(sourceCode, anchor, edits) {
|
|
229
|
-
const
|
|
230
|
-
const lineStart = sourceCode.text.lastIndexOf('\n', anchorStart - 1) + 1;
|
|
231
|
-
const placement = /^[ \t]*$/.test(sourceCode.text.slice(lineStart, anchorStart))
|
|
232
|
-
? (0, importInsertion_1.importAnchorLineStart)(sourceCode, anchor)
|
|
233
|
-
: anchor;
|
|
228
|
+
const placement = (0, importInsertion_1.importAnchorLineStartIfOwned)(sourceCode, anchor);
|
|
234
229
|
const offset = editOffset(placement);
|
|
235
230
|
const enclosing = edits.find((edit) => edit.start < offset && offset < edit.end);
|
|
236
231
|
return enclosing ? { kind: 'index', index: enclosing.start } : placement;
|
|
@@ -894,7 +894,7 @@ function generateHoistingFixes(groups, callback, depsArray, depTexts, insertionS
|
|
|
894
894
|
for (const group of groups.values()) {
|
|
895
895
|
const sortedProps = Array.from(group.properties.values()).sort((a, b) => a.order - b.order);
|
|
896
896
|
const pattern = `{ ${sortedProps.map((p) => p.text).join(', ')} }`;
|
|
897
|
-
hoistedLines.push(
|
|
897
|
+
hoistedLines.push(`const ${pattern} = ${nullishSourceText(group.objectText, group.inits[0])} ?? {};`);
|
|
898
898
|
}
|
|
899
899
|
reservedNamesByScope.set(scope, updatedReservedNames);
|
|
900
900
|
const newDepSet = new Set(newDepTexts);
|
|
@@ -904,9 +904,18 @@ function generateHoistingFixes(groups, callback, depsArray, depTexts, insertionS
|
|
|
904
904
|
newDepSet.add(name);
|
|
905
905
|
}
|
|
906
906
|
}
|
|
907
|
-
|
|
907
|
+
// Anchoring to the start of the statement's LINE is the same offset as the
|
|
908
|
+
// statement itself only while the statement opens that line. When it does not
|
|
909
|
+
// — a body collapsed onto one line, or a sibling declared ahead of it — that
|
|
910
|
+
// offset sits outside the enclosing function, so a declaration reading the
|
|
911
|
+
// function's own parameters would be hoisted out of the scope that binds them.
|
|
912
|
+
const text = sourceCode.getText();
|
|
913
|
+
const lineStart = text.lastIndexOf('\n', insertionStatement.range[0]) + 1;
|
|
914
|
+
const ownsItsLine = /^[\t ]*$/.test(text.slice(lineStart, insertionStatement.range[0]));
|
|
908
915
|
const fixes = [
|
|
909
|
-
|
|
916
|
+
ownsItsLine
|
|
917
|
+
? fixer.insertTextBeforeRange([lineStart, lineStart], `${hoistedLines.map((line) => `${indent}${line}`).join('\n')}\n`)
|
|
918
|
+
: fixer.insertTextBefore(insertionStatement, `${hoistedLines.join(' ')} `),
|
|
910
919
|
fixer.replaceText(depsArray, `[${newDepTexts.join(', ')}]`),
|
|
911
920
|
];
|
|
912
921
|
for (const decl of declarationsToRemove) {
|
|
@@ -459,7 +459,7 @@ exports.enforceMemoizeAsync = (0, createRule_1.createRule)({
|
|
|
459
459
|
// a header comment — while still placing it above the first
|
|
460
460
|
// existing import.
|
|
461
461
|
const anchor = (0, importInsertion_1.importInsertionAnchor)(sourceCode);
|
|
462
|
-
fixes.push((0, importInsertion_1.insertAtImportAnchor)(sourceCode, fixer, (0, importInsertion_1.
|
|
462
|
+
fixes.push((0, importInsertion_1.insertAtImportAnchor)(sourceCode, fixer, (0, importInsertion_1.importAnchorLineStartIfOwned)(sourceCode, anchor), `${(0, importInsertion_1.importAnchorIndent)(sourceCode, anchor)}${importStatement}\n`));
|
|
463
463
|
scheduledImportFix = true;
|
|
464
464
|
}
|
|
465
465
|
// Anchor the decorator on the method — its first token, so ahead of
|
|
@@ -507,7 +507,7 @@ exports.enforceMemoizeGetters = (0, createRule_1.createRule)({
|
|
|
507
507
|
// with the statement it displaces; widening to the line start
|
|
508
508
|
// then leaves that statement's indentation intact.
|
|
509
509
|
const indent = (0, importInsertion_1.importAnchorIndent)(sourceCode, anchor);
|
|
510
|
-
fixes.push((0, importInsertion_1.insertAtImportAnchor)(sourceCode, fixer, (0, importInsertion_1.
|
|
510
|
+
fixes.push((0, importInsertion_1.insertAtImportAnchor)(sourceCode, fixer, (0, importInsertion_1.importAnchorLineStartIfOwned)(sourceCode, anchor), `${indent}import { Memoize } from '${MEMOIZE_PREFERRED_MODULE}';\n`));
|
|
511
511
|
scheduledImportFix = true;
|
|
512
512
|
}
|
|
513
513
|
// Anchor the decorator on the member — its first token, so ahead of
|
|
@@ -258,10 +258,13 @@ function ensureDeepCompareImportFixes(context, fixer) {
|
|
|
258
258
|
const anchor = (0, importInsertion_1.importInsertionAnchor)(sourceCode);
|
|
259
259
|
const indent = (0, importInsertion_1.importAnchorIndent)(sourceCode, anchor);
|
|
260
260
|
const importText = `${indent}import { ${DEEP_COMPARE_HOOK} } from '${DEEP_COMPARE_MODULE}';\n`;
|
|
261
|
-
// Widened to the anchor's line start
|
|
262
|
-
// its own indentation
|
|
261
|
+
// Widened to the anchor's line start where the anchor opens it, because the
|
|
262
|
+
// emitted statement carries its own indentation and so leaves the displaced
|
|
263
|
+
// anchor sitting on the original. Where the anchor shares its line the
|
|
264
|
+
// widening is declined: the offset would otherwise fall ahead of the prologue
|
|
265
|
+
// this anchor was chosen to sit below.
|
|
263
266
|
return [
|
|
264
|
-
(0, importInsertion_1.insertAtImportAnchor)(sourceCode, fixer, (0, importInsertion_1.
|
|
267
|
+
(0, importInsertion_1.insertAtImportAnchor)(sourceCode, fixer, (0, importInsertion_1.importAnchorLineStartIfOwned)(sourceCode, anchor), importText),
|
|
265
268
|
];
|
|
266
269
|
}
|
|
267
270
|
function isImportedIdentifier(context, name) {
|
|
@@ -40,11 +40,25 @@ export declare function importInsertionAnchor(sourceCode: ImportInsertionSource)
|
|
|
40
40
|
*/
|
|
41
41
|
export declare function insertAtImportAnchor(sourceCode: ImportInsertionSource, fixer: TSESLint.RuleFixer, anchor: ImportInsertionAnchor, text: string): TSESLint.RuleFix;
|
|
42
42
|
/**
|
|
43
|
-
* Widens `anchor` to the start of its line
|
|
44
|
-
*
|
|
43
|
+
* Widens `anchor` to the start of its line only where whitespace is all that
|
|
44
|
+
* precedes it there, and leaves it alone otherwise.
|
|
45
|
+
*
|
|
45
46
|
* Raw-offset anchors pass through: they never sit inside an indented line.
|
|
47
|
+
*
|
|
48
|
+
* The widening buys one thing — an emitted `${indent}import …\n` leaves the
|
|
49
|
+
* displaced anchor on the indentation it already had — and that reasoning holds
|
|
50
|
+
* only while the anchor opens its line. When something else opens that line the
|
|
51
|
+
* widened offset sits ahead of it, and the anchor resolves past the prologue
|
|
52
|
+
* precisely because the prologue is what precedes it: a `'use client'` is a
|
|
53
|
+
* directive only while it is the first statement, so an import spliced above it
|
|
54
|
+
* demotes it to an inert expression statement that no bundler reads. Nothing
|
|
55
|
+
* downstream reports that — the output re-lints clean and the demoted directive
|
|
56
|
+
* is still valid TypeScript.
|
|
57
|
+
*
|
|
58
|
+
* Declining to widen costs the displaced anchor its indentation and keeps the
|
|
59
|
+
* file's meaning, which is the safe direction of that trade.
|
|
46
60
|
*/
|
|
47
|
-
export declare function
|
|
61
|
+
export declare function importAnchorLineStartIfOwned(sourceCode: ImportInsertionSource, anchor: ImportInsertionAnchor): ImportInsertionAnchor;
|
|
48
62
|
/**
|
|
49
63
|
* The whitespace prefix of the anchor's line, for fixers that replicate the
|
|
50
64
|
* anchor's indentation on the inserted import.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.importAnchorIndent = exports.
|
|
3
|
+
exports.importAnchorIndent = exports.importAnchorLineStartIfOwned = exports.insertAtImportAnchor = exports.importInsertionAnchor = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const disableDirectives_1 = require("./disableDirectives");
|
|
6
6
|
/**
|
|
@@ -95,18 +95,35 @@ function insertAtImportAnchor(sourceCode, fixer, anchor, text) {
|
|
|
95
95
|
}
|
|
96
96
|
exports.insertAtImportAnchor = insertAtImportAnchor;
|
|
97
97
|
/**
|
|
98
|
-
* Widens `anchor` to the start of its line
|
|
99
|
-
*
|
|
98
|
+
* Widens `anchor` to the start of its line only where whitespace is all that
|
|
99
|
+
* precedes it there, and leaves it alone otherwise.
|
|
100
|
+
*
|
|
100
101
|
* Raw-offset anchors pass through: they never sit inside an indented line.
|
|
102
|
+
*
|
|
103
|
+
* The widening buys one thing — an emitted `${indent}import …\n` leaves the
|
|
104
|
+
* displaced anchor on the indentation it already had — and that reasoning holds
|
|
105
|
+
* only while the anchor opens its line. When something else opens that line the
|
|
106
|
+
* widened offset sits ahead of it, and the anchor resolves past the prologue
|
|
107
|
+
* precisely because the prologue is what precedes it: a `'use client'` is a
|
|
108
|
+
* directive only while it is the first statement, so an import spliced above it
|
|
109
|
+
* demotes it to an inert expression statement that no bundler reads. Nothing
|
|
110
|
+
* downstream reports that — the output re-lints clean and the demoted directive
|
|
111
|
+
* is still valid TypeScript.
|
|
112
|
+
*
|
|
113
|
+
* Declining to widen costs the displaced anchor its indentation and keeps the
|
|
114
|
+
* file's meaning, which is the safe direction of that trade.
|
|
101
115
|
*/
|
|
102
|
-
function
|
|
116
|
+
function importAnchorLineStartIfOwned(sourceCode, anchor) {
|
|
103
117
|
if (anchor.kind === 'index') {
|
|
104
118
|
return anchor;
|
|
105
119
|
}
|
|
106
|
-
const
|
|
107
|
-
|
|
120
|
+
const start = anchor.target.range[0];
|
|
121
|
+
const lineStart = sourceCode.text.lastIndexOf('\n', start - 1) + 1;
|
|
122
|
+
return /^[\t ]*$/.test(sourceCode.text.slice(lineStart, start))
|
|
123
|
+
? { kind: 'index', index: lineStart }
|
|
124
|
+
: anchor;
|
|
108
125
|
}
|
|
109
|
-
exports.
|
|
126
|
+
exports.importAnchorLineStartIfOwned = importAnchorLineStartIfOwned;
|
|
110
127
|
/**
|
|
111
128
|
* The whitespace prefix of the anchor's line, for fixers that replicate the
|
|
112
129
|
* anchor's indentation on the inserted import.
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,42 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.20.141",
|
|
4
|
+
"date": "2026-08-12T00:57:09.340Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "enforce-early-destructuring",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
1956
|
|
11
|
+
],
|
|
12
|
+
"summary": "anchor the hoist to the hook call, not to its line (closes #1956)"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "enforce-memoize-async",
|
|
16
|
+
"changeType": "fix",
|
|
17
|
+
"issues": [
|
|
18
|
+
1957
|
|
19
|
+
],
|
|
20
|
+
"summary": "keep the injected import below a shared-line directive (closes #1957)"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "enforce-memoize-getters",
|
|
24
|
+
"changeType": "fix",
|
|
25
|
+
"issues": [
|
|
26
|
+
1958
|
|
27
|
+
],
|
|
28
|
+
"summary": "keep the injected import below a shared-line directive (closes #1958)"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "prefer-use-deep-compare-memo",
|
|
32
|
+
"changeType": "fix",
|
|
33
|
+
"issues": [
|
|
34
|
+
1959
|
|
35
|
+
],
|
|
36
|
+
"summary": "keep the injected import below a shared-line directive (closes #1959)"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
},
|
|
2
40
|
{
|
|
3
41
|
"version": "1.20.140",
|
|
4
42
|
"date": "2026-08-11T22:49:18.139Z",
|