From c0b9db0347368e5e4fcf35b45d8465820cc1cc84 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 27 Oct 2022 18:54:00 -0700 Subject: [PATCH] Implement deprecations for strict function units See #1776 See sass/sass#3374 --- CHANGELOG.md | 19 +++++++++++++++++++ lib/src/functions/color.dart | 31 ++++++++++++++++++------------- lib/src/value.dart | 12 +++++++++++- lib/src/value/number.dart | 17 +++++++++++++++++ 4 files changed, 65 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a56b2aae..5f08d4d34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ * Fix indentation for selectors that span multiple lines in a `@media` query. +* Emit a deprecation warning when passing `$alpha` values with units to + `color.adjust()` or `color.change()`. This will be an error in Dart Sass + 2.0.0. + +* Emit a deprecation warning when passing a `$weight` value with no units or + with units other than `%` to `color.mix()`. This will be an error in Dart Sass + 2.0.0. + +* Emit a deprecation warning when passing `$n` values with units to `list.nth()` + or `list.set-nth()`. This will be an error in Dart Sass 2.0.0. + +* Improve existing deprecation warnings to wrap `/`-as-division suggestions in + `calc()` expressions. + +### Dart API + +* Emit a deprecation warning when passing a `sassIndex` with units to + `Value.sassIndexToListIndex()`. This will be an error in Dart Sass 2.0.0. + ## 1.55.0 * **Potentially breaking bug fix:** Sass numbers are now universally stored as diff --git a/lib/src/functions/color.dart b/lib/src/functions/color.dart index 583bdb194..2efbf0f5d 100644 --- a/lib/src/functions/color.dart +++ b/lib/src/functions/color.dart @@ -453,9 +453,19 @@ SassColor _updateComponents(List arguments, /// [max] should be 255 for RGB channels, 1 for the alpha channel, and 100 /// for saturation, lightness, whiteness, and blackness. double? getParam(String name, num max, - {bool checkPercent = false, bool assertPercent = false}) { + {bool checkPercent = false, + bool assertPercent = false, + bool checkUnitless = false}) { var number = keywords.remove(name)?.assertNumber(name); if (number == null) return null; + if (!scale && checkUnitless) { + if (number.hasUnits) { + warn("\$$name: Passing a number with unit ${number.unitString} is " + "deprecated.\n" + "\n" + "To preserve current behavior: ${number.unitSuggestion(name)}"); + } + } if (!scale && checkPercent) _checkPercent(number, name); if (scale || assertPercent) number.assertUnit("%", name); if (scale) max = 100; @@ -465,7 +475,7 @@ SassColor _updateComponents(List arguments, change ? 0 : -max, max, name, checkPercent ? '%' : ''); } - var alpha = getParam("alpha", 1); + var alpha = getParam("alpha", 1, checkUnitless: true); var red = getParam("red", 255); var green = getParam("green", 255); var blue = getParam("blue", 255); @@ -656,12 +666,13 @@ void _checkAngle(SassNumber angle, String name) { var actualUnit = angle.numeratorUnits.first; message - ..writeln("To preserve current behavior: \$$name * 1deg/1$actualUnit") + ..writeln( + "To preserve current behavior: calc(\$$name * " "1deg/1$actualUnit)") ..writeln("To migrate to new behavior: 0deg + \$$name") ..writeln(); } else { message - ..writeln("To preserve current behavior: \$$name${_removeUnits(angle)}") + ..writeln("To preserve current behavior: ${angle.unitSuggestion(name)}") ..writeln(); } @@ -676,18 +687,10 @@ void _checkPercent(SassNumber number, String name) { warn( "\$$name: Passing a number without unit % ($number) is deprecated.\n" "\n" - "To preserve current behavior: \$$name${_removeUnits(number)} * 1%", + "To preserve current behavior: ${number.unitSuggestion(name, '%')}", deprecation: true); } -/// Returns the right-hand side of an expression that would remove all units -/// from `$number` but leaves the value the same. -/// -/// Used for constructing deprecation messages. -String _removeUnits(SassNumber number) => - number.denominatorUnits.map((unit) => " * 1$unit").join() + - number.numeratorUnits.map((unit) => " / 1$unit").join(); - /// Create an HWB color from the given [arguments]. Value _hwb(List arguments) { var alpha = arguments.length > 3 ? arguments[3] : null; @@ -805,6 +808,8 @@ double _percentageOrUnitless(SassNumber number, num max, String name) { /// Returns [color1] and [color2], mixed together and weighted by [weight]. SassColor _mixColors(SassColor color1, SassColor color2, SassNumber weight) { + _checkPercent(weight, 'weight'); + // This algorithm factors in both the user-provided weight (w) and the // difference between the alpha values of the two colors (a) to decide how // to perform the weighted average of the two RGB values. diff --git a/lib/src/value.dart b/lib/src/value.dart index fef8881c8..4c815bdf4 100644 --- a/lib/src/value.dart +++ b/lib/src/value.dart @@ -5,6 +5,7 @@ import 'package:meta/meta.dart'; import 'ast/selector.dart'; +import 'evaluation_context.dart'; import 'exception.dart'; import 'utils.dart'; import 'value/boolean.dart'; @@ -120,7 +121,16 @@ abstract class Value { /// [asList]. If [sassIndex] came from a function argument, [name] is the /// argument name (without the `$`). It's used for error reporting. int sassIndexToListIndex(Value sassIndex, [String? name]) { - var index = sassIndex.assertNumber(name).assertInt(name); + var indexValue = sassIndex.assertNumber(name); + if (indexValue.hasUnits) { + warn("\$$name: Passing a number with unit ${indexValue.unitString} is " + "deprecated.\n" + "\n" + "To preserve current behavior: " + + indexValue.unitSuggestion(name ?? 'index')); + } + + var index = indexValue.assertInt(name); if (index == 0) throw SassScriptException("List index may not be 0.", name); if (index.abs() > lengthAsList) { throw SassScriptException( diff --git a/lib/src/value/number.dart b/lib/src/value/number.dart index e57e8e303..87c159b96 100644 --- a/lib/src/value/number.dart +++ b/lib/src/value/number.dart @@ -933,4 +933,21 @@ abstract class SassNumber extends Value { var innerMap = _conversions[unit]; return innerMap == null ? 1 : 1 / innerMap.values.first; } + + /// Returns a suggested Sass snippet for converting a variable named [name] + /// (without `%`) containing this number into a number with the same value and + /// the given [unit]. + /// + /// If [unit] is null, this forces the number to be unitless. + /// + /// This is used for deprecation warnings when restricting which units are + /// allowed for a given function. + @internal + String unitSuggestion(String name, [String? unit]) { + var result = "\$$name" + + denominatorUnits.map((unit) => " * 1$unit").join() + + numeratorUnits.map((unit) => " / 1$unit").join() + + (unit == null ? "" : " * 1$unit"); + return numeratorUnits.isEmpty ? result : "calc($result)"; + } }