remark-mdat 2.2.4 → 3.0.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/dist/index.d.ts +33 -17
- package/dist/index.js +28 -23
- package/package.json +13 -19
- package/readme.md +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ import { z } from "zod";
|
|
|
3
3
|
import { Root } from "mdast";
|
|
4
4
|
import { VFile } from "vfile";
|
|
5
5
|
import { JsonValue, MergeDeep, Simplify } from "type-fest";
|
|
6
|
+
import "unist";
|
|
6
7
|
import { Plugin } from "unified";
|
|
7
|
-
|
|
8
8
|
//#region src/lib/mdat/rules.d.ts
|
|
9
9
|
/**
|
|
10
10
|
* Recursively simplifies a type for cleaner IDE hover display. Approximation of
|
|
@@ -15,8 +15,11 @@ type SimplifyDeep<T> = Simplify<MergeDeep<T, T>>;
|
|
|
15
15
|
* Context passed to rule content functions during expansion.
|
|
16
16
|
*/
|
|
17
17
|
type RuleContext = {
|
|
18
|
-
/** File path of the source document, if known. */
|
|
19
|
-
|
|
18
|
+
/** File path of the source document, if known. */
|
|
19
|
+
filePath: string | undefined;
|
|
20
|
+
/** Parsed YAML frontmatter from the document, if present. */
|
|
21
|
+
frontmatter: Record<string, unknown> | undefined;
|
|
22
|
+
/** The full mdast AST of the document. Do not mutate. */
|
|
20
23
|
tree: Root;
|
|
21
24
|
};
|
|
22
25
|
/**
|
|
@@ -41,21 +44,21 @@ type Rule =
|
|
|
41
44
|
/**
|
|
42
45
|
* Function that returns the Markdown string to expand at the comment site.
|
|
43
46
|
*/
|
|
44
|
-
((options: JsonValue, context: RuleContext) => Promise<string> | string)
|
|
47
|
+
((options: JsonValue, context: RuleContext) => Promise<string> | string) |
|
|
45
48
|
/**
|
|
46
49
|
* Compound rules may be defined an array of rules, without keywords. Can be
|
|
47
50
|
* defined at the top level, if no validation metadata is required, or as the
|
|
48
51
|
* 'content' value of a rule object with validation metadata.
|
|
49
52
|
*/
|
|
50
|
-
|
|
53
|
+
Rule[] |
|
|
51
54
|
/**
|
|
52
55
|
* The Markdown string to expand at the comment site.
|
|
53
56
|
*/
|
|
54
|
-
|
|
|
57
|
+
string |
|
|
55
58
|
/**
|
|
56
59
|
* Rule object with optional metadata.
|
|
57
60
|
*/
|
|
58
|
-
|
|
61
|
+
{
|
|
59
62
|
/**
|
|
60
63
|
* Gets content to expand into the comment. Can be a simple string for
|
|
61
64
|
* direct replacement, a function that returns a string, or an async
|
|
@@ -152,8 +155,11 @@ declare const mdatClean: typeof mdatCollapse;
|
|
|
152
155
|
//#region src/lib/mdast-utils/mdast-util-mdat-diff.d.ts
|
|
153
156
|
/** Per-tag comparison result from {@link mdatDiff}. */
|
|
154
157
|
type MdatDiffResult = {
|
|
155
|
-
/** The keyword for this tag. */
|
|
156
|
-
|
|
158
|
+
/** The keyword for this tag. */
|
|
159
|
+
keyword: string;
|
|
160
|
+
/** 1-based line number of the opening comment in the expanded document. */
|
|
161
|
+
line: number;
|
|
162
|
+
/** Comparison status. */
|
|
157
163
|
status: 'added' | 'missing' | 'ok' | 'stale' | 'unexpanded';
|
|
158
164
|
};
|
|
159
165
|
/**
|
|
@@ -204,18 +210,28 @@ declare function setLogger(logger?: ILogBasic | ILogLayer<unknown>): void;
|
|
|
204
210
|
//#region src/lib/mdat/mdat-log.d.ts
|
|
205
211
|
/** A simplified representation of a {@link VFileMessage}. */
|
|
206
212
|
type MdatMessage = {
|
|
207
|
-
/** Starting column of the message origin. */
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
213
|
+
/** Starting column of the message origin. */
|
|
214
|
+
column?: number;
|
|
215
|
+
/** Severity level. */
|
|
216
|
+
level: 'error' | 'info' | 'warn';
|
|
217
|
+
/** Starting line of the message origin. */
|
|
218
|
+
line?: number;
|
|
219
|
+
/** Human-readable description of the issue. */
|
|
220
|
+
message: string;
|
|
221
|
+
/** Namespace that produced the message (e.g. the rule name). */
|
|
211
222
|
source?: string;
|
|
212
223
|
};
|
|
213
224
|
/** Aggregated processing report for a single file. */
|
|
214
225
|
type MdatFileReport = {
|
|
215
|
-
/** Output path if the file was written to a different location. */
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
226
|
+
/** Output path if the file was written to a different location. */
|
|
227
|
+
destinationPath?: string;
|
|
228
|
+
/** Fatal errors that prevented successful processing. */
|
|
229
|
+
errors: MdatMessage[];
|
|
230
|
+
/** Informational messages. */
|
|
231
|
+
infos: MdatMessage[];
|
|
232
|
+
/** Original input file path. */
|
|
233
|
+
sourcePath: string;
|
|
234
|
+
/** Non-fatal warnings encountered during processing. */
|
|
219
235
|
warnings: MdatMessage[];
|
|
220
236
|
};
|
|
221
237
|
/** Converts an array of processed VFiles into {@link MdatFileReport} objects. */
|
package/dist/index.js
CHANGED
|
@@ -102,12 +102,12 @@ function reporterMdat(files) {
|
|
|
102
102
|
}
|
|
103
103
|
function mdatMessageToLogString(sourcePath, mdatMessage) {
|
|
104
104
|
const { column, level, line, message, source } = mdatMessage;
|
|
105
|
-
const resolvedSource = source ? picocolors.gray(`[${source}] `) : "";
|
|
106
|
-
const lineColumn = line && column ? `:${line}:${column}` : "";
|
|
105
|
+
const resolvedSource = source !== void 0 && source !== "" ? picocolors.gray(`[${source}] `) : "";
|
|
106
|
+
const lineColumn = line !== void 0 && line !== 0 && column !== void 0 && column !== 0 ? `:${line}:${column}` : "";
|
|
107
107
|
return `${resolvedSource}${highlightComments(message, level)} ${picocolors.whiteBright(sourcePath + lineColumn)}`;
|
|
108
108
|
}
|
|
109
109
|
function highlightComments(text, level) {
|
|
110
|
-
return text.replaceAll(/<!--.+-->/
|
|
110
|
+
return text.replaceAll(/<!--.+-->/gv, (match) => level === "info" ? picocolors.green(match) : level === "warn" ? picocolors.yellow(match) : picocolors.red(match));
|
|
111
111
|
}
|
|
112
112
|
//#endregion
|
|
113
113
|
//#region src/lib/mdat/parse.ts
|
|
@@ -135,9 +135,9 @@ function parseCommentNode(node, parent) {
|
|
|
135
135
|
throw new VFileMessage("Unknown error", node);
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
|
-
const HTML_COMMENT_OPEN_REGEX = /^\s*<!-{2,}\s
|
|
139
|
-
const HTML_COMMENT_CLOSE_REGEX = /\s*-{2,}>\s
|
|
140
|
-
const WHITESPACE_REGEX = /\s
|
|
138
|
+
const HTML_COMMENT_OPEN_REGEX = /^\s*<!-{2,}\s*/v;
|
|
139
|
+
const HTML_COMMENT_CLOSE_REGEX = /\s*-{2,}>\s*$/v;
|
|
140
|
+
const WHITESPACE_REGEX = /\s/v;
|
|
141
141
|
/**
|
|
142
142
|
* Parse any comment string into structured data. Comments using code-style
|
|
143
143
|
* notation (`//`, `#`, `/*`) are ignored and return `undefined`.
|
|
@@ -147,13 +147,12 @@ const WHITESPACE_REGEX = /\s/;
|
|
|
147
147
|
*/
|
|
148
148
|
function parseComment(text) {
|
|
149
149
|
if (!isComment(text)) return;
|
|
150
|
-
const closingPrefix = "/";
|
|
151
150
|
const commentHtml = text.trim();
|
|
152
151
|
const commentBody = commentHtml.replace(HTML_COMMENT_OPEN_REGEX, "").replace(HTML_COMMENT_CLOSE_REGEX, "");
|
|
153
152
|
const parenIndex = commentBody.indexOf("(");
|
|
154
|
-
const rawKeyword = parenIndex === -1 ? commentBody.split(WHITESPACE_REGEX)[0] : commentBody.slice(0, parenIndex).trim();
|
|
153
|
+
const rawKeyword = parenIndex === -1 ? commentBody.split(WHITESPACE_REGEX)[0] ?? "" : commentBody.slice(0, parenIndex).trim();
|
|
155
154
|
if (rawKeyword.startsWith("//") || rawKeyword.startsWith("#") || rawKeyword.startsWith("/*")) return;
|
|
156
|
-
const type = rawKeyword.startsWith(
|
|
155
|
+
const type = rawKeyword.startsWith("/") ? "close" : "open";
|
|
157
156
|
let keyword = rawKeyword;
|
|
158
157
|
if (type === "close") keyword = keyword.slice(1);
|
|
159
158
|
let options = {};
|
|
@@ -221,7 +220,7 @@ function mdatCollapse(tree, file) {
|
|
|
221
220
|
const NORMALIZED = Symbol("normalized");
|
|
222
221
|
/** Check whether rules have already been normalized. */
|
|
223
222
|
function isNormalized(rules) {
|
|
224
|
-
return NORMALIZED
|
|
223
|
+
return Object.hasOwn(rules, NORMALIZED);
|
|
225
224
|
}
|
|
226
225
|
/**
|
|
227
226
|
* Converts flexible {@link Rules} into strict {@link NormalizedRules} for
|
|
@@ -267,7 +266,7 @@ function validateRules(rules) {
|
|
|
267
266
|
try {
|
|
268
267
|
rulesSchema.parse(rules);
|
|
269
268
|
} catch (error) {
|
|
270
|
-
if (error instanceof Error) throw new TypeError(`Error validating rules: ${error.message}
|
|
269
|
+
if (error instanceof Error) throw new TypeError(`Error validating rules: ${error.message}`, { cause: error });
|
|
271
270
|
}
|
|
272
271
|
}
|
|
273
272
|
const functionSchema = z.custom((value) => typeof value === "function");
|
|
@@ -284,7 +283,7 @@ const ruleSchema = z.lazy(() => z.union([
|
|
|
284
283
|
order: z.number().optional()
|
|
285
284
|
})
|
|
286
285
|
]));
|
|
287
|
-
const COMMENT_PREFIX_REGEX = /^[
|
|
286
|
+
const COMMENT_PREFIX_REGEX = /^[\/*#]/v;
|
|
288
287
|
const keywordSchema = z.string().check(z.refine((key) => !COMMENT_PREFIX_REGEX.test(key), { message: "Rule keywords must not start with \"/\", \"*\", or \"#\" — these prefixes are reserved for comment syntax" }));
|
|
289
288
|
/** Zod schema for validating {@link Rules} records. */
|
|
290
289
|
const rulesSchema = z.record(keywordSchema, ruleSchema).describe("MDAT Rules");
|
|
@@ -333,8 +332,9 @@ function getSoleRule(rules) {
|
|
|
333
332
|
*/
|
|
334
333
|
function getSoleRuleKey(rules) {
|
|
335
334
|
const keys = Object.keys(rules);
|
|
336
|
-
|
|
337
|
-
|
|
335
|
+
const [soleKey] = keys;
|
|
336
|
+
if (soleKey === void 0 || keys.length > 1) throw new Error(`Expected exactly one rule, found ${keys.length}`);
|
|
337
|
+
return soleKey;
|
|
338
338
|
}
|
|
339
339
|
/**
|
|
340
340
|
* Get the sole entry in a record.
|
|
@@ -375,18 +375,22 @@ async function mdatExpand(tree, file, rules) {
|
|
|
375
375
|
if (parent === void 0 || index === void 0) return CONTINUE;
|
|
376
376
|
const commentMarker = parseCommentNode(node, parent);
|
|
377
377
|
if (commentMarker?.type !== "open") return CONTINUE;
|
|
378
|
-
|
|
378
|
+
const rule = normalizedRules[commentMarker.keyword];
|
|
379
|
+
if (rule === void 0) {
|
|
379
380
|
saveLog(file, "warn", "expand", `Missing rule for: ${commentMarker.html}`, node);
|
|
380
381
|
return CONTINUE;
|
|
381
382
|
}
|
|
382
|
-
commentMarkers.push(
|
|
383
|
+
commentMarkers.push({
|
|
384
|
+
marker: commentMarker,
|
|
385
|
+
rule
|
|
386
|
+
});
|
|
387
|
+
return CONTINUE;
|
|
383
388
|
});
|
|
384
|
-
commentMarkers.sort((a, b) =>
|
|
389
|
+
commentMarkers.sort((a, b) => a.rule.order - b.rule.order);
|
|
385
390
|
const parser = remark().use(remarkGfm);
|
|
386
|
-
for (const
|
|
387
|
-
const { html, keyword, node, options, parent } =
|
|
388
|
-
|
|
389
|
-
let newMarkdownString = "";
|
|
391
|
+
for (const { marker, rule } of commentMarkers) {
|
|
392
|
+
const { html, keyword, node, options, parent } = marker;
|
|
393
|
+
let newMarkdownString;
|
|
390
394
|
try {
|
|
391
395
|
newMarkdownString = await getRuleContent(rule, options, context, (warning) => {
|
|
392
396
|
saveLog(file, "warn", "expand", `${html}: ${warning}`, node);
|
|
@@ -424,11 +428,12 @@ function mdatSplit(tree, file) {
|
|
|
424
428
|
const htmlNodes = splitHtmlIntoMdastNodes(node);
|
|
425
429
|
if (htmlNodes.length > 1) {
|
|
426
430
|
saveLog(file, "warn", "split", "Multiple comments in a single HTML node.", node);
|
|
427
|
-
parent.children
|
|
431
|
+
parent.children[index] = {
|
|
428
432
|
children: htmlNodes,
|
|
429
433
|
type: "paragraph"
|
|
430
|
-
}
|
|
434
|
+
};
|
|
431
435
|
}
|
|
436
|
+
return CONTINUE;
|
|
432
437
|
});
|
|
433
438
|
}
|
|
434
439
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "remark-mdat",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "A remark plugin implementing the Markdown Autophagic Template (MDAT) system.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mdat",
|
|
@@ -46,11 +46,11 @@
|
|
|
46
46
|
"gray-matter-es": "^0.2.1",
|
|
47
47
|
"hast-util-from-html": "^2.0.3",
|
|
48
48
|
"json5": "^2.2.3",
|
|
49
|
-
"lognow": "^0.
|
|
49
|
+
"lognow": "^0.7.1",
|
|
50
50
|
"picocolors": "^1.1.1",
|
|
51
51
|
"remark": "^15.0.1",
|
|
52
52
|
"remark-gfm": "^4.0.1",
|
|
53
|
-
"type-fest": "^5.
|
|
53
|
+
"type-fest": "^5.8.0",
|
|
54
54
|
"unified": "^11.0.5",
|
|
55
55
|
"unist-util-visit": "^5.1.0",
|
|
56
56
|
"vfile": "^6.0.3",
|
|
@@ -58,25 +58,19 @@
|
|
|
58
58
|
"zod": "^4.4.3"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@arethetypeswrong/core": "^0.18.
|
|
62
|
-
"@kitschpatrol/shared-config": "^
|
|
63
|
-
"@types/node": "~
|
|
64
|
-
"@vitest/coverage-v8": "^4.1.
|
|
65
|
-
"bumpp": "^
|
|
66
|
-
"publint": "^0.3.
|
|
61
|
+
"@arethetypeswrong/core": "^0.18.5",
|
|
62
|
+
"@kitschpatrol/shared-config": "^8.4.0",
|
|
63
|
+
"@types/node": "~24.13.3",
|
|
64
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
65
|
+
"bumpp": "^12.0.0",
|
|
66
|
+
"publint": "^0.3.22",
|
|
67
67
|
"shx": "^0.4.0",
|
|
68
|
-
"tsdown": "^0.22.
|
|
69
|
-
"typescript": "~
|
|
70
|
-
"vitest": "^4.1.
|
|
68
|
+
"tsdown": "^0.22.14",
|
|
69
|
+
"typescript": "~6.0.3",
|
|
70
|
+
"vitest": "^4.1.10"
|
|
71
71
|
},
|
|
72
72
|
"engines": {
|
|
73
|
-
"node": ">=
|
|
74
|
-
},
|
|
75
|
-
"devEngines": {
|
|
76
|
-
"runtime": {
|
|
77
|
-
"name": "node",
|
|
78
|
-
"version": ">=22.22.2"
|
|
79
|
-
}
|
|
73
|
+
"node": ">=24.16.0"
|
|
80
74
|
},
|
|
81
75
|
"scripts": {
|
|
82
76
|
"bench": "vitest bench --run --no-file-parallelism --compare test/benchmarks/baseline.json",
|
package/readme.md
CHANGED
|
@@ -81,7 +81,7 @@ This plugin powers the higher-level [`mdat` package](https://github.com/kitschpa
|
|
|
81
81
|
|
|
82
82
|
### Dependencies
|
|
83
83
|
|
|
84
|
-
This library is ESM only and requires Node
|
|
84
|
+
This library is ESM only and requires Node 24.16.0+. It's designed to work with Remark 15. `remark-mdat` is implemented in TypeScript and bundles a complete set of type definitions.
|
|
85
85
|
|
|
86
86
|
### Installation
|
|
87
87
|
|