Skip to content

Commit e026c98

Browse files
AtkinsSJgmta
authored andcommitted
LibWeb/CSS: Insert required comments when serializing lists of tokens
Certain pairs of tokens are required to have `/**/` inserted between them to prevent eg two `<ident>`s getting merged together when round-tripping.
1 parent 9ac0966 commit e026c98

File tree

2 files changed

+164
-62
lines changed

2 files changed

+164
-62
lines changed

Libraries/LibWeb/CSS/Serialize.cpp

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
* SPDX-License-Identifier: BSD-2-Clause
55
*/
66

7+
#include <AK/GenericShorthands.h>
78
#include <AK/StringBuilder.h>
89
#include <AK/Utf8View.h>
910
#include <LibWeb/CSS/Parser/ComponentValue.h>
11+
#include <LibWeb/CSS/Parser/TokenStream.h>
1012
#include <LibWeb/CSS/Serialize.h>
1113
#include <LibWeb/Infra/Strings.h>
1214

@@ -198,11 +200,111 @@ String serialize_a_css_declaration(StringView property, StringView value, Import
198200
return builder.to_string_without_validation();
199201
}
200202

203+
// https://drafts.csswg.org/css-syntax/#serialization
204+
static bool needs_comment_between(Parser::ComponentValue const& first, Parser::ComponentValue const& second)
205+
{
206+
// For any consecutive pair of tokens, if the first token shows up in the row headings of the following table, and
207+
// the second token shows up in the column headings, and there’s a ✗ in the cell denoted by the intersection of the
208+
// chosen row and column, the pair of tokens must be serialized with a comment between them.
209+
//
210+
// If the tokenizer preserves comments, and there were comments originally between the token pair, the preserved
211+
// comment(s) should be used; otherwise, an empty comment (/**/) must be inserted. (Preserved comments may be
212+
// reinserted even if the following tables don’t require a comment between two tokens.)
213+
//
214+
// Single characters in the row and column headings represent a <delim-token> with that value, except for "(",
215+
// which represents a (-token.
216+
//
217+
// │ ident │ function │ url │ bad url │ - │ number │ percentage │ dimension │ CDC │ ( │ * │ %
218+
// ───────────┼───────┼──────────┼─────┼─────────┼───┼────────┼────────────┼───────────┼─────┼───┼───┼───
219+
// ident │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ │
220+
// at-keyword │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ │ │
221+
// hash │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ │ │
222+
// dimension │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ │ │
223+
// # │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ │ │
224+
// - │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ │ │
225+
// number │ ✗ │ ✗ │ ✗ │ ✗ │ │ ✗ │ ✗ │ ✗ │ ✗ │ │ │ ✗
226+
// @ │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ │ │ │ ✗ │ │ │
227+
// . │ │ │ │ │ │ ✗ │ ✗ │ ✗ │ │ │ │
228+
// + │ │ │ │ │ │ ✗ │ ✗ │ ✗ │ │ │ │
229+
// / │ │ │ │ │ │ │ │ │ │ │ ✗ │
230+
231+
if (first.is(Parser::Token::Type::Ident)) {
232+
if (second.is_function())
233+
return true;
234+
// NB: ( may also be part of a block.
235+
if (second.is_block() && second.block().is_paren())
236+
return true;
237+
if (!second.is_token())
238+
return false;
239+
if (second.token().type() == Parser::Token::Type::Delim)
240+
return second.is_delim('-') || second.is_delim('(');
241+
return first_is_one_of(second.token().type(),
242+
Parser::Token::Type::Ident, Parser::Token::Type::Url, Parser::Token::Type::BadUrl, Parser::Token::Type::Number, Parser::Token::Type::Percentage, Parser::Token::Type::Dimension, Parser::Token::Type::CDC);
243+
}
244+
245+
if (first.is(Parser::Token::Type::AtKeyword)
246+
|| first.is(Parser::Token::Type::Hash)
247+
|| first.is(Parser::Token::Type::Dimension)
248+
|| first.is_delim('#')
249+
|| first.is_delim('-')) {
250+
if (second.is_function())
251+
return true;
252+
if (!second.is_token())
253+
return false;
254+
if (second.token().type() == Parser::Token::Type::Delim)
255+
return second.token().delim() == '-';
256+
return first_is_one_of(second.token().type(),
257+
Parser::Token::Type::Ident, Parser::Token::Type::Url, Parser::Token::Type::BadUrl, Parser::Token::Type::Number, Parser::Token::Type::Percentage, Parser::Token::Type::Dimension, Parser::Token::Type::CDC);
258+
}
259+
260+
if (first.is(Parser::Token::Type::Number)) {
261+
if (second.is_function())
262+
return true;
263+
if (!second.is_token())
264+
return false;
265+
if (second.token().type() == Parser::Token::Type::Delim)
266+
return second.token().delim() == '%';
267+
return first_is_one_of(second.token().type(),
268+
Parser::Token::Type::Ident, Parser::Token::Type::Url, Parser::Token::Type::BadUrl, Parser::Token::Type::Number, Parser::Token::Type::Percentage, Parser::Token::Type::Dimension, Parser::Token::Type::CDC);
269+
}
270+
271+
if (first.is_delim('@')) {
272+
if (second.is_function())
273+
return true;
274+
if (!second.is_token())
275+
return false;
276+
if (second.token().type() == Parser::Token::Type::Delim)
277+
return second.token().delim() == '-';
278+
return first_is_one_of(second.token().type(),
279+
Parser::Token::Type::Ident, Parser::Token::Type::Url, Parser::Token::Type::BadUrl, Parser::Token::Type::CDC);
280+
}
281+
282+
if (first.is_delim('.') || first.is_delim('+')) {
283+
return second.is(Parser::Token::Type::Number) || second.is(Parser::Token::Type::Percentage) || second.is(Parser::Token::Type::Dimension);
284+
}
285+
286+
if (first.is_delim('/')) {
287+
return second.is_delim('*');
288+
}
289+
290+
return false;
291+
}
292+
201293
// https://drafts.csswg.org/css-syntax/#serialization
202294
String serialize_a_series_of_component_values(ReadonlySpan<Parser::ComponentValue> component_values)
203295
{
204-
// FIXME: There are special rules here where we should insert a comment between certain tokens. Do that!
205-
return MUST(String::join(""sv, component_values));
296+
Parser::TokenStream tokens { component_values };
297+
StringBuilder builder;
298+
299+
while (tokens.has_next_token()) {
300+
auto const& current_token = tokens.consume_a_token();
301+
auto const& next_token = tokens.next_token();
302+
builder.append(current_token.to_string());
303+
if (needs_comment_between(current_token, next_token))
304+
builder.append("/**/"sv);
305+
}
306+
307+
return builder.to_string_without_validation();
206308
}
207309

