mdrefine 0.2.0-rc.3 → 0.2.0

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/CHANGELOG.md CHANGED
@@ -8,6 +8,74 @@ release's section is also its [GitHub
8
8
  release](https://github.com/viell-dev/slop-markdown-tools/releases)'s notes; the
9
9
  prereleases' notes there are longer than their entries here.
10
10
 
11
+ ## 0.2.0 - 2026-10-18
12
+
13
+ The first stable release. It holds everything in the earlier prereleases, and
14
+ the changes since `0.2.0-rc.3` listed here. From this release on, npm's `latest`
15
+ tag names the newest stable release, and prereleases stay under `rc` and `beta`;
16
+ the [versioning
17
+ policy](https://viell-dev.github.io/slop-markdown-tools/versions.html) says what
18
+ a version number promises.
19
+
20
+ ### Upgrading from a prerelease
21
+
22
+ From `0.2.0-rc.3`:
23
+
24
+ - In the Obsidian dialect, `format` no longer breaks lines inside the text of a
25
+ link, and joins link text that is written across lines, also where
26
+ `0.2.0-rc.3` broke it. Notes that `0.2.0-rc.3` formatted change again: run
27
+ `format --write` once after upgrading.
28
+ - A document from stdin whose `--stdin-filepath` is ignored is no longer
29
+ processed: `lint -` reports nothing and `format -` prints it unchanged, with a
30
+ warning. In `mdrefine/node`, `discoverWorkspace` no longer selects `documents`
31
+ whose paths are ignored but lists them in `skipped`.
32
+
33
+ From an earlier prerelease, also read the upgrade notes of
34
+ [0.2.0-rc.3](https://github.com/viell-dev/slop-markdown-tools/releases/tag/0.2.0-rc.3).
35
+
36
+ ### Added
37
+
38
+ - `--no-warn-ignored` leaves out the warning for a file or directory named on
39
+ the command line that the tool skips as ignored, for hooks and scripts that
40
+ pass every changed file. The JSON report still lists it in `skipped`
41
+ ([#311](https://github.com/viell-dev/slop-markdown-tools/pull/311)).
42
+
43
+ ### Fixed
44
+
45
+ - `format --diff`, which `format` also does for files by default, takes about as
46
+ long as `format --check` when formatting changes lines throughout a long
47
+ document. Before, building the diff took time in proportion to the document's
48
+ length times its changed lines: two minutes for a generated document of 8,000
49
+ wrapped paragraphs that took four seconds to check
50
+ ([#315](https://github.com/viell-dev/slop-markdown-tools/pull/315)).
51
+ - A document from stdin whose `--stdin-filepath` the configuration, Git, or
52
+ discovery ignores is left out as a named file is: `lint -` reports nothing,
53
+ `format -` prints the document unchanged, and a warning says why. Before, it
54
+ was linted and formatted, so an editor's format-on-save rewrote a document
55
+ that the configuration says to leave alone. In the library, `documents` that
56
+ ignore rules leave out are no longer selected
57
+ ([#316](https://github.com/viell-dev/slop-markdown-tools/pull/316)).
58
+ - Stdin in a workspace root that Git ignores no longer warns that the root's
59
+ files are not processed
60
+ ([#316](https://github.com/viell-dev/slop-markdown-tools/pull/316)).
61
+ - In the Obsidian dialect, `style/wrap` keeps the text of a link on one line and
62
+ moves the link whole to the next line where it does not fit, and joins link
63
+ text written across lines. Obsidian's Live Preview shows a link whose text
64
+ spans lines as Markdown source. `0.2.0-rc.3` broke lines inside link text in
65
+ every dialect; elsewhere it still does, as GitHub, Forgejo, and Gitea render
66
+ such links
67
+ ([#312](https://github.com/viell-dev/slop-markdown-tools/pull/312)).
68
+ - Lists and block quotes below a first line of `---` or `+++` that no later line
69
+ closes are read as lists and block quotes, as renderers show them. Before, the
70
+ parser read them as paragraphs, and `style/wrap` joined them to the line
71
+ above, which GitHub then showed as text
72
+ ([#309](https://github.com/viell-dev/slop-markdown-tools/pull/309)).
73
+ - `style/thematic-break` and `style/heading` with `setext` no longer write a
74
+ line of `---` alone below such a first line of `---`, which made front matter
75
+ of everything between the two lines. The safety check refused every edit of
76
+ the rule in such a document
77
+ ([#309](https://github.com/viell-dev/slop-markdown-tools/pull/309)).
78
+
11
79
  ## 0.2.0-rc.3 - 2026-10-18
12
80
 
13
81
  ### Upgrading from a prerelease
package/CONTRIBUTING.md CHANGED
@@ -106,7 +106,8 @@ thematic breaks, and blank space in `blocks.test.ts`, problem rules in
106
106
  `references.test.ts`, `structure.test.ts`, and `content.test.ts`, wrapping in
107
107
  `wrap.test.ts` and `callout-wrap.test.ts`, links in `links.test.ts`, dollar math
108
108
  in `math.test.ts` against the renderers' output recorded in `math-renderers.ts`,
109
- and the engine, configuration, and plugins in `engine.test.ts`.
109
+ the engine, configuration, and plugins in `engine.test.ts`, and the diff that
110
+ `format` prints in `diff.test.ts`.
110
111
 
111
112
  `tests/integration/` runs the built CLI as a child process in temporary
112
113
  workspaces, and runs `examples/` and `benchmarks/` as they are, without the
package/README.md CHANGED
@@ -59,7 +59,7 @@ mdrefine needs Node.js 22.12 or newer. The package is `mdrefine`, and its
59
59
  command is `mdtools`:
60
60
 
61
61
  ```sh
62
- npm install --save-dev --save-exact mdrefine@0.2.0-rc.3
62
+ npm install --save-dev --save-exact mdrefine@0.2.0
63
63
  npx mdtools lint
64
64
  npx mdtools format --diff
65
65
  npx mdtools format --write
@@ -0,0 +1,10 @@
1
+ /**
2
+ * A unified diff that turns `before` into `after`, with the headers and hunks
3
+ * of `diff`'s `createTwoFilesPatch`. That function compares the whole texts,
4
+ * at a cost that grows with their length times the number of changed lines, so
5
+ * that rewrapping a long document took minutes to show. This one compares only
6
+ * the regions that changed and their neighbours. Where it removes and adds
7
+ * other lines than a comparison of the whole texts would, both diffs turn
8
+ * `before` into `after`.
9
+ */
10
+ export declare function unifiedDiff(oldFileName: string, newFileName: string, before: string, after: string): string;
@@ -0,0 +1,247 @@
1
+ import { diffArrays, formatPatch } from "diff";
2
+ /** Lines of context around each change, as `diff`'s `createTwoFilesPatch` shows them. */
3
+ const context = 4;
4
+ /**
5
+ * The most lines, of both texts together, that one run of the line diff
6
+ * compares. Its cost grows with the lines it compares times the lines that
7
+ * changed, so this limit keeps the cost of the whole diff in proportion to the
8
+ * length of the texts.
9
+ */
10
+ const limit = 100;
11
+ /** The lines of `text`, each with its line break, split where `diff` splits them. */
12
+ function lines(text) {
13
+ const result = [];
14
+ for (let start = 0; start < text.length;) {
15
+ const end = text.indexOf("\n", start) + 1 || text.length;
16
+ result.push(text.slice(start, end));
17
+ start = end;
18
+ }
19
+ return result;
20
+ }
21
+ /**
22
+ * The longest chain of `pairs`, which are in increasing order of their first
23
+ * item, whose second items increase too.
24
+ */
25
+ function chain(pairs) {
26
+ // The pair that ends the longest chain of each length so far, and each pair's predecessor.
27
+ const ends = [];
28
+ const previous = [];
29
+ pairs.forEach(([, place], index) => {
30
+ let low = 0;
31
+ for (let high = ends.length; low < high;) {
32
+ const middle = (low + high) >> 1;
33
+ if (pairs[ends[middle]][1] < place)
34
+ low = middle + 1;
35
+ else
36
+ high = middle;
37
+ }
38
+ previous[index] = low > 0 ? ends[low - 1] : -1;
39
+ ends[low] = index;
40
+ });
41
+ const result = [];
42
+ for (let index = ends.at(-1) ?? -1; index >= 0; index = previous[index])
43
+ result.push(pairs[index]);
44
+ return result.reverse();
45
+ }
46
+ /**
47
+ * The regions where `after` differs from `before`, in order, each followed by
48
+ * a line that both texts share, unless it reaches their ends.
49
+ *
50
+ * Lines that occur once in each text, in the same order in both, are anchors
51
+ * that the texts keep, as in patience diff, so that a region never reaches past
52
+ * an unchanged paragraph or heading. Between anchors, at a difference, the
53
+ * texts agree again at the nearest pair of equal lines: the one that skips the
54
+ * fewest lines of both texts together. An index of where each line occurs in
55
+ * `after` finds that pair in time proportional to the lines it skips, so the
56
+ * scan takes time in proportion to the length of the texts, however much
57
+ * changed. No line of a region equals a line of the other text's part of it,
58
+ * since that pair would have been nearer.
59
+ */
60
+ function regions(before, after) {
61
+ const occurrences = new Map();
62
+ after.forEach((line, index) => {
63
+ const entry = occurrences.get(line);
64
+ if (entry)
65
+ entry.places.push(index);
66
+ else
67
+ occurrences.set(line, { places: [index], passed: 0, old: 0 });
68
+ });
69
+ for (const line of before) {
70
+ const entry = occurrences.get(line);
71
+ if (entry)
72
+ entry.old++;
73
+ }
74
+ const anchors = chain(before.flatMap((line, index) => {
75
+ const entry = occurrences.get(line);
76
+ return entry?.old === 1 && entry.places.length === 1 ? [[index, entry.places[0]]] : [];
77
+ }));
78
+ /** The first place of `line` in `after` at or after `from`, which never decreases. */
79
+ const next = (line, from) => {
80
+ const entry = occurrences.get(line);
81
+ if (!entry)
82
+ return undefined;
83
+ while ((entry.places[entry.passed] ?? Infinity) < from)
84
+ entry.passed++;
85
+ return entry.places[entry.passed];
86
+ };
87
+ const found = [];
88
+ let anchor = 0;
89
+ for (let oldLine = 0, newLine = 0; oldLine < before.length || newLine < after.length;) {
90
+ if (before[oldLine] === after[newLine]) {
91
+ oldLine++;
92
+ newLine++;
93
+ continue;
94
+ }
95
+ while ((anchors[anchor]?.[0] ?? Infinity) < oldLine)
96
+ anchor++;
97
+ // The region ends at the next anchor, or at the ends of the texts, unless a pair is nearer.
98
+ const [oldLimit, newLimit] = anchors[anchor] ?? [before.length, after.length];
99
+ let oldEnd = oldLimit;
100
+ let newEnd = newLimit;
101
+ let skipped = oldEnd - oldLine + newEnd - newLine;
102
+ for (let line = oldLine; line < oldLimit && line - oldLine < skipped; line++) {
103
+ const place = next(before[line], newLine);
104
+ if (place !== undefined && place < newLimit && line - oldLine + place - newLine < skipped) {
105
+ skipped = line - oldLine + place - newLine;
106
+ oldEnd = line;
107
+ newEnd = place;
108
+ }
109
+ }
110
+ found.push({ oldStart: oldLine, oldEnd, newStart: newLine, newEnd });
111
+ oldLine = oldEnd;
112
+ newLine = newEnd;
113
+ }
114
+ return found;
115
+ }
116
+ /**
117
+ * The runs of lines that turn `before` into `after`. The line diff, which
118
+ * finds the fewest lines to remove and add, compares each group of nearby
119
+ * regions that fits within the limit, so that a pair of equal lines that only
120
+ * happens to be the nearest, such as two blank lines, does not decide how a
121
+ * change is shown. A group's lines run on to the next group, or to the ends,
122
+ * so that texts with one group get the diff of the whole texts. A region
123
+ * larger than the limit has no line in common with the other text's part of
124
+ * it, so the line diff would remove and add it whole, as this does.
125
+ */
126
+ function runs(before, after) {
127
+ const result = [];
128
+ const add = (kind, count) => {
129
+ const last = result.at(-1);
130
+ if (count === 0)
131
+ return;
132
+ if (last?.kind === kind)
133
+ last.count += count;
134
+ else
135
+ result.push({ kind, count });
136
+ };
137
+ const found = regions(before, after);
138
+ add(" ", found[0]?.oldStart ?? before.length);
139
+ for (let index = 0; index < found.length;) {
140
+ const first = found[index];
141
+ const size = (region) => region.oldEnd - first.oldStart + region.newEnd - first.newStart;
142
+ let end = index + 1;
143
+ while (end < found.length && size(found[end]) <= limit)
144
+ end++;
145
+ const last = found[end - 1];
146
+ const oldEnd = found[end]?.oldStart ?? before.length;
147
+ const newEnd = found[end]?.newStart ?? after.length;
148
+ if (size(last) > limit) {
149
+ add("-", last.oldEnd - first.oldStart);
150
+ add("+", last.newEnd - first.newStart);
151
+ add(" ", oldEnd - last.oldEnd);
152
+ }
153
+ else
154
+ for (const change of diffArrays(before.slice(first.oldStart, oldEnd), after.slice(first.newStart, newEnd)))
155
+ add(change.added ? "+" : change.removed ? "-" : " ", change.count);
156
+ index = end;
157
+ }
158
+ return result;
159
+ }
160
+ /**
161
+ * The hunks of `changes`, as `diff`'s `structuredPatch` makes them: up to
162
+ * `context` kept lines on each side of a change, and one hunk for changes
163
+ * whose contexts touch.
164
+ */
165
+ function hunks(before, after, changes) {
166
+ const result = [];
167
+ let hunk;
168
+ /** Adds a line to the hunk as a patch shows it, and marks a last line without a line break. */
169
+ const show = (prefix, line) => {
170
+ if (line.endsWith("\n"))
171
+ hunk.lines.push(prefix + line.slice(0, -1));
172
+ else
173
+ hunk.lines.push(prefix + line, "\");
174
+ if (prefix !== "+")
175
+ hunk.oldLines++;
176
+ if (prefix !== "-")
177
+ hunk.newLines++;
178
+ };
179
+ let oldLine = 0;
180
+ let newLine = 0;
181
+ changes.forEach((run, index) => {
182
+ if (run.kind === " ") {
183
+ if (hunk) {
184
+ // When another change follows within twice the context, the hunk takes it in.
185
+ const shown = run.count <= 2 * context && index < changes.length - 1
186
+ ? run.count
187
+ : Math.min(run.count, context);
188
+ for (const line of before.slice(oldLine, oldLine + shown))
189
+ show(" ", line);
190
+ if (shown < run.count || index === changes.length - 1) {
191
+ result.push(hunk);
192
+ hunk = undefined;
193
+ }
194
+ }
195
+ oldLine += run.count;
196
+ newLine += run.count;
197
+ return;
198
+ }
199
+ if (!hunk) {
200
+ // The kept lines before the change, which the previous run holds.
201
+ const leading = Math.min(changes[index - 1]?.count ?? 0, context);
202
+ hunk = {
203
+ oldStart: oldLine - leading + 1,
204
+ oldLines: 0,
205
+ newStart: newLine - leading + 1,
206
+ newLines: 0,
207
+ lines: [],
208
+ };
209
+ for (const line of before.slice(oldLine - leading, oldLine))
210
+ show(" ", line);
211
+ }
212
+ if (run.kind === "-") {
213
+ for (const line of before.slice(oldLine, oldLine + run.count))
214
+ show("-", line);
215
+ oldLine += run.count;
216
+ }
217
+ else {
218
+ for (const line of after.slice(newLine, newLine + run.count))
219
+ show("+", line);
220
+ newLine += run.count;
221
+ }
222
+ });
223
+ if (hunk)
224
+ result.push(hunk);
225
+ return result;
226
+ }
227
+ /**
228
+ * A unified diff that turns `before` into `after`, with the headers and hunks
229
+ * of `diff`'s `createTwoFilesPatch`. That function compares the whole texts,
230
+ * at a cost that grows with their length times the number of changed lines, so
231
+ * that rewrapping a long document took minutes to show. This one compares only
232
+ * the regions that changed and their neighbours. Where it removes and adds
233
+ * other lines than a comparison of the whole texts would, both diffs turn
234
+ * `before` into `after`.
235
+ */
236
+ export function unifiedDiff(oldFileName, newFileName, before, after) {
237
+ const oldLines = lines(before);
238
+ const newLines = lines(after);
239
+ return formatPatch({
240
+ oldFileName,
241
+ newFileName,
242
+ oldHeader: undefined,
243
+ newHeader: undefined,
244
+ hunks: hunks(oldLines, newLines, runs(oldLines, newLines)),
245
+ });
246
+ }
247
+ //# sourceMappingURL=diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/cli/diff.ts"],"names":[],"mappings":"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","sourcesContent":["import { diffArrays, formatPatch, type StructuredPatchHunk } from \"diff\";\n\n/** Lines of context around each change, as `diff`'s `createTwoFilesPatch` shows them. */\nconst context = 4;\n/**\n * The most lines, of both texts together, that one run of the line diff\n * compares. Its cost grows with the lines it compares times the lines that\n * changed, so this limit keeps the cost of the whole diff in proportion to the\n * length of the texts.\n */\nconst limit = 100;\n\n/** The lines of `text`, each with its line break, split where `diff` splits them. */\nfunction lines(text: string): string[] {\n const result: string[] = [];\n for (let start = 0; start < text.length;) {\n const end = text.indexOf(\"\\n\", start) + 1 || text.length;\n result.push(text.slice(start, end));\n start = end;\n }\n return result;\n}\n\n/** Lines `oldStart` to `oldEnd` of the old text, which became `newStart` to `newEnd` of the new. */\ninterface Region {\n oldStart: number;\n oldEnd: number;\n newStart: number;\n newEnd: number;\n}\n\n/**\n * Where a line occurs: its places in the new text, how many of them the scan\n * has passed, and how often it occurs in the old text.\n */\ninterface Occurrences {\n places: number[];\n passed: number;\n old: number;\n}\n\n/**\n * The longest chain of `pairs`, which are in increasing order of their first\n * item, whose second items increase too.\n */\nfunction chain(pairs: [number, number][]): [number, number][] {\n // The pair that ends the longest chain of each length so far, and each pair's predecessor.\n const ends: number[] = [];\n const previous: number[] = [];\n pairs.forEach(([, place], index) => {\n let low = 0;\n for (let high = ends.length; low < high;) {\n const middle = (low + high) >> 1;\n if (pairs[ends[middle]!]![1] < place) low = middle + 1;\n else high = middle;\n }\n previous[index] = low > 0 ? ends[low - 1]! : -1;\n ends[low] = index;\n });\n const result: [number, number][] = [];\n for (let index = ends.at(-1) ?? -1; index >= 0; index = previous[index]!)\n result.push(pairs[index]!);\n return result.reverse();\n}\n\n/**\n * The regions where `after` differs from `before`, in order, each followed by\n * a line that both texts share, unless it reaches their ends.\n *\n * Lines that occur once in each text, in the same order in both, are anchors\n * that the texts keep, as in patience diff, so that a region never reaches past\n * an unchanged paragraph or heading. Between anchors, at a difference, the\n * texts agree again at the nearest pair of equal lines: the one that skips the\n * fewest lines of both texts together. An index of where each line occurs in\n * `after` finds that pair in time proportional to the lines it skips, so the\n * scan takes time in proportion to the length of the texts, however much\n * changed. No line of a region equals a line of the other text's part of it,\n * since that pair would have been nearer.\n */\nfunction regions(before: string[], after: string[]): Region[] {\n const occurrences = new Map<string, Occurrences>();\n after.forEach((line, index) => {\n const entry = occurrences.get(line);\n if (entry) entry.places.push(index);\n else occurrences.set(line, { places: [index], passed: 0, old: 0 });\n });\n for (const line of before) {\n const entry = occurrences.get(line);\n if (entry) entry.old++;\n }\n const anchors = chain(\n before.flatMap((line, index): [number, number][] => {\n const entry = occurrences.get(line);\n return entry?.old === 1 && entry.places.length === 1 ? [[index, entry.places[0]!]] : [];\n }),\n );\n /** The first place of `line` in `after` at or after `from`, which never decreases. */\n const next = (line: string, from: number) => {\n const entry = occurrences.get(line);\n if (!entry) return undefined;\n while ((entry.places[entry.passed] ?? Infinity) < from) entry.passed++;\n return entry.places[entry.passed];\n };\n const found: Region[] = [];\n let anchor = 0;\n for (let oldLine = 0, newLine = 0; oldLine < before.length || newLine < after.length;) {\n if (before[oldLine] === after[newLine]) {\n oldLine++;\n newLine++;\n continue;\n }\n while ((anchors[anchor]?.[0] ?? Infinity) < oldLine) anchor++;\n // The region ends at the next anchor, or at the ends of the texts, unless a pair is nearer.\n const [oldLimit, newLimit] = anchors[anchor] ?? [before.length, after.length];\n let oldEnd = oldLimit;\n let newEnd = newLimit;\n let skipped = oldEnd - oldLine + newEnd - newLine;\n for (let line = oldLine; line < oldLimit && line - oldLine < skipped; line++) {\n const place = next(before[line]!, newLine);\n if (place !== undefined && place < newLimit && line - oldLine + place - newLine < skipped) {\n skipped = line - oldLine + place - newLine;\n oldEnd = line;\n newEnd = place;\n }\n }\n found.push({ oldStart: oldLine, oldEnd, newStart: newLine, newEnd });\n oldLine = oldEnd;\n newLine = newEnd;\n }\n return found;\n}\n\n/** Lines that both texts keep (\" \"), that the old text loses (\"-\"), or that the new gains (\"+\"). */\ninterface Run {\n kind: \" \" | \"-\" | \"+\";\n count: number;\n}\n\n/**\n * The runs of lines that turn `before` into `after`. The line diff, which\n * finds the fewest lines to remove and add, compares each group of nearby\n * regions that fits within the limit, so that a pair of equal lines that only\n * happens to be the nearest, such as two blank lines, does not decide how a\n * change is shown. A group's lines run on to the next group, or to the ends,\n * so that texts with one group get the diff of the whole texts. A region\n * larger than the limit has no line in common with the other text's part of\n * it, so the line diff would remove and add it whole, as this does.\n */\nfunction runs(before: string[], after: string[]): Run[] {\n const result: Run[] = [];\n const add = (kind: Run[\"kind\"], count: number) => {\n const last = result.at(-1);\n if (count === 0) return;\n if (last?.kind === kind) last.count += count;\n else result.push({ kind, count });\n };\n const found = regions(before, after);\n add(\" \", found[0]?.oldStart ?? before.length);\n for (let index = 0; index < found.length;) {\n const first = found[index]!;\n const size = (region: Region) =>\n region.oldEnd - first.oldStart + region.newEnd - first.newStart;\n let end = index + 1;\n while (end < found.length && size(found[end]!) <= limit) end++;\n const last = found[end - 1]!;\n const oldEnd = found[end]?.oldStart ?? before.length;\n const newEnd = found[end]?.newStart ?? after.length;\n if (size(last) > limit) {\n add(\"-\", last.oldEnd - first.oldStart);\n add(\"+\", last.newEnd - first.newStart);\n add(\" \", oldEnd - last.oldEnd);\n } else\n for (const change of diffArrays(\n before.slice(first.oldStart, oldEnd),\n after.slice(first.newStart, newEnd),\n ))\n add(change.added ? \"+\" : change.removed ? \"-\" : \" \", change.count);\n index = end;\n }\n return result;\n}\n\n/**\n * The hunks of `changes`, as `diff`'s `structuredPatch` makes them: up to\n * `context` kept lines on each side of a change, and one hunk for changes\n * whose contexts touch.\n */\nfunction hunks(before: string[], after: string[], changes: Run[]): StructuredPatchHunk[] {\n const result: StructuredPatchHunk[] = [];\n let hunk: StructuredPatchHunk | undefined;\n /** Adds a line to the hunk as a patch shows it, and marks a last line without a line break. */\n const show = (prefix: Run[\"kind\"], line: string) => {\n if (line.endsWith(\"\\n\")) hunk!.lines.push(prefix + line.slice(0, -1));\n else hunk!.lines.push(prefix + line, \"\\\\");\n if (prefix !== \"+\") hunk!.oldLines++;\n if (prefix !== \"-\") hunk!.newLines++;\n };\n let oldLine = 0;\n let newLine = 0;\n changes.forEach((run, index) => {\n if (run.kind === \" \") {\n if (hunk) {\n // When another change follows within twice the context, the hunk takes it in.\n const shown =\n run.count <= 2 * context && index < changes.length - 1\n ? run.count\n : Math.min(run.count, context);\n for (const line of before.slice(oldLine, oldLine + shown)) show(\" \", line);\n if (shown < run.count || index === changes.length - 1) {\n result.push(hunk);\n hunk = undefined;\n }\n }\n oldLine += run.count;\n newLine += run.count;\n return;\n }\n if (!hunk) {\n // The kept lines before the change, which the previous run holds.\n const leading = Math.min(changes[index - 1]?.count ?? 0, context);\n hunk = {\n oldStart: oldLine - leading + 1,\n oldLines: 0,\n newStart: newLine - leading + 1,\n newLines: 0,\n lines: [],\n };\n for (const line of before.slice(oldLine - leading, oldLine)) show(\" \", line);\n }\n if (run.kind === \"-\") {\n for (const line of before.slice(oldLine, oldLine + run.count)) show(\"-\", line);\n oldLine += run.count;\n } else {\n for (const line of after.slice(newLine, newLine + run.count)) show(\"+\", line);\n newLine += run.count;\n }\n });\n if (hunk) result.push(hunk);\n return result;\n}\n\n/**\n * A unified diff that turns `before` into `after`, with the headers and hunks\n * of `diff`'s `createTwoFilesPatch`. That function compares the whole texts,\n * at a cost that grows with their length times the number of changed lines, so\n * that rewrapping a long document took minutes to show. This one compares only\n * the regions that changed and their neighbours. Where it removes and adds\n * other lines than a comparison of the whole texts would, both diffs turn\n * `before` into `after`.\n */\nexport function unifiedDiff(\n oldFileName: string,\n newFileName: string,\n before: string,\n after: string,\n): string {\n const oldLines = lines(before);\n const newLines = lines(after);\n return formatPatch({\n oldFileName,\n newFileName,\n oldHeader: undefined,\n newHeader: undefined,\n hunks: hunks(oldLines, newLines, runs(oldLines, newLines)),\n });\n}\n"]}
package/dist/cli/main.js CHANGED
@@ -1,17 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command, CommanderError, InvalidArgumentError, Option } from "commander";
3
- import { createTwoFilesPatch } from "diff";
4
3
  import { readFile, realpath } from "node:fs/promises";
5
4
  import path from "node:path";
6
5
  import { fileURLToPath } from "node:url";
7
6
  import { format, lint, ruleRegistry } from "../core/engine.js";
8
7
  import { loadConfig } from "../config/load.js";
9
8
  import { dialectAliases, dialects, resolveConfig } from "../config/resolve.js";
10
- import { replacementFailure, replacementProblem, skippedMessage, writeFormatted, } from "../workspace/files.js";
9
+ import { isIgnoredInput, replacementFailure, replacementProblem, skippedMessage, writeFormatted, } from "../workspace/files.js";
11
10
  import { assumedDialect, checkRoot, discoverFrom } from "../workspace/discovery.js";
12
11
  import { enclosingVault } from "../workspace/vault.js";
13
12
  import { refusal, refusalText } from "../workspace/access.js";
14
13
  import { decodeText, NotUtf8Error } from "../workspace/encoding.js";
14
+ import { unifiedDiff } from "./diff.js";
15
15
  const manifest = JSON.parse(await readFile(fileURLToPath(new URL("../../package.json", import.meta.url)), "utf8"));
16
16
  // Subcommands inherit these settings, so they come before the commands.
17
17
  const program = new Command()
@@ -53,6 +53,7 @@ function common(command) {
53
53
  .option("--exclude <path>", "Exclude an exact file or directory (repeatable)", (value, previous = []) => [...previous, value])
54
54
  .option("--json", "Machine-readable output")
55
55
  .option("--quiet", "Report errors only: leave warnings out of the report and its counts")
56
+ .option("--no-warn-ignored", "Do not warn about named files and directories that are skipped as ignored")
56
57
  .option("--stdin-filepath <file>", "Filename context for stdin")
57
58
  .option("--max-warnings <count>", "Fail when warning count exceeds this nonnegative integer", (value) => {
58
59
  const number = Number(value);
@@ -148,7 +149,9 @@ async function run(mode, inputs, flags) {
148
149
  }
149
150
  }
150
151
  // Paths on the command line are relative to the working directory, and no
151
- // path selects the whole workspace; stdin is the only document then.
152
+ // path selects the whole workspace; stdin is the only document then. Ignore
153
+ // rules apply to the path that `--stdin-filepath` names, as to a named file,
154
+ // and not to the default name.
152
155
  const discovery = await discoverFrom({
153
156
  root,
154
157
  ...(stdin ? { inputs: [] } : inputs.length ? { inputs } : {}),
@@ -156,7 +159,7 @@ async function run(mode, inputs, flags) {
156
159
  config: loaded.config,
157
160
  plugins: loaded.plugins,
158
161
  ...(documents ? { documents } : {}),
159
- }, process.cwd());
162
+ }, process.cwd(), flags.stdinFilepath !== undefined);
160
163
  const reports = [];
161
164
  const changes = [];
162
165
  // Selected files that the system refused to let the tool read, or that are not UTF-8 text.
@@ -274,13 +277,17 @@ async function run(mode, inputs, flags) {
274
277
  const here = await realpath(process.cwd()).catch(() => process.cwd());
275
278
  const shown = (name) => path.relative(here, path.join(discovery.root, name)).split(path.sep).join("/");
276
279
  // Warnings about the run, not about a document: they do not change the exit status.
280
+ // A caller that passes every changed file can leave out those about ignored ones.
277
281
  for (const item of discovery.skipped)
278
- process.stderr.write(`mdtools: warning: ${skippedMessage(item, shown)}\n`);
282
+ if (flags.warnIgnored !== false || !isIgnoredInput(item))
283
+ process.stderr.write(`mdtools: warning: ${skippedMessage(item, shown)}\n`);
284
+ // An ignored document from stdin comes back unchanged, as in Prettier, so
285
+ // that an editor that replaces its text with the output loses nothing.
279
286
  if (stdin && mode === "format" && !flags.check && !flags.diff)
280
- process.stdout.write(reports[0]?.output ?? "");
287
+ process.stdout.write(reports[0]?.output ?? Object.values(documents ?? {})[0] ?? "");
281
288
  else if (mode === "format" && !flags.check && !flags.write)
282
289
  for (const change of changes)
283
- process.stdout.write(createTwoFilesPatch(`a/${shown(change.file)}`, `b/${shown(change.file)}`, change.before, change.after));
290
+ process.stdout.write(unifiedDiff(`a/${shown(change.file)}`, `b/${shown(change.file)}`, change.before, change.after));
284
291
  for (const report of reports)
285
292
  for (const item of report.diagnostics)
286
293
  process.stderr.write(`${shown(report.path)}:${item.line}:${item.column}: ${item.severity} ${item.rule}: ${item.message}\n`);