prettier-plugin-mdc 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -7
- package/dist/index.d.mts +5 -2
- package/dist/index.mjs +83 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,24 +2,68 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/prettier-plugin-mdc)
|
|
4
4
|
|
|
5
|
+
A [Prettier](https://prettier.io/) plugin for formatting [MDC (Markdown Components)](https://content.nuxt.com/docs/files/markdown#mdc-syntax) syntax used in [Nuxt Content](https://content.nuxt.com/).
|
|
6
|
+
|
|
5
7
|
## 💎 Features
|
|
6
8
|
|
|
9
|
+
- Preserve YAML front matter in components
|
|
10
|
+
- Support for nested components
|
|
11
|
+
- Compatible with GFM (GitHub Flavored Markdown) and math syntax
|
|
12
|
+
|
|
7
13
|
## 📦 Installation
|
|
8
14
|
|
|
9
15
|
```bash
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
npm install -D prettier-plugin-mdc
|
|
17
|
+
# or
|
|
18
|
+
yarn add -D prettier-plugin-mdc
|
|
19
|
+
# or
|
|
20
|
+
pnpm add -D prettier-plugin-mdc
|
|
13
21
|
```
|
|
14
22
|
|
|
15
23
|
## 🚀 Usage
|
|
16
24
|
|
|
17
|
-
|
|
25
|
+
Add the plugin to your Prettier configuration:
|
|
18
26
|
|
|
27
|
+
```json
|
|
28
|
+
// .prettierrc
|
|
29
|
+
{
|
|
30
|
+
"plugins": ["prettier-plugin-mdc"],
|
|
31
|
+
"overrides": [
|
|
32
|
+
{
|
|
33
|
+
"files": ["*.md"],
|
|
34
|
+
"options": {
|
|
35
|
+
"parser": "mdc"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
19
40
|
```
|
|
20
41
|
|
|
21
|
-
|
|
42
|
+
Or in `prettier.config.js`:
|
|
22
43
|
|
|
23
|
-
|
|
44
|
+
```js
|
|
45
|
+
export default {
|
|
46
|
+
plugins: ["prettier-plugin-mdc"],
|
|
47
|
+
overrides: [
|
|
48
|
+
{
|
|
49
|
+
files: ["*.md"],
|
|
50
|
+
options: {
|
|
51
|
+
parser: "mdc",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Then format your `.mdc` or `.md` files:
|
|
24
59
|
|
|
25
|
-
|
|
60
|
+
```bash
|
|
61
|
+
prettier --write "**/*.mdc"
|
|
62
|
+
prettier --write "**/*.md"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
For MDC Syntax Reference, please check [remark-mdc](https://github.com/nuxt-content/remark-mdc).
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
[MIT](./LICENSE). Made with ❤️ by [Ray](https://github.com/so1ve)
|
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
|
@@ -12,17 +12,6 @@ import { createSyncFn } from "synckit";
|
|
|
12
12
|
//#region src/constants.ts
|
|
13
13
|
const AST_FORMAT = "mdc";
|
|
14
14
|
|
|
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
15
|
//#endregion
|
|
27
16
|
//#region src/is.ts
|
|
28
17
|
const isTextComponentNode = (node) => node.type === "textComponent";
|
|
@@ -52,6 +41,39 @@ function linkNeedsCustomPrinting(node) {
|
|
|
52
41
|
return false;
|
|
53
42
|
}
|
|
54
43
|
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/validate.ts
|
|
46
|
+
function validateYamlBlocks(ast, text) {
|
|
47
|
+
function visit(node) {
|
|
48
|
+
if (isContainerComponentNode(node)) {
|
|
49
|
+
const pos = node.position;
|
|
50
|
+
if (pos) {
|
|
51
|
+
const startLine = pos.start.line;
|
|
52
|
+
const lines = text.split("\n");
|
|
53
|
+
if (startLine < lines.length) {
|
|
54
|
+
if (lines[startLine]?.trim() === "---" && !node.rawData) throw new Error(`Invalid YAML block in component "${node.name}" at line ${startLine + 1}: YAML block is
|
|
55
|
+
not properly closed`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (node.children) for (const child of node.children) visit(child);
|
|
59
|
+
} else if ("children" in node && Array.isArray(node.children)) for (const child of node.children) visit(child);
|
|
60
|
+
}
|
|
61
|
+
visit(ast);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/parsers.ts
|
|
66
|
+
const parsers = { [AST_FORMAT]: {
|
|
67
|
+
...markdown.parsers.markdown,
|
|
68
|
+
astFormat: AST_FORMAT,
|
|
69
|
+
parse: async (text) => {
|
|
70
|
+
const processor = unified().use(remarkParse, { commonmark: true }).use(remarkMath).use(remarkGfm).use(remarkMdc);
|
|
71
|
+
const ast = await processor.run(processor.parse(text));
|
|
72
|
+
validateYamlBlocks(ast, text);
|
|
73
|
+
return ast;
|
|
74
|
+
}
|
|
75
|
+
} };
|
|
76
|
+
|
|
55
77
|
//#endregion
|
|
56
78
|
//#region src/visitor-keys.ts
|
|
57
79
|
const visitorKeys = {
|
|
@@ -71,8 +93,23 @@ const extendedInlineNodes = [
|
|
|
71
93
|
"inlineCode",
|
|
72
94
|
"emphasis"
|
|
73
95
|
];
|
|
74
|
-
const
|
|
96
|
+
const extendedInlineNodesHaveAttributes = (node) => extendedInlineNodes.includes(node.type) && "attributes" in node;
|
|
75
97
|
const escapeQuotes = (value, quote) => value.replace(new RegExp(quote, "g"), `\\${quote}`);
|
|
98
|
+
/**
|
|
99
|
+
* Quote a string value using Prettier's quote selection logic:
|
|
100
|
+
*
|
|
101
|
+
* - Use preferred quote if value doesn't contain it
|
|
102
|
+
* - Switch to alternative quote if value contains preferred but not alternative
|
|
103
|
+
* - Use preferred quote with escaping if value contains both
|
|
104
|
+
*/
|
|
105
|
+
function quoteString(value, options) {
|
|
106
|
+
const preferredQuote = options.singleQuote ? "'" : "\"";
|
|
107
|
+
const alternativeQuote = options.singleQuote ? "\"" : "'";
|
|
108
|
+
const hasPreferred = value.includes(preferredQuote);
|
|
109
|
+
const hasAlternative = value.includes(alternativeQuote);
|
|
110
|
+
const quote = hasPreferred && !hasAlternative ? alternativeQuote : preferredQuote;
|
|
111
|
+
return `${quote}${escapeQuotes(value.replace(/\\/g, "\\\\"), quote)}${quote}`;
|
|
112
|
+
}
|
|
76
113
|
|
|
77
114
|
//#endregion
|
|
78
115
|
//#region src/yaml.ts
|
|
@@ -92,16 +129,15 @@ const formatYaml = (text, options) => {
|
|
|
92
129
|
const { hardline, join } = doc.builders;
|
|
93
130
|
const mapChildren = (path, print) => path.map(print, "children");
|
|
94
131
|
function serializeValue(value, options) {
|
|
95
|
-
|
|
96
|
-
if (typeof value === "string") return `${quote}${escapeQuotes(value.replace(/\\/g, "\\\\"), quote)}${quote}`;
|
|
132
|
+
if (typeof value === "string") return quoteString(value, options);
|
|
97
133
|
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
98
|
-
|
|
134
|
+
const preferredQuote = options.singleQuote ? "'" : "\"";
|
|
135
|
+
return `${preferredQuote}${escapeQuotes(JSON.stringify(value), preferredQuote)}${preferredQuote}`;
|
|
99
136
|
}
|
|
100
|
-
function printAttributes(
|
|
101
|
-
|
|
102
|
-
if (!attrs || Object.keys(attrs).length === 0) return "";
|
|
137
|
+
function printAttributes({ attributes }, options) {
|
|
138
|
+
if (!attributes || Object.keys(attributes).length === 0) return "";
|
|
103
139
|
const parts = [];
|
|
104
|
-
for (const [key, value] of Object.entries(
|
|
140
|
+
for (const [key, value] of Object.entries(attributes)) if (key === "id") parts.push(`#${value}`);
|
|
105
141
|
else if (key === "class") {
|
|
106
142
|
const classes = String(value).split(/\s+/).filter(Boolean);
|
|
107
143
|
for (const cls of classes) parts.push(`.${cls}`);
|
|
@@ -115,8 +151,7 @@ function printAttributes(node, options) {
|
|
|
115
151
|
function printBinding(node, options) {
|
|
116
152
|
const value = node.attributes?.value ?? "";
|
|
117
153
|
const defaultValue = node.attributes?.defaultValue;
|
|
118
|
-
|
|
119
|
-
if (defaultValue !== void 0 && defaultValue !== "undefined") return [`{{ ${value} || ${quote}${escapeQuotes(String(defaultValue), quote)}${quote} }}`];
|
|
154
|
+
if (defaultValue !== void 0 && defaultValue !== "undefined") return [`{{ ${value} || ${quoteString(String(defaultValue), options)} }}`];
|
|
120
155
|
return [`{{ ${value} }}`];
|
|
121
156
|
}
|
|
122
157
|
/**
|
|
@@ -134,6 +169,7 @@ function isShorthandSpan(node) {
|
|
|
134
169
|
}
|
|
135
170
|
return nodeLength <= 3;
|
|
136
171
|
}
|
|
172
|
+
const EMPTY_PROPS_RE = /\{\s*\}\s*$/;
|
|
137
173
|
/**
|
|
138
174
|
* Print inline text component: :name[content]{attrs} Special cases:
|
|
139
175
|
*
|
|
@@ -165,6 +201,10 @@ function printTextComponent(path, print, options) {
|
|
|
165
201
|
const parts = [`:${node.name}`];
|
|
166
202
|
if (node.children && node.children.length > 0) parts.push("[", ...printChildrenWithEscapedBrackets(), "]");
|
|
167
203
|
if (attrStr) parts.push(attrStr);
|
|
204
|
+
else if (node.position) {
|
|
205
|
+
const text = options.originalText.slice(node.position.start.offset, node.position.end.offset);
|
|
206
|
+
if (EMPTY_PROPS_RE.test(text)) parts.push("{}");
|
|
207
|
+
}
|
|
168
208
|
return parts;
|
|
169
209
|
}
|
|
170
210
|
/**
|
|
@@ -180,7 +220,7 @@ function getContainerDepth(path) {
|
|
|
180
220
|
*/
|
|
181
221
|
function printRawData(rawData, options) {
|
|
182
222
|
if (!rawData) return [];
|
|
183
|
-
let content = rawData.slice(1, -3).trimEnd();
|
|
223
|
+
let content = rawData.trimEnd().slice(1, -3).trimEnd();
|
|
184
224
|
if (!content) return [];
|
|
185
225
|
content = formatYaml(content, options);
|
|
186
226
|
return [
|
|
@@ -203,8 +243,15 @@ function printContainerComponent(path, print, options) {
|
|
|
203
243
|
const attrStr = printAttributes(node, options);
|
|
204
244
|
if (attrStr) parts.push(attrStr);
|
|
205
245
|
parts.push(hardline);
|
|
206
|
-
|
|
246
|
+
const rawDataDoc = printRawData(node.rawData, options);
|
|
247
|
+
parts.push(...rawDataDoc);
|
|
207
248
|
if (node.children && node.children.length > 0) {
|
|
249
|
+
if (rawDataDoc.length > 0 && node.rawData) {
|
|
250
|
+
const componentStartLine = node.position?.start.line ?? 0;
|
|
251
|
+
const rawDataNewlines = (node.rawData.match(/\n/g) ?? []).length;
|
|
252
|
+
const rawDataEndLine = componentStartLine + 1 + rawDataNewlines;
|
|
253
|
+
if ((node.children[0].position?.start.line ?? 0) > rawDataEndLine + 1) parts.push(hardline);
|
|
254
|
+
}
|
|
208
255
|
const childDocs = mapChildren(path, print);
|
|
209
256
|
parts.push(join(hardline, childDocs));
|
|
210
257
|
parts.push(hardline);
|
|
@@ -257,6 +304,8 @@ function printLink(path, print, options) {
|
|
|
257
304
|
parts.push(" ", quote, node.title, quote);
|
|
258
305
|
}
|
|
259
306
|
parts.push(")");
|
|
307
|
+
const attrStr = printAttributes(node, options);
|
|
308
|
+
if (attrStr) parts.push(attrStr);
|
|
260
309
|
return parts;
|
|
261
310
|
}
|
|
262
311
|
|
|
@@ -271,8 +320,8 @@ const printers = { [AST_FORMAT]: {
|
|
|
271
320
|
},
|
|
272
321
|
print(path, options, print, args) {
|
|
273
322
|
const { node } = path;
|
|
274
|
-
if (hasInlineAttribute(node)) return [mdastPrinter.print(path, options, print, args), printAttributes(node, options)];
|
|
275
323
|
if (isLinkNode(node) && linkNeedsCustomPrinting(node)) return printLink(path, print, options);
|
|
324
|
+
if (extendedInlineNodesHaveAttributes(node)) return [mdastPrinter.print(path, options, print, args), printAttributes(node, options)];
|
|
276
325
|
if (isTextComponentNode(node)) return printTextComponent(path, print, options);
|
|
277
326
|
else if (isContainerComponentNode(node)) return printContainerComponent(path, print, options);
|
|
278
327
|
else if (isComponentContainerSectionNode(node)) return printComponentContainerSection(path, print);
|
|
@@ -281,4 +330,13 @@ const printers = { [AST_FORMAT]: {
|
|
|
281
330
|
} };
|
|
282
331
|
|
|
283
332
|
//#endregion
|
|
284
|
-
|
|
333
|
+
//#region src/index.ts
|
|
334
|
+
const languages = [{
|
|
335
|
+
name: "mdc",
|
|
336
|
+
parsers: [AST_FORMAT],
|
|
337
|
+
extensions: [".mdc"],
|
|
338
|
+
vscodeLanguageIds: ["mdc"]
|
|
339
|
+
}];
|
|
340
|
+
|
|
341
|
+
//#endregion
|
|
342
|
+
export { languages, parsers, printers };
|