@blumintinc/eslint-plugin-blumint 1.20.174 → 1.20.175
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
|
@@ -647,9 +647,9 @@ exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
|
|
|
647
647
|
* inline there emits text the formatter immediately re-breaks, so the fix
|
|
648
648
|
* is never a fixed point of the consumer's own formatting pass (#2097).
|
|
649
649
|
*
|
|
650
|
-
* A list written across lines answers the first half outright
|
|
651
|
-
*
|
|
652
|
-
* the
|
|
650
|
+
* A list written across lines answers the first half outright and is
|
|
651
|
+
* handled before this is asked: the caller keeps it broken, except for
|
|
652
|
+
* the block-comment tail {@link flattenedListFixes} claims (#2142).
|
|
653
653
|
*
|
|
654
654
|
* The width half is answered only where the formatter's own answer is
|
|
655
655
|
* modelled end to end. A trailing options object gets hugged against the
|
|
@@ -683,17 +683,93 @@ exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
|
|
|
683
683
|
const widest = Math.max(...spans.map((span) => span.end - span.start), ...appended.map((argument) => argument.length));
|
|
684
684
|
return body + widest + ','.length <= PRINT_WIDTH;
|
|
685
685
|
}
|
|
686
|
+
/**
|
|
687
|
+
* The width the call prints at with its argument list riding one line:
|
|
688
|
+
* everything on the opening line through the parenthesis, each span
|
|
689
|
+
* joined by `, `, the tail comments a written comma strands outside the
|
|
690
|
+
* last span, the appended arguments, and whatever follows the closing
|
|
691
|
+
* parenthesis on its own line.
|
|
692
|
+
*/
|
|
693
|
+
function flatListWidth(layout, appended, nameDelta, gapComments) {
|
|
694
|
+
const { openParen, closeParen, spans } = layout;
|
|
695
|
+
const lastSpanEnd = spans[spans.length - 1].end;
|
|
696
|
+
const suffix = (sourceCode.lines[closeParen.loc.start.line - 1] ?? '')
|
|
697
|
+
.slice(closeParen.loc.start.column)
|
|
698
|
+
.trimEnd();
|
|
699
|
+
return (openParen.loc.end.column +
|
|
700
|
+
nameDelta +
|
|
701
|
+
spans.reduce((width, span) => width + (span.end - span.start), 0) +
|
|
702
|
+
(spans.length - 1) * ', '.length +
|
|
703
|
+
gapComments
|
|
704
|
+
.filter((comment) => comment.range[0] >= lastSpanEnd)
|
|
705
|
+
.reduce((width, comment) => width + ' '.length + (comment.range[1] - comment.range[0]), 0) +
|
|
706
|
+
appended.reduce((width, argument) => width + ', '.length + argument.length, 0) +
|
|
707
|
+
suffix.length);
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* The edits that print an authored-broken list flat, with `appended` on
|
|
711
|
+
* its tail — or `null` where the flat layout is out of reach or out of
|
|
712
|
+
* scope.
|
|
713
|
+
*
|
|
714
|
+
* A list written across lines usually stays broken: a multi-line argument
|
|
715
|
+
* cannot be inlined without rewriting its text, and a line comment pins
|
|
716
|
+
* its break outright. A break held up by nothing but a BLOCK comment
|
|
717
|
+
* trailing the last argument is neither — a block comment is no line
|
|
718
|
+
* terminator, so the consumer's formatter collapses the whole call as
|
|
719
|
+
* soon as it fits the print width, and no broken emission is a fixed
|
|
720
|
+
* point of its formatting pass there (#2142). Only that annotated tail
|
|
721
|
+
* claims the flat layout; elsewhere the author's breaks are kept — a
|
|
722
|
+
* choice the formatter may fold, but one this fix did not create.
|
|
723
|
+
*/
|
|
724
|
+
function flattenedListFixes(fixer, node, layout, appended, nameDelta, beforeClose, gapComments, ownLineComments) {
|
|
725
|
+
const { openParen, closeParen, spans } = layout;
|
|
726
|
+
if (gapComments.length === 0 ||
|
|
727
|
+
ownLineComments.length > 0 ||
|
|
728
|
+
gapComments.some(replacementSegments_1.requiresOwnLine) ||
|
|
729
|
+
calleeBreaksFirst(node.callee) ||
|
|
730
|
+
spans.some((span) => sourceCode.text.slice(span.start, span.end).includes('\n')) ||
|
|
731
|
+
flatListWidth(layout, appended, nameDelta, gapComments) > PRINT_WIDTH) {
|
|
732
|
+
return null;
|
|
733
|
+
}
|
|
734
|
+
const fixes = [
|
|
735
|
+
fixer.replaceTextRange([openParen.range[1], spans[0].start], ''),
|
|
736
|
+
];
|
|
737
|
+
for (let index = 1; index < spans.length; index++) {
|
|
738
|
+
fixes.push(fixer.replaceTextRange([spans[index - 1].end, spans[index].start], ', '));
|
|
739
|
+
}
|
|
740
|
+
const lastSpanEnd = spans[spans.length - 1].end;
|
|
741
|
+
const flatTail = appended.map((argument) => `, ${argument}`).join('');
|
|
742
|
+
if (beforeClose.value !== ',') {
|
|
743
|
+
// With no written comma the tail comments sit inside the last span,
|
|
744
|
+
// so everything between the span and the parenthesis is whitespace.
|
|
745
|
+
fixes.push(fixer.replaceTextRange([lastSpanEnd, closeParen.range[0]], flatTail));
|
|
746
|
+
return fixes;
|
|
747
|
+
}
|
|
748
|
+
const pastComma = gapComments.filter((comment) => comment.range[0] >= beforeClose.range[1]);
|
|
749
|
+
if (pastComma.length === 0) {
|
|
750
|
+
// The written comma already trails the annotation; it is the
|
|
751
|
+
// separator the appended arguments ride on.
|
|
752
|
+
fixes.push(fixer.replaceTextRange([beforeClose.range[1], closeParen.range[0]], ` ${appended.join(', ')}`));
|
|
753
|
+
return fixes;
|
|
754
|
+
}
|
|
755
|
+
// The written comma moves past the comments it precedes: prettier
|
|
756
|
+
// prints a block comment on a list element BEFORE the separator.
|
|
757
|
+
fixes.push(fixer.removeRange(beforeClose.range));
|
|
758
|
+
fixes.push(fixer.replaceTextRange([pastComma[pastComma.length - 1].range[1], closeParen.range[0]], flatTail));
|
|
759
|
+
return fixes;
|
|
760
|
+
}
|
|
686
761
|
/**
|
|
687
762
|
* The edits that add `appended` to the end of a call's argument list, laid
|
|
688
763
|
* out the way the consumer's formatter prints the result — or `null` for
|
|
689
764
|
* the one tail no edit can extend without retargeting a directive.
|
|
690
765
|
*
|
|
691
|
-
*
|
|
692
|
-
*
|
|
693
|
-
*
|
|
694
|
-
*
|
|
695
|
-
*
|
|
696
|
-
*
|
|
766
|
+
* No layout re-emits an argument from its text. The broken one rewrites
|
|
767
|
+
* the SEPARATORS between the arguments and shifts the indentation of the
|
|
768
|
+
* ones that span lines, and the flat one rewrites the separators alone,
|
|
769
|
+
* so everything between them — comments included, and a dropped
|
|
770
|
+
* `eslint-disable` silently re-enables the rule it was suppressing
|
|
771
|
+
* (#1877) — stays where it was written, attached to the argument it
|
|
772
|
+
* belongs to.
|
|
697
773
|
*
|
|
698
774
|
* `nameDelta` is how much the caller's own rename widens the call, since
|
|
699
775
|
* the two edits land on the same line and the width answer is about the
|
|
@@ -705,10 +781,17 @@ exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
|
|
|
705
781
|
fixer.insertTextAfter(args[args.length - 1], appended.map((argument) => `, ${argument}`).join('')),
|
|
706
782
|
];
|
|
707
783
|
const layout = callLayout(node);
|
|
708
|
-
if (!layout
|
|
784
|
+
if (!layout) {
|
|
709
785
|
return inline();
|
|
710
786
|
}
|
|
711
787
|
const { openParen, closeParen, spans } = layout;
|
|
788
|
+
const listSpansLines = sourceCode.text
|
|
789
|
+
.slice(openParen.range[1], closeParen.range[0])
|
|
790
|
+
.includes('\n');
|
|
791
|
+
if (!listSpansLines &&
|
|
792
|
+
!requiresBrokenList(node, layout, appended, nameDelta)) {
|
|
793
|
+
return inline();
|
|
794
|
+
}
|
|
712
795
|
// Between the last argument's own last token and the closing parenthesis
|
|
713
796
|
// sit the trailing comma, if one was written, and any comment. The tail
|
|
714
797
|
// SPAN absorbs such a comment whenever no comma follows it, so the span's
|
|
@@ -739,6 +822,12 @@ exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
|
|
|
739
822
|
if (sameLineComments.some(isPositionalDirective)) {
|
|
740
823
|
return null;
|
|
741
824
|
}
|
|
825
|
+
if (listSpansLines) {
|
|
826
|
+
const flattened = flattenedListFixes(fixer, node, layout, appended, nameDelta, beforeClose, gapComments, ownLineComments);
|
|
827
|
+
if (flattened) {
|
|
828
|
+
return flattened;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
742
831
|
// A broken list is indented one step past the line its parenthesis opens
|
|
743
832
|
// on, whatever depth that line sits at, and closes at that line's own
|
|
744
833
|
// column. A constant indent is right for exactly one call site.
|
|
@@ -769,18 +858,34 @@ exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
|
|
|
769
858
|
}
|
|
770
859
|
const separatorPresent = beforeClose.value === ',';
|
|
771
860
|
const lastSameLine = sameLineComments[sameLineComments.length - 1];
|
|
861
|
+
// WHERE the separator sits among the trailing comments is the
|
|
862
|
+
// formatter's call, not a constant: prettier prints a line comment on a
|
|
863
|
+
// list element after the comma and a block comment before it (#2142).
|
|
864
|
+
// The comma therefore lands past the leading run of block comments — at
|
|
865
|
+
// the argument's own last token when that run is empty — and a comma
|
|
866
|
+
// the author wrote on the wrong side of the run is moved rather than
|
|
867
|
+
// doubled. Everything else in the annotation is left byte for byte as
|
|
868
|
+
// written.
|
|
869
|
+
let blockRunEnd = lastArgumentToken.range[1];
|
|
870
|
+
for (const comment of sameLineComments) {
|
|
871
|
+
if ((0, replacementSegments_1.requiresLineBreakAfter)(comment)) {
|
|
872
|
+
break;
|
|
873
|
+
}
|
|
874
|
+
blockRunEnd = comment.range[1];
|
|
875
|
+
}
|
|
876
|
+
const commaMisplaced = separatorPresent && beforeClose.range[1] < blockRunEnd;
|
|
877
|
+
const needsComma = !separatorPresent || commaMisplaced;
|
|
878
|
+
if (commaMisplaced) {
|
|
879
|
+
fixes.push(fixer.removeRange(beforeClose.range));
|
|
880
|
+
}
|
|
772
881
|
if (ownLineComments.length === 0) {
|
|
773
882
|
// The comment trails the argument it annotates, so it keeps that line
|
|
774
|
-
// and the option opens the next one.
|
|
775
|
-
// the argument and the comment — after a line comment it is comment
|
|
776
|
-
// text — so one is inserted at the argument's own last token unless
|
|
777
|
-
// the author already wrote it, and everything through the end of the
|
|
778
|
-
// annotation is left byte for byte as written.
|
|
779
|
-
if (!separatorPresent) {
|
|
780
|
-
fixes.push(fixer.insertTextAfter(lastArgumentToken, ','));
|
|
781
|
-
}
|
|
883
|
+
// and the option opens the next one.
|
|
782
884
|
const annotationEnd = Math.max(lastSameLine.range[1], beforeClose.range[1]);
|
|
783
|
-
|
|
885
|
+
if (needsComma && blockRunEnd < annotationEnd) {
|
|
886
|
+
fixes.push(fixer.insertTextAfterRange([blockRunEnd, blockRunEnd], ','));
|
|
887
|
+
}
|
|
888
|
+
fixes.push(fixer.replaceTextRange([annotationEnd, closeParen.range[0]], `${needsComma && blockRunEnd >= annotationEnd ? ',' : ''}${appended
|
|
784
889
|
.map((argument) => `\n${body}${argument},`)
|
|
785
890
|
.join('')}\n${indent}`));
|
|
786
891
|
return fixes;
|
|
@@ -792,16 +897,20 @@ exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
|
|
|
792
897
|
const appendedText = appended
|
|
793
898
|
.map((argument) => `\n${body}${argument},`)
|
|
794
899
|
.join('');
|
|
795
|
-
const anchor = Math.max(separatorPresent
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
900
|
+
const anchor = Math.max(separatorPresent && !commaMisplaced
|
|
901
|
+
? beforeClose.range[1]
|
|
902
|
+
: lastArgumentToken.range[1], lastSameLine ? lastSameLine.range[1] : 0);
|
|
903
|
+
if (!needsComma) {
|
|
904
|
+
fixes.push(fixer.insertTextAfterRange([anchor, anchor], appendedText));
|
|
905
|
+
return fixes;
|
|
906
|
+
}
|
|
907
|
+
if (blockRunEnd < anchor) {
|
|
908
|
+
fixes.push(fixer.insertTextAfterRange([blockRunEnd, blockRunEnd], ','));
|
|
800
909
|
fixes.push(fixer.insertTextAfterRange([anchor, anchor], appendedText));
|
|
801
910
|
return fixes;
|
|
802
911
|
}
|
|
803
|
-
// With
|
|
804
|
-
//
|
|
912
|
+
// With the separator due at the annotation's own end, it and the option
|
|
913
|
+
// travel as one insertion, since both land at the same offset.
|
|
805
914
|
fixes.push(fixer.insertTextAfterRange([anchor, anchor], `,${appendedText}`));
|
|
806
915
|
return fixes;
|
|
807
916
|
}
|
|
@@ -883,14 +883,19 @@ function chainComments(node, sourceCode, operands) {
|
|
|
883
883
|
* inside an operand's own brackets — a line comment in an object literal passed
|
|
884
884
|
* to a call — belongs to that operand's layout and leaves the chain's own layout
|
|
885
885
|
* alone, which is how prettier prints it.
|
|
886
|
+
*
|
|
887
|
+
* `absorbed` holds the comments the rewrite carries out past the statement's
|
|
888
|
+
* closing punctuator: those sit in no gap of the emitted chain, so counting one
|
|
889
|
+
* as a break would split a group that ends up holding no comment at all
|
|
890
|
+
* (#2141).
|
|
886
891
|
*/
|
|
887
|
-
function chainBreaksLine(node, sourceCode) {
|
|
892
|
+
function chainBreaksLine(node, sourceCode, absorbed) {
|
|
888
893
|
if (node.type !== utils_1.AST_NODE_TYPES.LogicalExpression) {
|
|
889
894
|
return false;
|
|
890
895
|
}
|
|
891
|
-
return (strandedComments(node, sourceCode).some(replacementSegments_1.requiresLineBreakAfter) ||
|
|
892
|
-
chainBreaksLine(node.left, sourceCode) ||
|
|
893
|
-
chainBreaksLine(node.right, sourceCode));
|
|
896
|
+
return (strandedComments(node, sourceCode).some((comment) => !absorbed.has(comment) && (0, replacementSegments_1.requiresLineBreakAfter)(comment)) ||
|
|
897
|
+
chainBreaksLine(node.left, sourceCode, absorbed) ||
|
|
898
|
+
chainBreaksLine(node.right, sourceCode, absorbed));
|
|
894
899
|
}
|
|
895
900
|
/**
|
|
896
901
|
* Whether the chain a link belongs to breaks the line, asked of the whole chain
|
|
@@ -902,12 +907,16 @@ function chainBreaksLine(node, sourceCode) {
|
|
|
902
907
|
* broken packs two operands onto a line the formatter then splits (#2106).
|
|
903
908
|
*
|
|
904
909
|
* A comment trailing the chain, or leading it, is outside every gap: prettier
|
|
905
|
-
* prints it on the line the chain already occupies and moves no operand.
|
|
910
|
+
* prints it on the line the chain already occupies and moves no operand. The
|
|
911
|
+
* same goes for a comment in `absorbed` — one the fix carries out past the
|
|
912
|
+
* statement's closing punctuator: wherever the INPUT held it, the emission
|
|
913
|
+
* holds it behind the statement, outside every gap (#2141).
|
|
906
914
|
*/
|
|
907
|
-
function chainBreaksAtAGap(chain, sourceCode) {
|
|
915
|
+
function chainBreaksAtAGap(chain, sourceCode, absorbed) {
|
|
908
916
|
const operands = chainOperands(chain);
|
|
909
917
|
const { gaps } = chainComments(chain, sourceCode, operands);
|
|
910
|
-
return (gaps.some((gap) => [...gap.beforeOperator, ...gap.afterOperator].some(
|
|
918
|
+
return (gaps.some((gap) => [...gap.beforeOperator, ...gap.afterOperator].some((comment) => !absorbed.has(comment) && (0, replacementSegments_1.requiresLineBreakAfter)(comment))) ||
|
|
919
|
+
operands.some((operand) => chainBreaksLine(operand, sourceCode, absorbed)));
|
|
911
920
|
}
|
|
912
921
|
/**
|
|
913
922
|
* The depths the rebuilt expression lands at.
|
|
@@ -1196,6 +1205,36 @@ exports.preferNullishCoalescingBooleanProps = (0, createRule_1.createRule)({
|
|
|
1196
1205
|
});
|
|
1197
1206
|
});
|
|
1198
1207
|
segments.push(...comments.trailing.map(toSegment));
|
|
1208
|
+
// A comment trailing the whole expression rides out past the
|
|
1209
|
+
// punctuator that closes the statement the chain stands in,
|
|
1210
|
+
// where one is available to take over. Kept inside the
|
|
1211
|
+
// replacement, the line break such a comment demands strands
|
|
1212
|
+
// that punctuator on a line of its own — layout a formatter
|
|
1213
|
+
// folds straight back, and the fold moves an
|
|
1214
|
+
// `eslint-disable-next-line` written on the comment's line
|
|
1215
|
+
// onto a different subject, so the pipeline's formatting order
|
|
1216
|
+
// decides which violations are enforced (#2139). The span this
|
|
1217
|
+
// fix replaces can end PAST the node, at a redundant paren the
|
|
1218
|
+
// widening claims, so the lookup anchors on that paren where
|
|
1219
|
+
// the widening applies: asked of the node, it answers with the
|
|
1220
|
+
// paren — a token inside the span — instead of the punctuator
|
|
1221
|
+
// behind it. The landing widening reaches BACKWARD from the
|
|
1222
|
+
// node and never past its end, so everywhere else the span
|
|
1223
|
+
// ends at the node's own last token. The parenthesized
|
|
1224
|
+
// (selfParens) replacement keeps every comment inside the
|
|
1225
|
+
// emitted parens, where no punctuator can be stranded.
|
|
1226
|
+
const spanEndToken = redundantParens(node, sourceCode)
|
|
1227
|
+
? sourceCode.getTokenAfter(node)
|
|
1228
|
+
: sourceCode.getLastToken(node);
|
|
1229
|
+
const closingPunctuator = !selfParens && comments.trailing.length > 0 && spanEndToken
|
|
1230
|
+
? (0, replacementSegments_1.absorbableClosingPunctuator)(sourceCode, spanEndToken, comments.trailing)
|
|
1231
|
+
: null;
|
|
1232
|
+
// The absorption is decided BEFORE the break decision because
|
|
1233
|
+
// a comment carried past the punctuator sits in no gap of the
|
|
1234
|
+
// emitted chain: counted as a break it would put every operand
|
|
1235
|
+
// on a line of its own for a comment that no longer sits
|
|
1236
|
+
// between any of them (#2141).
|
|
1237
|
+
const absorbed = new Set(closingPunctuator ? comments.trailing : []);
|
|
1199
1238
|
// Prettier prints a logical chain as ONE group: once anything
|
|
1200
1239
|
// inside it breaks the line, EVERY operand takes a line of its
|
|
1201
1240
|
// own. So a chain broken by a comment anywhere gets a break in
|
|
@@ -1204,7 +1243,7 @@ exports.preferNullishCoalescingBooleanProps = (0, createRule_1.createRule)({
|
|
|
1204
1243
|
// The break rides on the last thing in the gap so a carried
|
|
1205
1244
|
// comment keeps the line it was written on, and a gap already
|
|
1206
1245
|
// holding a break needs no second separator.
|
|
1207
|
-
const chainBreaks = chainBreaksAtAGap(chainRootOf(node), sourceCode);
|
|
1246
|
+
const chainBreaks = chainBreaksAtAGap(chainRootOf(node), sourceCode, absorbed);
|
|
1208
1247
|
if (chainBreaks) {
|
|
1209
1248
|
for (const { start, end } of gapSpans) {
|
|
1210
1249
|
const separated = segments
|
|
@@ -1236,26 +1275,6 @@ exports.preferNullishCoalescingBooleanProps = (0, createRule_1.createRule)({
|
|
|
1236
1275
|
: null;
|
|
1237
1276
|
const leading = landing ? `\n${bodyIndent}` : '';
|
|
1238
1277
|
const range = replacementRange(node, sourceCode, landing);
|
|
1239
|
-
// A comment trailing the whole expression rides out past the
|
|
1240
|
-
// punctuator that closes the statement the chain stands in,
|
|
1241
|
-
// where one is available to take over. Kept inside the
|
|
1242
|
-
// replacement, the line break such a comment demands strands
|
|
1243
|
-
// that punctuator on a line of its own — layout a formatter
|
|
1244
|
-
// folds straight back, and the fold moves an
|
|
1245
|
-
// `eslint-disable-next-line` written on the comment's line
|
|
1246
|
-
// onto a different subject, so the pipeline's formatting order
|
|
1247
|
-
// decides which violations are enforced (#2139). The span this
|
|
1248
|
-
// fix replaces can end PAST the node, at a redundant paren the
|
|
1249
|
-
// widening claims, so the lookup anchors on the span's own
|
|
1250
|
-
// last token: asked of the node, it answers with that paren —
|
|
1251
|
-
// a token inside the span — instead of the punctuator behind
|
|
1252
|
-
// it.
|
|
1253
|
-
const spanEndToken = range[1] > node.range[1]
|
|
1254
|
-
? sourceCode.getTokenAfter(node)
|
|
1255
|
-
: sourceCode.getLastToken(node);
|
|
1256
|
-
const closingPunctuator = comments.trailing.length > 0 && spanEndToken
|
|
1257
|
-
? (0, replacementSegments_1.absorbableClosingPunctuator)(sourceCode, spanEndToken, comments.trailing)
|
|
1258
|
-
: null;
|
|
1259
1278
|
const bodySegments = closingPunctuator
|
|
1260
1279
|
? segments.slice(0, segments.length - comments.trailing.length)
|
|
1261
1280
|
: segments;
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.20.175",
|
|
4
|
+
"date": "2026-08-26T06:19:14.998Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "enforce-firestore-set-merge",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
2142
|
|
11
|
+
],
|
|
12
|
+
"summary": "place a block comment before the separator and fold a list that fits (closes #2142)"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "prefer-nullish-coalescing-boolean-props",
|
|
16
|
+
"changeType": "fix",
|
|
17
|
+
"issues": [
|
|
18
|
+
2141
|
|
19
|
+
],
|
|
20
|
+
"summary": "discount an absorbed comment from the chain-break decision (closes #2141)"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
},
|
|
2
24
|
{
|
|
3
25
|
"version": "1.20.174",
|
|
4
26
|
"date": "2026-08-26T04:47:21.242Z",
|