mdrefine 0.2.0-rc.3 → 0.2.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/CHANGELOG.md +84 -0
- package/CONTRIBUTING.md +2 -1
- package/README.md +1 -1
- package/dist/cli/diff.d.ts +10 -0
- package/dist/cli/diff.js +247 -0
- package/dist/cli/diff.js.map +1 -0
- package/dist/cli/main.js +14 -7
- package/dist/cli/main.js.map +1 -1
- package/dist/rules/blocks.js +17 -5
- package/dist/rules/blocks.js.map +1 -1
- package/dist/rules/style.js +93 -21
- package/dist/rules/style.js.map +1 -1
- package/dist/syntax/parse.d.ts +14 -1
- package/dist/syntax/parse.js +25 -3
- package/dist/syntax/parse.js.map +1 -1
- package/dist/workspace/discovery.d.ts +8 -3
- package/dist/workspace/discovery.js +21 -9
- package/dist/workspace/discovery.js.map +1 -1
- package/dist/workspace/files.d.ts +17 -1
- package/dist/workspace/files.js +82 -6
- package/dist/workspace/files.js.map +1 -1
- package/docs/api.md +3 -1
- package/docs/benchmarks.md +41 -41
- package/docs/cli.md +17 -7
- package/docs/design.md +5 -0
- package/docs/dialects.md +2 -1
- package/docs/integrations.md +8 -2
- package/docs/quick-start.md +1 -1
- package/docs/rules.md +18 -4
- package/package.json +2 -2
package/dist/rules/style.js
CHANGED
|
@@ -128,11 +128,13 @@ function markersLoosenRuns() {
|
|
|
128
128
|
/**
|
|
129
129
|
* The delimiter runs at `positions`, sorted, with the characters that `at`
|
|
130
130
|
* gives. Whether a run can open or close follows micromark, which also lets a
|
|
131
|
-
* run next to a `~` in GFM do both
|
|
132
|
-
*
|
|
133
|
-
*
|
|
131
|
+
* run next to a `~` in GFM do both, and in some versions one next to another
|
|
132
|
+
* `*` or `_`; with `commonmark`, it follows CommonMark, where those characters
|
|
133
|
+
* are punctuation. Container markers such as `>` count as punctuation here,
|
|
134
|
+
* while micromark sees the line ending before them; the caller compares the
|
|
135
|
+
* result with the parse.
|
|
134
136
|
*/
|
|
135
|
-
function delimiterRuns(positions, at, gfm) {
|
|
137
|
+
function delimiterRuns(positions, at, gfm, commonmark = false) {
|
|
136
138
|
const runs = [];
|
|
137
139
|
for (const position of positions) {
|
|
138
140
|
const last = runs.at(-1);
|
|
@@ -147,8 +149,9 @@ function delimiterRuns(positions, at, gfm) {
|
|
|
147
149
|
close: false,
|
|
148
150
|
});
|
|
149
151
|
}
|
|
150
|
-
const loosen = markersLoosenRuns();
|
|
151
|
-
const attention = (character) => (gfm && character === "~") ||
|
|
152
|
+
const loosen = !commonmark && markersLoosenRuns();
|
|
153
|
+
const attention = (character) => (!commonmark && gfm && character === "~") ||
|
|
154
|
+
(loosen && (character === "*" || character === "_"));
|
|
152
155
|
for (const run of runs) {
|
|
153
156
|
const previous = at(run.start - 1);
|
|
154
157
|
const next = at(run.end);
|
|
@@ -165,21 +168,23 @@ function delimiterRuns(positions, at, gfm) {
|
|
|
165
168
|
* The emphasis and strong emphasis that micromark makes of `runs`, as
|
|
166
169
|
* `start:end:size` strings: each closing run, in order, pairs with the nearest
|
|
167
170
|
* opening run of its marker before it, except where the rule of three forbids
|
|
168
|
-
* it, and the runs between them become text.
|
|
171
|
+
* it, and the runs between them become text. micromark applies the rule to the
|
|
172
|
+
* parts of two runs that are left; with `whole`, it counts the whole runs, as
|
|
173
|
+
* CommonMark, GitHub, and the reference parsers do (issue #323).
|
|
169
174
|
*/
|
|
170
|
-
function pairings(runs) {
|
|
171
|
-
const left = runs.map((run) => ({ ...run }));
|
|
175
|
+
function pairings(runs, whole = false) {
|
|
176
|
+
const left = runs.map((run) => ({ ...run, size: run.end - run.start }));
|
|
172
177
|
const pairs = [];
|
|
173
178
|
for (let index = 0; index < left.length; index++) {
|
|
174
179
|
const closer = left[index];
|
|
175
180
|
while (closer.close && closer.end > closer.start) {
|
|
176
|
-
const closeSize = closer.end - closer.start;
|
|
181
|
+
const closeSize = whole ? closer.size : closer.end - closer.start;
|
|
177
182
|
let found = index - 1;
|
|
178
183
|
for (; found >= 0; found--) {
|
|
179
184
|
const opener = left[found];
|
|
180
|
-
|
|
181
|
-
if (!opener.open || !openSize || opener.marker !== closer.marker)
|
|
185
|
+
if (!opener.open || opener.end === opener.start || opener.marker !== closer.marker)
|
|
182
186
|
continue;
|
|
187
|
+
const openSize = whole ? opener.size : opener.end - opener.start;
|
|
183
188
|
if ((opener.close || closer.open) && closeSize % 3 && !((openSize + closeSize) % 3))
|
|
184
189
|
continue;
|
|
185
190
|
break;
|
|
@@ -187,7 +192,7 @@ function pairings(runs) {
|
|
|
187
192
|
if (found < 0)
|
|
188
193
|
break;
|
|
189
194
|
const opener = left[found];
|
|
190
|
-
const use = opener.end - opener.start > 1 &&
|
|
195
|
+
const use = opener.end - opener.start > 1 && closer.end - closer.start > 1 ? 2 : 1;
|
|
191
196
|
opener.end -= use;
|
|
192
197
|
closer.start += use;
|
|
193
198
|
pairs.push(`${opener.end}:${closer.start}:${use}`);
|
|
@@ -298,6 +303,25 @@ function eachInline(tree, type, visitor) {
|
|
|
298
303
|
};
|
|
299
304
|
step(tree, tree);
|
|
300
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* Whether the text in `node`, outside code and other literal nodes, holds an
|
|
308
|
+
* unescaped run of tildes that could mark strikethrough in `dialect`, one or
|
|
309
|
+
* two tildes or in Obsidian two, while `scope`, where such runs pair, holds
|
|
310
|
+
* another.
|
|
311
|
+
*/
|
|
312
|
+
function holdsStrikethroughRun(node, scope, source, dialect) {
|
|
313
|
+
const pattern = dialect === "obsidian" ? "(?<![\\\\~])~~(?!~)" : "(?<![\\\\~])~{1,2}(?!~)";
|
|
314
|
+
const runs = located(scope) ? source.slice(...range(scope)).match(new RegExp(pattern, "g")) : [];
|
|
315
|
+
if (located(scope) && (runs?.length ?? 0) < 2)
|
|
316
|
+
return false;
|
|
317
|
+
const run = new RegExp(pattern);
|
|
318
|
+
let found = false;
|
|
319
|
+
walk(node, "text", (text) => {
|
|
320
|
+
if (!found && located(text) && run.test(source.slice(...range(text))))
|
|
321
|
+
found = true;
|
|
322
|
+
});
|
|
323
|
+
return found;
|
|
324
|
+
}
|
|
301
325
|
function markerRule(type, fallback) {
|
|
302
326
|
return {
|
|
303
327
|
description: `Choose ${type} delimiters without reprinting their contents.`,
|
|
@@ -378,6 +402,27 @@ function markerRule(type, fallback) {
|
|
|
378
402
|
reaches(start + size, 1) ||
|
|
379
403
|
reaches(end - size - 1, -1)))
|
|
380
404
|
return;
|
|
405
|
+
// micromark pairs emphasis and strikethrough in separate passes, in the
|
|
406
|
+
// order that their first delimiters appear in the paragraph, while
|
|
407
|
+
// GitHub pairs whichever closes first. So emphasis whose text holds a
|
|
408
|
+
// run of tildes that could mark strikethrough may be strikethrough and
|
|
409
|
+
// text there, and its markers what GitHub shows (issue #320).
|
|
410
|
+
if (gfm && holdsStrikethroughRun(node, scope, source, document.dialect))
|
|
411
|
+
return;
|
|
412
|
+
// GitHub reads a `_` that a run of tildes separates from a letter or
|
|
413
|
+
// digit as inside a word, where it neither opens nor closes, while the
|
|
414
|
+
// parser reads emphasis there (issue #322).
|
|
415
|
+
const tildesToWord = (index, step) => {
|
|
416
|
+
if (source[index] !== "~")
|
|
417
|
+
return false;
|
|
418
|
+
while (source[index] === "~")
|
|
419
|
+
index += step;
|
|
420
|
+
return /[\p{L}\p{N}]/u.test(source[index] ?? "");
|
|
421
|
+
};
|
|
422
|
+
if (gfm &&
|
|
423
|
+
(marker[0] === "_" || original[0] === "_") &&
|
|
424
|
+
(tildesToWord(start - 1, -1) || tildesToWord(end, 1)))
|
|
425
|
+
return;
|
|
381
426
|
const list = candidates.get(scope) ?? [];
|
|
382
427
|
list.push([start, end]);
|
|
383
428
|
candidates.set(scope, list);
|
|
@@ -401,7 +446,11 @@ function markerRule(type, fallback) {
|
|
|
401
446
|
// share a run of markers, as in `***a***` or `*a****c***`.
|
|
402
447
|
const { positions, emphasis, suspicious } = scopeDelimiters(source, scope);
|
|
403
448
|
const expected = emphasis.join(" ");
|
|
404
|
-
|
|
449
|
+
// micromark's pairing, or with `commonmark` CommonMark's.
|
|
450
|
+
const pairing = (changed, commonmark = false) => pairings(delimiterRuns(positions, (at) => (changed.has(at) ? marker[0] : source[at]), gfm, commonmark), commonmark).join(" ");
|
|
451
|
+
// Both micromark's pairing and CommonMark's, which renderers follow,
|
|
452
|
+
// have to stay the same with the new markers.
|
|
453
|
+
const kept = (changed) => pairing(changed) === expected && pairing(changed, true) === expected;
|
|
405
454
|
// micromark pairs `~` in a pass of its own, which can come first, and
|
|
406
455
|
// lets a run next to a `~` pair with one of the new marker, as in `_f~*a*e_`.
|
|
407
456
|
const tilde = gfm && source.slice(...range(scope)).includes("~");
|
|
@@ -409,12 +458,16 @@ function markerRule(type, fallback) {
|
|
|
409
458
|
if (!modelled)
|
|
410
459
|
accepted =
|
|
411
460
|
suspicious || (tilde && positions.some((at) => source[at] === marker[0])) ? [] : list;
|
|
412
|
-
|
|
461
|
+
// Where CommonMark pairs the text as written differently, the parser
|
|
462
|
+
// misread it, and renderers show other emphasis (issue #323).
|
|
463
|
+
else if (pairing(new Set(), true) !== expected)
|
|
464
|
+
accepted = [];
|
|
465
|
+
else if (!kept(markers(list))) {
|
|
413
466
|
accepted = [];
|
|
414
467
|
let changed = new Set();
|
|
415
468
|
for (const item of list) {
|
|
416
469
|
const trial = new Set([...changed, ...markers([item])]);
|
|
417
|
-
if (
|
|
470
|
+
if (!kept(trial))
|
|
418
471
|
continue;
|
|
419
472
|
changed = trial;
|
|
420
473
|
accepted.push(item);
|
|
@@ -654,9 +707,11 @@ function holds(node, type) {
|
|
|
654
707
|
* The inline nodes of a paragraph, from `start` on, that reflow keeps whole,
|
|
655
708
|
* sorted by position, and the hard line breaks anywhere in it. A container
|
|
656
709
|
* that holds inline HTML is kept whole too, because HTML can make the
|
|
657
|
-
* whitespace inside it significant.
|
|
710
|
+
* whitespace inside it significant. With `wholeLinks`, the text of a link is
|
|
711
|
+
* kept whole as an image's description is: Obsidian's Live Preview shows a
|
|
712
|
+
* link whose text spans lines as Markdown source (issue #306).
|
|
658
713
|
*/
|
|
659
|
-
function inlineAtoms(paragraph, start) {
|
|
714
|
+
function inlineAtoms(paragraph, start, wholeLinks) {
|
|
660
715
|
const atoms = [];
|
|
661
716
|
const breaks = [];
|
|
662
717
|
let unlocated = false;
|
|
@@ -674,6 +729,21 @@ function inlineAtoms(paragraph, start) {
|
|
|
674
729
|
continue;
|
|
675
730
|
if (child.type === "break")
|
|
676
731
|
breaks.push([a, b]);
|
|
732
|
+
else if (wholeLinks &&
|
|
733
|
+
(child.type === "link" || child.type === "linkReference") &&
|
|
734
|
+
child.children.length &&
|
|
735
|
+
labelEnd(child) &&
|
|
736
|
+
!holds(child, "html")) {
|
|
737
|
+
// The code spans, images, and other atoms in the text keep their own
|
|
738
|
+
// joining; between them, a line ending joins with the space that it
|
|
739
|
+
// renders as, as in an image's description.
|
|
740
|
+
const close = labelEnd(child);
|
|
741
|
+
const inner = atoms.length;
|
|
742
|
+
visit(child);
|
|
743
|
+
const within = atoms.splice(inner).sort((x, y) => x.from - y.from);
|
|
744
|
+
atoms.push({ from: a, to: close, joining: { within } });
|
|
745
|
+
atoms.push({ from: close, to: b, joining: destination });
|
|
746
|
+
}
|
|
677
747
|
else if (wrappable.has(child.type) &&
|
|
678
748
|
"children" in child &&
|
|
679
749
|
child.children.length &&
|
|
@@ -945,7 +1015,7 @@ const wrap = {
|
|
|
945
1015
|
}
|
|
946
1016
|
: {}),
|
|
947
1017
|
};
|
|
948
|
-
const inline = inlineAtoms(node, start);
|
|
1018
|
+
const inline = inlineAtoms(node, start, document.dialect === "obsidian");
|
|
949
1019
|
const protection = new Protection(start, end);
|
|
950
1020
|
for (const atom of inline.atoms)
|
|
951
1021
|
protection.add(atom.from, atom.to);
|
|
@@ -1074,9 +1144,11 @@ const wrap = {
|
|
|
1074
1144
|
break;
|
|
1075
1145
|
}
|
|
1076
1146
|
const last = words.at(-1);
|
|
1077
|
-
|
|
1147
|
+
let joining = protection.has(lineEnd)
|
|
1078
1148
|
? (atomAt(inline.atoms, lineEnd)?.joining ?? { reason: "line break in inline syntax" })
|
|
1079
1149
|
: undefined;
|
|
1150
|
+
if (joining && typeof joining === "object" && "within" in joining)
|
|
1151
|
+
joining = atomAt(joining.within, lineEnd)?.joining ?? "description";
|
|
1080
1152
|
if (!joining)
|
|
1081
1153
|
words.push(" ");
|
|
1082
1154
|
else if (last === undefined || /^[ \t]+$/.test(last))
|
|
@@ -1096,7 +1168,7 @@ const wrap = {
|
|
|
1096
1168
|
words[words.length - 1] = `${joining === "code" ? last : last.trimEnd()} `;
|
|
1097
1169
|
}
|
|
1098
1170
|
else
|
|
1099
|
-
failure = joining.reason;
|
|
1171
|
+
failure = "reason" in joining ? joining.reason : "line break in inline syntax";
|
|
1100
1172
|
}
|
|
1101
1173
|
words.push(...protection.split(line, lineAt));
|
|
1102
1174
|
lineEnd = lineAt + line.length;
|