remark-mdat 2.0.2 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +98 -48
- package/dist/index.js +206 -43
- package/package.json +6 -4
- package/readme.md +24 -2
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,10 @@ 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
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Recursively simplifies a type for cleaner IDE hover display. Approximation of
|
|
11
|
+
* type-fest's internal `SimplifyDeep`.
|
|
12
|
+
*/
|
|
10
13
|
type SimplifyDeep<T> = Simplify<MergeDeep<T, T>>;
|
|
11
14
|
/**
|
|
12
15
|
* Context passed to rule content functions during expansion.
|
|
@@ -17,19 +20,19 @@ type RuleContext = {
|
|
|
17
20
|
tree: Root;
|
|
18
21
|
};
|
|
19
22
|
/**
|
|
20
|
-
* Strict normalized rules used internally.
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
+
* Strict normalized rules used internally. Rules normalized to a form with
|
|
24
|
+
* async content functions and other default metadata Simplifies processing
|
|
25
|
+
* elsewhere, while retaining flexibility for rule authors
|
|
23
26
|
*/
|
|
24
27
|
type NormalizedRule = {
|
|
25
28
|
/**
|
|
26
|
-
* The function that generates the expanded Markdown string.
|
|
27
|
-
*
|
|
29
|
+
* The function that generates the expanded Markdown string. For 'compound'
|
|
30
|
+
* rules, this can be an array of rules (without keywords).
|
|
28
31
|
*/
|
|
29
32
|
content: ((options: JsonValue, context: RuleContext) => Promise<string>) | NormalizedRule[];
|
|
30
33
|
/**
|
|
31
|
-
* The order in which the rule should be applied during processing
|
|
32
|
-
*
|
|
34
|
+
* The order in which the rule should be applied during processing Helpful if
|
|
35
|
+
* a rule depends on the presence of content generated by another rule
|
|
33
36
|
* Defaults to 0.
|
|
34
37
|
*/
|
|
35
38
|
order: number;
|
|
@@ -40,9 +43,9 @@ type Rule =
|
|
|
40
43
|
*/
|
|
41
44
|
((options: JsonValue, context: RuleContext) => Promise<string> | string)
|
|
42
45
|
/**
|
|
43
|
-
* Compound rules may be defined an array of rules, without keywords.
|
|
44
|
-
*
|
|
45
|
-
* of a rule object with validation metadata.
|
|
46
|
+
* Compound rules may be defined an array of rules, without keywords. Can be
|
|
47
|
+
* defined at the top level, if no validation metadata is required, or as the
|
|
48
|
+
* 'content' value of a rule object with validation metadata.
|
|
46
49
|
*/
|
|
47
50
|
| Rule[]
|
|
48
51
|
/**
|
|
@@ -54,18 +57,22 @@ type Rule =
|
|
|
54
57
|
*/
|
|
55
58
|
| {
|
|
56
59
|
/**
|
|
57
|
-
* Gets content to expand into the comment.
|
|
58
|
-
*
|
|
60
|
+
* Gets content to expand into the comment. Can be a simple string for
|
|
61
|
+
* direct replacement, a function that returns a string, or an async
|
|
62
|
+
* function that returns a string.
|
|
59
63
|
*
|
|
60
64
|
* If a function is provided, it will be passed the following arguments:
|
|
61
|
-
*
|
|
62
|
-
* JSON value of options parsed immediately after the
|
|
63
|
-
* `<!-- keyword({something:
|
|
64
|
-
* `<!-- keyword {something: true}-->`
|
|
65
|
-
*
|
|
66
|
-
* @param context
|
|
67
|
-
*
|
|
68
|
-
*
|
|
65
|
+
*
|
|
66
|
+
* @param options JSON value of options parsed immediately after the
|
|
67
|
+
* comment keyword in the comment, e.g.: `<!-- keyword({something:
|
|
68
|
+
* true}) -->` or `<!-- keyword {something: true}-->` Sets options to
|
|
69
|
+
* {something: true}
|
|
70
|
+
* @param context Rule context containing the mdast AST, parsed
|
|
71
|
+
* frontmatter, and file path.
|
|
72
|
+
*
|
|
73
|
+
* @returns A string with the generated content. The string will be parsed
|
|
74
|
+
* as Markdown and inserted into the document at the comment's
|
|
75
|
+
* location.
|
|
69
76
|
*/
|
|
70
77
|
content: ((options: JsonValue, context: RuleContext) => Promise<string> | string) | Rule[] | string;
|
|
71
78
|
/**
|
|
@@ -75,27 +82,30 @@ type Rule =
|
|
|
75
82
|
order?: number;
|
|
76
83
|
};
|
|
77
84
|
/**
|
|
78
|
-
* Rules are record objects whose keys match strings inside a Markdown comment,
|
|
85
|
+
* Rules are record objects whose keys match strings inside a Markdown comment,
|
|
86
|
+
* and values explain what should be expanded at the comment site.
|
|
87
|
+
*
|
|
88
|
+
* The record value may be a string, or an object containing additional
|
|
89
|
+
* metadata, possibly with a function to invoke to generate content.
|
|
79
90
|
*
|
|
80
|
-
* The record value may be a string, or an object containing additional metadata, possibly with a function to invoke to generate content.
|
|
81
91
|
* @example
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
92
|
+
* Most basic rule:
|
|
93
|
+
* ```ts
|
|
94
|
+
* { basic: 'content' }
|
|
95
|
+
* ```
|
|
86
96
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
97
|
+
* Rule with dynamic content:
|
|
98
|
+
* ```ts
|
|
99
|
+
* { basic: () => `${new Date().toISOString()}` }
|
|
100
|
+
* ```
|
|
91
101
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
102
|
+
* Rule with metadata:
|
|
103
|
+
* ```ts
|
|
104
|
+
* { basic-meta: { order: 1, content: 'content'} }
|
|
105
|
+
* ```
|
|
96
106
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
107
|
+
* Rule with dynamic content and metadata:
|
|
108
|
+
* { basic-date: { order: 1, content: () => `${new Date().toISOString()}` } }
|
|
99
109
|
*/
|
|
100
110
|
type Rules = SimplifyDeep<Record<string, Rule>>;
|
|
101
111
|
/** A record mapping comment keywords to {@link NormalizedRule} objects. */
|
|
@@ -103,30 +113,61 @@ type NormalizedRules = SimplifyDeep<Record<string, NormalizedRule>>;
|
|
|
103
113
|
/** Zod schema for validating {@link Rules} records. */
|
|
104
114
|
declare const rulesSchema: z.ZodRecord<z.ZodString, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
|
|
105
115
|
/**
|
|
106
|
-
* Returns the rule value from a single-rule record.
|
|
107
|
-
*
|
|
116
|
+
* Returns the rule value from a single-rule record. Useful when aliasing rules
|
|
117
|
+
* or invoking them programmatically.
|
|
108
118
|
*
|
|
109
119
|
* Throws if there are no entries or more than one entry.
|
|
110
120
|
*/
|
|
111
121
|
declare function getSoleRule<T extends NormalizedRules | Rules>(rules: T): T[keyof T];
|
|
112
122
|
/**
|
|
113
|
-
* Returns the rule key from a single-rule record.
|
|
114
|
-
*
|
|
123
|
+
* Returns the rule key from a single-rule record. Useful for comment
|
|
124
|
+
* placeholder validation.
|
|
115
125
|
*
|
|
116
126
|
* Throws if there are no entries or more than one entry.
|
|
117
127
|
*/
|
|
118
128
|
declare function getSoleRuleKey<T extends NormalizedRules | Rules>(rules: T): keyof T;
|
|
119
129
|
//#endregion
|
|
120
130
|
//#region src/lib/mdast-utils/mdast-util-mdat.d.ts
|
|
121
|
-
/**
|
|
131
|
+
/**
|
|
132
|
+
* Mdast utility that splits, collapses, and then re-expands all mdat comments
|
|
133
|
+
* in the tree.
|
|
134
|
+
*/
|
|
122
135
|
declare function mdat(tree: Root, file: VFile, rules: NormalizedRules | Rules): Promise<void>;
|
|
123
136
|
//#endregion
|
|
124
|
-
//#region src/lib/mdast-utils/mdast-util-mdat-
|
|
137
|
+
//#region src/lib/mdast-utils/mdast-util-mdat-collapse.d.ts
|
|
125
138
|
/**
|
|
126
139
|
* Collapses any expanded mdat comments, effectively resetting the document to
|
|
127
|
-
* its pre-expansion state. No-op if no mdat
|
|
140
|
+
* its pre-expansion state, preserving the original comments. No-op if no mdat
|
|
141
|
+
* comments are found.
|
|
142
|
+
*/
|
|
143
|
+
declare function mdatCollapse(tree: Root, file: VFile): void;
|
|
144
|
+
//#endregion
|
|
145
|
+
//#region src/lib/mdast-utils/mdast-util-mdat-clean.d.ts
|
|
146
|
+
/**
|
|
147
|
+
* @deprecated Use {@link mdatCollapse} instead. This alias will be removed in a
|
|
148
|
+
* future major version.
|
|
128
149
|
*/
|
|
129
|
-
declare
|
|
150
|
+
declare const mdatClean: typeof mdatCollapse;
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/lib/mdast-utils/mdast-util-mdat-diff.d.ts
|
|
153
|
+
/** Per-tag comparison result from {@link mdatDiff}. */
|
|
154
|
+
type MdatDiffResult = {
|
|
155
|
+
/** The keyword for this tag. */keyword: string; /** 1-based line number of the opening comment in the expanded document. */
|
|
156
|
+
line: number; /** Comparison status. */
|
|
157
|
+
status: 'added' | 'missing' | 'ok' | 'stale' | 'unexpanded';
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* Compare original and expanded documents per-tag. Walks both ASTs to extract
|
|
161
|
+
* content between open/close comment markers, then compares per-tag.
|
|
162
|
+
*
|
|
163
|
+
* Callers should run {@link mdatSplit} on both trees before calling this
|
|
164
|
+
* function to ensure multi-comment nodes are split into individual nodes.
|
|
165
|
+
*
|
|
166
|
+
* Adds diagnostic messages to `expandedFile` via the VFile message pipeline.
|
|
167
|
+
*
|
|
168
|
+
* @returns Per-tag comparison results.
|
|
169
|
+
*/
|
|
170
|
+
declare function mdatDiff(originalTree: Root, originalFile: VFile, expandedTree: Root, expandedFile: VFile): MdatDiffResult[];
|
|
130
171
|
//#endregion
|
|
131
172
|
//#region src/lib/mdast-utils/mdast-util-mdat-expand.d.ts
|
|
132
173
|
/**
|
|
@@ -136,11 +177,20 @@ declare function mdatExpand(tree: Root, file: VFile, rules: NormalizedRules | Ru
|
|
|
136
177
|
//#endregion
|
|
137
178
|
//#region src/lib/mdast-utils/mdast-util-mdat-split.d.ts
|
|
138
179
|
/**
|
|
139
|
-
* Mdast utility plugin to split any multi-comment nodes and their content into
|
|
140
|
-
* nodes. They're wrapped in a paragraph so as not to
|
|
180
|
+
* Mdast utility plugin to split any multi-comment nodes and their content into
|
|
181
|
+
* individual MDAST HTML nodes. They're wrapped in a paragraph so as not to
|
|
182
|
+
* introduce new breaks.
|
|
141
183
|
*/
|
|
142
184
|
declare function mdatSplit(tree: Root, file: VFile): void;
|
|
143
185
|
//#endregion
|
|
186
|
+
//#region src/lib/mdast-utils/mdast-util-mdat-strip.d.ts
|
|
187
|
+
/**
|
|
188
|
+
* Strips all mdat comment nodes (both opening and closing) from the tree,
|
|
189
|
+
* preserving any content between them. Code-style comments (`//`, `#`, `/*`)
|
|
190
|
+
* are left untouched.
|
|
191
|
+
*/
|
|
192
|
+
declare function mdatStrip(tree: Root, _file: VFile): void;
|
|
193
|
+
//#endregion
|
|
144
194
|
//#region src/lib/mdat/log.d.ts
|
|
145
195
|
/**
|
|
146
196
|
* Set the logger instance for the module.
|
|
@@ -185,4 +235,4 @@ declare const optionsSchema: z.ZodObject<{
|
|
|
185
235
|
*/
|
|
186
236
|
declare const remarkMdat: Plugin<[Options?], Root>;
|
|
187
237
|
//#endregion
|
|
188
|
-
export { type MdatFileReport, type MdatMessage, type NormalizedRule, type NormalizedRules, type Options, type Rule, type RuleContext, type Rules, type SimplifyDeep, remarkMdat as default, getMdatReports, getSoleRule, getSoleRuleKey, mdat, mdatClean, mdatExpand, mdatSplit, optionsSchema, reporterMdat, rulesSchema, setLogger };
|
|
238
|
+
export { type MdatDiffResult, type MdatFileReport, type MdatMessage, type NormalizedRule, type NormalizedRules, type Options, type Rule, type RuleContext, type Rules, type SimplifyDeep, remarkMdat as default, getMdatReports, getSoleRule, getSoleRuleKey, mdat, mdatClean, mdatCollapse, mdatDiff, mdatExpand, mdatSplit, mdatStrip, optionsSchema, reporterMdat, rulesSchema, setLogger };
|
package/dist/index.js
CHANGED
|
@@ -111,7 +111,9 @@ function highlightComments(text, level) {
|
|
|
111
111
|
//#region src/lib/mdat/parse.ts
|
|
112
112
|
/**
|
|
113
113
|
* Parse an Mdast HTML comment node into structured data.
|
|
114
|
-
*
|
|
114
|
+
*
|
|
115
|
+
* @returns A CommentMarkerNode or undefined if the node is not a recognized
|
|
116
|
+
* comment.
|
|
115
117
|
*/
|
|
116
118
|
function parseCommentNode(node, parent) {
|
|
117
119
|
try {
|
|
@@ -126,17 +128,20 @@ function parseCommentNode(node, parent) {
|
|
|
126
128
|
if (error instanceof VFileMessage) {
|
|
127
129
|
error.line = node.position?.start.line;
|
|
128
130
|
throw error;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
+
}
|
|
132
|
+
if (error instanceof Error) throw new VFileMessage(error.message, node);
|
|
133
|
+
throw new VFileMessage("Unknown error", node);
|
|
131
134
|
}
|
|
132
135
|
}
|
|
133
136
|
const HTML_COMMENT_OPEN_REGEX = /^\s*<!-{2,}\s*/;
|
|
134
137
|
const HTML_COMMENT_CLOSE_REGEX = /\s*-{2,}>\s*$/;
|
|
135
138
|
const WHITESPACE_REGEX = /\s/;
|
|
136
139
|
/**
|
|
137
|
-
* Parse any comment string into structured data.
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
+
* Parse any comment string into structured data. Comments using code-style
|
|
141
|
+
* notation (`//`, `#`, `/*`) are ignored and return `undefined`.
|
|
142
|
+
*
|
|
143
|
+
* @returns A CommentMarker or undefined if the node is not a recognized
|
|
144
|
+
* comment.
|
|
140
145
|
*/
|
|
141
146
|
function parseComment(text) {
|
|
142
147
|
if (!isComment(text)) return;
|
|
@@ -173,12 +178,13 @@ function isComment(text) {
|
|
|
173
178
|
return trimmed.startsWith("<!--") && trimmed.endsWith("-->");
|
|
174
179
|
}
|
|
175
180
|
//#endregion
|
|
176
|
-
//#region src/lib/mdast-utils/mdast-util-mdat-
|
|
181
|
+
//#region src/lib/mdast-utils/mdast-util-mdat-collapse.ts
|
|
177
182
|
/**
|
|
178
183
|
* Collapses any expanded mdat comments, effectively resetting the document to
|
|
179
|
-
* its pre-expansion state. No-op if no mdat
|
|
184
|
+
* its pre-expansion state, preserving the original comments. No-op if no mdat
|
|
185
|
+
* comments are found.
|
|
180
186
|
*/
|
|
181
|
-
function
|
|
187
|
+
function mdatCollapse(tree, file) {
|
|
182
188
|
let lastOpenMarker;
|
|
183
189
|
visit(tree, "html", (node, index, parent) => {
|
|
184
190
|
if (parent === void 0 || index === void 0) return CONTINUE;
|
|
@@ -188,25 +194,23 @@ function mdatClean(tree, file) {
|
|
|
188
194
|
lastOpenMarker = marker;
|
|
189
195
|
return CONTINUE;
|
|
190
196
|
}
|
|
191
|
-
if (
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
return CONTINUE;
|
|
195
|
-
}
|
|
196
|
-
if (lastOpenMarker.parent !== marker.parent) {
|
|
197
|
-
saveLog(file, "error", "clean", "Opening marker doesn't share a parent", node);
|
|
198
|
-
return CONTINUE;
|
|
199
|
-
}
|
|
200
|
-
if (lastOpenMarker.keyword !== marker.keyword) {
|
|
201
|
-
saveLog(file, "error", "clean", "Opening marker doesn't share a keyword", node);
|
|
202
|
-
return CONTINUE;
|
|
203
|
-
}
|
|
204
|
-
const openMarkerIndex = parent.children.indexOf(lastOpenMarker.node);
|
|
205
|
-
const nodesToRemove = parent.children.indexOf(marker.node) - openMarkerIndex + 1;
|
|
206
|
-
parent.children.splice(openMarkerIndex + 1, nodesToRemove - 1);
|
|
207
|
-
lastOpenMarker = void 0;
|
|
208
|
-
return [CONTINUE, index - nodesToRemove + 1];
|
|
197
|
+
if (lastOpenMarker === void 0) {
|
|
198
|
+
saveLog(file, "error", "collapse", "Found closing marker without opening marker", node);
|
|
199
|
+
return CONTINUE;
|
|
209
200
|
}
|
|
201
|
+
if (lastOpenMarker.parent !== marker.parent) {
|
|
202
|
+
saveLog(file, "error", "collapse", "Opening marker doesn't share a parent", node);
|
|
203
|
+
return CONTINUE;
|
|
204
|
+
}
|
|
205
|
+
if (lastOpenMarker.keyword !== marker.keyword) {
|
|
206
|
+
saveLog(file, "error", "collapse", "Opening marker doesn't share a keyword", node);
|
|
207
|
+
return CONTINUE;
|
|
208
|
+
}
|
|
209
|
+
const openMarkerIndex = parent.children.indexOf(lastOpenMarker.node);
|
|
210
|
+
const nodesToRemove = parent.children.indexOf(marker.node) - openMarkerIndex + 1;
|
|
211
|
+
parent.children.splice(openMarkerIndex + 1, nodesToRemove - 1);
|
|
212
|
+
lastOpenMarker = void 0;
|
|
213
|
+
return [CONTINUE, index - nodesToRemove + 1];
|
|
210
214
|
});
|
|
211
215
|
}
|
|
212
216
|
//#endregion
|
|
@@ -217,7 +221,10 @@ const NORMALIZED = Symbol("normalized");
|
|
|
217
221
|
function isNormalized(rules) {
|
|
218
222
|
return NORMALIZED in rules;
|
|
219
223
|
}
|
|
220
|
-
/**
|
|
224
|
+
/**
|
|
225
|
+
* Converts flexible {@link Rules} into strict {@link NormalizedRules} for
|
|
226
|
+
* internal processing.
|
|
227
|
+
*/
|
|
221
228
|
function normalizeRules(rules) {
|
|
222
229
|
validateRules(rules);
|
|
223
230
|
const normalizedRules = {};
|
|
@@ -280,9 +287,9 @@ const keywordSchema = z.string().check(z.refine((key) => !COMMENT_PREFIX_REGEX.t
|
|
|
280
287
|
/** Zod schema for validating {@link Rules} records. */
|
|
281
288
|
const rulesSchema = z.record(keywordSchema, ruleSchema).describe("MDAT Rules");
|
|
282
289
|
/**
|
|
283
|
-
* Expand rule content. For compound rules (content arrays), individual
|
|
284
|
-
*
|
|
285
|
-
*
|
|
290
|
+
* Expand rule content. For compound rules (content arrays), individual sub-rule
|
|
291
|
+
* failures are reported via `onWarning` and skipped. The entire expansion only
|
|
292
|
+
* fails if every sub-rule fails.
|
|
286
293
|
*/
|
|
287
294
|
async function getRuleContent(rule, options, context, onWarning) {
|
|
288
295
|
if (Array.isArray(rule.content)) {
|
|
@@ -308,8 +315,8 @@ async function getRuleContent(rule, options, context, onWarning) {
|
|
|
308
315
|
}
|
|
309
316
|
}
|
|
310
317
|
/**
|
|
311
|
-
* Returns the rule value from a single-rule record.
|
|
312
|
-
*
|
|
318
|
+
* Returns the rule value from a single-rule record. Useful when aliasing rules
|
|
319
|
+
* or invoking them programmatically.
|
|
313
320
|
*
|
|
314
321
|
* Throws if there are no entries or more than one entry.
|
|
315
322
|
*/
|
|
@@ -317,8 +324,8 @@ function getSoleRule(rules) {
|
|
|
317
324
|
return getSoleRecord(rules);
|
|
318
325
|
}
|
|
319
326
|
/**
|
|
320
|
-
* Returns the rule key from a single-rule record.
|
|
321
|
-
*
|
|
327
|
+
* Returns the rule key from a single-rule record. Useful for comment
|
|
328
|
+
* placeholder validation.
|
|
322
329
|
*
|
|
323
330
|
* Throws if there are no entries or more than one entry.
|
|
324
331
|
*/
|
|
@@ -330,9 +337,11 @@ function getSoleRuleKey(rules) {
|
|
|
330
337
|
/**
|
|
331
338
|
* Get the sole entry in a record.
|
|
332
339
|
*
|
|
333
|
-
* Useful for working with Rules records
|
|
334
|
-
*
|
|
340
|
+
* Useful for working with Rules records that are only supposed to contain a
|
|
341
|
+
* single rule.
|
|
342
|
+
*
|
|
335
343
|
* @param record The record to get the sole entry from
|
|
344
|
+
*
|
|
336
345
|
* @returns The value of the sole entry in the record
|
|
337
346
|
* @throws {Error} If there are no entries or more than one entry
|
|
338
347
|
*/
|
|
@@ -401,8 +410,9 @@ async function mdatExpand(tree, file, rules) {
|
|
|
401
410
|
//#endregion
|
|
402
411
|
//#region src/lib/mdast-utils/mdast-util-mdat-split.ts
|
|
403
412
|
/**
|
|
404
|
-
* Mdast utility plugin to split any multi-comment nodes and their content into
|
|
405
|
-
* nodes. They're wrapped in a paragraph so as not to
|
|
413
|
+
* Mdast utility plugin to split any multi-comment nodes and their content into
|
|
414
|
+
* individual MDAST HTML nodes. They're wrapped in a paragraph so as not to
|
|
415
|
+
* introduce new breaks.
|
|
406
416
|
*/
|
|
407
417
|
function mdatSplit(tree, file) {
|
|
408
418
|
visit(tree, "html", (node, index, parent) => {
|
|
@@ -419,7 +429,10 @@ function mdatSplit(tree, file) {
|
|
|
419
429
|
}
|
|
420
430
|
});
|
|
421
431
|
}
|
|
422
|
-
/**
|
|
432
|
+
/**
|
|
433
|
+
* Splits a single mdast HTML node containing multiple comments into individual
|
|
434
|
+
* HTML and text nodes. Exported for testing.
|
|
435
|
+
*/
|
|
423
436
|
function splitHtmlIntoMdastNodes(mdastNode) {
|
|
424
437
|
const htmlTree = fromHtml(mdastNode.value, { fragment: true });
|
|
425
438
|
const mdastNodes = [];
|
|
@@ -465,13 +478,163 @@ function getOriginalMarkup(mdastNode, hastNode) {
|
|
|
465
478
|
}
|
|
466
479
|
//#endregion
|
|
467
480
|
//#region src/lib/mdast-utils/mdast-util-mdat.ts
|
|
468
|
-
/**
|
|
481
|
+
/**
|
|
482
|
+
* Mdast utility that splits, collapses, and then re-expands all mdat comments
|
|
483
|
+
* in the tree.
|
|
484
|
+
*/
|
|
469
485
|
async function mdat(tree, file, rules) {
|
|
470
486
|
mdatSplit(tree, file);
|
|
471
|
-
|
|
487
|
+
mdatCollapse(tree, file);
|
|
472
488
|
await mdatExpand(tree, file, rules);
|
|
473
489
|
}
|
|
474
490
|
//#endregion
|
|
491
|
+
//#region src/lib/mdast-utils/mdast-util-mdat-clean.ts
|
|
492
|
+
/**
|
|
493
|
+
* @deprecated Use {@link mdatCollapse} instead. This alias will be removed in a
|
|
494
|
+
* future major version.
|
|
495
|
+
*/
|
|
496
|
+
const mdatClean = mdatCollapse;
|
|
497
|
+
//#endregion
|
|
498
|
+
//#region src/lib/mdast-utils/mdast-util-mdat-diff.ts
|
|
499
|
+
/**
|
|
500
|
+
* Compare original and expanded documents per-tag. Walks both ASTs to extract
|
|
501
|
+
* content between open/close comment markers, then compares per-tag.
|
|
502
|
+
*
|
|
503
|
+
* Callers should run {@link mdatSplit} on both trees before calling this
|
|
504
|
+
* function to ensure multi-comment nodes are split into individual nodes.
|
|
505
|
+
*
|
|
506
|
+
* Adds diagnostic messages to `expandedFile` via the VFile message pipeline.
|
|
507
|
+
*
|
|
508
|
+
* @returns Per-tag comparison results.
|
|
509
|
+
*/
|
|
510
|
+
function mdatDiff(originalTree, originalFile, expandedTree, expandedFile) {
|
|
511
|
+
const originalText = originalFile.toString();
|
|
512
|
+
const expandedText = expandedFile.toString();
|
|
513
|
+
const originalSections = extractSections(originalTree, originalText, originalFile);
|
|
514
|
+
const expandedSections = extractSections(expandedTree, expandedText, expandedFile);
|
|
515
|
+
const results = [];
|
|
516
|
+
const originalByKeyword = groupByKeyword(originalSections);
|
|
517
|
+
const expandedByKeyword = groupByKeyword(expandedSections);
|
|
518
|
+
for (const [keyword, expandedList] of expandedByKeyword) {
|
|
519
|
+
const originalList = originalByKeyword.get(keyword) ?? [];
|
|
520
|
+
for (const [i, exp] of expandedList.entries()) {
|
|
521
|
+
const orig = originalList.at(i);
|
|
522
|
+
if (orig === void 0) {
|
|
523
|
+
results.push({
|
|
524
|
+
keyword,
|
|
525
|
+
line: exp.line,
|
|
526
|
+
status: "added"
|
|
527
|
+
});
|
|
528
|
+
saveLog(expandedFile, "info", "diff", `Added: <!-- ${keyword} -->`, exp.line);
|
|
529
|
+
} else if (orig.content === void 0) {
|
|
530
|
+
results.push({
|
|
531
|
+
keyword,
|
|
532
|
+
line: exp.line,
|
|
533
|
+
status: "unexpanded"
|
|
534
|
+
});
|
|
535
|
+
saveLog(expandedFile, "warn", "diff", `Unexpanded: <!-- ${keyword} -->`, exp.line);
|
|
536
|
+
} else if (orig.content === exp.content) {
|
|
537
|
+
results.push({
|
|
538
|
+
keyword,
|
|
539
|
+
line: exp.line,
|
|
540
|
+
status: "ok"
|
|
541
|
+
});
|
|
542
|
+
saveLog(expandedFile, "info", "diff", `Up to date: <!-- ${keyword} -->`, exp.line);
|
|
543
|
+
} else {
|
|
544
|
+
results.push({
|
|
545
|
+
keyword,
|
|
546
|
+
line: exp.line,
|
|
547
|
+
status: "stale"
|
|
548
|
+
});
|
|
549
|
+
saveLog(expandedFile, "warn", "diff", `Stale: <!-- ${keyword} -->`, exp.line);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
for (const [keyword, originalList] of originalByKeyword) {
|
|
554
|
+
const expandedList = expandedByKeyword.get(keyword) ?? [];
|
|
555
|
+
for (const orig of originalList.slice(expandedList.length)) {
|
|
556
|
+
results.push({
|
|
557
|
+
keyword,
|
|
558
|
+
line: orig.line,
|
|
559
|
+
status: "missing"
|
|
560
|
+
});
|
|
561
|
+
saveLog(expandedFile, "warn", "diff", `Missing: <!-- ${keyword} -->`, orig.line);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return results;
|
|
565
|
+
}
|
|
566
|
+
function extractSections(tree, text, file) {
|
|
567
|
+
const sections = [];
|
|
568
|
+
let lastOpenKeyword;
|
|
569
|
+
let lastOpenLine = 0;
|
|
570
|
+
let lastOpenEndOffset;
|
|
571
|
+
visit(tree, "html", (node, index, parent) => {
|
|
572
|
+
if (parent === void 0 || index === void 0) return CONTINUE;
|
|
573
|
+
const marker = parseCommentNode(node, parent);
|
|
574
|
+
if (marker === void 0) return CONTINUE;
|
|
575
|
+
if (marker.type === "open") {
|
|
576
|
+
if (lastOpenKeyword !== void 0) sections.push({
|
|
577
|
+
content: void 0,
|
|
578
|
+
keyword: lastOpenKeyword,
|
|
579
|
+
line: lastOpenLine
|
|
580
|
+
});
|
|
581
|
+
lastOpenKeyword = marker.keyword;
|
|
582
|
+
lastOpenLine = marker.node.position?.start.line ?? 0;
|
|
583
|
+
lastOpenEndOffset = marker.node.position?.end.offset;
|
|
584
|
+
return CONTINUE;
|
|
585
|
+
}
|
|
586
|
+
if (lastOpenKeyword === void 0) {
|
|
587
|
+
saveLog(file, "warn", "diff", `Close marker without open: <!-- /${marker.keyword} -->`, node);
|
|
588
|
+
return CONTINUE;
|
|
589
|
+
}
|
|
590
|
+
if (lastOpenKeyword !== marker.keyword) {
|
|
591
|
+
saveLog(file, "warn", "diff", `Keyword mismatch: expected <!-- /${lastOpenKeyword} -->, found <!-- /${marker.keyword} -->`, node);
|
|
592
|
+
return CONTINUE;
|
|
593
|
+
}
|
|
594
|
+
const closeStartOffset = marker.node.position?.start.offset;
|
|
595
|
+
const sliced = closeStartOffset === void 0 || lastOpenEndOffset === void 0 ? void 0 : text.slice(lastOpenEndOffset, closeStartOffset).replaceAll("\r\n", "\n").trim();
|
|
596
|
+
const content = sliced === "" ? void 0 : sliced;
|
|
597
|
+
sections.push({
|
|
598
|
+
content,
|
|
599
|
+
keyword: lastOpenKeyword,
|
|
600
|
+
line: lastOpenLine
|
|
601
|
+
});
|
|
602
|
+
lastOpenKeyword = void 0;
|
|
603
|
+
lastOpenEndOffset = void 0;
|
|
604
|
+
return CONTINUE;
|
|
605
|
+
});
|
|
606
|
+
if (lastOpenKeyword !== void 0) sections.push({
|
|
607
|
+
content: void 0,
|
|
608
|
+
keyword: lastOpenKeyword,
|
|
609
|
+
line: lastOpenLine
|
|
610
|
+
});
|
|
611
|
+
return sections;
|
|
612
|
+
}
|
|
613
|
+
function groupByKeyword(sections) {
|
|
614
|
+
const map = /* @__PURE__ */ new Map();
|
|
615
|
+
for (const section of sections) {
|
|
616
|
+
const list = map.get(section.keyword);
|
|
617
|
+
if (list) list.push(section);
|
|
618
|
+
else map.set(section.keyword, [section]);
|
|
619
|
+
}
|
|
620
|
+
return map;
|
|
621
|
+
}
|
|
622
|
+
//#endregion
|
|
623
|
+
//#region src/lib/mdast-utils/mdast-util-mdat-strip.ts
|
|
624
|
+
/**
|
|
625
|
+
* Strips all mdat comment nodes (both opening and closing) from the tree,
|
|
626
|
+
* preserving any content between them. Code-style comments (`//`, `#`, `/*`)
|
|
627
|
+
* are left untouched.
|
|
628
|
+
*/
|
|
629
|
+
function mdatStrip(tree, _file) {
|
|
630
|
+
visit(tree, "html", (node, index, parent) => {
|
|
631
|
+
if (parent === void 0 || index === void 0) return CONTINUE;
|
|
632
|
+
if (parseCommentNode(node, parent) === void 0) return CONTINUE;
|
|
633
|
+
parent.children.splice(index, 1);
|
|
634
|
+
return [CONTINUE, index];
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
//#endregion
|
|
475
638
|
//#region src/lib/remark-mdat.ts
|
|
476
639
|
const defaultRules = { mdat: `Powered by the Markdown Autophagic Template system: [mdat](https://github.com/kitschpatrol/mdat).` };
|
|
477
640
|
/** Zod schema for validating {@link Options}. */
|
|
@@ -489,4 +652,4 @@ const remarkMdat = function(options) {
|
|
|
489
652
|
};
|
|
490
653
|
};
|
|
491
654
|
//#endregion
|
|
492
|
-
export { remarkMdat as default, getMdatReports, getSoleRule, getSoleRuleKey, mdat, mdatClean, mdatExpand, mdatSplit, optionsSchema, reporterMdat, rulesSchema, setLogger };
|
|
655
|
+
export { remarkMdat as default, getMdatReports, getSoleRule, getSoleRuleKey, mdat, mdatClean, mdatCollapse, mdatDiff, mdatExpand, mdatSplit, mdatStrip, optionsSchema, reporterMdat, rulesSchema, setLogger };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "remark-mdat",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "A remark plugin implementing the Markdown Autophagic Template (MDAT) system.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mdat",
|
|
@@ -59,8 +59,9 @@
|
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@arethetypeswrong/core": "^0.18.2",
|
|
62
|
-
"@kitschpatrol/shared-config": "^7.0.
|
|
62
|
+
"@kitschpatrol/shared-config": "^7.0.1",
|
|
63
63
|
"@types/node": "~20.19.37",
|
|
64
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
64
65
|
"bumpp": "^11.0.1",
|
|
65
66
|
"publint": "^0.3.18",
|
|
66
67
|
"tsdown": "^0.21.7",
|
|
@@ -77,10 +78,11 @@
|
|
|
77
78
|
}
|
|
78
79
|
},
|
|
79
80
|
"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",
|
|
81
|
+
"bench": "vitest bench --run --no-file-parallelism --compare test/benchmarks/baseline.json",
|
|
82
|
+
"bench:baseline": "vitest bench --run --no-file-parallelism --outputJson test/benchmarks/baseline.json",
|
|
82
83
|
"build": "tsdown",
|
|
83
84
|
"clean": "git rm -f pnpm-lock.yaml ; git clean -fdX",
|
|
85
|
+
"coverage": "vitest --coverage --run",
|
|
84
86
|
"dev": "pnpm run test",
|
|
85
87
|
"fix": "ksc fix",
|
|
86
88
|
"lint": "ksc lint",
|
package/readme.md
CHANGED
|
@@ -46,11 +46,13 @@
|
|
|
46
46
|
- [Removed options](#removed-options)
|
|
47
47
|
- [Removed rule properties](#removed-rule-properties)
|
|
48
48
|
- [Changed rule properties](#changed-rule-properties)
|
|
49
|
+
- [Renamed `clean` to `collapse`](#renamed-clean-to-collapse)
|
|
49
50
|
- [Removed validation utility](#removed-validation-utility)
|
|
50
51
|
- [Rule function signature change](#rule-function-signature-change)
|
|
51
52
|
- [Stricter argument syntax](#stricter-argument-syntax)
|
|
52
53
|
- [Comment-style comments are ignored](#comment-style-comments-are-ignored)
|
|
53
54
|
- [Compound rule error handling](#compound-rule-error-handling)
|
|
55
|
+
- [New utility: `mdatStrip`](#new-utility-mdatstrip)
|
|
54
56
|
- [Removed export: `deepMergeDefined`](#removed-export-deepmergedefined)
|
|
55
57
|
- [Implementation notes](#implementation-notes)
|
|
56
58
|
- [Maintainers](#maintainers)
|
|
@@ -273,11 +275,11 @@ Errors and warnings are reported inline during expansion via [VFile messages](ht
|
|
|
273
275
|
|
|
274
276
|
_Exported as `mdatSplit(tree: Root, file: VFile): void`_
|
|
275
277
|
|
|
276
|
-
- [**`mdast-util-mdat-
|
|
278
|
+
- [**`mdast-util-mdat-collapse`**](./src/lib/mdast-utils/mdast-util-mdat-collapse.ts)
|
|
277
279
|
|
|
278
280
|
Transformer function that resets all mdat comment expansions in a file, collapsing expanded comments back into single-line placeholders.
|
|
279
281
|
|
|
280
|
-
_Exported as `
|
|
282
|
+
_Exported as `mdatCollapse(tree: Root, file: VFile): void`_
|
|
281
283
|
|
|
282
284
|
- [**`mdast-util-mdat-expand`**](./src/lib/mdast-utils/mdast-util-mdat-expand.ts)
|
|
283
285
|
|
|
@@ -285,6 +287,18 @@ Errors and warnings are reported inline during expansion via [VFile messages](ht
|
|
|
285
287
|
|
|
286
288
|
_Exported as `mdatExpand(tree: Root, file: VFile, rules: Rules): Promise<void>`_
|
|
287
289
|
|
|
290
|
+
- [**`mdast-util-mdat-strip`**](./src/lib/mdast-utils/mdast-util-mdat-strip.ts)
|
|
291
|
+
|
|
292
|
+
Transformer function that strips all mdat comment nodes (both opening and closing) from the tree, preserving any content between them. Code-style comments (`<!-- // ... -->`, `<!-- # ... -->`, `<!-- /* ... */ -->`) are left untouched. Useful for producing a final Markdown document with all mdat scaffolding removed.
|
|
293
|
+
|
|
294
|
+
_Exported as `mdatStrip(tree: Root, file: VFile): void`_
|
|
295
|
+
|
|
296
|
+
- [**`mdast-util-mdat-diff`**](./src/lib/mdast-utils/mdast-util-mdat-diff.ts)
|
|
297
|
+
|
|
298
|
+
Compares an original document against an expanded document per-tag, identifying which mdat comments have stale content, are unexpanded, missing, or are up to date. Reports results via VFile messages (`source: 'diff'`) and returns structured `MdatDiffResult[]`. Both trees should have `mdatSplit` applied before calling. Useful for implementing `check` commands that report which specific tags need updating.
|
|
299
|
+
|
|
300
|
+
_Exported as `mdatDiff(originalTree: Root, originalFile: VFile, expandedTree: Root, expandedFile: VFile): MdatDiffResult[]`_
|
|
301
|
+
|
|
288
302
|
## Migrating from 1.x to 2.x
|
|
289
303
|
|
|
290
304
|
Version 2.0 simplifies and solidifies the API by removing several configuration options and validation features that added complexity without sufficient benefit. The core expansion behavior is unchanged — the plugin still matches HTML comments to rules and expands them — but the way you configure it has changed.
|
|
@@ -339,6 +353,10 @@ The following plugin options have been removed entirely:
|
|
|
339
353
|
| ------------------ | ------------------ |
|
|
340
354
|
| `applicationOrder` | Change to `order`. |
|
|
341
355
|
|
|
356
|
+
### Renamed `clean` to `collapse`
|
|
357
|
+
|
|
358
|
+
The former `mdast-util-mdat-clean` / `mdatClean` is now `mdast-util-mdat-collapse` / `mdatCollapse` since this is more clearly the opposite of "expand", and aligns with language used in the Mdat CLI tool. (Note that `mdatClean` is still available as a deprecated alias, but it will be removed in 3.0.)
|
|
359
|
+
|
|
342
360
|
### Removed validation utility
|
|
343
361
|
|
|
344
362
|
The `mdast-util-mdat-check` utility and its export `mdatCheck` have been removed. Validation logic (missing rules, empty content, rule errors) is now handled inline during expansion by `mdatExpand`, which reports issues as VFile messages. Use `reporterMdat` to format and display these messages.
|
|
@@ -391,6 +409,10 @@ HTML comments using code-style prefixes (`<!-- // ... -->`, `<!-- # ... -->`, `<
|
|
|
391
409
|
|
|
392
410
|
In 1.x, a failing sub-rule in a compound rule (array of rules) caused the entire expansion to fail. In 2.x, individual sub-rule failures are reported as warnings and skipped — the expansion only fails if every sub-rule fails.
|
|
393
411
|
|
|
412
|
+
### New utility: `mdatStrip`
|
|
413
|
+
|
|
414
|
+
A new `mdast-util-mdat-strip` utility is available for removing all mdat comment nodes from a document while preserving the content between them. Code-style comments (`//`, `#`, `/*`) are left untouched. This is useful for producing a final Markdown document with no mdat scaffolding. See the [Utilities](#utilities) section for details.
|
|
415
|
+
|
|
394
416
|
### Removed export: `deepMergeDefined`
|
|
395
417
|
|
|
396
418
|
The `deepMergeDefined` utility has been moved to the [`mdat`](https://github.com/kitschpatrol/mdat) package. If you were importing it from `remark-mdat`, import it from `mdat` instead.
|