Skip to content

Commit e70cd5a

Browse files
authored
Merge pull request #2067 from sass/revert-calc
Revert new calculation functions
2 parents 4a86812 + a6a06b7 commit e70cd5a

File tree

14 files changed

+149
-824
lines changed

14 files changed

+149
-824
lines changed

CHANGELOG.md

+12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
## 1.66.0
2+
3+
* **Breaking change:** Drop support for the additional CSS calculations defined
4+
in CSS Values and Units 4. Custom Sass functions whose names overlapped with
5+
these new CSS functions were being parsed as CSS calculations instead, causing
6+
an unintentional breaking change outside our normal [compatibility policy] for
7+
CSS compatibility changes.
8+
9+
Support will be added again in a future version, but only after Sass has
10+
emitted a deprecation warning for all functions that will break for at least
11+
three months prior to the breakage.
12+
113
## 1.65.1
214

315
* Update abs-percent deprecatedIn version to `1.65.0`.

lib/src/ast/sass/expression/calculation.dart

-69
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,6 @@ final class CalculationExpression implements Expression {
4040
}
4141
}
4242

43-
/// Returns a `hypot()` calculation expression.
44-
CalculationExpression.hypot(Iterable<Expression> arguments, FileSpan span)
45-
: this("hypot", arguments, span);
46-
4743
/// Returns a `max()` calculation expression.
4844
CalculationExpression.max(Iterable<Expression> arguments, this.span)
4945
: name = "max",
@@ -53,76 +49,11 @@ final class CalculationExpression implements Expression {
5349
}
5450
}
5551

56-
/// Returns a `sqrt()` calculation expression.
57-
CalculationExpression.sqrt(Expression argument, FileSpan span)
58-
: this("sqrt", [argument], span);
59-
60-
/// Returns a `sin()` calculation expression.
61-
CalculationExpression.sin(Expression argument, FileSpan span)
62-
: this("sin", [argument], span);
63-
64-
/// Returns a `cos()` calculation expression.
65-
CalculationExpression.cos(Expression argument, FileSpan span)
66-
: this("cos", [argument], span);
67-
68-
/// Returns a `tan()` calculation expression.
69-
CalculationExpression.tan(Expression argument, FileSpan span)
70-
: this("tan", [argument], span);
71-
72-
/// Returns a `asin()` calculation expression.
73-
CalculationExpression.asin(Expression argument, FileSpan span)
74-
: this("asin", [argument], span);
75-
76-
/// Returns a `acos()` calculation expression.
77-
CalculationExpression.acos(Expression argument, FileSpan span)
78-
: this("acos", [argument], span);
79-
80-
/// Returns a `atan()` calculation expression.
81-
CalculationExpression.atan(Expression argument, FileSpan span)
82-
: this("atan", [argument], span);
83-
84-
/// Returns a `abs()` calculation expression.
85-
CalculationExpression.abs(Expression argument, FileSpan span)
86-
: this("abs", [argument], span);
87-
88-
/// Returns a `sign()` calculation expression.
89-
CalculationExpression.sign(Expression argument, FileSpan span)
90-
: this("sign", [argument], span);
91-
92-
/// Returns a `exp()` calculation expression.
93-
CalculationExpression.exp(Expression argument, FileSpan span)
94-
: this("exp", [argument], span);
95-
9652
/// Returns a `clamp()` calculation expression.
9753
CalculationExpression.clamp(
9854
Expression min, Expression value, Expression max, FileSpan span)
9955
: this("clamp", [min, max, value], span);
10056

