@blumintinc/eslint-plugin-blumint 1.16.1 → 1.16.2
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
|
@@ -368,6 +368,18 @@ function statementReferencesAny(statement, names) {
|
|
|
368
368
|
}
|
|
369
369
|
return false;
|
|
370
370
|
}
|
|
371
|
+
/**
|
|
372
|
+
* Find the index of the first statement that references any of the given names,
|
|
373
|
+
* searching forward from afterIndex.
|
|
374
|
+
*/
|
|
375
|
+
function findFirstUsageIndex(body, names, afterIndex) {
|
|
376
|
+
for (let cursor = afterIndex; cursor < body.length; cursor += 1) {
|
|
377
|
+
if (statementReferencesAny(body[cursor], names)) {
|
|
378
|
+
return cursor;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return -1;
|
|
382
|
+
}
|
|
371
383
|
function collectAssignedNamesFromPattern(target, names) {
|
|
372
384
|
if (TYPE_EXPRESSION_WRAPPERS.has(target.type) &&
|
|
373
385
|
'expression' in target) {
|
|
@@ -671,10 +683,13 @@ function findEarliestSafeIndex(body, startIndex, dependencies, { allowHooks }) {
|
|
|
671
683
|
}
|
|
672
684
|
function getStartWithComments(statement, sourceCode) {
|
|
673
685
|
const comments = sourceCode.getCommentsBefore(statement);
|
|
674
|
-
|
|
675
|
-
|
|
686
|
+
const start = comments.length === 0 ? statement.range[0] : comments[0].range[0];
|
|
687
|
+
const text = sourceCode.getText();
|
|
688
|
+
let cursor = start - 1;
|
|
689
|
+
while (cursor >= 0 && (text[cursor] === ' ' || text[cursor] === '\t')) {
|
|
690
|
+
cursor -= 1;
|
|
676
691
|
}
|
|
677
|
-
return
|
|
692
|
+
return cursor + 1;
|
|
678
693
|
}
|
|
679
694
|
function getNextStart(body, index, parent, sourceCode) {
|
|
680
695
|
const nextStatement = body[index + 1];
|
|
@@ -907,42 +922,95 @@ function isSiblingSourceDerivation(statement, sourceNodes, sourceDeclarators) {
|
|
|
907
922
|
return !sourceDeclarators.has(name);
|
|
908
923
|
});
|
|
909
924
|
}
|
|
925
|
+
/**
|
|
926
|
+
* Restrict late-declaration candidates to simple variables with at most an Identifier or
|
|
927
|
+
* Literal initializer. This ensures they are pure values that do not have side effects or
|
|
928
|
+
* change execution order when moved closer to their usage. More complex initializers are
|
|
929
|
+
* excluded to maintain temporal safety.
|
|
930
|
+
*/
|
|
931
|
+
function isLateDeclarationCandidate(statement) {
|
|
932
|
+
if (statement.type !== utils_1.AST_NODE_TYPES.VariableDeclaration ||
|
|
933
|
+
statement.declarations.length !== 1) {
|
|
934
|
+
return false;
|
|
935
|
+
}
|
|
936
|
+
const [declarator] = statement.declarations;
|
|
937
|
+
return (declarator.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
938
|
+
(!declarator.init ||
|
|
939
|
+
declarator.init.type === utils_1.AST_NODE_TYPES.Identifier ||
|
|
940
|
+
declarator.init.type === utils_1.AST_NODE_TYPES.Literal));
|
|
941
|
+
}
|
|
942
|
+
const LOOP_TYPES = new Set([
|
|
943
|
+
utils_1.AST_NODE_TYPES.ForStatement,
|
|
944
|
+
utils_1.AST_NODE_TYPES.ForInStatement,
|
|
945
|
+
utils_1.AST_NODE_TYPES.ForOfStatement,
|
|
946
|
+
utils_1.AST_NODE_TYPES.WhileStatement,
|
|
947
|
+
utils_1.AST_NODE_TYPES.DoWhileStatement,
|
|
948
|
+
]);
|
|
949
|
+
/**
|
|
950
|
+
* Treat a loop as an ordering barrier for late-declaration moves when it mutates any
|
|
951
|
+
* tracked variable inside the loop body.
|
|
952
|
+
*
|
|
953
|
+
* Variables that are declared before a loop and mutated inside it (accumulators,
|
|
954
|
+
* counters, collectors) must remain before the loop so their declaration is visible
|
|
955
|
+
* across all iterations. Moving them inside would reset them on every iteration,
|
|
956
|
+
* changing program semantics. This guard applies regardless of whether the variable
|
|
957
|
+
* is also read after the loop.
|
|
958
|
+
*
|
|
959
|
+
* Assumptions:
|
|
960
|
+
* - nameSet contains the name(s) being tracked for late declaration.
|
|
961
|
+
* - body is the array of statements in the current block.
|
|
962
|
+
* - usageIndex is the index of the first statement that references the variable(s).
|
|
963
|
+
*/
|
|
964
|
+
function isMutatedInLoop(body, usageIndex, nameSet) {
|
|
965
|
+
const firstUsage = body[usageIndex];
|
|
966
|
+
if (!firstUsage) {
|
|
967
|
+
return false;
|
|
968
|
+
}
|
|
969
|
+
if (!LOOP_TYPES.has(firstUsage.type)) {
|
|
970
|
+
return false;
|
|
971
|
+
}
|
|
972
|
+
return statementMutatesAny(firstUsage, nameSet);
|
|
973
|
+
}
|
|
910
974
|
function handleLateDeclarations(ruleContext, body, parent) {
|
|
911
975
|
const { sourceCode } = ruleContext;
|
|
912
976
|
body.forEach((statement, index) => {
|
|
913
|
-
if (statement
|
|
914
|
-
statement.declarations.length !== 1) {
|
|
977
|
+
if (!isLateDeclarationCandidate(statement)) {
|
|
915
978
|
return;
|
|
916
979
|
}
|
|
917
980
|
const [declarator] = statement.declarations;
|
|
918
|
-
if (declarator.id.type !== utils_1.AST_NODE_TYPES.Identifier ||
|
|
919
|
-
(declarator.init &&
|
|
920
|
-
declarator.init.type !== utils_1.AST_NODE_TYPES.Identifier &&
|
|
921
|
-
declarator.init.type !== utils_1.AST_NODE_TYPES.Literal)) {
|
|
922
|
-
return;
|
|
923
|
-
}
|
|
924
981
|
const name = declarator.id.name;
|
|
925
982
|
const dependencies = new Set();
|
|
926
983
|
if (declarator.init && declarator.init.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
927
984
|
dependencies.add(declarator.init.name);
|
|
928
985
|
}
|
|
929
986
|
const nameSet = new Set([name]);
|
|
930
|
-
|
|
931
|
-
for (let cursor = index + 1; cursor < body.length; cursor += 1) {
|
|
932
|
-
if (statementReferencesAny(body[cursor], nameSet)) {
|
|
933
|
-
usageIndex = cursor;
|
|
934
|
-
break;
|
|
935
|
-
}
|
|
936
|
-
}
|
|
987
|
+
const usageIndex = findFirstUsageIndex(body, nameSet, index + 1);
|
|
937
988
|
if (usageIndex === -1 || usageIndex <= index + 1) {
|
|
938
989
|
return;
|
|
939
990
|
}
|
|
991
|
+
// Loop mutations create a dependency barrier: declarations that precede loops and are
|
|
992
|
+
// mutated inside them cannot be safely moved, as the declaration must be visible across
|
|
993
|
+
// all iterations. Prevent false positives for this pattern.
|
|
994
|
+
if (isMutatedInLoop(body, usageIndex, nameSet)) {
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
940
997
|
const intervening = body.slice(index + 1, usageIndex);
|
|
941
998
|
// Only move across pure declarations that do not mention the placeholder or its initializer dependencies to avoid changing closure timing or TDZ behavior.
|
|
942
|
-
const crossesImpureOrTracked = intervening.some((stmt) => {
|
|
999
|
+
const crossesImpureOrTracked = intervening.some((stmt, i) => {
|
|
943
1000
|
if (!isPureDeclaration(stmt, { allowHooks: false })) {
|
|
944
1001
|
return true;
|
|
945
1002
|
}
|
|
1003
|
+
// Do not hop over another declaration that is used at the same index or earlier
|
|
1004
|
+
// if it is also a candidate for being moved.
|
|
1005
|
+
// This prevents circular swapping of related declarations (like resolve/reject pairs).
|
|
1006
|
+
if (isLateDeclarationCandidate(stmt)) {
|
|
1007
|
+
const declaredNames = getDeclaredNames(stmt);
|
|
1008
|
+
const firstUsageOfIntervening = findFirstUsageIndex(body, declaredNames, index + 1 + i + 1);
|
|
1009
|
+
if (firstUsageOfIntervening !== -1 &&
|
|
1010
|
+
firstUsageOfIntervening <= usageIndex) {
|
|
1011
|
+
return true;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
946
1014
|
if (statementDeclaresAny(stmt, nameSet) ||
|
|
947
1015
|
statementMutatesAny(stmt, nameSet)) {
|
|
948
1016
|
return true;
|
|
@@ -352,6 +352,17 @@ exports.noHungarian = (0, createRule_1.createRule)({
|
|
|
352
352
|
/[A-Z]/.test(variableName[variableName.length - normalizedMarker.length]))) {
|
|
353
353
|
return true;
|
|
354
354
|
}
|
|
355
|
+
// Abbreviation markers (str, num, int, bool, arr, obj, fn, func) are
|
|
356
|
+
// short enough that the raw-character boundary heuristic below can fire
|
|
357
|
+
// on them as substrings inside real English words (e.g. "int" inside
|
|
358
|
+
// "Mint", "str" inside "stream"). For these markers, require an exact
|
|
359
|
+
// match against a full camelCase token so that "Mint" → ["Mint"] never
|
|
360
|
+
// matches "int", while genuine Hungarian like intValue → ["int","Value"]
|
|
361
|
+
// still does.
|
|
362
|
+
if (ABBREVIATION_MARKER_SET.has(normalizedMarker)) {
|
|
363
|
+
const segments = splitCamelSegments(variableName);
|
|
364
|
+
return segments.some((s) => s.toLowerCase() === normalizedMarker);
|
|
365
|
+
}
|
|
355
366
|
// Check for word boundaries to avoid matching substrings
|
|
356
367
|
// For example, avoid matching "int" in "points" or "str" in "stream"
|
|
357
368
|
const markerIndex = normalizedVarName.indexOf(normalizedMarker);
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.16.2",
|
|
4
|
+
"date": "2026-06-30T22:17:37.457Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "logical-top-to-bottom-grouping",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
1247
|
|
11
|
+
],
|
|
12
|
+
"summary": "restore loop-mutation guard and indentation-preserving idempotent autofix (closes #1247)"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "no-hungarian",
|
|
16
|
+
"changeType": "fix",
|
|
17
|
+
"issues": [
|
|
18
|
+
1246
|
|
19
|
+
],
|
|
20
|
+
"summary": "only flag abbreviation markers at camelCase token boundaries (closes #1246)"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
},
|
|
2
24
|
{
|
|
3
25
|
"version": "1.16.1",
|
|
4
26
|
"date": "2026-06-30T15:58:17.558Z",
|