From e4c8cd67e85d6710ce6bc27107882d6ea1856de0 Mon Sep 17 00:00:00 2001 From: Pamela Lozano <30474787+pamelalozano16@users.noreply.github.com> Date: Wed, 9 Aug 2023 21:14:08 +0000 Subject: [PATCH] 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 --- CHANGELOG.md | 11 + lib/src/ast/sass/expression/calculation.dart | 69 +++ lib/src/deprecation.dart | 5 + lib/src/functions/math.dart | 84 +-- lib/src/js/value/calculation.dart | 4 +- lib/src/parse/stylesheet.dart | 51 +- lib/src/util/number.dart | 76 +++ lib/src/value/calculation.dart | 529 ++++++++++++++++++- lib/src/value/number.dart | 2 +- lib/src/value/number/unitless.dart | 2 +- lib/src/visitor/async_evaluate.dart | 47 +- lib/src/visitor/evaluate.dart | 50 +- pkg/sass_api/CHANGELOG.md | 4 + pkg/sass_api/pubspec.yaml | 4 +- pubspec.yaml | 2 +- 15 files changed, 834 insertions(+), 106 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de3e450d..af52a667 100644 --- a/CHANGELOG.md +++ b/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 diff --git a/lib/src/ast/sass/expression/calculation.dart b/lib/src/ast/sass/expression/calculation.dart index 38c25ed1..3c0b55af 100644 --- a/lib/src/ast/sass/expression/calculation.dart +++ b/lib/src/ast/sass/expression/calculation.dart @@ -40,6 +40,10 @@ 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", @@ -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 diff --git a/lib/src/deprecation.dart b/lib/src/deprecation.dart index 8e6f6174..01f9d2e4 100644 --- a/lib/src/deprecation.dart +++ b/lib/src/deprecation.dart @@ -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: diff --git a/lib/src/functions/math.dart b/lib/src/functions/math.dart index 5b7fa15f..83cc11b7 100644 --- a/lib/src/functions/math.dart +++ b/lib/src/functions/math.dart @@ -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)) { diff --git a/lib/src/js/value/calculation.dart b/lib/src/js/value/calculation.dart index 6154de77..51dfadae 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, - 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; }(); diff --git a/lib/src/parse/stylesheet.dart b/lib/src/parse/stylesheet.dart index 9e9f7b2e..78d66315 100644 --- a/lib/src/parse/stylesheet.dart +++ b/lib/src/parse/stylesheet.dart @@ -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 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. /// diff --git a/lib/src/util/number.dart b/lib/src/util/number.dart index 80fd3aaa..6cd85b67 100644 --- a/lib/src/util/number.dart +++ b/lib/src/util/number.dart @@ -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; + } +} diff --git a/lib/src/value/calculation.dart b/lib/src/value/calculation.dart index b39011bf..bd64ef7b 100644 --- a/lib/src/value/calculation.dart +++ b/lib/src/value/calculation.dart @@ -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 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 _simplifyArguments(Iterable 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(ValueVisitor visitor) => visitor.visitCalculation(this); diff --git a/lib/src/value/number.dart b/lib/src/value/number.dart index 410bc846..a5c90a50 100644 --- a/lib/src/value/number.dart +++ b/lib/src/value/number.dart @@ -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)); } diff --git a/lib/src/value/number/unitless.dart b/lib/src/value/number/unitless.dart index 06b54d39..7272b7c5 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); } - Value modulo(Value other) { + SassNumber 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 3e8dabcd..0896cc55 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -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 _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 _: diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index a8639f4e..495927c6 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: 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 _: diff --git a/pkg/sass_api/CHANGELOG.md b/pkg/sass_api/CHANGELOG.md index da3a1c94..ce62f329 100644 --- a/pkg/sass_api/CHANGELOG.md +++ b/pkg/sass_api/CHANGELOG.md @@ -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 diff --git a/pkg/sass_api/pubspec.yaml b/pkg/sass_api/pubspec.yaml index 3fe0ab0f..23d5acb1 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.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 diff --git a/pubspec.yaml b/pubspec.yaml index ec2e0a15..c8364f00 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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