@blumintinc/eslint-plugin-blumint 1.20.187 → 1.20.189
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
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TSESLint } from '@typescript-eslint/utils';
|
|
1
2
|
type MessageIds = 'avoidEntireObject' | 'removeUnusedDependency';
|
|
2
|
-
export declare const noEntireObjectHookDeps:
|
|
3
|
+
export declare const noEntireObjectHookDeps: TSESLint.RuleModule<MessageIds, [], TSESLint.RuleListener>;
|
|
3
4
|
export {};
|
|
@@ -1167,6 +1167,48 @@ function getObjectUsagesInHook(hookBody, objectName, typeInfo, bodyIsDeferred =
|
|
|
1167
1167
|
notUsed,
|
|
1168
1168
|
};
|
|
1169
1169
|
}
|
|
1170
|
+
/** The run of spaces/tabs opening the line that `offset` sits on. */
|
|
1171
|
+
function indentAt(text, offset) {
|
|
1172
|
+
const lineStart = text.lastIndexOf('\n', offset - 1) + 1;
|
|
1173
|
+
return /^[ \t]*/.exec(text.slice(lineStart, offset))?.[0] ?? '';
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Drop `span`, but re-emit any comment standing inside it.
|
|
1177
|
+
*
|
|
1178
|
+
* Removing a dependency means removing its separator too, so the span reaches
|
|
1179
|
+
* to a NEIGHBOURING element and therefore covers the margin between the two.
|
|
1180
|
+
* That margin is not the fixer's to delete: it can hold an
|
|
1181
|
+
* `eslint-disable-next-line` protecting the dependency that survives, and
|
|
1182
|
+
* dropping that directive silently re-enables another rule (#2208). Declining
|
|
1183
|
+
* the fix instead would only trade the lost comment for a transform that a
|
|
1184
|
+
* comment decides (#1877), so the comment is carried rather than obeyed.
|
|
1185
|
+
*
|
|
1186
|
+
* Each carried comment is re-emitted on a line of its own: a `//` comment
|
|
1187
|
+
* swallows whatever follows it on the same line, so folding one inline would
|
|
1188
|
+
* comment out the dependency that was meant to survive.
|
|
1189
|
+
*/
|
|
1190
|
+
function removeCarryingComments(fixer, sourceCode, span, anchor) {
|
|
1191
|
+
const carried = sourceCode
|
|
1192
|
+
.getAllComments()
|
|
1193
|
+
.filter((comment) => comment.range[0] >= span[0] && comment.range[1] <= span[1]);
|
|
1194
|
+
// With nothing to preserve the span is pure separator and whitespace, so the
|
|
1195
|
+
// plain removal keeps the comment-free output exactly as it has always been.
|
|
1196
|
+
if (carried.length === 0) {
|
|
1197
|
+
return fixer.removeRange(span);
|
|
1198
|
+
}
|
|
1199
|
+
const text = sourceCode.getText();
|
|
1200
|
+
// Anchor the indentation on the end the surviving code sits against.
|
|
1201
|
+
const indent = indentAt(text, anchor === 'toNextElement' ? span[1] : carried[0].range[0]);
|
|
1202
|
+
const body = carried
|
|
1203
|
+
.map((comment) => text.slice(comment.range[0], comment.range[1]))
|
|
1204
|
+
.join(`\n${indent}`);
|
|
1205
|
+
// Both spellings close on a fresh line: whatever the span abutted — the next
|
|
1206
|
+
// dependency, or the separator trailing the last one — would otherwise land
|
|
1207
|
+
// on the final carried comment's line and be commented out.
|
|
1208
|
+
return fixer.replaceTextRange(span, anchor === 'toNextElement'
|
|
1209
|
+
? `${body}\n${indent}`
|
|
1210
|
+
: `\n${indent}${body}\n${indent}`);
|
|
1211
|
+
}
|
|
1170
1212
|
exports.noEntireObjectHookDeps = (0, createRule_1.createRule)({
|
|
1171
1213
|
name: 'no-entire-object-hook-deps',
|
|
1172
1214
|
meta: {
|
|
@@ -1339,21 +1381,19 @@ exports.noEntireObjectHookDeps = (0, createRule_1.createRule)({
|
|
|
1339
1381
|
if (elementIndex === depsArg.elements.length - 1) {
|
|
1340
1382
|
const prevElement = depsArg.elements[elementIndex - 1];
|
|
1341
1383
|
if (prevElement) {
|
|
1342
|
-
|
|
1384
|
+
return removeCarryingComments(fixer, sourceCode, [
|
|
1343
1385
|
prevElement.range[1],
|
|
1344
1386
|
element.range[1],
|
|
1345
|
-
];
|
|
1346
|
-
return fixer.removeRange(range);
|
|
1387
|
+
], 'fromPrevElement');
|
|
1347
1388
|
}
|
|
1348
1389
|
}
|
|
1349
1390
|
// Otherwise, remove the element and the following comma
|
|
1350
1391
|
const nextElement = depsArg.elements[elementIndex + 1];
|
|
1351
1392
|
if (nextElement) {
|
|
1352
|
-
|
|
1393
|
+
return removeCarryingComments(fixer, sourceCode, [
|
|
1353
1394
|
element.range[0],
|
|
1354
1395
|
nextElement.range[0],
|
|
1355
|
-
];
|
|
1356
|
-
return fixer.removeRange(range);
|
|
1396
|
+
], 'toNextElement');
|
|
1357
1397
|
}
|
|
1358
1398
|
// Fallback to just removing the element
|
|
1359
1399
|
return fixer.remove(element);
|
|
@@ -838,7 +838,18 @@ function calculateImportPath(currentFilePath) {
|
|
|
838
838
|
return 'src/util/memo';
|
|
839
839
|
// Split the current file path into parts and normalize
|
|
840
840
|
const parts = currentFilePath.split(/[\\/]/); // Handle both Unix and Windows paths
|
|
841
|
-
|
|
841
|
+
/**
|
|
842
|
+
* The source root is the `src` NEAREST the file, not the first one in the
|
|
843
|
+
* path. `context.getFilename()` is always absolute under every real ESLint
|
|
844
|
+
* entry point, so the absolute prefix is part of the path being searched: a
|
|
845
|
+
* checkout under a directory named `src` (`~/src/proj/src/components/Foo.tsx`)
|
|
846
|
+
* would otherwise anchor on that outer segment and emit a specifier that walks
|
|
847
|
+
* out of the project entirely.
|
|
848
|
+
*
|
|
849
|
+
* Directory segments only — a file that is itself named `src` names no
|
|
850
|
+
* directory the import could be relative to.
|
|
851
|
+
*/
|
|
852
|
+
const srcIndex = parts.slice(0, -1).lastIndexOf('src');
|
|
842
853
|
if (srcIndex === -1) {
|
|
843
854
|
// If we're not in a src directory, use absolute path
|
|
844
855
|
return 'src/util/memo';
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.20.189",
|
|
4
|
+
"date": "2026-08-29T11:57:05.586Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "no-entire-object-hook-deps",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
2208
|
|
11
|
+
],
|
|
12
|
+
"summary": "carry comments in the removed dep's margin (closes #2208)"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"version": "1.20.188",
|
|
18
|
+
"date": "2026-08-29T07:11:55.294Z",
|
|
19
|
+
"rules": [
|
|
20
|
+
{
|
|
21
|
+
"name": "require-memo",
|
|
22
|
+
"changeType": "fix",
|
|
23
|
+
"issues": [
|
|
24
|
+
2207
|
|
25
|
+
],
|
|
26
|
+
"summary": "anchor the memo import on the nearest src, not the first (closes #2207)"
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
},
|
|
2
30
|
{
|
|
3
31
|
"version": "1.20.187",
|
|
4
32
|
"date": "2026-08-28T10:18:26.589Z",
|