Skip to content

Commit

Permalink
Add support for CSS round() with one unitless argument
Browse files Browse the repository at this point in the history
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
  • Loading branch information
nex3 committed Nov 13, 2024
1 parent d1fefb6 commit 45e2ffd
Show file tree
Hide file tree
Showing 11 changed files with 145 additions and 46 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 1.81.0

* Fix a few cases where deprecation warnings weren't being emitted for global
built-in functions whose names overlap with CSS calculations.

* Add support for the CSS `round()` calculation with a single argument, as long
as that argument might be a unitless number.

## 1.80.7

### Embedded Host
Expand Down
2 changes: 1 addition & 1 deletion lib/src/ast/css/style_rule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ abstract interface class CssStyleRule implements CssParentNode {

/// Whether this style rule was originally defined in a plain CSS stylesheet.
///
/// :nodoc:
/// @nodoc
@internal
bool get fromPlainCss;
}
4 changes: 2 additions & 2 deletions lib/src/js/value/calculation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ final JSClass calculationOperationClass = () {
_assertCalculationValue(left);
_assertCalculationValue(right);
return SassCalculation.operateInternal(operator, left, right,
inLegacySassFunction: false, simplify: false);
inLegacySassFunction: null, simplify: false, warn: null);
});

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

getJSClass(SassCalculation.operateInternal(
CalculationOperator.plus, SassNumber(1), SassNumber(1),
inLegacySassFunction: false, simplify: false))
inLegacySassFunction: null, simplify: false, warn: null))
.injectSuperclass(jsClass);
return jsClass;
}();
Expand Down
89 changes: 71 additions & 18 deletions lib/src/value/calculation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'dart:math' as math;

import 'package:charcode/charcode.dart';
import 'package:meta/meta.dart';
import 'package:source_span/source_span.dart';

import '../deprecation.dart';
import '../evaluation_context.dart';
Expand Down Expand Up @@ -482,13 +483,47 @@ final class SassCalculation extends Value {
/// 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]) {
[Object? numberOrStep, Object? step]) =>
roundInternal(strategyOrNumber, numberOrStep, step,
span: null, inLegacySassFunction: null, warn: null);