101-
/// Returns a `pow()` calculation expression.
102-
CalculationExpression.pow(Expression base, Expression exponent, FileSpan span)
103-
: this("pow", [base, exponent], span);
104-
105-
/// Returns a `log()` calculation expression.
106-
CalculationExpression.log(Expression number, Expression base, FileSpan span)
107-
: this("log", [number, base], span);
108-
109-
/// Returns a `round()` calculation expression.
110-
CalculationExpression.round(
111-
Expression strategy, Expression number, Expression step, FileSpan span)
112-
: this("round", [strategy, number, step], span);
113-
114-
/// Returns a `atan2()` calculation expression.
115-
CalculationExpression.atan2(Expression y, Expression x, FileSpan span)
116-
: this("atan2", [y, x], span);
117-
118-
/// Returns a `mod()` calculation expression.
119-
CalculationExpression.mod(Expression y, Expression x, FileSpan span)
120-
: this("mod", [y, x], span);
121-
122-
/// Returns a `rem()` calculation expression.
123-
CalculationExpression.rem(Expression y, Expression x, FileSpan span)
124-
: this("rem", [y, x], span);
125-
12657
/// Returns a calculation expression with the given name and arguments.
12758
///
12859
/// Unlike the other constructors, this doesn't verify that the arguments are

lib/src/functions/math.dart

+90-29
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,40 @@ import '../deprecation.dart';
1212
import '../evaluation_context.dart';
1313
import '../exception.dart';
1414
import '../module/built_in.dart';
15-
import '../util/number.dart';
1615
import '../value.dart';
1716

1817
/// The global definitions of Sass math functions.
1918
final global = UnmodifiableListView([
20-
_abs, _ceil, _floor, _max, _min, _percentage, _randomFunction, _round,
21-
_unit, //
19+
_function("abs", r"$number", (arguments) {
20+
var number = arguments[0].assertNumber("number");
21+
if (number.hasUnit("%")) {
22+
warnForDeprecation(
23+
"Passing percentage units to the global abs() function is "
24+
"deprecated.\n"
25+
"In the future, this will emit a CSS abs() function to be resolved "
26+
"by the browser.\n"
27+
"To preserve current behavior: math.abs($number)"
28+
"\n"
29+
"To emit a CSS abs() now: abs(#{$number})\n"
30+
"More info: https://sass-lang.com/d/abs-percent",
31+
Deprecation.absPercent);
32+
}
33+
return SassNumber.withUnits(number.value.abs(),
34+
numeratorUnits: number.numeratorUnits,
35+
denominatorUnits: number.denominatorUnits);
36+
}),
37+
38+
_ceil, _floor, _max, _min, _percentage, _randomFunction, _round, _unit, //
2239
_compatible.withName("comparable"),
2340
_isUnitless.withName("unitless"),
2441
]);
2542

