Skip to content

Commit

Permalink
Merge pull request #2067 from sass/revert-calc
Browse files Browse the repository at this point in the history
Revert new calculation functions
  • Loading branch information
nex3 committed Aug 17, 2023
2 parents 4a86812 + a6a06b7 commit e70cd5a
Show file tree
Hide file tree
Showing 14 changed files with 149 additions and 824 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## 1.66.0

* **Breaking change:** Drop support for the additional CSS calculations defined
in CSS Values and Units 4. Custom Sass functions whose names overlapped with
these new CSS functions were being parsed as CSS calculations instead, causing
an unintentional breaking change outside our normal [compatibility policy] for
CSS compatibility changes.

Support will be added again in a future version, but only after Sass has
emitted a deprecation warning for all functions that will break for at least
three months prior to the breakage.

## 1.65.1

* Update abs-percent deprecatedIn version to `1.65.0`.
Expand Down
69 changes: 0 additions & 69 deletions lib/src/ast/sass/expression/calculation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,6 @@ final class CalculationExpression implements Expression {
}
}

/// Returns a `hypot()` calculation expression.
CalculationExpression.hypot(Iterable<Expression> arguments, FileSpan span)
: this("hypot", arguments, span);

/// Returns a `max()` calculation expression.
CalculationExpression.max(Iterable<Expression> arguments, this.span)
: name = "max",
Expand All @@ -53,76 +49,11 @@ final class CalculationExpression implements Expression {
}
}

/// Returns a `sqrt()` calculation expression.
CalculationExpression.sqrt(Expression argument, FileSpan span)
: this("sqrt", [argument], span);

/// Returns a `sin()` calculation expression.
CalculationExpression.sin(Expression argument, FileSpan span)
: this("sin", [argument], span);

/// Returns a `cos()` calculation expression.
CalculationExpression.cos(Expression argument, FileSpan span)
: this("cos", [argument], span);

/// Returns a `tan()` calculation expression.
CalculationExpression.tan(Expression argument, FileSpan span)
: this("tan", [argument], span);

/// Returns a `asin()` calculation expression.
CalculationExpression.asin(Expression argument, FileSpan span)
: this("asin", [argument], span);

/// Returns a `acos()` calculation expression.
CalculationExpression.acos(Expression argument, FileSpan span)
: this("acos", [argument], span);

/// Returns a `atan()` calculation expression.
CalculationExpression.atan(Expression argument, FileSpan span)
: this("atan", [argument], span);

/// Returns a `abs()` calculation expression.
CalculationExpression.abs(Expression argument, FileSpan span)
: this("abs", [argument], span);

/// Returns a `sign()` calculation expression.
CalculationExpression.sign(Expression argument, FileSpan span)
: this("sign", [argument], span);

/// Returns a `exp()` calculation expression.
CalculationExpression.exp(Expression argument, FileSpan span)
: this("exp", [argument], span);

/// Returns a `clamp()` calculation expression.
CalculationExpression.clamp(
Expression min, Expression value, Expression max, FileSpan span)
: this("clamp", [min, max, value], span);

/// Returns a `pow()` calculation expression.
CalculationExpression.pow(Expression base, Expression exponent, FileSpan span)
: this("pow", [base, exponent], span);

/// Returns a `log()` calculation expression.
CalculationExpression.log(Expression number, Expression base, FileSpan span)
: this("log", [number, base], span);

/// Returns a `round()` calculation expression.
CalculationExpression.round(
Expression strategy, Expression number, Expression step, FileSpan span)
: this("round", [strategy, number, step], span);

/// Returns a `atan2()` calculation expression.
CalculationExpression.atan2(Expression y, Expression x, FileSpan span)
: this("atan2", [y, x], span);

/// Returns a `mod()` calculation expression.
CalculationExpression.mod(Expression y, Expression x, FileSpan span)
: this("mod", [y, x], span);

/// Returns a `rem()` calculation expression.
CalculationExpression.rem(Expression y, Expression x, FileSpan span)
: this("rem", [y, x], span);

/// Returns a calculation expression with the given name and arguments.
///
/// Unlike the other constructors, this doesn't verify that the arguments are
Expand Down
119 changes: 90 additions & 29 deletions lib/src/functions/math.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,40 @@ import '../deprecation.dart';
import '../evaluation_context.dart';
import '../exception.dart';
import '../module/built_in.dart';
import '../util/number.dart';
import '../value.dart';

