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.
Files changed (130) hide show
  1. package/CHANGELOG.md +577 -0
  2. package/CODE_OF_CONDUCT.md +30 -0
  3. package/CONTRIBUTING.md +189 -16
  4. package/README.md +100 -154
  5. package/SECURITY.md +15 -13
  6. package/config.schema.json +91 -0
  7. package/dist/cli/diff.d.ts +10 -0
  8. package/dist/cli/diff.js +247 -0
  9. package/dist/cli/diff.js.map +1 -0
  10. package/dist/cli/main.js +270 -150
  11. package/dist/cli/main.js.map +1 -1
  12. package/dist/config/load.js +67 -6
  13. package/dist/config/load.js.map +1 -1
  14. package/dist/config/problems.d.ts +7 -0
  15. package/dist/config/problems.js +208 -0
  16. package/dist/config/problems.js.map +1 -0
  17. package/dist/config/resolve.d.ts +42 -109
  18. package/dist/config/resolve.js +193 -52
  19. package/dist/config/resolve.js.map +1 -1
  20. package/dist/core/directives.d.ts +7 -0
  21. package/dist/core/directives.js +25 -13
  22. package/dist/core/directives.js.map +1 -1
  23. package/dist/core/engine.d.ts +42 -4
  24. package/dist/core/engine.js +518 -79
  25. package/dist/core/engine.js.map +1 -1
  26. package/dist/core/frozen.d.ts +6 -0
  27. package/dist/core/frozen.js +15 -0
  28. package/dist/core/frozen.js.map +1 -0
  29. package/dist/core/scope.d.ts +30 -0
  30. package/dist/core/scope.js +122 -0
  31. package/dist/core/scope.js.map +1 -0
  32. package/dist/core/types.d.ts +202 -23
  33. package/dist/core/types.js.map +1 -1
  34. package/dist/index.d.ts +2 -2
  35. package/dist/index.js.map +1 -1
  36. package/dist/node.d.ts +3 -0
  37. package/dist/node.js +4 -0
  38. package/dist/node.js.map +1 -0
  39. package/dist/rules/blocks.d.ts +34 -0
  40. package/dist/rules/blocks.js +550 -0
  41. package/dist/rules/blocks.js.map +1 -0
  42. package/dist/rules/content.d.ts +2 -0
  43. package/dist/rules/content.js +85 -0
  44. package/dist/rules/content.js.map +1 -0
  45. package/dist/rules/dialects.js +10 -6
  46. package/dist/rules/dialects.js.map +1 -1
  47. package/dist/rules/links.js +302 -6
  48. package/dist/rules/links.js.map +1 -1
  49. package/dist/rules/lists.d.ts +2 -0
  50. package/dist/rules/lists.js +924 -0
  51. package/dist/rules/lists.js.map +1 -0
  52. package/dist/rules/structure.js +127 -1
  53. package/dist/rules/structure.js.map +1 -1
  54. package/dist/rules/style.d.ts +8 -2
  55. package/dist/rules/style.js +1137 -165
  56. package/dist/rules/style.js.map +1 -1
  57. package/dist/syntax/block-identifier.d.ts +18 -0
  58. package/dist/syntax/block-identifier.js +24 -0
  59. package/dist/syntax/block-identifier.js.map +1 -0
  60. package/dist/syntax/obsidian.d.ts +29 -11
  61. package/dist/syntax/obsidian.js +9 -10
  62. package/dist/syntax/obsidian.js.map +1 -1
  63. package/dist/syntax/parse.d.ts +87 -3
  64. package/dist/syntax/parse.js +247 -19
  65. package/dist/syntax/parse.js.map +1 -1
  66. package/dist/workspace/access.d.ts +12 -3
  67. package/dist/workspace/access.js +33 -5
  68. package/dist/workspace/access.js.map +1 -1
  69. package/dist/workspace/discovery.d.ts +85 -0
  70. package/dist/workspace/discovery.js +155 -0
  71. package/dist/workspace/discovery.js.map +1 -0
  72. package/dist/workspace/encoding.d.ts +24 -0
  73. package/dist/workspace/encoding.js +113 -0
  74. package/dist/workspace/encoding.js.map +1 -0
  75. package/dist/workspace/files.d.ts +92 -6
  76. package/dist/workspace/files.js +715 -59
  77. package/dist/workspace/files.js.map +1 -1
  78. package/dist/workspace/git.d.ts +54 -0
  79. package/dist/workspace/git.js +198 -0
  80. package/dist/workspace/git.js.map +1 -0
  81. package/dist/workspace/index.d.ts +58 -3
  82. package/dist/workspace/index.js +136 -37
  83. package/dist/workspace/index.js.map +1 -1
  84. package/dist/workspace/selection.d.ts +2 -2
  85. package/dist/workspace/selection.js +3 -3
  86. package/dist/workspace/selection.js.map +1 -1
  87. package/dist/workspace/slug.js +8 -1
  88. package/dist/workspace/slug.js.map +1 -1
  89. package/dist/workspace/vault.js +3 -1
  90. package/dist/workspace/vault.js.map +1 -1
  91. package/docs/api.md +239 -0
  92. package/docs/benchmarks.md +140 -200
  93. package/docs/changelog.md +1 -0
  94. package/docs/cli.md +281 -89
  95. package/docs/configuration.md +193 -480
  96. package/docs/design.md +163 -284
  97. package/docs/dialects.md +169 -0
  98. package/docs/examples.md +151 -109
  99. package/docs/index.md +21 -37
  100. package/docs/integrations.md +144 -0
  101. package/docs/obsidian.md +91 -115
  102. package/docs/plugins.md +147 -212
  103. package/docs/quick-start.md +68 -74
  104. package/docs/rules.md +1237 -0
  105. package/docs/versions.md +56 -0
  106. package/examples/README.md +17 -14
  107. package/examples/automation/summarize-report.mjs +5 -2
  108. package/examples/library/lint-folder.mjs +5 -4
  109. package/examples/library/lint-workspace.mjs +32 -0
  110. package/examples/plugin/notes/release-checklist.md +2 -2
  111. package/examples/plugin/rules.mjs +17 -23
  112. package/examples/workspaces/obsidian-vault/after/mdtools.config.jsonc +2 -2
  113. package/examples/workspaces/obsidian-vault/before/mdtools.config.jsonc +2 -2
  114. package/examples/workspaces/repository-docs/after/README.md +2 -2
  115. package/examples/workspaces/repository-docs/after/mdtools.config.jsonc +0 -2
  116. package/examples/workspaces/repository-docs/before/README.md +2 -2
  117. package/examples/workspaces/repository-docs/before/docs/guide.md +7 -7
  118. package/examples/workspaces/repository-docs/before/docs/reference.md +3 -1
  119. package/examples/workspaces/repository-docs/before/mdtools.config.jsonc +0 -2
  120. package/package.json +38 -12
  121. package/docs/contributing.md +0 -218
  122. package/docs/releases/0.1.0-beta.1.md +0 -39
  123. package/docs/releases/0.1.0-beta.2.md +0 -83
  124. package/docs/releases/0.1.0-beta.3.md +0 -61
  125. package/docs/releases/0.1.0-beta.4.md +0 -50
  126. package/docs/releases/0.1.0-rc.1.md +0 -99
  127. package/docs/releases/0.2.0-rc.1.md +0 -141
  128. package/docs/releases/0.2.0-rc.2.md +0 -225
  129. package/docs/releases.md +0 -144
  130. package/docs/testing.md +0 -93
