@d3lm/lint-preset 1.5.0 → 1.5.1

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/README.md CHANGED
@@ -285,7 +285,7 @@ A comment with an issue in a part that is kept is handed to the agent, which can
285
285
 
286
286
  ### Configuration
287
287
 
288
- `width` is the preferred line length; `maxWidth` is the hard maximum, including indentation and the comment prefix. The CLI defaults to a maximum of 120 columns (or `width`, if larger). A balanced parenthetical can extend a line past `width` when this keeps it together within `maxWidth`. In longer paragraphs, the wrapper balances all lines together and may split a parenthetical when that produces a better layout. If the parenthetical cannot fit on a line of its own, it wraps normally. For example, `width: 80, maxWidth: 120` keeps `(JSDoc types, \`{@link ...}\`)` together in the example below:
288
+ `width` is the preferred line length; `maxWidth` is the hard maximum, including indentation and the comment prefix. The CLI defaults to a maximum of 120 columns (or `width`, if larger). A balanced parenthetical can extend a line past `width` when this keeps it together within `maxWidth`. In longer paragraphs, the wrapper balances all lines together and may split a parenthetical when that produces a better layout. A line never sticks out more than 15 columns past the line before it, like a full second line after a first line that the orphan rule keeps short, when up to two more lines avoid that. If the parenthetical cannot fit on a line of its own, it wraps normally. For example, `width: 80, maxWidth: 120` keeps `(JSDoc types, \`{@link ...}\`)` together in the example below:
289
289
 
