prettier-plugin-mdc 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -27,7 +27,15 @@ Add the plugin to your Prettier configuration:
27
27
  ```json
28
28
  // .prettierrc
29
29
  {
30
- "plugins": ["prettier-plugin-mdc"]
30
+ "plugins": ["prettier-plugin-mdc"],
31
+ "overrides": [
32
+ {
33
+ "files": ["*.md"],
34
+ "options": {
35
+ "parser": "mdc"
36
+ }
37
+ }
38
+ ]
31
39
  }
32
40
  ```
33
41
 
@@ -36,6 +44,14 @@ Or in `prettier.config.js`:
36
44
  ```js
37
45
  export default {
38
46
  plugins: ["prettier-plugin-mdc"],
47
+ overrides: [
48
+ {
49
+ files: ["*.md"],
50
+ options: {
51
+ parser: "mdc",
52
+ },
53
+ },
54
+ ],
39
55
  };
40
56
  ```
41
57
 
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { Parser, Printer } from "prettier";
1
+ import { Parser, Printer, SupportLanguage } from "prettier";
2
2
  import { Node } from "unist";
3
3
 
4
4
  //#region src/constants.d.ts
@@ -10,4 +10,7 @@ declare const parsers: Record<typeof AST_FORMAT, Parser<Node>>;
10
10
  //#region src/printers.d.ts
11
11
  declare const printers: Record<typeof AST_FORMAT, Printer<Node>>;
12
12
  //#endregion
13
- export { parsers, printers };
13
+ //#region src/index.d.ts
14
+ declare const languages: Partial<SupportLanguage>[];
15
+ //#endregion
16
+ export { languages, parsers, printers };
package/dist/index.mjs CHANGED
@@ -1,28 +1,15 @@
1
- import { createRequire } from "node:module";
2
1
  import markdown from "prettier/parser-markdown";
3
2
  import remarkGfm from "remark-gfm";
4
3
  import remarkMath from "remark-math";
5
4
  import remarkMdc from "remark-mdc";
6
5
  import remarkParse from "remark-parse";
7
6
  import { unified } from "unified";
8
- import * as markdown$1 from "prettier/plugins/markdown";
9
7
  import { doc } from "prettier";
10
- import { createSyncFn } from "synckit";
8
+ import * as markdown$1 from "prettier/plugins/markdown";
11
9
 
12
10
  //#region src/constants.ts
13
11
  const AST_FORMAT = "mdc";
14
12
 
15
- //#endregion
16
- //#region src/parsers.ts
17
- const parsers = { [AST_FORMAT]: {
18
- ...markdown.parsers.markdown,
19
- astFormat: AST_FORMAT,
20
- parse: async (text) => {
21
- const processor = unified().use(remarkParse, { commonmark: true }).use(remarkMath).use(remarkGfm).use(remarkMdc);
22
- return await processor.run(processor.parse(text));
23
- }
24
- } };
25
-
26
13
  //#endregion
27
14
  //#region src/is.ts
28
15
  const isTextComponentNode = (node) => node.type === "textComponent";
@@ -52,6 +39,39 @@ function linkNeedsCustomPrinting(node) {
52
39
  return false;
53
40
  }
54
41
 
42
+ //#endregion
43
+ //#region src/validate.ts
44
+ function validateYamlBlocks(ast, text) {
45
+ function visit(node) {
46
+ if (isContainerComponentNode(node)) {
47
+ const pos = node.position;
48
+ if (pos) {
49
+ const startLine = pos.start.line;
50
+ const lines = text.split("\n");
51
+ if (startLine < lines.length) {
52
+ if (lines[startLine]?.trim() === "---" && !node.rawData) throw new Error(`Invalid YAML block in component "${node.name}" at line ${startLine + 1}: YAML block is
53
+ not properly closed`);
54
+ }
55
+ }
56
+ if (node.children) for (const child of node.children) visit(child);
57
+ } else if ("children" in node && Array.isArray(node.children)) for (const child of node.children) visit(child);
58
+ }
59
+ visit(ast);
60
+ }
61
+
62
+ //#endregion
63
+ //#region src/parsers.ts
64
+ const parsers = { [AST_FORMAT]: {
65
+ ...markdown.parsers.markdown,
66
+ astFormat: AST_FORMAT,
67
+ parse: async (text) => {
68
+ const processor = unified().use(remarkParse, { commonmark: true }).use(remarkMath).use(remarkGfm).use(remarkMdc);
69
+ const ast = await processor.run(processor.parse(text));
70
+ validateYamlBlocks(ast, text);
71
+ return ast;
72
+ }
73
+ } };
74
+
55
75
  //#endregion
