@blumintinc/eslint-plugin-blumint 1.20.155 → 1.20.157
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
|
@@ -55,7 +55,10 @@ const IN_EXCEPTIONS = [
|
|
|
55
55
|
'increases',
|
|
56
56
|
'increasing',
|
|
57
57
|
'increasingly',
|
|
58
|
+
'increment',
|
|
58
59
|
'incremental',
|
|
60
|
+
'incremented',
|
|
61
|
+
'incrementing',
|
|
59
62
|
'increments',
|
|
60
63
|
'incriminating',
|
|
61
64
|
'incubation',
|
|
@@ -190,8 +193,11 @@ const IN_EXCEPTIONS = [
|
|
|
190
193
|
'inhaling',
|
|
191
194
|
'inherent',
|
|
192
195
|
'inherently',
|
|
196
|
+
'inherit',
|
|
193
197
|
'inheritance',
|
|
194
198
|
'inherited',
|
|
199
|
+
'inheriting',
|
|
200
|
+
'inherits',
|
|
195
201
|
'initial',
|
|
196
202
|
'initially',
|
|
197
203
|
'initials',
|
|
@@ -219,6 +225,10 @@ const IN_EXCEPTIONS = [
|
|
|
219
225
|
'inland',
|
|
220
226
|
'inlay',
|
|
221
227
|
'inlet',
|
|
228
|
+
'inline',
|
|
229
|
+
'inlined',
|
|
230
|
+
'inlines',
|
|
231
|
+
'inlining',
|
|
222
232
|
'inmate',
|
|
223
233
|
'inmates',
|
|
224
234
|
'inn',
|
|
@@ -294,6 +304,10 @@ const IN_EXCEPTIONS = [
|
|
|
294
304
|
'instances',
|
|
295
305
|
'instant',
|
|
296
306
|
'instantaneous',
|
|
307
|
+
'instantiate',
|
|
308
|
+
'instantiated',
|
|
309
|
+
'instantiates',
|
|
310
|
+
'instantiating',
|
|
297
311
|
'instantly',
|
|
298
312
|
'instead',
|
|
299
313
|
'instigated',
|
|
@@ -340,6 +354,11 @@ const IN_EXCEPTIONS = [
|
|
|
340
354
|
'intake',
|
|
341
355
|
'integer',
|
|
342
356
|
'integers',
|
|
357
|
+
'integrate',
|
|
358
|
+
'integrated',
|
|
359
|
+
'integrates',
|
|
360
|
+
'integrating',
|
|
361
|
+
'integration',
|
|
343
362
|
'intel',
|
|
344
363
|
'intellect',
|
|
345
364
|
'intellectual',
|
|
@@ -1806,6 +1806,49 @@ function moveSegment(segments, fromIndex, toIndex) {
|
|
|
1806
1806
|
next.splice(toIndex < fromIndex ? toIndex : toIndex - 1, 0, moved.replace(/[ \t]+$/u, ''));
|
|
1807
1807
|
return next;
|
|
1808
1808
|
}
|
|
1809
|
+
const LINE_TERMINATORS = new Set(['\n', '\r', '\u2028', '\u2029']);
|
|
1810
|
+
/**
|
|
1811
|
+
* Whether text appended to `text` would start on a fresh line.
|
|
1812
|
+
*
|
|
1813
|
+
* Trailing spaces and tabs are skipped: a segment ends with the indentation that
|
|
1814
|
+
* belonged to whatever followed it, and that indentation still leaves an appended
|
|
1815
|
+
* segment on a line of its own.
|
|
1816
|
+
*/
|
|
1817
|
+
function endsWithLineTerminator(text) {
|
|
1818
|
+
let cursor = text.length - 1;
|
|
1819
|
+
while (cursor >= 0 && (text[cursor] === ' ' || text[cursor] === '\t')) {
|
|
1820
|
+
cursor -= 1;
|
|
1821
|
+
}
|
|
1822
|
+
return cursor >= 0 && LINE_TERMINATORS.has(text[cursor]);
|
|
1823
|
+
}
|
|
1824
|
+
/**
|
|
1825
|
+
* The line break the source itself uses, so a separator restored below matches its
|
|
1826
|
+
* neighbours instead of mixing endings into a CRLF file.
|
|
1827
|
+
*/
|
|
1828
|
+
function lineBreakOf(text) {
|
|
1829
|
+
const index = text.indexOf('\n');
|
|
1830
|
+
return index > 0 && text[index - 1] === '\r' ? '\r\n' : '\n';
|
|
1831
|
+
}
|
|
1832
|
+
/**
|
|
1833
|
+
* Concatenates reordered segments, restoring the statement separation the source
|
|
1834
|
+
* carried positionally.
|
|
1835
|
+
*
|
|
1836
|
+
* A segment runs up to where the next one began, so it normally already ends with the
|
|
1837
|
+
* line break that separated them. Two do not: the block's last segment stops at `}` or
|
|
1838
|
+
* at the end of the file, and a statement sharing a line with its predecessor never had
|
|
1839
|
+
* a break in front of it. Once a reordering moves such a segment away from the end,
|
|
1840
|
+
* appending the next segment directly runs two statements together — and where the
|
|
1841
|
+
* first ends in a `//` comment that is not a formatting blemish, since the appended
|
|
1842
|
+
* statement becomes comment text and vanishes from the program (#2023).
|
|
1843
|
+
*
|
|
1844
|
+
* Only the joins are separated. Nothing is appended after the final segment, whose
|
|
1845
|
+
* successor is the untouched text outside the replaced range.
|
|
1846
|
+
*/
|
|
1847
|
+
function joinSegments(segments, lineBreak) {
|
|
1848
|
+
return segments.reduce((text, segment, index) => index === 0 || endsWithLineTerminator(text)
|
|
1849
|
+
? text + segment
|
|
1850
|
+
: text + lineBreak + segment, '');
|
|
1851
|
+
}
|
|
1809
1852
|
/**
|
|
1810
1853
|
* Emits an entire reordering as one fix by permuting the block's text segments.
|
|
1811
1854
|
*
|
|
@@ -1827,7 +1870,7 @@ function buildReorderFix(body, moves, parent, sourceCode, fixer) {
|
|
|
1827
1870
|
if (first > last) {
|
|
1828
1871
|
return null;
|
|
1829
1872
|
}
|
|
1830
|
-
return fixer.replaceTextRange([bounds[first], bounds[last + 1]], reordered.slice(first, last + 1).
|
|
1873
|
+
return fixer.replaceTextRange([bounds[first], bounds[last + 1]], joinSegments(reordered.slice(first, last + 1), lineBreakOf(sourceCode.getText())));
|
|
1831
1874
|
}
|
|
1832
1875
|
/**
|
|
1833
1876
|
* A fix is emitted only for a reordering the detector scores at zero relocatable
|
|
@@ -27,6 +27,7 @@ exports.preferNullishCoalescingBooleanProps = void 0;
|
|
|
27
27
|
const utils_1 = require("@typescript-eslint/utils");
|
|
28
28
|
const ts = __importStar(require("typescript"));
|
|
29
29
|
const createRule_1 = require("../utils/createRule");
|
|
30
|
+
const replacementSegments_1 = require("../utils/replacementSegments");
|
|
30
31
|
const BOOLEAN_PROP_REGEX = /^(is|has|should|can|will|do|does|did|was|were|enable|disable)/;
|
|
31
32
|
function isBooleanType(type, checker) {
|
|
32
33
|
if (type.isUnion()) {
|
|
@@ -531,6 +532,68 @@ function needsSelfParens(node, sourceCode) {
|
|
|
531
532
|
}
|
|
532
533
|
return !isParenthesized(node, sourceCode);
|
|
533
534
|
}
|
|
535
|
+
/**
|
|
536
|
+
* Comments the rewrite would delete.
|
|
537
|
+
*
|
|
538
|
+
* The fix rebuilds the whole expression from `getText` of each operand, so every
|
|
539
|
+
* comment written inside the node but outside both operand ranges has no anchor
|
|
540
|
+
* in the replacement: the trivia around the `||` operator, and anything sitting
|
|
541
|
+
* inside source-level parentheses that the rebuild discards. Comments nested
|
|
542
|
+
* within an operand travel with that operand's own text and are never stranded.
|
|
543
|
+
*/
|
|
544
|
+
function strandedComments(node, sourceCode) {
|
|
545
|
+
const enclosedBy = (operand) => (comment) => comment.range[0] >= operand.range[0] &&
|
|
546
|
+
comment.range[1] <= operand.range[1];
|
|
547
|
+
const inLeft = enclosedBy(node.left);
|
|
548
|
+
const inRight = enclosedBy(node.right);
|
|
549
|
+
return sourceCode
|
|
550
|
+
.getCommentsInside(node)
|
|
551
|
+
.filter((comment) => !inLeft(comment) && !inRight(comment));
|
|
552
|
+
}
|
|
553
|
+
function groupStrandedComments(node, sourceCode) {
|
|
554
|
+
const operator = sourceCode.getTokenAfter(node.left, {
|
|
555
|
+
filter: (token) => token.type === utils_1.AST_TOKEN_TYPES.Punctuator && token.value === '||',
|
|
556
|
+
});
|
|
557
|
+
const groups = {
|
|
558
|
+
leading: [],
|
|
559
|
+
beforeOperator: [],
|
|
560
|
+
afterOperator: [],
|
|
561
|
+
trailing: [],
|
|
562
|
+
};
|
|
563
|
+
for (const comment of strandedComments(node, sourceCode)) {
|
|
564
|
+
if (comment.range[1] <= node.left.range[0]) {
|
|
565
|
+
groups.leading.push(comment);
|
|
566
|
+
}
|
|
567
|
+
else if (comment.range[0] >= node.right.range[1]) {
|
|
568
|
+
groups.trailing.push(comment);
|
|
569
|
+
}
|
|
570
|
+
else if (operator && comment.range[1] <= operator.range[0]) {
|
|
571
|
+
groups.beforeOperator.push(comment);
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
groups.afterOperator.push(comment);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return groups;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* `return`, `throw` and `yield` forbid a LineTerminator between themselves and
|
|
581
|
+
* their operand, so a carried comment that demands its own line cannot sit
|
|
582
|
+
* between one of them and the rewritten expression: a line comment there ends
|
|
583
|
+
* the statement through ASI, and so does a block comment carrying a line
|
|
584
|
+
* terminator, which the grammar reads AS a LineTerminator (#1963). Such a
|
|
585
|
+
* comment rides ahead of the keyword instead.
|
|
586
|
+
*
|
|
587
|
+
* The keyword token is the anchor rather than the start of its line because an
|
|
588
|
+
* insertion point immediately before a token is always a token boundary, while a
|
|
589
|
+
* line start can fall inside a multi-line template literal and would be written
|
|
590
|
+
* into the template's text.
|
|
591
|
+
*/
|
|
592
|
+
const RESTRICTED_KEYWORDS = new Set(['return', 'throw', 'yield']);
|
|
593
|
+
function restrictedKeywordBefore(node, sourceCode) {
|
|
594
|
+
const before = sourceCode.getTokenBefore(node);
|
|
595
|
+
return before && RESTRICTED_KEYWORDS.has(before.value) ? before : null;
|
|
596
|
+
}
|
|
534
597
|
exports.preferNullishCoalescingBooleanProps = (0, createRule_1.createRule)({
|
|
535
598
|
name: 'prefer-nullish-coalescing-boolean-props',
|
|
536
599
|
meta: {
|
|
@@ -582,10 +645,67 @@ exports.preferNullishCoalescingBooleanProps = (0, createRule_1.createRule)({
|
|
|
582
645
|
right: rightText,
|
|
583
646
|
},
|
|
584
647
|
fix(fixer) {
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
648
|
+
const groups = groupStrandedComments(node, sourceCode);
|
|
649
|
+
// Whether the replacement is parenthesized is decided by the
|
|
650
|
+
// expression's context exactly as it is without comments:
|
|
651
|
+
// parentheses are tokens, so letting a carried comment add them
|
|
652
|
+
// would make the comment change the emitted program.
|
|
653
|
+
const selfParens = needsSelfParens(node, sourceCode);
|
|
654
|
+
const text = sourceCode.getText();
|
|
655
|
+
const toSegment = (comment) => ({
|
|
656
|
+
text: text.slice(comment.range[0], comment.range[1]),
|
|
657
|
+
breakAfter: (0, replacementSegments_1.requiresLineBreakAfter)(comment),
|
|
658
|
+
});
|
|
659
|
+
// A carried comment's only anchor is the line the expression
|
|
660
|
+
// opens on, which can start mid-line.
|
|
661
|
+
const startLine = sourceCode.lines[node.loc.start.line - 1] ?? '';
|
|
662
|
+
const indent = /^[\t ]*/.exec(startLine)?.[0] ?? '';
|
|
663
|
+
// Inside parentheses a newline can never trigger ASI, so every
|
|
664
|
+
// comment can ride within the replacement on a line of its own.
|
|
665
|
+
// Without them, a leading comment that demands its own line
|
|
666
|
+
// moves ahead of a restricted keyword when one governs the
|
|
667
|
+
// expression; everywhere else a line break before the
|
|
668
|
+
// expression is inert.
|
|
669
|
+
const keyword = selfParens
|
|
670
|
+
? null
|
|
671
|
+
: restrictedKeywordBefore(node, sourceCode);
|
|
672
|
+
const hoisted = keyword
|
|
673
|
+
? groups.leading.filter(replacementSegments_1.requiresOwnLine)
|
|
674
|
+
: [];
|
|
675
|
+
const segments = [
|
|
676
|
+
...groups.leading
|
|
677
|
+
.filter((comment) => !hoisted.includes(comment))
|
|
678
|
+
.map(toSegment),
|
|
679
|
+
{
|
|
680
|
+
text: parenthesizeLogical(leftText, node.left),
|
|
681
|
+
breakAfter: false,
|
|
682
|
+
},
|
|
683
|
+
...groups.beforeOperator.map(toSegment),
|
|
684
|
+
{ text: '??', breakAfter: false },
|
|
685
|
+
...groups.afterOperator.map(toSegment),
|
|
686
|
+
{
|
|
687
|
+
text: parenthesizeLogical(rightText, node.right),
|
|
688
|
+
breakAfter: false,
|
|
689
|
+
},
|
|
690
|
+
...groups.trailing.map(toSegment),
|
|
691
|
+
];
|
|
692
|
+
if (selfParens) {
|
|
693
|
+
return fixer.replaceText(node, (0, replacementSegments_1.joinSegments)(segments, indent));
|
|
694
|
+
}
|
|
695
|
+
const body = (0, replacementSegments_1.joinSegmentBody)(segments, indent);
|
|
696
|
+
const trailing = segments[segments.length - 1].breakAfter
|
|
697
|
+
? `\n${indent}`
|
|
698
|
+
: '';
|
|
699
|
+
const replacement = fixer.replaceText(node, `${body}${trailing}`);
|
|
700
|
+
if (!keyword || hoisted.length === 0) {
|
|
701
|
+
return replacement;
|
|
702
|
+
}
|
|
703
|
+
return [
|
|
704
|
+
fixer.insertTextBefore(keyword, hoisted
|
|
705
|
+
.map((comment) => `${text.slice(comment.range[0], comment.range[1])}\n${indent}`)
|
|
706
|
+
.join('')),
|
|
707
|
+
replacement,
|
|
708
|
+
];
|
|
589
709
|
},
|
|
590
710
|
});
|
|
591
711
|
}
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,40 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.20.157",
|
|
4
|
+
"date": "2026-08-16T06:24:21.626Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "enforce-positive-naming",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
2025
|
|
11
|
+
],
|
|
12
|
+
"summary": "treat the inherit/increment/integrate/instantiate/inline families as in- exceptions (closes #2025)"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"version": "1.20.156",
|
|
18
|
+
"date": "2026-08-15T18:39:54.919Z",
|
|
19
|
+
"rules": [
|
|
20
|
+
{
|
|
21
|
+
"name": "logical-top-to-bottom-grouping",
|
|
22
|
+
"changeType": "fix",
|
|
23
|
+
"issues": [
|
|
24
|
+
2023
|
|
25
|
+
],
|
|
26
|
+
"summary": "separate reordered statements so a relocated one cannot land inside a trailing // comment (closes #2023)"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "prefer-nullish-coalescing-boolean-props",
|
|
30
|
+
"changeType": "fix",
|
|
31
|
+
"issues": [
|
|
32
|
+
2024
|
|
33
|
+
],
|
|
34
|
+
"summary": "carry the comments stranded between the operands instead of deleting them (closes #2024)"
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
},
|
|
2
38
|
{
|
|
3
39
|
"version": "1.20.155",
|
|
4
40
|
"date": "2026-08-15T14:21:55.491Z",
|