mdrefine 0.2.0-rc.2 → 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 +577 -0
- package/CODE_OF_CONDUCT.md +30 -0
- package/CONTRIBUTING.md +189 -16
- package/README.md +100 -154
- package/SECURITY.md +15 -13
- package/config.schema.json +91 -0
- 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 +270 -150
- package/dist/cli/main.js.map +1 -1
- package/dist/config/load.js +67 -6
- package/dist/config/load.js.map +1 -1
- package/dist/config/problems.d.ts +7 -0
- package/dist/config/problems.js +208 -0
- package/dist/config/problems.js.map +1 -0
- package/dist/config/resolve.d.ts +42 -109
- package/dist/config/resolve.js +193 -52
- package/dist/config/resolve.js.map +1 -1
- package/dist/core/directives.d.ts +7 -0
- package/dist/core/directives.js +25 -13
- package/dist/core/directives.js.map +1 -1
- package/dist/core/engine.d.ts +42 -4
- package/dist/core/engine.js +518 -79
- package/dist/core/engine.js.map +1 -1
- package/dist/core/frozen.d.ts +6 -0
- package/dist/core/frozen.js +15 -0
- package/dist/core/frozen.js.map +1 -0
- package/dist/core/scope.d.ts +30 -0
- package/dist/core/scope.js +122 -0
- package/dist/core/scope.js.map +1 -0
- package/dist/core/types.d.ts +202 -23
- package/dist/core/types.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +3 -0
- package/dist/node.js +4 -0
- package/dist/node.js.map +1 -0
- package/dist/rules/blocks.d.ts +34 -0
- package/dist/rules/blocks.js +550 -0
- package/dist/rules/blocks.js.map +1 -0
- package/dist/rules/content.d.ts +2 -0
- package/dist/rules/content.js +85 -0
- package/dist/rules/content.js.map +1 -0
- package/dist/rules/dialects.js +10 -6
- package/dist/rules/dialects.js.map +1 -1
- package/dist/rules/links.js +302 -6
- package/dist/rules/links.js.map +1 -1
- package/dist/rules/lists.d.ts +2 -0
- package/dist/rules/lists.js +924 -0
- package/dist/rules/lists.js.map +1 -0
- package/dist/rules/structure.js +127 -1
- package/dist/rules/structure.js.map +1 -1
- package/dist/rules/style.d.ts +8 -2
- package/dist/rules/style.js +1137 -165
- package/dist/rules/style.js.map +1 -1
- package/dist/syntax/block-identifier.d.ts +18 -0
- package/dist/syntax/block-identifier.js +24 -0
- package/dist/syntax/block-identifier.js.map +1 -0
- package/dist/syntax/obsidian.d.ts +29 -11
- package/dist/syntax/obsidian.js +9 -10
- package/dist/syntax/obsidian.js.map +1 -1
- package/dist/syntax/parse.d.ts +87 -3
- package/dist/syntax/parse.js +247 -19
- package/dist/syntax/parse.js.map +1 -1
- package/dist/workspace/access.d.ts +12 -3
- package/dist/workspace/access.js +33 -5
- package/dist/workspace/access.js.map +1 -1
- package/dist/workspace/discovery.d.ts +85 -0
- package/dist/workspace/discovery.js +155 -0
- package/dist/workspace/discovery.js.map +1 -0
- package/dist/workspace/encoding.d.ts +24 -0
- package/dist/workspace/encoding.js +113 -0
- package/dist/workspace/encoding.js.map +1 -0
- package/dist/workspace/files.d.ts +92 -6
- package/dist/workspace/files.js +715 -59
- package/dist/workspace/files.js.map +1 -1
- package/dist/workspace/git.d.ts +54 -0
- package/dist/workspace/git.js +198 -0
- package/dist/workspace/git.js.map +1 -0
- package/dist/workspace/index.d.ts +58 -3
- package/dist/workspace/index.js +136 -37
- package/dist/workspace/index.js.map +1 -1
- package/dist/workspace/selection.d.ts +2 -2
- package/dist/workspace/selection.js +3 -3
- package/dist/workspace/selection.js.map +1 -1
- package/dist/workspace/slug.js +8 -1
- package/dist/workspace/slug.js.map +1 -1
- package/dist/workspace/vault.js +3 -1
- package/dist/workspace/vault.js.map +1 -1
- package/docs/api.md +239 -0
- package/docs/benchmarks.md +140 -200
- package/docs/changelog.md +1 -0
- package/docs/cli.md +281 -89
- package/docs/configuration.md +193 -480
- package/docs/design.md +163 -284
- package/docs/dialects.md +169 -0
- package/docs/examples.md +151 -109
- package/docs/index.md +21 -37
- package/docs/integrations.md +144 -0
- package/docs/obsidian.md +91 -115
- package/docs/plugins.md +147 -212
- package/docs/quick-start.md +68 -74
- package/docs/rules.md +1237 -0
- package/docs/versions.md +56 -0
- package/examples/README.md +17 -14
- package/examples/automation/summarize-report.mjs +5 -2
- package/examples/library/lint-folder.mjs +5 -4
- package/examples/library/lint-workspace.mjs +32 -0
- package/examples/plugin/notes/release-checklist.md +2 -2
- package/examples/plugin/rules.mjs +17 -23
- package/examples/workspaces/obsidian-vault/after/mdtools.config.jsonc +2 -2
- package/examples/workspaces/obsidian-vault/before/mdtools.config.jsonc +2 -2
- package/examples/workspaces/repository-docs/after/README.md +2 -2
- package/examples/workspaces/repository-docs/after/mdtools.config.jsonc +0 -2
- package/examples/workspaces/repository-docs/before/README.md +2 -2
- package/examples/workspaces/repository-docs/before/docs/guide.md +7 -7
- package/examples/workspaces/repository-docs/before/docs/reference.md +3 -1
- package/examples/workspaces/repository-docs/before/mdtools.config.jsonc +0 -2
- package/package.json +38 -12
- package/docs/contributing.md +0 -218
- package/docs/releases/0.1.0-beta.1.md +0 -39
- package/docs/releases/0.1.0-beta.2.md +0 -83
- package/docs/releases/0.1.0-beta.3.md +0 -61
- package/docs/releases/0.1.0-beta.4.md +0 -50
- package/docs/releases/0.1.0-rc.1.md +0 -99
- package/docs/releases/0.2.0-rc.1.md +0 -141
- package/docs/releases/0.2.0-rc.2.md +0 -225
- package/docs/releases.md +0 -144
- package/docs/testing.md +0 -93
package/docs/plugins.md
CHANGED
|
@@ -1,130 +1,11 @@
|
|
|
1
|
-
# Plugins
|
|
1
|
+
# Plugins
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
A plugin adds rules, presets, and syntax to the built-in ones. It is a
|
|
4
|
+
JavaScript module whose default export is a plugin object. The [plugin
|
|
5
|
+
example](examples.md#write-a-plugin) is a complete plugin with a problem rule
|
|
6
|
+
and a style rule to start from.
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
```ts
|
|
10
|
-
import { createWorkspace, format, lint } from "mdrefine";
|
|
11
|
-
|
|
12
|
-
const files = {
|
|
13
|
-
"note.md": "A *short* note with [a link](target.md).\n",
|
|
14
|
-
"target.md": "# Target\n",
|
|
15
|
-
};
|
|
16
|
-
const options = {
|
|
17
|
-
path: "note.md",
|
|
18
|
-
config: { extends: ["recommended", "github"] },
|
|
19
|
-
workspace: createWorkspace(files, { dialect: "github" }),
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const diagnostics = lint(files["note.md"], options);
|
|
23
|
-
const result = format(files["note.md"], options);
|
|
24
|
-
// result: { output, changed, diagnostics }
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Library calls do not read or write files; the
|
|
28
|
-
[library examples](examples.md#call-the-library) include a script that reads a
|
|
29
|
-
folder and builds the workspace from it. `path` and workspace keys use
|
|
30
|
-
forward-slash paths relative to the workspace root. Use `null` values for
|
|
31
|
-
non-Markdown attachments. A Markdown value can also be a synchronous loader
|
|
32
|
-
`() => string`; `createWorkspace` calls it only when that target needs fragment
|
|
33
|
-
validation, then keeps the target's anchors, not its text or its parsed form.
|
|
34
|
-
The anchors of Obsidian, of GitHub and CommonMark, and of Forgejo and Gitea
|
|
35
|
-
differ, so the loader is called once more when a later link to that target is
|
|
36
|
-
checked for a dialect of another of these groups; `0.2.0-rc.1` and earlier
|
|
37
|
-
called it at most once and kept the parsed target. Callers must provide a stable
|
|
38
|
-
source snapshot for each workspace instance. The CLI provides its own
|
|
39
|
-
per-invocation file loaders. `WorkspaceOptions.directories` can list existing
|
|
40
|
-
empty directories; parent directories of file entries are inferred. Resolution
|
|
41
|
-
returns `status: "directory"` for a directory instead of `"missing"`.
|
|
42
|
-
|
|
43
|
-
`WorkspaceOptions.unreadable` lists existing directories whose contents the host
|
|
44
|
-
could not read. A link that leads into one resolves with `status: "unreadable"`
|
|
45
|
-
instead of `"missing"`, and the result's `unreadable` field names the
|
|
46
|
-
directories that stand in the way. In the Obsidian dialect, a search by note
|
|
47
|
-
name that finds nothing also resolves as `"unreadable"`. One that finds a single
|
|
48
|
-
note is `"resolved"` with `unreadable` set, because those directories may hold
|
|
49
|
-
another note of that name. The same marks a note found beside the linking note
|
|
50
|
-
when the vault-root path of that spelling leads into an unreadable directory,
|
|
51
|
-
where Obsidian would look first. A rule must not rewrite such a link:
|
|
52
|
-
`links/path` and `links/notation` leave it as written, and the engine refuses an
|
|
53
|
-
edit by any rule that changes its destination, because only the same spelling is
|
|
54
|
-
known to name the same note. The CLI passes the directories it
|
|
55
|
-
[skipped](configuration.md#paths-that-cannot-be-read). A loader that throws an
|
|
56
|
-
error whose `code` is `EACCES` or `EPERM`, as Node.js does for a file the
|
|
57
|
-
process is not permitted to read, makes a link to a heading or block in that
|
|
58
|
-
file resolve as `"unreadable"` too, with `target` set and `unreadable` naming
|
|
59
|
-
the file; a link to the file without a fragment still resolves. Any other error
|
|
60
|
-
from a loader propagates. Versions up to `0.2.0-rc.1` have neither the option
|
|
61
|
-
nor the status, and let every loader error through.
|
|
62
|
-
|
|
63
|
-
`WorkspaceOptions.vaults` lists folders below the workspace root that are
|
|
64
|
-
Obsidian vaults of their own. An Obsidian link in a note of such a vault is
|
|
65
|
-
resolved within it: a path is counted from the vault's folder, and a search by
|
|
66
|
-
name looks only at the vault's files. The result's `vault` field then names the
|
|
67
|
-
folder, also when nothing was found; `target` stays relative to the workspace
|
|
68
|
-
root, so a rule that writes a path from the vault removes that prefix. A path
|
|
69
|
-
that names an existing file or folder outside the vault resolves as `"missing"`
|
|
70
|
-
with that file in `outside`. Notes outside the listed folders, and all other
|
|
71
|
-
dialects, resolve as without the option. The CLI passes the vaults it found
|
|
72
|
-
below the root. Versions up to `0.2.0-rc.1` have neither the option nor the
|
|
73
|
-
fields.
|
|
74
|
-
|
|
75
|
-
`WorkspaceOptions.rootInVault` is for a workspace whose root is a folder of an
|
|
76
|
-
Obsidian vault: the root's path from the vault's folder, such as
|
|
77
|
-
`Projects/Notes`. The rest of the vault is then out of sight, and an Obsidian
|
|
78
|
-
link whose target could lie there resolves as `"unreadable"`, not `"missing"`. A
|
|
79
|
-
target found by a search, or in a place that Obsidian tries after one outside
|
|
80
|
-
the root, is `"resolved"` with `unreadable` set and so is not rewritten. In both
|
|
81
|
-
cases the `unreadable` list holds an entry that starts with `..`, the way from
|
|
82
|
-
the root to the vault's folder. A resolved link also carries `rooted`, the
|
|
83
|
-
target's path from the folder of its vault, whenever that differs from `target`;
|
|
84
|
-
this applies to a vault listed in `vaults` as well. The CLI passes the option
|
|
85
|
-
when it finds the vault above the root. Versions up to `0.2.0-rc.1` do not have
|
|
86
|
-
it.
|
|
87
|
-
|
|
88
|
-
Without a workspace, local target checks and path rewriting are unavailable.
|
|
89
|
-
Obsidian reflow also requires an explicit `workspace.strictLineBreaks: true`;
|
|
90
|
-
callers are responsible for verifying that renderer setting.
|
|
91
|
-
|
|
92
|
-
A document is read in the dialect its configuration names: through `dialect`, a
|
|
93
|
-
preset, or a matching override. When none of them does, including when `config`
|
|
94
|
-
is omitted, `lint`, `format`, and `resolveConfig` assume `github`, and
|
|
95
|
-
`createWorkspace` parses link targets as `github` when
|
|
96
|
-
`WorkspaceOptions.dialect` is omitted. Version 0.2.0-rc.1 and earlier assumed
|
|
97
|
-
`commonmark` in both places; name it to keep that reading. Give the workspace
|
|
98
|
-
the dialect of the documents that link into it.
|
|
99
|
-
|
|
100
|
-
A host that knows more than the configuration says can change what is assumed,
|
|
101
|
-
without overriding a dialect that the configuration does name. Pass
|
|
102
|
-
`defaultDialect` in the options of `lint` and `format`, or as the fourth
|
|
103
|
-
argument of `resolveConfig(config, path, plugins, defaultDialect)`; it accepts a
|
|
104
|
-
dialect or an alias. The library reads no files and so recognizes nothing by
|
|
105
|
-
itself: the CLI passes `obsidian` for a document that has a `.obsidian` folder
|
|
106
|
-
in its own folder or in one above it, and gives the workspace that vault's
|
|
107
|
-
`strictLineBreaks` setting. A host for vaults should do the same.
|
|
108
|
-
|
|
109
|
-
```ts
|
|
110
|
-
// The host found `.obsidian/` at the root of the folder it read `files` from.
|
|
111
|
-
const defaultDialect = "obsidian";
|
|
112
|
-
const { dialect } = resolveConfig(config, "Note.md", [], defaultDialect);
|
|
113
|
-
const workspace = createWorkspace(files, { dialect, strictLineBreaks });
|
|
114
|
-
const result = format(files["Note.md"], { path: "Note.md", config, workspace, defaultDialect });
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
`parse`, `range`, `textContent`, `resolveConfig`, `validateConfig`,
|
|
118
|
-
`configSchema`, `presets`, `dialects`, `dialectAliases`, `canonicalDialect`,
|
|
119
|
-
`builtInRules`, `ruleRegistry`, `applyEdits`, and `semanticFingerprint` are also
|
|
120
|
-
exported. `range(node)` returns `[start, end]` UTF-16 offsets into the original
|
|
121
|
-
source. `configSchema` is the configuration's JSON Schema, and
|
|
122
|
-
`validateConfig(value)` throws when a value does not match it; rule-specific
|
|
123
|
-
schemas live on rule objects. `canonicalDialect(name)` maps an alias such as
|
|
124
|
-
`codeberg` to the dialect that rules and the workspace see (`forgejo`);
|
|
125
|
-
`document.dialect` is always canonical.
|
|
126
|
-
|
|
127
|
-
## Rule plugins
|
|
8
|
+
## A plugin
|
|
128
9
|
|
|
129
10
|
Create `rules.mjs` beside the configuration:
|
|
130
11
|
|
|
@@ -151,7 +32,7 @@ export default {
|
|
|
151
32
|
};
|
|
152
33
|
```
|
|
153
34
|
|
|
154
|
-
|
|
35
|
+
and load it from the configuration:
|
|
155
36
|
|
|
156
37
|
```json
|
|
157
38
|
{
|
|
@@ -160,68 +41,92 @@ Load it with:
|
|
|
160
41
|
}
|
|
161
42
|
```
|
|
162
43
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
`
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
`
|
|
199
|
-
|
|
200
|
-
`
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
`
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
44
|
+
`name` is the plugin's prefix: a lowercase identifier that starts with a letter
|
|
45
|
+
and holds letters, digits, and hyphens, unique among the loaded plugins. Its
|
|
46
|
+
rules are named `name/rule` and its presets `name/preset`; a rule ID that
|
|
47
|
+
already exists is an error. The name `engine` is reserved for the engine's own
|
|
48
|
+
diagnostics, such as `engine/rule-error`, and a plugin with that name is
|
|
49
|
+
rejected. A plugin can also provide `syntax`, described
|
|
50
|
+
[below](#syntax-extensions). The library takes plugin objects through its
|
|
51
|
+
`plugins` option; a list of strings in a library configuration loads nothing.
|
|
52
|
+
|
|
53
|
+
## Loading
|
|
54
|
+
|
|
55
|
+
The configuration's `plugins` entries are loaded when the configuration is, and
|
|
56
|
+
run with the caller's permissions; plugins are not sandboxed. An entry that
|
|
57
|
+
starts with `.` or is an absolute path names a file, relative to the
|
|
58
|
+
configuration file's directory. Any other entry is resolved as Node.js resolves
|
|
59
|
+
an `import` written in the configuration file, such as `sample-plugin`,
|
|
60
|
+
`@scope/sample-plugin`, or `sample-plugin/rules`. The package's entry can be an
|
|
61
|
+
`import` or `default` condition of its `exports`, or its `main` file. When the
|
|
62
|
+
import finds no entry, Node.js's CommonJS rules are tried, so a package that
|
|
63
|
+
declares only a `require` condition loads as well; a package with both is loaded
|
|
64
|
+
through `import`. The lookup applies the conditions that Node.js applies: its
|
|
65
|
+
defaults, those added with `--conditions` or `-C`, and without `node-addons`
|
|
66
|
+
under `--no-addons`, from Node.js's command line or `NODE_OPTIONS`. Options set
|
|
67
|
+
in other ways, such as a Node.js configuration file, are not seen.
|
|
68
|
+
|
|
69
|
+
A plugin that cannot be loaded stops the command with exit code `2` and the
|
|
70
|
+
message `Cannot load plugin "NAME" named in FILE: REASON`, where `REASON` is
|
|
71
|
+
Node.js's own, such as `Cannot find package 'sample-plugin' imported from FILE`,
|
|
72
|
+
or `no file at PATH` for a file entry. A module whose default export is not an
|
|
73
|
+
object with a string `name`, or whose plugin is named `engine`, gives
|
|
74
|
+
`Invalid plugin "NAME" named in FILE`.
|
|
75
|
+
|
|
76
|
+
## Rules
|
|
77
|
+
|
|
78
|
+
A rule is an object with a `description`, a `kind`, an optional `schema` and
|
|
79
|
+
`phase`, and a `check` function.
|
|
80
|
+
|
|
81
|
+
- `kind` is `"problem"` for a rule that only reports, or `"style"` for one that
|
|
82
|
+
can propose edits, which `format` applies. `format` ignores the edits of
|
|
83
|
+
problem rules.
|
|
84
|
+
- `schema` is a JSON Schema for the rule's options. The options a configuration
|
|
85
|
+
gives are checked against it, and a rule without a schema accepts any options;
|
|
86
|
+
`false` means that the rule takes none. Each schema is compiled once, for all
|
|
87
|
+
documents. Options are checked synchronously, so an asynchronous schema
|
|
88
|
+
(`$async`) is refused.
|
|
89
|
+
- `check({ document, options, workspace })` receives the parsed `document`
|
|
90
|
+
(`source`, `path`, `dialect`, and `tree`, an
|
|
91
|
+
[mdast](https://github.com/syntax-tree/mdast) tree with source positions), the
|
|
92
|
+
configured `options`, and the `workspace`, which is undefined when the host
|
|
93
|
+
gave none. It returns a list of findings.
|
|
94
|
+
|
|
95
|
+
A finding has a `message`, a `start` offset, an optional `end`, and, from a
|
|
96
|
+
style rule, an optional `edit` of the form `{ start, end, text }`. Offsets are
|
|
97
|
+
integers that count UTF-16 code units in `document.source`, from 0 to its
|
|
98
|
+
length, with `end` not before `start`; `range(node)` from the library gives a
|
|
99
|
+
node's offsets. `check` must be synchronous, must not change the document or
|
|
100
|
+
perform I/O, and must not keep state between calls.
|
|
101
|
+
|
|
102
|
+
A rule that throws, or returns anything other than a list of such findings, is
|
|
103
|
+
skipped for that document and reported with an `engine/rule-error` error that
|
|
104
|
+
names it and the reason, as in
|
|
105
|
+
`Rule local/example failed (TypeError: …); the rule was skipped for this document.`
|
|
106
|
+
The other rules and documents are still processed, and the CLI's exit code is
|
|
107
|
+
`2`. An error that the workspace throws, such as a file that cannot be read, is
|
|
108
|
+
not the rule's: `lint` and `format` throw it, and the CLI stops. A plugin rule
|
|
109
|
+
without a `check` function, a `kind` of `"style"` or `"problem"`, and a string
|
|
110
|
+
`description`, or with a `phase` or `schema` of another kind, stops the command
|
|
111
|
+
with an error that names it, even while the rule is off.
|
|
112
|
+
|
|
113
|
+
A style rule's `phase` is `"inline"` (the default), `"block"`, or `"document"`.
|
|
114
|
+
`format` runs the phases in that order and repeats them until nothing changes,
|
|
115
|
+
for at most eight passes. After each phase that changes the document, it parses
|
|
116
|
+
the result again and compares its meaning with the original's; the [safety
|
|
117
|
+
model](design.md#safety-model) describes what happens to edits that overlap or
|
|
118
|
+
would change the meaning. The comparison covers the syntax tree, not every
|
|
119
|
+
application's rendering, and it constrains the text a rule returns, not what its
|
|
120
|
+
code does.
|
|
216
121
|
|
|
217
122
|
## Resolve links in a rule
|
|
218
123
|
|
|
219
124
|
A rule asks the workspace where a link leads with
|
|
220
|
-
`workspace.resolve(source, destination, dialect)`: `source` is the
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
125
|
+
`workspace.resolve(source, destination, dialect, wiki)`: `source` is the linking
|
|
126
|
+
document's path (`document.path`), `destination` the destination as written,
|
|
127
|
+
`dialect` the document's dialect, and `wiki` true for a wikilink, which never
|
|
128
|
+
resolves to a directory. The built-in link rules use the same call, so a plugin
|
|
129
|
+
sees links as they do. Without a workspace, a rule should report nothing.
|
|
225
130
|
|
|
226
131
|
```js
|
|
227
132
|
export default {
|
|
@@ -259,7 +164,7 @@ The result's `status` is one of:
|
|
|
259
164
|
| `resolved` | One file matches. `target` is its path from the workspace root. |
|
|
260
165
|
| `missing` | No file matches. |
|
|
261
166
|
| `ambiguous` | Several files match and nothing decides between them, such as two notes whose names differ only by case. |
|
|
262
|
-
| `directory` | The destination names a
|
|
167
|
+
| `directory` | The destination names a directory. |
|
|
263
168
|
| `external` | The destination is a URL with a scheme, or starts with `//`. It is not checked. |
|
|
264
169
|
| `unavailable` | The destination cannot be judged from files: it starts with `/` or holds a `?`, outside the Obsidian dialect. |
|
|
265
170
|
| `unreadable` | The target could not be checked, because something that may hold it could not be read. |
|
|
@@ -267,33 +172,63 @@ The result's `status` is one of:
|
|
|
267
172
|
The other fields are present when they apply:
|
|
268
173
|
|
|
269
174
|
- `target`: the file found, with status `resolved`, and with `unreadable` when
|
|
270
|
-
the file
|
|
271
|
-
- `fragment`: the part after `#`, decoded, or an empty string
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
- `unreadable`:
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
the
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
175
|
+
the file exists but its content could not be read.
|
|
176
|
+
- `fragment`: the part after `#`, decoded, or an empty string; `fragmentExists`:
|
|
177
|
+
with status `resolved`, whether the target has that heading, block, or anchor
|
|
178
|
+
by the rules of `dialect`, and `true` without a fragment and for a target that
|
|
179
|
+
is not Markdown, whose fragments are its own.
|
|
180
|
+
- `unreadable`: the directories or the file that stood in the way. An entry that
|
|
181
|
+
starts with `..` stands for the part of a vault outside the workspace root. In
|
|
182
|
+
the Obsidian dialect, a search by note name that finds nothing readable
|
|
183
|
+
resolves as `unreadable`, and one that finds a single note resolves with
|
|
184
|
+
`unreadable` set, because the unreadable directories may hold another note of
|
|
185
|
+
that name. **A rule must not rewrite a `resolved` link whose `unreadable` is
|
|
186
|
+
set**, and the engine refuses an edit that changes such a link's destination.
|
|
187
|
+
- `vault`: the vault's directory, when an Obsidian link was resolved in a vault
|
|
188
|
+
below the workspace root. `rooted`: the target's vault-relative path, when it
|
|
189
|
+
differs from `target`. `outside`: with status `missing`, an existing file that
|
|
190
|
+
the path names outside the vault. `leavesRoot`: with status `unreadable`, the
|
|
191
|
+
symbolic link or junction through which the path leads outside the workspace
|
|
192
|
+
root.
|
|
193
|
+
|
|
194
|
+
A style rule that rewrites a destination should resolve the new destination as
|
|
195
|
+
well, and propose the edit only when both lead to the same `target`. The engine
|
|
196
|
+
checks this again and refuses an edit that changes where a link leads.
|
|
284
197
|
|
|
285
198
|
## Syntax extensions
|
|
286
199
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
positions
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
200
|
+
A plugin can provide `syntax: { micromark, mdast }`, a tokenizer extension and a
|
|
201
|
+
tree extension from the [micromark](https://github.com/micromark/micromark) and
|
|
202
|
+
mdast ecosystem. In TypeScript, extend mdast's node maps for the new node types.
|
|
203
|
+
Keep source positions on the nodes, and expose the meaning of a node in its
|
|
204
|
+
fields: the semantic comparison looks at every field except `position` and
|
|
205
|
+
`data`. Syntax that a parser reads as plain text is not safe to format merely
|
|
206
|
+
for that; test the extension's meaning and its source preservation.
|
|
207
|
+
|
|
208
|
+
This is not a remark plugin adapter. Existing micromark extensions can be
|
|
209
|
+
reused, but a remark plugin that changes the tree must be rewritten as a rule
|
|
210
|
+
that proposes edits. A plugin cannot replace link resolution; a library host can
|
|
211
|
+
pass its own implementation of the `Workspace` interface.
|
|
212
|
+
|
|
213
|
+
## Test a rule
|
|
214
|
+
|
|
215
|
+
Call the library with the plugin object, and check what it reports and what
|
|
216
|
+
formatting produces:
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
import { format, lint } from "mdrefine";
|
|
220
|
+
import plugin from "./rules.mjs";
|
|
221
|
+
|
|
222
|
+
const options = {
|
|
223
|
+
plugins: [plugin],
|
|
224
|
+
config: { extends: [], rules: { "local/initial-heading": "error" } },
|
|
225
|
+
};
|
|
226
|
+
console.assert(lint("Text.\n", options).length === 1);
|
|
227
|
+
console.assert(lint("# Title\n", options).length === 0);
|
|
228
|
+
const once = format("Text.\n", options).output;
|
|
229
|
+
console.assert(format(once, options).output === once);
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
A correct style rule leaves its own output unchanged when it runs again, and
|
|
233
|
+
changes nothing when it is turned off. Add a case for every construct the rule
|
|
234
|
+
could misread, such as code, HTML, and each dialect it supports.
|
package/docs/quick-start.md
CHANGED
|
@@ -1,97 +1,85 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Getting started
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
writing changes.
|
|
3
|
+
## Install
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
The CLI requires Node.js 22.12 or newer. Install the package in your document
|
|
9
|
-
workspace:
|
|
5
|
+
mdrefine needs Node.js 22.12 or newer. Install the package in the project that
|
|
6
|
+
holds your documents:
|
|
10
7
|
|
|
11
8
|
```sh
|
|
12
|
-
npm install --save-dev --save-exact mdrefine@0.2.0
|
|
9
|
+
npm install --save-dev --save-exact mdrefine@0.2.0
|
|
13
10
|
npx --no-install mdtools --help
|
|
14
11
|
```
|
|
15
12
|
|
|
16
|
-
The
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
The package is `mdrefine`; its command is `mdtools`. `npx` runs the command from
|
|
14
|
+
the project, and `--no-install` stops it from downloading a package when the
|
|
15
|
+
project has none; the examples below leave it out. The exact version lets you
|
|
16
|
+
choose when a release that formats differently reaches your documents; see
|
|
17
|
+
[versions](versions.md).
|
|
18
|
+
|
|
19
|
+
## Lint
|
|
20
|
+
|
|
21
|
+
Run the commands from the root of your documents, or point them there with
|
|
22
|
+
`--root`:
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
containing spaces.
|
|
24
|
+
```sh
|
|
25
|
+
npx mdtools lint
|
|
26
|
+
```
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
`lint` reports what the enabled rules find and never changes a file. Without
|
|
29
|
+
paths it checks every document of the workspace: each file ending in `.md` below
|
|
30
|
+
the configuration file's directory, or else below the Git repository or vault
|
|
31
|
+
you run it in; see [which files are
|
|
32
|
+
processed](cli.md#which-files-are-processed). Add `--json` for a report that
|
|
33
|
+
scripts can read, or `--max-warnings 0` to fail on warnings too.
|
|
27
34
|
|
|
28
|
-
|
|
29
|
-
newer; Node.js 24 LTS is preferred.
|
|
35
|
+
## Preview and write
|
|
30
36
|
|
|
31
37
|
```sh
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
npm run build
|
|
36
|
-
node dist/cli/main.js --help
|
|
38
|
+
npx mdtools format --diff
|
|
39
|
+
npx mdtools format --write
|
|
40
|
+
npx mdtools format --check
|
|
37
41
|
```
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
`--diff` prints what formatting would change, and is also what `format` does
|
|
44
|
+
without an option. `--write` writes the changes. `--check` writes nothing, names
|
|
45
|
+
each document that would change, and then exits with code `1`, which is the
|
|
46
|
+
check to run in CI. `format` changes only what its rules target, and refuses any
|
|
47
|
+
change that would alter what a document means. It can still report problems that
|
|
48
|
+
need a person, such as a broken link.
|
|
41
49
|
|
|
42
|
-
## Choose a
|
|
50
|
+
## Choose a preset and a dialect
|
|
43
51
|
|
|
44
|
-
Create `mdtools.config.jsonc`
|
|
52
|
+
Create `mdtools.config.jsonc` at the root of your documents:
|
|
45
53
|
|
|
46
54
|
```jsonc
|
|
47
55
|
{
|
|
56
|
+
"$schema": "./node_modules/mdrefine/config.schema.json",
|
|
48
57
|
"extends": ["recommended", "github"],
|
|
49
|
-
"ignore": ["vendor/**"
|
|
58
|
+
"ignore": ["vendor/**"],
|
|
50
59
|
}
|
|
51
60
|
```
|
|
52
61
|
|
|
53
|
-
The
|
|
54
|
-
|
|
55
|
-
GitHub preset adds table, task marker, and alert marker rules. The configuration
|
|
56
|
-
controls both linting and formatting.
|
|
62
|
+
The `$schema` line lets your editor complete and check the settings; see [JSON
|
|
63
|
+
Schema](configuration.md#json-schema).
|
|
57
64
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
65
|
+
`recommended` wraps prose at 80 columns; writes emphasis as `_text_`, strong
|
|
66
|
+
text as `**text**`, list items as `- item` and `1. item`, headings with `#`,
|
|
67
|
+
code fences with backticks, and thematic breaks as `---`; aligns tables; removes
|
|
68
|
+
trailing spaces and repeated blank lines; ends every file with a line break; and
|
|
69
|
+
checks local links. It also warns about [problems](rules.md) that show on the
|
|
70
|
+
rendered page, such as a reference without a definition, a skipped heading
|
|
71
|
+
level, or an image without alternative text. The second
|
|
72
|
+
[preset](configuration.md#presets) names where your documents are rendered and
|
|
73
|
+
so their [dialect](dialects.md): replace `github` with `forgejo` (or
|
|
74
|
+
`codeberg`), `gitea`, or `obsidian`. For plain CommonMark, keep only
|
|
75
|
+
`recommended` and add `"dialect": "commonmark"`.
|
|
62
76
|
|
|
63
|
-
Without a configuration file, the
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
Version 0.2.0-rc.1 and earlier assumed CommonMark, in which formatting reflows a
|
|
68
|
-
table and the lines of a note as an ordinary paragraph, so with those versions
|
|
69
|
-
create the configuration first. See [dialects](configuration.md#dialects).
|
|
70
|
-
|
|
71
|
-
## Lint without writing
|
|
72
|
-
|
|
73
|
-
```sh
|
|
74
|
-
npx --no-install mdtools lint --root /path/to/documents
|
|
75
|
-
```
|
|
77
|
+
Without a configuration file, the `recommended` rules apply, and documents are
|
|
78
|
+
read as GitHub Markdown, or as Obsidian notes inside a vault. A configuration
|
|
79
|
+
file is still worth creating: it enables the rules for your renderer and makes
|
|
80
|
+
the dialect explicit.
|
|
76
81
|
|
|
77
|
-
|
|
78
|
-
output, or `--max-warnings 0` to make warnings fail an automated check.
|
|
79
|
-
|
|
80
|
-
## Preview and apply formatting
|
|
81
|
-
|
|
82
|
-
```sh
|
|
83
|
-
npx --no-install mdtools format --root /path/to/documents --diff
|
|
84
|
-
npx --no-install mdtools format --root /path/to/documents --write
|
|
85
|
-
npx --no-install mdtools format --root /path/to/documents --check
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
Review the diff before running `--write`. The final check exits with code `1` if
|
|
89
|
-
formatting changes remain. Formatting may still report lint problems that
|
|
90
|
-
require a manual correction; inspect diagnostics as well as the exit code.
|
|
91
|
-
|
|
92
|
-
## Adjust the rules
|
|
93
|
-
|
|
94
|
-
Override a preset rule by adding a `rules` object:
|
|
82
|
+
Change a rule under `rules`:
|
|
95
83
|
|
|
96
84
|
```jsonc
|
|
97
85
|
{
|
|
@@ -103,10 +91,16 @@ Override a preset rule by adding a `rules` object:
|
|
|
103
91
|
}
|
|
104
92
|
```
|
|
105
93
|
|
|
106
|
-
This wraps at 100 columns and
|
|
107
|
-
|
|
94
|
+
This wraps at 100 columns and leaves emphasis markers as they are.
|
|
95
|
+
|
|
96
|
+
## Next steps
|
|
108
97
|
|
|
109
|
-
|
|
110
|
-
[
|
|
111
|
-
suppression comments.
|
|
112
|
-
|
|
98
|
+
- [Rules](rules.md): what each rule does, with examples.
|
|
99
|
+
- [Configuration](configuration.md): overrides, ignore patterns, plugins, and
|
|
100
|
+
suppression comments.
|
|
101
|
+
- [Command line](cli.md): file selection, stdin, the JSON report, and exit
|
|
102
|
+
codes.
|
|
103
|
+
- [Integrations](integrations.md): CI, Git hooks, editors, and moving from
|
|
104
|
+
Prettier or markdownlint.
|
|
105
|
+
- [Obsidian vaults](obsidian.md) and [examples](examples.md) of documents before
|
|
106
|
+
and after formatting.
|