56
76
  //#region src/visitor-keys.ts
57
77
  const visitorKeys = {
@@ -73,35 +93,36 @@ const extendedInlineNodes = [
73
93
  ];
74
94
  const extendedInlineNodesHaveAttributes = (node) => extendedInlineNodes.includes(node.type) && "attributes" in node;
75
95
  const escapeQuotes = (value, quote) => value.replace(new RegExp(quote, "g"), `\\${quote}`);
76
-
77
- //#endregion
78
- //#region src/yaml.ts
79
- const require = createRequire(import.meta.url);
80
- const formatYaml = (text, options) => {
81
- return createSyncFn(require.resolve("./yaml-worker.mjs"))(text, {
82
- tabWidth: options.tabWidth,
83
- useTabs: options.useTabs,
84
- singleQuote: options.singleQuote,
85
- printWidth: options.printWidth,
86
- proseWrap: options.proseWrap
87
- });
88
- };
96
+ /**
97
+ * Quote a string value using Prettier's quote selection logic:
98
+ *
99
+ * - Use preferred quote if value doesn't contain it
100
+ * - Switch to alternative quote if value contains preferred but not alternative
101
+ * - Use preferred quote with escaping if value contains both
102
+ */
103
+ function quoteString(value, options) {
104
+ const preferredQuote = options.singleQuote ? "'" : "\"";
105
+ const alternativeQuote = options.singleQuote ? "\"" : "'";
106
+ const hasPreferred = value.includes(preferredQuote);
107
+ const hasAlternative = value.includes(alternativeQuote);
108
+ const quote = hasPreferred && !hasAlternative ? alternativeQuote : preferredQuote;
109
+ return `${quote}${escapeQuotes(value.replace(/\\/g, "\\\\"), quote)}${quote}`;
110
+ }
89
111
 
90
112
  //#endregion
91
113
  //#region src/print.ts
92
- const { hardline, join } = doc.builders;
114
+ const { hardline: hardline$1, join } = doc.builders;
93
115
  const mapChildren = (path, print) => path.map(print, "children");
94
116
  function serializeValue(value, options) {
95
- const quote = options.singleQuote ? "'" : "\"";
96
- if (typeof value === "string") return `${quote}${escapeQuotes(value.replace(/\\/g, "\\\\"), quote)}${quote}`;
117
+ if (typeof value === "string") return quoteString(value, options);
97
118
  if (typeof value === "number" || typeof value === "boolean") return String(value);
98
- return `${quote}${escapeQuotes(JSON.stringify(value), quote)}${quote}`;
119
+ const preferredQuote = options.singleQuote ? "'" : "\"";
120
+ return `${preferredQuote}${escapeQuotes(JSON.stringify(value), preferredQuote)}${preferredQuote}`;
99
121
  }
100
- function printAttributes(node, options) {
101
- const attrs = node.attributes;
102
- if (!attrs || Object.keys(attrs).length === 0) return "";
122
+ function printAttributes({ attributes }, options) {
123
+ if (!attributes || Object.keys(attributes).length === 0) return "";
103
124
  const parts = [];
104
- for (const [key, value] of Object.entries(attrs)) if (key === "id") parts.push(`#${value}`);
125
+ for (const [key, value] of Object.entries(attributes)) if (key === "id") parts.push(`#${value}`);
105
126
  else if (key === "class") {
106
127
  const classes = String(value).split(/\s+/).filter(Boolean);
107
128
  for (const cls of classes) parts.push(`.${cls}`);
@@ -115,8 +136,7 @@ function printAttributes(node, options) {
115
136
  function printBinding(node, options) {
116
137
  const value = node.attributes?.value ?? "";
117
138
  const defaultValue = node.attributes?.defaultValue;
118
- const quote = options.singleQuote ? "'" : "\"";
119
- if (defaultValue !== void 0 && defaultValue !== "undefined") return [`{{ ${value} || ${quote}${escapeQuotes(String(defaultValue), quote)}${quote} }}`];
139
+ if (defaultValue !== void 0 && defaultValue !== "undefined") return [`{{ ${value} || ${quoteString(String(defaultValue), options)} }}`];
120
140
  return [`{{ ${value} }}`];
121
141
  }
122
142
  /**
@@ -134,6 +154,7 @@ function isShorthandSpan(node) {
134
154
  }
135
155
  return nodeLength <= 3;
136
156
  }
157
+ const EMPTY_PROPS_RE = /\{\s*\}\s*$/;
137
158
  /**
138
159
  * Print inline text component: :name[content]{attrs} Special cases:
139
160
  *
@@ -165,6 +186,10 @@ function printTextComponent(path, print, options) {
165
186
  const parts = [`:${node.name}`];
166
187
  if (node.children && node.children.length > 0) parts.push("[", ...printChildrenWithEscapedBrackets(), "]");
167
188
  if (attrStr) parts.push(attrStr);
189
+ else if (node.position) {
190
+ const text = options.originalText.slice(node.position.start.offset, node.position.end.offset);
191
+ if (EMPTY_PROPS_RE.test(text)) parts.push("{}");
192
+ }
168
193
  return parts;
169
194
  }
170
195
  /**
@@ -175,39 +200,25 @@ function getContainerDepth(path) {
175
200
  for (const item of path.stack) if (typeof item === "object" && item !== null && "type" in item && item.type === "containerComponent") depth++;
176
201
  return Math.max(0, depth - 1);
177
202
  }
178
- /**
179
- * Print YAML front matter from rawData rawData format: "\nkey: value\n---"
180
- */
181
- function printRawData(rawData, options) {
182
- if (!rawData) return [];
183
- let content = rawData.slice(1, -3).trimEnd();
184
- if (!content) return [];
185
- content = formatYaml(content, options);
186
- return [
187
- "---",
188
- hardline,
189
- join(hardline, content.split("\n")),
190
- hardline,
191
- "---",
192
- hardline
193
- ];
194
- }
195
- /**
196
- * Print container component: ::name{attrs}\n---\nfmAttrs\n---\nchildren\n::
197
- */
198
- function printContainerComponent(path, print, options) {
203
+ function printContainerComponentWithYamlDoc(path, print, options, yamlDoc) {
199
204
  const { node } = path;
200
205
  const depth = getContainerDepth(path);
201
206
  const colons = ":".repeat(depth + 2);
202
207
  const parts = [colons, node.name];
203
208
  const attrStr = printAttributes(node, options);
204
209
  if (attrStr) parts.push(attrStr);
205
- parts.push(hardline);
206
- parts.push(...printRawData(node.rawData, options));
210
+ parts.push(hardline$1);
211
+ if (yamlDoc.length > 0) parts.push(...yamlDoc);
207
212
  if (node.children && node.children.length > 0) {
213
+ if (yamlDoc.length > 0 && node.rawData) {
214
+ const componentStartLine = node.position?.start.line ?? 0;
215
+ const rawDataNewlines = (node.rawData.match(/\n/g) ?? []).length;
216
+ const rawDataEndLine = componentStartLine + 1 + rawDataNewlines;
217
+ if ((node.children[0].position?.start.line ?? 0) > rawDataEndLine + 1) parts.push(hardline$1);
218
+ }
208
219
  const childDocs = mapChildren(path, print);
209
- parts.push(join(hardline, childDocs));
210
- parts.push(hardline);
220
+ parts.push(join(hardline$1, childDocs));
221
+ parts.push(hardline$1);
211
222
  }
212
223
  parts.push(colons);
213
224
  return parts;
@@ -218,10 +229,10 @@ function printContainerComponent(path, print, options) {
218
229
  function printComponentContainerSection(path, print) {
219
230
  const { node } = path;
220
231
  const parts = [];
221
- if (node.name && node.name !== "default") parts.push(`#${node.name}`, hardline);
232
+ if (node.name && node.name !== "default") parts.push(`#${node.name}`, hardline$1);
222
233
  if (node.children && node.children.length > 0) {
223
234
  const childDocs = mapChildren(path, print);
224
- parts.push(join(hardline, childDocs));
235
+ parts.push(join(hardline$1, childDocs));
225
236
  }
226
237
  return parts;
227
238
  }
@@ -264,23 +275,60 @@ function printLink(path, print, options) {
264
275
 
265
276
  //#endregion
266
277
  //#region src/printers.ts
278
+ const { hardline } = doc.builders;
267
279
  const mdastPrinter = markdown$1.printers.mdast;
280
+ function extractYamlContent(rawData) {
281
+ if (!rawData) return;
282
+ return rawData.trimEnd().slice(1, -3).trimEnd() || void 0;
283
+ }
268
284
  const printers = { [AST_FORMAT]: {
269
285
  ...mdastPrinter,
270
286
  getVisitorKeys(node, nonTraversableKeys) {
271
287
  if (mdcNodeTypes.includes(node.type)) return visitorKeys[node.type];
272
288
  return mdastPrinter.getVisitorKeys(node, nonTraversableKeys);
273
289
  },
290
+ embed(path) {
291
+ const { node } = path;
292
+ if (isContainerComponentNode(node) && node.rawData) {
293
+ const yamlContent = extractYamlContent(node.rawData);
294
+ if (yamlContent) return async (textToDoc, print, _path, options) => {
295
+ let yamlDoc;
296
+ try {
297
+ yamlDoc = await textToDoc(yamlContent, { parser: "yaml" });
298
+ } catch {
299
+ yamlDoc = yamlContent;
300
+ }
301
+ return printContainerComponentWithYamlDoc(path, print, options, [
302
+ "---",
303
+ hardline,
304
+ yamlDoc,
305
+ hardline,
306
+ "---",
307
+ hardline
308
+ ]);
309
+ };
310
+ }
311
+ return null;
312
+ },
274
313
  print(path, options, print, args) {
275
314
  const { node } = path;
276
315
  if (isLinkNode(node) && linkNeedsCustomPrinting(node)) return printLink(path, print, options);
277
316
  if (extendedInlineNodesHaveAttributes(node)) return [mdastPrinter.print(path, options, print, args), printAttributes(node, options)];
278
317
  if (isTextComponentNode(node)) return printTextComponent(path, print, options);
279
- else if (isContainerComponentNode(node)) return printContainerComponent(path, print, options);
318
+ else if (isContainerComponentNode(node)) return printContainerComponentWithYamlDoc(path, print, options, []);
280
319
  else if (isComponentContainerSectionNode(node)) return printComponentContainerSection(path, print);
281
320
  return mdastPrinter.print(path, options, print, args);
282
321
  }
283
322
  } };
284
323
 
285
324
  //#endregion
286
- export { parsers, printers };
325
+ //#region src/index.ts
326
+ const languages = [{
327
+ name: "mdc",
328
+ parsers: [AST_FORMAT],
329
+ extensions: [".mdc"],
330
+ vscodeLanguageIds: ["mdc"]
331
+ }];
332
+
333
+ //#endregion
334
+ export { languages, parsers, printers };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prettier-plugin-mdc",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "author": "Ray <i@mk1.io> (@so1ve)",
5
5
  "type": "module",
6
6
  "description": "Prettier plugin for MDC syntax",
@@ -24,7 +24,6 @@
24
24
  "sideEffects": false,
25
25
  "exports": {
26
26
  ".": "./dist/index.mjs",
27
- "./yaml-worker": "./dist/yaml-worker.mjs",
28
27
  "./package.json": "./package.json"
29
28
  },
30
29
  "main": "./dist/index.mjs",
@@ -43,7 +42,6 @@
43
42
  "remark-math": "^6.0.0",
44
43
  "remark-mdc": "^3.10.0",
45
44
  "remark-parse": "^11.0.0",
46
- "synckit": "^0.11.11",
47
45
  "unified": "^11.0.5"
48
46
  },
49
47
  "devDependencies": {
@@ -1 +0,0 @@
1
- export { };
@@ -1,13 +0,0 @@
1
- import * as prettier from "prettier";
2
- import { runAsWorker } from "synckit";
3
-
4
- //#region src/yaml-worker.ts
5
- runAsWorker(async (text, options) => {
6
- return (await prettier.format(text, {
7
- ...options,
8
- parser: "yaml"
9
- })).trimEnd();
10
- });
11
-
12
- //#endregion
13
- export { };