@hekana/prettier-plugin-sneferu 0.0.0-stage → 0.3.0
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/CHANGELOG.md +9 -0
- package/LICENSE +1 -0
- package/README.md +14 -2
- package/dist/index.cjs +646 -0
- package/dist/index.d.cts +68 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +614 -0
- package/package.json +50 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Version bump to keep the packages in lockstep with the 0.3 language changes; formatting handles the new `hk-*` tags and localized settings.
|
|
8
|
+
|
|
9
|
+
Compatibility: artifacts built with 0.2 render the same HTML and CSS under 0.3, with two exceptions. `hk-button` links that are store-relative gain the `/en` prefix on English pages, and SVG element and attribute names keep their camelCase (for example `viewBox`). Responsive images now carry `width` and `height` attributes taken from props, so image markup differs from 0.2.
|
package/LICENSE
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Copyright (c) 2026 Hekana. All rights reserved. Use of these packages is governed by the Hekana developer terms.
|
package/README.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @hekana/prettier-plugin-sneferu
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Prettier 3 plugin that formats Sneferu theme files (`.sneferu`), part of Sneferu, the theme language and SDK for Hekana stores.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm i -D prettier @hekana/prettier-plugin-sneferu
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
`.prettierrc`:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{ "plugins": ["@hekana/prettier-plugin-sneferu"] }
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Docs: https://hekana.dev
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
default: () => index_default,
|
|
24
|
+
defaultOptions: () => defaultOptions,
|
|
25
|
+
languages: () => languages,
|
|
26
|
+
options: () => options,
|
|
27
|
+
parse: () => parse,
|
|
28
|
+
parsers: () => parsers,
|
|
29
|
+
printers: () => printers
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
|
|
33
|
+
// src/parse.ts
|
|
34
|
+
var import_sneferu_compiler = require("@hekana/sneferu-compiler");
|
|
35
|
+
var SneferuSyntaxError = class extends Error {
|
|
36
|
+
loc;
|
|
37
|
+
constructor(message, src, offset) {
|
|
38
|
+
const before = src.slice(0, offset).split("\n");
|
|
39
|
+
const line3 = before.length;
|
|
40
|
+
const column = before[before.length - 1].length + 1;
|
|
41
|
+
super(`${message} (${line3}:${column})`);
|
|
42
|
+
this.loc = { start: { line: line3, column } };
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
function lex(src) {
|
|
46
|
+
const toks = [];
|
|
47
|
+
let i = 0;
|
|
48
|
+
let text = "";
|
|
49
|
+
let textStart = 0;
|
|
50
|
+
const fail = (m, at2 = i) => {
|
|
51
|
+
throw new SneferuSyntaxError(m, src, at2);
|
|
52
|
+
};
|
|
53
|
+
const flush = () => {
|
|
54
|
+
if (text) toks.push({ t: "text", v: text, start: textStart, end: i });
|
|
55
|
+
text = "";
|
|
56
|
+
};
|
|
57
|
+
const at = (s) => src.startsWith(s, i);
|
|
58
|
+
while (i < src.length) {
|
|
59
|
+
if (at("<!--")) {
|
|
60
|
+
flush();
|
|
61
|
+
const end = src.indexOf("-->", i);
|
|
62
|
+
if (end === -1) fail("unterminated <!-- comment");
|
|
63
|
+
toks.push({ t: "comment", v: src.slice(i, end + 3), start: i, end: end + 3 });
|
|
64
|
+
i = end + 3;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (at("{{") || at("{%")) {
|
|
68
|
+
flush();
|
|
69
|
+
const isInterp = at("{{");
|
|
70
|
+
const end = src.indexOf(isInterp ? "}}" : "%}", i + 2);
|
|
71
|
+
if (end === -1) fail(`unterminated ${isInterp ? "{{" : "{%"}`);
|
|
72
|
+
const inner = src.slice(i + 2, end).trim();
|
|
73
|
+
toks.push(isInterp ? { t: "interp", src: inner, start: i, end: end + 2 } : { t: "dir", src: inner, start: i, end: end + 2 });
|
|
74
|
+
i = end + 2;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (at("<hk-schema>")) {
|
|
78
|
+
flush();
|
|
79
|
+
const end = src.indexOf("</hk-schema>", i);
|
|
80
|
+
if (end === -1) fail("unterminated <hk-schema>");
|
|
81
|
+
const raw = src.slice(i + "<hk-schema>".length, end);
|
|
82
|
+
toks.push({ t: "schema", json: raw.trim(), raw, start: i, end: end + "</hk-schema>".length });
|
|
83
|
+
i = end + "</hk-schema>".length;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (at("</")) {
|
|
87
|
+
const m = /^<\/([a-zA-Z][a-zA-Z0-9-]*)\s*>/.exec(src.slice(i));
|
|
88
|
+
if (m) {
|
|
89
|
+
flush();
|
|
90
|
+
toks.push({ t: "close", name: m[1], start: i, end: i + m[0].length });
|
|
91
|
+
i += m[0].length;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (src[i] === "<" && /[a-zA-Z]/.test(src[i + 1] ?? "")) {
|
|
96
|
+
flush();
|
|
97
|
+
const start = i;
|
|
98
|
+
i++;
|
|
99
|
+
let name = "";
|
|
100
|
+
while (i < src.length && /[a-zA-Z0-9-]/.test(src[i])) name += src[i++];
|
|
101
|
+
const attrs = [];
|
|
102
|
+
let selfClosing = false;
|
|
103
|
+
let closed = false;
|
|
104
|
+
while (i < src.length) {
|
|
105
|
+
while (/\s/.test(src[i] ?? "")) i++;
|
|
106
|
+
if (at("/>")) {
|
|
107
|
+
selfClosing = true;
|
|
108
|
+
i += 2;
|
|
109
|
+
closed = true;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
if (src[i] === ">") {
|
|
113
|
+
i++;
|
|
114
|
+
closed = true;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
let aName = "";
|
|
118
|
+
while (i < src.length && /[^\s=/>"']/.test(src[i])) aName += src[i++];
|
|
119
|
+
if (!aName) fail(`unexpected '${src[i]}' in <${name}>`);
|
|
120
|
+
let value = null;
|
|
121
|
+
let quote = "";
|
|
122
|
+
if (src[i] === "=") {
|
|
123
|
+
i++;
|
|
124
|
+
const q = src[i];
|
|
125
|
+
if (q === '"' || q === "'") {
|
|
126
|
+
const end = src.indexOf(q, i + 1);
|
|
127
|
+
if (end === -1) fail(`unterminated value for ${aName}`);
|
|
128
|
+
value = src.slice(i + 1, end);
|
|
129
|
+
quote = q;
|
|
130
|
+
i = end + 1;
|
|
131
|
+
} else {
|
|
132
|
+
value = "";
|
|
133
|
+
while (i < src.length && /[^\s>]/.test(src[i])) value += src[i++];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
attrs.push({ name: aName, value, quote });
|
|
137
|
+
}
|
|
138
|
+
if (!closed) fail(`unterminated tag <${name}>`, start);
|
|
139
|
+
toks.push({ t: "open", name, attrs, selfClosing, start, end: i });
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (!text) textStart = i;
|
|
143
|
+
text += src[i++];
|
|
144
|
+
}
|
|
145
|
+
flush();
|
|
146
|
+
return toks;
|
|
147
|
+
}
|
|
148
|
+
var keywordOf = (src) => src.split(/\s+/)[0] ?? "";
|
|
149
|
+
function parse(src) {
|
|
150
|
+
const root = { type: "root", children: [], start: 0, end: src.length, source: src };
|
|
151
|
+
const stack = [{ kind: "root", body: root.children }];
|
|
152
|
+
const top = () => stack[stack.length - 1];
|
|
153
|
+
const fail = (m, at) => {
|
|
154
|
+
throw new SneferuSyntaxError(m, src, at);
|
|
155
|
+
};
|
|
156
|
+
for (const tok of lex(src)) {
|
|
157
|
+
switch (tok.t) {
|
|
158
|
+
case "text":
|
|
159
|
+
top().body.push({ type: "text", value: tok.v, start: tok.start, end: tok.end });
|
|
160
|
+
break;
|
|
161
|
+
case "comment":
|
|
162
|
+
top().body.push({ type: "comment", value: tok.v, start: tok.start, end: tok.end });
|
|
163
|
+
break;
|
|
164
|
+
case "interp":
|
|
165
|
+
top().body.push({ type: "interp", src: tok.src, start: tok.start, end: tok.end });
|
|
166
|
+
break;
|
|
167
|
+
case "schema":
|
|
168
|
+
top().body.push({ type: "schema", json: tok.json, raw: tok.raw, start: tok.start, end: tok.end });
|
|
169
|
+
break;
|
|
170
|
+
case "open": {
|
|
171
|
+
const node = { type: "element", name: tok.name, attrs: tok.attrs, selfClosing: tok.selfClosing, children: [], start: tok.start, end: tok.end, openEnd: tok.end, closeStart: tok.end };
|
|
172
|
+
top().body.push(node);
|
|
173
|
+
if (!tok.selfClosing && !import_sneferu_compiler.VOID_ELEMENTS.has(tok.name.toLowerCase())) stack.push({ kind: "el", node, body: node.children });
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case "close": {
|
|
177
|
+
const f = top();
|
|
178
|
+
if (f.kind !== "el" || f.node.name.toLowerCase() !== tok.name.toLowerCase()) fail(`</${tok.name}> does not close the open ${f.kind === "el" ? `<${f.node.name}>` : f.kind === "root" ? "nothing" : `{% ${f.kind} %}`}`, tok.start);
|
|
179
|
+
const el = f.node;
|
|
180
|
+
el.end = tok.end;
|
|
181
|
+
el.closeStart = tok.start;
|
|
182
|
+
stack.pop();
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
case "dir": {
|
|
186
|
+
const kw = keywordOf(tok.src);
|
|
187
|
+
if (kw === "if") {
|
|
188
|
+
const node = { type: "if", branches: [{ src: tok.src, body: [] }], endSrc: "", start: tok.start, end: tok.end };
|
|
189
|
+
top().body.push(node);
|
|
190
|
+
stack.push({ kind: "if", node, body: node.branches[0].body });
|
|
191
|
+
} else if (kw === "elsif" || kw === "else") {
|
|
192
|
+
const f = top();
|
|
193
|
+
if (f.kind !== "if") fail(`{% ${kw} %} outside {% if %}`, tok.start);
|
|
194
|
+
const n = f.node;
|
|
195
|
+
if (n.else) fail(`{% ${kw} %} after {% else %}`, tok.start);
|
|
196
|
+
if (kw === "elsif") {
|
|
197
|
+
const b = { src: tok.src, body: [] };
|
|
198
|
+
n.branches.push(b);
|
|
199
|
+
f.body = b.body;
|
|
200
|
+
} else {
|
|
201
|
+
n.else = { src: tok.src, body: [] };
|
|
202
|
+
f.body = n.else.body;
|
|
203
|
+
}
|
|
204
|
+
} else if (kw === "endif" || kw === "endfor") {
|
|
205
|
+
const f = top();
|
|
206
|
+
if (f.kind !== (kw === "endif" ? "if" : "for")) fail(`{% ${kw} %} has no matching opening tag`, tok.start);
|
|
207
|
+
const n = f.node;
|
|
208
|
+
n.endSrc = tok.src;
|
|
209
|
+
n.end = tok.end;
|
|
210
|
+
stack.pop();
|
|
211
|
+
} else if (kw === "for") {
|
|
212
|
+
const node = { type: "for", src: tok.src, body: [], endSrc: "", start: tok.start, end: tok.end };
|
|
213
|
+
top().body.push(node);
|
|
214
|
+
stack.push({ kind: "for", node, body: node.body });
|
|
215
|
+
} else {
|
|
216
|
+
top().body.push({ type: "directive", kw, src: tok.src, start: tok.start, end: tok.end });
|
|
217
|
+
}
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const open = top();
|
|
223
|
+
if (open.kind !== "root") fail(`${open.kind === "el" ? `<${open.node.name}>` : `{% ${open.kind} %}`} is never closed`, open.node.start);
|
|
224
|
+
return root;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/print.ts
|
|
228
|
+
var import_prettier2 = require("prettier");
|
|
229
|
+
var import_sneferu_compiler3 = require("@hekana/sneferu-compiler");
|
|
230
|
+
|
|
231
|
+
// src/expr.ts
|
|
232
|
+
var import_sneferu_compiler2 = require("@hekana/sneferu-compiler");
|
|
233
|
+
var KEYWORDS = /* @__PURE__ */ new Set(["and", "or", "not"]);
|
|
234
|
+
function tokenize(src) {
|
|
235
|
+
const out = [];
|
|
236
|
+
let i = 0;
|
|
237
|
+
while (i < src.length) {
|
|
238
|
+
const c = src[i];
|
|
239
|
+
if (/\s/.test(c)) {
|
|
240
|
+
i++;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (c === "'" || c === '"') {
|
|
244
|
+
const end = src.indexOf(c, i + 1);
|
|
245
|
+
if (end === -1) return void 0;
|
|
246
|
+
out.push({ k: "str", v: src.slice(i + 1, end), q: c });
|
|
247
|
+
i = end + 1;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const num = /^-?\d+(\.\d+)?/.exec(src.slice(i));
|
|
251
|
+
if (num && !/[A-Za-z_]/.test(src[i + num[0].length] ?? "")) {
|
|
252
|
+
out.push({ k: "num", v: num[0] });
|
|
253
|
+
i += num[0].length;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
const id = /^[A-Za-z_][A-Za-z0-9_-]*/.exec(src.slice(i));
|
|
257
|
+
if (id) {
|
|
258
|
+
out.push({ k: "id", v: id[0] });
|
|
259
|
+
i += id[0].length;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const op = /^(==|!=|>=|<=|>|<)/.exec(src.slice(i));
|
|
263
|
+
if (op) {
|
|
264
|
+
out.push({ k: "op", v: op[0] });
|
|
265
|
+
i += op[0].length;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (".[]()|:,".includes(c)) {
|
|
269
|
+
out.push({ k: "punc", v: c });
|
|
270
|
+
i++;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
return void 0;
|
|
274
|
+
}
|
|
275
|
+
return out;
|
|
276
|
+
}
|
|
277
|
+
var isP = (t, v) => !!t && t.k === "punc" && t.v === v;
|
|
278
|
+
function sep(p, c) {
|
|
279
|
+
if (c.k === "op" || p.k === "op" || isP(c, "|") || isP(p, "|")) return " ";
|
|
280
|
+
if (isP(c, ".") || isP(c, "]") || isP(c, ")") || isP(c, ",") || isP(c, ":") || isP(c, "[")) return "";
|
|
281
|
+
if (isP(p, ".") || isP(p, "[") || isP(p, "(")) return "";
|
|
282
|
+
if (isP(c, "(")) return p.k === "id" && !KEYWORDS.has(p.v) ? "" : " ";
|
|
283
|
+
return " ";
|
|
284
|
+
}
|
|
285
|
+
function printTokens(toks, quote) {
|
|
286
|
+
let out = "";
|
|
287
|
+
toks.forEach((t, i) => {
|
|
288
|
+
if (i > 0) out += sep(toks[i - 1], t);
|
|
289
|
+
if (t.k === "str") {
|
|
290
|
+
const q = t.v.includes(quote) ? t.q : quote;
|
|
291
|
+
out += q + t.v + q;
|
|
292
|
+
} else out += t.v;
|
|
293
|
+
});
|
|
294
|
+
return out;
|
|
295
|
+
}
|
|
296
|
+
var same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
297
|
+
function formatExpr(src, quote) {
|
|
298
|
+
const before = (0, import_sneferu_compiler2.parseExpr)(src);
|
|
299
|
+
if (!before.expr) return src;
|
|
300
|
+
const toks = tokenize(src);
|
|
301
|
+
if (!toks) return src;
|
|
302
|
+
const out = printTokens(toks, quote);
|
|
303
|
+
return same((0, import_sneferu_compiler2.parseExpr)(out), before) ? out : src;
|
|
304
|
+
}
|
|
305
|
+
function formatDirective(src, kw, quote) {
|
|
306
|
+
const arg = src.slice(kw.length).trim();
|
|
307
|
+
if (kw === "if" || kw === "elsif") return arg ? `${kw} ${formatExpr(arg, quote)}` : src;
|
|
308
|
+
if (kw === "else" || kw === "endif" || kw === "endfor") return arg ? src : kw;
|
|
309
|
+
if (kw === "for") {
|
|
310
|
+
const m = /^([A-Za-z_][A-Za-z0-9_]*)\s+in\s+(.+?)(?:\s+limit:\s*(\S+))?$/.exec(arg);
|
|
311
|
+
if (!m) return src;
|
|
312
|
+
const out = `for ${m[1]} in ${formatExpr(m[2], quote)}${m[3] !== void 0 ? ` limit: ${m[3]}` : ""}`;
|
|
313
|
+
return same((0, import_sneferu_compiler2.parseFor)(out.slice(4)), (0, import_sneferu_compiler2.parseFor)(arg)) ? out : src;
|
|
314
|
+
}
|
|
315
|
+
if (kw === "render") {
|
|
316
|
+
const out = formatRender(arg, quote);
|
|
317
|
+
return out !== void 0 && same(renderMeaning(out.slice(0)), renderMeaning(arg)) ? `render ${out}` : src;
|
|
318
|
+
}
|
|
319
|
+
return src;
|
|
320
|
+
}
|
|
321
|
+
var RENDER = /^'([a-z0-9-]+)'\s*(?:,\s*(.*))?$/;
|
|
322
|
+
var PAIR = /^([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(.+)$/;
|
|
323
|
+
function renderMeaning(arg) {
|
|
324
|
+
const m = RENDER.exec(arg);
|
|
325
|
+
if (!m) return null;
|
|
326
|
+
return [m[1], (0, import_sneferu_compiler2.splitArgs)(m[2] ?? "").map((pair) => {
|
|
327
|
+
const pm = PAIR.exec(pair);
|
|
328
|
+
return pm ? [pm[1], (0, import_sneferu_compiler2.parseExpr)(pm[2])] : ["?", pair];
|
|
329
|
+
})];
|
|
330
|
+
}
|
|
331
|
+
function formatRender(arg, quote) {
|
|
332
|
+
const m = RENDER.exec(arg);
|
|
333
|
+
if (!m) return void 0;
|
|
334
|
+
const pairs = (0, import_sneferu_compiler2.splitArgs)(m[2] ?? "").map((pair) => {
|
|
335
|
+
const pm = PAIR.exec(pair);
|
|
336
|
+
return pm ? `${pm[1]}: ${formatExpr(pm[2], quote)}` : void 0;
|
|
337
|
+
});
|
|
338
|
+
if (pairs.some((p) => p === void 0)) return void 0;
|
|
339
|
+
return [`'${m[1]}'`, ...pairs].join(", ");
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// src/json.ts
|
|
343
|
+
var import_prettier = require("prettier");
|
|
344
|
+
var { group, indent, line, softline, hardline, join } = import_prettier.doc.builders;
|
|
345
|
+
function parseJson(src) {
|
|
346
|
+
let i = 0;
|
|
347
|
+
const ws = () => {
|
|
348
|
+
while (i < src.length && " \n\r".includes(src[i])) i++;
|
|
349
|
+
};
|
|
350
|
+
const str = () => {
|
|
351
|
+
const m = /^"(?:[^"\\\u0000-\u001f]|\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4}))*"/.exec(src.slice(i));
|
|
352
|
+
if (!m) throw new Error("string");
|
|
353
|
+
i += m[0].length;
|
|
354
|
+
return m[0];
|
|
355
|
+
};
|
|
356
|
+
const value = () => {
|
|
357
|
+
ws();
|
|
358
|
+
const c = src[i];
|
|
359
|
+
if (c === "{") {
|
|
360
|
+
i++;
|
|
361
|
+
ws();
|
|
362
|
+
const props = [];
|
|
363
|
+
if (src[i] === "}") {
|
|
364
|
+
i++;
|
|
365
|
+
return { k: "obj", props };
|
|
366
|
+
}
|
|
367
|
+
for (; ; ) {
|
|
368
|
+
ws();
|
|
369
|
+
const key = str();
|
|
370
|
+
ws();
|
|
371
|
+
if (src[i++] !== ":") throw new Error(":");
|
|
372
|
+
props.push([key, value()]);
|
|
373
|
+
ws();
|
|
374
|
+
const d = src[i++];
|
|
375
|
+
if (d === "}") return { k: "obj", props };
|
|
376
|
+
if (d !== ",") throw new Error(",");
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (c === "[") {
|
|
380
|
+
i++;
|
|
381
|
+
ws();
|
|
382
|
+
const items = [];
|
|
383
|
+
if (src[i] === "]") {
|
|
384
|
+
i++;
|
|
385
|
+
return { k: "arr", items };
|
|
386
|
+
}
|
|
387
|
+
for (; ; ) {
|
|
388
|
+
items.push(value());
|
|
389
|
+
ws();
|
|
390
|
+
const d = src[i++];
|
|
391
|
+
if (d === "]") return { k: "arr", items };
|
|
392
|
+
if (d !== ",") throw new Error(",");
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (c === '"') return { k: "raw", v: str() };
|
|
396
|
+
const m = /^(?:-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null)/.exec(src.slice(i));
|
|
397
|
+
if (!m) throw new Error("value");
|
|
398
|
+
i += m[0].length;
|
|
399
|
+
return { k: "raw", v: m[0] };
|
|
400
|
+
};
|
|
401
|
+
try {
|
|
402
|
+
JSON.parse(src);
|
|
403
|
+
const v = value();
|
|
404
|
+
ws();
|
|
405
|
+
return i === src.length ? v : void 0;
|
|
406
|
+
} catch {
|
|
407
|
+
return void 0;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
function print(j) {
|
|
411
|
+
if (j.k === "raw") return j.v;
|
|
412
|
+
if (j.k === "obj") {
|
|
413
|
+
if (j.props.length === 0) return "{}";
|
|
414
|
+
return ["{", indent([hardline, join([",", hardline], j.props.map(([k, v]) => [k, ": ", print(v)]))]), hardline, "}"];
|
|
415
|
+
}
|
|
416
|
+
if (j.items.length === 0) return "[]";
|
|
417
|
+
const items = j.items.map(print);
|
|
418
|
+
if (j.items.every((x) => x.k === "raw")) return group(["[", indent([softline, join([",", line], items)]), softline, "]"]);
|
|
419
|
+
return ["[", indent([hardline, join([",", hardline], items)]), hardline, "]"];
|
|
420
|
+
}
|
|
421
|
+
function jsonDoc(src) {
|
|
422
|
+
const j = parseJson(src);
|
|
423
|
+
return j ? print(j) : void 0;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// src/print.ts
|
|
427
|
+
var { group: group2, indent: indent2, line: line2, softline: softline2, hardline: hardline2, fill, literalline } = import_prettier2.doc.builders;
|
|
428
|
+
var { replaceEndOfLine } = import_prettier2.doc.utils;
|
|
429
|
+
var WS_SPLIT = /([ \t\n\r\f]+)/;
|
|
430
|
+
var IGNORE = /^<!--\s*prettier-ignore\s*-->$/;
|
|
431
|
+
var isWord = (x) => !!x && "word" in x;
|
|
432
|
+
var neighbor = (x) => x === null ? null : "word" in x ? { other: true } : x.node.type === "element" ? { el: x.node.name } : { other: true };
|
|
433
|
+
var blockyNode = (n) => !!n && ["element", "if", "for", "directive", "schema"].includes(n.type);
|
|
434
|
+
var blocky = (x) => !!x && "node" in x && blockyNode(x.node);
|
|
435
|
+
var edgeBlocky = (x) => !!x && "node" in x && (x.node.type === "element" ? (0, import_sneferu_compiler3.isBlockLevel)(x.node.name) : blockyNode(x.node));
|
|
436
|
+
var newGap = () => ({ ws: false, hidden: [], runs: [{ ws: false, newlines: 0 }] });
|
|
437
|
+
function split(children) {
|
|
438
|
+
const items = [];
|
|
439
|
+
const gaps = [newGap()];
|
|
440
|
+
const gap = () => gaps[gaps.length - 1];
|
|
441
|
+
let ignoreNext = false;
|
|
442
|
+
const push = (it) => {
|
|
443
|
+
items.push(it);
|
|
444
|
+
gaps.push(newGap());
|
|
445
|
+
};
|
|
446
|
+
for (const n of children) {
|
|
447
|
+
if (n.type === "text") {
|
|
448
|
+
for (const part of n.value.split(WS_SPLIT)) {
|
|
449
|
+
if (part === "") continue;
|
|
450
|
+
if (/^[ \t\n\r\f]+$/.test(part)) {
|
|
451
|
+
const run = gap().runs[gap().runs.length - 1];
|
|
452
|
+
gap().ws = true;
|
|
453
|
+
run.ws = true;
|
|
454
|
+
run.newlines = Math.max(run.newlines, (part.match(/\n/g) ?? []).length);
|
|
455
|
+
} else {
|
|
456
|
+
push({ word: part });
|
|
457
|
+
ignoreNext = false;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
} else if (n.type === "comment" || n.type === "schema") {
|
|
461
|
+
gap().hidden.push(n);
|
|
462
|
+
gap().runs.push({ ws: false, newlines: 0 });
|
|
463
|
+
if (n.type === "comment" && IGNORE.test(n.value)) ignoreNext = true;
|
|
464
|
+
} else {
|
|
465
|
+
push({ node: n, ignore: ignoreNext });
|
|
466
|
+
ignoreNext = false;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return { items, gaps };
|
|
470
|
+
}
|
|
471
|
+
function isFree(items, i, ctx, edges) {
|
|
472
|
+
const L = items[i - 1] ?? null;
|
|
473
|
+
const R = items[i] ?? null;
|
|
474
|
+
if (isWord(L) && isWord(R)) return false;
|
|
475
|
+
if (isWord(L)) return (0, import_sneferu_compiler3.freeBeside)(neighbor(R), ctx, edges);
|
|
476
|
+
if (isWord(R)) return (0, import_sneferu_compiler3.freeBeside)(neighbor(L), ctx, edges);
|
|
477
|
+
return (0, import_sneferu_compiler3.freeBeside)(neighbor(L), ctx, edges) || (0, import_sneferu_compiler3.freeBeside)(neighbor(R), ctx, edges);
|
|
478
|
+
}
|
|
479
|
+
var verbatim = (s) => replaceEndOfLine(s, literalline);
|
|
480
|
+
function printList(children, ctx, edges, c) {
|
|
481
|
+
const { items, gaps } = split(children);
|
|
482
|
+
const parts = [];
|
|
483
|
+
let keepSpace = false;
|
|
484
|
+
gaps.forEach((g, i) => {
|
|
485
|
+
const L = items[i - 1] ?? null;
|
|
486
|
+
const R = items[i] ?? null;
|
|
487
|
+
const free = isFree(items, i, ctx, edges);
|
|
488
|
+
if (!free && g.ws) keepSpace = true;
|
|
489
|
+
g.runs.forEach((run, k) => {
|
|
490
|
+
const edge = L === null || R === null;
|
|
491
|
+
const near = (x) => edge && edges ? edgeBlocky(x) : blocky(x);
|
|
492
|
+
const left = k === 0 ? near(L) : blockyNode(g.hidden[k - 1]);
|
|
493
|
+
const right = k === g.runs.length - 1 ? near(R) : blockyNode(g.hidden[k]);
|
|
494
|
+
const inner = (k > 0 || L !== null) && (k < g.runs.length - 1 || R !== null);
|
|
495
|
+
const hard = inner && run.newlines >= 2 ? BLANK : hardline2;
|
|
496
|
+
let sep2;
|
|
497
|
+
if (free) sep2 = g.hidden.length || left || right ? hard : softline2;
|
|
498
|
+
else if (run.ws) sep2 = run.newlines > 0 && (left || right) ? hard : line2;
|
|
499
|
+
else sep2 = "";
|
|
500
|
+
parts.push(sep2);
|
|
501
|
+
if (k < g.hidden.length) parts.push(printNode(g.hidden[k], ctx, c));
|
|
502
|
+
});
|
|
503
|
+
if (R) parts.push(printItem(R, ctx, c));
|
|
504
|
+
});
|
|
505
|
+
return { parts, keepSpace };
|
|
506
|
+
}
|
|
507
|
+
var BLANK = [hardline2, hardline2];
|
|
508
|
+
var isHard = (d) => d === hardline2 || d === BLANK;
|
|
509
|
+
function layout(body) {
|
|
510
|
+
const out = [];
|
|
511
|
+
let run = [];
|
|
512
|
+
for (let i = 0; i < body.length; i++) {
|
|
513
|
+
if (i % 2 === 1 && isHard(body[i])) {
|
|
514
|
+
out.push(fill(run), body[i]);
|
|
515
|
+
run = [];
|
|
516
|
+
} else run.push(body[i]);
|
|
517
|
+
}
|
|
518
|
+
out.push(fill(run));
|
|
519
|
+
return out;
|
|
520
|
+
}
|
|
521
|
+
function wrapList({ parts, keepSpace }) {
|
|
522
|
+
if (parts.length === 1) return keepSpace ? " " : "";
|
|
523
|
+
return [indent2([parts[0], layout(parts.slice(1, -1))]), parts[parts.length - 1]];
|
|
524
|
+
}
|
|
525
|
+
function printItem(it, ctx, c) {
|
|
526
|
+
if ("word" in it) return it.word;
|
|
527
|
+
if (it.ignore) return verbatim(c.src.slice(it.node.start, it.node.end));
|
|
528
|
+
return printNode(it.node, ctx, c);
|
|
529
|
+
}
|
|
530
|
+
function printNode(n, ctx, c) {
|
|
531
|
+
switch (n.type) {
|
|
532
|
+
case "text":
|
|
533
|
+
return n.value;
|
|
534
|
+
case "comment":
|
|
535
|
+
return verbatim(n.value);
|
|
536
|
+
case "interp":
|
|
537
|
+
return `{{ ${formatExpr(n.src, c.quote)} }}`;
|
|
538
|
+
case "directive":
|
|
539
|
+
return `{% ${formatDirective(n.src, n.kw, c.quote)} %}`;
|
|
540
|
+
case "schema": {
|
|
541
|
+
const j = jsonDoc(n.json);
|
|
542
|
+
if (!j) return verbatim(`<hk-schema>${n.raw}</hk-schema>`);
|
|
543
|
+
return c.opts.indentSchema ? ["<hk-schema>", indent2([hardline2, j]), hardline2, "</hk-schema>"] : ["<hk-schema>", hardline2, j, hardline2, "</hk-schema>"];
|
|
544
|
+
}
|
|
545
|
+
case "if": {
|
|
546
|
+
const parts = [];
|
|
547
|
+
for (const b of n.branches) parts.push(`{% ${formatDirective(b.src, keywordOf(b.src), c.quote)} %}`, wrapList(printList(b.body, ctx, false, c)));
|
|
548
|
+
if (n.else) parts.push(`{% ${formatDirective(n.else.src, keywordOf(n.else.src), c.quote)} %}`, wrapList(printList(n.else.body, ctx, false, c)));
|
|
549
|
+
parts.push(`{% ${formatDirective(n.endSrc, keywordOf(n.endSrc), c.quote)} %}`);
|
|
550
|
+
return group2(parts);
|
|
551
|
+
}
|
|
552
|
+
case "for":
|
|
553
|
+
return group2([
|
|
554
|
+
`{% ${formatDirective(n.src, "for", c.quote)} %}`,
|
|
555
|
+
wrapList(printList(n.body, ctx, false, c)),
|
|
556
|
+
`{% ${formatDirective(n.endSrc, keywordOf(n.endSrc), c.quote)} %}`
|
|
557
|
+
]);
|
|
558
|
+
case "element": {
|
|
559
|
+
const open = printOpenTag(n, c);
|
|
560
|
+
if (n.selfClosing || import_sneferu_compiler3.VOID_ELEMENTS.has(n.name.toLowerCase())) return open;
|
|
561
|
+
const close = `</${n.name}>`;
|
|
562
|
+
const inner = (0, import_sneferu_compiler3.innerContext)(n.name, ctx);
|
|
563
|
+
if (inner === "raw") return [open, verbatim(c.src.slice(n.openEnd, n.closeStart)), close];
|
|
564
|
+
return group2([open, wrapList(printList(n.children, inner, true, c)), close]);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
function printOpenTag(n, c) {
|
|
569
|
+
const attrs = n.attrs.map((a) => printAttr(a, c));
|
|
570
|
+
if (attrs.length === 0) return n.selfClosing ? `<${n.name} />` : `<${n.name}>`;
|
|
571
|
+
return group2([`<${n.name}`, indent2([line2, join2(line2, attrs)]), n.selfClosing ? [line2, "/>"] : [softline2, ">"]]);
|
|
572
|
+
}
|
|
573
|
+
var join2 = import_prettier2.doc.builders.join;
|
|
574
|
+
var same2 = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
575
|
+
var INTERP = /\{\{([\s\S]*?)\}\}/g;
|
|
576
|
+
function attrMeaning(v) {
|
|
577
|
+
const parts = [];
|
|
578
|
+
let last = 0;
|
|
579
|
+
for (const m of v.matchAll(INTERP)) {
|
|
580
|
+
if (m.index > last) parts.push(v.slice(last, m.index));
|
|
581
|
+
parts.push((0, import_sneferu_compiler3.parseExpr)(m[1].trim()));
|
|
582
|
+
last = m.index + m[0].length;
|
|
583
|
+
}
|
|
584
|
+
if (last < v.length) parts.push(v.slice(last));
|
|
585
|
+
return parts;
|
|
586
|
+
}
|
|
587
|
+
function printAttr(a, c) {
|
|
588
|
+
if (a.value === null) return a.name;
|
|
589
|
+
const v = a.value;
|
|
590
|
+
const pref = c.opts.singleQuote ? "'" : '"';
|
|
591
|
+
const other = pref === '"' ? "'" : '"';
|
|
592
|
+
for (const q of [pref, other]) {
|
|
593
|
+
const nv = v.includes("{{") ? v.replace(INTERP, (_m, e) => `{{ ${formatExpr(e.trim(), q === '"' ? "'" : '"')} }}`) : v;
|
|
594
|
+
if (!nv.includes(q) && same2(attrMeaning(nv), attrMeaning(v))) return `${a.name}=${q}${nv}${q}`;
|
|
595
|
+
}
|
|
596
|
+
return `${a.name}=${a.quote}${v}${a.quote}`;
|
|
597
|
+
}
|
|
598
|
+
function print2(path, options2) {
|
|
599
|
+
const root = path.node;
|
|
600
|
+
const c = { src: root.source, opts: options2, quote: options2.sneferuSingleQuote ? "'" : '"' };
|
|
601
|
+
const { parts } = printList(root.children, "block", true, c);
|
|
602
|
+
if (parts.length === 1) return "";
|
|
603
|
+
return [layout(parts.slice(1, -1)), hardline2];
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// src/index.ts
|
|
607
|
+
var languages = [
|
|
608
|
+
{ name: "Sneferu", parsers: ["sneferu"], extensions: [".sneferu"], vscodeLanguageIds: ["sneferu"] }
|
|
609
|
+
];
|
|
610
|
+
var parsers = {
|
|
611
|
+
sneferu: {
|
|
612
|
+
parse: (text) => parse(text),
|
|
613
|
+
astFormat: "sneferu-ast",
|
|
614
|
+
locStart: (n) => n.start,
|
|
615
|
+
locEnd: (n) => n.end
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
var printers = {
|
|
619
|
+
"sneferu-ast": { print: print2 }
|
|
620
|
+
};
|
|
621
|
+
var options = {
|
|
622
|
+
sneferuSingleQuote: {
|
|
623
|
+
type: "boolean",
|
|
624
|
+
category: "Sneferu",
|
|
625
|
+
default: true,
|
|
626
|
+
description: "Use single quotes for strings inside {{ }} and {% %} (a string that contains a single quote keeps double quotes)."
|
|
627
|
+
},
|
|
628
|
+
indentSchema: {
|
|
629
|
+
type: "boolean",
|
|
630
|
+
category: "Sneferu",
|
|
631
|
+
default: true,
|
|
632
|
+
description: "Indent the JSON inside <hk-schema> one level."
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
var defaultOptions = { printWidth: 120, tabWidth: 2 };
|
|
636
|
+
var plugin = { languages, parsers, printers, options, defaultOptions };
|
|
637
|
+
var index_default = plugin;
|
|
638
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
639
|
+
0 && (module.exports = {
|
|
640
|
+
defaultOptions,
|
|
641
|
+
languages,
|
|
642
|
+
options,
|
|
643
|
+
parse,
|
|
644
|
+
parsers,
|
|
645
|
+
printers
|
|
646
|
+
});
|