2
2
// MIT-style license that can be found in the LICENSE file or at
3
3
// https://opensource.org/licenses/MIT.
4
4
5
+ import 'package:charcode/charcode.dart' ;
5
6
import 'package:meta/meta.dart' ;
6
7
7
8
import '../../exception.dart' ;
8
9
import '../../logger.dart' ;
9
10
import '../../parse/scss.dart' ;
11
+ import '../../util/nullable.dart' ;
12
+ import '../../value.dart' ;
10
13
import '../../visitor/interface/expression.dart' ;
11
- import 'node .dart' ;
14
+ import '../sass .dart' ;
12
15
13
16
/// A SassScript expression in a Sass syntax tree.
14
17
///
@@ -27,3 +30,85 @@ abstract interface class Expression implements SassNode {
27
30
factory Expression .parse (String contents, {Object ? url, Logger ? logger}) =>
28
31
ScssParser (contents, url: url, logger: logger).parseExpression ();
29
32
}
33
+
34
+ // Use an extension class rather than a method so we don't have to make
35
+ // [Expression] a concrete base class for something we'll get rid of anyway once
36
+ // we remove the global math functions that make this necessary.
37
+ extension ExpressionExtensions on Expression {
38
+ /// Whether this expression can be used in a calculation context.
39
+ ///
40
+ /// @nodoc
41
+ @internal
42
+ bool get isCalculationSafe => accept (_IsCalculationSafeVisitor ());
43
+ }
44
+
45
+ // We could use [AstSearchVisitor] to implement this more tersely, but that
46
+ // would default to returning `true` if we added a new expression type and
47
+ // forgot to update this class.
48
+ class _IsCalculationSafeVisitor implements ExpressionVisitor <bool > {
49
+ const _IsCalculationSafeVisitor ();
50
+
51
+ bool visitBinaryOperationExpression (BinaryOperationExpression node) =>
52
+ (const {
53
+ BinaryOperator .times,
54
+ BinaryOperator .dividedBy,
55
+ BinaryOperator .plus,
56
+ BinaryOperator .minus
57
+ }).contains (node.operator ) &&
58
+ (node.left.accept (this ) || node.right.accept (this ));
59
+
60
+ bool visitBooleanExpression (BooleanExpression node) => false ;
61
+
62
+ bool visitColorExpression (ColorExpression node) => false ;
63
+
64
+ bool visitFunctionExpression (FunctionExpression node) => true ;
65
+
66
+ bool visitInterpolatedFunctionExpression (
67
+ InterpolatedFunctionExpression node) =>
68
+ true ;
69
+
70
+ bool visitIfExpression (IfExpression node) => true ;
71
+
72
+ bool visitListExpression (ListExpression node) =>
73
+ node.separator == ListSeparator .space &&
74
+ ! node.hasBrackets &&
75
+ node.contents.length > 1 &&
76
+ node.contents.every ((expression) => expression.accept (this ));
77
+
78
+ bool visitMapExpression (MapExpression node) => false ;
79
+
80
+ bool visitNullExpression (NullExpression node) => false ;
81
+
82
+ bool visitNumberExpression (NumberExpression node) => true ;
83
+
84
+ bool visitParenthesizedExpression (ParenthesizedExpression node) =>
85
+ node.expression.accept (this );
86
+
87
+ bool visitSelectorExpression (SelectorExpression node) => false ;
88
+
89
+ bool visitStringExpression (StringExpression node) {
90
+ if (node.hasQuotes) return false ;
91
+
92
+ // Exclude non-identifier constructs that are parsed as [StringExpression]s.
93
+ // We could just check if they parse as valid identifiers, but this is
94
+ // cheaper.
95
+ var text = node.text.initialPlain;
96
+ return
97
+ // !important
98
+ ! text.startsWith ("!" ) &&
99
+ // ID-style identifiers
100
+ ! text.startsWith ("#" ) &&
101
+ // Unicode ranges
102
+ text.codeUnitAtOrNull (1 ) != $plus &&
103
+ // url()
104
+ text.codeUnitAtOrNull (3 ) != $lparen;
105
+ }
106
+
107
+ bool visitSupportsExpression (SupportsExpression node) => false ;
108
+
109
+ bool visitUnaryOperationExpression (UnaryOperationExpression node) => false ;
110
+
111
+ bool visitValueExpression (ValueExpression node) => false ;
112
+
113
+ bool visitVariableExpression (VariableExpression node) => true ;
114
+ }
0 commit comments