remark-mdat 1.2.5 → 2.0.0-preview.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/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { ILogBasic, ILogLayer } from "lognow";
1
2
  import { z } from "zod";
2
3
  import { Root } from "mdast";
3
4
  import { VFile } from "vfile";
@@ -12,30 +13,17 @@ type SimplifyDeep<T> = Simplify<MergeDeep<T, T>>;
12
13
  * Simplifies processing elsewhere, while retaining flexibility for rule authors
13
14
  */
14
15
  type NormalizedRule = {
15
- /**
16
- * The order in which the rule should be applied during processing
17
- * Helpful if a rule depends on the presence of content generated by another rule
18
- * Defaults to 0.
19
- */
20
- applicationOrder: number;
21
16
  /**
22
17
  * The function that generates the expanded Markdown string.
23
18
  * For 'compound' rules, this can be an array of rules (without keywords).
24
19
  */
25
20
  content: ((options: JsonValue, tree: Root) => Promise<string>) | NormalizedRule[];
26
21
  /**
27
- * The expected order of the keyword in the document relative to other expander comments.
28
- * Used for validation purposes.
29
- * Leave undefined to order skip validation.
30
- * Defaults to undefined, which means order is not enforced.
31
- */
32
- order: number | undefined;
33
- /**
34
- * Whether the presence of the keyword comment in the document is required.
35
- * Used for validation purposes.
36
- * Defaults to false.
22
+ * The order in which the rule should be applied during processing
23
+ * Helpful if a rule depends on the presence of content generated by another rule
24
+ * Defaults to 0.
37
25
  */
38
- required: boolean;
26
+ order: number;
39
27
  };
40
28
  type Rule =
41
29
  /**
@@ -53,14 +41,9 @@ type Rule =
53
41
  */
54
42
  | string
55
43
  /**
56
- * Rule object with optional validation metadata.
44
+ * Rule object with optional metadata.
57
45
  */
58
46
  | {
59
- /**
60
- * The order in which the rule should be applied during processing.
61
- * Defaults to 0.
62
- */
63
- applicationOrder?: number;
64
47
  /**
65
48
  * Gets content to expand into the comment.
66
49
  * Can be a simple string for direct replacement, a function that returns a string, or an async function that returns a string.
@@ -77,15 +60,10 @@ type Rule =
77
60
  */
78
61
  content: ((options: JsonValue, tree: Root) => Promise<string> | string) | Rule[] | string;
79
62
  /**
80
- * The expected order of the keyword in the document relative to other expander comments.
81
- * Defaults to undefined, which means order is not enforced.
82
- */
83
- order?: number | undefined;
84
- /**
85
- * Whether the presence of the keyword comment in the document is required.
86
- * Defaults to false.
63
+ * The order in which the rule should be applied during processing.
64
+ * Defaults to 0.
87
65
  */
88
- required?: boolean;
66
+ order?: number;
89
67
  };
90
68
  /**
91
69
  * Rules are record objects whose keys match strings inside a Markdown comment, and values explain what should be expanded at the comment site.
@@ -104,15 +82,15 @@ type Rule =
104
82
  *
105
83
  * Rule with metadata:
106
84
  * ```ts
107
- * { basic-meta: { required: true, content: 'content'} }
85
+ * { basic-meta: { order: 1, content: 'content'} }
108
86
  * ```
109
87
  *
110
88
  * Rule with dynamic content and metadata:
111
- * { basic-date: { required: true, content: () => `${new Date().toISOString()}` } }
89
+ * { basic-date: { order: 1, content: () => `${new Date().toISOString()}` } }
112
90
  */
113
91
  type Rules = SimplifyDeep<Record<string, Rule>>;
114
92
  type NormalizedRules = SimplifyDeep<Record<string, NormalizedRule>>;
115
- declare const rulesSchema: z.ZodRecord<z.ZodString, z.ZodType<any, z.ZodTypeDef, any>>;
93
+ declare const rulesSchema: z.ZodRecord<z.ZodString, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
116
94
  /**
117
95
  * Returns the rule value from a single-rule record.
118
96
  * Useful when aliasing rules or invoking them programmatically.
@@ -129,51 +107,20 @@ declare function getSoleRule<T extends NormalizedRules | Rules>(rules: T): T[key
129
107
  declare function getSoleRuleKey<T extends NormalizedRules | Rules>(rules: T): keyof T;
130
108
  //#endregion
131
109
  //#region src/lib/mdast-utils/mdast-util-mdat.d.ts
132
- type MdatOptions = {
133
- addMetaComment: boolean | string;
134
- closingPrefix: string;
135
- keywordPrefix: string;
136
- metaCommentIdentifier: string;
137
- rules: Rules;
138
- };
139
- declare function mdat(tree: Root, file: VFile, options: MdatOptions): Promise<void>;
140
- //#endregion
141
- //#region src/lib/mdast-utils/mdast-util-mdat-check.d.ts
142
- type MdatCheckOptions = {
143
- addMetaComment: boolean | string;
144
- closingPrefix: string;
145
- keywordPrefix: string;
146
- metaCommentIdentifier: string; /** Enable extra checks, too noisy for real life. */
147
- paranoid: boolean;
148
- rules: Rules;
149
- };
150
- /**
151
- * Mdast utility function to check mdat source document, and output.
152
- */
153
- declare function mdatCheck(tree: Root, file: VFile, options: MdatCheckOptions): Promise<void>;
110
+ declare function mdat(tree: Root, file: VFile, rules: Rules): Promise<void>;
154
111
  //#endregion
155
112
  //#region src/lib/mdast-utils/mdast-util-mdat-clean.d.ts
156
- type MdatCleanOptions = {
157
- closingPrefix: string;
158
- keywordPrefix: string;
159
- metaCommentIdentifier: string;
160
- };
161
113
  /**
162
- * Collapses any expanded mdat comments and removes meta comments,
163
- * effectively resetting the document to its pre-expansion state. No-op if no
164
- * mdat comments are found.
114
+ * Collapses any expanded mdat comments, effectively resetting the document to
115
+ * its pre-expansion state. No-op if no mdat comments are found.
165
116
  */
166
- declare function mdatClean(tree: Root, file: VFile, options: MdatCleanOptions): void;
117
+ declare function mdatClean(tree: Root, file: VFile): void;
167
118
  //#endregion
168
119
  //#region src/lib/mdast-utils/mdast-util-mdat-expand.d.ts
169
- type MdatExpandOptions = {
170
- addMetaComment: boolean | string;
171
- closingPrefix: string;
172
- keywordPrefix: string;
173
- metaCommentIdentifier: string;
174
- rules: Rules;
175
- };
176
- declare function mdatExpand(tree: Root, file: VFile, options: MdatExpandOptions): Promise<void>;
120
+ /**
121
+ * Mdast utility to expand mdat comments in the tree.
122
+ */
123
+ declare function mdatExpand(tree: Root, file: VFile, rules: Rules): Promise<void>;
177
124
  //#endregion
178
125
  //#region src/lib/mdast-utils/mdast-util-mdat-split.d.ts
