remark-mdat 2.0.0 → 2.0.2
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 +21 -14
- package/dist/index.js +31 -22
- package/package.json +5 -3
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { JsonValue, MergeDeep, Simplify } from "type-fest";
|
|
|
6
6
|
import { Plugin } from "unified";
|
|
7
7
|
|
|
8
8
|
//#region src/lib/mdat/rules.d.ts
|
|
9
|
+
/** Recursively simplifies a type for cleaner IDE hover display. Approximation of type-fest's internal `SimplifyDeep`. */
|
|
9
10
|
type SimplifyDeep<T> = Simplify<MergeDeep<T, T>>;
|
|
10
11
|
/**
|
|
11
12
|
* Context passed to rule content functions during expansion.
|
|
@@ -97,7 +98,9 @@ type Rule =
|
|
|
97
98
|
* { basic-date: { order: 1, content: () => `${new Date().toISOString()}` } }
|
|
98
99
|
*/
|
|
99
100
|
type Rules = SimplifyDeep<Record<string, Rule>>;
|
|
101
|
+
/** A record mapping comment keywords to {@link NormalizedRule} objects. */
|
|
100
102
|
type NormalizedRules = SimplifyDeep<Record<string, NormalizedRule>>;
|
|
103
|
+
/** Zod schema for validating {@link Rules} records. */
|
|
101
104
|
declare const rulesSchema: z.ZodRecord<z.ZodString, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
|
|
102
105
|
/**
|
|
103
106
|
* Returns the rule value from a single-rule record.
|
|
@@ -115,7 +118,8 @@ declare function getSoleRule<T extends NormalizedRules | Rules>(rules: T): T[key
|
|
|
115
118
|
declare function getSoleRuleKey<T extends NormalizedRules | Rules>(rules: T): keyof T;
|
|
116
119
|
//#endregion
|
|
117
120
|
//#region src/lib/mdast-utils/mdast-util-mdat.d.ts
|
|
118
|
-
|
|
121
|
+
/** Mdast utility that splits, cleans, and expands all mdat comments in the tree. */
|
|
122
|
+
declare function mdat(tree: Root, file: VFile, rules: NormalizedRules | Rules): Promise<void>;
|
|
119
123
|
//#endregion
|
|
120
124
|
//#region src/lib/mdast-utils/mdast-util-mdat-clean.d.ts
|
|
121
125
|
/**
|
|
@@ -128,7 +132,7 @@ declare function mdatClean(tree: Root, file: VFile): void;
|
|
|
128
132
|
/**
|
|
129
133
|
* Mdast utility to expand mdat comments in the tree.
|
|
130
134
|
*/
|
|
131
|
-
declare function mdatExpand(tree: Root, file: VFile, rules: Rules): Promise<void>;
|
|
135
|
+
declare function mdatExpand(tree: Root, file: VFile, rules: NormalizedRules | Rules): Promise<void>;
|
|
132
136
|
//#endregion
|
|
133
137
|
//#region src/lib/mdast-utils/mdast-util-mdat-split.d.ts
|
|
134
138
|
/**
|
|
@@ -146,30 +150,33 @@ declare function mdatSplit(tree: Root, file: VFile): void;
|
|
|
146
150
|
declare function setLogger(logger?: ILogBasic | ILogLayer): void;
|
|
147
151
|
//#endregion
|
|
148
152
|
//#region src/lib/mdat/mdat-log.d.ts
|
|
149
|
-
/**
|
|
150
|
-
* Tries to provide a simpler wrapper to vfile.message
|
|
151
|
-
*/
|
|
153
|
+
/** A simplified representation of a {@link VFileMessage}. */
|
|
152
154
|
type MdatMessage = {
|
|
153
|
-
column?: number;
|
|
154
|
-
level: 'error' | 'info' | 'warn';
|
|
155
|
-
line?: number;
|
|
156
|
-
message: string;
|
|
155
|
+
/** Starting column of the message origin. */column?: number; /** Severity level. */
|
|
156
|
+
level: 'error' | 'info' | 'warn'; /** Starting line of the message origin. */
|
|
157
|
+
line?: number; /** Human-readable description of the issue. */
|
|
158
|
+
message: string; /** Namespace that produced the message (e.g. the rule name). */
|
|
157
159
|
source?: string;
|
|
158
160
|
};
|
|
161
|
+
/** Aggregated processing report for a single file. */
|
|
159
162
|
type MdatFileReport = {
|
|
160
|
-
destinationPath?: string;
|
|
161
|
-
errors: MdatMessage[];
|
|
162
|
-
infos: MdatMessage[];
|
|
163
|
-
sourcePath: string;
|
|
163
|
+
/** Output path if the file was written to a different location. */destinationPath?: string; /** Fatal errors that prevented successful processing. */
|
|
164
|
+
errors: MdatMessage[]; /** Informational messages. */
|
|
165
|
+
infos: MdatMessage[]; /** Original input file path. */
|
|
166
|
+
sourcePath: string; /** Non-fatal warnings encountered during processing. */
|
|
164
167
|
warnings: MdatMessage[];
|
|
165
168
|
};
|
|
169
|
+
/** Converts an array of processed VFiles into {@link MdatFileReport} objects. */
|
|
166
170
|
declare function getMdatReports(files: VFile[]): MdatFileReport[];
|
|
171
|
+
/** Logs a human-readable processing report for each VFile to the library logger. */
|
|
167
172
|
declare function reporterMdat(files: VFile[]): void;
|
|
168
173
|
//#endregion
|
|
169
174
|
//#region src/lib/remark-mdat.d.ts
|
|
175
|
+
/** Configuration for the remarkMdat plugin. */
|
|
170
176
|
type Options = {
|
|
171
|
-
rules?: Rules;
|
|
177
|
+
/** Rules mapping comment keywords to expansion content. Merged with built-in defaults. */rules?: Rules;
|
|
172
178
|
};
|
|
179
|
+
/** Zod schema for validating {@link Options}. */
|
|
173
180
|
declare const optionsSchema: z.ZodObject<{
|
|
174
181
|
rules: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>>;
|
|
175
182
|
}, z.core.$strip>;
|
package/dist/index.js
CHANGED
|
@@ -63,6 +63,7 @@ function vFileMessageToMdatMessage(vFileMessage) {
|
|
|
63
63
|
source: vFileMessage.source
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
|
+
/** Converts an array of processed VFiles into {@link MdatFileReport} objects. */
|
|
66
67
|
function getMdatReports(files) {
|
|
67
68
|
return files.map((file) => getMdatReport(file));
|
|
68
69
|
}
|
|
@@ -83,6 +84,7 @@ function getMdatReport(file) {
|
|
|
83
84
|
}
|
|
84
85
|
return mdatFileReport;
|
|
85
86
|
}
|
|
87
|
+
/** Logs a human-readable processing report for each VFile to the library logger. */
|
|
86
88
|
function reporterMdat(files) {
|
|
87
89
|
for (const file of files) {
|
|
88
90
|
const { destinationPath, errors, infos, sourcePath, warnings } = getMdatReport(file);
|
|
@@ -128,6 +130,9 @@ function parseCommentNode(node, parent) {
|
|
|
128
130
|
else throw new VFileMessage("Unknown error", node);
|
|
129
131
|
}
|
|
130
132
|
}
|
|
133
|
+
const HTML_COMMENT_OPEN_REGEX = /^\s*<!-{2,}\s*/;
|
|
134
|
+
const HTML_COMMENT_CLOSE_REGEX = /\s*-{2,}>\s*$/;
|
|
135
|
+
const WHITESPACE_REGEX = /\s/;
|
|
131
136
|
/**
|
|
132
137
|
* Parse any comment string into structured data.
|
|
133
138
|
* Comments using code-style notation (`//`, `#`, `/*`) are ignored and return `undefined`.
|
|
@@ -137,9 +142,9 @@ function parseComment(text) {
|
|
|
137
142
|
if (!isComment(text)) return;
|
|
138
143
|
const closingPrefix = "/";
|
|
139
144
|
const commentHtml = text.trim();
|
|
140
|
-
const commentBody = commentHtml.replace(
|
|
145
|
+
const commentBody = commentHtml.replace(HTML_COMMENT_OPEN_REGEX, "").replace(HTML_COMMENT_CLOSE_REGEX, "");
|
|
141
146
|
const parenIndex = commentBody.indexOf("(");
|
|
142
|
-
const rawKeyword = parenIndex === -1 ? commentBody.split(
|
|
147
|
+
const rawKeyword = parenIndex === -1 ? commentBody.split(WHITESPACE_REGEX)[0] : commentBody.slice(0, parenIndex).trim();
|
|
143
148
|
if (rawKeyword.startsWith("//") || rawKeyword.startsWith("#") || rawKeyword.startsWith("/*")) return;
|
|
144
149
|
const type = rawKeyword.startsWith(closingPrefix) ? "close" : "open";
|
|
145
150
|
let keyword = rawKeyword;
|
|
@@ -206,7 +211,15 @@ function mdatClean(tree, file) {
|
|
|
206
211
|
}
|
|
207
212
|
//#endregion
|
|
208
213
|
//#region src/lib/mdat/rules.ts
|
|
214
|
+
/** Brand symbol to detect pre-normalized rules and skip re-validation. */
|
|
215
|
+
const NORMALIZED = Symbol("normalized");
|
|
216
|
+
/** Check whether rules have already been normalized. */
|
|
217
|
+
function isNormalized(rules) {
|
|
218
|
+
return NORMALIZED in rules;
|
|
219
|
+
}
|
|
220
|
+
/** Converts flexible {@link Rules} into strict {@link NormalizedRules} for internal processing. */
|
|
209
221
|
function normalizeRules(rules) {
|
|
222
|
+
validateRules(rules);
|
|
210
223
|
const normalizedRules = {};
|
|
211
224
|
for (const [keyword, rule] of Object.entries(rules)) if (typeof rule === "string") normalizedRules[keyword] = {
|
|
212
225
|
content: async () => rule,
|
|
@@ -237,9 +250,10 @@ function normalizeRules(rules) {
|
|
|
237
250
|
order: rule.order ?? 0
|
|
238
251
|
};
|
|
239
252
|
}
|
|
240
|
-
|
|
253
|
+
Object.defineProperty(normalizedRules, NORMALIZED, { value: true });
|
|
241
254
|
return normalizedRules;
|
|
242
255
|
}
|
|
256
|
+
/** Validates rules against {@link rulesSchema}, throwing on invalid input. */
|
|
243
257
|
function validateRules(rules) {
|
|
244
258
|
try {
|
|
245
259
|
rulesSchema.parse(rules);
|
|
@@ -247,18 +261,7 @@ function validateRules(rules) {
|
|
|
247
261
|
if (error instanceof Error) throw new TypeError(`Error validating rules: ${error.message}`);
|
|
248
262
|
}
|
|
249
263
|
}
|
|
250
|
-
function validateNormalizedRules(rules) {
|
|
251
|
-
try {
|
|
252
|
-
normalizedRulesSchema.parse(rules);
|
|
253
|
-
} catch (error) {
|
|
254
|
-
if (error instanceof Error) throw new TypeError(`Error validating rules: ${error.message}`);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
264
|
const functionSchema = z.custom((value) => typeof value === "function");
|
|
258
|
-
const normalizedRuleSchema = z.lazy(() => z.object({
|
|
259
|
-
content: z.union([functionSchema, z.array(normalizedRuleSchema)]),
|
|
260
|
-
order: z.number()
|
|
261
|
-
}));
|
|
262
265
|
const ruleSchema = z.lazy(() => z.union([
|
|
263
266
|
functionSchema,
|
|
264
267
|
z.array(ruleSchema),
|
|
@@ -272,9 +275,10 @@ const ruleSchema = z.lazy(() => z.union([
|
|
|
272
275
|
order: z.number().optional()
|
|
273
276
|
})
|
|
274
277
|
]));
|
|
275
|
-
const
|
|
278
|
+
const COMMENT_PREFIX_REGEX = /^[/*#]/;
|
|
279
|
+
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" }));
|
|
280
|
+
/** Zod schema for validating {@link Rules} records. */
|
|
276
281
|
const rulesSchema = z.record(keywordSchema, ruleSchema).describe("MDAT Rules");
|
|
277
|
-
const normalizedRulesSchema = z.record(keywordSchema, normalizedRuleSchema).describe("MDAT Rules");
|
|
278
282
|
/**
|
|
279
283
|
* Expand rule content. For compound rules (content arrays), individual
|
|
280
284
|
* sub-rule failures are reported via `onWarning` and skipped. The entire
|
|
@@ -344,8 +348,7 @@ function getSoleRecord(record) {
|
|
|
344
348
|
* Mdast utility to expand mdat comments in the tree.
|
|
345
349
|
*/
|
|
346
350
|
async function mdatExpand(tree, file, rules) {
|
|
347
|
-
|
|
348
|
-
const normalizedRules = normalizeRules(rules);
|
|
351
|
+
const normalizedRules = isNormalized(rules) ? rules : normalizeRules(rules);
|
|
349
352
|
const frontmatter = (() => {
|
|
350
353
|
if (typeof file.value !== "string") return;
|
|
351
354
|
const { data } = matter(file.value);
|
|
@@ -368,6 +371,7 @@ async function mdatExpand(tree, file, rules) {
|
|
|
368
371
|
commentMarkers.push(commentMarker);
|
|
369
372
|
});
|
|
370
373
|
commentMarkers.sort((a, b) => normalizedRules[a.keyword].order - normalizedRules[b.keyword].order);
|
|
374
|
+
const parser = remark().use(remarkGfm);
|
|
371
375
|
for (const comment of commentMarkers) {
|
|
372
376
|
const { html, keyword, node, options, parent } = comment;
|
|
373
377
|
const rule = normalizedRules[keyword];
|
|
@@ -384,7 +388,7 @@ async function mdatExpand(tree, file, rules) {
|
|
|
384
388
|
}
|
|
385
389
|
continue;
|
|
386
390
|
}
|
|
387
|
-
const newNodes =
|
|
391
|
+
const newNodes = parser.parse(newMarkdownString).children;
|
|
388
392
|
const closingNode = {
|
|
389
393
|
type: "html",
|
|
390
394
|
value: `<!-- /${keyword} -->`
|
|
@@ -403,6 +407,8 @@ async function mdatExpand(tree, file, rules) {
|
|
|
403
407
|
function mdatSplit(tree, file) {
|
|
404
408
|
visit(tree, "html", (node, index, parent) => {
|
|
405
409
|
if (parent === void 0 || index === void 0) return CONTINUE;
|
|
410
|
+
const v = node.value;
|
|
411
|
+
if (v.startsWith("<!--") && v.endsWith("-->") && !v.includes("<!--", 4)) return CONTINUE;
|
|
406
412
|
const htmlNodes = splitHtmlIntoMdastNodes(node);
|
|
407
413
|
if (htmlNodes.length > 1) {
|
|
408
414
|
saveLog(file, "warn", "split", "Multiple comments in a single HTML node.", node);
|
|
@@ -413,6 +419,7 @@ function mdatSplit(tree, file) {
|
|
|
413
419
|
}
|
|
414
420
|
});
|
|
415
421
|
}
|
|
422
|
+
/** Splits a single mdast HTML node containing multiple comments into individual HTML and text nodes. Exported for testing. */
|
|
416
423
|
function splitHtmlIntoMdastNodes(mdastNode) {
|
|
417
424
|
const htmlTree = fromHtml(mdastNode.value, { fragment: true });
|
|
418
425
|
const mdastNodes = [];
|
|
@@ -458,6 +465,7 @@ function getOriginalMarkup(mdastNode, hastNode) {
|
|
|
458
465
|
}
|
|
459
466
|
//#endregion
|
|
460
467
|
//#region src/lib/mdast-utils/mdast-util-mdat.ts
|
|
468
|
+
/** Mdast utility that splits, cleans, and expands all mdat comments in the tree. */
|
|
461
469
|
async function mdat(tree, file, rules) {
|
|
462
470
|
mdatSplit(tree, file);
|
|
463
471
|
mdatClean(tree, file);
|
|
@@ -466,17 +474,18 @@ async function mdat(tree, file, rules) {
|
|
|
466
474
|
//#endregion
|
|
467
475
|
//#region src/lib/remark-mdat.ts
|
|
468
476
|
const defaultRules = { mdat: `Powered by the Markdown Autophagic Template system: [mdat](https://github.com/kitschpatrol/mdat).` };
|
|
477
|
+
/** Zod schema for validating {@link Options}. */
|
|
469
478
|
const optionsSchema = z.object({ rules: rulesSchema.optional() }).describe("MDAT Plugin Options");
|
|
470
479
|
/**
|
|
471
480
|
* A remark plugin that expands HTML comments in Markdown files.
|
|
472
481
|
*/
|
|
473
482
|
const remarkMdat = function(options) {
|
|
474
|
-
const
|
|
483
|
+
const normalizedRules = normalizeRules({
|
|
475
484
|
...defaultRules,
|
|
476
485
|
...options?.rules
|
|
477
|
-
};
|
|
486
|
+
});
|
|
478
487
|
return async function(tree, file) {
|
|
479
|
-
await mdat(tree, file,
|
|
488
|
+
await mdat(tree, file, normalizedRules);
|
|
480
489
|
};
|
|
481
490
|
};
|
|
482
491
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "remark-mdat",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "A remark plugin implementing the Markdown Autophagic Template (MDAT) system.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mdat",
|
|
@@ -46,7 +46,7 @@
|
|
|
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.6.0",
|
|
50
50
|
"picocolors": "^1.1.1",
|
|
51
51
|
"remark": "^15.0.1",
|
|
52
52
|
"remark-gfm": "^4.0.1",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@arethetypeswrong/core": "^0.18.2",
|
|
62
|
-
"@kitschpatrol/shared-config": "^
|
|
62
|
+
"@kitschpatrol/shared-config": "^7.0.0",
|
|
63
63
|
"@types/node": "~20.19.37",
|
|
64
64
|
"bumpp": "^11.0.1",
|
|
65
65
|
"publint": "^0.3.18",
|
|
@@ -77,6 +77,8 @@
|
|
|
77
77
|
}
|
|
78
78
|
},
|
|
79
79
|
"scripts": {
|
|
80
|
+
"bench": "vitest bench --no-file-parallelism --compare test/benchmarks/baseline.json",
|
|
81
|
+
"bench:baseline": "vitest bench --no-file-parallelism --outputJson test/benchmarks/baseline.json",
|
|
80
82
|
"build": "tsdown",
|
|
81
83
|
"clean": "git rm -f pnpm-lock.yaml ; git clean -fdX",
|
|
82
84
|
"dev": "pnpm run test",
|