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/syntax/parse.d.ts
CHANGED
|
@@ -39,6 +39,17 @@ export declare function splitByteOrderMark(source: string): [string, string];
|
|
|
39
39
|
* renderers without front matter read the block. Undefined for other trees.
|
|
40
40
|
*/
|
|
41
41
|
export declare function frontMatterReading(tree: Root): Root | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* The fence, `---` or `+++`, of front matter that the first line of `source`
|
|
44
|
+
* opens and no line closes. The front matter extension opens front matter at a
|
|
45
|
+
* first line of `---` or `+++` followed only by spaces or tabs, and closes it
|
|
46
|
+
* at the next line of the same fence, also followed only by spaces or tabs.
|
|
47
|
+
* Without a closing line the document has no front matter, but the extension
|
|
48
|
+
* then reads the lists and block quotes below as paragraphs (issue #278,
|
|
49
|
+
* micromark/micromark-extension-frontmatter#5). An edit that writes such a line
|
|
50
|
+
* below makes front matter of everything above it.
|
|
51
|
+
*/
|
|
52
|
+
export declare function unclosedFrontMatter(source: string): "---" | "+++" | undefined;
|
|
42
53
|
/**
|
|
43
54
|
* Parse a document in `dialect`, or the dialect that an alias such as
|
|
44
55
|
* `codeberg` stands for, which the result holds; an unknown dialect is an
|
|
@@ -50,7 +61,9 @@ export declare function frontMatterReading(tree: Root): Root | undefined;
|
|
|
50
61
|
* Front matter whose `---` or `+++` closes it inside a code block, an HTML
|
|
51
62
|
* block, or display math, opened by a line of the front matter, is read as
|
|
52
63
|
* Markdown: the document is read without it (issue #295), and
|
|
53
|
-
* `frontMatterReading` gives the other reading.
|
|
64
|
+
* `frontMatterReading` gives the other reading. A document whose first line
|
|
65
|
+
* would open front matter that no line closes is read without the front matter
|
|
66
|
+
* extension, as renderers read it (issue #278).
|
|
54
67
|
*/
|
|
55
68
|
export declare function parse(source: string, dialect: DialectName, path?: string, plugins?: readonly Plugin[]): Document;
|
|
56
69
|
/**
|
package/dist/syntax/parse.js
CHANGED
|
@@ -168,6 +168,24 @@ export function frontMatterReading(tree) {
|
|
|
168
168
|
const literalOpener = /^ {0,3}(?:`{3,}|~{3,}|\$\$|<)/m;
|
|
169
169
|
/** Blocks in which a `---` or `+++` line is content, not a closing line. */
|
|
170
170
|
const literalBlocks = new Set(["code", "html", "math"]);
|
|
171
|
+
/**
|
|
172
|
+
* The fence, `---` or `+++`, of front matter that the first line of `source`
|
|
173
|
+
* opens and no line closes. The front matter extension opens front matter at a
|
|
174
|
+
* first line of `---` or `+++` followed only by spaces or tabs, and closes it
|
|
175
|
+
* at the next line of the same fence, also followed only by spaces or tabs.
|
|
176
|
+
* Without a closing line the document has no front matter, but the extension
|
|
177
|
+
* then reads the lists and block quotes below as paragraphs (issue #278,
|
|
178
|
+
* micromark/micromark-extension-frontmatter#5). An edit that writes such a line
|
|
179
|
+
* below makes front matter of everything above it.
|
|
180
|
+
*/
|
|
181
|
+
export function unclosedFrontMatter(source) {
|
|
182
|
+
const opening = /^(---|\+\+\+)[ \t]*(?:\r\n|\r|\n)/.exec(source);
|
|
183
|
+
if (!opening)
|
|
184
|
+
return undefined;
|
|
185
|
+
const fence = opening[1];
|
|
186
|
+
const closing = new RegExp(`(?:^|\\r|\\n)${fence.replaceAll("+", "\\+")}[ \\t]*(?:\\r|\\n|$)`);
|
|
187
|
+
return closing.test(source.slice(opening[0].length)) ? undefined : fence;
|
|
188
|
+
}
|
|
171
189
|
/**
|
|
172
190
|
* Parse a document in `dialect`, or the dialect that an alias such as
|
|
173
191
|
* `codeberg` stands for, which the result holds; an unknown dialect is an
|
|
@@ -179,7 +197,9 @@ const literalBlocks = new Set(["code", "html", "math"]);
|
|
|
179
197
|
* Front matter whose `---` or `+++` closes it inside a code block, an HTML
|
|
180
198
|
* block, or display math, opened by a line of the front matter, is read as
|
|
181
199
|
* Markdown: the document is read without it (issue #295), and
|
|
182
|
-
* `frontMatterReading` gives the other reading.
|
|
200
|
+
* `frontMatterReading` gives the other reading. A document whose first line
|
|
201
|
+
* would open front matter that no line closes is read without the front matter
|
|
202
|
+
* extension, as renderers read it (issue #278).
|
|
183
203
|
*/
|
|
184
204
|
export function parse(source, dialect, path = "document.md", plugins = []) {
|
|
185
205
|
const read = knownDialect(dialect);
|
|
@@ -196,7 +216,9 @@ export function parse(source, dialect, path = "document.md", plugins = []) {
|
|
|
196
216
|
// footnotes, autolinks, and dollar math. Forgejo and Gitea syntax beyond
|
|
197
217
|
// that is protected by rules rather than parsed.
|
|
198
218
|
if (read !== "commonmark") {
|
|
199
|
-
|
|
219
|
+
// Obsidian marks strikethrough with two tildes only; `~a~` is text there
|
|
220
|
+
// (issue #319).
|
|
221
|
+
extensions.push(gfm({ singleTilde: read !== "obsidian" }), math(mathConditions[read]));
|
|
200
222
|
mdastExtensions.push(...gfmTree, mathFromMarkdown());
|
|
201
223
|
}
|
|
202
224
|
if (read === "obsidian") {
|
|
@@ -211,7 +233,7 @@ export function parse(source, dialect, path = "document.md", plugins = []) {
|
|
|
211
233
|
}
|
|
212
234
|
return fromMarkdown(source, { extensions, mdastExtensions });
|
|
213
235
|
};
|
|
214
|
-
let result = tree(
|
|
236
|
+
let result = tree(!unclosedFrontMatter(source));
|
|
215
237
|
// The tree's types know YAML front matter only; the parser also reads TOML.
|
|
216
238
|
const matter = result.children[0];
|
|
217
239
|
// Only front matter with a line that could open such a block is read again.
|
package/dist/syntax/parse.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/syntax/parse.ts"],"names":[],"mappings":"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","sourcesContent":["import { fromMarkdown } from \"mdast-util-from-markdown\";\nimport type { Extension as TreeExtension } from \"mdast-util-from-markdown\";\nimport type { Extension as SyntaxExtension } from \"micromark-util-types\";\nimport { gfm } from \"micromark-extension-gfm\";\nimport { gfmFromMarkdown } from \"mdast-util-gfm\";\nimport { frontmatter } from \"micromark-extension-frontmatter\";\nimport { frontmatterFromMarkdown } from \"mdast-util-frontmatter\";\nimport { mathFromMarkdown } from \"mdast-util-math\";\nimport type { InlineCode, Nodes, Paragraph, Root } from \"mdast\";\nimport type { Dialect, DialectName, Document, Plugin } from \"../core/types.js\";\nimport { knownDialect } from \"../config/resolve.js\";\nimport { math } from \"./math.js\";\nimport type { MathConditions } from \"./math.js\";\nimport { obsidianSyntax, obsidianTree } from \"./obsidian.js\";\n\n// GFM's tree transform performs GitHub's second, transform-time autolink pass\n// (`[www.example.com]` or a Forgejo shortlink's `|https://` in files), but its\n// nodes carry no source positions, which made the engine refuse whole documents.\n// No built-in rule acts on literal autolinks, so output is unchanged without it;\n// plugins inspecting link nodes do not see those autolinks.\nconst gfmTree = gfmFromMarkdown().map((extension) => {\n const copy = { ...extension };\n delete copy.transforms;\n return copy;\n});\n\n/** The offset of the `]` that closes the text of each link and image, by node. */\nconst labelEnds = new WeakMap<Nodes, number>();\n/**\n * Record where the text of a link or image ends. The tree keeps an image's\n * description only as plain text, so its source positions are otherwise lost.\n */\nconst labelEndTree: TreeExtension = {\n exit: {\n // A label has an opening and a closing marker; the closing one comes last.\n labelMarker(token) {\n // The label's fragment is on top of the stack, and the link or image below it.\n const node = this.stack[this.stack.length - 2];\n if (node?.type === \"link\" || node?.type === \"image\") labelEnds.set(node, token.start.offset);\n },\n },\n};\n/**\n * Where the text of a link or the description of an image ends in the source:\n * the offset of its closing `]`. Undefined for other nodes and for nodes that\n * `parse` did not make.\n */\nexport function labelEnd(node: Nodes): number | undefined {\n return labelEnds.get(node);\n}\n/**\n * What the value of a code span in the tree leaves out, by node: the space or\n * line ending that the parser took off each end, and which lines of the\n * document are lazy continuation lines.\n */\nconst codeSpanSources = new WeakMap<\n InlineCode,\n { padding: string[]; lazy: Record<number, boolean> }\n>();\n/**\n * The extensions of one parse that record what code spans leave out. The\n * parser knows which lines are lazy once it has read the document's blocks, and\n * a construct at a backtick, which never matches, reads that from it before a\n * code span is read there.\n */\nfunction codeSpanRecording(): { syntax: SyntaxExtension; tree: TreeExtension } {\n let lazy: Record<number, boolean> = {};\n return {\n syntax: {\n text: {\n [0x60]: {\n // The text tokenizer asks each construct at a backtick whether the\n // character before allows it, and tries them all if one does. This\n // one never does, so the parse is the same as without it.\n previous() {\n lazy = this.parser.lazy;\n return false;\n },\n tokenize(_effects, _ok, nok) {\n lazy = this.parser.lazy;\n return nok;\n },\n },\n },\n },\n tree: {\n exit: {\n // The span's value is collected in a fragment on top of the stack, with\n // the span's node below it.\n codeTextSequence() {\n const node = this.stack[this.stack.length - 2];\n if (node?.type === \"inlineCode\" && !codeSpanSources.has(node))\n codeSpanSources.set(node, { padding: [], lazy });\n },\n codeTextPadding(token) {\n const node = this.stack[this.stack.length - 2];\n if (node?.type === \"inlineCode\")\n codeSpanSources.get(node)?.padding.push(this.sliceSerialize(token));\n },\n },\n },\n };\n}\n/**\n * The text of a code span as GitHub shows it, read in CommonMark's order: the\n * spaces and tabs that start each line after the first are dropped, as from\n * every line of a paragraph, but kept on a lazy continuation line, one that\n * lacks its block quote's `>` or its list item's indentation; then each line\n * ending becomes a space; then one space comes off each end when both ends\n * have one and the span is not only spaces. The parser keeps the spaces and\n * tabs in the span's value, and takes a space or line ending off each end\n * before line endings become spaces (issues #276 and #290). A node that\n * `parse` did not make is read as if its value were all of its content.\n */\nexport function codeSpanText(node: InlineCode): string {\n const { padding = [], lazy = {} } = codeSpanSources.get(node) ?? {};\n let line = node.position?.start.line ?? 0;\n const content = `${padding[0] ?? \"\"}${node.value}${padding[1] ?? \"\"}`.replace(\n /(?:\\r\\n|\\r|\\n)([ \\t]*)/g,\n (_, indentation: string) => (lazy[++line] ? ` ${indentation}` : \" \"),\n );\n return /^ [^]* $/.test(content) && /[^ ]/.test(content) ? content.slice(1, -1) : content;\n}\n/**\n * Whether a code span goes on in a lazy continuation line that starts with\n * spaces or tabs. GitHub keeps them in the span, as `codeSpanText` does, and\n * commonmark.js drops them, so renderers show such a span differently.\n */\nexport function lazyIndentation(node: InlineCode): boolean {\n const { padding = [], lazy = {} } = codeSpanSources.get(node) ?? {};\n let line = node.position?.start.line ?? 0;\n const content = `${padding[0] ?? \"\"}${node.value}${padding[1] ?? \"\"}`;\n for (const [, indentation] of content.matchAll(/(?:\\r\\n|\\r|\\n)([ \\t]*)/g))\n if (lazy[++line] && indentation) return true;\n return false;\n}\n\n/**\n * Whose conditions for `$…$` in running text each dialect follows, as observed\n * on GitHub's file view, on Forgejo 16 and Gitea 1.25 and 28, and in Obsidian's\n * Reading view and Live Preview. CommonMark has no math.\n */\nconst mathConditions: Record<Exclude<Dialect, \"commonmark\">, MathConditions> = {\n github: \"github\",\n forgejo: \"forge\",\n gitea: \"forge\",\n obsidian: \"obsidian\",\n};\n\n/**\n * The byte order marks that `source` starts with, and the text after them.\n * Some editors write a mark at the start of a UTF-8 file, and a program that\n * adds one without looking can leave two. The parser skips one mark at the\n * start of what it is given.\n */\nexport function splitByteOrderMark(source: string): [string, string] {\n let end = 0;\n while (source.charCodeAt(end) === 0xfeff) end++;\n return [source.slice(0, end), source.slice(end)];\n}\n\n/**\n * The reading with front matter of a document that `parse` read without it,\n * by the tree of the reading without it.\n */\nconst frontMatterReadings = new WeakMap<Root, Root>();\n/**\n * The document read with front matter, where `parse` read it without front\n * matter because its front matter would end inside a code block, an HTML\n * block, or display math. Site generators read front matter there, and so does\n * GitHub's file view when it is a YAML mapping; GitHub's comments and\n * renderers without front matter read the block. Undefined for other trees.\n */\nexport function frontMatterReading(tree: Root): Root | undefined {\n return frontMatterReadings.get(tree);\n}\n/**\n * A line of front matter that could open a block whose content is literal: a\n * code fence, display math, or an HTML block.\n */\nconst literalOpener = /^ {0,3}(?:`{3,}|~{3,}|\\$\\$|<)/m;\n/** Blocks in which a `---` or `+++` line is content, not a closing line. */\nconst literalBlocks = new Set([\"code\", \"html\", \"math\"]);\n\n/**\n * Parse a document in `dialect`, or the dialect that an alias such as\n * `codeberg` stands for, which the result holds; an unknown dialect is an\n * error. Byte order marks at the start of `source` are not part of the\n * document: the document's source starts after them, as the positions in the\n * tree count. `path` is the document's path relative to the workspace root,\n * and `plugins` add their syntax extensions.\n *\n * Front matter whose `---` or `+++` closes it inside a code block, an HTML\n * block, or display math, opened by a line of the front matter, is read as\n * Markdown: the document is read without it (issue #295), and\n * `frontMatterReading` gives the other reading.\n */\nexport function parse(\n source: string,\n dialect: DialectName,\n path = \"document.md\",\n plugins: readonly Plugin[] = [],\n): Document {\n const read = knownDialect(dialect);\n source = splitByteOrderMark(source)[1];\n const tree = (withFrontMatter: boolean): Root => {\n const codeSpans = codeSpanRecording();\n const extensions = [codeSpans.syntax];\n const mdastExtensions = [labelEndTree, codeSpans.tree];\n if (withFrontMatter) {\n extensions.unshift(frontmatter([\"yaml\", \"toml\"]));\n mdastExtensions.unshift(frontmatterFromMarkdown([\"yaml\", \"toml\"]));\n }\n // GitHub, Forgejo, and Gitea share GFM tables, task lists, strikethrough,\n // footnotes, autolinks, and dollar math. Forgejo and Gitea syntax beyond\n // that is protected by rules rather than parsed.\n if (read !== \"commonmark\") {\n extensions.push(gfm(), math(mathConditions[read]));\n mdastExtensions.push(...gfmTree, mathFromMarkdown());\n }\n if (read === \"obsidian\") {\n extensions.push(obsidianSyntax);\n mdastExtensions.push(obsidianTree);\n }\n for (const plugin of plugins) {\n if (plugin.syntax) {\n extensions.push(plugin.syntax.micromark);\n mdastExtensions.push(plugin.syntax.mdast);\n }\n }\n return fromMarkdown(source, { extensions, mdastExtensions });\n };\n let result = tree(true);\n // The tree's types know YAML front matter only; the parser also reads TOML.\n const matter = result.children[0] as { type: string; value?: string } | undefined;\n // Only front matter with a line that could open such a block is read again.\n // It is given up only when the line that closes it is content of that block\n // in the other reading, which a multiline TOML string or a YAML block\n // scalar holding a fence does not decide by itself.\n if (\n (matter?.type === \"yaml\" || matter?.type === \"toml\") &&\n literalOpener.test(matter.value ?? \"\")\n ) {\n const closing = result.children[0]!.position!.end.line;\n const plain = tree(false);\n if (\n plain.children.some(\n (node) =>\n literalBlocks.has(node.type) &&\n node.position!.start.line < closing &&\n node.position!.end.line >= closing,\n )\n ) {\n frontMatterReadings.set(plain, result);\n result = plain;\n }\n }\n return { source, dialect: read, path, tree: result };\n}\n\n/**\n * The `[start, end]` offsets of a node of a parsed document in its `source`:\n * half-open, in UTF-16 code units. A node without a source position, such as\n * one that a plugin's tree extension built without one, is an error.\n */\nexport function range(node: Nodes): [number, number] {\n if (node.position?.start.offset === undefined || node.position.end.offset === undefined) {\n throw new Error(`Missing source position for ${node.type}`);\n }\n return [node.position.start.offset, node.position.end.offset];\n}\n/** An alert's or callout's marker, such as `[!NOTE]` or `[!tip]`. */\nconst calloutMarker = /^\\[![\\w-]+\\]/;\n/**\n * Whether a paragraph starts with text that `pattern` matches, by default an\n * alert's or callout's marker: in `written`, its source from its start, or in\n * its text as renderers read it, where a backslash escape or a character\n * reference stands for the character it writes. GitHub reads `\\[!NOTE]`,\n * `[!NOTE]`, and `[!NOTE]` as alerts too (issue #294).\n */\nexport function startsWithMarker(\n written: string,\n paragraph: Paragraph,\n pattern: RegExp = calloutMarker,\n): boolean {\n const first = paragraph.children[0];\n return pattern.test(written) || (first?.type === \"text\" && pattern.test(first.value));\n}\n/**\n * A node's text as a reader sees it: the values of its text, code, and other\n * literal nodes, in order. A code span gives its text as `codeSpanText` reads\n * it, on one line, a wikilink its label, or else its target, and an Obsidian\n * comment nothing.\n */\nexport function textContent(node: Nodes): string {\n if (node.type === \"wikiLink\") return node.label ?? node.target;\n if (node.type === \"obsidianComment\") return \"\";\n if (node.type === \"inlineCode\") return codeSpanText(node);\n if (\"value\" in node) return node.value;\n if (\"children\" in node) return node.children.map(textContent).join(\"\");\n return \"\";\n}\n"]}
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/syntax/parse.ts"],"names":[],"mappings":"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","sourcesContent":["import { fromMarkdown } from \"mdast-util-from-markdown\";\nimport type { Extension as TreeExtension } from \"mdast-util-from-markdown\";\nimport type { Extension as SyntaxExtension } from \"micromark-util-types\";\nimport { gfm } from \"micromark-extension-gfm\";\nimport { gfmFromMarkdown } from \"mdast-util-gfm\";\nimport { frontmatter } from \"micromark-extension-frontmatter\";\nimport { frontmatterFromMarkdown } from \"mdast-util-frontmatter\";\nimport { mathFromMarkdown } from \"mdast-util-math\";\nimport type { InlineCode, Nodes, Paragraph, Root } from \"mdast\";\nimport type { Dialect, DialectName, Document, Plugin } from \"../core/types.js\";\nimport { knownDialect } from \"../config/resolve.js\";\nimport { math } from \"./math.js\";\nimport type { MathConditions } from \"./math.js\";\nimport { obsidianSyntax, obsidianTree } from \"./obsidian.js\";\n\n// GFM's tree transform performs GitHub's second, transform-time autolink pass\n// (`[www.example.com]` or a Forgejo shortlink's `|https://` in files), but its\n// nodes carry no source positions, which made the engine refuse whole documents.\n// No built-in rule acts on literal autolinks, so output is unchanged without it;\n// plugins inspecting link nodes do not see those autolinks.\nconst gfmTree = gfmFromMarkdown().map((extension) => {\n const copy = { ...extension };\n delete copy.transforms;\n return copy;\n});\n\n/** The offset of the `]` that closes the text of each link and image, by node. */\nconst labelEnds = new WeakMap<Nodes, number>();\n/**\n * Record where the text of a link or image ends. The tree keeps an image's\n * description only as plain text, so its source positions are otherwise lost.\n */\nconst labelEndTree: TreeExtension = {\n exit: {\n // A label has an opening and a closing marker; the closing one comes last.\n labelMarker(token) {\n // The label's fragment is on top of the stack, and the link or image below it.\n const node = this.stack[this.stack.length - 2];\n if (node?.type === \"link\" || node?.type === \"image\") labelEnds.set(node, token.start.offset);\n },\n },\n};\n/**\n * Where the text of a link or the description of an image ends in the source:\n * the offset of its closing `]`. Undefined for other nodes and for nodes that\n * `parse` did not make.\n */\nexport function labelEnd(node: Nodes): number | undefined {\n return labelEnds.get(node);\n}\n/**\n * What the value of a code span in the tree leaves out, by node: the space or\n * line ending that the parser took off each end, and which lines of the\n * document are lazy continuation lines.\n */\nconst codeSpanSources = new WeakMap<\n InlineCode,\n { padding: string[]; lazy: Record<number, boolean> }\n>();\n/**\n * The extensions of one parse that record what code spans leave out. The\n * parser knows which lines are lazy once it has read the document's blocks, and\n * a construct at a backtick, which never matches, reads that from it before a\n * code span is read there.\n */\nfunction codeSpanRecording(): { syntax: SyntaxExtension; tree: TreeExtension } {\n let lazy: Record<number, boolean> = {};\n return {\n syntax: {\n text: {\n [0x60]: {\n // The text tokenizer asks each construct at a backtick whether the\n // character before allows it, and tries them all if one does. This\n // one never does, so the parse is the same as without it.\n previous() {\n lazy = this.parser.lazy;\n return false;\n },\n tokenize(_effects, _ok, nok) {\n lazy = this.parser.lazy;\n return nok;\n },\n },\n },\n },\n tree: {\n exit: {\n // The span's value is collected in a fragment on top of the stack, with\n // the span's node below it.\n codeTextSequence() {\n const node = this.stack[this.stack.length - 2];\n if (node?.type === \"inlineCode\" && !codeSpanSources.has(node))\n codeSpanSources.set(node, { padding: [], lazy });\n },\n codeTextPadding(token) {\n const node = this.stack[this.stack.length - 2];\n if (node?.type === \"inlineCode\")\n codeSpanSources.get(node)?.padding.push(this.sliceSerialize(token));\n },\n },\n },\n };\n}\n/**\n * The text of a code span as GitHub shows it, read in CommonMark's order: the\n * spaces and tabs that start each line after the first are dropped, as from\n * every line of a paragraph, but kept on a lazy continuation line, one that\n * lacks its block quote's `>` or its list item's indentation; then each line\n * ending becomes a space; then one space comes off each end when both ends\n * have one and the span is not only spaces. The parser keeps the spaces and\n * tabs in the span's value, and takes a space or line ending off each end\n * before line endings become spaces (issues #276 and #290). A node that\n * `parse` did not make is read as if its value were all of its content.\n */\nexport function codeSpanText(node: InlineCode): string {\n const { padding = [], lazy = {} } = codeSpanSources.get(node) ?? {};\n let line = node.position?.start.line ?? 0;\n const content = `${padding[0] ?? \"\"}${node.value}${padding[1] ?? \"\"}`.replace(\n /(?:\\r\\n|\\r|\\n)([ \\t]*)/g,\n (_, indentation: string) => (lazy[++line] ? ` ${indentation}` : \" \"),\n );\n return /^ [^]* $/.test(content) && /[^ ]/.test(content) ? content.slice(1, -1) : content;\n}\n/**\n * Whether a code span goes on in a lazy continuation line that starts with\n * spaces or tabs. GitHub keeps them in the span, as `codeSpanText` does, and\n * commonmark.js drops them, so renderers show such a span differently.\n */\nexport function lazyIndentation(node: InlineCode): boolean {\n const { padding = [], lazy = {} } = codeSpanSources.get(node) ?? {};\n let line = node.position?.start.line ?? 0;\n const content = `${padding[0] ?? \"\"}${node.value}${padding[1] ?? \"\"}`;\n for (const [, indentation] of content.matchAll(/(?:\\r\\n|\\r|\\n)([ \\t]*)/g))\n if (lazy[++line] && indentation) return true;\n return false;\n}\n\n/**\n * Whose conditions for `$…$` in running text each dialect follows, as observed\n * on GitHub's file view, on Forgejo 16 and Gitea 1.25 and 28, and in Obsidian's\n * Reading view and Live Preview. CommonMark has no math.\n */\nconst mathConditions: Record<Exclude<Dialect, \"commonmark\">, MathConditions> = {\n github: \"github\",\n forgejo: \"forge\",\n gitea: \"forge\",\n obsidian: \"obsidian\",\n};\n\n/**\n * The byte order marks that `source` starts with, and the text after them.\n * Some editors write a mark at the start of a UTF-8 file, and a program that\n * adds one without looking can leave two. The parser skips one mark at the\n * start of what it is given.\n */\nexport function splitByteOrderMark(source: string): [string, string] {\n let end = 0;\n while (source.charCodeAt(end) === 0xfeff) end++;\n return [source.slice(0, end), source.slice(end)];\n}\n\n/**\n * The reading with front matter of a document that `parse` read without it,\n * by the tree of the reading without it.\n */\nconst frontMatterReadings = new WeakMap<Root, Root>();\n/**\n * The document read with front matter, where `parse` read it without front\n * matter because its front matter would end inside a code block, an HTML\n * block, or display math. Site generators read front matter there, and so does\n * GitHub's file view when it is a YAML mapping; GitHub's comments and\n * renderers without front matter read the block. Undefined for other trees.\n */\nexport function frontMatterReading(tree: Root): Root | undefined {\n return frontMatterReadings.get(tree);\n}\n/**\n * A line of front matter that could open a block whose content is literal: a\n * code fence, display math, or an HTML block.\n */\nconst literalOpener = /^ {0,3}(?:`{3,}|~{3,}|\\$\\$|<)/m;\n/** Blocks in which a `---` or `+++` line is content, not a closing line. */\nconst literalBlocks = new Set([\"code\", \"html\", \"math\"]);\n/**\n * The fence, `---` or `+++`, of front matter that the first line of `source`\n * opens and no line closes. The front matter extension opens front matter at a\n * first line of `---` or `+++` followed only by spaces or tabs, and closes it\n * at the next line of the same fence, also followed only by spaces or tabs.\n * Without a closing line the document has no front matter, but the extension\n * then reads the lists and block quotes below as paragraphs (issue #278,\n * micromark/micromark-extension-frontmatter#5). An edit that writes such a line\n * below makes front matter of everything above it.\n */\nexport function unclosedFrontMatter(source: string): \"---\" | \"+++\" | undefined {\n const opening = /^(---|\\+\\+\\+)[ \\t]*(?:\\r\\n|\\r|\\n)/.exec(source);\n if (!opening) return undefined;\n const fence = opening[1] as \"---\" | \"+++\";\n const closing = new RegExp(`(?:^|\\\\r|\\\\n)${fence.replaceAll(\"+\", \"\\\\+\")}[ \\\\t]*(?:\\\\r|\\\\n|$)`);\n return closing.test(source.slice(opening[0].length)) ? undefined : fence;\n}\n\n/**\n * Parse a document in `dialect`, or the dialect that an alias such as\n * `codeberg` stands for, which the result holds; an unknown dialect is an\n * error. Byte order marks at the start of `source` are not part of the\n * document: the document's source starts after them, as the positions in the\n * tree count. `path` is the document's path relative to the workspace root,\n * and `plugins` add their syntax extensions.\n *\n * Front matter whose `---` or `+++` closes it inside a code block, an HTML\n * block, or display math, opened by a line of the front matter, is read as\n * Markdown: the document is read without it (issue #295), and\n * `frontMatterReading` gives the other reading. A document whose first line\n * would open front matter that no line closes is read without the front matter\n * extension, as renderers read it (issue #278).\n */\nexport function parse(\n source: string,\n dialect: DialectName,\n path = \"document.md\",\n plugins: readonly Plugin[] = [],\n): Document {\n const read = knownDialect(dialect);\n source = splitByteOrderMark(source)[1];\n const tree = (withFrontMatter: boolean): Root => {\n const codeSpans = codeSpanRecording();\n const extensions = [codeSpans.syntax];\n const mdastExtensions = [labelEndTree, codeSpans.tree];\n if (withFrontMatter) {\n extensions.unshift(frontmatter([\"yaml\", \"toml\"]));\n mdastExtensions.unshift(frontmatterFromMarkdown([\"yaml\", \"toml\"]));\n }\n // GitHub, Forgejo, and Gitea share GFM tables, task lists, strikethrough,\n // footnotes, autolinks, and dollar math. Forgejo and Gitea syntax beyond\n // that is protected by rules rather than parsed.\n if (read !== \"commonmark\") {\n // Obsidian marks strikethrough with two tildes only; `~a~` is text there\n // (issue #319).\n extensions.push(gfm({ singleTilde: read !== \"obsidian\" }), math(mathConditions[read]));\n mdastExtensions.push(...gfmTree, mathFromMarkdown());\n }\n if (read === \"obsidian\") {\n extensions.push(obsidianSyntax);\n mdastExtensions.push(obsidianTree);\n }\n for (const plugin of plugins) {\n if (plugin.syntax) {\n extensions.push(plugin.syntax.micromark);\n mdastExtensions.push(plugin.syntax.mdast);\n }\n }\n return fromMarkdown(source, { extensions, mdastExtensions });\n };\n let result = tree(!unclosedFrontMatter(source));\n // The tree's types know YAML front matter only; the parser also reads TOML.\n const matter = result.children[0] as { type: string; value?: string } | undefined;\n // Only front matter with a line that could open such a block is read again.\n // It is given up only when the line that closes it is content of that block\n // in the other reading, which a multiline TOML string or a YAML block\n // scalar holding a fence does not decide by itself.\n if (\n (matter?.type === \"yaml\" || matter?.type === \"toml\") &&\n literalOpener.test(matter.value ?? \"\")\n ) {\n const closing = result.children[0]!.position!.end.line;\n const plain = tree(false);\n if (\n plain.children.some(\n (node) =>\n literalBlocks.has(node.type) &&\n node.position!.start.line < closing &&\n node.position!.end.line >= closing,\n )\n ) {\n frontMatterReadings.set(plain, result);\n result = plain;\n }\n }\n return { source, dialect: read, path, tree: result };\n}\n\n/**\n * The `[start, end]` offsets of a node of a parsed document in its `source`:\n * half-open, in UTF-16 code units. A node without a source position, such as\n * one that a plugin's tree extension built without one, is an error.\n */\nexport function range(node: Nodes): [number, number] {\n if (node.position?.start.offset === undefined || node.position.end.offset === undefined) {\n throw new Error(`Missing source position for ${node.type}`);\n }\n return [node.position.start.offset, node.position.end.offset];\n}\n/** An alert's or callout's marker, such as `[!NOTE]` or `[!tip]`. */\nconst calloutMarker = /^\\[![\\w-]+\\]/;\n/**\n * Whether a paragraph starts with text that `pattern` matches, by default an\n * alert's or callout's marker: in `written`, its source from its start, or in\n * its text as renderers read it, where a backslash escape or a character\n * reference stands for the character it writes. GitHub reads `\\[!NOTE]`,\n * `[!NOTE]`, and `[!NOTE]` as alerts too (issue #294).\n */\nexport function startsWithMarker(\n written: string,\n paragraph: Paragraph,\n pattern: RegExp = calloutMarker,\n): boolean {\n const first = paragraph.children[0];\n return pattern.test(written) || (first?.type === \"text\" && pattern.test(first.value));\n}\n/**\n * A node's text as a reader sees it: the values of its text, code, and other\n * literal nodes, in order. A code span gives its text as `codeSpanText` reads\n * it, on one line, a wikilink its label, or else its target, and an Obsidian\n * comment nothing.\n */\nexport function textContent(node: Nodes): string {\n if (node.type === \"wikiLink\") return node.label ?? node.target;\n if (node.type === \"obsidianComment\") return \"\";\n if (node.type === \"inlineCode\") return codeSpanText(node);\n if (\"value\" in node) return node.value;\n if (\"children\" in node) return node.children.map(textContent).join(\"\");\n return \"\";\n}\n"]}
|
|
@@ -29,7 +29,8 @@ export interface DiscoverOptions {
|
|
|
29
29
|
/**
|
|
30
30
|
* Documents whose text the caller supplies, such as an editor's unsaved
|
|
31
31
|
* text, by path relative to the root. They are selected, and the workspace
|
|
32
|
-
* reads this text instead of a file at that path.
|
|
32
|
+
* reads this text instead of a file at that path. One whose path ignore rules
|
|
33
|
+
* leave out is not selected and is reported in `skipped`, as a named file is.
|
|
33
34
|
*/
|
|
34
35
|
documents?: Record<string, string>;
|
|
35
36
|
}
|
|
@@ -76,5 +77,9 @@ export declare function checkRoot(given: string): Promise<void>;
|
|
|
76
77
|
* is written.
|
|
77
78
|
*/
|
|
78
79
|
export declare function discoverWorkspace(options: DiscoverOptions): Promise<WorkspaceDiscovery>;
|
|
79
|
-
/**
|
|
80
|
-
|
|
80
|
+
/**
|
|
81
|
+
* The same, with relative inputs and exclusions resolved from `base`. Without
|
|
82
|
+
* `ignoreDocuments`, ignore rules do not leave out the supplied documents, as
|
|
83
|
+
* for text from stdin without a path of its own.
|
|
84
|
+
*/
|
|
85
|
+
export declare function discoverFrom(options: DiscoverOptions, base: string, ignoreDocuments?: boolean): Promise<WorkspaceDiscovery>;
|
|
@@ -3,7 +3,7 @@ import { constants } from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { resolveConfig } from "../config/resolve.js";
|
|
5
5
|
import { pathError } from "./access.js";
|
|
6
|
-
import { discover, unlistedLookup, vaultFor } from "./files.js";
|
|
6
|
+
import { discover, isIgnoredInput, unlistedLookup, vaultFor } from "./files.js";
|
|
7
7
|
import { createWorkspace } from "./index.js";
|
|
8
8
|
import { excludeSelection } from "./selection.js";
|
|
9
9
|
import { vaultOf } from "./vault.js";
|
|
@@ -42,27 +42,39 @@ export async function checkRoot(given) {
|
|
|
42
42
|
export function discoverWorkspace(options) {
|
|
43
43
|
return discoverFrom(options, path.resolve(options.root));
|
|
44
44
|
}
|
|
45
|
-
/**
|
|
46
|
-
|
|
45
|
+
/**
|
|
46
|
+
* The same, with relative inputs and exclusions resolved from `base`. Without
|
|
47
|
+
* `ignoreDocuments`, ignore rules do not leave out the supplied documents, as
|
|
48
|
+
* for text from stdin without a path of its own.
|
|
49
|
+
*/
|
|
50
|
+
export async function discoverFrom(options, base, ignoreDocuments = true) {
|
|
47
51
|
await checkRoot(options.root);
|
|
48
52
|
// Which files to leave out does not depend on a document's dialect.
|
|
49
53
|
const resolved = resolveConfig({ config: options.config, plugins: options.plugins });
|
|
54
|
+
const documents = new Map();
|
|
55
|
+
for (const [given, text] of Object.entries(options.documents ?? {})) {
|
|
56
|
+
const name = path.posix.normalize(given.split(path.sep).join("/"));
|
|
57
|
+
if (name === "." || name === ".." || name.startsWith("../") || path.isAbsolute(given))
|
|
58
|
+
throw new Error(`Document path is outside the workspace: ${given}`);
|
|
59
|
+
documents.set(name, text);
|
|
60
|
+
}
|
|
50
61
|
// Discovery always visits the whole workspace, whose files are link targets;
|
|
51
62
|
// the inputs only choose what is selected.
|
|
52
63
|
const none = options.inputs?.length === 0;
|
|
53
|
-
const set = await discover(options.root, none ? [] : (options.inputs ?? []), resolved.ignore, resolved.resolve, base);
|
|
64
|
+
const set = await discover(options.root, none ? [] : (options.inputs ?? []), resolved.ignore, resolved.resolve, base, { documents: ignoreDocuments ? [...documents.keys()] : [], selectNone: none });
|
|
54
65
|
set.selected = none
|
|
55
66
|
? []
|
|
56
67
|
: await excludeSelection(set.root, set.selected, options.exclude ?? [], base);
|
|
68
|
+
// A supplied document that ignore rules leave out is reported as a named
|
|
69
|
+
// file is, and not selected.
|
|
70
|
+
const ignored = new Set(set.skipped.flatMap((item) => item.type === "file" && isIgnoredInput(item) ? [item.path] : []));
|
|
57
71
|
// A supplied document can lie in a folder that discovery did not visit, so
|
|
58
72
|
// its vault is found by a search of its own.
|
|
59
73
|
const supplied = new Map();
|
|
60
|
-
for (const [
|
|
61
|
-
const name = path.posix.normalize(given.split(path.sep).join("/"));
|
|
62
|
-
if (name === "." || name === ".." || name.startsWith("../") || path.isAbsolute(given))
|
|
63
|
-
throw new Error(`Document path is outside the workspace: ${given}`);
|
|
74
|
+
for (const [name, text] of documents) {
|
|
64
75
|
set.files[name] = text;
|
|
65
|
-
|
|
76
|
+
if (!ignored.has(name))
|
|
77
|
+
supplied.set(name, undefined);
|
|
66
78
|
}
|
|
67
79
|
for (const name of supplied.keys())
|
|
68
80
|
supplied.set(name, await vaultFor(set, name));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/workspace/discovery.ts"],"names":[],"mappings":"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","sourcesContent":["import { access, stat } from \"node:fs/promises\";\nimport { constants } from \"node:fs\";\nimport path from \"node:path\";\nimport { resolveConfig } from \"../config/resolve.js\";\nimport type { Config, Dialect, Plugin, ProcessOptions, Workspace } from \"../core/types.js\";\nimport { pathError } from \"./access.js\";\nimport { discover, unlistedLookup, vaultFor } from \"./files.js\";\nimport type { Skipped } from \"./files.js\";\nimport { createWorkspace } from \"./index.js\";\nimport { excludeSelection } from \"./selection.js\";\nimport { vaultOf } from \"./vault.js\";\nimport type { Vault } from \"./vault.js\";\n\n/** The options of `discoverWorkspace`. */\nexport interface DiscoverOptions {\n /** The workspace root: the folder whose documents are processed and whose files are link targets. */\n root: string;\n /**\n * Files, folders, and patterns to select, absolute or relative to the root.\n * A folder selects its Markdown files, those named `.md` or `.markdown`; a\n * file is selected whatever its name. An input that names no existing path\n * and holds pattern characters (`*`, `?`, `[`, `{`) is a pattern, with `/`\n * between folders on every system: it selects the Markdown files that it\n * matches, and matching none is an error. Ignored files are not selected,\n * and an ignored file or folder named here is reported in `skipped`.\n * Omitted, every document of the workspace is selected; an empty list\n * selects none.\n */\n inputs?: string[];\n /**\n * Files and folders to leave out of the selection, absolute or relative to\n * the root. They are paths, not patterns, and stay link targets.\n */\n exclude?: string[];\n /** The configuration: its `ignore` and `resolve` settings, and the dialect of each document. */\n config?: Config;\n /** Plugin objects, whose rules and presets the configuration can name. */\n plugins?: Plugin[];\n /**\n * Documents whose text the caller supplies, such as an editor's unsaved\n * text, by path relative to the root. They are selected, and the workspace\n * reads this text instead of a file at that path.\n */\n documents?: Record<string, string>;\n}\n/** The files of a workspace that `discoverWorkspace` found, and how to process them. */\nexport interface WorkspaceDiscovery {\n /** The workspace root's real path. */\n readonly root: string;\n /** The documents to process, as forward-slash paths relative to the root, sorted. */\n readonly selected: readonly string[];\n /** What discovery could not read or use and left out, as in the CLI's JSON report. */\n readonly skipped: readonly Skipped[];\n /**\n * The text of a selected document. A file is read when it is first needed\n * and then kept; an error with the code `EACCES`, `EPERM`, or `EBUSY` means\n * that the system did not let it be read, and one with the code\n * `ERR_ENCODING_INVALID_ENCODED_DATA` that its bytes are not UTF-8 text.\n */\n read(path: string): string;\n /**\n * The options to lint or format a selected document with, as the CLI does:\n * its path, the configuration and plugins, a workspace for its dialect with\n * its vault's line-break setting, and the dialect to assume for a note.\n */\n options(path: string): ProcessOptions;\n}\n\n/**\n * The dialect to assume for a document when nothing names one, or undefined for\n * the library's own default. An Obsidian vault shows a single line break as a\n * line break and holds wikilinks, both of which any other dialect lets reflow\n * change, so a document that a vault holds is read as a note.\n */\nexport function assumedDialect(vault: Vault | string | undefined): Dialect | undefined {\n return vault ? \"obsidian\" : undefined;\n}\n/**\n * Stops when `given` cannot serve as a workspace root, with a message that\n * names it. Without this check the first thing to fail could be a search\n * inside it, with a message about a path the user never named.\n */\nexport async function checkRoot(given: string): Promise<void> {\n const root = path.resolve(given);\n try {\n if (!(await stat(root)).isDirectory())\n throw new Error(`Workspace root is not a directory: ${root}`);\n await access(root, constants.R_OK | constants.X_OK);\n } catch (error) {\n throw pathError(\"Workspace root\", root, error);\n }\n}\n/**\n * Find the files of a workspace and select documents the way the CLI does:\n * honoring Git's ignore rules and the configuration's `ignore` and `resolve`\n * settings, skipping links, nested repositories, and what the system does not\n * let the tool read, and recognizing each document's Obsidian vault. Nothing\n * is written.\n */\nexport function discoverWorkspace(options: DiscoverOptions): Promise<WorkspaceDiscovery> {\n return discoverFrom(options, path.resolve(options.root));\n}\n/** The same, with relative inputs and exclusions resolved from `base`. */\nexport async function discoverFrom(\n options: DiscoverOptions,\n base: string,\n): Promise<WorkspaceDiscovery> {\n await checkRoot(options.root);\n // Which files to leave out does not depend on a document's dialect.\n const resolved = resolveConfig({ config: options.config, plugins: options.plugins });\n // Discovery always visits the whole workspace, whose files are link targets;\n // the inputs only choose what is selected.\n const none = options.inputs?.length === 0;\n const set = await discover(\n options.root,\n none ? [] : (options.inputs ?? []),\n resolved.ignore,\n resolved.resolve,\n base,\n );\n set.selected = none\n ? []\n : await excludeSelection(set.root, set.selected, options.exclude ?? [], base);\n // A supplied document can lie in a folder that discovery did not visit, so\n // its vault is found by a search of its own.\n const supplied = new Map<string, Vault | undefined>();\n for (const [given, text] of Object.entries(options.documents ?? {})) {\n const name = path.posix.normalize(given.split(path.sep).join(\"/\"));\n if (name === \".\" || name === \"..\" || name.startsWith(\"../\") || path.isAbsolute(given))\n throw new Error(`Document path is outside the workspace: ${given}`);\n set.files[name] = text;\n supplied.set(name, undefined);\n }\n for (const name of supplied.keys()) supplied.set(name, await vaultFor(set, name));\n const selected = [...new Set([...set.selected, ...supplied.keys()])].sort();\n const chosen = new Set(selected);\n const known = (name: string) => {\n if (!chosen.has(name)) throw new Error(`Not a selected document: ${name}`);\n };\n // One index per active dialect, built when a document of the dialect is first\n // processed, and a view of it with each vault's line-break setting.\n const indexes = new Map<Dialect, Workspace>();\n const views = new Map<Dialect, Map<Vault, Workspace>>();\n // Links into folders that discovery does not enter are checked on disk.\n const lookup = unlistedLookup(set);\n function index(dialect: Dialect): Workspace {\n let workspace = indexes.get(dialect);\n if (workspace) return workspace;\n // The vault that the root is a folder of, if it is one: links may lead out of sight.\n const enclosing = set.vaults.find((item) => item.rootInVault);\n workspace = createWorkspace(set.files, {\n dialect,\n directories: set.directories,\n // Links into a directory that could not be read are unchecked, not missing.\n unreadable: set.skipped.flatMap((item) =>\n item.type === \"directory\" && item.code !== \"IGNORED\" ? [item.path] : [],\n ),\n // The vault at or above the root holds the whole workspace and needs no entry.\n vaults: set.vaults.flatMap((item) => (item.path ? [item.path] : [])),\n ...(enclosing?.rootInVault ? { rootInVault: enclosing.rootInVault } : {}),\n lookup,\n });\n indexes.set(dialect, workspace);\n return workspace;\n }\n return {\n root: set.root,\n selected,\n skipped: set.skipped,\n read(name) {\n known(name);\n const value = set.files[name];\n if (typeof value === \"string\") return value;\n if (typeof value === \"function\") return value();\n throw new Error(`Not a Markdown document: ${name}`);\n },\n options(name) {\n known(name);\n const vault = supplied.has(name) ? supplied.get(name) : vaultOf(set.vaults, name);\n const assumed = assumedDialect(vault);\n const { dialect } = resolveConfig({\n config: options.config,\n path: name,\n plugins: options.plugins,\n defaultDialect: assumed,\n });\n let workspace = index(dialect);\n // Whether a note may be reflowed is a setting of its own vault.\n if (vault?.strictLineBreaks !== undefined) {\n let ofDialect = views.get(dialect);\n if (!ofDialect) views.set(dialect, (ofDialect = new Map()));\n const shared = workspace;\n workspace = ofDialect.get(vault) ?? { ...shared, strictLineBreaks: vault.strictLineBreaks };\n ofDialect.set(vault, workspace);\n }\n return {\n path: name,\n ...(options.config ? { config: options.config } : {}),\n ...(options.plugins ? { plugins: options.plugins } : {}),\n workspace,\n ...(assumed ? { defaultDialect: assumed } : {}),\n };\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/workspace/discovery.ts"],"names":[],"mappings":"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","sourcesContent":["import { access, stat } from \"node:fs/promises\";\nimport { constants } from \"node:fs\";\nimport path from \"node:path\";\nimport { resolveConfig } from \"../config/resolve.js\";\nimport type { Config, Dialect, Plugin, ProcessOptions, Workspace } from \"../core/types.js\";\nimport { pathError } from \"./access.js\";\nimport { discover, isIgnoredInput, unlistedLookup, vaultFor } from \"./files.js\";\nimport type { Skipped } from \"./files.js\";\nimport { createWorkspace } from \"./index.js\";\nimport { excludeSelection } from \"./selection.js\";\nimport { vaultOf } from \"./vault.js\";\nimport type { Vault } from \"./vault.js\";\n\n/** The options of `discoverWorkspace`. */\nexport interface DiscoverOptions {\n /** The workspace root: the folder whose documents are processed and whose files are link targets. */\n root: string;\n /**\n * Files, folders, and patterns to select, absolute or relative to the root.\n * A folder selects its Markdown files, those named `.md` or `.markdown`; a\n * file is selected whatever its name. An input that names no existing path\n * and holds pattern characters (`*`, `?`, `[`, `{`) is a pattern, with `/`\n * between folders on every system: it selects the Markdown files that it\n * matches, and matching none is an error. Ignored files are not selected,\n * and an ignored file or folder named here is reported in `skipped`.\n * Omitted, every document of the workspace is selected; an empty list\n * selects none.\n */\n inputs?: string[];\n /**\n * Files and folders to leave out of the selection, absolute or relative to\n * the root. They are paths, not patterns, and stay link targets.\n */\n exclude?: string[];\n /** The configuration: its `ignore` and `resolve` settings, and the dialect of each document. */\n config?: Config;\n /** Plugin objects, whose rules and presets the configuration can name. */\n plugins?: Plugin[];\n /**\n * Documents whose text the caller supplies, such as an editor's unsaved\n * text, by path relative to the root. They are selected, and the workspace\n * reads this text instead of a file at that path. One whose path ignore rules\n * leave out is not selected and is reported in `skipped`, as a named file is.\n */\n documents?: Record<string, string>;\n}\n/** The files of a workspace that `discoverWorkspace` found, and how to process them. */\nexport interface WorkspaceDiscovery {\n /** The workspace root's real path. */\n readonly root: string;\n /** The documents to process, as forward-slash paths relative to the root, sorted. */\n readonly selected: readonly string[];\n /** What discovery could not read or use and left out, as in the CLI's JSON report. */\n readonly skipped: readonly Skipped[];\n /**\n * The text of a selected document. A file is read when it is first needed\n * and then kept; an error with the code `EACCES`, `EPERM`, or `EBUSY` means\n * that the system did not let it be read, and one with the code\n * `ERR_ENCODING_INVALID_ENCODED_DATA` that its bytes are not UTF-8 text.\n */\n read(path: string): string;\n /**\n * The options to lint or format a selected document with, as the CLI does:\n * its path, the configuration and plugins, a workspace for its dialect with\n * its vault's line-break setting, and the dialect to assume for a note.\n */\n options(path: string): ProcessOptions;\n}\n\n/**\n * The dialect to assume for a document when nothing names one, or undefined for\n * the library's own default. An Obsidian vault shows a single line break as a\n * line break and holds wikilinks, both of which any other dialect lets reflow\n * change, so a document that a vault holds is read as a note.\n */\nexport function assumedDialect(vault: Vault | string | undefined): Dialect | undefined {\n return vault ? \"obsidian\" : undefined;\n}\n/**\n * Stops when `given` cannot serve as a workspace root, with a message that\n * names it. Without this check the first thing to fail could be a search\n * inside it, with a message about a path the user never named.\n */\nexport async function checkRoot(given: string): Promise<void> {\n const root = path.resolve(given);\n try {\n if (!(await stat(root)).isDirectory())\n throw new Error(`Workspace root is not a directory: ${root}`);\n await access(root, constants.R_OK | constants.X_OK);\n } catch (error) {\n throw pathError(\"Workspace root\", root, error);\n }\n}\n/**\n * Find the files of a workspace and select documents the way the CLI does:\n * honoring Git's ignore rules and the configuration's `ignore` and `resolve`\n * settings, skipping links, nested repositories, and what the system does not\n * let the tool read, and recognizing each document's Obsidian vault. Nothing\n * is written.\n */\nexport function discoverWorkspace(options: DiscoverOptions): Promise<WorkspaceDiscovery> {\n return discoverFrom(options, path.resolve(options.root));\n}\n/**\n * The same, with relative inputs and exclusions resolved from `base`. Without\n * `ignoreDocuments`, ignore rules do not leave out the supplied documents, as\n * for text from stdin without a path of its own.\n */\nexport async function discoverFrom(\n options: DiscoverOptions,\n base: string,\n ignoreDocuments = true,\n): Promise<WorkspaceDiscovery> {\n await checkRoot(options.root);\n // Which files to leave out does not depend on a document's dialect.\n const resolved = resolveConfig({ config: options.config, plugins: options.plugins });\n const documents = new Map<string, string>();\n for (const [given, text] of Object.entries(options.documents ?? {})) {\n const name = path.posix.normalize(given.split(path.sep).join(\"/\"));\n if (name === \".\" || name === \"..\" || name.startsWith(\"../\") || path.isAbsolute(given))\n throw new Error(`Document path is outside the workspace: ${given}`);\n documents.set(name, text);\n }\n // Discovery always visits the whole workspace, whose files are link targets;\n // the inputs only choose what is selected.\n const none = options.inputs?.length === 0;\n const set = await discover(\n options.root,\n none ? [] : (options.inputs ?? []),\n resolved.ignore,\n resolved.resolve,\n base,\n { documents: ignoreDocuments ? [...documents.keys()] : [], selectNone: none },\n );\n set.selected = none\n ? []\n : await excludeSelection(set.root, set.selected, options.exclude ?? [], base);\n // A supplied document that ignore rules leave out is reported as a named\n // file is, and not selected.\n const ignored = new Set(\n set.skipped.flatMap((item) =>\n item.type === \"file\" && isIgnoredInput(item) ? [item.path] : [],\n ),\n );\n // A supplied document can lie in a folder that discovery did not visit, so\n // its vault is found by a search of its own.\n const supplied = new Map<string, Vault | undefined>();\n for (const [name, text] of documents) {\n set.files[name] = text;\n if (!ignored.has(name)) supplied.set(name, undefined);\n }\n for (const name of supplied.keys()) supplied.set(name, await vaultFor(set, name));\n const selected = [...new Set([...set.selected, ...supplied.keys()])].sort();\n const chosen = new Set(selected);\n const known = (name: string) => {\n if (!chosen.has(name)) throw new Error(`Not a selected document: ${name}`);\n };\n // One index per active dialect, built when a document of the dialect is first\n // processed, and a view of it with each vault's line-break setting.\n const indexes = new Map<Dialect, Workspace>();\n const views = new Map<Dialect, Map<Vault, Workspace>>();\n // Links into folders that discovery does not enter are checked on disk.\n const lookup = unlistedLookup(set);\n function index(dialect: Dialect): Workspace {\n let workspace = indexes.get(dialect);\n if (workspace) return workspace;\n // The vault that the root is a folder of, if it is one: links may lead out of sight.\n const enclosing = set.vaults.find((item) => item.rootInVault);\n workspace = createWorkspace(set.files, {\n dialect,\n directories: set.directories,\n // Links into a directory that could not be read are unchecked, not missing.\n unreadable: set.skipped.flatMap((item) =>\n item.type === \"directory\" && item.code !== \"IGNORED\" ? [item.path] : [],\n ),\n // The vault at or above the root holds the whole workspace and needs no entry.\n vaults: set.vaults.flatMap((item) => (item.path ? [item.path] : [])),\n ...(enclosing?.rootInVault ? { rootInVault: enclosing.rootInVault } : {}),\n lookup,\n });\n indexes.set(dialect, workspace);\n return workspace;\n }\n return {\n root: set.root,\n selected,\n skipped: set.skipped,\n read(name) {\n known(name);\n const value = set.files[name];\n if (typeof value === \"string\") return value;\n if (typeof value === \"function\") return value();\n throw new Error(`Not a Markdown document: ${name}`);\n },\n options(name) {\n known(name);\n const vault = supplied.has(name) ? supplied.get(name) : vaultOf(set.vaults, name);\n const assumed = assumedDialect(vault);\n const { dialect } = resolveConfig({\n config: options.config,\n path: name,\n plugins: options.plugins,\n defaultDialect: assumed,\n });\n let workspace = index(dialect);\n // Whether a note may be reflowed is a setting of its own vault.\n if (vault?.strictLineBreaks !== undefined) {\n let ofDialect = views.get(dialect);\n if (!ofDialect) views.set(dialect, (ofDialect = new Map()));\n const shared = workspace;\n workspace = ofDialect.get(vault) ?? { ...shared, strictLineBreaks: vault.strictLineBreaks };\n ofDialect.set(vault, workspace);\n }\n return {\n path: name,\n ...(options.config ? { config: options.config } : {}),\n ...(options.plugins ? { plugins: options.plugins } : {}),\n workspace,\n ...(assumed ? { defaultDialect: assumed } : {}),\n };\n },\n };\n}\n"]}
|
|
@@ -24,6 +24,13 @@ type PathText = (file: string) => string;
|
|
|
24
24
|
* names them from the current directory.
|
|
25
25
|
*/
|
|
26
26
|
export declare function skippedMessage(item: Skipped, show: PathText): string;
|
|
27
|
+
/**
|
|
28
|
+
* Whether a skipped entry reports a named file or folder that discovery leaves
|
|
29
|
+
* out as ignored, a warning that the CLI's `--no-warn-ignored` leaves out. An
|
|
30
|
+
* ignored workspace root is not one: no path named it, and none of its files is
|
|
31
|
+
* processed.
|
|
32
|
+
*/
|
|
33
|
+
export declare function isIgnoredInput(item: Skipped): boolean;
|
|
27
34
|
export interface FileSet {
|
|
28
35
|
root: string;
|
|
29
36
|
files: Record<string, WorkspaceSource>;
|
|
@@ -69,8 +76,17 @@ export declare function vaultFor(set: FileSet, name: string): Promise<Vault | un
|
|
|
69
76
|
* name (`isMarkdownName`), that nothing ignores. A file named on its own is
|
|
70
77
|
* selected whatever its name: naming it is the request. When ignore rules
|
|
71
78
|
* leave out a named file or folder, `skipped` says so and why.
|
|
79
|
+
*
|
|
80
|
+
* `documents` are paths from the root of documents whose text a caller
|
|
81
|
+
* supplies, which need not exist. They are not selected here, but ignore rules
|
|
82
|
+
* leave them out as named files, as far as their folders exist. With
|
|
83
|
+
* `selectNone`, the caller selects nothing from disk, and an ignored root is
|
|
84
|
+
* not reported.
|
|
72
85
|
*/
|
|
73
|
-
export declare function discover(rootPath: string, supplied: string[], excluded: string[], resolve?: ResolveOptions, base?: string
|
|
86
|
+
export declare function discover(rootPath: string, supplied: string[], excluded: string[], resolve?: ResolveOptions, base?: string, { documents, selectNone }?: {
|
|
87
|
+
documents?: string[];
|
|
88
|
+
selectNone?: boolean;
|
|
89
|
+
}): Promise<FileSet>;
|
|
74
90
|
/**
|
|
75
91
|
* A file that `writeFormatted` would not write, or did not write completely,
|
|
76
92
|
* with the reason in words that do not name it.
|
package/dist/workspace/files.js
CHANGED
|
@@ -25,6 +25,17 @@ function skip(entry, words) {
|
|
|
25
25
|
export function skippedMessage(item, show) {
|
|
26
26
|
return wording.get(item)?.(show) ?? item.message;
|
|
27
27
|
}
|
|
28
|
+
/** The skipped entries of named inputs that ignore rules leave out. */
|
|
29
|
+
const ignoredInputs = new WeakSet();
|
|
30
|
+
/**
|
|
31
|
+
* Whether a skipped entry reports a named file or folder that discovery leaves
|
|
32
|
+
* out as ignored, a warning that the CLI's `--no-warn-ignored` leaves out. An
|
|
33
|
+
* ignored workspace root is not one: no path named it, and none of its files is
|
|
34
|
+
* processed.
|
|
35
|
+
*/
|
|
36
|
+
export function isIgnoredInput(item) {
|
|
37
|
+
return ignoredInputs.has(item);
|
|
38
|
+
}
|
|
28
39
|
/** A file of ignore rules as a message shows it: one outside the repository by its full path. */
|
|
29
40
|
function showFile(show, file) {
|
|
30
41
|
return path.isAbsolute(file) ? file : show(file);
|
|
@@ -359,8 +370,14 @@ function gitRules(root, top) {
|
|
|
359
370
|
* name (`isMarkdownName`), that nothing ignores. A file named on its own is
|
|
360
371
|
* selected whatever its name: naming it is the request. When ignore rules
|
|
361
372
|
* leave out a named file or folder, `skipped` says so and why.
|
|
373
|
+
*
|
|
374
|
+
* `documents` are paths from the root of documents whose text a caller
|
|
375
|
+
* supplies, which need not exist. They are not selected here, but ignore rules
|
|
376
|
+
* leave them out as named files, as far as their folders exist. With
|
|
377
|
+
* `selectNone`, the caller selects nothing from disk, and an ignored root is
|
|
378
|
+
* not reported.
|
|
362
379
|
*/
|
|
363
|
-
export async function discover(rootPath, supplied, excluded, resolve = {}, base = process.cwd()) {
|
|
380
|
+
export async function discover(rootPath, supplied, excluded, resolve = {}, base = process.cwd(), { documents = [], selectNone = false } = {}) {
|
|
364
381
|
const root = await realpath(rootPath);
|
|
365
382
|
let top;
|
|
366
383
|
try {
|
|
@@ -435,6 +452,26 @@ export async function discover(rootPath, supplied, excluded, resolve = {}, base
|
|
|
435
452
|
named.set(at, [request]);
|
|
436
453
|
}
|
|
437
454
|
}
|
|
455
|
+
/** The supplied documents, which ignore rules leave out as they do named files. */
|
|
456
|
+
const documentInputs = [];
|
|
457
|
+
for (const name of documents) {
|
|
458
|
+
const existing = namedInputs.get(name);
|
|
459
|
+
if (existing) {
|
|
460
|
+
if (!existing.directory)
|
|
461
|
+
documentInputs.push(existing);
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
const request = { path: name, directory: false, document: true };
|
|
465
|
+
namedInputs.set(name, request);
|
|
466
|
+
documentInputs.push(request);
|
|
467
|
+
for (let at = name; at !== "."; at = path.posix.dirname(at)) {
|
|
468
|
+
const list = named.get(at);
|
|
469
|
+
if (list)
|
|
470
|
+
list.push(request);
|
|
471
|
+
else
|
|
472
|
+
named.set(at, [request]);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
438
475
|
/**
|
|
439
476
|
* Record why the entry `name` leaves out the named inputs at or below it for
|
|
440
477
|
* which `applies` holds, unless something above it did already.
|
|
@@ -558,8 +595,10 @@ export async function discover(rootPath, supplied, excluded, resolve = {}, base
|
|
|
558
595
|
leaveOut(name, name, error);
|
|
559
596
|
continue;
|
|
560
597
|
}
|
|
598
|
+
// A nested repository is another project, not one that a rule leaves
|
|
599
|
+
// alone: text supplied for one of its documents is read as its own.
|
|
561
600
|
if (nested)
|
|
562
|
-
leaveNamed(name, (input) => (show) => input.path === name ? "a nested repository" : `in ${show(name)}, a nested repository
|
|
601
|
+
leaveNamed(name, (input) => (show) => input.path === name ? "a nested repository" : `in ${show(name)}, a nested repository`, (input) => !input.document);
|
|
563
602
|
if (nested && resolve.nestedRepositories !== true)
|
|
564
603
|
continue;
|
|
565
604
|
// Inside a nested repository, only its own rules and settings apply, as in Git.
|
|
@@ -588,7 +627,7 @@ export async function discover(rootPath, supplied, excluded, resolve = {}, base
|
|
|
588
627
|
request.reason ??= () => `matched by the configuration's ignore pattern "${pattern}"`;
|
|
589
628
|
continue;
|
|
590
629
|
}
|
|
591
|
-
let chosen = request !== undefined;
|
|
630
|
+
let chosen = request !== undefined && !request.document;
|
|
592
631
|
if (markdown) {
|
|
593
632
|
chosen ||= requests.some((folder) => !folder || name === folder || name.startsWith(`${folder}/`));
|
|
594
633
|
for (const item of patterns)
|
|
@@ -599,8 +638,42 @@ export async function discover(rootPath, supplied, excluded, resolve = {}, base
|
|
|
599
638
|
selected.push(name);
|
|
600
639
|
}
|
|
601
640
|
}
|
|
641
|
+
// A supplied document that no file holds yet is left out by the rules of
|
|
642
|
+
// its folder as a file there would be.
|
|
643
|
+
for (const request of documentInputs) {
|
|
644
|
+
if (request.reason || request.path in files)
|
|
645
|
+
continue;
|
|
646
|
+
if (path.posix.dirname(request.path) !== (relative || "."))
|
|
647
|
+
continue;
|
|
648
|
+
const entry = path.posix.basename(request.path);
|
|
649
|
+
if (alwaysSkipped.has(entry))
|
|
650
|
+
request.reason = () => `discovery always leaves out ${entry}`;
|
|
651
|
+
const rule = gitIgnoredAbove ? undefined : gitIgnoreRule(scopes, entry, false);
|
|
652
|
+
if (rule && untracked(request))
|
|
653
|
+
request.reason ??= (show) => `matched by "${rule.pattern}" in ${showFile(show, rule.file)}`;
|
|
654
|
+
}
|
|
602
655
|
}
|
|
603
656
|
await walk("", rootLayers, rootIgnoreCase, false, ignoredRoot !== undefined);
|
|
657
|
+
// The names that discovery always leaves out, and the configuration's
|
|
658
|
+
// patterns, apply to a supplied document's path also where no folder of it
|
|
659
|
+
// exists.
|
|
660
|
+
for (const request of documentInputs) {
|
|
661
|
+
if (request.reason)
|
|
662
|
+
continue;
|
|
663
|
+
const parts = request.path.split("/");
|
|
664
|
+
const at = parts.findIndex((part) => alwaysSkipped.has(part));
|
|
665
|
+
if (at >= 0) {
|
|
666
|
+
const folder = parts.slice(0, at + 1).join("/");
|
|
667
|
+
request.reason =
|
|
668
|
+
folder === request.path
|
|
669
|
+
? () => `discovery always leaves out ${parts[at]}`
|
|
670
|
+
: (show) => `in ${show(folder)}, which discovery always leaves out`;
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
const pattern = ignorePatterns.find((item) => item.matcher.match(request.path))?.pattern;
|
|
674
|
+
if (pattern !== undefined)
|
|
675
|
+
request.reason = () => `matched by the configuration's ignore pattern "${pattern}"`;
|
|
676
|
+
}
|
|
604
677
|
// Nothing that Git does not track is processed in a root that Git ignores.
|
|
605
678
|
let rootIgnored;
|
|
606
679
|
if (ignoredRoot) {
|
|
@@ -613,7 +686,7 @@ export async function discover(rootPath, supplied, excluded, resolve = {}, base
|
|
|
613
686
|
for (const input of namedInputs.values())
|
|
614
687
|
if (untracked(input))
|
|
615
688
|
input.reason ??= folder ? why : (show) => `in the workspace root, which is ${why(show)}`;
|
|
616
|
-
if (!namedInputs.size && !isTracked(lead))
|
|
689
|
+
if (!namedInputs.size && !selectNone && !isTracked(lead))
|
|
617
690
|
rootIgnored = why;
|
|
618
691
|
}
|
|
619
692
|
// An input in a directory that was left out is an error: the user asked for it.
|
|
@@ -635,8 +708,11 @@ export async function discover(rootPath, supplied, excluded, resolve = {}, base
|
|
|
635
708
|
// A named input that ignore rules leave out is not an error, but the user asked for it.
|
|
636
709
|
for (const input of namedInputs.values()) {
|
|
637
710
|
const why = input.reason;
|
|
638
|
-
if (why)
|
|
639
|
-
|
|
711
|
+
if (!why)
|
|
712
|
+
continue;
|
|
713
|
+
const item = skip({ path: input.path, type: input.directory ? "directory" : "file", code: "IGNORED" }, (show) => `Skipped ignored input: ${show(input.path)} (${why(show)}). ${input.directory ? "Its files are" : "It is"} not processed.`);
|
|
714
|
+
ignoredInputs.add(item);
|
|
715
|
+
skipped.push(item);
|
|
640
716
|
}
|
|
641
717
|
// A root that is no vault itself can be a folder of one.
|
|
642
718
|
if (!vaultFolders.has("")) {
|