/// Like [round], but with the internal-only [inLegacySassFunction] and
/// [warn] parameters.
///
/// If [inLegacySassFunction] isn't null, this allows unitless numbers to be
/// added and subtracted with numbers with units, for backwards-compatibility
/// with the old global `min()` and `max()` functions. This emits a
/// deprecation warning using the string as the function's name.
///
/// If [simplify] is `false`, no simplification will be done.
///
/// The [warn] callback is used to surface deprecation warnings.
///
/// @nodoc
@internal
static Value roundInternal(
Object strategyOrNumber, Object? numberOrStep, Object? step,
{required FileSpan? span,
required String? inLegacySassFunction,
required void Function(String message, [Deprecation? deprecation])?
warn}) {
switch ((
_simplify(strategyOrNumber),
numberOrStep.andThen(_simplify),
step.andThen(_simplify)
)) {
case (SassNumber number, null, null):
case (SassNumber(hasUnits: false) && var number, null, null):
return SassNumber(number.value.round());

case (SassNumber number, null, null) when inLegacySassFunction != null:
warn!(
"In future versions of Sass, round() will be interpreted as a CSS "
"round() calculation. This requires an explicit modulus when "
"rounding numbers with units. If you want to use the Sass "
"function, call math.round() instead.\n"
"\n"
"See https://sass-lang.com/d/import",
Deprecation.globalBuiltin);
return _matchUnits(number.value.round().toDouble(), number);

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

case (SassString rest, null, null):
return SassCalculation._("round", [rest]);

case (var number, null, null):
throw SassScriptException(
"Single argument $number expected to be simplifiable.");
return SassCalculation._("round", [number]);

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

/// Like [operate], but with the internal-only [inLegacySassFunction] parameter.
/// Like [operate], but with the internal-only [inLegacySassFunction] and
/// [warn] parameters.
///
/// 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.
/// If [inLegacySassFunction] isn't null, this allows unitless numbers to be
/// added and subtracted with numbers with units, for backwards-compatibility
/// with the old global `min()` and `max()` functions. This emits a
/// deprecation warning using the string as the function's name.
///
/// If [simplify] is `false`, no simplification will be done.
///
/// The [warn] callback is used to surface deprecation warnings.
///
/// @nodoc
@internal
static Object operateInternal(
CalculationOperator operator, Object left, Object right,
{required bool inLegacySassFunction, required bool simplify}) {
{required String? inLegacySassFunction,
required bool simplify,
required void Function(String message, [Deprecation? deprecation])?
warn}) {
if (!simplify) return CalculationOperation._(operator, left, right);
left = _simplify(left);
right = _simplify(right);

if (operator case CalculationOperator.plus || CalculationOperator.minus) {
if (left is SassNumber &&
right is SassNumber &&
(inLegacySassFunction
? left.isComparableTo(right)
: left.hasCompatibleUnits(right))) {
if (left is SassNumber && right is SassNumber) {
var compatible = left.hasCompatibleUnits(right);
if (!compatible &&
inLegacySassFunction != null &&
left.isComparableTo(right)) {
warn!(
"In future versions of Sass, $inLegacySassFunction() will be "
"interpreted as the CSS $inLegacySassFunction() calculation. "
"This doesn't allow unitless numbers to be mixed with numbers "
"with units. If you want to use the Sass function, call "
"math.$inLegacySassFunction() instead.\n"
"\n"
"See https://sass-lang.com/d/import",
Deprecation.globalBuiltin);
compatible = true;
}
if (compatible) {
return operator == CalculationOperator.plus
? left.plus(right)
: left.minus(right);
}
}

_verifyCompatibleNumbers([left, right]);
Expand Down
35 changes: 25 additions & 10 deletions lib/src/visitor/async_evaluate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2555,12 +2555,12 @@ final class _EvaluateVisitor
// Note that the list of calculation functions is also tracked in
// lib/src/visitor/is_plain_css_safe.dart.
switch (node.name.toLowerCase()) {
case "min" || "max" || "round" || "abs"
case ("min" || "max" || "round" || "abs") && var name
when node.arguments.named.isEmpty &&
node.arguments.rest == null &&
node.arguments.positional
.every((argument) => argument.isCalculationSafe):
return await _visitCalculation(node, inLegacySassFunction: true);
return await _visitCalculation(node, inLegacySassFunction: name);

case "calc" ||
"clamp" ||
Expand Down Expand Up @@ -2607,8 +2607,15 @@ final class _EvaluateVisitor
return result;
}

/// Evaluates [node] as a calculation.
///
/// If [inLegacySassFunction] isn't null, 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.
/// The parameter is the name of the function, which is used for reporting
/// deprecation warnings.
Future<Value> _visitCalculation(FunctionExpression node,
{bool inLegacySassFunction = false}) async {
{String? inLegacySassFunction}) async {
if (node.arguments.named.isNotEmpty) {
throw _exception(
"Keyword arguments can't be used with calculations.", node.span);
Expand Down Expand Up @@ -2656,8 +2663,12 @@ final class _EvaluateVisitor
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)),
"round" => SassCalculation.roundInternal(arguments[0],
arguments.elementAtOrNull(1), arguments.elementAtOrNull(2),
span: node.span,
inLegacySassFunction: inLegacySassFunction,
warn: (message, [deprecation]) =>
_warn(message, node.span, deprecation)),
"clamp" => SassCalculation.clamp(arguments[0],
arguments.elementAtOrNull(1), arguments.elementAtOrNull(2)),
_ => throw UnsupportedError('Unknown calculation name "${node.name}".')
Expand Down Expand Up @@ -2753,11 +2764,13 @@ final class _EvaluateVisitor

/// Evaluates [node] as a component of a calculation.
///
/// 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()`, `max()`, `round()`, and `abs()` functions.
/// If [inLegacySassFunction] isn't null, 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.
/// The parameter is the name of the function, which is used for reporting
/// deprecation warnings.
Future<Object> _visitCalculationExpression(Expression node,
{required bool inLegacySassFunction}) async {
{required String? inLegacySassFunction}) async {
switch (node) {
case ParenthesizedExpression(expression: var inner):
var result = await _visitCalculationExpression(inner,
Expand Down Expand Up @@ -2788,7 +2801,9 @@ final class _EvaluateVisitor
await _visitCalculationExpression(right,
inLegacySassFunction: inLegacySassFunction),
inLegacySassFunction: inLegacySassFunction,
simplify: !_inSupportsDeclaration));
simplify: !_inSupportsDeclaration,
warn: (message, [deprecation]) =>
_warn(message, node.span, deprecation)));

case NumberExpression() ||
VariableExpression() ||
Expand Down
37 changes: 26 additions & 11 deletions lib/src/visitor/evaluate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// DO NOT EDIT. This file was generated from async_evaluate.dart.
// See tool/grind/synchronize.dart for details.
//
// Checksum: 3986f5db33dd220dcd971a39e8587ca4e52d9a3f
// Checksum: fbffa0dbe5a1af846dc83752457d39fb2984d280
//
// ignore_for_file: unused_import

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

case "calc" ||
"clamp" ||
Expand Down Expand Up @@ -2585,8 +2585,15 @@ final class _EvaluateVisitor
return result;
}

/// Evaluates [node] as a calculation.
///
/// If [inLegacySassFunction] isn't null, 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.
/// The parameter is the name of the function, which is used for reporting
/// deprecation warnings.
Value _visitCalculation(FunctionExpression node,
{bool inLegacySassFunction = false}) {
{String? inLegacySassFunction}) {
if (node.arguments.named.isNotEmpty) {
throw _exception(
"Keyword arguments can't be used with calculations.", node.span);
Expand Down Expand Up @@ -2634,8 +2641,12 @@ final class _EvaluateVisitor
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)),
"round" => SassCalculation.roundInternal(arguments[0],
arguments.elementAtOrNull(1), arguments.elementAtOrNull(2),
span: node.span,
inLegacySassFunction: inLegacySassFunction,
warn: (message, [deprecation]) =>
_warn(message, node.span, deprecation)),
"clamp" => SassCalculation.clamp(arguments[0],
arguments.elementAtOrNull(1), arguments.elementAtOrNull(2)),
_ => throw UnsupportedError('Unknown calculation name "${node.name}".')
Expand Down Expand Up @@ -2731,11 +2742,13 @@ final class _EvaluateVisitor

/// Evaluates [node] as a component of a calculation.
///
/// 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()`, `max()`, `round()`, and `abs()` functions.
/// If [inLegacySassFunction] isn't null, 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.
/// The parameter is the name of the function, which is used for reporting
/// deprecation warnings.
Object _visitCalculationExpression(Expression node,
{required bool inLegacySassFunction}) {
{required String? inLegacySassFunction}) {
switch (node) {
case ParenthesizedExpression(expression: var inner):
var result = _visitCalculationExpression(inner,
Expand Down Expand Up @@ -2766,7 +2779,9 @@ final class _EvaluateVisitor
_visitCalculationExpression(right,
inLegacySassFunction: inLegacySassFunction),
inLegacySassFunction: inLegacySassFunction,
simplify: !_inSupportsDeclaration));
simplify: !_inSupportsDeclaration,
warn: (message, [deprecation]) =>
_warn(message, node.span, deprecation)));

case NumberExpression() ||
VariableExpression() ||
Expand Down
4 changes: 4 additions & 0 deletions pkg/sass-parser/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.4.5

* No user-visible changes.

## 0.4.4

* No user-visible changes.
Expand Down
2 changes: 1 addition & 1 deletion pkg/sass-parser/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sass-parser",
"version": "0.4.4",
"version": "0.4.5",
"description": "A PostCSS-compatible wrapper of the official Sass parser",
"repository": "sass/sass",
"author": "Google Inc.",
Expand Down
4 changes: 4 additions & 0 deletions pkg/sass_api/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 14.2.0

* No user-visible changes.

## 14.1.3

* No user-visible changes.
Expand Down
4 changes: 2 additions & 2 deletions pkg/sass_api/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ 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: 14.1.3
version: 14.2.0
description: Additional APIs for Dart Sass.
homepage: https://github.com/sass/dart-sass

environment:
sdk: ">=3.3.0 <4.0.0"

dependencies:
sass: 1.80.7
sass: 1.81.0

dev_dependencies:
dartdoc: ^8.0.14
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: sass
version: 1.80.7
version: 1.81.0
description: A Sass implementation in Dart.
homepage: https://github.com/sass/dart-sass

Expand Down

0 comments on commit 45e2ffd

Please sign in to comment.