Skip to content

Commit cbdcd58

Browse files
authored
Emergency fix: Temporarily drop support for min/max calculations (#1477)
See sass/sass#3142 See sass/sass-spec#1702
1 parent bb08672 commit cbdcd58

File tree

5 files changed

+185
-17
lines changed

5 files changed

+185
-17
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
## 1.40.1
2+
3+
* **Potentially breaking bug fix:** `min()` and `max()` expressions outside of
4+
calculations now behave the same way they did in 1.39.2, returning unquoted
5+
strings if they contain no Sass-specific features and calling the global
6+
`min()` and `max()` functions otherwise. Within calculations, they continue to
7+
behave how they did in 1.40.0.
8+
9+
This fixes an unintended breaking change added in 1.40.0, wherein passing a
10+
unitless number and a number without units to `min()` or `max()` now produces
11+
an error. Since this breakage affects a major Sass library, we're temporarily
12+
reverting support for `min()` and `max()` calculations while we work on
13+
designing a longer-term fix.
14+
115
## 1.40.0
216

317
* Add support for first-class `calc()` expressions (as well as `clamp()` and

lib/src/parse/stylesheet.dart

Lines changed: 164 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2714,6 +2714,25 @@ abstract class StylesheetParser extends Parser {
27142714
..writeCharCode($lparen);
27152715
break;
27162716

2717+
case "min":
2718+
case "max":
2719+
// min() and max() are parsed as the plain CSS mathematical functions if
2720+
// possible, and otherwise are parsed as normal Sass functions.
2721+
var beginningOfContents = scanner.state;
2722+
if (!scanner.scanChar($lparen)) return null;
2723+
whitespace();
2724+
2725+
var buffer = InterpolationBuffer()
2726+
..write(name)
2727+
..writeCharCode($lparen);
2728+
2729+
if (!_tryMinMaxContents(buffer)) {
2730+
scanner.state = beginningOfContents;
2731+
return null;
2732+
}
2733+
2734+
return StringExpression(buffer.interpolation(scanner.spanFrom(start)));
2735+
27172736
case "progid":
27182737
if (!scanner.scanChar($colon)) return null;
27192738
buffer = InterpolationBuffer()
@@ -2748,7 +2767,11 @@ abstract class StylesheetParser extends Parser {
27482767
///
27492768
/// Assumes the scanner is positioned immediately before the opening
27502769
/// parenthesis of the argument list.
2751-
CalculationExpression? _tryCalculation(String name, LineScannerState start) {
2770+
///
2771+
/// If [allowMinMax] is `true`, this parses `min()` and `max()` functions as
2772+
/// calculations.
2773+
CalculationExpression? _tryCalculation(String name, LineScannerState start,
2774+
{bool allowMinMax = false}) {
27522775
assert(scanner.peekChar() == $lparen);
27532776
switch (name) {
27542777
case "calc":
@@ -2757,18 +2780,9 @@ abstract class StylesheetParser extends Parser {
27572780

27582781
case "min":
27592782
case "max":
2760-
// min() and max() are parsed as calculations if possible, and otherwise
2761-
// are parsed as normal Sass functions.
2762-
var beforeArguments = scanner.state;
2763-
List<Expression> arguments;
2764-
try {
2765-
arguments = _calculationArguments();
2766-
} on FormatException catch (_) {
2767-
scanner.state = beforeArguments;
2768-
return null;
2769-
}
2770-
2771-
return CalculationExpression(name, arguments, scanner.spanFrom(start));
2783+
if (!allowMinMax) return null;
2784+
return CalculationExpression(
2785+
name, _calculationArguments(), scanner.spanFrom(start));
27722786

27732787
case "clamp":
27742788
var arguments = _calculationArguments(3);
@@ -2779,6 +2793,142 @@ abstract class StylesheetParser extends Parser {
27792793
}
27802794
}
27812795

2796+
/// Consumes the contents of a plain-CSS `min()` or `max()` function into
2797+
/// [buffer] if one is available.
2798+
///
2799+
/// Returns whether this succeeded.
2800+
///
2801+
/// If [allowComma] is `true` (the default), this allows `CalcValue`
2802+
/// productions separated by commas.
2803+
bool _tryMinMaxContents(InterpolationBuffer buffer,
2804+
{bool allowComma = true}) {
2805+
// The number of open parentheses that need to be closed.
2806+
while (true) {
2807+
var next = scanner.peekChar();
2808+
switch (next) {
2809+
case $minus:
2810+
case $plus:
2811+
case $0:
2812+
case $1:
2813+
case $2:
2814+
case $3:
2815+
case $4:
2816+
case $5:
2817+
case $6:
2818+
case $7:
2819+
case $8:
2820+
case $9:
2821+
case $dot:
2822+
try {
2823+
buffer.write(rawText(_number));
2824+
} on FormatException catch (_) {
2825+
return false;
2826+
}
2827+
break;
2828+
2829+
case $hash:
2830+
if (scanner.peekChar(1) != $lbrace) return false;
2831+
buffer.add(singleInterpolation());
2832+
break;
2833+
2834+
case $c:
2835+
case $C:
2836+
switch (scanner.peekChar(1)) {
2837+
case $a:
2838+
case $A:
2839+
if (!_tryMinMaxFunction(buffer, "calc")) return false;
2840+
break;
2841+
2842+
case $l:
2843+
case $L:
2844+
if (!_tryMinMaxFunction(buffer, "clamp")) return false;
2845+
break;
2846+
}
2847+
break;
2848+
2849+
case $e:
2850+
case $E:
2851+
if (!_tryMinMaxFunction(buffer, "env")) return false;
2852+
break;
2853+
2854+
case $v:
2855+
case $V:
2856+
if (!_tryMinMaxFunction(buffer, "var")) return false;
2857+
break;
2858+
2859+
case $lparen:
2860+
buffer.writeCharCode(scanner.readChar());
2861+
if (!_tryMinMaxContents(buffer, allowComma: false)) return false;
2862+
break;
2863+
2864+
case $m:
2865+
case $M:
2866+
scanner.readChar();
2867+
if (scanIdentChar($i)) {
2868+
if (!scanIdentChar($n)) return false;
2869+
buffer.write("min(");
2870+
} else if (scanIdentChar($a)) {
2871+
if (!scanIdentChar($x)) return false;
2872+
buffer.write("max(");
2873+
} else {
2874+
return false;
2875+
}
2876+
if (!scanner.scanChar($lparen)) return false;
2877+
2878+
if (!_tryMinMaxContents(buffer)) return false;
2879+
break;
2880+
2881+
default:
2882+
return false;
2883+
}
2884+
2885+
whitespace();
2886+
2887+
next = scanner.peekChar();
2888+
switch (next) {
2889+
case $rparen:
2890+
buffer.writeCharCode(scanner.readChar());
2891+
return true;
2892+
2893+
case $plus:
2894+
case $minus:
2895+
case $asterisk:
2896+
case $slash:
2897+
buffer.writeCharCode($space);
2898+
buffer.writeCharCode(scanner.readChar());
2899+
buffer.writeCharCode($space);
2900+
break;
2901+
2902+
case $comma:
2903+
if (!allowComma) return false;
2904+
buffer.writeCharCode(scanner.readChar());
2905+
buffer.writeCharCode($space);
2906+
break;
2907+
2908+
default:
2909+
return false;
2910+
}
2911+
2912+
whitespace();
2913+
}
2914+
}
2915+
2916+
/// Consumes a function named [name] containing an
2917+
/// `InterpolatedDeclarationValue` if possible, and adds its text to [buffer].
2918+
///
2919+
/// Returns whether such a function could be consumed.
2920+
bool _tryMinMaxFunction(InterpolationBuffer buffer, String name) {
2921+
if (!scanIdentifier(name)) return false;
2922+
if (!scanner.scanChar($lparen)) return false;
2923+
buffer
2924+
..write(name)
2925+
..writeCharCode($lparen)
2926+
..addInterpolation(_interpolatedDeclarationValue(allowEmpty: true))
2927+
..writeCharCode($rparen);
2928+
if (!scanner.scanChar($rparen)) return false;
2929+
return true;
2930+
}
2931+
27822932
/// Consumes and returns arguments for a calculation expression, including the
27832933
/// opening and closing parentheses.
27842934
///
@@ -2876,7 +3026,7 @@ abstract class StylesheetParser extends Parser {
28763026
if (scanner.peekChar() != $lparen) scanner.error('Expected "(" or ".".');
28773027

28783028
var lowerCase = ident.toLowerCase();
2879-
var calculation = _tryCalculation(lowerCase, start);
3029+
var calculation = _tryCalculation(lowerCase, start, allowMinMax: true);
28803030
if (calculation != null) {
28813031
return calculation;
28823032
} else if (lowerCase == "if") {

pkg/sass_api/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 1.0.0-beta.10
2+
3+
* No user-visible changes.
4+
15
## 1.0.0-beta.9
26

37
* Add the `CalculationExpression` type to represent calculations in the Sass

pkg/sass_api/pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ 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: 1.0.0-beta.9
5+
version: 1.0.0-beta.10
66
description: Additional APIs for Dart Sass.
77
homepage: https://github.com/sass/dart-sass
88

99
environment:
1010
sdk: '>=2.12.0 <3.0.0'
1111

1212
dependencies:
13-
sass: 1.40.0
13+
sass: 1.40.1
1414

1515
dependency_overrides:
1616
sass: {path: ../..}

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: sass
2-
version: 1.40.0
2+
version: 1.40.1
33
description: A Sass implementation in Dart.
44
homepage: https://github.com/sass/dart-sass
55

0 commit comments

Comments
 (0)