2643
/// The Sass math module.
2744
final module = BuiltInModule("math", functions: <Callable>[
28-
_abs, _acos, _asin, _atan, _atan2, _ceil, _clamp, _cos, _compatible, //
29-
_floor, _hypot, _isUnitless, _log, _max, _min, _percentage, _pow, //
30-
_randomFunction, _round, _sin, _sqrt, _tan, _unit, _div
45+
_numberFunction("abs", (value) => value.abs()),
46+
_acos, _asin, _atan, _atan2, _ceil, _clamp, _cos, _compatible, _floor, //
47+
_hypot, _isUnitless, _log, _max, _min, _percentage, _pow, _randomFunction,
48+
_round, _sin, _sqrt, _tan, _unit, _div
3149
], variables: {
3250
"e": SassNumber(math.e),
3351
"pi": SassNumber(math.pi),
@@ -89,8 +107,6 @@ final _round = _numberFunction("round", (number) => number.round().toDouble());
89107
/// Distance functions
90108
///
91109
92-
final _abs = _numberFunction("abs", (value) => value.abs());
93-
94110
final _hypot = _function("hypot", r"$numbers...", (arguments) {
95111
var numbers =
96112
arguments[0].asList.map((argument) => argument.assertNumber()).toList();
@@ -133,32 +149,87 @@ final _log = _function("log", r"$number, $base: null", (arguments) {
133149
final _pow = _function("pow", r"$base, $exponent", (arguments) {
134150
var base = arguments[0].assertNumber("base");
135151
var exponent = arguments[1].assertNumber("exponent");
136-
return pow(base, exponent);
152+
if (base.hasUnits) {
153+
throw SassScriptException("\$base: Expected $base to have no units.");
154+
} else if (exponent.hasUnits) {
155+
throw SassScriptException(
156+
"\$exponent: Expected $exponent to have no units.");
157+
} else {
158+
return SassNumber(math.pow(base.value, exponent.value));
159+
}
137160
});
138161

139-
final _sqrt = _singleArgumentMathFunc("sqrt", sqrt);
162+
final _sqrt = _function("sqrt", r"$number", (arguments) {
163+
var number = arguments[0].assertNumber("number");
164+
if (number.hasUnits) {
165+
throw SassScriptException("\$number: Expected $number to have no units.");
166+
} else {
167+
return SassNumber(math.sqrt(number.value));
168+
}
169+
});
140170

141171
///
142172
/// Trigonometric functions
143173
///
144174
145-
final _acos = _singleArgumentMathFunc("acos", acos);
175+
final _acos = _function("acos", r"$number", (arguments) {
176+
var number = arguments[0].assertNumber("number");
177+
if (number.hasUnits) {
178+
throw SassScriptException("\$number: Expected $number to have no units.");
179+
} else {
180+
return SassNumber.withUnits(math.acos(number.value) * 180 / math.pi,
181+
numeratorUnits: ['deg']);
182+
}
183+
});
146184

147-
final _asin = _singleArgumentMathFunc("asin", asin);
185+
final _asin = _function("asin", r"$number", (arguments) {
186+
var number = arguments[0].assertNumber("number");
187+
if (number.hasUnits) {
188+
throw SassScriptException("\$number: Expected $number to have no units.");
189+
} else {
190+
return SassNumber.withUnits(math.asin(number.value) * 180 / math.pi,
191+
numeratorUnits: ['deg']);
192+
}
193+
});
148194

149-
final _atan = _singleArgumentMathFunc("atan", atan);
195+
final _atan = _function("atan", r"$number", (arguments) {
196+
var number = arguments[0].assertNumber("number");
197+
if (number.hasUnits) {
198+
throw SassScriptException("\$number: Expected $number to have no units.");
199+
} else {
200+
return SassNumber.withUnits(math.atan(number.value) * 180 / math.pi,
201+
numeratorUnits: ['deg']);
202+
}
203+
});
150204

151205
final _atan2 = _function("atan2", r"$y, $x", (arguments) {
152206
var y = arguments[0].assertNumber("y");
153207
var x = arguments[1].assertNumber("x");
154-
return atan2(y, x);
208+
return SassNumber.withUnits(
209+
math.atan2(y.value, x.convertValueToMatch(y, 'x', 'y')) * 180 / math.pi,
210+
numeratorUnits: ['deg']);
155211
});
156212

157-
final _cos = _singleArgumentMathFunc("cos", cos);
158-
159-
final _sin = _singleArgumentMathFunc("sin", sin);
160-
161-
final _tan = _singleArgumentMathFunc("tan", tan);
213+
final _cos = _function(
214+
"cos",
215+
r"$number",
216+
(arguments) => SassNumber(math.cos(arguments[0]
217+
.assertNumber("number")
218+
.coerceValueToUnit("rad", "number"))));
219+
220+
final _sin = _function(
221+
"sin",
222+
r"$number",
223+
(arguments) => SassNumber(math.sin(arguments[0]
224+
.assertNumber("number")
225+
.coerceValueToUnit("rad", "number"))));
226+
227+
final _tan = _function(
228+
"tan",
229+
r"$number",
230+
(arguments) => SassNumber(math.tan(arguments[0]
231+
.assertNumber("number")
232+
.coerceValueToUnit("rad", "number"))));
162233

163234
///
164235
/// Unit functions
@@ -234,16 +305,6 @@ final _div = _function("div", r"$number1, $number2", (arguments) {
234305
/// Helpers
235306
///
236307
237-
/// Returns a [Callable] named [name] that calls a single argument
238-
/// math function.
239-
BuiltInCallable _singleArgumentMathFunc(
240-
String name, SassNumber mathFunc(SassNumber value)) {
241-
return _function(name, r"$number", (arguments) {
242-
var number = arguments[0].assertNumber("number");
243-
return mathFunc(number);
244-
});
245-
}
246-
247308
/// Returns a [Callable] named [name] that transforms a number's value
248309
/// using [transform] and preserves its units.
249310
BuiltInCallable _numberFunction(String name, double transform(double value)) {

lib/src/js/value/calculation.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ final JSClass calculationOperationClass = () {
9393
_assertCalculationValue(left);
9494
_assertCalculationValue(right);
9595
return SassCalculation.operateInternal(operator, left, right,
96-
inLegacySassFunction: false, simplify: false);
96+
inMinMax: false, simplify: false);
9797
});
9898

9999
jsClass.defineMethods({
@@ -109,7 +109,7 @@ final JSClass calculationOperationClass = () {
109109

110110
getJSClass(SassCalculation.operateInternal(
111111
CalculationOperator.plus, SassNumber(1), SassNumber(1),
112-
inLegacySassFunction: false, simplify: false))
112+
inMinMax: false, simplify: false))
113113
.injectSuperclass(jsClass);
114114
return jsClass;
115115
}();

lib/src/parse/stylesheet.dart

+10-43
Original file line numberDiff line numberDiff line change
@@ -2065,8 +2065,7 @@ abstract class StylesheetParser extends Parser {
20652065
/// produces a potentially slash-separated number.
20662066
bool _isSlashOperand(Expression expression) =>
20672067
expression is NumberExpression ||
2068-
(expression is CalculationExpression &&
2069-
!{'min', 'max', 'round', 'abs'}.contains(expression.name)) ||
2068+
expression is CalculationExpression ||
20702069
(expression is BinaryOperationExpression && expression.allowsSlash);
20712070

20722071
/// Consumes an expression that doesn't contain any top-level whitespace.
@@ -2653,64 +2652,32 @@ abstract class StylesheetParser extends Parser {
26532652
assert(scanner.peekChar() == $lparen);
26542653
switch (name) {
26552654
case "calc":
2656-
case "sqrt":
2657-
case "sin":
2658-
case "cos":
2659-
case "tan":
2660-
case "asin":
2661-
case "acos":
2662-
case "atan":
2663-
case "exp":
2664-
case "sign":
26652655
var arguments = _calculationArguments(1);
26662656
return CalculationExpression(name, arguments, scanner.spanFrom(start));
26672657

2668-
case "abs":
2669-
return _tryArgumentsCalculation(name, start, 1);
2670-
2671-
case "hypot":
2672-
var arguments = _calculationArguments();
2673-
return CalculationExpression(name, arguments, scanner.spanFrom(start));
2674-
26752658
case "min" || "max":
26762659
// min() and max() are parsed as calculations if possible, and otherwise
26772660
// are parsed as normal Sass functions.
2678-
return _tryArgumentsCalculation(name, start, null);
2679-
2680-
case "pow":
2681-
case "log":
2682-
case "atan2":
2683-
case "mod":
2684-
case "rem":
2685-
var arguments = _calculationArguments(2);
2661+
var beforeArguments = scanner.state;
2662+
List<Expression> arguments;
2663+
try {
2664+
arguments = _calculationArguments();
2665+
} on FormatException catch (_) {
2666+
scanner.state = beforeArguments;
2667+
return null;
2668+
}
2669+
26862670
return CalculationExpression(name, arguments, scanner.spanFrom(start));
26872671

26882672
case "clamp":
26892673
var arguments = _calculationArguments(3);
26902674
return CalculationExpression(name, arguments, scanner.spanFrom(start));
26912675

2692-
case "round":
2693-
return _tryArgumentsCalculation(name, start, 3);
2694-
26952676
case _:
26962677
return null;
26972678
}
26982679
}
26992680

2700-
// Returns a CalculationExpression if the function can be parsed as a calculation,
2701-
// otherwise, returns null and the function is parsed as a normal Sass function.
2702-
CalculationExpression? _tryArgumentsCalculation(
2703-
String name, LineScannerState start, int? maxArgs) {
2704-
var beforeArguments = scanner.state;
2705-
try {
2706-
var arguments = _calculationArguments(maxArgs);
2707-
return CalculationExpression(name, arguments, scanner.spanFrom(start));
2708-
} on FormatException catch (_) {
2709-
scanner.state = beforeArguments;
2710-
return null;
2711-
}
2712-
}
2713-
27142681
/// Consumes and returns arguments for a calculation expression, including the
27152682
/// opening and closing parentheses.
27162683
///

0 commit comments

Comments
 (0)