Skip to content

Commit edd3854

Browse files
authored
feat(php8-attribute): PHP 8 Attribute output (#1761)
* Added support for PHP 8 promoted properties * Added tests for Throw as expression * added option for php 8.0 * added support for match statement * added support for union types * remove useless test * added missing union tests * updated upstream php-parser version to include attribs * work in progress for attribute output * updated PHP paser for support for attriute namespaces * attribute arguments support * removed old code * fixed arguments, anon classes * change inline option for printAttrs method to object, with inline key * outputs attrs in groups * fixed matching indents
1 parent 050ce79 commit edd3854

4 files changed

Lines changed: 228 additions & 8 deletions

File tree

src/printer.js

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,10 +1199,51 @@ function printClassPart(
11991199
);
12001200
}
12011201

1202+
function printAttrs(path, options, print, { inline = false } = {}) {
1203+
const allAttrs = [];
1204+
if (!path.getValue().attrGroups) {
1205+
return [];
1206+
}
1207+
path.each((agPath) => {
1208+
const attrGroup = ["#["];
1209+
if (allAttrs.length > 0) {
1210+
allAttrs.push(inline ? " " : hardline);
1211+
}
1212+
attrGroup.push(ifBreak(softline));
1213+
agPath.each((attrPath) => {
1214+
const attrNode = attrPath.getValue();
1215+
if (attrGroup.length > 2) {
1216+
attrGroup.push(",", ifBreak(softline, " "));
1217+
}
1218+
const attrStmt = [attrNode.name];
1219+
if (attrNode.args.length > 0) {
1220+
attrStmt.push(printArgumentsList(attrPath, options, print, "args"));
1221+
}
1222+
attrGroup.push(group(concat(attrStmt)));
1223+
}, "attrs");
1224+
allAttrs.push(
1225+
group(
1226+
concat([
1227+
indent(concat(attrGroup)),
1228+
ifBreak(softline),
1229+
"]",
1230+
softline,
1231+
inline ? " " : "",
1232+
])
1233+
)
1234+
);
1235+
}, "attrGroups");
1236+
if (allAttrs.length === 0) {
1237+
return [];
1238+
}
1239+
return [concat([...allAttrs, inline ? "" : hardline])];
1240+
}
1241+
12021242
function printClass(path, options, print) {
12031243
const node = path.getValue();
1204-
1205-
const declaration = [];
1244+
const isAnonymousClass = node.kind === "class" && node.isAnonymous;
1245+
const attrs = printAttrs(path, options, print, { inline: isAnonymousClass });
1246+
const declaration = isAnonymousClass ? [] : [...attrs];
12061247

12071248
if (node.isFinal) {
12081249
declaration.push("final ");
@@ -1212,8 +1253,6 @@ function printClass(path, options, print) {
12121253
declaration.push("abstract ");
12131254
}
12141255

1215-
const isAnonymousClass = node.kind === "class" && node.isAnonymous;
1216-
12171256
// `new` print `class` keyword with arguments
12181257
declaration.push(isAnonymousClass ? "" : node.kind);
12191258

@@ -1318,7 +1357,9 @@ function printClass(path, options, print) {
13181357

13191358
function printFunction(path, options, print) {
13201359
const node = path.getValue();
1321-
const declaration = [];
1360+
const declaration = [
1361+
...printAttrs(path, options, print, { inline: node.kind === "closure" }),
1362+
];
13221363

13231364
if (node.isFinal) {
13241365
declaration.push("final ");
@@ -1745,6 +1786,7 @@ function printNode(path, options, print) {
17451786
return printFunction(path, options, print);
17461787
case "arrowfunc":
17471788
return concat([
1789+
...printAttrs(path, options, print, { inline: true }),
17481790
node.isStatic ? "static " : "",
17491791
"fn",
17501792
printArgumentsList(path, options, print),
@@ -1764,6 +1806,7 @@ function printNode(path, options, print) {
17641806
promoted = "private ";
17651807
}
17661808
const name = concat([
1809+
...printAttrs(path, options, print, { inline: true }),
17671810
promoted,
17681811
node.nullable ? "?" : "",
17691812
node.type ? concat([path.call(print, "type"), " "]) : "",
@@ -1823,6 +1866,11 @@ function printNode(path, options, print) {
18231866
])
18241867
);
18251868
case "propertystatement": {
1869+
const attrs = [];
1870+
path.map(
1871+
(propPath) => attrs.push(...printAttrs(propPath, options, print)),
1872+
"properties"
1873+
);
18261874
const printed = path.map((childPath) => {
18271875
return print(childPath);
18281876
}, "properties");
@@ -1842,6 +1890,7 @@ function printNode(path, options, print) {
18421890

18431891
return group(
18441892
concat([
1893+
...attrs,
18451894
hasVisibility
18461895
? concat([node.visibility === null ? "var" : node.visibility, ""])
18471896
: "",
@@ -2148,6 +2197,7 @@ function printNode(path, options, print) {
21482197
) {
21492198
return concat([
21502199
"new ",
2200+
...path.call(printAttrs, "what"),
21512201
path.call(print, "what"),
21522202
"(",
21532203
join(", ", path.map(print, "arguments")),
@@ -2168,6 +2218,10 @@ function printNode(path, options, print) {
21682218
" ",
21692219
])
21702220
: "",
2221+
...path.call(
2222+
(pa, opt, pr) => printAttrs(pa, opt, pr, { inline: true }),
2223+
"what"
2224+
),
21712225
"class",
21722226
node.arguments.length > 0
21732227
? concat([" ", printArgumentsList(path, options, print)])
@@ -2406,9 +2460,8 @@ function printNode(path, options, print) {
24062460
}
24072461
case "constantstatement":
24082462
case "classconstant": {
2409-
const printed = path.map((childPath) => {
2410-
return print(childPath);
2411-
}, "constants");
2463+
const attrs = printAttrs(path, options, print);
2464+
const printed = path.map((childPath) => print(childPath), "constants");
24122465

24132466
let firstVariable;
24142467

@@ -2421,6 +2474,7 @@ function printNode(path, options, print) {
24212474

24222475
return group(
24232476
concat([
2477+
...attrs,
24242478
node.visibility ? concat([node.visibility, " "]) : "",
24252479
"const",
24262480
firstVariable ? concat([" ", firstVariable]) : "",
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`attributes.php 1`] = `
4+
====================================options=====================================
5+
parsers: ["php"]
6+
printWidth: 80
7+
| printWidth
8+
=====================================input======================================
9+
<?php
10+
11+
#[A,B]
12+
#[C('arg1','arg2',D::class)]
13+
class D {
14+
15+
/** @var int Constant F */
16+
#[E]
17+
const F = 0;
18+
19+
#[G] public int $h = 0;
20+
21+
/**
22+
* @param int $m input Integer
23+
* @return callable function doubles int
24+
*/
25+
#[I,J] function k(#[L] int $m):callable {
26+
return #[N,O]#[P] fn(#[Q] int $r) => $r * 2;
27+
}
28+
29+
// Testing S
30+
#[S]
31+
//Testing S-T
32+
#[T] //Testing T
33+
34+
private function u() {
35+
return #[V] function() { return null; };
36+
}
37+
38+
}
39+
40+
#[W('a', null, 'looooong','paraaaams','list','aaaaaaaaaaaaa','vvvvvvvvvvvv','cccccccccc','eeeeeeeeeee'), X()] function Y(#[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ(12345678, 1234578)] string $_):string {return new #[NON, Anon()] class {};}
41+
42+
#[IA('interface'),\\Looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong\\Namespace\\WithStuff\\IB]
43+
interface IC {
44+
#[ID] const IE = 123;
45+
46+
#[IG, IH('abc'),IJ()] public function ik();
47+
}
48+
49+
=====================================output=====================================
50+
<?php
51+
52+
#[A, B]
53+
#[C("arg1", "arg2", D::class)]
54+
class D
55+
{
56+
/** @var int Constant F */
57+
#[E]
58+
const F = 0;
59+
60+
#[G]
61+
public int $h = 0;
62+
63+
/**
64+
* @param int $m input Integer
65+
* @return callable function doubles int
66+
*/
67+
#[I, J]
68+
function k(#[L] int $m): callable {
69+
return #[N, O] #[P] fn(#[Q] int $r) => $r * 2;
70+
} //Testing T
71+
72+
// Testing S
73+
//Testing S-T
74+
#[S]
75+
#[T]
76+
private function u()
77+
{
78+
return #[V] function () {
79+
return null;
80+
};
81+
}
82+
}
83+
84+
#[
85+
W(
86+
"a",
87+
null,
88+
"looooong",
89+
"paraaaams",
90+
"list",
91+
"aaaaaaaaaaaaa",
92+
"vvvvvvvvvvvv",
93+
"cccccccccc",
94+
"eeeeeeeeeee"
95+
),
96+
X
97+
]
98+
99+
function Y(
100+
#[
101+
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ(
102+
12345678,
103+
1234578
104+
)
105+
]
106+
string $_
107+
): string {
108+
return new #[NON, Anon] class {};
109+
}
110+
111+
#[
112+
IA("interface"),
113+
\\Looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong\\Namespace\\WithStuff\\IB
114+
]
115+
116+
interface IC
117+
{
118+
#[ID]
119+
const IE = 123;
120+
121+
#[IG, IH("abc"), IJ]
122+
public function ik();
123+
}
124+
125+
================================================================================
126+
`;

tests/attributes/attributes.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
#[A,B]
4+
#[C('arg1','arg2',D::class)]
5+
class D {
6+
7+
/** @var int Constant F */
8+
#[E]
9+
const F = 0;
10+
11+
#[G] public int $h = 0;
12+
13+
/**
14+
* @param int $m input Integer
15+
* @return callable function doubles int
16+
*/
17+
#[I,J] function k(#[L] int $m):callable {
18+
return #[N,O]#[P] fn(#[Q] int $r) => $r * 2;
19+
}
20+
21+
// Testing S
22+
#[S]
23+
//Testing S-T
24+
#[T] //Testing T
25+
26+
private function u() {
27+
return #[V] function() { return null; };
28+
}
29+
30+
}
31+
32+
#[W('a', null, 'looooong','paraaaams','list','aaaaaaaaaaaaa','vvvvvvvvvvvv','cccccccccc','eeeeeeeeeee'), X()] function Y(#[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ(12345678, 1234578)] string $_):string {return new #[NON, Anon()] class {};}
33+
34+
#[IA('interface'),\Looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong\Namespace\WithStuff\IB]
35+
interface IC {
36+
#[ID] const IE = 123;
37+
38+
#[IG, IH('abc'),IJ()] public function ik();
39+
}

tests/attributes/jsfmt.spec.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
run_spec(__dirname, ["php"]);

0 commit comments

Comments
 (0)