Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert new calculation functions #2067

Merged
merged 3 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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