Merge pull request #2067 from sass/revert-calc

Revert new calculation functions
This commit is contained in:
Natalie Weizenbaum 2023-08-17 12:02:51 -07:00 committed by GitHub
commit e70cd5a0a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 146 additions and 821 deletions

View File

@ -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`.

View File

@ -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",
@ -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

View File

@ -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),
@ -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();
@ -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 _cos = _function(
"cos",
r"$number",
(arguments) => SassNumber(math.cos(arguments[0]
.assertNumber("number")
.coerceValueToUnit("rad", "number"))));
final _sin = _singleArgumentMathFunc("sin", sin);
final _sin = _function(
"sin",
r"$number",
(arguments) => SassNumber(math.sin(arguments[0]
.assertNumber("number")
.coerceValueToUnit("rad", "number"))));
final _tan = _singleArgumentMathFunc("tan", tan);
final _tan = _function(
"tan",
r"$number",
(arguments) => SassNumber(math.tan(arguments[0]
.assertNumber("number")
.coerceValueToUnit("rad", "number"))));
///
/// Unit functions
@ -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)) {

View File

@ -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({
@ -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;
}();

View File

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

View File

@ -110,7 +110,6 @@ double fuzzyAssertRange(double number, int min, int max, [String? name]) {
///
/// [floored division]: https://en.wikipedia.org/wiki/Modulo_operation#Variants_of_the_definition
double moduloLikeSass(double num1, double num2) {
if (num2.isInfinite && num1.sign != num2.sign) return double.nan;
if (num2 > 0) return num1 % num2;
if (num2 == 0) return double.nan;
@ -119,78 +118,3 @@ double moduloLikeSass(double num1, double num2) {
var result = num1 % num2;
return result == 0 ? 0 : result + num2;
}
/// Returns the square root of [number].
SassNumber sqrt(SassNumber number) {
number.assertNoUnits("number");
return SassNumber(math.sqrt(number.value));
}
/// Returns the sine of [number].
SassNumber sin(SassNumber number) =>
SassNumber(math.sin(number.coerceValueToUnit("rad", "number")));
/// Returns the cosine of [number].
SassNumber cos(SassNumber number) =>
SassNumber(math.cos(number.coerceValueToUnit("rad", "number")));
/// Returns the tangent of [number].
SassNumber tan(SassNumber number) =>
SassNumber(math.tan(number.coerceValueToUnit("rad", "number")));
/// Returns the arctangent of [number].
SassNumber atan(SassNumber number) {
number.assertNoUnits("number");
return SassNumber.withUnits(math.atan(number.value) * 180 / math.pi,
numeratorUnits: ['deg']);
}
/// Returns the arcsine of [number].
SassNumber asin(SassNumber number) {
number.assertNoUnits("number");
return SassNumber.withUnits(math.asin(number.value) * 180 / math.pi,
numeratorUnits: ['deg']);
}
/// Returns the arccosine of [number]
SassNumber acos(SassNumber number) {
number.assertNoUnits("number");
return SassNumber.withUnits(math.acos(number.value) * 180 / math.pi,
numeratorUnits: ['deg']);
}
/// Returns the absolute value of [number].
SassNumber abs(SassNumber number) =>
SassNumber(number.value.abs()).coerceToMatch(number);
/// Returns the logarithm of [number] with respect to [base].
SassNumber log(SassNumber number, SassNumber? base) {
if (base != null) {
return SassNumber(math.log(number.value) / math.log(base.value));
}
return SassNumber(math.log(number.value));
}
/// Returns the value of [base] raised to the power of [exponent].
SassNumber pow(SassNumber base, SassNumber exponent) {
base.assertNoUnits("base");
exponent.assertNoUnits("exponent");
return SassNumber(math.pow(base.value, exponent.value));
}
/// Returns the arctangent for [y] and [x].
SassNumber atan2(SassNumber y, SassNumber x) {
return SassNumber.withUnits(
math.atan2(y.value, x.convertValueToMatch(y, 'x', 'y')) * 180 / math.pi,
numeratorUnits: ['deg']);
}
/// Extension methods to get the sign of the double's numerical value,
/// including positive and negative zero.
extension DoubleWithSignedZero on double {
double get signIncludingZero {
if (identical(this, -0.0)) return -1.0;
if (this == 0) return 1.0;
return sign;
}
}

View File

@ -2,16 +2,11 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:math' as math;
import 'package:meta/meta.dart';
import '../deprecation.dart';
import '../evaluation_context.dart';
import '../exception.dart';
import '../callable.dart';
import '../util/nullable.dart';
import '../util/number.dart' as number_lib;
import '../util/number.dart';
import '../utils.dart';
import '../value.dart';
import '../visitor/interface/value.dart';
@ -124,187 +119,6 @@ final class SassCalculation extends Value {
return SassCalculation._("max", args);
}
/// Creates a `hypot()` calculation with the given [arguments].
///
/// Each argument must be either a [SassNumber], a [SassCalculation], an
/// unquoted [SassString], a [CalculationOperation], or a
/// [CalculationInterpolation]. It must be passed at least one argument.
///
/// This automatically simplifies the calculation, so it may return a
/// [SassNumber] rather than a [SassCalculation]. It throws an exception if it
/// can determine that the calculation will definitely produce invalid CSS.
static Value hypot(Iterable<Object> arguments) {
var args = _simplifyArguments(arguments);
if (args.isEmpty) {
throw ArgumentError("hypot() must have at least one argument.");
}
_verifyCompatibleNumbers(args);
var subtotal = 0.0;
var first = args.first;
if (first is! SassNumber || first.hasUnit('%')) {
return SassCalculation._("hypot", args);
}
for (var i = 0; i < args.length; i++) {
var number = args.elementAt(i);
if (number is! SassNumber || !number.hasCompatibleUnits(first)) {
return SassCalculation._("hypot", args);
}
var value =
number.convertValueToMatch(first, "numbers[${i + 1}]", "numbers[1]");
subtotal += value * value;
}
return SassNumber.withUnits(math.sqrt(subtotal),
numeratorUnits: first.numeratorUnits,
denominatorUnits: first.denominatorUnits);
}
/// Creates a `sqrt()` calculation with the given [argument].
///
/// The [argument] must be either a [SassNumber], a [SassCalculation], an
/// unquoted [SassString], a [CalculationOperation], or a
/// [CalculationInterpolation].
///
/// This automatically simplifies the calculation, so it may return a
/// [SassNumber] rather than a [SassCalculation]. It throws an exception if it
/// can determine that the calculation will definitely produce invalid CSS.
static Value sqrt(Object argument) =>
_singleArgument("sqrt", argument, number_lib.sqrt, forbidUnits: true);
/// Creates a `sin()` calculation with the given [argument].
///
/// The [argument] must be either a [SassNumber], a [SassCalculation], an
/// unquoted [SassString], a [CalculationOperation], or a
/// [CalculationInterpolation].
///
/// This automatically simplifies the calculation, so it may return a
/// [SassNumber] rather than a [SassCalculation]. It throws an exception if it
/// can determine that the calculation will definitely produce invalid CSS.
static Value sin(Object argument) =>
_singleArgument("sin", argument, number_lib.sin);
/// Creates a `cos()` calculation with the given [argument].
///
/// The [argument] must be either a [SassNumber], a [SassCalculation], an
/// unquoted [SassString], a [CalculationOperation], or a
/// [CalculationInterpolation].
///
/// This automatically simplifies the calculation, so it may return a
/// [SassNumber] rather than a [SassCalculation]. It throws an exception if it
/// can determine that the calculation will definitely produce invalid CSS.
static Value cos(Object argument) =>
_singleArgument("cos", argument, number_lib.cos);
/// Creates a `tan()` calculation with the given [argument].
///
/// The [argument] must be either a [SassNumber], a [SassCalculation], an
/// unquoted [SassString], a [CalculationOperation], or a
/// [CalculationInterpolation].
///
/// This automatically simplifies the calculation, so it may return a
/// [SassNumber] rather than a [SassCalculation]. It throws an exception if it
/// can determine that the calculation will definitely produce invalid CSS.
static Value tan(Object argument) =>
_singleArgument("tan", argument, number_lib.tan);
/// Creates an `atan()` calculation with the given [argument].
///
/// The [argument] must be either a [SassNumber], a [SassCalculation], an
/// unquoted [SassString], a [CalculationOperation], or a
/// [CalculationInterpolation].
///
/// This automatically simplifies the calculation, so it may return a
/// [SassNumber] rather than a [SassCalculation]. It throws an exception if it
/// can determine that the calculation will definitely produce invalid CSS.
static Value atan(Object argument) =>
_singleArgument("atan", argument, number_lib.atan, forbidUnits: true);
/// Creates an `asin()` calculation with the given [argument].
///
/// The [argument] must be either a [SassNumber], a [SassCalculation], an
/// unquoted [SassString], a [CalculationOperation], or a
/// [CalculationInterpolation].
///
/// This automatically simplifies the calculation, so it may return a
/// [SassNumber] rather than a [SassCalculation]. It throws an exception if it
/// can determine that the calculation will definitely produce invalid CSS.
static Value asin(Object argument) =>
_singleArgument("asin", argument, number_lib.asin, forbidUnits: true);
/// Creates an `acos()` calculation with the given [argument].
///
/// The [argument] must be either a [SassNumber], a [SassCalculation], an
/// unquoted [SassString], a [CalculationOperation], or a
/// [CalculationInterpolation].
///
/// This automatically simplifies the calculation, so it may return a
/// [SassNumber] rather than a [SassCalculation]. It throws an exception if it
/// can determine that the calculation will definitely produce invalid CSS.
static Value acos(Object argument) =>
_singleArgument("acos", argument, number_lib.acos, forbidUnits: true);
/// Creates an `abs()` calculation with the given [argument].
///
/// The [argument] must be either a [SassNumber], a [SassCalculation], an
/// unquoted [SassString], a [CalculationOperation], or a
/// [CalculationInterpolation].
///
/// This automatically simplifies the calculation, so it may return a
/// [SassNumber] rather than a [SassCalculation]. It throws an exception if it
/// can determine that the calculation will definitely produce invalid CSS.
static Value abs(Object argument) {
argument = _simplify(argument);
if (argument is! SassNumber) return SassCalculation._("abs", [argument]);
if (argument.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($argument)"
"\n"
"To emit a CSS abs() now: abs(#{$argument})\n"
"More info: https://sass-lang.com/d/abs-percent",
Deprecation.absPercent);
}
return number_lib.abs(argument);
}
/// Creates an `exp()` calculation with the given [argument].
///
/// The [argument] must be either a [SassNumber], a [SassCalculation], an
/// unquoted [SassString], a [CalculationOperation], or a
/// [CalculationInterpolation].
///
/// This automatically simplifies the calculation, so it may return a
/// [SassNumber] rather than a [SassCalculation]. It throws an exception if it
/// can determine that the calculation will definitely produce invalid CSS.
static Value exp(Object argument) {
argument = _simplify(argument);
if (argument is! SassNumber) {
return SassCalculation._("exp", [argument]);
}
argument.assertNoUnits();
return number_lib.pow(SassNumber(math.e), argument);
}
/// Creates a `sign()` calculation with the given [argument].
///
/// The [argument] must be either a [SassNumber], a [SassCalculation], an
/// unquoted [SassString], a [CalculationOperation], or a
/// [CalculationInterpolation].
///
/// This automatically simplifies the calculation, so it may return a
/// [SassNumber] rather than a [SassCalculation]. It throws an exception if it
/// can determine that the calculation will definitely produce invalid CSS.
static Value sign(Object argument) {
argument = _simplify(argument);
return switch (argument) {
SassNumber(value: double(isNaN: true) || 0) => argument,
SassNumber arg when !arg.hasUnit('%') =>
SassNumber(arg.value.sign).coerceToMatch(argument),
_ => SassCalculation._("sign", [argument]),
};
}
/// Creates a `clamp()` calculation with the given [min], [value], and [max].
///
/// Each argument must be either a [SassNumber], a [SassCalculation], an
@ -343,255 +157,6 @@ final class SassCalculation extends Value {
return SassCalculation._("clamp", args);
}
/// Creates a `pow()` calculation with the given [base] and [exponent].
///
/// Each argument must be either a [SassNumber], a [SassCalculation], an
/// unquoted [SassString], a [CalculationOperation], or a
/// [CalculationInterpolation].
///
/// This automatically simplifies the calculation, so it may return a
/// [SassNumber] rather than a [SassCalculation]. It throws an exception if it
/// can determine that the calculation will definitely produce invalid CSS.
///
/// This may be passed fewer than two arguments, but only if one of the
/// arguments is an unquoted `var()` string.
static Value pow(Object base, Object? exponent) {
var args = [base, if (exponent != null) exponent];
_verifyLength(args, 2);
base = _simplify(base);
exponent = exponent.andThen(_simplify);
if (base is! SassNumber || exponent is! SassNumber) {
return SassCalculation._("pow", args);
}
base.assertNoUnits();
exponent.assertNoUnits();
return number_lib.pow(base, exponent);
}
/// Creates a `log()` calculation with the given [number] and [base].
///
/// Each argument must be either a [SassNumber], a [SassCalculation], an
/// unquoted [SassString], a [CalculationOperation], or a
/// [CalculationInterpolation].
///
/// This automatically simplifies the calculation, so it may return a
/// [SassNumber] rather than a [SassCalculation]. It throws an exception if it
/// can determine that the calculation will definitely produce invalid CSS.
///
/// If arguments contains exactly a single argument,
/// the base is set to `math.e` by default.
static Value log(Object number, Object? base) {
number = _simplify(number);
base = base.andThen(_simplify);
var args = [number, if (base != null) base];
if (number is! SassNumber || (base != null && base is! SassNumber)) {
return SassCalculation._("log", args);
}
number.assertNoUnits();
if (base is SassNumber) {
base.assertNoUnits();
return number_lib.log(number, base);
}
return number_lib.log(number, null);
}
/// Creates a `atan2()` calculation for [y] and [x].
///
/// Each argument must be either a [SassNumber], a [SassCalculation], an
/// unquoted [SassString], a [CalculationOperation], or a
/// [CalculationInterpolation].
///
/// This automatically simplifies the calculation, so it may return a
/// [SassNumber] rather than a [SassCalculation]. It throws an exception if it
/// can determine that the calculation will definitely produce invalid CSS.
///
/// This may be passed fewer than two arguments, but only if one of the
/// arguments is an unquoted `var()` string.
static Value atan2(Object y, Object? x) {
y = _simplify(y);
x = x.andThen(_simplify);
var args = [y, if (x != null) x];
_verifyLength(args, 2);
_verifyCompatibleNumbers(args);
if (y is! SassNumber ||
x is! SassNumber ||
y.hasUnit('%') ||
x.hasUnit('%') ||
!y.hasCompatibleUnits(x)) {
return SassCalculation._("atan2", args);
}
return number_lib.atan2(y, x);
}
/// Creates a `rem()` calculation with the given [dividend] and [modulus].
///
/// Each argument must be either a [SassNumber], a [SassCalculation], an
/// unquoted [SassString], a [CalculationOperation], or a
/// [CalculationInterpolation].
///
/// This automatically simplifies the calculation, so it may return a
/// [SassNumber] rather than a [SassCalculation]. It throws an exception if it
/// can determine that the calculation will definitely produce invalid CSS.
///
/// This may be passed fewer than two arguments, but only if one of the
/// arguments is an unquoted `var()` string.
static Value rem(Object dividend, Object? modulus) {
dividend = _simplify(dividend);
modulus = modulus.andThen(_simplify);
var args = [dividend, if (modulus != null) modulus];
_verifyLength(args, 2);
_verifyCompatibleNumbers(args);
if (dividend is! SassNumber ||
modulus is! SassNumber ||
!dividend.hasCompatibleUnits(modulus)) {
return SassCalculation._("rem", args);
}
var result = dividend.modulo(modulus);
if (modulus.value.signIncludingZero != dividend.value.signIncludingZero) {
if (modulus.value.isInfinite) return dividend;
if (result.value == 0) {
return result.unaryMinus();
}
return result.minus(modulus);
}
return result;
}
/// Creates a `mod()` calculation with the given [dividend] and [modulus].
///
/// Each argument must be either a [SassNumber], a [SassCalculation], an
/// unquoted [SassString], a [CalculationOperation], or a
/// [CalculationInterpolation].
///
/// This automatically simplifies the calculation, so it may return a
/// [SassNumber] rather than a [SassCalculation]. It throws an exception if it
/// can determine that the calculation will definitely produce invalid CSS.
///
/// This may be passed fewer than two arguments, but only if one of the
/// arguments is an unquoted `var()` string.
static Value mod(Object dividend, Object? modulus) {
dividend = _simplify(dividend);
modulus = modulus.andThen(_simplify);
var args = [dividend, if (modulus != null) modulus];
_verifyLength(args, 2);
_verifyCompatibleNumbers(args);
if (dividend is! SassNumber ||
modulus is! SassNumber ||
!dividend.hasCompatibleUnits(modulus)) {
return SassCalculation._("mod", args);
}
return dividend.modulo(modulus);
}
/// Creates a `round()` calculation with the given [strategyOrNumber], [numberOrStep], and [step].
/// Strategy must be either nearest, up, down or to-zero.
///
/// Number and step must be either a [SassNumber], a [SassCalculation], an
/// unquoted [SassString], a [CalculationOperation], or a
/// [CalculationInterpolation].
///
/// This automatically simplifies the calculation, so it may return a
/// [SassNumber] rather than a [SassCalculation]. It throws an exception if it
/// can determine that the calculation will definitely produce invalid CSS.
///
/// This may be passed fewer than two arguments, but only if one of the
/// arguments is an unquoted `var()` string.
static Value round(Object strategyOrNumber,
[Object? numberOrStep, Object? step]) {
switch ((
_simplify(strategyOrNumber),
numberOrStep.andThen(_simplify),
step.andThen(_simplify)
)) {
case (SassNumber number, null, null):
return _matchUnits(number.value.round().toDouble(), number);
case (SassNumber number, SassNumber step, null)
when !number.hasCompatibleUnits(step):
_verifyCompatibleNumbers([number, step]);
return SassCalculation._("round", [number, step]);
case (SassNumber number, SassNumber step, null):
_verifyCompatibleNumbers([number, step]);
return _roundWithStep('nearest', number, step);
case (
SassString(text: 'nearest' || 'up' || 'down' || 'to-zero') &&
var strategy,
SassNumber number,
SassNumber step
)
when !number.hasCompatibleUnits(step):
_verifyCompatibleNumbers([number, step]);
return SassCalculation._("round", [strategy, number, step]);
case (
SassString(text: 'nearest' || 'up' || 'down' || 'to-zero') &&
var strategy,
SassNumber number,
SassNumber step
):
_verifyCompatibleNumbers([number, step]);
return _roundWithStep(strategy.text, number, step);
case (
SassString(text: 'nearest' || 'up' || 'down' || 'to-zero') &&
var strategy,
(SassString() || CalculationInterpolation()) && var rest?,
null
):
return SassCalculation._("round", [strategy, rest]);
case (
SassString(text: 'nearest' || 'up' || 'down' || 'to-zero'),
_?,
null
):
throw SassScriptException("If strategy is not null, step is required.");
case (
SassString(text: 'nearest' || 'up' || 'down' || 'to-zero'),
null,
null
):
throw SassScriptException(
"Number to round and step arguments are required.");
case (
(SassString() || CalculationInterpolation()) && var rest,
null,
null
):
return SassCalculation._("round", [rest]);
case (var number, null, null):
throw SassScriptException(
"Single argument $number expected to be simplifiable.");
case (var number, var step?, null):
return SassCalculation._("round", [number, step]);
case (
(SassString(text: 'nearest' || 'up' || 'down' || 'to-zero') ||
SassString(isVar: true)) &&
var strategy,
var number?,
var step?
):
return SassCalculation._("round", [strategy, number, step]);
case (_, _?, _?):
throw SassScriptException(
"$strategyOrNumber must be either nearest, up, down or to-zero.");
case (_, null, _?):
// TODO(pamelalozano): Get rid of this case once dart-lang/sdk#52908 is solved.
// ignore: unreachable_switch_case
case (_, _, _):
throw SassScriptException("Invalid parameters.");
}
}
/// Creates and simplifies a [CalculationOperation] with the given [operator],
/// [left], and [right].
///
@ -603,12 +168,11 @@ final class SassCalculation extends Value {
/// a [CalculationInterpolation].
static Object operate(
CalculationOperator operator, Object left, Object right) =>
operateInternal(operator, left, right,
inLegacySassFunction: false, simplify: true);
operateInternal(operator, left, right, inMinMax: false, simplify: true);
/// Like [operate], but with the internal-only [inLegacySassFunction] parameter.
/// Like [operate], but with the internal-only [inMinMax] parameter.
///
/// If [inLegacySassFunction] is `true`, this allows unitless numbers to be added and
/// If [inMinMax] is `true`, this allows unitless numbers to be added and
/// subtracted with numbers with units, for backwards-compatibility with the
/// old global `min()` and `max()` functions.
///
@ -616,7 +180,7 @@ final class SassCalculation extends Value {
@internal
static Object operateInternal(
CalculationOperator operator, Object left, Object right,
{required bool inLegacySassFunction, required bool simplify}) {
{required bool inMinMax, required bool simplify}) {
if (!simplify) return CalculationOperation._(operator, left, right);
left = _simplify(left);
right = _simplify(right);
@ -624,7 +188,7 @@ final class SassCalculation extends Value {
if (operator case CalculationOperator.plus || CalculationOperator.minus) {
if (left is SassNumber &&
right is SassNumber &&
(inLegacySassFunction
(inMinMax
? left.isComparableTo(right)
: left.hasCompatibleUnits(right))) {
return operator == CalculationOperator.plus
@ -634,7 +198,7 @@ final class SassCalculation extends Value {
_verifyCompatibleNumbers([left, right]);
if (right is SassNumber && number_lib.fuzzyLessThan(right.value, 0)) {
if (right is SassNumber && fuzzyLessThan(right.value, 0)) {
right = right.times(SassNumber(-1));
operator = operator == CalculationOperator.plus
? CalculationOperator.minus
@ -655,70 +219,6 @@ final class SassCalculation extends Value {
/// simplification.
SassCalculation._(this.name, this.arguments);
// Returns [value] coerced to [number]'s units.
static SassNumber _matchUnits(double value, SassNumber number) =>
SassNumber.withUnits(value,
numeratorUnits: number.numeratorUnits,
denominatorUnits: number.denominatorUnits);
/// Returns a rounded [number] based on a selected rounding [strategy],
/// to the nearest integer multiple of [step].
static SassNumber _roundWithStep(
String strategy, SassNumber number, SassNumber step) {
if (!{'nearest', 'up', 'down', 'to-zero'}.contains(strategy)) {
throw ArgumentError(
"$strategy must be either nearest, up, down or to-zero.");
}
if (number.value.isInfinite && step.value.isInfinite ||
step.value == 0 ||
number.value.isNaN ||
step.value.isNaN) {
return _matchUnits(double.nan, number);
}
if (number.value.isInfinite) return number;
if (step.value.isInfinite) {
return switch ((strategy, number.value)) {
(_, 0) => number,
('nearest' || 'to-zero', > 0) => _matchUnits(0.0, number),
('nearest' || 'to-zero', _) => _matchUnits(-0.0, number),
('up', > 0) => _matchUnits(double.infinity, number),
('up', _) => _matchUnits(-0.0, number),
('down', < 0) => _matchUnits(-double.infinity, number),
('down', _) => _matchUnits(0, number),
(_, _) => throw UnsupportedError("Invalid argument: $strategy.")
};
}
var stepWithNumberUnit = step.convertValueToMatch(number);
return switch (strategy) {
'nearest' => _matchUnits(
(number.value / stepWithNumberUnit).round() * stepWithNumberUnit,
number),
'up' => _matchUnits(
(step.value < 0
? (number.value / stepWithNumberUnit).floor()
: (number.value / stepWithNumberUnit).ceil()) *
stepWithNumberUnit,
number),
'down' => _matchUnits(
(step.value < 0
? (number.value / stepWithNumberUnit).ceil()
: (number.value / stepWithNumberUnit).floor()) *
stepWithNumberUnit,
number),
'to-zero' => number.value < 0
? _matchUnits(
(number.value / stepWithNumberUnit).ceil() * stepWithNumberUnit,
number)
: _matchUnits(
(number.value / stepWithNumberUnit).floor() * stepWithNumberUnit,
number),
_ => _matchUnits(double.nan, number)
};
}
/// Returns an unmodifiable list of [args], with each argument simplified.
static List<Object> _simplifyArguments(Iterable<Object> args) =>
List.unmodifiable(args.map(_simplify));
@ -779,21 +279,6 @@ final class SassCalculation extends Value {
"${pluralize('was', args.length, plural: 'were')} passed.");
}
/// Returns a [Callable] named [name] that calls a single argument
/// math function.
///
/// If [forbidUnits] is `true` it will throw an error if [argument] has units.
static Value _singleArgument(
String name, Object argument, SassNumber mathFunc(SassNumber value),
{bool forbidUnits = false}) {
argument = _simplify(argument);
if (argument is! SassNumber) {
return SassCalculation._(name, [argument]);
}
if (forbidUnits) argument.assertNoUnits();
return mathFunc(argument);
}
/// @nodoc
@internal
T accept<T>(ValueVisitor<T> visitor) => visitor.visitCalculation(this);

View File

@ -710,7 +710,7 @@ abstract class SassNumber extends Value {
/// @nodoc
@internal
SassNumber modulo(Value other) {
Value modulo(Value other) {
if (other is SassNumber) {
return withValue(_coerceUnits(other, moduloLikeSass));
}

View File

@ -98,7 +98,7 @@ class UnitlessSassNumber extends SassNumber {
return super.lessThanOrEquals(other);
}
SassNumber modulo(Value other) {
Value modulo(Value other) {
if (other is SassNumber) {
return other.withValue(moduloLikeSass(value, other.value));
}

View File

@ -2300,8 +2300,7 @@ final class _EvaluateVisitor
var arguments = [
for (var argument in node.arguments)
await _visitCalculationValue(argument,
inLegacySassFunction:
{'min', 'max', 'round', 'abs'}.contains(node.name))
inMinMax: node.name == 'min' || node.name == 'max')
];
if (_inSupportsDeclaration) {
return SassCalculation.unsimplified(node.name, arguments);
@ -2310,31 +2309,8 @@ final class _EvaluateVisitor
try {
return switch (node.name) {
"calc" => SassCalculation.calc(arguments[0]),
"sqrt" => SassCalculation.sqrt(arguments[0]),
"sin" => SassCalculation.sin(arguments[0]),
"cos" => SassCalculation.cos(arguments[0]),
"tan" => SassCalculation.tan(arguments[0]),
"asin" => SassCalculation.asin(arguments[0]),
"acos" => SassCalculation.acos(arguments[0]),
"atan" => SassCalculation.atan(arguments[0]),
"abs" => SassCalculation.abs(arguments[0]),
"exp" => SassCalculation.exp(arguments[0]),
"sign" => SassCalculation.sign(arguments[0]),
"min" => SassCalculation.min(arguments),
"max" => SassCalculation.max(arguments),
"hypot" => SassCalculation.hypot(arguments),
"pow" =>
SassCalculation.pow(arguments[0], arguments.elementAtOrNull(1)),
"atan2" =>
SassCalculation.atan2(arguments[0], arguments.elementAtOrNull(1)),
"log" =>
SassCalculation.log(arguments[0], arguments.elementAtOrNull(1)),
"mod" =>
SassCalculation.mod(arguments[0], arguments.elementAtOrNull(1)),
"rem" =>
SassCalculation.rem(arguments[0], arguments.elementAtOrNull(1)),
"round" => SassCalculation.round(arguments[0],
arguments.elementAtOrNull(1), arguments.elementAtOrNull(2)),
"clamp" => SassCalculation.clamp(arguments[0],
arguments.elementAtOrNull(1), arguments.elementAtOrNull(2)),
_ => throw UnsupportedError('Unknown calculation name "${node.name}".')
@ -2343,9 +2319,7 @@ final class _EvaluateVisitor
// The simplification logic in the [SassCalculation] static methods will
// throw an error if the arguments aren't compatible, but we have access
// to the original spans so we can throw a more informative error.
if (error.message.contains("compatible")) {
_verifyCompatibleNumbers(arguments, node.arguments);
}
_verifyCompatibleNumbers(arguments, node.arguments);
throwWithTrace(_exception(error.message, node.span), error, stackTrace);
}
}
@ -2387,15 +2361,14 @@ final class _EvaluateVisitor
/// Evaluates [node] as a component of a calculation.
///
/// If [inLegacySassFunction] is `true`, this allows unitless numbers to be added and
/// If [inMinMax] is `true`, this allows unitless numbers to be added and
/// subtracted with numbers with units, for backwards-compatibility with the
/// old global `min()`, `max()`, `round()`, and `abs()` functions.
/// old global `min()` and `max()` functions.
Future<Object> _visitCalculationValue(Expression node,
{required bool inLegacySassFunction}) async {
{required bool inMinMax}) async {
switch (node) {
case ParenthesizedExpression(expression: var inner):
var result = await _visitCalculationValue(inner,
inLegacySassFunction: inLegacySassFunction);
var result = await _visitCalculationValue(inner, inMinMax: inMinMax);
return inner is FunctionExpression &&
inner.name.toLowerCase() == 'var' &&
result is SassString &&
@ -2426,11 +2399,9 @@ final class _EvaluateVisitor
node,
() async => SassCalculation.operateInternal(
_binaryOperatorToCalculationOperator(operator),
await _visitCalculationValue(left,
inLegacySassFunction: inLegacySassFunction),
await _visitCalculationValue(right,
inLegacySassFunction: inLegacySassFunction),
inLegacySassFunction: inLegacySassFunction,
await _visitCalculationValue(left, inMinMax: inMinMax),
await _visitCalculationValue(right, inMinMax: inMinMax),
inMinMax: inMinMax,
simplify: !_inSupportsDeclaration));
case _:

View File

@ -5,7 +5,7 @@
// DO NOT EDIT. This file was generated from async_evaluate.dart.
// See tool/grind/synchronize.dart for details.
//
// Checksum: e4d8cd913b88b73d11417b5ccda03a6313a5bb78
// Checksum: 6eb7f76735562eba91e9460af796b269b3b0aaf7
//
// ignore_for_file: unused_import
@ -2282,8 +2282,7 @@ final class _EvaluateVisitor
var arguments = [
for (var argument in node.arguments)
_visitCalculationValue(argument,
inLegacySassFunction:
{'min', 'max', 'round', 'abs'}.contains(node.name))
inMinMax: node.name == 'min' || node.name == 'max')
];
if (_inSupportsDeclaration) {
return SassCalculation.unsimplified(node.name, arguments);
@ -2292,31 +2291,8 @@ final class _EvaluateVisitor
try {
return switch (node.name) {
"calc" => SassCalculation.calc(arguments[0]),
"sqrt" => SassCalculation.sqrt(arguments[0]),
"sin" => SassCalculation.sin(arguments[0]),
"cos" => SassCalculation.cos(arguments[0]),
"tan" => SassCalculation.tan(arguments[0]),
"asin" => SassCalculation.asin(arguments[0]),
"acos" => SassCalculation.acos(arguments[0]),
"atan" => SassCalculation.atan(arguments[0]),
"abs" => SassCalculation.abs(arguments[0]),
"exp" => SassCalculation.exp(arguments[0]),
"sign" => SassCalculation.sign(arguments[0]),
"min" => SassCalculation.min(arguments),
"max" => SassCalculation.max(arguments),
"hypot" => SassCalculation.hypot(arguments),
"pow" =>
SassCalculation.pow(arguments[0], arguments.elementAtOrNull(1)),
"atan2" =>
SassCalculation.atan2(arguments[0], arguments.elementAtOrNull(1)),
"log" =>
SassCalculation.log(arguments[0], arguments.elementAtOrNull(1)),
"mod" =>
SassCalculation.mod(arguments[0], arguments.elementAtOrNull(1)),
"rem" =>
SassCalculation.rem(arguments[0], arguments.elementAtOrNull(1)),
"round" => SassCalculation.round(arguments[0],
arguments.elementAtOrNull(1), arguments.elementAtOrNull(2)),
"clamp" => SassCalculation.clamp(arguments[0],
arguments.elementAtOrNull(1), arguments.elementAtOrNull(2)),
_ => throw UnsupportedError('Unknown calculation name "${node.name}".')
@ -2325,9 +2301,7 @@ final class _EvaluateVisitor
// The simplification logic in the [SassCalculation] static methods will
// throw an error if the arguments aren't compatible, but we have access
// to the original spans so we can throw a more informative error.
if (error.message.contains("compatible")) {
_verifyCompatibleNumbers(arguments, node.arguments);
}
_verifyCompatibleNumbers(arguments, node.arguments);
throwWithTrace(_exception(error.message, node.span), error, stackTrace);
}
}
@ -2369,15 +2343,13 @@ final class _EvaluateVisitor
/// Evaluates [node] as a component of a calculation.
///
/// If [inLegacySassFunction] is `true`, this allows unitless numbers to be added and
/// If [inMinMax] is `true`, this allows unitless numbers to be added and
/// subtracted with numbers with units, for backwards-compatibility with the
/// old global `min()`, `max()`, `round()`, and `abs()` functions.
Object _visitCalculationValue(Expression node,
{required bool inLegacySassFunction}) {
/// old global `min()` and `max()` functions.
Object _visitCalculationValue(Expression node, {required bool inMinMax}) {
switch (node) {
case ParenthesizedExpression(expression: var inner):
var result = _visitCalculationValue(inner,
inLegacySassFunction: inLegacySassFunction);
var result = _visitCalculationValue(inner, inMinMax: inMinMax);
return inner is FunctionExpression &&
inner.name.toLowerCase() == 'var' &&
result is SassString &&
@ -2408,11 +2380,9 @@ final class _EvaluateVisitor
node,
() => SassCalculation.operateInternal(
_binaryOperatorToCalculationOperator(operator),
_visitCalculationValue(left,
inLegacySassFunction: inLegacySassFunction),
_visitCalculationValue(right,
inLegacySassFunction: inLegacySassFunction),
inLegacySassFunction: inLegacySassFunction,
_visitCalculationValue(left, inMinMax: inMinMax),
_visitCalculationValue(right, inMinMax: inMinMax),
inMinMax: inMinMax,
simplify: !_inSupportsDeclaration));
case _:

View File

@ -1,3 +1,7 @@
## 8.2.0
* No user-visible changes.
## 8.1.1
* No user-visible changes.

View File

@ -2,7 +2,7 @@ name: sass_api
# Note: Every time we add a new Sass AST node, we need to bump the *major*
# version because it's a breaking change for anyone who's implementing the
# visitor interface(s).
version: 8.1.1
version: 8.2.0
description: Additional APIs for Dart Sass.
homepage: https://github.com/sass/dart-sass
@ -10,7 +10,7 @@ environment:
sdk: ">=3.0.0 <4.0.0"
dependencies:
sass: 1.65.1
sass: 1.66.0
dev_dependencies:
dartdoc: ^5.0.0

View File

@ -1,5 +1,5 @@
name: sass
version: 1.65.1
version: 1.66.0
description: A Sass implementation in Dart.
homepage: https://github.com/sass/dart-sass