Skip to content

Commit 0cb9224

Browse files
committed
Merge branch 'prop-tag' into master
Close GH-131 Improve formatting for props statement, a twig component feature.
2 parents 0ed1cab + 62d557e commit 0cb9224

File tree

10 files changed

+137
-1
lines changed

10 files changed

+137
-1
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+
### Features
6+
- [GH-131](https://github.com/zackad/prettier-plugin-twig/issues/131) Improve formatting for `props` tags, feature of [twig component anonymous component](https://symfony.com/bundles/ux-twig-component/current/index.html#anonymous-components)
7+
58
---
69
## 0.15.3 (2025-02-22)
710

src/melody/melody-extension-core/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { SetParser } from "./parser/set.js";
3131
import { SpacelessParser } from "./parser/spaceless.js";
3232
import { UseParser } from "./parser/use.js";
3333
import { MountParser } from "./parser/mount.js";
34+
import { PropsParser } from "./parser/props.js";
3435

3536
import forVisitor from "./visitors/for.js";
3637
import testVisitor from "./visitors/tests.js";
@@ -97,7 +98,8 @@ export const extension = {
9798
SetParser,
9899
SpacelessParser,
99100
UseParser,
100-
MountParser
101+
MountParser,
102+
PropsParser
101103
],
102104
unaryOperators,
103105
binaryOperators,
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { DoStatement, PropItem, PropsStatement } from "../types.js";
2+
import {
3+
createNode,
4+
setEndFromToken,
5+
setStartFromToken,
6+
Types
7+
} from "../../melody-parser/index.js";
8+
import { Identifier } from "../../melody-types/index.js";
9+
10+
export const PropsParser = {
11+
name: "props",
12+
parse(parser, token) {
13+
const tokenStream = parser.tokens;
14+
const items = [];
15+
let name;
16+
let nameNode;
17+
let valueNode;
18+
19+
do {
20+
name = tokenStream.expect(Types.SYMBOL);
21+
nameNode = createNode(Identifier, name, name.text);
22+
if (tokenStream.test(Types.ASSIGNMENT)) {
23+
tokenStream.expect(Types.ASSIGNMENT);
24+
valueNode = parser.matchExpression();
25+
} else {
26+
valueNode = undefined;
27+
}
28+
const item = new PropItem(nameNode, valueNode);
29+
items.push(item);
30+
} while (tokenStream.nextIf(Types.COMMA));
31+
32+
const propsStatement = new PropsStatement(items);
33+
setStartFromToken(propsStatement, token);
34+
setEndFromToken(propsStatement, tokenStream.expect(Types.TAG_END));
35+
return propsStatement;
36+
}
37+
};

src/melody/melody-extension-core/types.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,36 @@ type(SetStatement, "SetStatement");
319319
alias(SetStatement, "Statement", "ContextMutation");
320320
visitor(SetStatement, "assignments");
321321

322+
export class PropsStatement extends Node {
323+
/**
324+
*
325+
* @param {Array<PropItem>} items
326+
*/
327+
constructor(items) {
328+
super();
329+
this.items = items;
330+
}
331+
}
332+
type(PropsStatement, "PropsStatement");
333+
alias(PropsStatement, "Statement");
334+
visitor(PropsStatement, "items");
335+
336+
export class PropItem extends Node {
337+
/**
338+
*
339+
* @param {Identifier} name
340+
* @param {Node|undefined} value
341+
*/
342+
constructor(name, value = undefined) {
343+
super();
344+
this.name = name;
345+
this.value = value;
346+
}
347+
}
348+
type(PropItem, "PropItem");
349+
alias(PropItem, "Expression");
350+
visitor(PropItem, "value");
351+
322352
export class SpacelessBlock extends Node {
323353
/**
324354
* @param {Node} [body]

src/print/PropItem.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { doc } from "prettier";
2+
import { EXPRESSION_NEEDED, STRING_NEEDS_QUOTES } from "../util/index.js";
3+
4+
const { group, indent, join, line } = doc.builders;
5+
6+
const printPropItem = (node, path, print) => {
7+
node[EXPRESSION_NEEDED] = false;
8+
node[STRING_NEEDS_QUOTES] = true;
9+
10+
const docs = [path.call(print, "name")];
11+
if (node.value !== undefined) {
12+
docs.push([" = ", path.call(print, "value")]);
13+
}
14+
15+
return group(docs);
16+
};
17+
18+
export { printPropItem };

src/print/PropsStatement.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { doc } from "prettier";
2+
3+
const { group, indent, join, line, indentIfBreak, ifBreak, softline } =
4+
doc.builders;
5+
6+
const printPropsStatement = (node, path, print) => {
7+
const itemsGroupId = Symbol("prop-items");
8+
9+
const mappedItems = path.map(print, "items");
10+
const joinedItems = join([",", line], mappedItems);
11+
12+
return group(
13+
[
14+
node.trimLeft ? "{%-" : "{%",
15+
" props ",
16+
indentIfBreak([softline, joinedItems], { groupId: itemsGroupId }),
17+
line,
18+
node.trimRight ? "-%}" : "%}"
19+
],
20+
{ id: itemsGroupId }
21+
);
22+
};
23+
24+
export { printPropsStatement };

src/printer.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import { printIfStatement } from "./print/IfStatement.js";
2929
import { printMountStatement } from "./print/MountStatement.js";
3030
import { printForStatement } from "./print/ForStatement.js";
3131
import { printSetStatement } from "./print/SetStatement.js";
32+
import { printPropsStatement } from "./print/PropsStatement.js";
33+
import { printPropItem } from "./print/PropItem.js";
3234
import { printDoStatement } from "./print/DoStatement.js";
3335
import { printExtendsStatement } from "./print/ExtendsStatement.js";
3436
import { printEmbedStatement } from "./print/EmbedStatement.js";
@@ -221,6 +223,8 @@ printFunctions["MountStatement"] = printMountStatement;
221223
printFunctions["ForStatement"] = printForStatement;
222224
printFunctions["BinaryConcatExpression"] = printBinaryExpression;
223225
printFunctions["SetStatement"] = printSetStatement;
226+
printFunctions["PropsStatement"] = printPropsStatement;
227+
printFunctions["PropItem"] = printPropItem;
224228
printFunctions["VariableDeclarationStatement"] =
225229
printVariableDeclarationStatement;
226230
printFunctions["DoStatement"] = printDoStatement;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{% props
2+
label = 'Label',
3+
error = null,
4+
id,
5+
layout = null,
6+
label_hidden = 'false'
7+
%}
8+
9+
{% props name = 'Some Name', description = 'default is empty' %}

tests/GenericTags/jsfmt.spec.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ describe("Generic tags", () => {
4141
});
4242
await expect(actual).toMatchFileSnapshot(snapshotFile);
4343
});
44+
it("should handle props tag", async () => {
45+
const { actual, snapshotFile } = await run_spec(import.meta.url, {
46+
source: "props.twig"
47+
});
48+
await expect(actual).toMatchFileSnapshot(snapshotFile);
49+
});
4450
it("should handle redirect tag", async () => {
4551
const { actual, snapshotFile } = await run_spec(import.meta.url, {
4652
source: "redirect.twig"

tests/GenericTags/props.twig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{% props label = 'Label', error = null, id, layout = null, label_hidden = 'false' %}
2+
3+
{% props name = 'Some Name', description = 'default is empty' %}

0 commit comments

Comments
 (0)