-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathindex.mjs
More file actions
120 lines (105 loc) · 3.28 KB
/
index.mjs
File metadata and controls
120 lines (105 loc) · 3.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import htmlParserPlugin from "prettier/plugins/html";
import { formatAlpineAttribute, isAlpineAttribute } from "./alpine.mjs";
import { replaceDjangoTags } from "./django.mjs";
const htmlParser = htmlParserPlugin.parsers.html;
const htmlPrinter = htmlParserPlugin.printers.html;
/** @type {Record<string, import('prettier').Parser>} */
export const parsers = {
html: {
...htmlParser,
preprocess(text, options) {
// Remove django template tags with placeholders
const { djangoTags, newString, nesting } = replaceDjangoTags(text);
// Store them for later
options.djangoTags = djangoTags;
options.nesting = nesting;
return newString;
},
},
};
// Perform custom Alpine and Django printing, including reinserting Django tags
/** @type {Record<string, import('prettier').Printer>} */
export const printers = {
html: {
...htmlPrinter,
preprocess: async (ast, options) => {
ast = htmlPrinter.preprocess(ast, options);
// Find Alpine directives and format them
const traverse = async (node) => {
if (node.type === "element") {
if (node.attrs) {
for (const attr of node.attrs) {
if (attr.name && attr.value && isAlpineAttribute(attr.name)) {
attr.value = await formatAlpineAttribute(
attr.name,
attr.value,
options,
options.nesting[attr.valueSpan.start.line]
);
}
}
}
}
if (node.children) {
for (const child of node.children) {
await traverse(child);
}
}
};
await traverse(ast);
return ast;
},
print(path, options, print) {
let doc = htmlPrinter.print(path, options, print);
// format all django tags
const djangoTags =
options.djangoTags && Object.values(options.djangoTags);
djangoTags?.forEach((tag) => {
tag.tag = formatDjangoTag(tag.tag);
});
const memory = new Set();
const traverse = (node) => {
if (typeof node === "string") {
if (node.indexOf("__DJANGO_TAG_") === -1) return node;
// Reinsert django tags
djangoTags?.forEach((tag) => {
node = node.replace(tag.key, formatDjangoTag(tag.tag));
});
return node;
}
if (memory.has(node)) return node;
memory.add(node);
if (node.contents) {
node.contents = traverse(node.contents);
} else if (node.parts) {
node.parts = traverse(node.parts);
} else if (Array.isArray(node)) {
for (let i = 0; i < node.length; i++) {
node[i] = traverse(node[i]);
}
}
return node;
};
doc = traverse(doc);
return doc;
},
},
};
export const defaultOptions = {
singleQuote: true,
};
function formatDjangoTag(text) {
const varMatch = text.match(/^\{\{(\w+)\}\}$/);
if (varMatch) {
return '{{ ' + varMatch[1] + ' }}';
}
// Remove duplicate spaces that are not leading and inside quotes
const tagMatch = text.match(/^(\{% *)(.*)/);
if (tagMatch) {
return tagMatch[1] + tagMatch[2].replace(
/ {2,}(?=([^"\\]*(\\.|"([^"\\]*\\.)*[^"\\]*"))*[^"]*$)/g,
" "
);
}
return text;
}