@reteps/tree-sitter-htmlmustache 0.0.36 → 0.0.37
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 +41 -0
- package/cli/out/main.js +148 -12
- package/package.json +1 -1
- package/src/scanner.c +6 -0
- package/tree-sitter-htmlmustache.wasm +0 -0
package/README.md
CHANGED
|
@@ -135,6 +135,47 @@ echo '<div><p>hi</p></div>' | htmlmustache format --stdin
|
|
|
135
135
|
| `--print-width N` | Max line width (default: 80) |
|
|
136
136
|
| `--mustache-spaces` | Add spaces inside mustache delimiters |
|
|
137
137
|
|
|
138
|
+
## Format Ignore
|
|
139
|
+
|
|
140
|
+
Skip formatting for specific regions using ignore directives. Both HTML and Mustache comment forms are supported.
|
|
141
|
+
|
|
142
|
+
### Ignore Next Node
|
|
143
|
+
|
|
144
|
+
Place a comment immediately before the element to preserve its original formatting:
|
|
145
|
+
|
|
146
|
+
```html
|
|
147
|
+
<!-- htmlmustache-ignore -->
|
|
148
|
+
<div class="a" id="b" >
|
|
149
|
+
manually formatted
|
|
150
|
+
</div>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```html
|
|
154
|
+
{{! htmlmustache-ignore }}
|
|
155
|
+
<table><tr><td>compact</td><td>table</td></tr></table>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Only the immediately following sibling node is ignored. Subsequent nodes are formatted normally.
|
|
159
|
+
|
|
160
|
+
### Ignore Region
|
|
161
|
+
|
|
162
|
+
Wrap a region in start/end comments to preserve everything between them:
|
|
163
|
+
|
|
164
|
+
```html
|
|
165
|
+
<!-- htmlmustache-ignore-start -->
|
|
166
|
+
<div class="a" >content</div>
|
|
167
|
+
<p> kept as-is </p>
|
|
168
|
+
<!-- htmlmustache-ignore-end -->
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
```html
|
|
172
|
+
{{! htmlmustache-ignore-start }}
|
|
173
|
+
{{#items}}<li>{{name}}</li>{{/items}}
|
|
174
|
+
{{! htmlmustache-ignore-end }}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
If `ignore-start` has no matching `ignore-end`, all remaining siblings in the current scope are preserved as raw text.
|
|
178
|
+
|
|
138
179
|
## Configuration
|
|
139
180
|
|
|
140
181
|
### `.htmlmustache.jsonc`
|
package/cli/out/main.js
CHANGED
|
@@ -2387,15 +2387,16 @@ function normalizeMustacheWhitespace(raw, addSpaces) {
|
|
|
2387
2387
|
const first = lines[0].trimStart();
|
|
2388
2388
|
const last = lines[lines.length - 1].trimEnd();
|
|
2389
2389
|
if (lines.length === 1) {
|
|
2390
|
-
return `{{${prefix}${
|
|
2390
|
+
return `{{${prefix} ${first} }}`;
|
|
2391
2391
|
}
|
|
2392
2392
|
const middle = lines.slice(1, -1);
|
|
2393
|
-
return `{{${prefix}${
|
|
2393
|
+
return `{{${prefix} ${first}
|
|
2394
2394
|
${middle.join("\n")}
|
|
2395
|
-
${last}
|
|
2395
|
+
${last} }}`;
|
|
2396
2396
|
}
|
|
2397
2397
|
const trimmed = inner.trim();
|
|
2398
|
-
|
|
2398
|
+
const s = prefix === "!" ? " " : space;
|
|
2399
|
+
return `{{${prefix}${s}${trimmed}${s}}}`;
|
|
2399
2400
|
}
|
|
2400
2401
|
const plainMatch = raw.match(/^\{\{([\s\S]*)\}\}$/);
|
|
2401
2402
|
if (plainMatch) {
|
|
@@ -2409,6 +2410,28 @@ function normalizeMustacheWhitespaceAll(raw, addSpaces) {
|
|
|
2409
2410
|
return normalizeMustacheWhitespace(match, addSpaces);
|
|
2410
2411
|
});
|
|
2411
2412
|
}
|
|
2413
|
+
function getIgnoreDirective(node) {
|
|
2414
|
+
if (node.type !== "html_comment" && node.type !== "mustache_comment") {
|
|
2415
|
+
return null;
|
|
2416
|
+
}
|
|
2417
|
+
let inner = null;
|
|
2418
|
+
if (node.type === "html_comment") {
|
|
2419
|
+
const match = node.text.match(/^<!--([\s\S]*)-->$/);
|
|
2420
|
+
if (match) {
|
|
2421
|
+
inner = match[1].trim();
|
|
2422
|
+
}
|
|
2423
|
+
} else {
|
|
2424
|
+
const match = node.text.match(/^\{\{!([\s\S]*)\}\}$/);
|
|
2425
|
+
if (match) {
|
|
2426
|
+
inner = match[1].trim();
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
if (!inner) return null;
|
|
2430
|
+
if (inner === "htmlmustache-ignore") return "ignore";
|
|
2431
|
+
if (inner === "htmlmustache-ignore-start") return "ignore-start";
|
|
2432
|
+
if (inner === "htmlmustache-ignore-end") return "ignore-end";
|
|
2433
|
+
return null;
|
|
2434
|
+
}
|
|
2412
2435
|
|
|
2413
2436
|
// lsp/server/src/formatting/classifier.ts
|
|
2414
2437
|
var customCodeTags = /* @__PURE__ */ new Set();
|
|
@@ -2622,14 +2645,16 @@ function hasImplicitEndTagsRecursive(node) {
|
|
|
2622
2645
|
if (node.type === "html_element") {
|
|
2623
2646
|
let hasStartTag = false;
|
|
2624
2647
|
let hasEndTag = false;
|
|
2648
|
+
let hasContentChildren = false;
|
|
2625
2649
|
for (let i = 0; i < node.childCount; i++) {
|
|
2626
2650
|
const child = node.child(i);
|
|
2627
2651
|
if (!child) continue;
|
|
2628
2652
|
if (child.type === "html_start_tag") hasStartTag = true;
|
|
2629
|
-
if (child.type === "html_end_tag") hasEndTag = true;
|
|
2630
|
-
if (child.type === "html_forced_end_tag") return true;
|
|
2653
|
+
else if (child.type === "html_end_tag") hasEndTag = true;
|
|
2654
|
+
else if (child.type === "html_forced_end_tag") return true;
|
|
2655
|
+
else if (!child.type.startsWith("_")) hasContentChildren = true;
|
|
2631
2656
|
}
|
|
2632
|
-
if (hasStartTag && !hasEndTag) return true;
|
|
2657
|
+
if (hasStartTag && !hasEndTag && hasContentChildren) return true;
|
|
2633
2658
|
}
|
|
2634
2659
|
for (let i = 0; i < node.childCount; i++) {
|
|
2635
2660
|
const child = node.child(i);
|
|
@@ -2900,8 +2925,16 @@ function formatHtmlElement(node, context) {
|
|
|
2900
2925
|
}
|
|
2901
2926
|
}
|
|
2902
2927
|
} else if (!isBlock && !hasHtmlElementChildren) {
|
|
2928
|
+
let prevEnd = startTag ? startTag.endIndex : -1;
|
|
2903
2929
|
for (const child of contentNodes) {
|
|
2930
|
+
if (prevEnd >= 0 && child.startIndex > prevEnd) {
|
|
2931
|
+
const gap = context.document.getText().slice(prevEnd, child.startIndex);
|
|
2932
|
+
if (/\s/.test(gap)) {
|
|
2933
|
+
parts.push(text(" "));
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2904
2936
|
parts.push(formatNode(child, context));
|
|
2937
|
+
prevEnd = child.endIndex;
|
|
2905
2938
|
}
|
|
2906
2939
|
} else {
|
|
2907
2940
|
const formattedContent = formatBlockChildren(contentNodes, context);
|
|
@@ -3002,7 +3035,29 @@ function formatScriptStyleElement(node, context) {
|
|
|
3002
3035
|
parts.push(text(child.text));
|
|
3003
3036
|
}
|
|
3004
3037
|
} else {
|
|
3005
|
-
|
|
3038
|
+
const dedented = dedentContent(child.text);
|
|
3039
|
+
if (dedented.length > 0) {
|
|
3040
|
+
const contentLines = dedented.split("\n");
|
|
3041
|
+
if (contentLines.length === 1) {
|
|
3042
|
+
parts.push(text(contentLines[0]));
|
|
3043
|
+
} else {
|
|
3044
|
+
const lineDocs = [];
|
|
3045
|
+
for (let j = 0; j < contentLines.length; j++) {
|
|
3046
|
+
if (j > 0) {
|
|
3047
|
+
if (contentLines[j] === "") {
|
|
3048
|
+
lineDocs.push("\n");
|
|
3049
|
+
} else {
|
|
3050
|
+
lineDocs.push(hardline);
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
if (contentLines[j] !== "") {
|
|
3054
|
+
lineDocs.push(text(contentLines[j]));
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
parts.push(indent(concat([hardline, ...lineDocs])));
|
|
3058
|
+
parts.push(hardline);
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3006
3061
|
}
|
|
3007
3062
|
}
|
|
3008
3063
|
}
|
|
@@ -3184,18 +3239,89 @@ function formatBlockChildren(nodes, context) {
|
|
|
3184
3239
|
let lastNodeEnd = -1;
|
|
3185
3240
|
let pendingBlankLine = false;
|
|
3186
3241
|
let blankLineBeforeCurrentLine = false;
|
|
3242
|
+
let ignoreNext = false;
|
|
3243
|
+
let inIgnoreRegion = false;
|
|
3244
|
+
let ignoreRegionStartIndex = -1;
|
|
3187
3245
|
for (let i = 0; i < nodes.length; i++) {
|
|
3188
3246
|
const node = nodes[i];
|
|
3189
|
-
|
|
3190
|
-
if (lastNodeEnd >= 0 && node.startIndex > lastNodeEnd) {
|
|
3247
|
+
if (lastNodeEnd >= 0 && node.startIndex > lastNodeEnd && !inIgnoreRegion) {
|
|
3191
3248
|
const gap = context.document.getText().slice(lastNodeEnd, node.startIndex);
|
|
3192
|
-
const prevNode = nodes[i - 1];
|
|
3193
|
-
const prevTreatAsBlock = shouldTreatAsBlock(prevNode, i - 1, nodes);
|
|
3194
3249
|
const newlineCount = (gap.match(/\n/g) || []).length;
|
|
3195
3250
|
if (newlineCount >= 2) {
|
|
3196
3251
|
pendingBlankLine = true;
|
|
3197
3252
|
}
|
|
3253
|
+
}
|
|
3254
|
+
const directive = getIgnoreDirective(node);
|
|
3255
|
+
if (directive === "ignore-end" && inIgnoreRegion) {
|
|
3256
|
+
if (currentLine.length > 0) {
|
|
3257
|
+
const lineContent = trimDoc(inlineContentToFill(currentLine));
|
|
3258
|
+
if (hasDocContent(lineContent)) {
|
|
3259
|
+
lines.push({ doc: lineContent, blankLineBefore: blankLineBeforeCurrentLine });
|
|
3260
|
+
}
|
|
3261
|
+
currentLine = [];
|
|
3262
|
+
blankLineBeforeCurrentLine = false;
|
|
3263
|
+
}
|
|
3264
|
+
const rawText = context.document.getText().slice(ignoreRegionStartIndex, node.startIndex).replace(/^\n/, "").replace(/\n$/, "");
|
|
3265
|
+
if (rawText.length > 0) {
|
|
3266
|
+
lines.push({ doc: text(rawText), blankLineBefore: false });
|
|
3267
|
+
}
|
|
3268
|
+
const commentText = node.type === "mustache_comment" ? mustacheText(node.text, context) : node.text;
|
|
3269
|
+
lines.push({ doc: text(commentText), blankLineBefore: false });
|
|
3270
|
+
inIgnoreRegion = false;
|
|
3271
|
+
ignoreRegionStartIndex = -1;
|
|
3272
|
+
lastNodeEnd = node.endIndex;
|
|
3273
|
+
continue;
|
|
3274
|
+
}
|
|
3275
|
+
if (inIgnoreRegion) {
|
|
3276
|
+
lastNodeEnd = node.endIndex;
|
|
3277
|
+
continue;
|
|
3278
|
+
}
|
|
3279
|
+
if (directive === "ignore-start") {
|
|
3280
|
+
if (currentLine.length > 0) {
|
|
3281
|
+
const lineContent = trimDoc(inlineContentToFill(currentLine));
|
|
3282
|
+
if (hasDocContent(lineContent)) {
|
|
3283
|
+
lines.push({ doc: lineContent, blankLineBefore: blankLineBeforeCurrentLine });
|
|
3284
|
+
}
|
|
3285
|
+
currentLine = [];
|
|
3286
|
+
blankLineBeforeCurrentLine = false;
|
|
3287
|
+
}
|
|
3288
|
+
const commentText = node.type === "mustache_comment" ? mustacheText(node.text, context) : node.text;
|
|
3289
|
+
lines.push({ doc: text(commentText), blankLineBefore: pendingBlankLine });
|
|
3290
|
+
pendingBlankLine = false;
|
|
3291
|
+
inIgnoreRegion = true;
|
|
3292
|
+
ignoreRegionStartIndex = node.endIndex;
|
|
3293
|
+
lastNodeEnd = node.endIndex;
|
|
3294
|
+
continue;
|
|
3295
|
+
}
|
|
3296
|
+
if (directive === "ignore") {
|
|
3297
|
+
if (currentLine.length > 0) {
|
|
3298
|
+
const lineContent = trimDoc(inlineContentToFill(currentLine));
|
|
3299
|
+
if (hasDocContent(lineContent)) {
|
|
3300
|
+
lines.push({ doc: lineContent, blankLineBefore: blankLineBeforeCurrentLine });
|
|
3301
|
+
}
|
|
3302
|
+
currentLine = [];
|
|
3303
|
+
blankLineBeforeCurrentLine = false;
|
|
3304
|
+
}
|
|
3305
|
+
const commentText = node.type === "mustache_comment" ? mustacheText(node.text, context) : node.text;
|
|
3306
|
+
lines.push({ doc: text(commentText), blankLineBefore: pendingBlankLine });
|
|
3307
|
+
pendingBlankLine = false;
|
|
3308
|
+
ignoreNext = true;
|
|
3309
|
+
lastNodeEnd = node.endIndex;
|
|
3310
|
+
continue;
|
|
3311
|
+
}
|
|
3312
|
+
if (ignoreNext) {
|
|
3313
|
+
lines.push({ doc: text(node.text), blankLineBefore: pendingBlankLine });
|
|
3314
|
+
pendingBlankLine = false;
|
|
3315
|
+
ignoreNext = false;
|
|
3316
|
+
lastNodeEnd = node.endIndex;
|
|
3317
|
+
continue;
|
|
3318
|
+
}
|
|
3319
|
+
const treatAsBlock = shouldTreatAsBlock(node, i, nodes);
|
|
3320
|
+
if (lastNodeEnd >= 0 && node.startIndex > lastNodeEnd) {
|
|
3321
|
+
const prevNode = nodes[i - 1];
|
|
3322
|
+
const prevTreatAsBlock = shouldTreatAsBlock(prevNode, i - 1, nodes);
|
|
3198
3323
|
if (!prevTreatAsBlock && !treatAsBlock) {
|
|
3324
|
+
const gap = context.document.getText().slice(lastNodeEnd, node.startIndex);
|
|
3199
3325
|
if (/\s/.test(gap)) {
|
|
3200
3326
|
currentLine.push(line);
|
|
3201
3327
|
}
|
|
@@ -3315,6 +3441,13 @@ function formatBlockChildren(nodes, context) {
|
|
|
3315
3441
|
}
|
|
3316
3442
|
lastNodeEnd = node.endIndex;
|
|
3317
3443
|
}
|
|
3444
|
+
if (inIgnoreRegion && nodes.length > 0) {
|
|
3445
|
+
const lastNode = nodes[nodes.length - 1];
|
|
3446
|
+
const rawText = context.document.getText().slice(ignoreRegionStartIndex, lastNode.endIndex).replace(/^\n/, "");
|
|
3447
|
+
if (rawText.length > 0) {
|
|
3448
|
+
lines.push({ doc: text(rawText), blankLineBefore: false });
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3318
3451
|
if (currentLine.length > 0) {
|
|
3319
3452
|
const lineContent = trimDoc(inlineContentToFill(currentLine));
|
|
3320
3453
|
if (hasDocContent(lineContent)) {
|
|
@@ -3411,6 +3544,9 @@ function formatDocument2(tree, document, options, params = {}) {
|
|
|
3411
3544
|
if (customCodeTags2) {
|
|
3412
3545
|
setCustomCodeTags(customCodeTags2);
|
|
3413
3546
|
}
|
|
3547
|
+
if (tree.rootNode.hasError) {
|
|
3548
|
+
return [];
|
|
3549
|
+
}
|
|
3414
3550
|
const configMap = buildConfigMap(customCodeTagConfigs);
|
|
3415
3551
|
const context = {
|
|
3416
3552
|
document,
|
package/package.json
CHANGED
package/src/scanner.c
CHANGED
|
@@ -516,6 +516,12 @@ static bool scan_mustache_end_tag_html_implicit_end_tag(Scanner *scanner, TSLexe
|
|
|
516
516
|
if (scanner->mustache_tags.size > 0) {
|
|
517
517
|
MustacheTag *current_mustache_tag = array_back(&scanner->mustache_tags);
|
|
518
518
|
if (scanner->tags.size > current_mustache_tag->html_tag_stack_size) {
|
|
519
|
+
Tag *top_tag = array_back(&scanner->tags);
|
|
520
|
+
// Void elements don't cross mustache boundaries — let the normal
|
|
521
|
+
// implicit end handler close them instead of forcing closure.
|
|
522
|
+
if (tag_is_void(top_tag)) {
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
519
525
|
pop_html_tag(scanner);
|
|
520
526
|
lexer->result_symbol = MUSTACHE_END_TAG_HTML_IMPLICIT_END_TAG;
|
|
521
527
|
return true;
|
|
Binary file
|