290
290
  ```ts
291
291
  /**
@@ -578,6 +578,8 @@ function hasWidow(lines) {
578
578
  }
579
579
  var FULLNESS_WEIGHT = 0.07;
580
580
  var PARENTHETICAL_SPLIT_WEIGHT = 2;
581
+ var MAX_STEP = 15;
582
+ var EXTRA_LINES = 2;
581
583
  function fewestLines(starts, target) {
582
584
  const total = starts.length - 1;
583
585
  const fewest = new Int32Array(total + 1);
@@ -710,7 +712,56 @@ function balanceTokens(tokens, firstLimit, restLimit, count, options, preferredL
710
712
  const bounds = [0, ...best.breaks, total];
711
713
  return bounds.slice(0, -1).map((start, index) => tokens.slice(start, bounds[index + 1]));
712
714
  }
715
+ function hasStep(lines, offset = 0) {
716
+ return lines.some((line, index) => {
717
+ if (index === 0) {
718
+ return false;
719
+ }
720
+ const before = lineLength(lines[index - 1]) + (index === 1 ? offset : 0);
721
+ return lineLength(line) - before > MAX_STEP;
722
+ });
723
+ }
724
+ function splitCostsOf(tokens, limit) {
725
+ const splitCosts = /* @__PURE__ */ new Map();
726
+ for (const group of parentheticalGroups(tokens, limit)) {
727
+ const length = lineLength(tokens.slice(group.start, group.end));
728
+ for (let at = group.start + 1; at < group.end; at += 1) {
729
+ splitCosts.set(at, length * PARENTHETICAL_SPLIT_WEIGHT);
730
+ }
731
+ }
732
+ return splitCosts;
733
+ }
734
+ function hasParentheticals(tokens, restLimit, options) {
735
+ if (options.parentheticalOverflow === void 0) {
736
+ return false;
737
+ }
738
+ return bindParentheticals(tokens, restLimit + options.parentheticalOverflow).some((token) => isParenthetical(token));
739
+ }
713
740
  function layoutTokens(tokens, firstLimit, restLimit, options) {
741
+ const lines = layoutLines(tokens, firstLimit, restLimit, options);
742
+ const offset = restLimit - firstLimit;
743
+ if (options.keepFirstLine || options.keepSteps || !hasStep(lines, offset)) {
744
+ return lines;
745
+ }
746
+ const overflow = hasParentheticals(tokens, restLimit, options) ? options.parentheticalOverflow ?? 0 : 0;
747
+ const splitCosts = overflow > 0 ? splitCostsOf(tokens, restLimit + overflow) : /* @__PURE__ */ new Map();
748
+ for (let count = lines.length + 1; count <= lines.length + EXTRA_LINES; count += 1) {
749
+ const even = balanceTokens(
750
+ tokens,
751
+ firstLimit + overflow,
752
+ restLimit + overflow,
753
+ count,
754
+ options,
755
+ restLimit,
756
+ splitCosts
757
+ );
758
+ if (even && !hasStep(even, offset)) {
759
+ return even;
760
+ }
761
+ }
762
+ return lines;
763
+ }
764
+ function layoutLines(tokens, firstLimit, restLimit, options) {
714
765
  if (options.parentheticalOverflow !== void 0) {
715
766
  const grouped = bindParentheticals(tokens, restLimit + options.parentheticalOverflow);
716
767
  if (grouped.some((token) => isParenthetical(token))) {
@@ -744,14 +795,7 @@ function layoutTokens(tokens, firstLimit, restLimit, options) {
744
795
  return expanded;
745
796
  });
746
797
  }
747
- const splitCosts = /* @__PURE__ */ new Map();
748
- const groups = parentheticalGroups(tokens, restLimit + options.parentheticalOverflow);
749
- for (const group of groups) {
750
- const length = lineLength(tokens.slice(group.start, group.end));
751
- for (let at = group.start + 1; at < group.end; at += 1) {
752
- splitCosts.set(at, length * PARENTHETICAL_SPLIT_WEIGHT);
753
- }
754
- }
798
+ const splitCosts = splitCostsOf(tokens, restLimit + options.parentheticalOverflow);
755
799
  const preferred = wrapTokens(tokens, firstLimit, restLimit, { ...options, parentheticalOverflow: void 0 });
756
800
  for (let count = minimumLines; count <= Math.max(preferred.length + 1, preserved.length); count += 1) {
757
801
  const balanced2 = balanceTokens(
@@ -1270,11 +1314,11 @@ function analyzeParagraph(paragraph, options) {
1270
1314
  parentheticalOverflow: options.maxWidth === void 0 ? void 0 : options.maxWidth - options.width,
1271
1315
  keepFirstLine: paragraph.kind === "tag"
1272
1316
  };
1273
- const layOut = (from) => {
1274
- const even2 = layoutTokens(from, firstLimit, restLimit, layoutOptions);
1275
- return { even: even2, wrapped: breakAfterSentence(even2, firstLimit, restLimit, layoutOptions) };
1317
+ const layOut = (from, settings) => {
1318
+ const even2 = layoutTokens(from, firstLimit, restLimit, settings);
1319
+ return { even: even2, wrapped: breakAfterSentence(even2, firstLimit, restLimit, settings) };
1276
1320
  };
1277
- let units = bindLineStarts(paragraph.kind === "tag" ? bindTagHead(tokens) : tokens, startsLine);
1321
+ const units = bindLineStarts(paragraph.kind === "tag" ? bindTagHead(tokens) : tokens, startsLine);
1278
1322
  const hardLimit = restLimit + (options.maxWidth === void 0 ? options.tolerance : options.maxWidth - options.width);
1279
1323
  const written = currentLines.map((words) => ` ${words.join(" ")} `);
1280
1324
  const staysInLimit = (unit) => {
@@ -1283,29 +1327,38 @@ function analyzeParagraph(paragraph, options) {
1283
1327
  if (!units.every((unit) => staysInLimit(unit))) {
1284
1328
  return keep({ tier: "manual", reason: "unbreakable text across lines" });
1285
1329
  }
1286
- let { even, wrapped } = layOut(units);
1287
1330
  const bodyOf = (lines) => {
1288
1331
  return lines.map((words, position) => {
1289
1332
  const text = `${position === 0 ? paragraph.marker : hang}${words.join(" ")}`;
1290
1333
  return { ...first, text, verbatim: false, hardBreak: HARD_BREAK.test(text) };
1291
1334
  });
1292
1335
  };
1293
- for (; ; ) {
1294
- const body = bodyOf(wrapped);
1295
- const rewrap = structureOf(body);
1296
- if (!rewrap) {
1297
- break;
1298
- }
1299
- const tipping = body.findIndex((_, position) => structureOf(body.slice(0, position + 1)) !== void 0);
1300
- const start = wrapped.slice(0, Math.max(tipping, 1)).flat().length;
1301
- const pair = `${units[start - 1]} ${units[start]}`;
1302
- if (body.length < 2 || !staysInLimit(pair)) {
1303
- const reading = rewrap.tier === "code-like" ? "code" : rewrap.reason;
1304
- return keep({ tier: "manual", reason: `rewrap would read as ${reading}` });
1336
+ const settle = (settings) => {
1337
+ let current = units;
1338
+ let { even: even2, wrapped: wrapped2 } = layOut(current, settings);
1339
+ for (; ; ) {
1340
+ const body = bodyOf(wrapped2);
1341
+ const rewrap = structureOf(body);
1342
+ if (!rewrap) {
1343
+ return { even: even2, wrapped: wrapped2 };
1344
+ }
1345
+ const tipping = body.findIndex((_, position) => structureOf(body.slice(0, position + 1)) !== void 0);
1346
+ const start = wrapped2.slice(0, Math.max(tipping, 1)).flat().length;
1347
+ const pair = `${current[start - 1]} ${current[start]}`;
1348
+ if (body.length < 2 || !staysInLimit(pair)) {
1349
+ const reading = rewrap.tier === "code-like" ? "code" : rewrap.reason;
1350
+ return { tier: "manual", reason: `rewrap would read as ${reading}` };
1351
+ }
1352
+ current = current.toSpliced(start - 1, 2, pair);
1353
+ ({ even: even2, wrapped: wrapped2 } = layOut(current, settings));
1305
1354
  }
1306
- units = units.toSpliced(start - 1, 2, pair);
1307
- ({ even, wrapped } = layOut(units));
1355
+ };
1356
+ const settled = settle(layoutOptions);
1357
+ const layout = "wrapped" in settled ? settled : settle({ ...layoutOptions, keepSteps: true });
1358
+ if (!("wrapped" in layout)) {
1359
+ return keep(layout);
1308
1360
  }
1361
+ const { even, wrapped } = layout;
1309
1362
  const matchesLayout = sameLayout(wrapped, currentLines);
1310
1363
  const lineIssues = [];
1311
1364
  const overflow = options.maxWidth === void 0 ? [] : overflowIssues(paragraph.lines, options);