/// The global definitions of Sass math functions.
final global = UnmodifiableListView([
_abs, _ceil, _floor, _max, _min, _percentage, _randomFunction, _round,
_unit, //
_function("abs", r"$number", (arguments) {
var number = arguments[0].assertNumber("number");
if (number.hasUnit("%")) {
warnForDeprecation(
"Passing percentage units to the global abs() function is "
"deprecated.\n"
"In the future, this will emit a CSS abs() function to be resolved "
"by the browser.\n"
"To preserve current behavior: math.abs($number)"
"\n"
"To emit a CSS abs() now: abs(#{$number})\n"
"More info: https://sass-lang.com/d/abs-percent",
Deprecation.absPercent);
}
return SassNumber.withUnits(number.value.abs(),
numeratorUnits: number.numeratorUnits,
denominatorUnits: number.denominatorUnits);
}),

_ceil, _floor, _max, _min, _percentage, _randomFunction, _round, _unit, //
_compatible.withName("comparable"),
_isUnitless.withName("unitless"),
]);

/// The Sass math module.
final module = BuiltInModule("math", functions: <Callable>[
_abs, _acos, _asin, _atan, _atan2, _ceil, _clamp, _cos, _compatible, //
_floor, _hypot, _isUnitless, _log, _max, _min, _percentage, _pow, //
_randomFunction, _round, _sin, _sqrt, _tan, _unit, _div
_numberFunction("abs", (value) => value.abs()),
_acos, _asin, _atan, _atan2, _ceil, _clamp, _cos, _compatible, _floor, //
_hypot, _isUnitless, _log, _max, _min, _percentage, _pow, _randomFunction,
_round, _sin, _sqrt, _tan, _unit, _div
], variables: {
"e": SassNumber(math.e),
"pi": SassNumber(math.pi),
Expand Down Expand Up @@ -89,8 +107,6 @@ final _round = _numberFunction("round", (number) => number.round().toDouble());
/// Distance functions
///
final _abs = _numberFunction("abs", (value) => value.abs());

final _hypot = _function("hypot", r"$numbers...", (arguments) {
var numbers =
arguments[0].asList.map((argument) => argument.assertNumber()).toList();
Expand Down Expand Up @@ -133,32 +149,87 @@ final _log = _function("log", r"$number, $base: null", (arguments) {
final _pow = _function("pow", r"$base, $exponent", (arguments) {
var base = arguments[0].assertNumber("base");
var exponent = arguments[1].assertNumber("exponent");
return pow(base, exponent);
if (base.hasUnits) {
throw SassScriptException("\$base: Expected $base to have no units.");
} else if (exponent.hasUnits) {
throw SassScriptException(
"\$exponent: Expected $exponent to have no units.");
} else {
return SassNumber(math.pow(base.value, exponent.value));
}
});

final _sqrt = _singleArgumentMathFunc("sqrt", sqrt);
final _sqrt = _function("sqrt", r"$number", (arguments) {
var number = arguments[0].assertNumber("number");
if (number.hasUnits) {
throw SassScriptException("\$number: Expected $number to have no units.");
} else {
return SassNumber(math.sqrt(number.value));
}
});

///
/// Trigonometric functions
///
final _acos = _singleArgumentMathFunc("acos", acos);
final _acos = _function("acos", r"$number", (arguments) {
var number = arguments[0].assertNumber("number");
if (number.hasUnits) {
throw SassScriptException("\$number: Expected $number to have no units.");
} else {
return SassNumber.withUnits(math.acos(number.value) * 180 / math.pi,
numeratorUnits: ['deg']);
}
});

final _asin = _singleArgumentMathFunc("asin", asin);
final _asin = _function("asin", r"$number", (arguments) {
var number = arguments[0].assertNumber("number");
if (number.hasUnits) {
throw SassScriptException("\$number: Expected $number to have no units.");
} else {
return SassNumber.withUnits(math.asin(number.value) * 180 / math.pi,
numeratorUnits: ['deg']);
}
});

final _atan = _singleArgumentMathFunc("atan", atan);
final _atan = _function("atan", r"$number", (arguments) {
var number = arguments[0].assertNumber("number");
if (number.hasUnits) {
throw SassScriptException("\$number: Expected $number to have no units.");
} else {
return SassNumber.withUnits(math.atan(number.value) * 180 / math.pi,
numeratorUnits: ['deg']);
}
});

final _atan2 = _function("atan2", r"$y, $x", (arguments) {
var y = arguments[0].assertNumber("y");
var x = arguments[1].assertNumber("x");
return atan2(y, x);
return SassNumber.withUnits(
math.atan2(y.value, x.convertValueToMatch(y, 'x', 'y')) * 180 / math.pi,
numeratorUnits: ['deg']);
});

final _cos = _singleArgumentMathFunc("cos", cos);

final _sin = _singleArgumentMathFunc("sin", sin);

final _tan = _singleArgumentMathFunc("tan", tan);
final _cos = _function(
"cos",
r"$number",
(arguments) => SassNumber(math.cos(arguments[0]
.assertNumber("number")
.coerceValueToUnit("rad", "number"))));

final _sin = _function(
"sin",
r"$number",
(arguments) => SassNumber(math.sin(arguments[0]
.assertNumber("number")
.coerceValueToUnit("rad", "number"))));

final _tan = _function(
"tan",
r"$number",
(arguments) => SassNumber(math.tan(arguments[0]
.assertNumber("number")
.coerceValueToUnit("rad", "number"))));

///
/// Unit functions
Expand Down Expand Up @@ -234,16 +305,6 @@ final _div = _function("div", r"$number1, $number2", (arguments) {
/// Helpers
///
/// Returns a [Callable] named [name] that calls a single argument
/// math function.
BuiltInCallable _singleArgumentMathFunc(
String name, SassNumber mathFunc(SassNumber value)) {
return _function(name, r"$number", (arguments) {
var number = arguments[0].assertNumber("number");
return mathFunc(number);
});
}

/// Returns a [Callable] named [name] that transforms a number's value
/// using [transform] and preserves its units.
BuiltInCallable _numberFunction(String name, double transform(double value)) {
Expand Down
4 changes: 2 additions & 2 deletions lib/src/js/value/calculation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ final JSClass calculationOperationClass = () {
_assertCalculationValue(left);
_assertCalculationValue(right);
return SassCalculation.operateInternal(operator, left, right,
inLegacySassFunction: false, simplify: false);
inMinMax: false, simplify: false);
});

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

getJSClass(SassCalculation.operateInternal(
CalculationOperator.plus, SassNumber(1), SassNumber(1),
inLegacySassFunction: false, simplify: false))
inMinMax: false, simplify: false))
.injectSuperclass(jsClass);
return jsClass;
}();
Expand Down
53 changes: 10 additions & 43 deletions lib/src/parse/stylesheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2065,8 +2065,7 @@ abstract class StylesheetParser extends Parser {
/// produces a potentially slash-separated number.
bool _isSlashOperand(Expression expression) =>
expression is NumberExpression ||
(expression is CalculationExpression &&
!{'min', 'max', 'round', 'abs'}.contains(expression.name)) ||
expression is CalculationExpression ||
(expression is BinaryOperationExpression && expression.allowsSlash);

/// Consumes an expression that doesn't contain any top-level whitespace.
Expand Down Expand Up @@ -2653,64 +2652,32 @@ abstract class StylesheetParser extends Parser {
assert(scanner.peekChar() == $lparen);
switch (name) {
case "calc":
case "sqrt":
case "sin":
case "cos":
case "tan":
case "asin":
case "acos":
case "atan":
case "exp":
case "sign":
var arguments = _calculationArguments(1);
return CalculationExpression(name, arguments, scanner.spanFrom(start));

case "abs":
return _tryArgumentsCalculation(name, start, 1);

case "hypot":
var arguments = _calculationArguments();
return CalculationExpression(name, arguments, scanner.spanFrom(start));

case "min" || "max":
// min() and max() are parsed as calculations if possible, and otherwise
// are parsed as normal Sass functions.
return _tryArgumentsCalculation(name, start, null);

case "pow":
case "log":
case "atan2":
case "mod":
case "rem":
var arguments = _calculationArguments(2);
var beforeArguments = scanner.state;
List<Expression> arguments;
try {
arguments = _calculationArguments();
} on FormatException catch (_) {
scanner.state = beforeArguments;
return null;
}

return CalculationExpression(name, arguments, scanner.spanFrom(start));

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

case "round":
return _tryArgumentsCalculation(name, start, 3);

case _:
return null;
}
}

// Returns a CalculationExpression if the function can be parsed as a calculation,
// otherwise, returns null and the function is parsed as a normal Sass function.
CalculationExpression? _tryArgumentsCalculation(
String name, LineScannerState start, int? maxArgs) {
var beforeArguments = scanner.state;
try {
var arguments = _calculationArguments(maxArgs);
return CalculationExpression(name, arguments, scanner.spanFrom(start));
} on FormatException catch (_) {
scanner.state = beforeArguments;
return null;
}
}

/// Consumes and returns arguments for a calculation expression, including the
/// opening and closing parentheses.
///
Expand Down
Loading

0 comments on commit e70cd5a

Please sign in to comment.