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 +17 -1
- package/dist/index.d.mts +5 -2
- package/dist/index.mjs +114 -66
- package/package.json +1 -3
- package/dist/yaml-worker.d.mts +0 -1
- package/dist/yaml-worker.mjs +0 -13
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
|
-
|
|
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
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
119
|
+
const preferredQuote = options.singleQuote ? "'" : "\"";
|
|
120
|
+
return `${preferredQuote}${escapeQuotes(JSON.stringify(value), preferredQuote)}${preferredQuote}`;
|
|
99
121
|
}
|
|
100
|
-
function printAttributes(
|
|
101
|
-
|
|
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(
|
|
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
|
-
|
|
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(...
|
|
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
|
|
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
|
-
|
|
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.
|
|
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": {
|
package/dist/yaml-worker.d.mts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { };
|
package/dist/yaml-worker.mjs
DELETED
|
@@ -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 { };
|