@@ -1,44 +1,105 @@
1
1
  import type { Root } from "mdast";
2
2
  import type { Extension as SyntaxExtension } from "micromark-util-types";
3
3
  import type { Extension as TreeExtension } from "mdast-util-from-markdown";
4
- import type { AnySchema } from "ajv";
4
+ /** A dialect: the renderer whose Markdown syntax a document is read in. */
5
5
  export type Dialect = "commonmark" | "github" | "forgejo" | "gitea" | "obsidian";
6
- /** A dialect or one of its aliases, accepted wherever configuration names a dialect. */
6
+ /**
7
+ * A dialect or one of its aliases, accepted wherever a dialect is named:
8
+ * `codeberg` is `forgejo`. Parsed documents and resolved configurations hold
9
+ * the dialect itself.
10
+ */
7
11
  export type DialectName = Dialect | "codeberg";
12
+ /** How a rule's findings count: `off` disables the rule. */
8
13
  export type Severity = "off" | "warn" | "error";
9
- export type RuleSetting = Severity | [Severity, Record<string, unknown>];
14
+ /**
15
+ * A rule's setting in a configuration: its severity, or a list of its severity
16
+ * and, optionally, its options, which the rule's `schema` checks.
17
+ */
18
+ export type RuleSetting = Severity | readonly [Severity] | readonly [Severity, Readonly<Record<string, unknown>>];
19
+ /**
20
+ * A JSON Schema of draft 7, the version that Ajv validates by default: `true`
21
+ * accepts every value, `false` none, and an object holds keywords such as
22
+ * `type` and `properties`.
23
+ */
24
+ export type JsonSchema = boolean | JsonSchemaObject;
25
+ /** A JSON Schema written as an object of keywords. */
26
+ export interface JsonSchemaObject {
27
+ readonly [keyword: string]: unknown;
28
+ }
29
+ /** A replacement of part of a text. */
10
30
  export interface Edit {
31
+ /** Where the replaced text starts: an offset in UTF-16 code units, from 0. */
11
32
  start: number;
33
+ /** Where the replaced text ends, not included: an offset, not before `start`. */
12
34
  end: number;
35
+ /** The text that replaces it. */
13
36
  text: string;
14
37
  }
38
+ /**
39
+ * What a rule reports. Offsets are integers into `document.source`, from 0 to
40
+ * its length, and `end` is not before `start`; a rule that returns anything
41
+ * else is skipped for the document and reported as `engine/rule-error`.
42
+ */
15
43
  export interface Finding {
44
+ /** What is wrong, in a sentence. */
16
45
  message: string;
46
+ /** Where the finding starts. */
17
47
  start: number;
48
+ /** Where it ends, not included; omitted for a finding at one position. */
18
49
  end?: number;
50
+ /** A style rule's correction, which `format` applies. Problem rules give none. */
19
51
  edit?: Edit;
20
52
  }
53
+ /**
54
+ * A finding as `lint` and `format` report it. Its offsets, and those of its
55
+ * edit, count UTF-16 code units in the text given to them, byte order marks
56
+ * included, so that the edit applies to that text.
57
+ */
21
58
  export interface Diagnostic extends Finding {
59
+ /** The rule's ID, such as `style/wrap`, or `engine/…` for the engine's own diagnostics. */
22
60
  rule: string;
61
+ /** The severity that the configuration gives the rule. */
23
62
  severity: Exclude<Severity, "off">;
63
+ /** The line of `start`, counting from 1. */
24
64
  line: number;
65
+ /** The column of `start` in UTF-16 code units, counting from 1. */
25
66
  column: number;
26
67
  }
68
+ /**
69
+ * A parsed document. `Document` is also the name of the browser's DOM type;
70
+ * a module that uses both can import this one under another name.
71
+ */
27
72
  export interface Document {
73
+ /** The text that was parsed, without the byte order marks it started with. */
28
74
  source: string;
75
+ /** The document's path, relative to the workspace root with forward slashes. */
29
76
  path: string;
77
+ /** The mdast syntax tree, whose positions are offsets into `source`. */
30
78
  tree: Root;
79
+ /** The dialect it was read in, never an alias. */
31
80
  dialect: Dialect;
32
81
  }
