Skip to content

Commit 57a6853

Browse files
authored
Add support for CSS round() with one unitless argument (#2436)
This also adds deprecation warnings for cases where a CSS calculation is determined to need to mimic a global Sass function, to match the global Sass function deprecation. See sass/sass#3803 Closes #2433
1 parent e400bdd commit 57a6853

12 files changed

+152
-54
lines changed

CHANGELOG.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
## 1.81.0-dev
1+
## 1.81.0
22

3-
* No user-visible changes.
3+
* Fix a few cases where deprecation warnings weren't being emitted for global
4+
built-in functions whose names overlap with CSS calculations.
5+
6+
* Add support for the CSS `round()` calculation with a single argument, as long
7+
as that argument might be a unitless number.
48

59
## 1.80.7
610

lib/src/ast/css/style_rule.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ abstract interface class CssStyleRule implements CssParentNode {
2121

2222
/// Whether this style rule was originally defined in a plain CSS stylesheet.
2323
///
24-
/// :nodoc:
24+
/// @nodoc
2525
@internal
2626
bool get fromPlainCss;
2727
}

lib/src/js/value/calculation.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ final JSClass calculationOperationClass = () {
8888
_assertCalculationValue(left);
8989
_assertCalculationValue(right);
9090
return SassCalculation.operateInternal(operator, left, right,
91-
inLegacySassFunction: false, simplify: false);
91+
inLegacySassFunction: null, simplify: false, warn: null);
9292
});
9393

9494
jsClass.defineMethods({
@@ -104,7 +104,7 @@ final JSClass calculationOperationClass = () {
104104

105105
getJSClass(SassCalculation.operateInternal(
106106
CalculationOperator.plus, SassNumber(1), SassNumber(1),
107-
inLegacySassFunction: false, simplify: false))
107+
inLegacySassFunction: null, simplify: false, warn: null))
108108
.injectSuperclass(jsClass);
109109
return jsClass;
110110
}();

lib/src/value/calculation.dart

+74-21
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:math' as math;
66

77
import 'package:charcode/charcode.dart';
88
import 'package:meta/meta.dart';
9+
import 'package:source_span/source_span.dart';
910

