Skip to content

Commit ec0bab5

Browse files
authored
Fix a bug with nested properties beneath Sass-syntax custom props (#1096)
Closes #1095
1 parent 82b2779 commit ec0bab5

File tree

9 files changed

+52
-24
lines changed

9 files changed

+52
-24
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 1.26.12
2+
3+
* Fix a bug where nesting properties beneath a Sass-syntax custom property
4+
(written as `#{--foo}: ...`) would crash.
5+
16
## 1.26.11
27

38
* **Potentially breaking bug fix:** `selector.nest()` now throws an error

lib/src/ast/css/declaration.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,15 @@ abstract class CssDeclaration extends CssNode {
2424
/// the variable was used. Otherwise, this is identical to [value.span].
2525
FileSpan get valueSpanForMap;
2626

27-
T accept<T>(CssVisitor<T> visitor) => visitor.visitCssDeclaration(this);
27+
/// Returns whether this is a CSS Custom Property declaration.
28+
bool get isCustomProperty;
29+
30+
/// Whether this is was originally parsed as a custom property declaration, as
31+
/// opposed to using something like `#{--foo}: ...` to cause it to be parsed
32+
/// as a normal Sass declaration.
33+
///
34+
/// If this is `true`, [isCustomProperty] will also be `true`.
35+
bool get parsedAsCustomProperty;
36+
37+
T accept<T>(CssVisitor<T> visitor);
2838
}

lib/src/ast/css/modifiable/declaration.dart

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// MIT-style license that can be found in the LICENSE file or at
33
// https://opensource.org/licenses/MIT.
44

5+
import 'package:meta/meta.dart';
56
import 'package:source_span/source_span.dart';
67

78
import '../../../value.dart';
@@ -15,13 +16,26 @@ class ModifiableCssDeclaration extends ModifiableCssNode
1516
implements CssDeclaration {
1617
final CssValue<String> name;
1718
final CssValue<Value> value;
19+
final bool parsedAsCustomProperty;
1820
final FileSpan valueSpanForMap;
1921
final FileSpan span;
2022

23+
bool get isCustomProperty => name.value.startsWith('--');
24+
25+
/// Returns a new CSS declaration with the given properties.
2126
ModifiableCssDeclaration(this.name, this.value, this.span,
22-
{FileSpan valueSpanForMap})
23-
: valueSpanForMap = valueSpanForMap ?? span;
27+
{@required bool parsedAsCustomProperty, FileSpan valueSpanForMap})
28+
: parsedAsCustomProperty = parsedAsCustomProperty,
29+
valueSpanForMap = valueSpanForMap ?? span {
30+
if (!isCustomProperty && parsedAsCustomProperty) {
31+
throw ArgumentError(
32+
'sassSyntaxCustomProperty must be false if name doesn\'t begin with '
33+
'"--".');
34+
}
35+
}
2436

2537
T accept<T>(ModifiableCssVisitor<T> visitor) =>
2638
visitor.visitCssDeclaration(this);
39+
40+
String toString() => "$name: $value;";
2741
}

lib/src/ast/sass/statement/declaration.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,15 @@ class Declaration extends ParentStatement {
2020

2121
final FileSpan span;
2222

23+
/// Returns whether this is a CSS Custom Property declaration.
24+
///
25+
/// Note that this can return `false` for declarations that will ultimately be
26+
/// serialized as custom properties if they aren't *parsed as* custom
27+
/// properties, such as `#{--foo}: ...`.
28+
bool get isCustomProperty => name.initialPlain.startsWith('--');
29+
2330
Declaration(this.name, this.span, {this.value, Iterable<Statement> children})
2431
: super(children = children == null ? null : List.unmodifiable(children));
2532

2633
T accept<T>(StatementVisitor<T> visitor) => visitor.visitDeclaration(this);
27-
28-
String toString() => "$name: $value;";
2934
}

lib/src/visitor/async_evaluate.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1039,8 +1039,9 @@ class _EvaluateVisitor
10391039
if (cssValue != null &&
10401040
(!cssValue.value.isBlank || _isEmptyList(cssValue.value))) {
10411041
_parent.addChild(ModifiableCssDeclaration(name, cssValue, node.span,
1042+
parsedAsCustomProperty: node.isCustomProperty,
10421043
valueSpanForMap: _expressionNode(node.value)?.span));
1043-
} else if (name.value.startsWith('--')) {
1044+
} else if (name.value.startsWith('--') && node.children == null) {
10441045
throw _exception(
10451046
"Custom property values may not be empty.", node.value.span);
10461047
}
@@ -2559,6 +2560,7 @@ class _EvaluateVisitor
25592560