82
+ /** Where a link leads, as `Workspace.resolve` finds it. */
33
83
  export interface LinkResolution {
34
84
  /**
85
+ * `resolved`: one file matches; `missing`: none does; `ambiguous`: several
86
+ * match and nothing decides between them; `directory`: the destination names
87
+ * a directory; `external`: a URL with a scheme, or one starting with `//`;
88
+ * `unavailable`: the destination cannot be judged from files.
35
89
  * `unreadable` means that the target could not be checked, because the
36
90
  * workspace could not read what it would have to; it is neither found nor
37
91
  * known to be missing.
38
92
  */
39
93
  status: "resolved" | "missing" | "ambiguous" | "external" | "unavailable" | "directory" | "unreadable";
94
+ /** The file found, as a path from the workspace root. */
40
95
  target?: string;
96
+ /** The part of the destination after `#`, decoded, or an empty string. */
41
97
  fragment?: string;
98
+ /**
99
+ * With status `resolved`: whether the target has the heading, block, or
100
+ * anchor that `fragment` names, by the rules of the dialect; `true` without
101
+ * a fragment.
102
+ */
42
103
  fragmentExists?: boolean;
43
104
  /**
44
105
  * What could not be read and limits the result. With status `unreadable` it
@@ -51,6 +112,12 @@ export interface LinkResolution {
51
112
  * for the part of an Obsidian vault that lies outside the workspace root.
52
113
  */
53
114
  unreadable?: string[];
115
+ /**
116
+ * With status `unreadable`: the symbolic link or junction inside the
117
+ * workspace through which the link's path leads to a place outside the
118
+ * workspace root, which bounds what the tool reads.
119
+ */
120
+ leavesRoot?: string;
54
121
  /**
55
122
  * With status `resolved` in the Obsidian dialect: the target's path as
56
123
  * Obsidian counts it, from the folder of the linking note's vault, when that
@@ -69,77 +136,189 @@ export interface LinkResolution {
69
136
  */
70
137
  outside?: string;
71
138
  }
139
+ /**
140
+ * The files that links can reach, which `createWorkspace` builds. A host can
141
+ * implement it itself.
142
+ */
72
143
  export interface Workspace {
144
+ /**
145
+ * Where a link leads. `source` is the linking document's path, `destination`
146
+ * the destination as written, and `dialect` the linking document's dialect;
147
+ * `wiki` is true for an Obsidian wikilink or embed. An error that it throws
148
+ * stops `lint` and `format`.
149
+ */
73
150
  resolve(source: string, destination: string, dialect: Dialect, wiki?: boolean): LinkResolution;
74
- /** Obsidian's strictLineBreaks setting; false prevents semantic-changing prose reflow. */
151
+ /**
152
+ * Obsidian's "Strict line breaks" setting of the vault, which is a property
153
+ * of the workspace: `true` when it is verified on. Obsidian notes are wrapped
154
+ * only then, because otherwise a single line break shows as a line break.
155
+ */
75
156
  strictLineBreaks?: boolean;
76
157
  }
158
+ /** What a rule's `check` receives. */
77
159
  export interface RuleContext {
160
+ /** The parsed document, which the rule must not change. */
78
161
  document: Document;
79
- options: Record<string, unknown>;
162
+ /** The rule's options from the configuration, checked against its `schema`; `{}` without. */
163
+ options: Readonly<Record<string, unknown>>;
164
+ /** The workspace that links resolve in, or undefined when the host gave none. */
80
165
  workspace?: Workspace;
81
166
  }
167
+ /**
168
+ * A rule. `ruleRegistry`, and so `lint` and `format`, reject a plugin rule
169
+ * that does not have this shape, naming it.
170
+ */
82
171
  export interface Rule {
172
+ /** What the rule checks, in a sentence. */
83
173
  description: string;
174
+ /** `problem` only reports; `style` may propose edits, which `format` applies. */
84
175
  kind: "style" | "problem";
85
176
  /** Inline edits, then block layout, then document-wide whitespace. */
86
177
  phase?: "inline" | "block" | "document";
87
- schema?: AnySchema;
178
+ /** Validates the rule's options; compiled once and reused for every document. */
179
+ schema?: JsonSchema;
180
+ /**
181
+ * The rule's findings for one document. A rule that throws is skipped for
182
+ * that document and reported as `engine/rule-error`.
183
+ */
88
184
  check(context: RuleContext): Finding[];
89
185
  }
186
+ /** A plugin: rules, presets, and syntax, named with its `name` as a prefix. */
90
187
  export interface Plugin {
188
+ /**
189
+ * The prefix of its rules and presets: a lowercase identifier of letters,
190
+ * digits, and hyphens that starts with a letter, unique among the plugins of
191
+ * a run. `engine` is reserved for the engine's own diagnostics.
192
+ */
91
193
  name: string;
194
+ /** Rules by name; rule `x` of plugin `p` has the ID `p/x`. */
92
195
  rules?: Record<string, Rule>;
196
+ /** Presets by name; preset `x` of plugin `p` is extended as `p/x`. */
93
197
  presets?: Record<string, Config>;
198
+ /** A micromark syntax extension and the mdast extension that builds its nodes. */
94
199
  syntax?: {
95
200
  micromark: SyntaxExtension;
96
201
  mdast: TreeExtension;
97
202
  };
98
203
  }
204
+ /** Settings for the documents that match one of `files`. */
99
205
  export interface Override {
100
- files: string[];
101
- dialect?: DialectName;
102
- rules?: Record<string, RuleSetting>;
206
+ /** Glob patterns, matched against paths relative to the workspace root, with forward slashes. */
207
+ readonly files: readonly string[];
208
+ /** The dialect of the matching documents. */
209
+ readonly dialect?: DialectName;
210
+ /** Rule settings for the matching documents, applied after the configuration's own. */
211
+ readonly rules?: Readonly<Record<string, RuleSetting>>;
103
212
  }
213
+ /** Files that links may lead to without being linted or formatted. */
104
214
  export interface ResolveOptions {
215
+ /** Files that `.gitignore` files exclude are link targets. Default: false. */
105
216
  gitIgnored?: boolean;
217
+ /** Files of nested Git repositories are link targets. Default: false. */
106
218
  nestedRepositories?: boolean;
107
219
  }
