@d3lm/lint-preset 1.5.2 → 1.5.5

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.
@@ -4,8 +4,9 @@ interface WrapOptions {
4
4
  /**
5
5
  * Sets whether a line may not end with the first word of a sentence, clause, or parenthetical,
6
6
  * like `This`, `the` after a comma, or `(the`. Such a word moves to the next line instead.
7
- * It also sets whether the first of two lines ends with a sentence instead of the start of the
8
- * next one, as long as the second line is then no longer than the first.
7
+ * It also sets whether a line avoids ending with the first two words of a sentence, and whether
8
+ * the first of two lines ends with a sentence instead of the start of the next one, as long as
9
+ * the second line is then no longer than the first.
9
10
  */
10
11
  readonly avoidSentenceOrphans: boolean;
11
12
  /**
@@ -18,7 +19,7 @@ interface WrapOptions {
18
19
  */
19
20
  readonly parentheticalOverflow?: number;
20
21
  }
21
- type LineIssueType = 'overflow' | 'underfill' | 'orphan' | 'widow' | 'unbalanced';
22
+ type LineIssueType = 'overflow' | 'underfill' | 'orphan' | 'widow' | 'unbalanced' | 'split-parenthetical';
22
23
  /**
23
24
  * JSDoc writes types and `{@link ...}` tags as `{...}` groups, so they stay intact like code spans.
24
25
  */
