Skip to content

Commit 62236e5

Browse files
feat: add support for single-quoted attribute values
1 parent ccdfb54 commit 62236e5

File tree

8 files changed

+1037
-8
lines changed

8 files changed

+1037
-8
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## unreleased
44

5+
### Bugfixes
6+
- [GH-122](https://github.com/zackad/prettier-plugin-twig/issues/122) Add support for single-quoted attribute values
7+
58
---
69
## 0.16.2 (2025-12-12)
710

src/melody/melody-parser/Lexer.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const State = {
3131
const STATE = Symbol("STATE");
3232
const OPERATORS = Symbol("OPERATORS");
3333
const STRING_START = Symbol("STRING_START");
34+
const ATTR_QUOTE = Symbol("ATTR_QUOTE");
3435

3536
const CHAR_TO_TOKEN = {
3637
"[": TokenTypes.LBRACE,
@@ -57,6 +58,7 @@ export default class Lexer {
5758
this[STATE] = [State.TEXT];
5859
this[OPERATORS] = [];
5960
this[STRING_START] = null;
61+
this[ATTR_QUOTE] = null;
6062
this.options = {
6163
preserveSourceLiterally: preserveSourceLiterally === true
6264
};
@@ -74,6 +76,7 @@ export default class Lexer {
7476
reset() {
7577
this.input.reset();
7678
this[STATE] = [State.TEXT];
79+
this[ATTR_QUOTE] = null;
7780
}
7881

7982
get source() {
@@ -265,6 +268,8 @@ export default class Lexer {
265268
this.popState();
266269
return this.createToken(TokenTypes.ELEMENT_END, pos);
267270
case '"':
271+
case "'":
272+
this[ATTR_QUOTE] = c;
268273
input.next();
269274
this.pushState(State.ATTRIBUTE_VALUE);
270275
return this.createToken(TokenTypes.STRING_START, pos);
@@ -275,12 +280,14 @@ export default class Lexer {
275280
return this.matchSymbol(pos);
276281
}
277282
} else if (this.state === State.ATTRIBUTE_VALUE) {
278-
if (c === '"') {
283+
const quoteChar = this[ATTR_QUOTE];
284+
if (c === quoteChar) {
279285
input.next();
280286
this.popState();
287+
this[ATTR_QUOTE] = null;
281288
return this.createToken(TokenTypes.STRING_END, pos);
282289
}
283-
return this.matchAttributeValue(pos);
290+
return this.matchAttributeValue(pos, quoteChar);
284291
} else if (this.state === State.DECLARATION) {
285292
switch (c) {
286293
case ">":
@@ -569,9 +576,9 @@ export default class Lexer {
569576
return result;
570577
}
571578

572-
matchAttributeValue(pos) {
579+
matchAttributeValue(pos, quoteChar = '"') {
573580
const input = this.input;
574-
const start = this.state === State.STRING_SINGLE ? "'" : '"';
581+
const start = quoteChar;
575582
let c = input.la(0);
576583
const c2 = input.la(1);
577584
if (c === "{" && (c2 === "{" || c2 === "#" || c2 === "%")) {

src/melody/melody-parser/Parser.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ export default class Parser {
432432
if (nodes.length > 1) {
433433
expr.wasImplicitConcatenation = true;
434434
}
435-
const attr = new n.Attribute(keyNode, expr);
435+
const attr = new n.Attribute(keyNode, expr, start.text);
436436
copyStart(attr, keyNode);
437437
copyEnd(attr, expr);
438438
element.attributes.push(attr);

src/melody/melody-types/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,11 +448,13 @@ export class Attribute extends Node {
448448
/**
449449
* @param {Node} name
450450
* @param {Node} value
451+
* @param {string} quoteChar
451452
*/
452-
constructor(name, value = null) {
453+
constructor(name, value = null, quoteChar = '"') {
453454
super();
454455
this.name = name;
455456
this.value = value;
457+
this.quoteChar = quoteChar;
456458
}
457459

458460
isImmutable() {

src/print/Attribute.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ const printAttribute = (node, path, print = print) => {
2525
node[EXPRESSION_NEEDED] = true;
2626
node[STRING_NEEDS_QUOTES] = false;
2727
if (node.value) {
28-
docs.push('="');
28+
const quote = node.quoteChar || '"';
29+
docs.push("=", quote);
2930
if (
3031
Node.isBinaryConcatExpression(node.value) &&
3132
node.value.wasImplicitConcatenation
@@ -41,7 +42,7 @@ const printAttribute = (node, path, print = print) => {
4142
}
4243
docs.push(path.call(print, "value"));
4344
}
44-
docs.push('"');
45+
docs.push(quote);
4546
}
4647

4748
return docs;

0 commit comments

Comments
 (0)