Skip to content
This repository was archived by the owner on Nov 1, 2024. It is now read-only.

Commit 33b1f2a

Browse files
committed
Support parsing identifiers with escape codes in them
The output of `CssPrinter` will now also retain escape codes in identifiers. This ensures they remain valid identifiers, as the escaped values may not parse as valid identifiers. The parser will also no longer accept an ID or class selector with space between the first token (`#` or `.` respectively) and the identifier. The parser will now fail immediately on these selector errors instead of attempting to recover. Recovering in a robust manner is difficult given that this parser immediately attempts to parse at the selector granularity, rather than first parsing the style sheet into rules as described [here][parse-rule]. [parse-rule]: https://www.w3.org/TR/css-syntax-3/#consume-a-qualified-rule Fixes https://github.com/dart-lang/csslib/issues/58.
1 parent 5d0997f commit 33b1f2a

File tree

4 files changed

+47
-37
lines changed

4 files changed

+47
-37
lines changed

lib/parser.dart

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,12 +1307,14 @@ class _Parser {
13071307
var selectors = <Selector>[];
13081308
var start = _peekToken.span;
13091309

1310+
tokenizer.inSelector = true;
13101311
do {
13111312
var selector = processSelector();
13121313
if (selector != null) {
13131314
selectors.add(selector);
13141315
}
13151316
} while (_maybeEat(TokenKind.COMMA));
1317+
tokenizer.inSelector = false;
13161318

13171319
if (selectors.isNotEmpty) {
13181320
return SelectorGroup(selectors, _makeSpan(start));
@@ -1501,35 +1503,20 @@ class _Parser {
15011503
case TokenKind.HASH:
15021504
_eat(TokenKind.HASH);
15031505

1504-
var hasWhiteSpace = false;
15051506
if (_anyWhiteSpaceBeforePeekToken(TokenKind.HASH)) {
1506-
_warning('Not a valid ID selector expected #id', _makeSpan(start));
1507-
hasWhiteSpace = true;
1508-
}
1509-
if (_peekIdentifier()) {
1510-
var id = identifier();
1511-
if (hasWhiteSpace) {
1512-
// Generate bad selector id (normalized).
1513-
id.name = ' ${id.name}';
1514-
}
1515-
return IdSelector(id, _makeSpan(start));
1507+
_error('Not a valid ID selector expected #id', _makeSpan(start));
1508+
return null;
15161509
}
1517-
return null;
1510+
return IdSelector(identifier(), _makeSpan(start));
15181511
case TokenKind.DOT:
15191512
_eat(TokenKind.DOT);
15201513

1521-
var hasWhiteSpace = false;
15221514
if (_anyWhiteSpaceBeforePeekToken(TokenKind.DOT)) {
1523-
_warning('Not a valid class selector expected .className',
1515+
_error('Not a valid class selector expected .className',
15241516
_makeSpan(start));
1525-
hasWhiteSpace = true;
1526-
}
1527-
var id = identifier();
1528-
if (hasWhiteSpace) {
1529-
// Generate bad selector class (normalized).
1530-
id.name = ' ${id.name}';
1517+
return null;
15311518
}
1532-
return ClassSelector(id, _makeSpan(start));
1519+
return ClassSelector(identifier(), _makeSpan(start));
15331520
case TokenKind.COLON:
15341521
// :pseudo-class ::pseudo-element
15351522
return processPseudoSelector(start);

lib/src/tree.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ class Identifier extends TreeNode {
2020
dynamic visit(VisitorBase visitor) => visitor.visitIdentifier(this);
2121

2222
@override
23-
String toString() => name;
23+
String toString() {
24+
// Try to use the identifier's original lexeme to preserve any escape codes
25+
// as authored. The name, which may include escaped values, may no longer be
26+
// a valid identifier.
27+
return span?.text ?? name;
28+
}
2429
}
2530

2631
class Wildcard extends TreeNode {
@@ -274,7 +279,7 @@ class AttributeSelector extends SimpleSelector {
274279
String valueToString() {
275280
if (value != null) {
276281
if (value is Identifier) {
277-
return value.name;
282+
return value.toString();
278283
} else {
279284
return '"$value"';
280285
}

test/error_test.dart

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -134,37 +134,27 @@ void testBadSelectors() {
134134

135135
// Invalid id selector.
136136
var input = '# foo { color: #ff00ff; }';
137-
var stylesheet = parseCss(input, errors: errors);
137+
parseCss(input, errors: errors);
138138

139-
expect(errors.isEmpty, false);
139+
expect(errors, isNotEmpty);
140140
expect(errors[0].toString(), r'''
141141
error on line 1, column 1: Not a valid ID selector expected #id
142142
143143
1 │ # foo { color: #ff00ff; }
144144
│ ^
145145
╵''');
146-
expect(stylesheet != null, true);
147-
expect(prettyPrint(stylesheet), r'''
148-
# foo {
149-
color: #f0f;
150-
}''');
151146

152147
// Invalid class selector.
153148
input = '. foo { color: #ff00ff; }';
154-
stylesheet = parseCss(input, errors: errors..clear());
149+
parseCss(input, errors: errors..clear());
155150

156-
expect(errors.isEmpty, false);
151+
expect(errors, isNotEmpty);
157152
expect(errors[0].toString(), r'''
158153
error on line 1, column 1: Not a valid class selector expected .className
159154
160155
1 │ . foo { color: #ff00ff; }
161156
│ ^
162157
╵''');
163-
expect(stylesheet != null, true);
164-
expect(prettyPrint(stylesheet), r'''
165-
. foo {
166-
color: #f0f;
167-
}''');
168158
}
169159

170160
/// Test for bad hex values.

test/escape_codes_test.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import 'package:csslib/parser.dart';
2+
import 'package:test/test.dart';
3+
4+
import 'testing.dart';
5+
6+
void main() {
7+
final errors = <Message>[];
8+
9+
tearDown(() {
10+
errors.clear();
11+
});
12+
13+
group('handles escape codes', () {
14+
group('in an identifier', () {
15+
test('with trailing space', () {
16+
final selectorAst = selector(r'.\35 00px', errors: errors);
17+
expect(errors, isEmpty);
18+
expect(compactOuptut(selectorAst), r'.\35 00px');
19+
});
20+
21+
test('in an attribute selector value', () {
22+
final selectorAst = selector(r'[elevation=\31]', errors: errors);
23+
expect(errors, isEmpty);
24+
expect(compactOuptut(selectorAst), r'[elevation=\31]');
25+
});
26+
});
27+
});
28+
}

0 commit comments

Comments
 (0)