mdrefine 0.1.0-beta.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/CONTRIBUTING.md +10 -0
- package/LICENSE +21 -0
- package/README.md +133 -0
- package/SECURITY.md +16 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +158 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/config/load.d.ts +9 -0
- package/dist/config/load.js +75 -0
- package/dist/config/load.js.map +1 -0
- package/dist/config/resolve.d.ts +93 -0
- package/dist/config/resolve.js +110 -0
- package/dist/config/resolve.js.map +1 -0
- package/dist/core/directives.d.ts +3 -0
- package/dist/core/directives.js +48 -0
- package/dist/core/directives.js.map +1 -0
- package/dist/core/engine.d.ts +8 -0
- package/dist/core/engine.js +192 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/core/types.d.ts +92 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/rules/dialects.d.ts +2 -0
- package/dist/rules/dialects.js +105 -0
- package/dist/rules/dialects.js.map +1 -0
- package/dist/rules/links.d.ts +2 -0
- package/dist/rules/links.js +238 -0
- package/dist/rules/links.js.map +1 -0
- package/dist/rules/style.d.ts +12 -0
- package/dist/rules/style.js +244 -0
- package/dist/rules/style.js.map +1 -0
- package/dist/syntax/obsidian.d.ts +33 -0
- package/dist/syntax/obsidian.js +126 -0
- package/dist/syntax/obsidian.js.map +1 -0
- package/dist/syntax/parse.d.ts +5 -0
- package/dist/syntax/parse.js +45 -0
- package/dist/syntax/parse.js.map +1 -0
- package/dist/workspace/files.d.ts +8 -0
- package/dist/workspace/files.js +98 -0
- package/dist/workspace/files.js.map +1 -0
- package/dist/workspace/index.d.ts +10 -0
- package/dist/workspace/index.js +100 -0
- package/dist/workspace/index.js.map +1 -0
- package/docs/cli.md +71 -0
- package/docs/configuration.md +171 -0
- package/docs/contributing.md +103 -0
- package/docs/design.md +79 -0
- package/docs/index.md +48 -0
- package/docs/obsidian.md +69 -0
- package/docs/plugins.md +114 -0
- package/docs/quick-start.md +101 -0
- package/docs/releases/0.1.0-beta.1.md +39 -0
- package/docs/releases.md +90 -0
- package/examples/github.jsonc +7 -0
- package/examples/obsidian.jsonc +8 -0
- package/package.json +101 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Ajv } from "ajv";
|
|
2
|
+
import { minimatch } from "minimatch";
|
|
3
|
+
export const presets = {
|
|
4
|
+
recommended: {
|
|
5
|
+
rules: {
|
|
6
|
+
"style/wrap": ["warn", { width: 80 }],
|
|
7
|
+
"style/emphasis": ["warn", { marker: "_" }],
|
|
8
|
+
"style/strong": ["warn", { marker: "*" }],
|
|
9
|
+
"style/final-newline": "warn",
|
|
10
|
+
"links/valid": "error",
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
github: {
|
|
14
|
+
dialect: "github",
|
|
15
|
+
rules: { "github/task-marker": "warn", "github/alert-marker": "warn", "style/table": "warn" },
|
|
16
|
+
},
|
|
17
|
+
obsidian: {
|
|
18
|
+
dialect: "obsidian",
|
|
19
|
+
rules: {
|
|
20
|
+
"obsidian/callout-marker": "warn",
|
|
21
|
+
"obsidian/block-reference": "error",
|
|
22
|
+
"obsidian/strict-line-breaks": "warn",
|
|
23
|
+
"github/task-marker": "warn",
|
|
24
|
+
"style/table": "warn",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
const severity = { enum: ["off", "warn", "error"] };
|
|
29
|
+
const rules = {
|
|
30
|
+
type: "object",
|
|
31
|
+
additionalProperties: {
|
|
32
|
+
anyOf: [
|
|
33
|
+
severity,
|
|
34
|
+
{ type: "array", items: [severity, { type: "object" }], minItems: 2, maxItems: 2 },
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
export const configSchema = {
|
|
39
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
40
|
+
type: "object",
|
|
41
|
+
additionalProperties: false,
|
|
42
|
+
properties: {
|
|
43
|
+
$schema: { type: "string" },
|
|
44
|
+
extends: { type: "array", items: { type: "string" } },
|
|
45
|
+
dialect: { enum: ["commonmark", "github", "obsidian"] },
|
|
46
|
+
rules,
|
|
47
|
+
ignore: { type: "array", items: { type: "string" } },
|
|
48
|
+
plugins: { type: "array", items: { type: "string" } },
|
|
49
|
+
overrides: {
|
|
50
|
+
type: "array",
|
|
51
|
+
items: {
|
|
52
|
+
type: "object",
|
|
53
|
+
required: ["files"],
|
|
54
|
+
additionalProperties: false,
|
|
55
|
+
properties: {
|
|
56
|
+
files: { type: "array", items: { type: "string" }, minItems: 1 },
|
|
57
|
+
dialect: { enum: ["commonmark", "github", "obsidian"] },
|
|
58
|
+
rules,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
const validate = new Ajv({ allErrors: true }).compile(configSchema);
|
|
65
|
+
export function validateConfig(value) {
|
|
66
|
+
if (!validate(value))
|
|
67
|
+
throw new Error(`Invalid configuration: ${new Ajv().errorsText(validate.errors)}`);
|
|
68
|
+
}
|
|
69
|
+
export function setting(value) {
|
|
70
|
+
return Array.isArray(value)
|
|
71
|
+
? { severity: value[0], options: value[1] }
|
|
72
|
+
: { severity: value, options: {} };
|
|
73
|
+
}
|
|
74
|
+
export function resolveConfig(config = {}, path = "document.md", plugins = []) {
|
|
75
|
+
validateConfig(config);
|
|
76
|
+
const available = { ...presets };
|
|
77
|
+
for (const plugin of plugins)
|
|
78
|
+
for (const [name, preset] of Object.entries(plugin.presets ?? {}))
|
|
79
|
+
available[`${plugin.name}/${name}`] = preset;
|
|
80
|
+
const result = { dialect: "commonmark", rules: {}, ignore: [] };
|
|
81
|
+
const overrides = [];
|
|
82
|
+
function merge(part, chain) {
|
|
83
|
+
validateConfig(part);
|
|
84
|
+
for (const name of part.extends ?? []) {
|
|
85
|
+
if (chain.includes(name))
|
|
86
|
+
throw new Error(`Circular preset: ${[...chain, name].join(" -> ")}`);
|
|
87
|
+
const preset = available[name];
|
|
88
|
+
if (!preset)
|
|
89
|
+
throw new Error(`Unknown preset: ${name}`);
|
|
90
|
+
merge(preset, [...chain, name]);
|
|
91
|
+
}
|
|
92
|
+
if (part.dialect)
|
|
93
|
+
result.dialect = part.dialect;
|
|
94
|
+
Object.assign(result.rules, part.rules);
|
|
95
|
+
result.ignore.push(...(part.ignore ?? []));
|
|
96
|
+
overrides.push(...(part.overrides ?? []));
|
|
97
|
+
}
|
|
98
|
+
if (config.extends === undefined)
|
|
99
|
+
merge(presets.recommended, []);
|
|
100
|
+
merge(config, []);
|
|
101
|
+
for (const override of overrides) {
|
|
102
|
+
if (override.files.some((pattern) => minimatch(path.replaceAll("\\", "/"), pattern, { dot: true }))) {
|
|
103
|
+
if (override.dialect)
|
|
104
|
+
result.dialect = override.dialect;
|
|
105
|
+
Object.assign(result.rules, override.rules);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/config/resolve.ts"],"names":[],"mappings":"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","sourcesContent":["import { Ajv } from \"ajv\";\nimport { minimatch } from \"minimatch\";\nimport type { Config, Plugin, ResolvedConfig, RuleSetting } from \"../core/types.js\";\n\nexport const presets: Record<string, Config> = {\n recommended: {\n rules: {\n \"style/wrap\": [\"warn\", { width: 80 }],\n \"style/emphasis\": [\"warn\", { marker: \"_\" }],\n \"style/strong\": [\"warn\", { marker: \"*\" }],\n \"style/final-newline\": \"warn\",\n \"links/valid\": \"error\",\n },\n },\n github: {\n dialect: \"github\",\n rules: { \"github/task-marker\": \"warn\", \"github/alert-marker\": \"warn\", \"style/table\": \"warn\" },\n },\n obsidian: {\n dialect: \"obsidian\",\n rules: {\n \"obsidian/callout-marker\": \"warn\",\n \"obsidian/block-reference\": \"error\",\n \"obsidian/strict-line-breaks\": \"warn\",\n \"github/task-marker\": \"warn\",\n \"style/table\": \"warn\",\n },\n },\n};\nconst severity = { enum: [\"off\", \"warn\", \"error\"] };\nconst rules = {\n type: \"object\",\n additionalProperties: {\n anyOf: [\n severity,\n { type: \"array\", items: [severity, { type: \"object\" }], minItems: 2, maxItems: 2 },\n ],\n },\n};\nexport const configSchema = {\n $schema: \"http://json-schema.org/draft-07/schema#\",\n type: \"object\",\n additionalProperties: false,\n properties: {\n $schema: { type: \"string\" },\n extends: { type: \"array\", items: { type: \"string\" } },\n dialect: { enum: [\"commonmark\", \"github\", \"obsidian\"] },\n rules,\n ignore: { type: \"array\", items: { type: \"string\" } },\n plugins: { type: \"array\", items: { type: \"string\" } },\n overrides: {\n type: \"array\",\n items: {\n type: \"object\",\n required: [\"files\"],\n additionalProperties: false,\n properties: {\n files: { type: \"array\", items: { type: \"string\" }, minItems: 1 },\n dialect: { enum: [\"commonmark\", \"github\", \"obsidian\"] },\n rules,\n },\n },\n },\n },\n};\nconst validate = new Ajv({ allErrors: true }).compile(configSchema);\nexport function validateConfig(value: unknown): asserts value is Config {\n if (!validate(value))\n throw new Error(`Invalid configuration: ${new Ajv().errorsText(validate.errors)}`);\n}\nexport function setting(value: RuleSetting): {\n severity: \"off\" | \"warn\" | \"error\";\n options: Record<string, unknown>;\n} {\n return Array.isArray(value)\n ? { severity: value[0], options: value[1] }\n : { severity: value, options: {} };\n}\nexport function resolveConfig(\n config: Config = {},\n path = \"document.md\",\n plugins: Plugin[] = [],\n): ResolvedConfig {\n validateConfig(config);\n const available = { ...presets };\n for (const plugin of plugins)\n for (const [name, preset] of Object.entries(plugin.presets ?? {}))\n available[`${plugin.name}/${name}`] = preset;\n const result: ResolvedConfig = { dialect: \"commonmark\", rules: {}, ignore: [] };\n const overrides: NonNullable<Config[\"overrides\"]> = [];\n function merge(part: Config, chain: string[]) {\n validateConfig(part);\n for (const name of part.extends ?? []) {\n if (chain.includes(name))\n throw new Error(`Circular preset: ${[...chain, name].join(\" -> \")}`);\n const preset = available[name];\n if (!preset) throw new Error(`Unknown preset: ${name}`);\n merge(preset, [...chain, name]);\n }\n if (part.dialect) result.dialect = part.dialect;\n Object.assign(result.rules, part.rules);\n result.ignore.push(...(part.ignore ?? []));\n overrides.push(...(part.overrides ?? []));\n }\n if (config.extends === undefined) merge(presets.recommended!, []);\n merge(config, []);\n for (const override of overrides) {\n if (\n override.files.some((pattern) =>\n minimatch(path.replaceAll(\"\\\\\", \"/\"), pattern, { dot: true }),\n )\n ) {\n if (override.dialect) result.dialect = override.dialect;\n Object.assign(result.rules, override.rules);\n }\n }\n return result;\n}\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { visit } from "unist-util-visit";
|
|
2
|
+
import { range } from "../syntax/parse.js";
|
|
3
|
+
/** Directives are recognized only in parsed HTML comments, never in code examples. */
|
|
4
|
+
export function suppressions(document) {
|
|
5
|
+
const intervals = [];
|
|
6
|
+
const active = new Map();
|
|
7
|
+
visit(document.tree, "html", (node) => {
|
|
8
|
+
const match = /^<!--\s*mdtools-(disable-next-line|disable|enable)(?:\s+([^]*?))?\s*-->$/.exec(node.value.trim());
|
|
9
|
+
if (!match)
|
|
10
|
+
return;
|
|
11
|
+
const names = match[2]
|
|
12
|
+
?.trim()
|
|
13
|
+
.split(/[\s,]+/)
|
|
14
|
+
.filter(Boolean) ?? ["*"];
|
|
15
|
+
const rules = new Set(names.length ? names : ["*"]);
|
|
16
|
+
const [start, end] = range(node);
|
|
17
|
+
if (match[1] === "disable-next-line") {
|
|
18
|
+
const next = document.source.indexOf("\n", end);
|
|
19
|
+
if (next < 0)
|
|
20
|
+
return;
|
|
21
|
+
const last = document.source.indexOf("\n", next + 1);
|
|
22
|
+
intervals.push({ start: next + 1, end: last < 0 ? document.source.length : last, rules });
|
|
23
|
+
}
|
|
24
|
+
else if (match[1] === "disable") {
|
|
25
|
+
for (const rule of rules)
|
|
26
|
+
if (!active.has(rule))
|
|
27
|
+
active.set(rule, end);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
for (const rule of rules.has("*") ? active.keys() : rules) {
|
|
31
|
+
const from = active.get(rule);
|
|
32
|
+
if (from !== undefined)
|
|
33
|
+
intervals.push({ start: from, end: start, rules: new Set([rule]) });
|
|
34
|
+
active.delete(rule);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
for (const [rule, start] of active)
|
|
39
|
+
intervals.push({ start, end: document.source.length, rules: new Set([rule]) });
|
|
40
|
+
return (rule, finding) => intervals.some((interval) => {
|
|
41
|
+
const start = finding.edit?.start ?? finding.start;
|
|
42
|
+
const end = finding.edit?.end ?? finding.end ?? finding.start;
|
|
43
|
+
return ((interval.rules.has("*") || interval.rules.has(rule)) &&
|
|
44
|
+
start <= interval.end &&
|
|
45
|
+
end >= interval.start);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=directives.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directives.js","sourceRoot":"","sources":["../../src/core/directives.ts"],"names":[],"mappings":"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","sourcesContent":["import { visit } from \"unist-util-visit\";\nimport type { Document, Finding } from \"./types.js\";\nimport { range } from \"../syntax/parse.js\";\n\n/** Directives are recognized only in parsed HTML comments, never in code examples. */\nexport function suppressions(document: Document): (rule: string, finding: Finding) => boolean {\n const intervals: { start: number; end: number; rules: Set<string> }[] = [];\n const active = new Map<string, number>();\n visit(document.tree, \"html\", (node) => {\n const match = /^<!--\\s*mdtools-(disable-next-line|disable|enable)(?:\\s+([^]*?))?\\s*-->$/.exec(\n node.value.trim(),\n );\n if (!match) return;\n const names = match[2]\n ?.trim()\n .split(/[\\s,]+/)\n .filter(Boolean) ?? [\"*\"];\n const rules = new Set(names.length ? names : [\"*\"]);\n const [start, end] = range(node);\n if (match[1] === \"disable-next-line\") {\n const next = document.source.indexOf(\"\\n\", end);\n if (next < 0) return;\n const last = document.source.indexOf(\"\\n\", next + 1);\n intervals.push({ start: next + 1, end: last < 0 ? document.source.length : last, rules });\n } else if (match[1] === \"disable\") {\n for (const rule of rules) if (!active.has(rule)) active.set(rule, end);\n } else {\n for (const rule of rules.has(\"*\") ? active.keys() : rules) {\n const from = active.get(rule);\n if (from !== undefined) intervals.push({ start: from, end: start, rules: new Set([rule]) });\n active.delete(rule);\n }\n }\n });\n for (const [rule, start] of active)\n intervals.push({ start, end: document.source.length, rules: new Set([rule]) });\n return (rule, finding) =>\n intervals.some((interval) => {\n const start = finding.edit?.start ?? finding.start;\n const end = finding.edit?.end ?? finding.end ?? finding.start;\n return (\n (interval.rules.has(\"*\") || interval.rules.has(rule)) &&\n start <= interval.end &&\n end >= interval.start\n );\n });\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Diagnostic, Document, Edit, FormatResult, Plugin, ProcessOptions, Rule, Workspace } from "./types.js";
|
|
2
|
+
export declare const builtInRules: Record<string, Rule>;
|
|
3
|
+
export declare function ruleRegistry(plugins?: Plugin[]): Record<string, Rule>;
|
|
4
|
+
export declare function lint(source: string, options?: ProcessOptions): Diagnostic[];
|
|
5
|
+
export declare function applyEdits(source: string, edits: Edit[]): string;
|
|
6
|
+
/** Compare semantic trees, with link identity supplied by the selected renderer's resolver. */
|
|
7
|
+
export declare function semanticFingerprint(document: Document, workspace?: Workspace): string;
|
|
8
|
+
export declare function format(source: string, options?: ProcessOptions): FormatResult;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { Ajv } from "ajv";
|
|
2
|
+
import { resolveConfig, setting } from "../config/resolve.js";
|
|
3
|
+
import { parse } from "../syntax/parse.js";
|
|
4
|
+
import { styleRules } from "../rules/style.js";
|
|
5
|
+
import { dialectRules } from "../rules/dialects.js";
|
|
6
|
+
import { linkRules } from "../rules/links.js";
|
|
7
|
+
import { suppressions } from "./directives.js";
|
|
8
|
+
export const builtInRules = { ...styleRules, ...dialectRules, ...linkRules };
|
|
9
|
+
export function ruleRegistry(plugins = []) {
|
|
10
|
+
const rules = { ...builtInRules };
|
|
11
|
+
const names = new Set();
|
|
12
|
+
for (const plugin of plugins) {
|
|
13
|
+
if (!/^[a-z][a-z\d-]*$/.test(plugin.name) || names.has(plugin.name))
|
|
14
|
+
throw new Error(`Invalid or duplicate plugin name: ${plugin.name}`);
|
|
15
|
+
names.add(plugin.name);
|
|
16
|
+
for (const [name, rule] of Object.entries(plugin.rules ?? {})) {
|
|
17
|
+
const id = `${plugin.name}/${name}`;
|
|
18
|
+
if (rules[id])
|
|
19
|
+
throw new Error(`Duplicate rule: ${id}`);
|
|
20
|
+
rules[id] = rule;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return rules;
|
|
24
|
+
}
|
|
25
|
+
function prepare(options) {
|
|
26
|
+
const config = resolveConfig(options.config, options.path, options.plugins);
|
|
27
|
+
const rules = ruleRegistry(options.plugins);
|
|
28
|
+
const ajv = new Ajv({ allErrors: true });
|
|
29
|
+
for (const [name, value] of Object.entries(config.rules)) {
|
|
30
|
+
const rule = rules[name];
|
|
31
|
+
if (!rule)
|
|
32
|
+
throw new Error(`Unknown rule: ${name}`);
|
|
33
|
+
const enabled = setting(value);
|
|
34
|
+
if (rule.schema && enabled.severity !== "off") {
|
|
35
|
+
const validate = ajv.compile(rule.schema);
|
|
36
|
+
if (!validate(enabled.options))
|
|
37
|
+
throw new Error(`Invalid options for ${name}: ${ajv.errorsText(validate.errors)}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { config, rules };
|
|
41
|
+
}
|
|
42
|
+
function diagnostic(source, rule, severity, finding) {
|
|
43
|
+
const prefix = source.slice(0, finding.start);
|
|
44
|
+
return {
|
|
45
|
+
...finding,
|
|
46
|
+
rule,
|
|
47
|
+
severity,
|
|
48
|
+
line: prefix.split("\n").length,
|
|
49
|
+
column: finding.start - prefix.lastIndexOf("\n"),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function inspect(document, config, rules, workspace, phase) {
|
|
53
|
+
const result = [];
|
|
54
|
+
const suppressed = suppressions(document);
|
|
55
|
+
for (const [name, value] of Object.entries(config.rules)) {
|
|
56
|
+
const enabled = setting(value);
|
|
57
|
+
const rule = rules[name];
|
|
58
|
+
if (enabled.severity === "off" ||
|
|
59
|
+
(phase && (rule.kind !== "style" || (rule.phase ?? "inline") !== phase)))
|
|
60
|
+
continue;
|
|
61
|
+
const findings = rule.check({
|
|
62
|
+
document,
|
|
63
|
+
options: enabled.options,
|
|
64
|
+
...(workspace ? { workspace } : {}),
|
|
65
|
+
});
|
|
66
|
+
for (const finding of findings)
|
|
67
|
+
if (!suppressed(name, finding))
|
|
68
|
+
result.push(diagnostic(document.source, name, enabled.severity, finding));
|
|
69
|
+
}
|
|
70
|
+
return result.sort((a, b) => a.start - b.start || a.rule.localeCompare(b.rule));
|
|
71
|
+
}
|
|
72
|
+
export function lint(source, options = {}) {
|
|
73
|
+
const { config, rules } = prepare(options);
|
|
74
|
+
const document = parse(source, config.dialect, options.path, options.plugins);
|
|
75
|
+
return inspect(document, config, rules, options.workspace);
|
|
76
|
+
}
|
|
77
|
+
export function applyEdits(source, edits) {
|
|
78
|
+
const sorted = [...edits].sort((a, b) => a.start - b.start || a.end - b.end);
|
|
79
|
+
let previous;
|
|
80
|
+
const accepted = [];
|
|
81
|
+
for (const edit of sorted) {
|
|
82
|
+
if (!Number.isInteger(edit.start) ||
|
|
83
|
+
!Number.isInteger(edit.end) ||
|
|
84
|
+
edit.start < 0 ||
|
|
85
|
+
edit.end < edit.start ||
|
|
86
|
+
edit.end > source.length)
|
|
87
|
+
throw new Error("Rule returned an invalid edit range.");
|
|
88
|
+
if (previous &&
|
|
89
|
+
edit.start === previous.start &&
|
|
90
|
+
edit.end === previous.end &&
|
|
91
|
+
edit.text === previous.text)
|
|
92
|
+
continue;
|
|
93
|
+
if (previous && (edit.start < previous.end || edit.start === previous.start))
|
|
94
|
+
throw new Error("Enabled rules proposed overlapping edits; disable one of the conflicting rules.");
|
|
95
|
+
accepted.push(edit);
|
|
96
|
+
previous = edit;
|
|
97
|
+
}
|
|
98
|
+
let output = source;
|
|
99
|
+
for (const edit of accepted.reverse())
|
|
100
|
+
output = output.slice(0, edit.start) + edit.text + output.slice(edit.end);
|
|
101
|
+
return output;
|
|
102
|
+
}
|
|
103
|
+
/** Compare semantic trees, with link identity supplied by the selected renderer's resolver. */
|
|
104
|
+
export function semanticFingerprint(document, workspace) {
|
|
105
|
+
const canonicalUrl = (url, wiki = false) => {
|
|
106
|
+
const resolution = workspace?.resolve(document.path, url, document.dialect, wiki);
|
|
107
|
+
return resolution?.status === "resolved"
|
|
108
|
+
? `${resolution.target}#${resolution.fragment ?? ""}`
|
|
109
|
+
: url;
|
|
110
|
+
};
|
|
111
|
+
function normalize(node, calloutHeader = false) {
|
|
112
|
+
if (node.type === "wikiLink") {
|
|
113
|
+
if (node.embed)
|
|
114
|
+
return { type: "embed", url: canonicalUrl(node.target ?? "", true), label: node.label };
|
|
115
|
+
return {
|
|
116
|
+
type: "link",
|
|
117
|
+
url: canonicalUrl(node.target ?? "", true),
|
|
118
|
+
title: null,
|
|
119
|
+
children: [{ type: "text", value: node.label ?? node.target }],
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const result = {};
|
|
123
|
+
for (const [key, value] of Object.entries(node)) {
|
|
124
|
+
if (key === "position" || key === "data")
|
|
125
|
+
continue;
|
|
126
|
+
if (key === "children" && "children" in node) {
|
|
127
|
+
result.children = node.children.map((child, index) => normalize(child, index === 0 && (node.type === "blockquote" || calloutHeader)));
|
|
128
|
+
}
|
|
129
|
+
else if (key === "url" && typeof value === "string")
|
|
130
|
+
result[key] = canonicalUrl(value);
|
|
131
|
+
else if (key === "value" && node.type === "text") {
|
|
132
|
+
let text = node.value.replace(/[ \t\r\n]+/g, " ");
|
|
133
|
+
if (calloutHeader && document.dialect !== "commonmark")
|
|
134
|
+
text = text.replace(/^\[!([\w-]+)\]/, (_, marker) => `[!${marker.toLowerCase()}]`);
|
|
135
|
+
result[key] = text;
|
|
136
|
+
}
|
|
137
|
+
else
|
|
138
|
+
result[key] = value;
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
return JSON.stringify(normalize(document.tree), (_key, value) => {
|
|
143
|
+
if (value && typeof value === "object" && !Array.isArray(value))
|
|
144
|
+
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)));
|
|
145
|
+
return value;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
export function format(source, options = {}) {
|
|
149
|
+
const { config, rules } = prepare(options);
|
|
150
|
+
let output = source;
|
|
151
|
+
const initial = parse(source, config.dialect, options.path, options.plugins);
|
|
152
|
+
const fingerprint = semanticFingerprint(initial, options.workspace);
|
|
153
|
+
const seen = new Set([source]);
|
|
154
|
+
try {
|
|
155
|
+
for (let pass = 0; pass < 8; pass++) {
|
|
156
|
+
const before = output;
|
|
157
|
+
for (const phase of ["inline", "block", "document"]) {
|
|
158
|
+
const document = parse(output, config.dialect, options.path, options.plugins);
|
|
159
|
+
const edits = inspect(document, config, rules, options.workspace, phase).flatMap((item) => item.edit ? [item.edit] : []);
|
|
160
|
+
if (!edits.length)
|
|
161
|
+
continue;
|
|
162
|
+
const candidate = applyEdits(output, edits);
|
|
163
|
+
if (semanticFingerprint(parse(candidate, config.dialect, options.path, options.plugins), options.workspace) !== fingerprint)
|
|
164
|
+
throw new Error(`Formatting in the ${phase} phase would change parsed meaning; the document was left unchanged.`);
|
|
165
|
+
output = candidate;
|
|
166
|
+
}
|
|
167
|
+
if (output === before)
|
|
168
|
+
return {
|
|
169
|
+
output,
|
|
170
|
+
changed: output !== source,
|
|
171
|
+
diagnostics: inspect(parse(output, config.dialect, options.path, options.plugins), config, rules, options.workspace),
|
|
172
|
+
};
|
|
173
|
+
if (seen.has(output))
|
|
174
|
+
throw new Error("Formatting rules oscillate; the document was left unchanged.");
|
|
175
|
+
seen.add(output);
|
|
176
|
+
}
|
|
177
|
+
throw new Error("Formatting did not converge in eight passes; the document was left unchanged.");
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
return {
|
|
181
|
+
output: source,
|
|
182
|
+
changed: false,
|
|
183
|
+
diagnostics: [
|
|
184
|
+
diagnostic(source, "engine/unsafe-format", "error", {
|
|
185
|
+
start: 0,
|
|
186
|
+
message: error instanceof Error ? error.message : String(error),
|
|
187
|
+
}),
|
|
188
|
+
],
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/core/engine.ts"],"names":[],"mappings":"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","sourcesContent":["import { Ajv } from \"ajv\";\nimport type { Nodes } from \"mdast\";\nimport { resolveConfig, setting } from \"../config/resolve.js\";\nimport { parse } from \"../syntax/parse.js\";\nimport { styleRules } from \"../rules/style.js\";\nimport { dialectRules } from \"../rules/dialects.js\";\nimport { linkRules } from \"../rules/links.js\";\nimport { suppressions } from \"./directives.js\";\nimport type {\n Diagnostic,\n Document,\n Edit,\n Finding,\n FormatResult,\n Plugin,\n ProcessOptions,\n ResolvedConfig,\n Rule,\n Workspace,\n} from \"./types.js\";\n\nexport const builtInRules: Record<string, Rule> = { ...styleRules, ...dialectRules, ...linkRules };\nexport function ruleRegistry(plugins: Plugin[] = []): Record<string, Rule> {\n const rules = { ...builtInRules };\n const names = new Set<string>();\n for (const plugin of plugins) {\n if (!/^[a-z][a-z\\d-]*$/.test(plugin.name) || names.has(plugin.name))\n throw new Error(`Invalid or duplicate plugin name: ${plugin.name}`);\n names.add(plugin.name);\n for (const [name, rule] of Object.entries(plugin.rules ?? {})) {\n const id = `${plugin.name}/${name}`;\n if (rules[id]) throw new Error(`Duplicate rule: ${id}`);\n rules[id] = rule;\n }\n }\n return rules;\n}\nfunction prepare(options: ProcessOptions) {\n const config = resolveConfig(options.config, options.path, options.plugins);\n const rules = ruleRegistry(options.plugins);\n const ajv = new Ajv({ allErrors: true });\n for (const [name, value] of Object.entries(config.rules)) {\n const rule = rules[name];\n if (!rule) throw new Error(`Unknown rule: ${name}`);\n const enabled = setting(value);\n if (rule.schema && enabled.severity !== \"off\") {\n const validate = ajv.compile(rule.schema);\n if (!validate(enabled.options))\n throw new Error(`Invalid options for ${name}: ${ajv.errorsText(validate.errors)}`);\n }\n }\n return { config, rules };\n}\nfunction diagnostic(\n source: string,\n rule: string,\n severity: \"warn\" | \"error\",\n finding: Finding,\n): Diagnostic {\n const prefix = source.slice(0, finding.start);\n return {\n ...finding,\n rule,\n severity,\n line: prefix.split(\"\\n\").length,\n column: finding.start - prefix.lastIndexOf(\"\\n\"),\n };\n}\nfunction inspect(\n document: Document,\n config: ResolvedConfig,\n rules: Record<string, Rule>,\n workspace?: Workspace,\n phase?: string,\n): Diagnostic[] {\n const result: Diagnostic[] = [];\n const suppressed = suppressions(document);\n for (const [name, value] of Object.entries(config.rules)) {\n const enabled = setting(value);\n const rule = rules[name]!;\n if (\n enabled.severity === \"off\" ||\n (phase && (rule.kind !== \"style\" || (rule.phase ?? \"inline\") !== phase))\n )\n continue;\n const findings = rule.check({\n document,\n options: enabled.options,\n ...(workspace ? { workspace } : {}),\n });\n for (const finding of findings)\n if (!suppressed(name, finding))\n result.push(diagnostic(document.source, name, enabled.severity, finding));\n }\n return result.sort((a, b) => a.start - b.start || a.rule.localeCompare(b.rule));\n}\nexport function lint(source: string, options: ProcessOptions = {}): Diagnostic[] {\n const { config, rules } = prepare(options);\n const document = parse(source, config.dialect, options.path, options.plugins);\n return inspect(document, config, rules, options.workspace);\n}\nexport function applyEdits(source: string, edits: Edit[]): string {\n const sorted = [...edits].sort((a, b) => a.start - b.start || a.end - b.end);\n let previous: Edit | undefined;\n const accepted: Edit[] = [];\n for (const edit of sorted) {\n if (\n !Number.isInteger(edit.start) ||\n !Number.isInteger(edit.end) ||\n edit.start < 0 ||\n edit.end < edit.start ||\n edit.end > source.length\n )\n throw new Error(\"Rule returned an invalid edit range.\");\n if (\n previous &&\n edit.start === previous.start &&\n edit.end === previous.end &&\n edit.text === previous.text\n )\n continue;\n if (previous && (edit.start < previous.end || edit.start === previous.start))\n throw new Error(\n \"Enabled rules proposed overlapping edits; disable one of the conflicting rules.\",\n );\n accepted.push(edit);\n previous = edit;\n }\n let output = source;\n for (const edit of accepted.reverse())\n output = output.slice(0, edit.start) + edit.text + output.slice(edit.end);\n return output;\n}\n/** Compare semantic trees, with link identity supplied by the selected renderer's resolver. */\nexport function semanticFingerprint(document: Document, workspace?: Workspace): string {\n const canonicalUrl = (url: string, wiki = false) => {\n const resolution = workspace?.resolve(document.path, url, document.dialect, wiki);\n return resolution?.status === \"resolved\"\n ? `${resolution.target}#${resolution.fragment ?? \"\"}`\n : url;\n };\n function normalize(node: Nodes, calloutHeader = false): unknown {\n if (node.type === \"wikiLink\") {\n if (node.embed)\n return { type: \"embed\", url: canonicalUrl(node.target ?? \"\", true), label: node.label };\n return {\n type: \"link\",\n url: canonicalUrl(node.target ?? \"\", true),\n title: null,\n children: [{ type: \"text\", value: node.label ?? node.target }],\n };\n }\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(node)) {\n if (key === \"position\" || key === \"data\") continue;\n if (key === \"children\" && \"children\" in node) {\n result.children = node.children.map((child, index) =>\n normalize(child, index === 0 && (node.type === \"blockquote\" || calloutHeader)),\n );\n } else if (key === \"url\" && typeof value === \"string\") result[key] = canonicalUrl(value);\n else if (key === \"value\" && node.type === \"text\") {\n let text = node.value.replace(/[ \\t\\r\\n]+/g, \" \");\n if (calloutHeader && document.dialect !== \"commonmark\")\n text = text.replace(\n /^\\[!([\\w-]+)\\]/,\n (_, marker: string) => `[!${marker.toLowerCase()}]`,\n );\n result[key] = text;\n } else result[key] = value;\n }\n return result;\n }\n return JSON.stringify(normalize(document.tree), (_key, value: unknown) => {\n if (value && typeof value === \"object\" && !Array.isArray(value))\n return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)));\n return value;\n });\n}\nexport function format(source: string, options: ProcessOptions = {}): FormatResult {\n const { config, rules } = prepare(options);\n let output = source;\n const initial = parse(source, config.dialect, options.path, options.plugins);\n const fingerprint = semanticFingerprint(initial, options.workspace);\n const seen = new Set([source]);\n try {\n for (let pass = 0; pass < 8; pass++) {\n const before = output;\n for (const phase of [\"inline\", \"block\", \"document\"]) {\n const document = parse(output, config.dialect, options.path, options.plugins);\n const edits = inspect(document, config, rules, options.workspace, phase).flatMap((item) =>\n item.edit ? [item.edit] : [],\n );\n if (!edits.length) continue;\n const candidate = applyEdits(output, edits);\n if (\n semanticFingerprint(\n parse(candidate, config.dialect, options.path, options.plugins),\n options.workspace,\n ) !== fingerprint\n )\n throw new Error(\n `Formatting in the ${phase} phase would change parsed meaning; the document was left unchanged.`,\n );\n output = candidate;\n }\n if (output === before)\n return {\n output,\n changed: output !== source,\n diagnostics: inspect(\n parse(output, config.dialect, options.path, options.plugins),\n config,\n rules,\n options.workspace,\n ),\n };\n if (seen.has(output))\n throw new Error(\"Formatting rules oscillate; the document was left unchanged.\");\n seen.add(output);\n }\n throw new Error(\n \"Formatting did not converge in eight passes; the document was left unchanged.\",\n );\n } catch (error) {\n return {\n output: source,\n changed: false,\n diagnostics: [\n diagnostic(source, \"engine/unsafe-format\", \"error\", {\n start: 0,\n message: error instanceof Error ? error.message : String(error),\n }),\n ],\n };\n }\n}\n"]}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { Root } from "mdast";
|
|
2
|
+
import type { Extension as SyntaxExtension } from "micromark-util-types";
|
|
3
|
+
import type { Extension as TreeExtension } from "mdast-util-from-markdown";
|
|
4
|
+
import type { AnySchema } from "ajv";
|
|
5
|
+
export type Dialect = "commonmark" | "github" | "obsidian";
|
|
6
|
+
export type Severity = "off" | "warn" | "error";
|
|
7
|
+
export type RuleSetting = Severity | [Severity, Record<string, unknown>];
|
|
8
|
+
export interface Edit {
|
|
9
|
+
start: number;
|
|
10
|
+
end: number;
|
|
11
|
+
text: string;
|
|
12
|
+
}
|
|
13
|
+
export interface Finding {
|
|
14
|
+
message: string;
|
|
15
|
+
start: number;
|
|
16
|
+
end?: number;
|
|
17
|
+
edit?: Edit;
|
|
18
|
+
}
|
|
19
|
+
export interface Diagnostic extends Finding {
|
|
20
|
+
rule: string;
|
|
21
|
+
severity: Exclude<Severity, "off">;
|
|
22
|
+
line: number;
|
|
23
|
+
column: number;
|
|
24
|
+
}
|
|
25
|
+
export interface Document {
|
|
26
|
+
source: string;
|
|
27
|
+
path: string;
|
|
28
|
+
tree: Root;
|
|
29
|
+
dialect: Dialect;
|
|
30
|
+
}
|
|
31
|
+
export interface LinkResolution {
|
|
32
|
+
status: "resolved" | "missing" | "ambiguous" | "external" | "unavailable";
|
|
33
|
+
target?: string;
|
|
34
|
+
fragment?: string;
|
|
35
|
+
fragmentExists?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface Workspace {
|
|
38
|
+
resolve(source: string, destination: string, dialect: Dialect, wiki?: boolean): LinkResolution;
|
|
39
|
+
/** Obsidian's strictLineBreaks setting; false prevents semantic-changing prose reflow. */
|
|
40
|
+
strictLineBreaks?: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface RuleContext {
|
|
43
|
+
document: Document;
|
|
44
|
+
options: Record<string, unknown>;
|
|
45
|
+
workspace?: Workspace;
|
|
46
|
+
}
|
|
47
|
+
export interface Rule {
|
|
48
|
+
description: string;
|
|
49
|
+
kind: "style" | "problem";
|
|
50
|
+
/** Inline edits, then block layout, then document-wide whitespace. */
|
|
51
|
+
phase?: "inline" | "block" | "document";
|
|
52
|
+
schema?: AnySchema;
|
|
53
|
+
check(context: RuleContext): Finding[];
|
|
54
|
+
}
|
|
55
|
+
export interface Plugin {
|
|
56
|
+
name: string;
|
|
57
|
+
rules?: Record<string, Rule>;
|
|
58
|
+
presets?: Record<string, Config>;
|
|
59
|
+
syntax?: {
|
|
60
|
+
micromark: SyntaxExtension;
|
|
61
|
+
mdast: TreeExtension;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export interface Override {
|
|
65
|
+
files: string[];
|
|
66
|
+
dialect?: Dialect;
|
|
67
|
+
rules?: Record<string, RuleSetting>;
|
|
68
|
+
}
|
|
69
|
+
export interface Config {
|
|
70
|
+
extends?: string[];
|
|
71
|
+
dialect?: Dialect;
|
|
72
|
+
rules?: Record<string, RuleSetting>;
|
|
73
|
+
ignore?: string[];
|
|
74
|
+
overrides?: Override[];
|
|
75
|
+
plugins?: string[];
|
|
76
|
+
}
|
|
77
|
+
export interface ResolvedConfig {
|
|
78
|
+
dialect: Dialect;
|
|
79
|
+
rules: Record<string, RuleSetting>;
|
|
80
|
+
ignore: string[];
|
|
81
|
+
}
|
|
82
|
+
export interface ProcessOptions {
|
|
83
|
+
path?: string;
|
|
84
|
+
config?: Config;
|
|
85
|
+
plugins?: Plugin[];
|
|
86
|
+
workspace?: Workspace;
|
|
87
|
+
}
|
|
88
|
+
export interface FormatResult {
|
|
89
|
+
output: string;
|
|
90
|
+
changed: boolean;
|
|
91
|
+
diagnostics: Diagnostic[];
|
|
92
|
+
}
|
|
@@ -0,0 +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\" | \"obsidian\";\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 status: \"resolved\" | \"missing\" | \"ambiguous\" | \"external\" | \"unavailable\";\n target?: string;\n fragment?: string;\n fragmentExists?: boolean;\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?: Dialect;\n rules?: Record<string, RuleSetting>;\n}\nexport interface Config {\n extends?: string[];\n dialect?: Dialect;\n rules?: Record<string, RuleSetting>;\n ignore?: string[];\n overrides?: Override[];\n plugins?: string[];\n}\nexport interface ResolvedConfig {\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}\nexport interface FormatResult {\n output: string;\n changed: boolean;\n diagnostics: Diagnostic[];\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { lint, format, applyEdits, semanticFingerprint, builtInRules, ruleRegistry, } from "./core/engine.js";
|
|
2
|
+
export { resolveConfig, validateConfig, configSchema, presets } from "./config/resolve.js";
|
|
3
|
+
export { parse, range, textContent } from "./syntax/parse.js";
|
|
4
|
+
export { createWorkspace } from "./workspace/index.js";
|
|
5
|
+
export type { WorkspaceOptions } from "./workspace/index.js";
|
|
6
|
+
export type * from "./core/types.js";
|
|
7
|
+
export type { ObsidianLiteral } from "./syntax/obsidian.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { lint, format, applyEdits, semanticFingerprint, builtInRules, ruleRegistry, } from "./core/engine.js";
|
|
2
|
+
export { resolveConfig, validateConfig, configSchema, presets } from "./config/resolve.js";
|
|
3
|
+
export { parse, range, textContent } from "./syntax/parse.js";
|
|
4
|
+
export { createWorkspace } from "./workspace/index.js";
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC3F,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 { resolveConfig, validateConfig, configSchema, presets } from \"./config/resolve.js\";\nexport { parse, range, textContent } from \"./syntax/parse.js\";\nexport { createWorkspace } from \"./workspace/index.js\";\nexport type { WorkspaceOptions } from \"./workspace/index.js\";\nexport type * from \"./core/types.js\";\nexport type { ObsidianLiteral } from \"./syntax/obsidian.js\";\n"]}
|