220
+ /**
221
+ * A configuration, as a configuration file holds it. `configSchema` describes
222
+ * it, and `validateConfig` checks it.
223
+ */
108
224
  export interface Config {
109
- resolve?: ResolveOptions;
110
- extends?: string[];
225
+ /** The JSON Schema for editors; the tool ignores it. */
226
+ $schema?: string;
227
+ /** Files that links may lead to; see `ResolveOptions`. */
228
+ resolve?: Readonly<ResolveOptions>;
229
+ /**
230
+ * Presets to start from, in order: a preset name or a list of them. Without
231
+ * it, the configuration starts from `recommended`.
232
+ */
233
+ extends?: string | readonly string[];
111
234
  /**
112
235
  * The dialect documents are read in. When neither this setting, a preset, nor
113
236
  * a matching override names one, the caller's default dialect is assumed, and
114
237
  * `github` when the caller gives none.
115
238
  */
116
239
  dialect?: DialectName;
117
- rules?: Record<string, RuleSetting>;
118
- ignore?: string[];
119
- overrides?: Override[];
120
- plugins?: string[];
240
+ /** Rule settings by rule ID, applied after those of the presets. */
241
+ rules?: Readonly<Record<string, RuleSetting>>;
242
+ /** Glob patterns of documents that are never linted or formatted; they stay link targets. */
243
+ ignore?: readonly string[];
244
+ /** Settings for some documents, applied in order after the others. */
245
+ overrides?: readonly Override[];
246
+ /**
247
+ * Plugins to load, as files or package names. Only a configuration file
248
+ * loads them; the library takes plugin objects through its `plugins` option.
249
+ */
250
+ plugins?: readonly string[];
121
251
  }
252
+ /** The settings in effect for one document, which `resolveConfig` returns. */
122
253
  export interface ResolvedConfig {
254
+ /** The `resolve` settings of the configuration and its presets. */
123
255
  resolve: ResolveOptions;
256
+ /** The dialect the document is read in, never an alias. */
124
257
  dialect: Dialect;
258
+ /** The setting of every rule that the configuration, its presets, or an override names. */
125
259
  rules: Record<string, RuleSetting>;
260
+ /** The `ignore` patterns of the configuration and its presets. */
126
261
  ignore: string[];
127
262
  }
