@mrhenry/prettier-twig 0.1.4 → 0.1.5
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 +4 -0
- package/package.json +1 -1
- package/src/printer.js +25 -3
- package/test/prettier-twig.test.js +78 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.5 (2026-09-25)
|
|
4
|
+
|
|
5
|
+
* Preserve the `<html>` tag's line layout: keep it on a single line when the source did (even with several attributes), and format it multi-line when the source split it
|
|
6
|
+
|
|
3
7
|
## 0.1.3 (2026-09-24)
|
|
4
8
|
|
|
5
9
|
* Normalize twig structures inside attribute values (`{ w:1256 , q: 45 }` becomes `{ w: 1256, q: 45 }`)
|
package/package.json
CHANGED
package/src/printer.js
CHANGED
|
@@ -609,6 +609,28 @@ function isSingleLineStartTag(node) {
|
|
|
609
609
|
return realAttrs.length <= 1 && !hasConditional && !hasComplexValue;
|
|
610
610
|
}
|
|
611
611
|
|
|
612
|
+
/**
|
|
613
|
+
* Whether an `<html>` start tag must stay on a single line because the source
|
|
614
|
+
* wrote it that way. Unlike other elements, `html` may carry several attributes
|
|
615
|
+
* on one line; a tag the author split across lines is formatted multi-line
|
|
616
|
+
* instead. Conditional attributes are excluded because their blocks always
|
|
617
|
+
* expand.
|
|
618
|
+
*
|
|
619
|
+
* @param {any} node
|
|
620
|
+
* @param {string} source
|
|
621
|
+
* @returns {boolean}
|
|
622
|
+
*/
|
|
623
|
+
function keepsHtmlTagOnOneLine(node, source) {
|
|
624
|
+
if (node.name !== 'html') {
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
627
|
+
const attrs = node.attrs ?? [];
|
|
628
|
+
const hasConditional = attrs.some(
|
|
629
|
+
(/** @type {any} */ a) => a.type === 'twig' || a.type === 'twigBlock',
|
|
630
|
+
);
|
|
631
|
+
return !hasConditional && !/[\r\n]/.test(source.slice(node.rawStart, node.startTagEnd));
|
|
632
|
+
}
|
|
633
|
+
|
|
612
634
|
/**
|
|
613
635
|
* Prints an element's start tag.
|
|
614
636
|
*
|
|
@@ -619,9 +641,9 @@ function isSingleLineStartTag(node) {
|
|
|
619
641
|
*/
|
|
620
642
|
function printOpenTag(node, attrDocs, source) {
|
|
621
643
|
const forcedSingleLine = FORCE_SINGLE_LINE.has(node.name);
|
|
622
|
-
const singleLineTag = forcedSingleLine || isSingleLineStartTag(node);
|
|
644
|
+
const singleLineTag = forcedSingleLine || keepsHtmlTagOnOneLine(node, source) || isSingleLineStartTag(node);
|
|
623
645
|
return forcedSingleLine
|
|
624
|
-
? // `
|
|
646
|
+
? // `link` and `meta` are never broken across lines
|
|
625
647
|
collapseWhitespace(source.slice(node.rawStart, node.startTagEnd)).replace(
|
|
626
648
|
/\s*\/?>$/,
|
|
627
649
|
node.selfClosing ? ' />' : '>',
|
|
@@ -783,7 +805,7 @@ function printInlineText(inner) {
|
|
|
783
805
|
}
|
|
784
806
|
|
|
785
807
|
/** Elements whose start tag must always stay on a single line. */
|
|
786
|
-
const FORCE_SINGLE_LINE = new Set(['
|
|
808
|
+
const FORCE_SINGLE_LINE = new Set(['link', 'meta']);
|
|
787
809
|
|
|
788
810
|
/** Void elements that never have an end tag. */
|
|
789
811
|
const VOID_ELEMENTS = new Set([
|
|
@@ -126,6 +126,84 @@ test('keeps html, link and meta tags on a single line', async () => {
|
|
|
126
126
|
}
|
|
127
127
|
});
|
|
128
128
|
|
|
129
|
+
test('preserves the html tag line layout', async () => {
|
|
130
|
+
{
|
|
131
|
+
// a simple twig value in a single attribute stays on one line
|
|
132
|
+
const out = await fmt(`<html lang="{{ currentSite.language }}">
|
|
133
|
+
<head></head>
|
|
134
|
+
</html>`);
|
|
135
|
+
assert.equal(
|
|
136
|
+
out,
|
|
137
|
+
`<html lang="{{ currentSite.language }}">
|
|
138
|
+
<head></head>
|
|
139
|
+
</html>
|
|
140
|
+
`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
{
|
|
145
|
+
// several attributes written on one line stay on one line
|
|
146
|
+
const out = await fmt(`<html lang="en" class="no-js" data-x="1">
|
|
147
|
+
<head></head>
|
|
148
|
+
</html>`);
|
|
149
|
+
assert.equal(
|
|
150
|
+
out,
|
|
151
|
+
`<html lang="en" class="no-js" data-x="1">
|
|
152
|
+
<head></head>
|
|
153
|
+
</html>
|
|
154
|
+
`,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
{
|
|
159
|
+
// a tag the author split across lines is formatted multi-line
|
|
160
|
+
const out = await fmt(`<html
|
|
161
|
+
lang="en"
|
|
162
|
+
class="no-js"
|
|
163
|
+
data-x="1"
|
|
164
|
+
>
|
|
165
|
+
<head></head>
|
|
166
|
+
</html>`);
|
|
167
|
+
assert.equal(
|
|
168
|
+
out,
|
|
169
|
+
`<html
|
|
170
|
+
lang="en"
|
|
171
|
+
class="no-js"
|
|
172
|
+
data-x="1"
|
|
173
|
+
>
|
|
174
|
+
<head></head>
|
|
175
|
+
</html>
|
|
176
|
+
`,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
{
|
|
181
|
+
// conditional attributes force the multi-line layout
|
|
182
|
+
const out = await fmt(`<html
|
|
183
|
+
lang="en"
|
|
184
|
+
class="no-js"
|
|
185
|
+
{% if current %}
|
|
186
|
+
data-current
|
|
187
|
+
{% endif %}
|
|
188
|
+
>
|
|
189
|
+
<head></head>
|
|
190
|
+
</html>`);
|
|
191
|
+
assert.equal(
|
|
192
|
+
out,
|
|
193
|
+
`<html
|
|
194
|
+
lang="en"
|
|
195
|
+
class="no-js"
|
|
196
|
+
{% if current %}
|
|
197
|
+
data-current
|
|
198
|
+
{% endif %}
|
|
199
|
+
>
|
|
200
|
+
<head></head>
|
|
201
|
+
</html>
|
|
202
|
+
`,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
129
207
|
test('does not close open-ended partials', async () => {
|
|
130
208
|
{
|
|
131
209
|
// a partial that opens the document, completed by another template
|