1011
import '../deprecation.dart';
1112
import '../evaluation_context.dart';
@@ -482,13 +483,47 @@ final class SassCalculation extends Value {
482483
/// This may be passed fewer than two arguments, but only if one of the
483484
/// arguments is an unquoted `var()` string.
484485
static Value round(Object strategyOrNumber,
485-
[Object? numberOrStep, Object? step]) {
486+
[Object? numberOrStep, Object? step]) =>
487+
roundInternal(strategyOrNumber, numberOrStep, step,
488+
span: null, inLegacySassFunction: null, warn: null);
489+
490+
/// Like [round], but with the internal-only [inLegacySassFunction] and
491+
/// [warn] parameters.
492+
///
493+
/// If [inLegacySassFunction] isn't null, this allows unitless numbers to be
494+
/// added and subtracted with numbers with units, for backwards-compatibility
495+
/// with the old global `min()` and `max()` functions. This emits a
496+
/// deprecation warning using the string as the function's name.
497+
///
498+
/// If [simplify] is `false`, no simplification will be done.
499+
///
500+
/// The [warn] callback is used to surface deprecation warnings.
501+
///
502+
/// @nodoc
503+
@internal
504+
static Value roundInternal(
505+
Object strategyOrNumber, Object? numberOrStep, Object? step,
506+
{required FileSpan? span,
507+
required String? inLegacySassFunction,
508+
required void Function(String message, [Deprecation? deprecation])?
509+
warn}) {
486510
switch ((
487511
_simplify(strategyOrNumber),
488512
numberOrStep.andThen(_simplify),
489513
step.andThen(_simplify)
490514
)) {
491-
case (SassNumber number, null, null):
515+
case (SassNumber(hasUnits: false) && var number, null, null):
516+
return SassNumber(number.value.round());
517+
518+
case (SassNumber number, null, null) when inLegacySassFunction != null:
519+
warn!(
520+
"In future versions of Sass, round() will be interpreted as a CSS "
521+
"round() calculation. This requires an explicit modulus when "
522+
"rounding numbers with units. If you want to use the Sass "
523+
"function, call math.round() instead.\n"
524+
"\n"
525+
"See https://sass-lang.com/d/import",
526+
Deprecation.globalBuiltin);
492527
return _matchUnits(number.value.round().toDouble(), number);
493528

494529
case (SassNumber number, SassNumber step, null)
@@ -542,12 +577,8 @@ final class SassCalculation extends Value {
542577
throw SassScriptException(
543578
"Number to round and step arguments are required.");
544579

545-
case (SassString rest, null, null):
546-
return SassCalculation._("round", [rest]);
547-
548580
case (var number, null, null):
549-
throw SassScriptException(
550-
"Single argument $number expected to be simplifiable.");
581+
return SassCalculation._("round", [number]);
551582

552583
case (var number, var step?, null):
553584
return SassCalculation._("round", [number, step]);
@@ -584,32 +615,54 @@ final class SassCalculation extends Value {
584615
static Object operate(
585616
CalculationOperator operator, Object left, Object right) =>
586617
operateInternal(operator, left, right,
587-
inLegacySassFunction: false, simplify: true);
618+
inLegacySassFunction: null, simplify: true, warn: null);
588619

589-
/// Like [operate], but with the internal-only [inLegacySassFunction] parameter.
620+
/// Like [operate], but with the internal-only [inLegacySassFunction] and
621+
/// [warn] parameters.
590622
///
591-
/// If [inLegacySassFunction] is `true`, this allows unitless numbers to be added and
592-
/// subtracted with numbers with units, for backwards-compatibility with the
593-
/// old global `min()` and `max()` functions.
623+
/// If [inLegacySassFunction] isn't null, this allows unitless numbers to be
624+
/// added and subtracted with numbers with units, for backwards-compatibility
625+
/// with the old global `min()` and `max()` functions. This emits a
626+
/// deprecation warning using the string as the function's name.
594627
///
595628
/// If [simplify] is `false`, no simplification will be done.
629+
///
630+
/// The [warn] callback is used to surface deprecation warnings.
631+
///
632+
/// @nodoc
596633
@internal
597634
static Object operateInternal(
598635
CalculationOperator operator, Object left, Object right,
599-
{required bool inLegacySassFunction, required bool simplify}) {
636+
{required String? inLegacySassFunction,
637+
required bool simplify,
638+
required void Function(String message, [Deprecation? deprecation])?
639+
warn}) {
600640
if (!simplify) return CalculationOperation._(operator, left, right);
601641
left = _simplify(left);
602642
right = _simplify(right);
603643

604644
if (operator case CalculationOperator.plus || CalculationOperator.minus) {
605-
if (left is SassNumber &&
606-
right is SassNumber &&
607-
(inLegacySassFunction
608-
? left.isComparableTo(right)
609-
: left.hasCompatibleUnits(right))) {
610-
return operator == CalculationOperator.plus
611-
? left.plus(right)
612-
: left.minus(right);
645+
if (left is SassNumber && right is SassNumber) {
646+
var compatible = left.hasCompatibleUnits(right);
647+
if (!compatible &&
648+
inLegacySassFunction != null &&
649+
left.isComparableTo(right)) {
650+
warn!(
651+
"In future versions of Sass, $inLegacySassFunction() will be "
652+
"interpreted as the CSS $inLegacySassFunction() calculation. "
653+
"This doesn't allow unitless numbers to be mixed with numbers "
654+
"with units. If you want to use the Sass function, call "
655+
"math.$inLegacySassFunction() instead.\n"
656+
"\n"
657+
"See https://sass-lang.com/d/import",
658+
Deprecation.globalBuiltin);
659+
compatible = true;
660+
}
661+
if (compatible) {
662+
return operator == CalculationOperator.plus
663+
? left.plus(right)
664+
: left.minus(right);
665+
}
613666
}
614667

615668
_verifyCompatibleNumbers([left, right]);

lib/src/visitor/async_evaluate.dart

+25-10
Original file line numberDiff line numberDiff line change
@@ -2555,12 +2555,12 @@ final class _EvaluateVisitor
25552555
// Note that the list of calculation functions is also tracked in
25562556
// lib/src/visitor/is_plain_css_safe.dart.
25572557
switch (node.name.toLowerCase()) {
2558-
case "min" || "max" || "round" || "abs"
2558+
case ("min" || "max" || "round" || "abs") && var name
25592559
when node.arguments.named.isEmpty &&
25602560
node.arguments.rest == null &&
25612561
node.arguments.positional
25622562
.every((argument) => argument.isCalculationSafe):
2563-
return await _visitCalculation(node, inLegacySassFunction: true);
2563+
return await _visitCalculation(node, inLegacySassFunction: name);
25642564

25652565
case "calc" ||
25662566
"clamp" ||
@@ -2607,8 +2607,15 @@ final class _EvaluateVisitor
26072607
return result;
26082608
}
26092609

2610+
/// Evaluates [node] as a calculation.
2611+
///
2612+
/// If [inLegacySassFunction] isn't null, this allows unitless numbers to be
2613+
/// added and subtracted with numbers with units, for backwards-compatibility
2614+
/// with the old global `min()`, `max()`, `round()`, and `abs()` functions.
2615+
/// The parameter is the name of the function, which is used for reporting
2616+
/// deprecation warnings.
26102617
Future<Value> _visitCalculation(FunctionExpression node,
2611-
{bool inLegacySassFunction = false}) async {
2618+
{String? inLegacySassFunction}) async {
26122619
if (node.arguments.named.isNotEmpty) {
26132620
throw _exception(
26142621
"Keyword arguments can't be used with calculations.", node.span);
@@ -2656,8 +2663,12 @@ final class _EvaluateVisitor
26562663
SassCalculation.mod(arguments[0], arguments.elementAtOrNull(1)),
26572664
"rem" =>
26582665
SassCalculation.rem(arguments[0], arguments.elementAtOrNull(1)),
2659-
"round" => SassCalculation.round(arguments[0],
2660-
arguments.elementAtOrNull(1), arguments.elementAtOrNull(2)),
2666+
"round" => SassCalculation.roundInternal(arguments[0],
2667+
arguments.elementAtOrNull(1), arguments.elementAtOrNull(2),
2668+
span: node.span,
2669+
inLegacySassFunction: inLegacySassFunction,
2670+
warn: (message, [deprecation]) =>
2671+
_warn(message, node.span, deprecation)),
26612672
"clamp" => SassCalculation.clamp(arguments[0],
26622673
arguments.elementAtOrNull(1), arguments.elementAtOrNull(2)),
26632674
_ => throw UnsupportedError('Unknown calculation name "${node.name}".')
@@ -2753,11 +2764,13 @@ final class _EvaluateVisitor
27532764

27542765
/// Evaluates [node] as a component of a calculation.
27552766
///
2756-
/// If [inLegacySassFunction] is `true`, this allows unitless numbers to be added and
2757-
/// subtracted with numbers with units, for backwards-compatibility with the
2758-
/// old global `min()`, `max()`, `round()`, and `abs()` functions.
2767+
/// If [inLegacySassFunction] isn't null, this allows unitless numbers to be
2768+
/// added and subtracted with numbers with units, for backwards-compatibility
2769+
/// with the old global `min()`, `max()`, `round()`, and `abs()` functions.
2770+
/// The parameter is the name of the function, which is used for reporting
2771+
/// deprecation warnings.
27592772
Future<Object> _visitCalculationExpression(Expression node,
2760-
{required bool inLegacySassFunction}) async {
2773+
{required String? inLegacySassFunction}) async {
27612774
switch (node) {
27622775
case ParenthesizedExpression(expression: var inner):
27632776
var result = await _visitCalculationExpression(inner,
@@ -2788,7 +2801,9 @@ final class _EvaluateVisitor
27882801
await _visitCalculationExpression(right,
27892802
inLegacySassFunction: inLegacySassFunction),
27902803
inLegacySassFunction: inLegacySassFunction,
2791-
simplify: !_inSupportsDeclaration));
2804+
simplify: !_inSupportsDeclaration,
2805+
warn: (message, [deprecation]) =>
2806+
_warn(message, node.span, deprecation)));
27922807

27932808
case NumberExpression() ||
27942809
VariableExpression() ||

lib/src/visitor/evaluate.dart

+26-11
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// DO NOT EDIT. This file was generated from async_evaluate.dart.
66
// See tool/grind/synchronize.dart for details.
77
//
8-
// Checksum: 3986f5db33dd220dcd971a39e8587ca4e52d9a3f
8+
// Checksum: fbffa0dbe5a1af846dc83752457d39fb2984d280
99
//
1010
// ignore_for_file: unused_import
1111

@@ -2533,12 +2533,12 @@ final class _EvaluateVisitor
25332533
// Note that the list of calculation functions is also tracked in
25342534
// lib/src/visitor/is_plain_css_safe.dart.
25352535
switch (node.name.toLowerCase()) {
2536-
case "min" || "max" || "round" || "abs"
2536+
case ("min" || "max" || "round" || "abs") && var name
25372537
when node.arguments.named.isEmpty &&
25382538
node.arguments.rest == null &&
25392539
node.arguments.positional
25402540
.every((argument) => argument.isCalculationSafe):
2541-
return _visitCalculation(node, inLegacySassFunction: true);
2541+
return _visitCalculation(node, inLegacySassFunction: name);
25422542

25432543
case "calc" ||
25442544
"clamp" ||
@@ -2585,8 +2585,15 @@ final class _EvaluateVisitor
25852585
return result;
25862586
}
25872587

2588+
/// Evaluates [node] as a calculation.
2589+
///
2590+
/// If [inLegacySassFunction] isn't null, this allows unitless numbers to be
2591+
/// added and subtracted with numbers with units, for backwards-compatibility
2592+
/// with the old global `min()`, `max()`, `round()`, and `abs()` functions.
2593+
/// The parameter is the name of the function, which is used for reporting
2594+
/// deprecation warnings.
25882595
Value _visitCalculation(FunctionExpression node,
2589-
{bool inLegacySassFunction = false}) {
2596+
{String? inLegacySassFunction}) {
25902597
if (node.arguments.named.isNotEmpty) {
25912598
throw _exception(
25922599
"Keyword arguments can't be used with calculations.", node.span);
@@ -2634,8 +2641,12 @@ final class _EvaluateVisitor
26342641
SassCalculation.mod(arguments[0], arguments.elementAtOrNull(1)),
26352642
"rem" =>
26362643
SassCalculation.rem(arguments[0], arguments.elementAtOrNull(1)),
2637-
"round" => SassCalculation.round(arguments[0],
2638-
arguments.elementAtOrNull(1), arguments.elementAtOrNull(2)),
2644+
"round" => SassCalculation.roundInternal(arguments[0],
2645+
arguments.elementAtOrNull(1), arguments.elementAtOrNull(2),
2646+
span: node.span,
2647+
inLegacySassFunction: inLegacySassFunction,
2648+
warn: (message, [deprecation]) =>
2649+
_warn(message, node.span, deprecation)),
26392650
"clamp" => SassCalculation.clamp(arguments[0],
26402651
arguments.elementAtOrNull(1), arguments.elementAtOrNull(2)),
26412652
_ => throw UnsupportedError('Unknown calculation name "${node.name}".')
@@ -2731,11 +2742,13 @@ final class _EvaluateVisitor
27312742

27322743
/// Evaluates [node] as a component of a calculation.
27332744
///
2734-
/// If [inLegacySassFunction] is `true`, this allows unitless numbers to be added and
2735-
/// subtracted with numbers with units, for backwards-compatibility with the
2736-
/// old global `min()`, `max()`, `round()`, and `abs()` functions.
2745+
/// If [inLegacySassFunction] isn't null, this allows unitless numbers to be
2746+
/// added and subtracted with numbers with units, for backwards-compatibility
2747+
/// with the old global `min()`, `max()`, `round()`, and `abs()` functions.
2748+
/// The parameter is the name of the function, which is used for reporting
2749+
/// deprecation warnings.
27372750
Object _visitCalculationExpression(Expression node,
2738-
{required bool inLegacySassFunction}) {
2751+
{required String? inLegacySassFunction}) {
27392752
switch (node) {
27402753
case ParenthesizedExpression(expression: var inner):
27412754
var result = _visitCalculationExpression(inner,
@@ -2766,7 +2779,9 @@ final class _EvaluateVisitor
27662779
_visitCalculationExpression(right,
27672780
inLegacySassFunction: inLegacySassFunction),
27682781
inLegacySassFunction: inLegacySassFunction,
2769-
simplify: !_inSupportsDeclaration));
2782+
simplify: !_inSupportsDeclaration,
2783+
warn: (message, [deprecation]) =>
2784+
_warn(message, node.span, deprecation)));
27702785

27712786
case NumberExpression() ||
27722787
VariableExpression() ||

pkg/sass-parser/CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
## 0.4.5-dev
1+
## 0.4.5
22

33
* Add support for parsing the `@forward` rule.
44

pkg/sass-parser/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sass-parser",
3-
"version": "0.4.5-dev",
3+
"version": "0.4.5",
44
"description": "A PostCSS-compatible wrapper of the official Sass parser",
55
"repository": "sass/sass",
66
"author": "Google Inc.",

pkg/sass_api/CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
## 14.2.0-dev
1+
## 14.2.0
22

33
* No user-visible changes.
44

pkg/sass_api/pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: sass_api
22
# Note: Every time we add a new Sass AST node, we need to bump the *major*
33
# version because it's a breaking change for anyone who's implementing the
44
# visitor interface(s).
5-
version: 14.2.0-dev
5+
version: 14.2.0
66
description: Additional APIs for Dart Sass.
77
homepage: https://github.com/sass/dart-sass
88

pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: sass
2-
version: 1.81.0-dev
2+
version: 1.81.0
33
description: A Sass implementation in Dart.
44
homepage: https://github.com/sass/dart-sass
55

0 commit comments

Comments
 (0)