25602561
Future<void> visitCssDeclaration(CssDeclaration node) async {
25612562
_parent.addChild(ModifiableCssDeclaration(node.name, node.value, node.span,
2563+
parsedAsCustomProperty: node.isCustomProperty,
25622564
valueSpanForMap: node.valueSpanForMap));
25632565
}
25642566

lib/src/visitor/clone_css.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class _CloneCssVisitor implements CssVisitor<ModifiableCssNode> {
4444

4545
ModifiableCssDeclaration visitCssDeclaration(CssDeclaration node) =>
4646
ModifiableCssDeclaration(node.name, node.value, node.span,
47+
parsedAsCustomProperty: node.parsedAsCustomProperty,
4748
valueSpanForMap: node.valueSpanForMap);
4849

4950
ModifiableCssImport visitCssImport(CssImport node) =>

lib/src/visitor/evaluate.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// DO NOT EDIT. This file was generated from async_evaluate.dart.
66
// See tool/grind/synchronize.dart for details.
77
//
8-
// Checksum: f6fe6645ccec58216ef623851bd2594de291a360
8+
// Checksum: 651a7e9f78b68bfd440241304301cf78711553a4
99
//
1010
// ignore_for_file: unused_import
1111

@@ -1041,8 +1041,9 @@ class _EvaluateVisitor
10411041
if (cssValue != null &&
10421042
(!cssValue.value.isBlank || _isEmptyList(cssValue.value))) {
10431043
_parent.addChild(ModifiableCssDeclaration(name, cssValue, node.span,
1044+
parsedAsCustomProperty: node.isCustomProperty,
10441045
valueSpanForMap: _expressionNode(node.value)?.span));
1045-
} else if (name.value.startsWith('--')) {
1046+
} else if (name.value.startsWith('--') && node.children == null) {
10461047
throw _exception(
10471048
"Custom property values may not be empty.", node.value.span);
10481049
}
@@ -2541,6 +2542,7 @@ class _EvaluateVisitor
25412542

25422543
void visitCssDeclaration(CssDeclaration node) {
25432544
_parent.addChild(ModifiableCssDeclaration(node.name, node.value, node.span,
2545+
parsedAsCustomProperty: node.isCustomProperty,
25442546
valueSpanForMap: node.valueSpanForMap));
25452547
}
25462548

lib/src/visitor/serialize.dart

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,10 @@ class _SerializeVisitor
333333
_write(node.name);
334334
_buffer.writeCharCode($colon);
335335

336-
if (_isParsedCustomProperty(node)) {
336+
// If `node` is a custom property that was parsed as a normal Sass-syntax
337+
// property (such as `#{--foo}: ...`), we serialize its value using the
338+
// normal Sass property logic as well.
339+
if (node.isCustomProperty && node.parsedAsCustomProperty) {
337340
_for(node.value, () {
338341
if (_isCompressed) {
339342
_writeFoldedValue(node);
@@ -355,20 +358,6 @@ class _SerializeVisitor
355358
}
356359
}
357360

358-
/// Returns whether [node] is a custom property that was parsed as a custom
359-
/// property (rather than being dynamically generated, as in `#{--foo}: ...`).
360-
///
361-
/// We only re-indent custom property values that were parsed as custom
362-
/// properties, which we detect as unquoted strings. It's possible to have
363-
/// false positives here, since someone could write `#{--foo}: unquoted`, but
364-
/// that's unlikely enough that we can spare the extra time a no-op
365-
/// reindenting will take.
366-
bool _isParsedCustomProperty(CssDeclaration node) {
367-
if (!node.name.value.startsWith("--")) return false;
368-
var value = node.value.value;
369-
return value is SassString && !value.hasQuotes;
370-
}
371-
372361
/// Emits the value of [node], with all newlines followed by whitespace
373362
void _writeFoldedValue(CssDeclaration node) {
374363
var scanner = StringScanner((node.value.value as SassString).text);

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: sass
2-
version: 1.26.11
2+
version: 1.26.12
33
description: A Sass implementation in Dart.
44
author: Sass Team
55
homepage: https://github.com/sass/dart-sass

0 commit comments

Comments
 (0)