Skip to content

Commit 8bfe430

Browse files
authored
Merge pull request #6 from node-projects/copilot/check-css-features-and-tests
Fix grid-template-areas round-trip bug, fix mapVisit empty-node delimiters, add complex CSS tests
2 parents d6a2c73 + f42a157 commit 8bfe430

8 files changed

Lines changed: 595 additions & 26 deletions

File tree

src/stringify/compiler.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,12 @@ class Compiler {
134134
delim = delim || '';
135135

136136
for (let i = 0, length = nodes.length; i < length; i++) {
137-
buf += this.visit(nodes[i]);
138-
if (delim && i < length - 1) {
139-
buf += this.emit(delim);
137+
const str = this.visit(nodes[i]);
138+
if (str) {
139+
if (delim && buf) {
140+
buf += this.emit(delim);
141+
}
142+
buf += str;
140143
}
141144
}
142145

@@ -598,17 +601,19 @@ class Compiler {
598601
this.emit(';')
599602
);
600603
}
601-
if (node.property === 'grid-template-areas')
604+
if (node.property === 'grid-template-areas') {
605+
const indent = this.indent();
606+
const pad = indent.length + node.property.length + 2; // 2 for ": "
607+
const parts = node.value.split('\n');
608+
const aligned = parts
609+
.map((p, i) => (i === 0 ? p : ' '.repeat(pad) + p.trimStart()))
610+
.join('\n');
602611
return (
603-
this.emit(this.indent()) +
604-
this.emit(
605-
node.property +
606-
': ' +
607-
node.value.split('\n').join('\n'.padEnd(22) + this.indent()),
608-
node.position,
609-
) +
612+
this.emit(indent) +
613+
this.emit(`${node.property}: ${aligned}`, node.position) +
610614
this.emit(';')
611615
);
616+
}
612617
return (
613618
this.emit(this.indent()) +
614619
this.emit(`${node.property}: ${node.value}`, node.position) +

test/cases/escapes/output.css

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,16 @@
44

55
/* will match elements with class=":`(" */
66

7-
8-
97
/* will match elements with class="1a2b3c" */
108

11-
12-
139
/* will match the element with id="#fake-id" */
1410

15-
16-
1711
/* will match the element with id="---" */
1812

19-
20-
2113
/* will match the element with id="-a-b-c-" */
2214

23-
24-
2515
/* will match the element with id="©" */
2616

27-
28-
2917
/* More tests from http://mathiasbynens.be/demo/html5-id */
3018

3119
html {
@@ -175,4 +163,4 @@ li {
175163

176164
/* css-parse does not yet pass this test */
177165

178-
/*#\{\}{background:lime;}*/
166+
/*#\{\}{background:lime;}*/

test/cases/modern-css/ast.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"type":"stylesheet","stylesheet":{"source":"input.css","rules":[{"type":"layer","layer":"base, components, utilities","position":{"start":{"line":1,"column":1},"end":{"line":3,"column":1},"source":"input.css"}},{"type":"property","name":"--accent","declarations":[{"type":"declaration","property":"syntax","value":"\"<color>\"","position":{"start":{"line":4,"column":3},"end":{"line":4,"column":20},"source":"input.css"}},{"type":"declaration","property":"inherits","value":"true","position":{"start":{"line":5,"column":3},"end":{"line":5,"column":17},"source":"input.css"}},{"type":"declaration","property":"initial-value","value":"oklch(0.7 0.15 180)","position":{"start":{"line":6,"column":3},"end":{"line":6,"column":37},"source":"input.css"}}],"position":{"start":{"line":3,"column":1},"end":{"line":7,"column":2},"source":"input.css"}},{"type":"font-face","declarations":[{"type":"declaration","property":"font-family","value":"\"App Font\"","position":{"start":{"line":10,"column":3},"end":{"line":10,"column":26},"source":"input.css"}},{"type":"declaration","property":"src","value":"url(\"font.woff2\") format(\"woff2\")","position":{"start":{"line":11,"column":3},"end":{"line":11,"column":41},"source":"input.css"}},{"type":"declaration","property":"font-display","value":"swap","position":{"start":{"line":12,"column":3},"end":{"line":12,"column":21},"source":"input.css"}},{"type":"declaration","property":"font-weight","value":"100 900","position":{"start":{"line":13,"column":3},"end":{"line":13,"column":23},"source":"input.css"}}],"position":{"start":{"line":9,"column":1},"end":{"line":14,"column":2},"source":"input.css"}},{"type":"layer","layer":"base","rules":[{"type":"rule","selectors":[":root"],"declarations":[{"type":"declaration","property":"--accent","value":"oklch(0.7 0.15 180)","position":{"start":{"line":18,"column":5},"end":{"line":18,"column":34},"source":"input.css"}},{"type":"declaration","property":"--spacing","value":"clamp(0.5rem, 2vw, 2rem)","position":{"start":{"line":19,"column":5},"end":{"line":19,"column":40},"source":"input.css"}}],"position":{"start":{"line":17,"column":3},"end":{"line":20,"column":4},"source":"input.css"}},{"type":"rule","selectors":["*","*::before","*::after"],"declarations":[{"type":"declaration","property":"box-sizing","value":"border-box","position":{"start":{"line":25,"column":5},"end":{"line":25,"column":27},"source":"input.css"}},{"type":"declaration","property":"margin","value":"0","position":{"start":{"line":26,"column":5},"end":{"line":26,"column":14},"source":"input.css"}}],"position":{"start":{"line":22,"column":3},"end":{"line":27,"column":4},"source":"input.css"}}],"position":{"start":{"line":16,"column":1},"end":{"line":28,"column":2},"source":"input.css"}},{"type":"layer","layer":"components","rules":[{"type":"rule","selectors":[".card"],"declarations":[{"type":"declaration","property":"container-type","value":"inline-size","position":{"start":{"line":32,"column":5},"end":{"line":32,"column":32},"source":"input.css"}},{"type":"declaration","property":"padding","value":"var(--spacing)","position":{"start":{"line":33,"column":5},"end":{"line":33,"column":28},"source":"input.css"}},{"type":"declaration","property":"background","value":"color-mix(in oklch, var(--accent) 10%, white)","position":{"start":{"line":34,"column":5},"end":{"line":34,"column":62},"source":"input.css"}},{"type":"rule","selectors":["& .title"],"declarations":[{"type":"declaration","property":"font-size","value":"1.5em","position":{"start":{"line":37,"column":7},"end":{"line":37,"column":23},"source":"input.css"}},{"type":"declaration","property":"color","value":"var(--accent)","position":{"start":{"line":38,"column":7},"end":{"line":38,"column":27},"source":"input.css"}},{"type":"rule","selectors":["&:hover"],"declarations":[{"type":"declaration","property":"color","value":"color-mix(in oklch, var(--accent), black 20%)","position":{"start":{"line":41,"column":9},"end":{"line":41,"column":61},"source":"input.css"}}],"position":{"start":{"line":40,"column":7},"end":{"line":42,"column":8},"source":"input.css"}}],"position":{"start":{"line":36,"column":5},"end":{"line":43,"column":6},"source":"input.css"}},{"type":"container","container":"(min-width: 400px)","rules":[{"type":"declaration","property":"display","value":"grid","position":{"start":{"line":46,"column":7},"end":{"line":46,"column":20},"source":"input.css"}},{"type":"declaration","property":"grid-template-columns","value":"1fr 2fr","position":{"start":{"line":47,"column":7},"end":{"line":47,"column":37},"source":"input.css"}},{"type":"declaration","property":"gap","value":"var(--spacing)","position":{"start":{"line":48,"column":7},"end":{"line":48,"column":26},"source":"input.css"}}],"position":{"start":{"line":45,"column":5},"end":{"line":49,"column":6},"source":"input.css"}},{"type":"media","media":"(prefers-color-scheme: dark)","rules":[{"type":"declaration","property":"background","value":"color-mix(in oklch, var(--accent) 10%, black)","position":{"start":{"line":52,"column":7},"end":{"line":52,"column":64},"source":"input.css"}}],"position":{"start":{"line":51,"column":5},"end":{"line":53,"column":6},"source":"input.css"}}],"position":{"start":{"line":31,"column":3},"end":{"line":54,"column":4},"source":"input.css"}}],"position":{"start":{"line":30,"column":1},"end":{"line":55,"column":2},"source":"input.css"}},{"type":"layer","layer":"utilities","rules":[{"type":"rule","selectors":[".sr-only:not(:focus):not(:active)"],"declarations":[{"type":"declaration","property":"clip","value":"rect(0 0 0 0)","position":{"start":{"line":59,"column":5},"end":{"line":59,"column":24},"source":"input.css"}},{"type":"declaration","property":"clip-path","value":"inset(50%)","position":{"start":{"line":60,"column":5},"end":{"line":60,"column":26},"source":"input.css"}},{"type":"declaration","property":"height","value":"1px","position":{"start":{"line":61,"column":5},"end":{"line":61,"column":16},"source":"input.css"}},{"type":"declaration","property":"overflow","value":"hidden","position":{"start":{"line":62,"column":5},"end":{"line":62,"column":21},"source":"input.css"}},{"type":"declaration","property":"position","value":"absolute","position":{"start":{"line":63,"column":5},"end":{"line":63,"column":23},"source":"input.css"}},{"type":"declaration","property":"white-space","value":"nowrap","position":{"start":{"line":64,"column":5},"end":{"line":64,"column":24},"source":"input.css"}},{"type":"declaration","property":"width","value":"1px","position":{"start":{"line":65,"column":5},"end":{"line":65,"column":15},"source":"input.css"}}],"position":{"start":{"line":58,"column":3},"end":{"line":66,"column":4},"source":"input.css"}}],"position":{"start":{"line":57,"column":1},"end":{"line":67,"column":2},"source":"input.css"}},{"type":"supports","supports":"selector(:has(> .child))","rules":[{"type":"rule","selectors":[".parent:has(> .child)"],"declarations":[{"type":"declaration","property":"color","value":"red","position":{"start":{"line":71,"column":5},"end":{"line":71,"column":15},"source":"input.css"}}],"position":{"start":{"line":70,"column":3},"end":{"line":72,"column":4},"source":"input.css"}}],"position":{"start":{"line":69,"column":1},"end":{"line":73,"column":2},"source":"input.css"}},{"type":"scope","scope":"(.theme-dark) to (.theme-light)","rules":[{"type":"rule","selectors":[":scope"],"declarations":[{"type":"declaration","property":"background","value":"#222","position":{"start":{"line":77,"column":5},"end":{"line":77,"column":21},"source":"input.css"}}],"position":{"start":{"line":76,"column":3},"end":{"line":78,"column":4},"source":"input.css"}},{"type":"rule","selectors":["a"],"declarations":[{"type":"declaration","property":"color","value":"lightblue","position":{"start":{"line":80,"column":5},"end":{"line":80,"column":21},"source":"input.css"}}],"position":{"start":{"line":79,"column":3},"end":{"line":81,"column":4},"source":"input.css"}}],"position":{"start":{"line":75,"column":1},"end":{"line":82,"column":2},"source":"input.css"}},{"type":"media","media":"(width > 600px)","rules":[{"type":"rule","selectors":[".wide"],"declarations":[{"type":"declaration","property":"display","value":"flex","position":{"start":{"line":86,"column":5},"end":{"line":86,"column":18},"source":"input.css"}}],"position":{"start":{"line":85,"column":3},"end":{"line":87,"column":4},"source":"input.css"}}],"position":{"start":{"line":84,"column":1},"end":{"line":88,"column":2},"source":"input.css"}},{"type":"media","media":"print","rules":[{"type":"page","selectors":[],"declarations":[{"type":"declaration","property":"margin","value":"2cm","position":{"start":{"line":92,"column":5},"end":{"line":92,"column":16},"source":"input.css"}},{"type":"page-margin-box","name":"top-center","declarations":[{"type":"declaration","property":"content","value":"\"Document Title\"","position":{"start":{"line":94,"column":7},"end":{"line":94,"column":32},"source":"input.css"}}],"position":{"start":{"line":93,"column":5},"end":{"line":95,"column":6},"source":"input.css"}}],"position":{"start":{"line":91,"column":3},"end":{"line":96,"column":4},"source":"input.css"}},{"type":"rule","selectors":[".no-print"],"declarations":[{"type":"declaration","property":"display","value":"none !important","position":{"start":{"line":98,"column":5},"end":{"line":98,"column":29},"source":"input.css"}}],"position":{"start":{"line":97,"column":3},"end":{"line":99,"column":4},"source":"input.css"}}],"position":{"start":{"line":90,"column":1},"end":{"line":100,"column":2},"source":"input.css"}}],"parsingErrors":[]}}

test/cases/modern-css/compressed.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/cases/modern-css/input.css

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
@layer base, components, utilities;
2+
3+
@property --accent {
4+
syntax: "<color>";
5+
inherits: true;
6+
initial-value: oklch(0.7 0.15 180);
7+
}
8+
9+
@font-face {
10+
font-family: "App Font";
11+
src: url("font.woff2") format("woff2");
12+
font-display: swap;
13+
font-weight: 100 900;
14+
}
15+
16+
@layer base {
17+
:root {
18+
--accent: oklch(0.7 0.15 180);
19+
--spacing: clamp(0.5rem, 2vw, 2rem);
20+
}
21+
22+
*,
23+
*::before,
24+
*::after {
25+
box-sizing: border-box;
26+
margin: 0;
27+
}
28+
}
29+
30+
@layer components {
31+
.card {
32+
container-type: inline-size;
33+
padding: var(--spacing);
34+
background: color-mix(in oklch, var(--accent) 10%, white);
35+
36+
& .title {
37+
font-size: 1.5em;
38+
color: var(--accent);
39+
40+
&:hover {
41+
color: color-mix(in oklch, var(--accent), black 20%);
42+
}
43+
}
44+
45+
@container (min-width: 400px) {
46+
display: grid;
47+
grid-template-columns: 1fr 2fr;
48+
gap: var(--spacing);
49+
}
50+
51+
@media (prefers-color-scheme: dark) {
52+
background: color-mix(in oklch, var(--accent) 10%, black);
53+
}
54+
}
55+
}
56+
57+
@layer utilities {
58+
.sr-only:not(:focus):not(:active) {
59+
clip: rect(0 0 0 0);
60+
clip-path: inset(50%);
61+
height: 1px;
62+
overflow: hidden;
63+
position: absolute;
64+
white-space: nowrap;
65+
width: 1px;
66+
}
67+
}
68+
69+
@supports selector(:has(> .child)) {
70+
.parent:has(> .child) {
71+
color: red;
72+
}
73+
}
74+
75+
@scope (.theme-dark) to (.theme-light) {
76+
:scope {
77+
background: #222;
78+
}
79+
a {
80+
color: lightblue;
81+
}
82+
}
83+
84+
@media (width > 600px) {
85+
.wide {
86+
display: flex;
87+
}
88+
}
89+
90+
@media print {
91+
@page {
92+
margin: 2cm;
93+
@top-center {
94+
content: "Document Title";
95+
}
96+
}
97+
.no-print {
98+
display: none !important;
99+
}
100+
}

test/cases/modern-css/output.css

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
@layer base, components, utilities;
2+
3+
@property --accent {
4+
syntax: "<color>";
5+
inherits: true;
6+
initial-value: oklch(0.7 0.15 180);
7+
}
8+
9+
@font-face {
10+
font-family: "App Font";
11+
src: url("font.woff2") format("woff2");
12+
font-display: swap;
13+
font-weight: 100 900;
14+
}
15+
16+
@layer base {
17+
:root {
18+
--accent: oklch(0.7 0.15 180);
19+
--spacing: clamp(0.5rem, 2vw, 2rem);
20+
}
21+
22+
*,
23+
*::before,
24+
*::after {
25+
box-sizing: border-box;
26+
margin: 0;
27+
}
28+
}
29+
30+
@layer components {
31+
.card {
32+
container-type: inline-size;
33+
padding: var(--spacing);
34+
background: color-mix(in oklch, var(--accent) 10%, white);
35+
& .title {
36+
font-size: 1.5em;
37+
color: var(--accent);
38+
&:hover {
39+
color: color-mix(in oklch, var(--accent), black 20%);
40+
}
41+
}
42+
@container (min-width: 400px) {
43+
display: grid;
44+
45+
grid-template-columns: 1fr 2fr;
46+
47+
gap: var(--spacing);
48+
}
49+
@media (prefers-color-scheme: dark) {
50+
background: color-mix(in oklch, var(--accent) 10%, black);
51+
}
52+
}
53+
}
54+
55+
@layer utilities {
56+
.sr-only:not(:focus):not(:active) {
57+
clip: rect(0 0 0 0);
58+
clip-path: inset(50%);
59+
height: 1px;
60+
overflow: hidden;
61+
position: absolute;
62+
white-space: nowrap;
63+
width: 1px;
64+
}
65+
}
66+
67+
@supports selector(:has(> .child)) {
68+
.parent:has(> .child) {
69+
color: red;
70+
}
71+
}
72+
73+
@scope (.theme-dark) to (.theme-light) {
74+
:scope {
75+
background: #222;
76+
}
77+
78+
a {
79+
color: lightblue;
80+
}
81+
}
82+
83+
@media (width > 600px) {
84+
.wide {
85+
display: flex;
86+
}
87+
}
88+
89+
@media print {
90+
@page {
91+
margin: 2cm;
92+
@top-center {
93+
content: "Document Title";
94+
}
95+
}
96+
97+
.no-print {
98+
display: none !important;
99+
}
100+
}

test/cases/special-strings/output.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828

2929
.grid-areas {
3030
grid-template-areas: "header header header"
31-
"sidebar main aside"
32-
"footer footer footer";
31+
"sidebar main aside"
32+
"footer footer footer";
3333
grid-template-columns: 200px 1fr 150px;
3434
}
3535

0 commit comments

Comments
 (0)