208310
}

Tests/LibWeb/Text/expected/wpt-import/css/css-syntax/serialize-consecutive-tokens.txt

Lines changed: 60 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,77 +2,77 @@ Harness status: OK
22

33
Found 72 tests
44

5-
1 Pass
6-
71 Fail
7-
Fail Serialization of consecutive foo and bar tokens.
8-
Fail Serialization of consecutive foo and bar() tokens.
5+
59 Pass
6+
13 Fail
7+
Pass Serialization of consecutive foo and bar tokens.
8+
Pass Serialization of consecutive foo and bar() tokens.
99
Fail Serialization of consecutive foo and url(bar) tokens.
10-
Fail Serialization of consecutive foo and - tokens.
11-
Fail Serialization of consecutive foo and 123 tokens.
12-
Fail Serialization of consecutive foo and 123% tokens.
13-
Fail Serialization of consecutive foo and 123em tokens.
14-
Fail Serialization of consecutive foo and --> tokens.
15-
Fail Serialization of consecutive foo and () tokens.
16-
Fail Serialization of consecutive @foo and bar tokens.
17-
Fail Serialization of consecutive @foo and bar() tokens.
10+
Pass Serialization of consecutive foo and - tokens.
11+
Pass Serialization of consecutive foo and 123 tokens.
12+
Pass Serialization of consecutive foo and 123% tokens.
13+
Pass Serialization of consecutive foo and 123em tokens.
14+
Pass Serialization of consecutive foo and --> tokens.
15+
Pass Serialization of consecutive foo and () tokens.
16+
Pass Serialization of consecutive @foo and bar tokens.
17+
Pass Serialization of consecutive @foo and bar() tokens.
1818
Fail Serialization of consecutive @foo and url(bar) tokens.
19-
Fail Serialization of consecutive @foo and - tokens.
20-
Fail Serialization of consecutive @foo and 123 tokens.
21-
Fail Serialization of consecutive @foo and 123% tokens.
22-
Fail Serialization of consecutive @foo and 123em tokens.
23-
Fail Serialization of consecutive @foo and --> tokens.
24-
Fail Serialization of consecutive #foo and bar tokens.
25-
Fail Serialization of consecutive #foo and bar() tokens.
19+
Pass Serialization of consecutive @foo and - tokens.
20+
Pass Serialization of consecutive @foo and 123 tokens.
21+
Pass Serialization of consecutive @foo and 123% tokens.
22+
Pass Serialization of consecutive @foo and 123em tokens.
23+
Pass Serialization of consecutive @foo and --> tokens.
24+
Pass Serialization of consecutive #foo and bar tokens.
25+
Pass Serialization of consecutive #foo and bar() tokens.
2626
Fail Serialization of consecutive #foo and url(bar) tokens.
27-
Fail Serialization of consecutive #foo and - tokens.
28-
Fail Serialization of consecutive #foo and 123 tokens.
29-
Fail Serialization of consecutive #foo and 123% tokens.
30-
Fail Serialization of consecutive #foo and 123em tokens.
31-
Fail Serialization of consecutive #foo and --> tokens.
32-
Fail Serialization of consecutive 123foo and bar tokens.
33-
Fail Serialization of consecutive 123foo and bar() tokens.
27+
Pass Serialization of consecutive #foo and - tokens.
28+
Pass Serialization of consecutive #foo and 123 tokens.
29+
Pass Serialization of consecutive #foo and 123% tokens.
30+
Pass Serialization of consecutive #foo and 123em tokens.
31+
Pass Serialization of consecutive #foo and --> tokens.
32+
Pass Serialization of consecutive 123foo and bar tokens.
33+
Pass Serialization of consecutive 123foo and bar() tokens.
3434
Fail Serialization of consecutive 123foo and url(bar) tokens.
35-
Fail Serialization of consecutive 123foo and - tokens.
36-
Fail Serialization of consecutive 123foo and 123 tokens.
37-
Fail Serialization of consecutive 123foo and 123% tokens.
38-
Fail Serialization of consecutive 123foo and 123em tokens.
39-
Fail Serialization of consecutive 123foo and --> tokens.
40-
Fail Serialization of consecutive # and bar tokens.
41-
Fail Serialization of consecutive # and bar() tokens.
35+
Pass Serialization of consecutive 123foo and - tokens.
36+
Pass Serialization of consecutive 123foo and 123 tokens.
37+
Pass Serialization of consecutive 123foo and 123% tokens.
38+
Pass Serialization of consecutive 123foo and 123em tokens.
39+
Pass Serialization of consecutive 123foo and --> tokens.
40+
Pass Serialization of consecutive # and bar tokens.
41+
Pass Serialization of consecutive # and bar() tokens.
4242
Fail Serialization of consecutive # and url(bar) tokens.
43-
Fail Serialization of consecutive # and - tokens.
44-
Fail Serialization of consecutive # and 123 tokens.
45-
Fail Serialization of consecutive # and 123% tokens.
46-
Fail Serialization of consecutive # and 123em tokens.
47-
Fail Serialization of consecutive - and bar tokens.
48-
Fail Serialization of consecutive - and bar() tokens.
43+
Pass Serialization of consecutive # and - tokens.
44+
Pass Serialization of consecutive # and 123 tokens.
45+
Pass Serialization of consecutive # and 123% tokens.
46+
Pass Serialization of consecutive # and 123em tokens.
47+
Pass Serialization of consecutive - and bar tokens.
48+
Pass Serialization of consecutive - and bar() tokens.
4949
Fail Serialization of consecutive - and url(bar) tokens.
50-
Fail Serialization of consecutive - and - tokens.
51-
Fail Serialization of consecutive - and 123 tokens.
52-
Fail Serialization of consecutive - and 123% tokens.
53-
Fail Serialization of consecutive - and 123em tokens.
54-
Fail Serialization of consecutive 123 and bar tokens.
55-
Fail Serialization of consecutive 123 and bar() tokens.
50+
Pass Serialization of consecutive - and - tokens.
51+
Pass Serialization of consecutive - and 123 tokens.
52+
Pass Serialization of consecutive - and 123% tokens.
53+
Pass Serialization of consecutive - and 123em tokens.
54+
Pass Serialization of consecutive 123 and bar tokens.
55+
Pass Serialization of consecutive 123 and bar() tokens.
5656
Fail Serialization of consecutive 123 and url(bar) tokens.
57-
Fail Serialization of consecutive 123 and 123 tokens.
58-
Fail Serialization of consecutive 123 and 123% tokens.
59-
Fail Serialization of consecutive 123 and 123em tokens.
60-
Fail Serialization of consecutive 123 and % tokens.
61-
Fail Serialization of consecutive @ and bar tokens.
62-
Fail Serialization of consecutive @ and bar() tokens.
57+
Pass Serialization of consecutive 123 and 123 tokens.
58+
Pass Serialization of consecutive 123 and 123% tokens.
59+
Pass Serialization of consecutive 123 and 123em tokens.
60+
Pass Serialization of consecutive 123 and % tokens.
61+
Pass Serialization of consecutive @ and bar tokens.
62+
Pass Serialization of consecutive @ and bar() tokens.
6363
Fail Serialization of consecutive @ and url(bar) tokens.
64-
Fail Serialization of consecutive @ and - tokens.
65-
Fail Serialization of consecutive . and 123 tokens.
66-
Fail Serialization of consecutive . and 123% tokens.
67-
Fail Serialization of consecutive . and 123em tokens.
68-
Fail Serialization of consecutive + and 123 tokens.
69-
Fail Serialization of consecutive + and 123% tokens.
70-
Fail Serialization of consecutive + and 123em tokens.
71-
Fail Serialization of consecutive / and * tokens.
64+
Pass Serialization of consecutive @ and - tokens.
65+
Pass Serialization of consecutive . and 123 tokens.
66+
Pass Serialization of consecutive . and 123% tokens.
67+
Pass Serialization of consecutive . and 123em tokens.
68+
Pass Serialization of consecutive + and 123 tokens.
69+
Pass Serialization of consecutive + and 123% tokens.
70+
Pass Serialization of consecutive + and 123em tokens.
71+
Pass Serialization of consecutive / and * tokens.
7272
Fail Comments are handled correctly when computing a/* comment */b using t1:.
7373
Fail Comments are handled correctly when computing a/* comment */var(--t1) using t1:b.
7474
Fail Comments are handled correctly when computing var(--t1)b using t1:a/* comment */.
7575
Fail Comments are handled correctly when computing var(--t1)b using t1:'a/* unfinished '.
7676
Pass Comments are handled correctly when computing var(--t1)b using t1:"a/* unfinished ".
7777
Fail Comments are handled correctly when computing var(--t1)b using t1:'a " '/* comment */.
78-
Fail Empty fallback between tokens must not disturb comment insertion
78+
Pass Empty fallback between tokens must not disturb comment insertion

0 commit comments

Comments
 (0)