179
126
  /**
@@ -182,21 +129,13 @@ declare function mdatExpand(tree: Root, file: VFile, options: MdatExpandOptions)
182
129
  */
183
130
  declare function mdatSplit(tree: Root, file: VFile): void;
184
131
  //#endregion
185
- //#region src/lib/mdat/deep-merge-defined.d.ts
186
- declare function deepMergeDefined<T extends Record<string, unknown>>(...objects: T[]): T;
187
- //#endregion
188
132
  //#region src/lib/mdat/log.d.ts
189
- declare const log: {
190
- verbose: boolean;
191
- log(...data: unknown[]): void;
192
- logPrefixed(prefix: string, ...data: unknown[]): void;
193
- info(...data: unknown[]): void;
194
- infoPrefixed(prefix: string, ...data: unknown[]): void;
195
- warn(...data: unknown[]): void;
196
- warnPrefixed(prefix: string, ...data: unknown[]): void;
197
- error(...data: unknown[]): void;
198
- errorPrefixed(prefix: string, ...data: unknown[]): void;
199
- };
133
+ /**
134
+ * Set the logger instance for the module.
135
+ * Export this for library consumers to inject their own logger.
136
+ * @param logger - Accepts either a LogLayer instance or a Console- or Stream-like log target
137
+ */
138
+ declare function setLogger(logger?: ILogBasic | ILogLayer): void;
200
139
  //#endregion
201
140
  //#region src/lib/mdat/mdat-log.d.ts
202
141
  /**
@@ -220,29 +159,10 @@ declare function getMdatReports(files: VFile[]): MdatFileReport[];
220
159
  declare function reporterMdat(files: VFile[]): void;
221
160
  //#endregion
222
161
  //#region src/lib/remark-mdat.d.ts
223
- type Options = Partial<MdatOptions>;
224
- declare const optionsSchema: z.ZodObject<{
225
- addMetaComment: z.ZodOptional<z.ZodUnion<[z.ZodBoolean, z.ZodString]>>;
226
- closingPrefix: z.ZodOptional<z.ZodString>;
227
- keywordPrefix: z.ZodOptional<z.ZodString>;
228
- metaCommentIdentifier: z.ZodOptional<z.ZodString>;
229
- rules: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodType<any, z.ZodTypeDef, any>>>;
230
- }, "strip", z.ZodTypeAny, {
231
- closingPrefix?: string | undefined;
232
- keywordPrefix?: string | undefined;
233
- addMetaComment?: string | boolean | undefined;
234
- metaCommentIdentifier?: string | undefined;
235
- rules?: Record<string, any> | undefined;
236
- }, {
237
- closingPrefix?: string | undefined;
238
- keywordPrefix?: string | undefined;
239
- addMetaComment?: string | boolean | undefined;
240
- metaCommentIdentifier?: string | undefined;
241
- rules?: Record<string, any> | undefined;
242
- }>;
162
+ type Options = Rules;
243
163
  /**
244
164
  * A remark plugin that expands HTML comments in Markdown files.
245
165
  */
246
166
  declare const remarkMdat: Plugin<[Options], Root>;
247
167
  //#endregion
248
- export { type MdatCheckOptions, type MdatCleanOptions, type MdatExpandOptions, type MdatFileReport, type MdatMessage, type MdatOptions, type NormalizedRule, type NormalizedRules, type Options, type Rule, type Rules, type SimplifyDeep, deepMergeDefined, remarkMdat as default, getMdatReports, getSoleRule, getSoleRuleKey, log, mdat, mdatCheck, mdatClean, mdatExpand, mdatSplit, optionsSchema, reporterMdat, rulesSchema };
168
+ export { type MdatFileReport, type MdatMessage, type NormalizedRule, type NormalizedRules, type Options, type Rule, type Rules, type SimplifyDeep, remarkMdat as default, getMdatReports, getSoleRule, getSoleRuleKey, mdat, mdatClean, mdatExpand, mdatSplit, rulesSchema as optionsSchema, rulesSchema, reporterMdat, setLogger };
package/dist/index.js CHANGED
@@ -1,49 +1,30 @@
1
- import Table from "cli-table3";
2
- import picocolors from "picocolors";
3
1
  import { CONTINUE, SKIP, visit } from "unist-util-visit";
4
2
  import path from "node:path";
3
+ import picocolors from "picocolors";
4
+ import { createLogger, injectionHelper } from "lognow";
5
5
  import json5 from "json5";
6
6
  import { VFileMessage } from "vfile-message";
7
- import { z } from "zod";
8
7
  import { remark } from "remark";
9
8
  import remarkGfm from "remark-gfm";
9
+ import { z } from "zod";
10
10
  import { fromHtml } from "hast-util-from-html";
11
- import { deepmerge } from "deepmerge-ts";
11
+ //#endregion
12
12
  //#region src/lib/mdat/log.ts
13
- const isNode = process?.versions?.node !== void 0;
14
- const log = {
15
- verbose: false,
16
- log(...data) {
17
- if (!this.verbose) return;
18
- const levelPrefix = picocolors.gray("[Log]");
19
- if (isNode) console.warn(levelPrefix, ...data);
20
- else console.log(levelPrefix, ...data);
21
- },
22
- logPrefixed(prefix, ...data) {
23
- this.info(picocolors.blue(`[${prefix}]`), ...data);
24
- },
25
- info(...data) {
26
- if (!this.verbose) return;
27
- const levelPrefix = picocolors.green("[Info]");
28
- if (isNode) console.warn(levelPrefix, ...data);
29
- else console.info(levelPrefix, ...data);
30
- },
31
- infoPrefixed(prefix, ...data) {
32
- this.info(picocolors.blue(`[${prefix}]`), ...data);
33
- },
34
- warn(...data) {
35
- console.warn(picocolors.yellow("[Warning]"), ...data);
36
- },
37
- warnPrefixed(prefix, ...data) {
38
- this.warn(picocolors.blue(`[${prefix}]`), ...data);
39
- },
40
- error(...data) {
41
- console.error(picocolors.red("[Error]"), ...data);
42
- },
43
- errorPrefixed(prefix, ...data) {
44
- this.error(picocolors.blue(`[${prefix}]`), ...data);
45
- }
46
- };
13
+ /**
14
+ * The default logger instance for the library.
15
+ */
16
+ let log = createLogger({
17
+ logToConsole: { showTime: false },
18
+ name: "remark-mdat"
19
+ });
20
+ /**
21
+ * Set the logger instance for the module.
22
+ * Export this for library consumers to inject their own logger.
23
+ * @param logger - Accepts either a LogLayer instance or a Console- or Stream-like log target
24
+ */
25
+ function setLogger(logger) {
26
+ log = injectionHelper(logger);
27
+ }
47
28
  //#endregion
48
29
  //#region src/lib/mdat/mdat-log.ts