128
- export interface ProcessOptions {
129
- path?: string;
130
- config?: Config;
131
- plugins?: Plugin[];
132
- workspace?: Workspace;
263
+ /** What decides the configuration in effect for a document; see `resolveConfig`. */
264
+ export interface ResolveConfigOptions {
265
+ /**
266
+ * The document's path, relative to the workspace root with forward slashes.
267
+ * It selects overrides and resolves links; `document.md` when omitted.
268
+ */
269
+ path?: string | undefined;
270
+ /** The configuration. Without it, the `recommended` preset applies. */
271
+ config?: Config | undefined;
272
+ /** Plugin objects, whose rules and presets the configuration can name. */
273
+ plugins?: readonly Plugin[] | undefined;
133
274
  /**
134
275
  * The dialect to assume when the configuration names none for the document;
135
276
  * `github` when omitted. A dialect named by the configuration, a preset, or a
136
- * matching override takes precedence. The CLI passes `obsidian` when the
137
- * workspace root is an Obsidian vault.
277
+ * matching override takes precedence. The CLI passes `obsidian` for a
278
+ * document inside an Obsidian vault, wherever the vault lies in the workspace.
279
+ */
280
+ defaultDialect?: DialectName | undefined;
281
+ }
282
+ /** The options of `lint` and `format`. */
283
+ export interface ProcessOptions extends ResolveConfigOptions {
284
+ /** The files that links can reach. Without it, links are not checked or rewritten. */
285
+ workspace?: Workspace | undefined;
286
+ /**
287
+ * Parts of the source to lint or format, as half-open UTF-16 offsets into
288
+ * the text as given. Each range is widened to the complete blocks that it
289
+ * touches, such as paragraphs, headings, tables, and code blocks, also inside
290
+ * lists and block quotes, with their whole lines and the line breaks next to
291
+ * them, where blank lines between blocks are edited. `lint` then returns the
292
+ * diagnostics that start in those parts, and `format` applies only edits
293
+ * that lie wholly in them, so that the rest of the text stays as it is. The
294
+ * whole document is still parsed and checked. Omitted, the whole document is
295
+ * processed; an empty list processes nothing.
138
296
  */
139
- defaultDialect?: DialectName;
297
+ ranges?: readonly SourceRange[] | undefined;
140
298
  }
299
+ /** A part of a text, as half-open UTF-16 offsets. */
300
+ export interface SourceRange {
301
+ /** Where the part starts. */
302
+ start: number;
303
+ /** Where it ends, not included. */
304
+ end: number;
305
+ }
306
+ /** What `format` returns. */
141
307
  export interface FormatResult {
308
+ /**
309
+ * The formatted text. It leaves out the edits of each rule that would change
310
+ * the document's meaning, reported as `engine/unsafe-format` where the
311
+ * meaning would first differ, and is the text as given when formatting did
312
+ * not settle.
313
+ */
142
314
  output: string;
315
+ /** Whether `output` differs from the text as given. */
143
316
  changed: boolean;
317
+ /** The findings that remain in `output`, and the engine's own diagnostics. */
144
318
  diagnostics: Diagnostic[];
319
+ /**
320
+ * With `ranges`: the parts of the output that formatting was limited to,
321
+ * each range widened to its blocks and moved by the edits applied in it.
322
+ */
323
+ scope?: SourceRange[];
145
324
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Root } from \"mdast\";\nimport type { Extension as SyntaxExtension } from \"micromark-util-types\";\nimport type { Extension as TreeExtension } from \"mdast-util-from-markdown\";\nimport type { AnySchema } from \"ajv\";\n\nexport type Dialect = \"commonmark\" | \"github\" | \"forgejo\" | \"gitea\" | \"obsidian\";\n/** A dialect or one of its aliases, accepted wherever configuration names a dialect. */\nexport type DialectName = Dialect | \"codeberg\";\nexport type Severity = \"off\" | \"warn\" | \"error\";\nexport type RuleSetting = Severity | [Severity, Record<string, unknown>];\nexport interface Edit {\n start: number;\n end: number;\n text: string;\n}\nexport interface Finding {\n message: string;\n start: number;\n end?: number;\n edit?: Edit;\n}\nexport interface Diagnostic extends Finding {\n rule: string;\n severity: Exclude<Severity, \"off\">;\n line: number;\n column: number;\n}\nexport interface Document {\n source: string;\n path: string;\n tree: Root;\n dialect: Dialect;\n}\nexport interface LinkResolution {\n /**\n * `unreadable` means that the target could not be checked, because the\n * workspace could not read what it would have to; it is neither found nor\n * known to be missing.\n */\n status:\n \"resolved\" | \"missing\" | \"ambiguous\" | \"external\" | \"unavailable\" | \"directory\" | \"unreadable\";\n target?: string;\n fragment?: string;\n fragmentExists?: boolean;\n /**\n * What could not be read and limits the result. With status `unreadable` it\n * is the target file itself, when `target` is set and its content was needed\n * to check the fragment, or else the directories that may hold the target.\n * With status `resolved` the target was found among the readable files, by an\n * Obsidian name search or in a place that Obsidian tries after one of these\n * directories. They may hold another match, or the one Obsidian would choose,\n * so the link must not be rewritten. An entry that starts with `..` stands\n * for the part of an Obsidian vault that lies outside the workspace root.\n */\n unreadable?: string[];\n /**\n * With status `resolved` in the Obsidian dialect: the target's path as\n * Obsidian counts it, from the folder of the linking note's vault, when that\n * folder is not the workspace root and the path so differs from `target`.\n */\n rooted?: string;\n /**\n * The folder of the linking note's vault, relative to the workspace root, when\n * an Obsidian link was resolved inside a vault that lies below the root. A\n * path that Obsidian counts from the vault starts there, not at the root.\n */\n vault?: string;\n /**\n * With status `missing`: an existing file or folder that the link's path names\n * outside the linking note's vault, where Obsidian does not look.\n */\n outside?: string;\n}\nexport interface Workspace {\n resolve(source: string, destination: string, dialect: Dialect, wiki?: boolean): LinkResolution;\n /** Obsidian's strictLineBreaks setting; false prevents semantic-changing prose reflow. */\n strictLineBreaks?: boolean;\n}\nexport interface RuleContext {\n document: Document;\n options: Record<string, unknown>;\n workspace?: Workspace;\n}\nexport interface Rule {\n description: string;\n kind: \"style\" | \"problem\";\n /** Inline edits, then block layout, then document-wide whitespace. */\n phase?: \"inline\" | \"block\" | \"document\";\n schema?: AnySchema;\n check(context: RuleContext): Finding[];\n}\nexport interface Plugin {\n name: string;\n rules?: Record<string, Rule>;\n presets?: Record<string, Config>;\n syntax?: { micromark: SyntaxExtension; mdast: TreeExtension };\n}\nexport interface Override {\n files: string[];\n dialect?: DialectName;\n rules?: Record<string, RuleSetting>;\n}\nexport interface ResolveOptions {\n gitIgnored?: boolean;\n nestedRepositories?: boolean;\n}\nexport interface Config {\n resolve?: ResolveOptions;\n extends?: string[];\n /**\n * The dialect documents are read in. When neither this setting, a preset, nor\n * a matching override names one, the caller's default dialect is assumed, and\n * `github` when the caller gives none.\n */\n dialect?: DialectName;\n rules?: Record<string, RuleSetting>;\n ignore?: string[];\n overrides?: Override[];\n plugins?: string[];\n}\nexport interface ResolvedConfig {\n resolve: ResolveOptions;\n dialect: Dialect;\n rules: Record<string, RuleSetting>;\n ignore: string[];\n}\nexport interface ProcessOptions {\n path?: string;\n config?: Config;\n plugins?: Plugin[];\n workspace?: Workspace;\n /**\n * The dialect to assume when the configuration names none for the document;\n * `github` when omitted. A dialect named by the configuration, a preset, or a\n * matching override takes precedence. The CLI passes `obsidian` when the\n * workspace root is an Obsidian vault.\n */\n defaultDialect?: DialectName;\n}\nexport interface FormatResult {\n output: string;\n changed: boolean;\n diagnostics: Diagnostic[];\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Root } from \"mdast\";\nimport type { Extension as SyntaxExtension } from \"micromark-util-types\";\nimport type { Extension as TreeExtension } from \"mdast-util-from-markdown\";\n\n/** A dialect: the renderer whose Markdown syntax a document is read in. */\nexport type Dialect = \"commonmark\" | \"github\" | \"forgejo\" | \"gitea\" | \"obsidian\";\n/**\n * A dialect or one of its aliases, accepted wherever a dialect is named:\n * `codeberg` is `forgejo`. Parsed documents and resolved configurations hold\n * the dialect itself.\n */\nexport type DialectName = Dialect | \"codeberg\";\n/** How a rule's findings count: `off` disables the rule. */\nexport type Severity = \"off\" | \"warn\" | \"error\";\n/**\n * A rule's setting in a configuration: its severity, or a list of its severity\n * and, optionally, its options, which the rule's `schema` checks.\n */\nexport type RuleSetting =\n Severity | readonly [Severity] | readonly [Severity, Readonly<Record<string, unknown>>];\n/**\n * A JSON Schema of draft 7, the version that Ajv validates by default: `true`\n * accepts every value, `false` none, and an object holds keywords such as\n * `type` and `properties`.\n */\nexport type JsonSchema = boolean | JsonSchemaObject;\n/** A JSON Schema written as an object of keywords. */\nexport interface JsonSchemaObject {\n readonly [keyword: string]: unknown;\n}\n/** A replacement of part of a text. */\nexport interface Edit {\n /** Where the replaced text starts: an offset in UTF-16 code units, from 0. */\n start: number;\n /** Where the replaced text ends, not included: an offset, not before `start`. */\n end: number;\n /** The text that replaces it. */\n text: string;\n}\n/**\n * What a rule reports. Offsets are integers into `document.source`, from 0 to\n * its length, and `end` is not before `start`; a rule that returns anything\n * else is skipped for the document and reported as `engine/rule-error`.\n */\nexport interface Finding {\n /** What is wrong, in a sentence. */\n message: string;\n /** Where the finding starts. */\n start: number;\n /** Where it ends, not included; omitted for a finding at one position. */\n end?: number;\n /** A style rule's correction, which `format` applies. Problem rules give none. */\n edit?: Edit;\n}\n/**\n * A finding as `lint` and `format` report it. Its offsets, and those of its\n * edit, count UTF-16 code units in the text given to them, byte order marks\n * included, so that the edit applies to that text.\n */\nexport interface Diagnostic extends Finding {\n /** The rule's ID, such as `style/wrap`, or `engine/…` for the engine's own diagnostics. */\n rule: string;\n /** The severity that the configuration gives the rule. */\n severity: Exclude<Severity, \"off\">;\n /** The line of `start`, counting from 1. */\n line: number;\n /** The column of `start` in UTF-16 code units, counting from 1. */\n column: number;\n}\n/**\n * A parsed document. `Document` is also the name of the browser's DOM type;\n * a module that uses both can import this one under another name.\n */\nexport interface Document {\n /** The text that was parsed, without the byte order marks it started with. */\n source: string;\n /** The document's path, relative to the workspace root with forward slashes. */\n path: string;\n /** The mdast syntax tree, whose positions are offsets into `source`. */\n tree: Root;\n /** The dialect it was read in, never an alias. */\n dialect: Dialect;\n}\n/** Where a link leads, as `Workspace.resolve` finds it. */\nexport interface LinkResolution {\n /**\n * `resolved`: one file matches; `missing`: none does; `ambiguous`: several\n * match and nothing decides between them; `directory`: the destination names\n * a directory; `external`: a URL with a scheme, or one starting with `//`;\n * `unavailable`: the destination cannot be judged from files.\n * `unreadable` means that the target could not be checked, because the\n * workspace could not read what it would have to; it is neither found nor\n * known to be missing.\n */\n status:\n \"resolved\" | \"missing\" | \"ambiguous\" | \"external\" | \"unavailable\" | \"directory\" | \"unreadable\";\n /** The file found, as a path from the workspace root. */\n target?: string;\n /** The part of the destination after `#`, decoded, or an empty string. */\n fragment?: string;\n /**\n * With status `resolved`: whether the target has the heading, block, or\n * anchor that `fragment` names, by the rules of the dialect; `true` without\n * a fragment.\n */\n fragmentExists?: boolean;\n /**\n * What could not be read and limits the result. With status `unreadable` it\n * is the target file itself, when `target` is set and its content was needed\n * to check the fragment, or else the directories that may hold the target.\n * With status `resolved` the target was found among the readable files, by an\n * Obsidian name search or in a place that Obsidian tries after one of these\n * directories. They may hold another match, or the one Obsidian would choose,\n * so the link must not be rewritten. An entry that starts with `..` stands\n * for the part of an Obsidian vault that lies outside the workspace root.\n */\n unreadable?: string[];\n /**\n * With status `unreadable`: the symbolic link or junction inside the\n * workspace through which the link's path leads to a place outside the\n * workspace root, which bounds what the tool reads.\n */\n leavesRoot?: string;\n /**\n * With status `resolved` in the Obsidian dialect: the target's path as\n * Obsidian counts it, from the folder of the linking note's vault, when that\n * folder is not the workspace root and the path so differs from `target`.\n */\n rooted?: string;\n /**\n * The folder of the linking note's vault, relative to the workspace root, when\n * an Obsidian link was resolved inside a vault that lies below the root. A\n * path that Obsidian counts from the vault starts there, not at the root.\n */\n vault?: string;\n /**\n * With status `missing`: an existing file or folder that the link's path names\n * outside the linking note's vault, where Obsidian does not look.\n */\n outside?: string;\n}\n/**\n * The files that links can reach, which `createWorkspace` builds. A host can\n * implement it itself.\n */\nexport interface Workspace {\n /**\n * Where a link leads. `source` is the linking document's path, `destination`\n * the destination as written, and `dialect` the linking document's dialect;\n * `wiki` is true for an Obsidian wikilink or embed. An error that it throws\n * stops `lint` and `format`.\n */\n resolve(source: string, destination: string, dialect: Dialect, wiki?: boolean): LinkResolution;\n /**\n * Obsidian's \"Strict line breaks\" setting of the vault, which is a property\n * of the workspace: `true` when it is verified on. Obsidian notes are wrapped\n * only then, because otherwise a single line break shows as a line break.\n */\n strictLineBreaks?: boolean;\n}\n/** What a rule's `check` receives. */\nexport interface RuleContext {\n /** The parsed document, which the rule must not change. */\n document: Document;\n /** The rule's options from the configuration, checked against its `schema`; `{}` without. */\n options: Readonly<Record<string, unknown>>;\n /** The workspace that links resolve in, or undefined when the host gave none. */\n workspace?: Workspace;\n}\n/**\n * A rule. `ruleRegistry`, and so `lint` and `format`, reject a plugin rule\n * that does not have this shape, naming it.\n */\nexport interface Rule {\n /** What the rule checks, in a sentence. */\n description: string;\n /** `problem` only reports; `style` may propose edits, which `format` applies. */\n kind: \"style\" | \"problem\";\n /** Inline edits, then block layout, then document-wide whitespace. */\n phase?: \"inline\" | \"block\" | \"document\";\n /** Validates the rule's options; compiled once and reused for every document. */\n schema?: JsonSchema;\n /**\n * The rule's findings for one document. A rule that throws is skipped for\n * that document and reported as `engine/rule-error`.\n */\n check(context: RuleContext): Finding[];\n}\n/** A plugin: rules, presets, and syntax, named with its `name` as a prefix. */\nexport interface Plugin {\n /**\n * The prefix of its rules and presets: a lowercase identifier of letters,\n * digits, and hyphens that starts with a letter, unique among the plugins of\n * a run. `engine` is reserved for the engine's own diagnostics.\n */\n name: string;\n /** Rules by name; rule `x` of plugin `p` has the ID `p/x`. */\n rules?: Record<string, Rule>;\n /** Presets by name; preset `x` of plugin `p` is extended as `p/x`. */\n presets?: Record<string, Config>;\n /** A micromark syntax extension and the mdast extension that builds its nodes. */\n syntax?: { micromark: SyntaxExtension; mdast: TreeExtension };\n}\n/** Settings for the documents that match one of `files`. */\nexport interface Override {\n /** Glob patterns, matched against paths relative to the workspace root, with forward slashes. */\n readonly files: readonly string[];\n /** The dialect of the matching documents. */\n readonly dialect?: DialectName;\n /** Rule settings for the matching documents, applied after the configuration's own. */\n readonly rules?: Readonly<Record<string, RuleSetting>>;\n}\n/** Files that links may lead to without being linted or formatted. */\nexport interface ResolveOptions {\n /** Files that `.gitignore` files exclude are link targets. Default: false. */\n gitIgnored?: boolean;\n /** Files of nested Git repositories are link targets. Default: false. */\n nestedRepositories?: boolean;\n}\n/**\n * A configuration, as a configuration file holds it. `configSchema` describes\n * it, and `validateConfig` checks it.\n */\nexport interface Config {\n /** The JSON Schema for editors; the tool ignores it. */\n $schema?: string;\n /** Files that links may lead to; see `ResolveOptions`. */\n resolve?: Readonly<ResolveOptions>;\n /**\n * Presets to start from, in order: a preset name or a list of them. Without\n * it, the configuration starts from `recommended`.\n */\n extends?: string | readonly string[];\n /**\n * The dialect documents are read in. When neither this setting, a preset, nor\n * a matching override names one, the caller's default dialect is assumed, and\n * `github` when the caller gives none.\n */\n dialect?: DialectName;\n /** Rule settings by rule ID, applied after those of the presets. */\n rules?: Readonly<Record<string, RuleSetting>>;\n /** Glob patterns of documents that are never linted or formatted; they stay link targets. */\n ignore?: readonly string[];\n /** Settings for some documents, applied in order after the others. */\n overrides?: readonly Override[];\n /**\n * Plugins to load, as files or package names. Only a configuration file\n * loads them; the library takes plugin objects through its `plugins` option.\n */\n plugins?: readonly string[];\n}\n/** The settings in effect for one document, which `resolveConfig` returns. */\nexport interface ResolvedConfig {\n /** The `resolve` settings of the configuration and its presets. */\n resolve: ResolveOptions;\n /** The dialect the document is read in, never an alias. */\n dialect: Dialect;\n /** The setting of every rule that the configuration, its presets, or an override names. */\n rules: Record<string, RuleSetting>;\n /** The `ignore` patterns of the configuration and its presets. */\n ignore: string[];\n}\n/** What decides the configuration in effect for a document; see `resolveConfig`. */\nexport interface ResolveConfigOptions {\n /**\n * The document's path, relative to the workspace root with forward slashes.\n * It selects overrides and resolves links; `document.md` when omitted.\n */\n path?: string | undefined;\n /** The configuration. Without it, the `recommended` preset applies. */\n config?: Config | undefined;\n /** Plugin objects, whose rules and presets the configuration can name. */\n plugins?: readonly Plugin[] | undefined;\n /**\n * The dialect to assume when the configuration names none for the document;\n * `github` when omitted. A dialect named by the configuration, a preset, or a\n * matching override takes precedence. The CLI passes `obsidian` for a\n * document inside an Obsidian vault, wherever the vault lies in the workspace.\n */\n defaultDialect?: DialectName | undefined;\n}\n/** The options of `lint` and `format`. */\nexport interface ProcessOptions extends ResolveConfigOptions {\n /** The files that links can reach. Without it, links are not checked or rewritten. */\n workspace?: Workspace | undefined;\n /**\n * Parts of the source to lint or format, as half-open UTF-16 offsets into\n * the text as given. Each range is widened to the complete blocks that it\n * touches, such as paragraphs, headings, tables, and code blocks, also inside\n * lists and block quotes, with their whole lines and the line breaks next to\n * them, where blank lines between blocks are edited. `lint` then returns the\n * diagnostics that start in those parts, and `format` applies only edits\n * that lie wholly in them, so that the rest of the text stays as it is. The\n * whole document is still parsed and checked. Omitted, the whole document is\n * processed; an empty list processes nothing.\n */\n ranges?: readonly SourceRange[] | undefined;\n}\n/** A part of a text, as half-open UTF-16 offsets. */\nexport interface SourceRange {\n /** Where the part starts. */\n start: number;\n /** Where it ends, not included. */\n end: number;\n}\n/** What `format` returns. */\nexport interface FormatResult {\n /**\n * The formatted text. It leaves out the edits of each rule that would change\n * the document's meaning, reported as `engine/unsafe-format` where the\n * meaning would first differ, and is the text as given when formatting did\n * not settle.\n */\n output: string;\n /** Whether `output` differs from the text as given. */\n changed: boolean;\n /** The findings that remain in `output`, and the engine's own diagnostics. */\n diagnostics: Diagnostic[];\n /**\n * With `ranges`: the parts of the output that formatting was limited to,\n * each range widened to its blocks and moved by the edits applied in it.\n */\n scope?: SourceRange[];\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -2,6 +2,6 @@ export { lint, format, applyEdits, semanticFingerprint, builtInRules, ruleRegist
2
2
  export { resolveConfig, validateConfig, configSchema, presets, dialects, dialectAliases, canonicalDialect, } from "./config/resolve.js";
3
3
  export { parse, range, textContent } from "./syntax/parse.js";
4
4
  export { createWorkspace } from "./workspace/index.js";
5
- export type { WorkspaceOptions, WorkspaceSource } from "./workspace/index.js";
5
+ export type { LookupResult, WorkspaceOptions, WorkspaceSource } from "./workspace/index.js";
6
6
  export type * from "./core/types.js";
7
- export type { ObsidianLiteral } from "./syntax/obsidian.js";
7
+ export type { ObsidianComment, ObsidianHighlight, WikiLink } from "./syntax/obsidian.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,EACJ,MAAM,EACN,UAAU,EACV,mBAAmB,EACnB,YAAY,EACZ,YAAY,GACb,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,aAAa,EACb,cAAc,EACd,YAAY,EACZ,OAAO,EACP,QAAQ,EACR,cAAc,EACd,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC","sourcesContent":["export {\n lint,\n format,\n applyEdits,\n semanticFingerprint,\n builtInRules,\n ruleRegistry,\n} from \"./core/engine.js\";\nexport {\n resolveConfig,\n validateConfig,\n configSchema,\n presets,\n dialects,\n dialectAliases,\n canonicalDialect,\n} from \"./config/resolve.js\";\nexport { parse, range, textContent } from \"./syntax/parse.js\";\nexport { createWorkspace } from \"./workspace/index.js\";\nexport type { WorkspaceOptions, WorkspaceSource } from \"./workspace/index.js\";\nexport type * from \"./core/types.js\";\nexport type { ObsidianLiteral } from \"./syntax/obsidian.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,EACJ,MAAM,EACN,UAAU,EACV,mBAAmB,EACnB,YAAY,EACZ,YAAY,GACb,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,aAAa,EACb,cAAc,EACd,YAAY,EACZ,OAAO,EACP,QAAQ,EACR,cAAc,EACd,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC","sourcesContent":["export {\n lint,\n format,\n applyEdits,\n semanticFingerprint,\n builtInRules,\n ruleRegistry,\n} from \"./core/engine.js\";\nexport {\n resolveConfig,\n validateConfig,\n configSchema,\n presets,\n dialects,\n dialectAliases,\n canonicalDialect,\n} from \"./config/resolve.js\";\nexport { parse, range, textContent } from \"./syntax/parse.js\";\nexport { createWorkspace } from \"./workspace/index.js\";\nexport type { LookupResult, WorkspaceOptions, WorkspaceSource } from \"./workspace/index.js\";\nexport type * from \"./core/types.js\";\nexport type { ObsidianComment, ObsidianHighlight, WikiLink } from \"./syntax/obsidian.js\";\n"]}
package/dist/node.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { discoverWorkspace } from "./workspace/discovery.js";
2
+ export type { DiscoverOptions, WorkspaceDiscovery } from "./workspace/discovery.js";
3
+ export type { Skipped } from "./workspace/files.js";
package/dist/node.js ADDED
@@ -0,0 +1,4 @@
1
+ // `mdrefine/node`: what needs the filesystem, kept apart from the main entry,
2
+ // which never touches it.
3
+ export { discoverWorkspace } from "./workspace/discovery.js";
4
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,0BAA0B;AAC1B,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC","sourcesContent":["// `mdrefine/node`: what needs the filesystem, kept apart from the main entry,\n// which never touches it.\nexport { discoverWorkspace } from \"./workspace/discovery.js\";\nexport type { DiscoverOptions, WorkspaceDiscovery } from \"./workspace/discovery.js\";\nexport type { Skipped } from \"./workspace/files.js\";\n"]}
@@ -0,0 +1,34 @@
1
+ import type { Heading, Nodes } from "mdast";
2
+ import type { Document, Edit, Rule } from "../core/types.js";
3
+ /**
4
+ * Whether a line added directly below `node`, at its own level, would become
5
+ * part of an HTML block or Obsidian comment that `node` ends with and that has
6
+ * no end marker. The parser lets such an HTML block run on through the blank
7
+ * lines of a list item or footnote, and through a final line ending, but a
8
+ * block quote ends it. An Obsidian comment runs on to the end of the document,
9
+ * whatever contains it.
10
+ */
11
+ export declare function takesLines(node: Nodes): boolean;
12
+ /**
13
+ * Whether a line ending added at the end of the document would change its
14
+ * last block. Besides the blocks of `takesLines`, the parser counts a last
15
+ * line of blank space in a code or math block that has no closing fence only
16
+ * when a line ending follows it.
17
+ */
18
+ export declare function takesFinalLineEnding(document: Document): boolean;
19
+ /**
20
+ * Whether `style/heading` writes headings of levels 1 and 2 underlined: with
21
+ * `setext`, or with `consistent` when the document's first heading is.
22
+ */
23
+ export declare function underlinedHeadings(document: Document, style: unknown): boolean;
24
+ /**
25
+ * The edit that writes the `#` heading `node`, of level 1 or 2, underlined
26
+ * with `===` or `---`, or undefined where its text could not stand on a line
27
+ * of its own as the same heading. Only headings at the start of a line of the
28
+ * document itself, below a blank line or at the start, are underlined:
29
+ * directly below text, the heading's line would continue a paragraph. On
30
+ * Forgejo and Gitea, a heading with a `{` keeps its form, because their
31
+ * attribute blocks depend on it.
32
+ */
33
+ export declare function underlineEdit(document: Document, node: Heading): Edit | undefined;
34
+ export declare const blockRules: Record<string, Rule>;