22// MIT-style license that can be found in the LICENSE file or at
33// https://opensource.org/licenses/MIT.
44
5+ import 'package:charcode/charcode.dart' ;
56import 'package:meta/meta.dart' ;
67
78import '../../exception.dart' ;
89import '../../logger.dart' ;
910import '../../parse/scss.dart' ;
11+ import '../../util/nullable.dart' ;
12+ import '../../value.dart' ;
1013import '../../visitor/interface/expression.dart' ;
11- import 'node .dart' ;
14+ import '../sass .dart' ;
1215
1316/// A SassScript expression in a Sass syntax tree.
1417///
@@ -27,3 +30,85 @@ abstract interface class Expression implements SassNode {
2730 factory Expression .parse (String contents, {Object ? url, Logger ? logger}) =>
2831 ScssParser (contents, url: url, logger: logger).parseExpression ();
2932}
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