49
30
  function saveLog(file, level, source, message, lineOrNode, maybeColumn) {
@@ -127,12 +108,11 @@ function highlightComments(text, level) {
127
108
  //#region src/lib/mdat/parse.ts
128
109
  /**
129
110
  * Parse an Mdast HTML comment node into structured data.
130
- * @returns A discriminated union of CommentMarkerNode based on comment type, or
131
- * undefined if the node is not a comment.
111
+ * @returns A CommentMarkerNode or undefined if the node is not a recognized comment.
132
112
  */
133
- function parseCommentNode(node, parent, options) {
113
+ function parseCommentNode(node, parent) {
134
114
  try {
135
- const result = parseComment(node.value, options);
115
+ const result = parseComment(node.value);
136
116
  if (result === void 0) return;
137
117
  return {
138
118
  ...result,
@@ -149,110 +129,111 @@ function parseCommentNode(node, parent, options) {
149
129
  }
150
130
  /**
151
131
  * Parse any comment string into structured data.
152
- * @returns A discriminated union of CommentMarker based on comment type, or
153
- * undefined if the node is not a comment.
132
+ * Comments using code-style notation (`//`, `#`, `/*`) are ignored and return `undefined`.
133
+ * @returns A CommentMarker or undefined if the node is not a recognized comment.
154
134
  */
155
- function parseComment(text, options) {
135
+ function parseComment(text) {
156
136
  if (!isComment(text)) return;
157
- const { closingPrefix, keywordPrefix, metaCommentIdentifier } = options;
158
- if (closingPrefix === "") throw new VFileMessage("closingPrefix must not be an empty string");
137
+ const closingPrefix = "/";
159
138
  const commentHtml = text.trim();
160
139
  const commentBody = commentHtml.replace(/^\s*<!-{2,}\s*/, "").replace(/\s*-{2,}>\s*$/, "");
161
- const [rawKeyword, ...argumentParts] = commentBody.split(/(\s+|\(|\{)/);
162
- const type = rawKeyword.startsWith(metaCommentIdentifier) ? "meta" : keywordPrefix !== "" && !rawKeyword.startsWith(keywordPrefix) && !rawKeyword.startsWith(`${closingPrefix}${keywordPrefix}`) ? "native" : rawKeyword.startsWith(closingPrefix) ? "close" : "open";
163
- if (type === "meta") return {
164
- content: trimMetaIdentifiers(commentBody, metaCommentIdentifier),
165
- html: commentHtml,
166
- type
167
- };
168
- if (type === "native") return {
169
- content: commentBody,
170
- html: commentHtml,
171
- type
172
- };
140
+ const parenIndex = commentBody.indexOf("(");
141
+ const rawKeyword = parenIndex === -1 ? commentBody.split(/\s/)[0] : commentBody.slice(0, parenIndex).trim();
142
+ if (rawKeyword.startsWith("//") || rawKeyword.startsWith("#") || rawKeyword.startsWith("/*")) return;
143
+ const type = rawKeyword.startsWith(closingPrefix) ? "close" : "open";
173
144
  let keyword = rawKeyword;
174
- if (keyword.startsWith(closingPrefix)) keyword = keyword.slice(closingPrefix.length);
175
- if (keyword.startsWith(keywordPrefix)) keyword = keyword.slice(keywordPrefix.length);
176
- const optionText = makeValidJson(argumentParts.join(""));
177
- if (type === "open" || type === "close") {
178
- let options = {};
179
- try {
180
- options = json5.parse(optionText);
181
- } catch (error) {
182
- if (error instanceof Error) throw new VFileMessage(`Failed to parse comment options "${optionText}" for keyword "${keyword}": ${error.message}`);
145
+ if (type === "close") keyword = keyword.slice(1);
146
+ let options = {};
147
+ if (parenIndex !== -1) {
148
+ const lastParen = commentBody.lastIndexOf(")");
149
+ if (lastParen > parenIndex) {
150
+ const argText = commentBody.slice(parenIndex + 1, lastParen).trim();
151
+ if (argText.length > 0) try {
152
+ options = json5.parse(argText);
153
+ } catch (error) {
154
+ if (error instanceof Error) throw new VFileMessage(`Failed to parse comment options "${argText}" for keyword "${keyword}": ${error.message}`);
155
+ }
183
156
  }
184
- return {
185
- closingPrefix,
186
- html: commentHtml,
187
- keyword,
188
- keywordPrefix,
189
- options,
190
- type
191
- };
192
157
  }
158
+ return {
159
+ html: commentHtml,
160
+ keyword,
161
+ options,
162
+ type
163
+ };
193
164
  }
194
165
  function isComment(text) {
195
166
  const trimmed = text.trim();
196
167
  return trimmed.startsWith("<!--") && trimmed.endsWith("-->");
197
168
  }
198
- function makeValidJson(text) {
199
- text = text.trim();
200
- text = text.startsWith("(") ? text.slice(1) : text;
201
- text = text.endsWith(")") ? text.slice(0, -1) : text;
202
- text = text.trim();
203
- if (!text.startsWith("{") && !text.startsWith("[")) text = "{" + text;
204
- if (!text.endsWith("}") && !text.endsWith("]")) text += "}";
205
- return text;
206
- }
207
- function trimMetaIdentifiers(text, metaCommentIdentifier) {
208
- text = text.trim();
209
- text = text.startsWith(metaCommentIdentifier) ? text.slice(metaCommentIdentifier.length) : text;
210
- text = text.endsWith(metaCommentIdentifier) ? text.slice(0, -metaCommentIdentifier.length) : text;
211
- return text;
169
+ //#endregion
170
+ //#region src/lib/mdast-utils/mdast-util-mdat-clean.ts
171
+ /**
172
+ * Collapses any expanded mdat comments, effectively resetting the document to
173
+ * its pre-expansion state. No-op if no mdat comments are found.
174
+ */
175
+ function mdatClean(tree, file) {
176
+ let lastOpenMarker;
177
+ visit(tree, "html", (node, index, parent) => {
178
+ if (parent === void 0 || index === void 0) return CONTINUE;
179
+ const marker = parseCommentNode(node, parent);
180
+ if (marker === void 0) return CONTINUE;
181
+ if (marker.type === "open") {
182
+ lastOpenMarker = marker;
183
+ return CONTINUE;
184
+ }
185
+ if (marker.type === "close") {
186
+ if (lastOpenMarker === void 0) {
187
+ saveLog(file, "error", "clean", "Found closing marker without opening marker", node);
188
+ return CONTINUE;
189
+ }
190
+ if (lastOpenMarker.parent !== marker.parent) {
191
+ saveLog(file, "error", "clean", "Opening marker doesn't share a parent", node);
192
+ return CONTINUE;
193
+ }
194
+ if (lastOpenMarker.keyword !== marker.keyword) {
195
+ saveLog(file, "error", "clean", "Opening marker doesn't share a keyword", node);
196
+ return CONTINUE;
197
+ }
198
+ const openMarkerIndex = parent.children.indexOf(lastOpenMarker.node);
199
+ const nodesToRemove = parent.children.indexOf(marker.node) - openMarkerIndex + 1;
200
+ parent.children.splice(openMarkerIndex + 1, nodesToRemove - 1);
201
+ lastOpenMarker = void 0;
202
+ return [CONTINUE, index - nodesToRemove + 1];
203
+ }
204
+ });
212
205
  }
213
206
  //#endregion
214
207
  //#region src/lib/mdat/rules.ts
215
208
  function normalizeRules(rules) {
216
209
  const normalizedRules = {};
217
210
  for (const [keyword, rule] of Object.entries(rules)) if (typeof rule === "string") normalizedRules[keyword] = {
218
- applicationOrder: 0,
219
211
  content: async () => rule,
220
- order: void 0,
221
- required: false
212
+ order: 0
222
213
  };
223
214
  else if (typeof rule === "function") normalizedRules[keyword] = {
224
- applicationOrder: 0,
225
215
  content: async (options, tree) => rule(options, tree),
226
- order: void 0,
227
- required: false
216
+ order: 0
228
217
  };
229
218
  else if (Array.isArray(rule)) normalizedRules[keyword] = {
230
- applicationOrder: 0,
231
219
  content: Object.values(normalizeRules(Object.fromEntries(rule.entries()))),
232
- order: void 0,
233
- required: false
220
+ order: 0
234
221
  };
235
222
  else if (typeof rule.content === "string") {
236
223
  const ruleContent = rule.content;
237
224
  normalizedRules[keyword] = {
238
- applicationOrder: rule.applicationOrder ?? 0,
239
225
  content: async () => ruleContent,
240
- order: rule.order ?? void 0,
241
- required: rule.required ?? false
226
+ order: rule.order ?? 0
242
227
  };
243
228
  } else if (Array.isArray(rule.content)) normalizedRules[keyword] = {
244
- applicationOrder: rule.applicationOrder ?? 0,
245
229
  content: Object.values(normalizeRules(Object.fromEntries(rule.content.entries()))),
246
- order: rule.order ?? void 0,
247
- required: rule.required ?? false
230
+ order: rule.order ?? 0
248
231
  };
249
232
  else {
250
233
  const ruleContent = rule.content;
251
234
  normalizedRules[keyword] = {
252
- applicationOrder: rule.applicationOrder ?? 0,
253
235
  content: async (options, tree) => ruleContent(options, tree),
254
- order: rule.order ?? void 0,
255
- required: rule.required ?? false
236
+ order: rule.order ?? 0
256
237
  };
257
238
  }
258
239
  validateNormalizedRules(normalizedRules);
@@ -272,32 +253,27 @@ function validateNormalizedRules(rules) {
272
253
  if (error instanceof Error) throw new TypeError(`Error validating rules: ${error.message}`);
273
254
  }
274
255
  }
275
- const jsonValueSchema = z.any();
276
- const rootSchema = z.any();
256
+ const functionSchema = z.custom((value) => typeof value === "function");
277
257
  const normalizedRuleSchema = z.lazy(() => z.object({
278
- applicationOrder: z.number(),
279
- content: z.union([z.function().args(jsonValueSchema.optional(), rootSchema.optional()).returns(z.promise(z.string())), z.array(normalizedRuleSchema)]),
280
- order: z.number().optional(),
281
- required: z.boolean().default(false)
258
+ content: z.union([functionSchema, z.array(normalizedRuleSchema)]),
259
+ order: z.number()
282
260
  }));
283
- const ruleContentFunctionSchema = z.function().args(jsonValueSchema.optional(), rootSchema.optional()).returns(z.union([z.string(), z.promise(z.string())]));
284
261
  const ruleSchema = z.lazy(() => z.union([
285
- ruleContentFunctionSchema,
262
+ functionSchema,
286
263
  z.array(ruleSchema),
287
264
  z.string(),
288
265
  z.object({
289
- applicationOrder: z.number().optional(),
290
266
  content: z.union([
291
- ruleContentFunctionSchema,
267
+ functionSchema,
292
268
  z.array(ruleSchema),
293
269
  z.string()
294
270
  ]),
295
- order: z.number().optional(),
296
- required: z.boolean().optional()
271
+ order: z.number().optional()
297
272
  })
298
273
  ]));
299
- const rulesSchema = z.record(ruleSchema).describe("MDAT Rules");
300
- const normalizedRulesSchema = z.record(normalizedRuleSchema).describe("MDAT Rules");
274
+ const keywordSchema = z.string().check(z.refine((key) => !/^[/*#]/.test(key), { message: "Rule keywords must not start with \"/\", \"*\", or \"#\" — these prefixes are reserved for comment syntax" }));
275
+ const rulesSchema = z.record(keywordSchema, ruleSchema).describe("MDAT Rules");
276
+ const normalizedRulesSchema = z.record(keywordSchema, normalizedRuleSchema).describe("MDAT Rules");
301
277
  /**
302
278
  * Compound rule helpers, used in both "expand" and "check" utilities
303
279
  */
@@ -357,206 +333,28 @@ function getSoleRecord(record) {
357
333
  return recordValues[0];
358
334
  }
359
335
  //#endregion
360
- //#region src/lib/mdast-utils/mdast-util-mdat-check.ts
336
+ //#region src/lib/mdast-utils/mdast-util-mdat-expand.ts
361
337
  /**
362
- * Mdast utility function to check mdat source document, and output.
338
+ * Mdast utility to expand mdat comments in the tree.
363
339
  */
364
- async function mdatCheck(tree, file, options) {
365
- const { closingPrefix, keywordPrefix, metaCommentIdentifier, paranoid, rules: rawRules } = options;
366
- validateRules(rawRules);
367
- const rules = normalizeRules(rawRules);
340
+ async function mdatExpand(tree, file, rules) {
341
+ validateRules(rules);
342
+ const normalizedRules = normalizeRules(rules);
368
343
  const commentMarkers = [];
369
344
  visit(tree, "html", (node, index, parent) => {
370
345
  if (parent === void 0 || index === void 0) return CONTINUE;
371
- const commentMarker = parseCommentNode(node, parent, {
372
- closingPrefix,
373
- keywordPrefix,
374
- metaCommentIdentifier
375
- });
376
- if (commentMarker !== void 0) {
377
- const rule = commentMarker.type === "open" || commentMarker.type === "close" ? rules[commentMarker.keyword] : void 0;
378
- commentMarkers.push({
379
- ...commentMarker,
380
- rule
381
- });
382
- }
383
- });
384
- checkMissingRequiredComments(file, commentMarkers, rules, rawRules);
385
- checkCommentOrder(file, commentMarkers);
386
- checkMetaCommentPresence(file, commentMarkers, options);
387
- await checkRulesReturnedContent(file, commentMarkers, tree);
388
- if (paranoid) checkMissingOptionalComments(file, commentMarkers, rules, rawRules);
389
- checkMissingRules(file, commentMarkers);
390
- checkMissingPrefix(file, commentMarkers, rules, options);
391
- }
392
- /**
393
- * Check that all the rules are working by getting their content
394
- */
395
- async function checkRulesReturnedContent(file, comments, tree) {
396
- for (const comment of comments) if (comment.type === "open" && comment.rule !== void 0) try {
397
- if ((await getRuleContent(comment.rule, comment.options, tree, true)).trim() === "") saveLog(file, comment.rule.required ? "error" : "warn", "check", `${comment.html} returned an empty string.`, comment.node);
398
- } catch (error) {
399
- if (error instanceof Error) saveLog(file, comment.rule.required ? "error" : "warn", "check", `Could not get content for ${comment.html}. ${error.message}`, comment.node);
400
- }
401
- }
402
- /**
403
- * Check for comments with missing prefix (have an un-prefixed comment that matches a rule)
404
- */
405
- function checkMissingPrefix(file, comments, rules, options) {
406
- if (options.keywordPrefix === "") return;
407
- const ruleKeywords = Object.keys(rules);
408
- for (const comment of comments) if (comment.type === "native" && ruleKeywords.includes(comment.content)) saveLog(file, "warn", "check", `Missing prefix: ${comment.html}`, comment.node);
409
- }
410
- /**
411
- * Check for missing "optional" rules. These are instances where we have the comment, but not the rule
412
- */
413
- function checkMissingRules(file, comments) {
414
- for (const comment of comments) if (comment.type === "open" && comment.rule === void 0) saveLog(file, "warn", "check", `Missing rule for: ${comment.html}`, comment.node);
415
- }
416
- /**
417
- * Check for missing optional comments. We have defined the rule, but not written a matching comment.
418
- */
419
- function checkMissingOptionalComments(file, comments, rules, rawRules) {
420
- for (const [keyword, rule] of Object.entries(rules)) if (!rule.required && !comments.some((comment) => comment.type === "open" && comment.keyword === keyword) && !satisfiedByCompoundRule(keyword, rawRules)) saveLog(file, "warn", "check", `Missing optional: <!-- ${keyword} -->`);
421
- }
422
- /**
423
- * Check for missing required comments.
424
- * The rule set includes a rule with `required: true`, but no matching comment was found in the document.
425
- */
426
- function checkMissingRequiredComments(file, comments, rules, rawRules) {
427
- for (const [keyword, rule] of Object.entries(rules)) if (rule.required && !comments.some((comment) => comment.type === "open" && comment.keyword === keyword) && !satisfiedByCompoundRule(keyword, rawRules)) saveLog(file, "error", "check", `Missing required: <!-- ${keyword} -->`);
428
- }
429
- /**
430
- * Extract the content value from a raw rule, unwrapping object-form rules.
431
- */
432
- function getRawRuleContent(rule) {
433
- if (typeof rule === "object" && !Array.isArray(rule)) return rule.content;
434
- return rule;
435
- }
436
- /**
437
- * Get the sub-rule array from a compound rule, if it is one.
438
- */
439
- function getCompoundSubRules(rule) {
440
- if (Array.isArray(rule)) return rule;
441
- if (typeof rule === "object" && !Array.isArray(rule) && Array.isArray(rule.content)) return rule.content;
442
- }
443
- /**
444
- * Helper to see if a rule keyword is covered by a compound rule in the rule set.
445
- * Checks whether any compound rule contains the same raw content value (by reference)
446
- * as the rule for the given keyword. This works when the sub-rule was imported
447
- * directly into the compound rule definition.
448
- */
449
- function satisfiedByCompoundRule(keyword, rawRules) {
450
- const rawRule = rawRules[keyword];
451
- const ruleContent = getRawRuleContent(rawRule);
452
- for (const otherRule of Object.values(rawRules)) {
453
- const subRules = getCompoundSubRules(otherRule);
454
- if (subRules === void 0) continue;
455
- if (subRules.some((subRule) => getRawRuleContent(subRule) === ruleContent)) return true;
456
- }
457
- return false;
458
- }
459
- /**
460
- * Check if comment order in document is different from order specified in the rules
461
- */
462
- function checkCommentOrder(file, comments) {
463
- const commentsInOrderOfAppearance = comments.filter((commentMarker) => commentMarker.type === "open" && commentMarker.rule?.order !== void 0);
464
- const commentsInCorrectOrder = [...commentsInOrderOfAppearance].toSorted((a, b) => {
465
- const orderA = a.rule?.order;
466
- const orderB = b.rule?.order;
467
- if (orderA === void 0 || orderB === void 0) throw new Error("Unexpected undefined rule order");
468
- return orderA - orderB;
469
- });
470
- const currentOrderList = commentOrderList(commentsInOrderOfAppearance);
471
- const correctOrderList = commentOrderList(commentsInCorrectOrder);
472
- const table = new Table({
473
- head: [picocolors.red(picocolors.bold("Current Order")), picocolors.green(picocolors.bold("Required Order"))],
474
- style: { compact: true }
475
- });
476
- if (currentOrderList.join(",") !== correctOrderList.join(",")) {
477
- table.push(...currentOrderList.map((currentOrder, index) => [currentOrder, correctOrderList[index]]));
478
- saveLog(file, "error", "check", `Out of order:\n${table.toString()}`);
479
- }
480
- }
481
- /**
482
- * Check that meta presence / absence comment matches options.
483
- */
484
- function checkMetaCommentPresence(file, comments, options) {
485
- const { addMetaComment } = options;
486
- const metaCommentCount = comments.filter((comment) => comment.type === "meta").length;
487
- const shouldHaveMetaComment = typeof addMetaComment === "string" ? true : addMetaComment;
488
- if (shouldHaveMetaComment && metaCommentCount !== 1) saveLog(file, "error", "check", `Missing meta comment`);
489
- if (!shouldHaveMetaComment && metaCommentCount !== 0) saveLog(file, "error", "check", `Unexpected meta comment`);
490
- if (metaCommentCount > 1) saveLog(file, "error", "check", `Multiple meta comments`);
491
- }
492
- function commentOrderList(comments) {
493
- return comments.map((comment, index) => {
494
- if (comment.type === "open" || comment.type === "close") return `${index + 1}. ${comment.html}`;
495
- throw new Error("Unexpected comment type");
496
- });
497
- }
498
- //#endregion
499
- //#region src/lib/mdast-utils/mdast-util-mdat-clean.ts
500
- /**
501
- * Collapses any expanded mdat comments and removes meta comments,
502
- * effectively resetting the document to its pre-expansion state. No-op if no
503
- * mdat comments are found.
504
- */
505
- function mdatClean(tree, file, options) {
506
- let lastOpenMarker;
507
- visit(tree, "html", (node, index, parent) => {
508
- if (parent === void 0 || index === void 0) return CONTINUE;
509
- const marker = parseCommentNode(node, parent, options);
510
- if (marker === void 0 || marker.type === "native") return CONTINUE;
511
- if (marker.type === "meta") {
512
- parent.children.splice(index, 1);
513
- return [CONTINUE, index];
514
- }
515
- if (marker.type === "open") {
516
- lastOpenMarker = marker;
346
+ const commentMarker = parseCommentNode(node, parent);
347
+ if (commentMarker?.type !== "open") return CONTINUE;
348
+ if (normalizedRules[commentMarker.keyword] === void 0) {
349
+ saveLog(file, "warn", "expand", `Missing rule for: ${commentMarker.html}`, node);
517
350
  return CONTINUE;
518
351
  }
519
- if (marker.type === "close") {
520
- if (lastOpenMarker === void 0) {
521
- saveLog(file, "error", "clean", "Found closing marker without opening marker", node);
522
- return CONTINUE;
523
- }
524
- if (lastOpenMarker.parent !== marker.parent) {
525
- saveLog(file, "error", "clean", "Opening marker doesn't share a parent", node);
526
- return CONTINUE;
527
- }
528
- if (lastOpenMarker.keyword !== marker.keyword) {
529
- saveLog(file, "error", "clean", "Opening marker doesn't share a keyword", node);
530
- return CONTINUE;
531
- }
532
- const openMarkerIndex = parent.children.indexOf(lastOpenMarker.node);
533
- const nodesToRemove = parent.children.indexOf(marker.node) - openMarkerIndex + 1;
534
- parent.children.splice(openMarkerIndex + 1, nodesToRemove - 1);
535
- lastOpenMarker = void 0;
536
- return [CONTINUE, index - nodesToRemove + 1];
537
- }
352
+ commentMarkers.push(commentMarker);
538
353
  });
539
- }
540
- //#endregion
541
- //#region src/lib/mdast-utils/mdast-util-mdat-expand.ts
542
- async function mdatExpand(tree, file, options) {
543
- const { addMetaComment, closingPrefix, keywordPrefix, metaCommentIdentifier, rules: rawRules } = options;
544
- validateRules(rawRules);
545
- const rules = normalizeRules(rawRules);
546
- const commentMarkers = [];
547
- visit(tree, "html", (node, index, parent) => {
548
- if (parent === void 0 || index === void 0) return CONTINUE;
549
- const commentMarker = parseCommentNode(node, parent, {
550
- closingPrefix,
551
- keywordPrefix,
552
- metaCommentIdentifier
553
- });
554
- if (commentMarker?.type === "open" && rules[commentMarker.keyword] !== void 0) commentMarkers.push(commentMarker);
555
- });
556
- commentMarkers.sort((a, b) => rules[a.keyword].applicationOrder - rules[b.keyword].applicationOrder);
354
+ commentMarkers.sort((a, b) => normalizedRules[a.keyword].order - normalizedRules[b.keyword].order);
557
355
  for (const comment of commentMarkers) {
558
- const { closingPrefix, html, keyword, keywordPrefix, node, options, parent } = comment;
559
- const rule = rules[keyword];
356
+ const { html, keyword, node, options, parent } = comment;
357
+ const rule = normalizedRules[keyword];
560
358
  let newMarkdownString = "";
561
359
  try {
562
360
  newMarkdownString = await getRuleContent(rule, options, tree);
@@ -571,19 +369,12 @@ async function mdatExpand(tree, file, options) {
571
369
  const newNodes = remark().use(remarkGfm).parse(newMarkdownString).children;
572
370
  const closingNode = {
573
371
  type: "html",
574
- value: `<!-- ${closingPrefix}${keywordPrefix}${keyword} -->`
372
+ value: `<!-- /${keyword} -->`
575
373
  };
576
374
  const openingCommentIndex = parent.children.indexOf(node);
577
375
  parent.children.splice(openingCommentIndex + 1, 0, ...newNodes, closingNode);
578
376
  saveLog(file, "info", "expand", `Expanded: ${html}`, node);
579
377
  }
580
- if (addMetaComment) {
581
- const metaComment = {
582
- type: "html",
583
- value: `<!--${metaCommentIdentifier} ${typeof addMetaComment === "string" ? addMetaComment : "Warning: Content inside HTML comment blocks was generated by mdat and may be overwritten."} ${metaCommentIdentifier}-->`
584
- };
585
- tree.children.unshift(metaComment);
586
- }
587
378
  }
588
379
  //#endregion
589
380
  //#region src/lib/mdast-utils/mdast-util-mdat-split.ts
@@ -649,66 +440,25 @@ function getOriginalMarkup(mdastNode, hastNode) {
649
440
  }
650
441
  //#endregion
651
442
  //#region src/lib/mdast-utils/mdast-util-mdat.ts
652
- async function mdat(tree, file, options) {
653
- const { addMetaComment, closingPrefix, keywordPrefix, metaCommentIdentifier, rules } = options;
443
+ async function mdat(tree, file, rules) {
654
444
  mdatSplit(tree, file);
655
- mdatClean(tree, file, {
656
- closingPrefix,
657
- keywordPrefix,
658
- metaCommentIdentifier
659
- });
660
- await mdatExpand(tree, file, {
661
- addMetaComment,
662
- closingPrefix,
663
- keywordPrefix,
664
- metaCommentIdentifier,
665
- rules
666
- });
667
- await mdatCheck(tree, file, {
668
- addMetaComment,
669
- closingPrefix,
670
- keywordPrefix,
671
- metaCommentIdentifier,
672
- paranoid: false,
673
- rules
674
- });
675
- }
676
- //#endregion
677
- //#region src/lib/mdat/deep-merge-defined.ts
678
- function stripUndefinedDeep(object) {
679
- if (Array.isArray(object)) return object.map((v) => v && typeof v === "object" ? stripUndefinedDeep(v) : v).filter((v) => v !== void 0);
680
- return Object.entries(object).map(([k, v]) => [k, v && typeof v === "object" ? stripUndefinedDeep(v) : v]).reduce((acc, [k, v]) => v === void 0 ? acc : {
681
- ...acc,
682
- [k]: v
683
- }, {});
684
- }
685
- function deepMergeDefined(...objects) {
686
- return deepmerge(...objects.map((v, i) => i === 0 ? v : stripUndefinedDeep(v)));
445
+ mdatClean(tree, file);
446
+ await mdatExpand(tree, file, rules);
687
447
  }
688
448
  //#endregion
689
449
  //#region src/lib/remark-mdat.ts
690
- const defaultOptions = {
691
- addMetaComment: false,
692
- closingPrefix: "/",
693
- keywordPrefix: "",
694
- metaCommentIdentifier: "+",
695
- rules: { mdat: `Powered by the Markdown Autophagic Template system: [mdat](https://github.com/kitschpatrol/mdat).` }
696
- };
697
- const optionsSchema = z.object({
698
- addMetaComment: z.union([z.boolean(), z.string()]).optional(),
699
- closingPrefix: z.string().min(1).optional(),
700
- keywordPrefix: z.string().optional(),
701
- metaCommentIdentifier: z.string().optional(),
702
- rules: rulesSchema.optional()
703
- }).describe("MDAT Options");
450
+ const defaultRules = { mdat: `Powered by the Markdown Autophagic Template system: [mdat](https://github.com/kitschpatrol/mdat).` };
704
451
  /**
705
452
  * A remark plugin that expands HTML comments in Markdown files.
706
453
  */
707
- const remarkMdat = function(options) {
708
- const resolvedOptions = deepMergeDefined(defaultOptions, options);
454
+ const remarkMdat = function(rules) {
455
+ const resolvedRules = {
456
+ ...defaultRules,
457
+ ...rules
458
+ };
709
459
  return async function(tree, file) {
710
- await mdat(tree, file, resolvedOptions);
460
+ await mdat(tree, file, resolvedRules);
711
461
  };
712
462
  };
713
463
  //#endregion
714
- export { deepMergeDefined, remarkMdat as default, getMdatReports, getSoleRule, getSoleRuleKey, log, mdat, mdatCheck, mdatClean, mdatExpand, mdatSplit, optionsSchema, reporterMdat, rulesSchema };
464
+ export { remarkMdat as default, getMdatReports, getSoleRule, getSoleRuleKey, mdat, mdatClean, mdatExpand, mdatSplit, rulesSchema as optionsSchema, rulesSchema, reporterMdat, setLogger };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remark-mdat",
3
- "version": "1.2.5",
3
+ "version": "2.0.0-preview.1",
4
4
  "description": "A remark plugin implementing the Markdown Autophagic Template (MDAT) system.",
5
5
  "keywords": [
6
6
  "mdat",
@@ -43,10 +43,9 @@
43
43
  "dependencies": {
44
44
  "@types/mdast": "^4.0.4",
45
45
  "@types/unist": "^3.0.3",
46
- "cli-table3": "^0.6.5",
47
- "deepmerge-ts": "^7.1.5",
48
46
  "hast-util-from-html": "^2.0.3",
49
47
  "json5": "^2.2.3",
48
+ "lognow": "^0.5.2",
50
49
  "picocolors": "^1.1.1",
51
50
  "remark": "^15.0.1",
52
51
  "remark-gfm": "^4.0.1",
@@ -55,7 +54,7 @@
55
54
  "unist-util-visit": "^5.1.0",
56
55
  "vfile": "^6.0.3",
57
56
  "vfile-message": "^4.0.3",
58
- "zod": "^3.25.76"
57
+ "zod": "^4.3.6"
59
58
  },
60
59
  "devDependencies": {
61
60
  "@arethetypeswrong/core": "^0.18.2",
@@ -68,7 +67,7 @@
68
67
  "vitest": "^4.1.1"
69
68
  },
70
69
  "engines": {
71
- "node": ">=20.0.0"
70
+ "node": ">=20.19.0"
72
71
  },
73
72
  "devEngines": {
74
73
  "runtime": {
@@ -83,6 +82,7 @@
83
82
  "fix": "ksc fix",
84
83
  "lint": "ksc lint",
85
84
  "release": "bumpp --commit 'Release: %s' && pnpm run build && NPM_AUTH_TOKEN=$(op read 'op://Personal/npm/token') && pnpm publish",
85
+ "release-preview": "bumpp --preid preview --commit 'Release: %s' && NPM_AUTH_TOKEN=$(op read 'op://Personal/npm/token') && pnpm publish --tag preview",
86
86
  "test": "vitest run"
87
87
  }
88
88
  }
package/readme.md CHANGED
@@ -1,5 +1,3 @@
1
- <!--+ Warning: Content inside HTML comment blocks was generated by mdat and may be overwritten. +-->
2
-
3
1
  <!-- title -->
4
2
 
5
3
  # remark-mdat
@@ -44,7 +42,6 @@
44
42
  - [Examples](#examples)
45
43
  - [Utilities](#utilities)
46
44
  - [Implementation notes](#implementation-notes)
47
- - [The future](#the-future)
48
45
  - [Maintainers](#maintainers)
49
46
  - [Acknowledgments](#acknowledgments)
50
47
  - [Contributing](#contributing)
@@ -105,19 +102,9 @@ remark().use(remarkMdat)
105
102
 
106
103
  #### Options
107
104
 
108
- The plugin accepts an optional options object. All fields are optional:
109
-
110
- | Option | Type | Default | Description |
111
- | ----------------------- | ------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
112
- | `rules` | `Rules` | `{}` | A record mapping comment keywords to rules that determine what content is expanded at each comment site. See [Rules](#rules) below. |
113
- | `addMetaComment` | `boolean \| string` | `false` | If `true`, prepends a warning comment to the document noting that content was auto-generated. If a `string`, uses that string as the warning message. |
114
- | `closingPrefix` | `string` | `'/'` | The prefix used to identify closing comment tags, e.g. the `/` in `<!-- /keyword -->`. |
115
- | `keywordPrefix` | `string` | `''` | A prefix required on all mdat comments. Useful for namespacing, e.g. setting `'mm-'` means only `<!-- mm-keyword -->` comments are processed. |
116
- | `metaCommentIdentifier` | `string` | `'+'` | The character used to identify auto-generated meta comments, e.g. `<!--+ ... +-->`. |
117
-
118
- #### Rules
105
+ The plugin accepts an optional `Rules` object as its options. This is a `Record<string, Rule>` where each key is a keyword matching an HTML comment in the Markdown file (e.g. `title` matches `<!-- title -->`).
119
106
 
120
- Rules are defined as a `Record<string, Rule>` where each key is a keyword matching an HTML comment in the Markdown file (e.g. `title` matches `<!-- title -->`).
107
+ HTML comments using code-style notation (`<!-- // ... -->`, `<!-- # ... -->`, `<!-- /* ... */ -->`) are ignored and will not be treated as mdat keywords. Rule keywords cannot start with `/`, `*`, or `#`.
121
108
 
122
109
  A `Rule` value can take several forms:
123
110
 
@@ -125,25 +112,51 @@ A `Rule` value can take several forms:
125
112
  const rules: Rules = {
126
113
  // String: direct replacement
127
114
  greeting: 'Hello, world!',
128
- // Array: compound rule combining multiple sub-rules
129
- header: ['# My Project', () => getDescription()],
130
- // Function with arguments: receives parsed options from the comment
131
- // e.g. <!-- greeting({name: "Alice"}) --> or <!-- greeting {name: "Alice"} -->
132
- personalGreeting: (options) => `Hello, ${options.name}!`,
115
+
133
116
  // Function: dynamic content (sync or async)
134
117
  time: () => new Date().toDateString(),
118
+
119
+ // Function with arguments: receives parsed options from the comment
120
+ personalGreeting: (options) => `Hello, ${options.name}!`,
121
+
135
122
  // Object: rule with validation metadata
136
123
  title: {
137
- applicationOrder: 0, // Processing priority (default: 0)
124
+ order: 0, // Processing priority (default: 0)
138
125
  content: () => getTitle(), // String, function, or array
139
- order: 1, // Expected position relative to other comments
140
- required: true, // Error if comment is missing (default: false)
141
126
  },
127
+
128
+ // Array: compound rule combining multiple sub-rules
129
+ header: ['# My Project', () => getDescription()],
130
+
142
131
  // Function with document access: receives the full mdast tree
143
132
  toc: (_options, tree) => generateTocFromTree(tree),
144
133
  }
145
134
  ```
146
135
 
136
+ #### Passing arguments to rules
137
+
138
+ Arguments are passed using function-call syntax: `<!-- keyword(...) -->`. The value inside the parentheses is parsed as [JSON5](https://json5.org/), which means unquoted keys and single quotes are allowed.
139
+
140
+ ```md
141
+ Options as JSON5 (unquoted keys, single quotes):
142
+
143
+ <!-- greeting({name: 'Alice', shout: true}) -->
144
+
145
+ Options as strict JSON:
146
+
147
+ <!-- greeting({"name": "Alice", "shout": true}) -->
148
+
149
+ Single primitive value:
150
+
151
+ <!-- repeat(3) -->
152
+ ```
153
+
154
+ Any JSON5 value is supported: objects, arrays, strings, numbers, and booleans. Comments without parentheses receive an empty object `{}` as their options.
155
+
156
+ For simplicity's sake, only a single argument position is supported. If you need pass multiple arguments, wrap them in an object.
157
+
158
+ Prefer object arguments for all but the most contextually clear argument values.
159
+
147
160
  ### Examples
148
161
 
149
162
  #### Basic
@@ -167,7 +180,7 @@ console.log(markdownOutput.toString())
167
180
  // <!-- /mdat -->
168
181
  ```
169
182
 
170
- #### With options
183
+ #### With rules
171
184
 
172
185
  If you wanted to replace `<!-- time -->` comments in your Markdown file with the current time, you could pass in a rule:
173
186
 
@@ -176,15 +189,15 @@ import type { Rules } from 'remark-mdat'
176
189
  import { remark } from 'remark'
177
190
  import remarkMdat from 'remark-mdat'
178
191
 
179
- // Create the rule
192
+ // Create the rules
180
193
  const rules: Rules = {
181
194
  time: () => new Date().toDateString(),
182
195
  }
183
196
 
184
197
  const markdownInput = '<!-- time -->'
185
198
 
186
- // Pass the time rule to remarkMdat
187
- const markdownOutput = await remark().use(remarkMdat, { rules }).process(markdownInput)
199
+ // Pass the rules to remarkMdat
200
+ const markdownOutput = await remark().use(remarkMdat, rules).process(markdownInput)
188
201
 
189
202
  console.log(markdownOutput.toString())
190
203
 
@@ -202,15 +215,15 @@ See the [`mdat`](https://github.com/kitschpatrol/mdat) package for a higher-leve
202
215
 
203
216
  The plugin bundles a number of [mdast](https://github.com/syntax-tree/mdast) utilities designed to operate directly on syntax trees. These are exported to support customized Unified.js processors and enforce modularity and separation of concerns in mdat's internal implementation, but you do not need to use them directly — all functionality is encapsulated in the single `remarkMdat` plugin export.
204
217
 
205
- The remark-mdat plugin chains these utilities together to accommodate the typical use case of end-to-end expansion and validation of mdat comments. For now, the individual utility transformers are not published individually to NPM, and are instead bundled with `remark-mdat`.
218
+ The remark-mdat plugin chains these utilities together to accommodate the typical use case of end-to-end expansion of mdat comments. For now, the individual utility transformers are not published individually to NPM, and are instead bundled with `remark-mdat`.
206
219
 
207
- - [**`mdast-util-mdat`**](./src/lib/mdast-utils/mdast-util-mdat.ts)
220
+ Errors and warnings are reported inline during expansion via [VFile messages](https://github.com/vfile/vfile-message), following remark ecosystem conventions. Use `reporterMdat` to extract and format these messages for console output.
208
221
 
209
- Composite transformer function performing end-to-end mdat comment expansion and validation on Markdown ASTs by chaining the other utility functions described below.
222
+ - [**`mdast-util-mdat`**](./src/lib/mdast-utils/mdast-util-mdat.ts)
210
223
 
211
- _Exported as `mdat(tree: Root, file: VFile, options: MdatOptions): Promise<void>`_
224
+ Composite transformer function performing end-to-end mdat comment expansion on Markdown ASTs by chaining the other utility functions described below.
212
225
 
213
- `MdatOptions` includes `addMetaComment`, `closingPrefix`, `keywordPrefix`, `metaCommentIdentifier`, and `rules` (all required, unlike the plugin's `Options` where they are optional with defaults).
226
+ _Exported as `mdat(tree: Root, file: VFile, rules: Rules): Promise<void>`_
214
227
 
215
228
  Utilities wrapped by `mdast-util-mdat`:
216
229
  - [**`mdast-util-mdat-split`**](./src/lib/mdast-utils/mdast-util-mdat-split.ts)
@@ -221,37 +234,21 @@ The remark-mdat plugin chains these utilities together to accommodate the typica
221
234
 
222
235
  - [**`mdast-util-mdat-clean`**](./src/lib/mdast-utils/mdast-util-mdat-clean.ts)
223
236
 
224
- Transformer function that "resets" all mdat comment expansions in a file, collapsing expanded comments back into single-line placeholders.
225
-
226
- _Exported as `mdatClean(tree: Root, file: VFile, options: MdatCleanOptions): void`_
237
+ Transformer function that resets all mdat comment expansions in a file, collapsing expanded comments back into single-line placeholders.
227
238
 
228
- `MdatCleanOptions` includes `closingPrefix`, `keywordPrefix`, and `metaCommentIdentifier`.
239
+ _Exported as `mdatClean(tree: Root, file: VFile): void`_
229
240
 
230
241
  - [**`mdast-util-mdat-expand`**](./src/lib/mdast-utils/mdast-util-mdat-expand.ts)
231
242
 
232
- Transformer function that expands mdat comments (e.g. `<!-- title -->`) in a Markdown file according to the rule set passed in to the options argument.
233
-
234
- _Exported as `mdatExpand(tree: Root, file: VFile, options: MdatExpandOptions): Promise<void>`_
235
-
236
- `MdatExpandOptions` includes `addMetaComment`, `closingPrefix`, `keywordPrefix`, `metaCommentIdentifier`, and `rules`.
237
-
238
- - [**`mdast-util-mdat-check`**](./src/lib/mdast-utils/mdast-util-mdat-check.ts)
243
+ Transformer function that expands mdat comments (e.g. `<!-- title -->`) in a Markdown file according to the provided rules. Reports errors for rules that throw or return empty content, and warnings for comments with no matching rule.
239
244
 
240
- Transformer function that validates an expanded Markdown document against the requirements defined in the rules passed in to the options argument. Does not modify the tree, it only appends messages to the VFile.
241
-
242
- _Exported as `mdatCheck(tree: Root, file: VFile, options: MdatCheckOptions): Promise<void>`_
243
-
244
- `MdatCheckOptions` extends `MdatExpandOptions` with a `paranoid` boolean for extra validation checks.
245
-
246
- See `reporterMdat` to extract, format, and log results from VFile messages written by `mdatCheck`.
245
+ _Exported as `mdatExpand(tree: Root, file: VFile, rules: Rules): Promise<void>`_
247
246
 
248
247
  ## Implementation notes
249
248
 
250
249
  This project was split from a monorepo containing both `mdat` and `remark-mdat` into separate repos in July 2024.
251
250
 
252
- ## The future
253
-
254
- - Consider making remark a peer dependency? Though perhaps not [strip-markdown/issues/24](https://github.com/remarkjs/strip-markdown/issues/24)...
251
+ Remark is not a peer dependency on account of this discussion: [strip-markdown/issues/24](https://github.com/remarkjs/strip-markdown/issues/24)
255
252
 
256
253
  ## Maintainers
257
254