@@ -43,8 +44,8 @@ interface AnalyzeOptions extends WrapOptions {
43
44
  readonly maxWidth?: number;
44
45
  /**
45
46
  * Sets the maximum length of the line that holds a comment whose whole text is one
46
- * prose paragraph. Such a comment stays on one line up to this length, even past
47
- * `width`. It defaults to `width`.
47
+ * prose paragraph. Such a comment stays on one line up to this length plus `tolerance`,
48
+ * even past `width`, but never past `maxWidth`. It defaults to `width`.
48
49
  */
49
50
  readonly singleLineWidth?: number;
50
51
  readonly tabWidth: number;
@@ -206,8 +207,9 @@ interface CommentsConfig {
206
207
  readonly maxWidth?: number;
207
208
  /**
208
209
  * Sets the maximum length of the line that holds a comment whose whole text is one prose
209
- * paragraph. Such a comment stays on one line up to this length, even past `width`, and is
210
- * wrapped to `width` beyond it. Defaults to 120 and never exceeds `maxWidth`.
210
+ * paragraph. Such a comment stays on one line up to this length plus `tolerance`, even past
211
+ * `width`, and is wrapped to `width` beyond it. Defaults to 120, and the line never exceeds
212
+ * `maxWidth`.
211
213
  */
212
214
  readonly singleLineWidth?: number;
213
215
  /**
@@ -220,8 +222,10 @@ interface CommentsConfig {
220
222
  * Keep the first word of a new sentence or parenthetical off the end of a line, e.g.
221
223
  * `...the module. This` or `...reflows (the` followed by the rest on the next line.
222
224
  * A paragraph of two lines also ends its first line with a sentence when the rest fits
223
- * on the second line and is no longer than the first, instead of with the start of the
224
- * next sentence, like `...the module. This one`. Defaults to `true`.
225
+ * on the second line and is no longer than the first, instead of with the start of
226
+ * the next sentence, like `...the module. This one`. Other lines avoid ending with the
227
+ * first two words of a sentence, like `...opens. It allocates`, unless that makes them
228
+ * much less even. Defaults to `true`.
225
229
  */
226
230
  readonly avoidSentenceOrphans?: boolean;
227
231
  /**
@@ -4,8 +4,9 @@ interface WrapOptions {
4
4
  /**
5
5
  * Sets whether a line may not end with the first word of a sentence, clause, or parenthetical,
6
6
  * like `This`, `the` after a comma, or `(the`. Such a word moves to the next line instead.
7
- * It also sets whether the first of two lines ends with a sentence instead of the start of the
8
- * next one, as long as the second line is then no longer than the first.
7
+ * It also sets whether a line avoids ending with the first two words of a sentence, and whether
8
+ * the first of two lines ends with a sentence instead of the start of the next one, as long as
9
+ * the second line is then no longer than the first.
9
10
  */
10
11
  readonly avoidSentenceOrphans: boolean;
11
12
  /**
@@ -18,7 +19,7 @@ interface WrapOptions {
18
19
  */
19
20
  readonly parentheticalOverflow?: number;
20
21
  }
21
- type LineIssueType = 'overflow' | 'underfill' | 'orphan' | 'widow' | 'unbalanced';
22
+ type LineIssueType = 'overflow' | 'underfill' | 'orphan' | 'widow' | 'unbalanced' | 'split-parenthetical';
22
23
  /**
23
24
  * JSDoc writes types and `{@link ...}` tags as `{...}` groups, so they stay intact like code spans.
24
25
  */
@@ -43,8 +44,8 @@ interface AnalyzeOptions extends WrapOptions {
43
44
  readonly maxWidth?: number;
44
45
  /**
45
46
  * Sets the maximum length of the line that holds a comment whose whole text is one
46
- * prose paragraph. Such a comment stays on one line up to this length, even past
47
- * `width`. It defaults to `width`.
47
+ * prose paragraph. Such a comment stays on one line up to this length plus `tolerance`,
48
+ * even past `width`, but never past `maxWidth`. It defaults to `width`.
48
49
  */
49
50
  readonly singleLineWidth?: number;
50
51
  readonly tabWidth: number;
@@ -206,8 +207,9 @@ interface CommentsConfig {
206
207
  readonly maxWidth?: number;
207
208
  /**
208
209
  * Sets the maximum length of the line that holds a comment whose whole text is one prose
209
- * paragraph. Such a comment stays on one line up to this length, even past `width`, and is
210
- * wrapped to `width` beyond it. Defaults to 120 and never exceeds `maxWidth`.
210
+ * paragraph. Such a comment stays on one line up to this length plus `tolerance`, even past
211
+ * `width`, and is wrapped to `width` beyond it. Defaults to 120, and the line never exceeds
212
+ * `maxWidth`.
211
213
  */
212
214
  readonly singleLineWidth?: number;
213
215
  /**
@@ -220,8 +222,10 @@ interface CommentsConfig {
220
222
  * Keep the first word of a new sentence or parenthetical off the end of a line, e.g.
221
223
  * `...the module. This` or `...reflows (the` followed by the rest on the next line.
222
224
  * A paragraph of two lines also ends its first line with a sentence when the rest fits
223
- * on the second line and is no longer than the first, instead of with the start of the
224
- * next sentence, like `...the module. This one`. Defaults to `true`.
225
+ * on the second line and is no longer than the first, instead of with the start of
226
+ * the next sentence, like `...the module. This one`. Other lines avoid ending with the
227
+ * first two words of a sentence, like `...opens. It allocates`, unless that makes them
228
+ * much less even. Defaults to `true`.
225
229
  */
226
230
  readonly avoidSentenceOrphans?: boolean;
227
231
  /**
package/dist/comments.js CHANGED
@@ -199,6 +199,9 @@ function parentheticalGroups(tokens, limit) {
199
199
  }
200
200
  return groups;
201
201
  }
202
+ function parentheticalAround(tokens, at, limit) {
203
+ return parentheticalGroups(tokens, limit).find((group) => group.start < at && at < group.end);
204
+ }
202
205
  function bindParentheticals(tokens, limit) {
203
206
  const groups = new Map(parentheticalGroups(tokens, limit).map((group) => [group.start, group.end]));
204
207
  const units = [];
@@ -288,6 +291,8 @@ function hasWidow(lines) {
288
291
  }
289
292
  var FULLNESS_WEIGHT = 0.07;
290
293
  var PARENTHETICAL_SPLIT_WEIGHT = 2;
294
+ var SENTENCE_START_COST = 25;
295
+ var MAX_SENTENCE_START_WORDS = 2;
291
296
  var MAX_STEP = 15;
292
297
  var EXTRA_LINES = 2;
293
298
  function fewestLines(starts, target) {
@@ -375,7 +380,7 @@ function balanceTokens(tokens, firstLimit, restLimit, count, options, preferredL
375
380
  afterOrphan: tokens.map((_, index) => {
376
381
  return options.avoidSentenceOrphans && index >= 2 && isOrphan(tokens[index - 2], tokens[index - 1]);
377
382
  }),
378
- splitCosts
383
+ splitCosts: withSentenceStartCosts(tokens, splitCosts, options)
379
384
  };
380
385
  const targets = [];
381
386
  for (let target = restLimit; target >= Math.max(longest, Math.floor(restLimit / 2)); target -= 1) {
@@ -441,6 +446,22 @@ function splitCostsOf(tokens, limit) {
441
446
  }
442
447
  return splitCosts;
443
448
  }
449
+ function withSentenceStartCosts(tokens, splitCosts, options) {
450
+ if (!options.avoidSentenceOrphans || options.ignoreSentenceStarts) {
451
+ return splitCosts;
452
+ }
453
+ const costs = new Map(splitCosts);
454
+ for (const [index, token] of tokens.entries()) {
455
+ if (!endsSentence(token)) {
456
+ continue;
457
+ }
458
+ const last = Math.min(index + 1 + MAX_SENTENCE_START_WORDS, tokens.length - 1);
459
+ for (let end = index + 2; end <= last && !endsSentence(tokens[end - 1]); end += 1) {
460
+ costs.set(end, (costs.get(end) ?? 0) + SENTENCE_START_COST);
461
+ }
462
+ }
463
+ return costs;
464
+ }
444
465
  function hasParentheticals(tokens, restLimit, options) {
445
466
  if (options.parentheticalOverflow === void 0) {
446
467
  return false;
@@ -603,6 +624,7 @@ var ISSUE_TIERS = {
603
624
  widow: "verdict",
604
625
  unbalanced: "verdict",
605
626
  orphan: "hint",
627
+ "split-parenthetical": "hint",
606
628
  banner: "hint",
607
629
  "em-dash": "hint",
608
630
  semicolon: "hint",
@@ -1098,9 +1120,48 @@ function analyzeParagraph(paragraph, options) {
1098
1120
  if (hasWidow(currentLines) && !hasWidow(wrapped)) {
1099
1121
  lineIssues.push({ type: "widow", line: currentLines.length - 1 });
1100
1122
  }
1101
- if (lineIssues.length === 0 && overflow.length === 0 && (currentLines.length === wrapped.length || options.maxWidth !== void 0)) {
1102
- const line = wrapped.findIndex((words, position) => words.join(" ") !== currentLines[position]?.join(" "));
1103
- lineIssues.push({ type: sameLayout(even, currentLines) ? "orphan" : "unbalanced", line: Math.max(line, 0) });
1123
+ const keepsOnlyOrphans = () => {
1124
+ const free = settle({ ...layoutOptions, avoidSentenceOrphans: false });
1125
+ return !("wrapped" in free) || sameLayout(free.wrapped, currentLines);
1126
+ };
1127
+ const keepsOnlySentenceStarts = () => {
1128
+ const free = settle({ ...layoutOptions, ignoreSentenceStarts: true });
1129
+ return "wrapped" in free && (sameLayout(free.even, currentLines) || sameLayout(free.wrapped, currentLines));
1130
+ };
1131
+ const splitsOnlyParenthetical = (line) => {
1132
+ if (layoutOptions.parentheticalOverflow === void 0) {
1133
+ return false;
1134
+ }
1135
+ const tokenCount = (lines) => tokenize(lines.flat().join(" ")).length;
1136
+ const end = tokenCount(currentLines.slice(0, line + 1));
1137
+ const group = parentheticalAround(tokens, end, restLimit + layoutOptions.parentheticalOverflow);
1138
+ if (!group) {
1139
+ return false;
1140
+ }
1141
+ const keepsGroup = wrapped.every((_, position) => {
1142
+ const breakAt = tokenCount(wrapped.slice(0, position + 1));
1143
+ return breakAt <= group.start || breakAt >= group.end;
1144
+ });
1145
+ if (!keepsGroup) {
1146
+ return false;
1147
+ }
1148
+ const restOptions = { ...layoutOptions, keepFirstLine: false };
1149
+ const rest = bindLineStarts(tokens.slice(end), startsLine);
1150
+ const restLines = layoutTokens(rest, restLimit, restLimit, restOptions);
1151
+ return sameLayout(breakAfterSentence(restLines, restLimit, restLimit, restOptions), currentLines.slice(line + 1));
1152
+ };
1153
+ if (overflow.length === 0 && (currentLines.length === wrapped.length || options.maxWidth !== void 0)) {
1154
+ const line = Math.max(
1155
+ wrapped.findIndex((words, position) => words.join(" ") !== currentLines[position]?.join(" ")),
1156
+ 0
1157
+ );
1158
+ if (lineIssues.length === 0 && (sameLayout(even, currentLines) || keepsOnlySentenceStarts())) {
1159
+ lineIssues.push({ type: "orphan", line });
1160
+ } else if (lineIssues.length === 0) {
1161
+ lineIssues.push({ type: splitsOnlyParenthetical(line) ? "split-parenthetical" : "unbalanced", line });
1162
+ } else if (lineIssues.every((issue) => issue.type === "orphan") && !sameLayout(even, currentLines) && !keepsOnlyOrphans()) {
1163
+ lineIssues.push({ type: "unbalanced", line });
1164
+ }
1104
1165
  }
1105
1166
  }
1106
1167
  const suggestion = wrapped.map(
@@ -1233,7 +1294,9 @@ function inspectSource(source, filename, options) {
1233
1294
  options.width,
1234
1295
  Math.min(options.singleLineWidth ?? options.width, options.maxWidth ?? Infinity)
1235
1296
  );
1236
- const paragraphOptions = shape.paragraphs.length === 1 && singleLineLength(shape.paragraphs[0], options) <= singleLineWidth ? { ...options, width: singleLineWidth } : options;
1297
+ const singleLineLimit = Math.min(singleLineWidth + options.tolerance, options.maxWidth ?? Infinity);
1298
+ const singleLine = shape.paragraphs.length === 1 ? singleLineLength(shape.paragraphs[0], options) : Infinity;
1299
+ const paragraphOptions = singleLine <= singleLineLimit ? { ...options, width: Math.max(singleLineWidth, singleLine) } : options;
1237
1300
  const lines = sourceLines.slice(shape.startLine, shape.endLine + 1);
1238
1301
  reports.push({
1239
1302
  file: filename,
@@ -1296,7 +1359,7 @@ When rules pull in different directions, the earlier one wins.
1296
1359
  ## Layout
1297
1360
 
1298
1361
  - After you finish, the tool rewraps prose paragraphs, list items, and JSDoc tag descriptions. The width is a preferred target. A parenthetical may extend a line up to the configured maximum so it stays together, and lines may go up to the configured tolerance past the width when that saves a line. In longer paragraphs, balanced lines take priority over keeping every parenthetical on one line. The tool also avoids short last lines where possible. Leave these line breaks to the tool, except for the sentence-orphan case below.
1299
- - Don't end a line with the first word of a new sentence, clause, or parenthetical, like \`This\` after a period, \`the\` after a comma, or \`(the\`. Move it to the next line instead.
1362
+ - Don't end a line with the first word of a new sentence, clause, or parenthetical, like \`This\` after a period, \`the\` after a comma, or \`(the\`. Move it to the next line instead. The same goes for the first two words of a sentence, like \`It allocates\` after a period, when the note points at them. A long word, like an identifier or a code span, may stay at the end of a line when moving it would leave that line far shorter than the lines around it. Then leave the line breaks as they are.
1300
1363
  - In a paragraph of two lines, the first line ends with a sentence whenever the rest fits on the second line and is no longer than the first. Don't end it with the start of the next sentence, like \`A wrap\` in \`...lay out differently. A wrap\`. Move those words to the second line instead.
1301
1364
  - A comment whose whole text is one paragraph stays on a single line when that line fits a separate, larger single-line width, even past the width. Only a longer comment wraps to the width.
1302
1365
  - Separate paragraphs with an empty comment line.