diff --git a/CHANGELOG.md b/CHANGELOG.md index af52a6675..de3e450d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,3 @@ -## 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 diff --git a/lib/src/ast/sass/expression/calculation.dart b/lib/src/ast/sass/expression/calculation.dart index 3c0b55af3..38c25ed14 100644 --- a/lib/src/ast/sass/expression/calculation.dart +++ b/lib/src/ast/sass/expression/calculation.dart @@ -40,10 +40,6 @@ final class CalculationExpression implements Expression { } } - /// Returns a `hypot()` calculation expression. - CalculationExpression.hypot(Iterable arguments, FileSpan span) - : this("hypot", arguments, span); - /// Returns a `max()` calculation expression. CalculationExpression.max(Iterable 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 diff --git a/lib/src/deprecation.dart b/lib/src/deprecation.dart index 01f9d2e4e..8e6f6174d 100644 --- a/lib/src/deprecation.dart +++ b/lib/src/deprecation.dart @@ -55,11 +55,6 @@ 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: diff --git a/lib/src/functions/math.dart b/lib/src/functions/math.dart index 83cc11b76..5b7fa15f5 100644 --- a/lib/src/functions/math.dart +++ b/lib/src/functions/math.dart @@ -12,7 +12,6 @@ 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. @@ -133,32 +132,87 @@ final _log = _function("log", r"$number, $base: null", (arguments) { final _pow = _function("pow", r"$base, $exponent", (arguments) { var base = arguments[0].assertNumber("base"); var exponent = arguments[1].assertNumber("exponent"); - return pow(base, exponent); + if (base.hasUnits) { + throw SassScriptException("\$base: Expected $base to have no units."); + } else if (exponent.hasUnits) { + throw SassScriptException( + "\$exponent: Expected $exponent to have no units."); + } else { + return SassNumber(math.pow(base.value, exponent.value)); + } }); -final _sqrt = _singleArgumentMathFunc("sqrt", sqrt); +final _sqrt = _function("sqrt", r"$number", (arguments) { + var number = arguments[0].assertNumber("number"); + if (number.hasUnits) { + throw SassScriptException("\$number: Expected $number to have no units."); + } else { + return SassNumber(math.sqrt(number.value)); + } +}); /// /// Trigonometric functions /// -final _acos = _singleArgumentMathFunc("acos", acos); +final _acos = _function("acos", r"$number", (arguments) { + var number = arguments[0].assertNumber("number"); + if (number.hasUnits) { + throw SassScriptException("\$number: Expected $number to have no units."); + } else { + return SassNumber.withUnits(math.acos(number.value) * 180 / math.pi, + numeratorUnits: ['deg']); + } +}); -final _asin = _singleArgumentMathFunc("asin", asin); +final _asin = _function("asin", r"$number", (arguments) { + var number = arguments[0].assertNumber("number"); + if (number.hasUnits) { + throw SassScriptException("\$number: Expected $number to have no units."); + } else { + return SassNumber.withUnits(math.asin(number.value) * 180 / math.pi, + numeratorUnits: ['deg']); + } +}); -final _atan = _singleArgumentMathFunc("atan", atan); +final _atan = _function("atan", r"$number", (arguments) { + var number = arguments[0].assertNumber("number"); + if (number.hasUnits) { + throw SassScriptException("\$number: Expected $number to have no units."); + } else { + return SassNumber.withUnits(math.atan(number.value) * 180 / math.pi, + numeratorUnits: ['deg']); + } +}); final _atan2 = _function("atan2", r"$y, $x", (arguments) { var y = arguments[0].assertNumber("y"); var x = arguments[1].assertNumber("x"); - return atan2(y, x); + return SassNumber.withUnits( + math.atan2(y.value, x.convertValueToMatch(y, 'x', 'y')) * 180 / math.pi, + numeratorUnits: ['deg']); }); -final _cos = _singleArgumentMathFunc("cos", cos); - -final _sin = _singleArgumentMathFunc("sin", sin); - -final _tan = _singleArgumentMathFunc("tan", tan); +final _cos = _function( + "cos", + r"$number", + (arguments) => SassNumber(math.cos(arguments[0] + .assertNumber("number") + .coerceValueToUnit("rad", "number")))); + +final _sin = _function( + "sin", + r"$number", + (arguments) => SassNumber(math.sin(arguments[0] + .assertNumber("number") + .coerceValueToUnit("rad", "number")))); + +final _tan = _function( + "tan", + r"$number", + (arguments) => SassNumber(math.tan(arguments[0] + .assertNumber("number") + .coerceValueToUnit("rad", "number")))); /// /// Unit functions @@ -234,16 +288,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)) { diff --git a/lib/src/js/value/calculation.dart b/lib/src/js/value/calculation.dart index 51dfadae8..6154de77b 100644 --- a/lib/src/js/value/calculation.dart +++ b/lib/src/js/value/calculation.dart @@ -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; }(); diff --git a/lib/src/parse/stylesheet.dart b/lib/src/parse/stylesheet.dart index 78d66315a..9e9f7b2ed 100644 --- a/lib/src/parse/stylesheet.dart +++ b/lib/src/parse/stylesheet.dart @@ -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); - - case "pow": - case "log": - case "atan2": - case "mod": - case "rem": - var arguments = _calculationArguments(2); + var beforeArguments = scanner.state; + List arguments; + try { + arguments = _calculationArguments(); + } on FormatException catch (_) { + scanner.state = beforeArguments; + return null; + } + return CalculationExpression(name, arguments, scanner.spanFrom(start)); case "clamp": var arguments = _calculationArguments(3); return CalculationExpression(name, arguments, scanner.spanFrom(start)); - case "round": - return _tryArgumentsCalculation(name, start, 3); - case _: return null; } } - // Returns a CalculationExpression if the function can be parsed as a calculation, - // otherwise, returns null and the function is parsed as a normal Sass function. - CalculationExpression? _tryArgumentsCalculation( - String name, LineScannerState start, int? maxArgs) { - var beforeArguments = scanner.state; - try { - var arguments = _calculationArguments(maxArgs); - return CalculationExpression(name, arguments, scanner.spanFrom(start)); - } on FormatException catch (_) { - scanner.state = beforeArguments; - return null; - } - } - /// Consumes and returns arguments for a calculation expression, including the /// opening and closing parentheses. /// diff --git a/lib/src/util/number.dart b/lib/src/util/number.dart index 6cd85b67e..80fd3aaa2 100644 --- a/lib/src/util/number.dart +++ b/lib/src/util/number.dart @@ -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; - } -} diff --git a/lib/src/value/calculation.dart b/lib/src/value/calculation.dart index bd64ef7b7..b39011bf0 100644 --- a/lib/src/value/calculation.dart +++ b/lib/src/value/calculation.dart @@ -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 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 _simplifyArguments(Iterable 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(ValueVisitor visitor) => visitor.visitCalculation(this); diff --git a/lib/src/value/number.dart b/lib/src/value/number.dart index a5c90a501..410bc8465 100644 --- a/lib/src/value/number.dart +++ b/lib/src/value/number.dart @@ -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)); } diff --git a/lib/src/value/number/unitless.dart b/lib/src/value/number/unitless.dart index 7272b7c59..06b54d39b 100644 --- a/lib/src/value/number/unitless.dart +++ b/lib/src/value/number/unitless.dart @@ -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)); } diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index 0896cc55e..3e8dabcd3 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -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 _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 _: diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index 495927c65..a8639f4e6 100644 --- a/lib/src/visitor/evaluate.dart +++ b/lib/src/visitor/evaluate.dart @@ -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 _: diff --git a/pkg/sass_api/CHANGELOG.md b/pkg/sass_api/CHANGELOG.md index ce62f329d..da3a1c94d 100644 --- a/pkg/sass_api/CHANGELOG.md +++ b/pkg/sass_api/CHANGELOG.md @@ -1,7 +1,3 @@ -## 8.1.0 - -* No user-visible changes. - ## 8.0.0 * Various classes now use Dart 3 [class modifiers] to more specifically restrict diff --git a/pkg/sass_api/pubspec.yaml b/pkg/sass_api/pubspec.yaml index 23d5acb14..3fe0ab0f6 100644 --- a/pkg/sass_api/pubspec.yaml +++ b/pkg/sass_api/pubspec.yaml @@ -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.0 +version: 8.0.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.0 + sass: 1.64.3 dev_dependencies: dartdoc: ^5.0.0 diff --git a/pubspec.yaml b/pubspec.yaml index 1ad532724..4c22ad22e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.65.0 +version: 1.64.3 description: A Sass implementation in Dart. homepage: https://github.com/sass/dart-sass