mirror of
https://github.com/danog/dart-sass.git
synced 2024-11-26 12:14:42 +01:00
Calc functions implementation (#1970)
* Sqrt calc function implementation * Pow calc function * Calc round function * Corrections and rename parameter to inLegacySassFunction * Unary calc functions * Arguments with no units correction * Refactor round function * Update modulo function to return SassNumber and corrections * Round accepting fake units fix * Up/Down round strategy fix * Return incompatible message fix
This commit is contained in:
parent
4c3bd0e89f
commit
e4c8cd67e8
11
CHANGELOG.md
11
CHANGELOG.md
@ -1,3 +1,14 @@
|
||||
## 1.65.0
|
||||
|
||||
* All functions defined in CSS Values and Units 4 are now parsed as calculation
|
||||
objects: `round()`, `mod()`, `rem()`, `sin()`, `cos()`, `tan()`, `asin()`,
|
||||
`acos()`, `atan()`, `atan2()`, `pow()`, `sqrt()`, `hypot()`, `log()`, `exp()`,
|
||||
`abs()`, and `sign()`.
|
||||
|
||||
* Deprecate explicitly passing the `%` unit to the global `abs()` function. In
|
||||
future releases, this will emit a CSS abs() function to be resolved by the
|
||||
browser. This deprecation is named `abs-percent`.
|
||||
|
||||
## 1.64.3
|
||||
|
||||
### Dart API
|
||||
|
@ -40,6 +40,10 @@ 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",
|
||||
@ -49,11 +53,76 @@ 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
|
||||
|
@ -55,6 +55,11 @@ enum Deprecation {
|
||||
deprecatedIn: '1.56.0',
|
||||
description: 'Passing invalid units to built-in functions.'),
|
||||
|
||||
/// Deprecation for passing percentages to the Sass abs() function.
|
||||
absPercent('abs-percent',
|
||||
deprecatedIn: '1.64.0',
|
||||
description: 'Passing percentages to the Sass abs() function.'),
|
||||
|
||||
duplicateVariableFlags('duplicate-var-flags',
|
||||
deprecatedIn: '1.62.0',
|
||||
description:
|
||||
|
@ -12,6 +12,7 @@ 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.
|
||||
@ -132,87 +133,32 @@ 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");
|
||||
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));
|
||||
}
|
||||
return pow(base, exponent);
|
||||
});
|
||||
|
||||
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));
|
||||
}
|
||||
});
|
||||
final _sqrt = _singleArgumentMathFunc("sqrt", sqrt);
|
||||
|
||||
///
|
||||
/// Trigonometric functions
|
||||
///
|
||||
|
||||
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 _acos = _singleArgumentMathFunc("acos", acos);
|
||||
|
||||
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 _asin = _singleArgumentMathFunc("asin", asin);
|
||||
|
||||
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 _atan = _singleArgumentMathFunc("atan", atan);
|
||||
|
||||
final _atan2 = _function("atan2", r"$y, $x", (arguments) {
|
||||
var y = arguments[0].assertNumber("y");
|
||||
var x = arguments[1].assertNumber("x");
|
||||
return SassNumber.withUnits(
|
||||
math.atan2(y.value, x.convertValueToMatch(y, 'x', 'y')) * 180 / math.pi,
|
||||
numeratorUnits: ['deg']);
|
||||
return atan2(y, x);
|
||||
});
|
||||
|
||||
final _cos = _function(
|
||||
"cos",
|
||||
r"$number",
|
||||
(arguments) => SassNumber(math.cos(arguments[0]
|
||||
.assertNumber("number")
|
||||
.coerceValueToUnit("rad", "number"))));
|
||||
final _cos = _singleArgumentMathFunc("cos", cos);
|
||||
|
||||
final _sin = _function(
|
||||
"sin",
|
||||
r"$number",
|
||||
(arguments) => SassNumber(math.sin(arguments[0]
|
||||
.assertNumber("number")
|
||||
.coerceValueToUnit("rad", "number"))));
|
||||
final _sin = _singleArgumentMathFunc("sin", sin);
|
||||
|
||||
final _tan = _function(
|
||||
"tan",
|
||||
r"$number",
|
||||
(arguments) => SassNumber(math.tan(arguments[0]
|
||||
.assertNumber("number")
|
||||
.coerceValueToUnit("rad", "number"))));
|
||||
final _tan = _singleArgumentMathFunc("tan", tan);
|
||||
|
||||
///
|
||||
/// Unit functions
|
||||
@ -288,6 +234,16 @@ 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)) {
|
||||
|
@ -93,7 +93,7 @@ final JSClass calculationOperationClass = () {
|
||||
_assertCalculationValue(left);
|
||||
_assertCalculationValue(right);
|
||||
return SassCalculation.operateInternal(operator, left, right,
|
||||
inMinMax: false, simplify: false);
|
||||
inLegacySassFunction: false, simplify: false);
|
||||
});
|
||||
|
||||
jsClass.defineMethods({
|
||||
@ -109,7 +109,7 @@ final JSClass calculationOperationClass = () {
|
||||
|
||||
getJSClass(SassCalculation.operateInternal(
|
||||
CalculationOperator.plus, SassNumber(1), SassNumber(1),
|
||||
inMinMax: false, simplify: false))
|
||||
inLegacySassFunction: false, simplify: false))
|
||||
.injectSuperclass(jsClass);
|
||||
return jsClass;
|
||||
}();
|
||||
|
@ -2065,7 +2065,8 @@ abstract class StylesheetParser extends Parser {
|
||||
/// produces a potentially slash-separated number.
|
||||
bool _isSlashOperand(Expression expression) =>
|
||||
expression is NumberExpression ||
|
||||
expression is CalculationExpression ||
|
||||
(expression is CalculationExpression &&
|
||||
!{'min', 'max', 'round', 'abs'}.contains(expression.name)) ||
|
||||
(expression is BinaryOperationExpression && expression.allowsSlash);
|
||||
|
||||
/// Consumes an expression that doesn't contain any top-level whitespace.
|
||||
@ -2652,32 +2653,64 @@ 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.
|
||||
var beforeArguments = scanner.state;
|
||||
List<Expression> arguments;
|
||||
try {
|
||||
arguments = _calculationArguments();
|
||||
} on FormatException catch (_) {
|
||||
scanner.state = beforeArguments;
|
||||
return null;
|
||||
}
|
||||
return _tryArgumentsCalculation(name, start, 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.
|
||||
///
|
||||
|
@ -110,6 +110,7 @@ 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;
|
||||
|
||||
@ -118,3 +119,78 @@ 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;
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,16 @@
|
||||
// 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';
|
||||
import '../util/number.dart' as number_lib;
|
||||
import '../utils.dart';
|
||||
import '../value.dart';
|
||||
import '../visitor/interface/value.dart';
|
||||
@ -119,6 +124,187 @@ 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
|
||||
@ -157,6 +343,255 @@ 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].
|
||||
///
|
||||
@ -168,11 +603,12 @@ final class SassCalculation extends Value {
|
||||
/// a [CalculationInterpolation].
|
||||
static Object operate(
|
||||
CalculationOperator operator, Object left, Object right) =>
|
||||
operateInternal(operator, left, right, inMinMax: false, simplify: true);
|
||||
operateInternal(operator, left, right,
|
||||
inLegacySassFunction: false, simplify: true);
|
||||
|
||||
/// Like [operate], but with the internal-only [inMinMax] parameter.
|
||||
/// Like [operate], but with the internal-only [inLegacySassFunction] parameter.
|
||||
///
|
||||
/// If [inMinMax] is `true`, this allows unitless numbers to be added and
|
||||
/// If [inLegacySassFunction] 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.
|
||||
///
|
||||
@ -180,7 +616,7 @@ final class SassCalculation extends Value {
|
||||
@internal
|
||||
static Object operateInternal(
|
||||
CalculationOperator operator, Object left, Object right,
|
||||
{required bool inMinMax, required bool simplify}) {
|
||||
{required bool inLegacySassFunction, required bool simplify}) {
|
||||
if (!simplify) return CalculationOperation._(operator, left, right);
|
||||
left = _simplify(left);
|
||||
right = _simplify(right);
|
||||
@ -188,7 +624,7 @@ final class SassCalculation extends Value {
|
||||
if (operator case CalculationOperator.plus || CalculationOperator.minus) {
|
||||
if (left is SassNumber &&
|
||||
right is SassNumber &&
|
||||
(inMinMax
|
||||
(inLegacySassFunction
|
||||
? left.isComparableTo(right)
|
||||
: left.hasCompatibleUnits(right))) {
|
||||
return operator == CalculationOperator.plus
|
||||
@ -198,7 +634,7 @@ final class SassCalculation extends Value {
|
||||
|
||||
_verifyCompatibleNumbers([left, right]);
|
||||
|
||||
if (right is SassNumber && fuzzyLessThan(right.value, 0)) {
|
||||
if (right is SassNumber && number_lib.fuzzyLessThan(right.value, 0)) {
|
||||
right = right.times(SassNumber(-1));
|
||||
operator = operator == CalculationOperator.plus
|
||||
? CalculationOperator.minus
|
||||
@ -219,6 +655,70 @@ 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));
|
||||
@ -279,6 +779,21 @@ 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);
|
||||
|
@ -710,7 +710,7 @@ abstract class SassNumber extends Value {
|
||||
|
||||
/// @nodoc
|
||||
@internal
|
||||
Value modulo(Value other) {
|
||||
SassNumber modulo(Value other) {
|
||||
if (other is SassNumber) {
|
||||
return withValue(_coerceUnits(other, moduloLikeSass));
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ class UnitlessSassNumber extends SassNumber {
|
||||
return super.lessThanOrEquals(other);
|
||||
}
|
||||
|
||||
Value modulo(Value other) {
|
||||
SassNumber modulo(Value other) {
|
||||
if (other is SassNumber) {
|
||||
return other.withValue(moduloLikeSass(value, other.value));
|
||||
}
|
||||
|
@ -2300,7 +2300,8 @@ final class _EvaluateVisitor
|
||||
var arguments = [
|
||||
for (var argument in node.arguments)
|
||||
await _visitCalculationValue(argument,
|
||||
inMinMax: node.name == 'min' || node.name == 'max')
|
||||
inLegacySassFunction:
|
||||
{'min', 'max', 'round', 'abs'}.contains(node.name))
|
||||
];
|
||||
if (_inSupportsDeclaration) {
|
||||
return SassCalculation.unsimplified(node.name, arguments);
|
||||
@ -2309,8 +2310,31 @@ 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}".')
|
||||
@ -2319,7 +2343,9 @@ 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.
|
||||
_verifyCompatibleNumbers(arguments, node.arguments);
|
||||
if (error.message.contains("compatible")) {
|
||||
_verifyCompatibleNumbers(arguments, node.arguments);
|
||||
}
|
||||
throwWithTrace(_exception(error.message, node.span), error, stackTrace);
|
||||
}
|
||||
}
|
||||
@ -2361,14 +2387,15 @@ final class _EvaluateVisitor
|
||||
|
||||
/// Evaluates [node] as a component of a calculation.
|
||||
///
|
||||
/// If [inMinMax] is `true`, this allows unitless numbers to be added and
|
||||
/// If [inLegacySassFunction] 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.
|
||||
/// old global `min()`, `max()`, `round()`, and `abs()` functions.
|
||||
Future<Object> _visitCalculationValue(Expression node,
|
||||
{required bool inMinMax}) async {
|
||||
{required bool inLegacySassFunction}) async {
|
||||
switch (node) {
|
||||
case ParenthesizedExpression(expression: var inner):
|
||||
var result = await _visitCalculationValue(inner, inMinMax: inMinMax);
|
||||
var result = await _visitCalculationValue(inner,
|
||||
inLegacySassFunction: inLegacySassFunction);
|
||||
return inner is FunctionExpression &&
|
||||
inner.name.toLowerCase() == 'var' &&
|
||||
result is SassString &&
|
||||
@ -2399,9 +2426,11 @@ final class _EvaluateVisitor
|
||||
node,
|
||||
() async => SassCalculation.operateInternal(
|
||||
_binaryOperatorToCalculationOperator(operator),
|
||||
await _visitCalculationValue(left, inMinMax: inMinMax),
|
||||
await _visitCalculationValue(right, inMinMax: inMinMax),
|
||||
inMinMax: inMinMax,
|
||||
await _visitCalculationValue(left,
|
||||
inLegacySassFunction: inLegacySassFunction),
|
||||
await _visitCalculationValue(right,
|
||||
inLegacySassFunction: inLegacySassFunction),
|
||||
inLegacySassFunction: inLegacySassFunction,
|
||||
simplify: !_inSupportsDeclaration));
|
||||
|
||||
case _:
|
||||
|
@ -5,7 +5,7 @@
|
||||
// DO NOT EDIT. This file was generated from async_evaluate.dart.
|
||||
// See tool/grind/synchronize.dart for details.
|
||||
//
|
||||
// Checksum: 6eb7f76735562eba91e9460af796b269b3b0aaf7
|
||||
// Checksum: e4d8cd913b88b73d11417b5ccda03a6313a5bb78
|
||||
//
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
@ -2282,7 +2282,8 @@ final class _EvaluateVisitor
|
||||
var arguments = [
|
||||
for (var argument in node.arguments)
|
||||
_visitCalculationValue(argument,
|
||||
inMinMax: node.name == 'min' || node.name == 'max')
|
||||
inLegacySassFunction:
|
||||
{'min', 'max', 'round', 'abs'}.contains(node.name))
|
||||
];
|
||||
if (_inSupportsDeclaration) {
|
||||
return SassCalculation.unsimplified(node.name, arguments);
|
||||
@ -2291,8 +2292,31 @@ 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}".')
|
||||
@ -2301,7 +2325,9 @@ 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.
|
||||
_verifyCompatibleNumbers(arguments, node.arguments);
|
||||
if (error.message.contains("compatible")) {
|
||||
_verifyCompatibleNumbers(arguments, node.arguments);
|
||||
}
|
||||
throwWithTrace(_exception(error.message, node.span), error, stackTrace);
|
||||
}
|
||||
}
|
||||
@ -2343,13 +2369,15 @@ final class _EvaluateVisitor
|
||||
|
||||
/// Evaluates [node] as a component of a calculation.
|
||||
///
|
||||
/// If [inMinMax] is `true`, this allows unitless numbers to be added and
|
||||
/// If [inLegacySassFunction] 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.
|
||||
Object _visitCalculationValue(Expression node, {required bool inMinMax}) {
|
||||
/// old global `min()`, `max()`, `round()`, and `abs()` functions.
|
||||
Object _visitCalculationValue(Expression node,
|
||||
{required bool inLegacySassFunction}) {
|
||||
switch (node) {
|
||||
case ParenthesizedExpression(expression: var inner):
|
||||
var result = _visitCalculationValue(inner, inMinMax: inMinMax);
|
||||
var result = _visitCalculationValue(inner,
|
||||
inLegacySassFunction: inLegacySassFunction);
|
||||
return inner is FunctionExpression &&
|
||||
inner.name.toLowerCase() == 'var' &&
|
||||
result is SassString &&
|
||||
@ -2380,9 +2408,11 @@ final class _EvaluateVisitor
|
||||
node,
|
||||
() => SassCalculation.operateInternal(
|
||||
_binaryOperatorToCalculationOperator(operator),
|
||||
_visitCalculationValue(left, inMinMax: inMinMax),
|
||||
_visitCalculationValue(right, inMinMax: inMinMax),
|
||||
inMinMax: inMinMax,
|
||||
_visitCalculationValue(left,
|
||||
inLegacySassFunction: inLegacySassFunction),
|
||||
_visitCalculationValue(right,
|
||||
inLegacySassFunction: inLegacySassFunction),
|
||||
inLegacySassFunction: inLegacySassFunction,
|
||||
simplify: !_inSupportsDeclaration));
|
||||
|
||||
case _:
|
||||
|
@ -1,3 +1,7 @@
|
||||
## 8.1.0
|
||||
|
||||
* No user-visible changes.
|
||||
|
||||
## 8.0.0
|
||||
|
||||
* Various classes now use Dart 3 [class modifiers] to more specifically restrict
|
||||
|
@ -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.0.0
|
||||
version: 8.1.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.64.3
|
||||
sass: 1.65.0
|
||||
|
||||
dev_dependencies:
|
||||
dartdoc: ^5.0.0
|
||||
|
@ -1,5 +1,5 @@
|
||||
name: sass
|
||||
version: 1.64.3
|
||||
version: 1.65.0
|
||||
description: A Sass implementation in Dart.
|
||||
homepage: https://github.com/sass/dart-sass
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user