Skip to content

Commit

Permalink
Update implementation for lab() tests (#2093)
Browse files Browse the repository at this point in the history
  • Loading branch information
nex3 committed Sep 28, 2023
1 parent c273c45 commit 3cc99eb
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 39 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,10 @@ jobs:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
dart_channel: [stable]
include: [{os: ubuntu-latest, dart_channel: dev}]
# TODO(nweiz): Re-enable this when
# https://github.com/dart-lang/sdk/issues/52121#issuecomment-1728534228
# is addressed.
# include: [{os: ubuntu-latest, dart_channel: dev}]

steps:
- uses: actions/checkout@v3
Expand Down
106 changes: 77 additions & 29 deletions lib/src/functions/color.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import '../deprecation.dart';
import '../evaluation_context.dart';
import '../exception.dart';
import '../module/built_in.dart';
import '../parse/scss.dart';
import '../util/map.dart';
import '../util/nullable.dart';
import '../util/number.dart';
Expand Down Expand Up @@ -431,6 +432,17 @@ final module = BuiltInModule("color", functions: <Callable>[
_function("is-legacy", r"$color",
(arguments) => SassBoolean(arguments[0].assertColor("color").isLegacy)),

_function(
"is-missing",
r"$color, $channel",
(arguments) => SassBoolean(arguments[0]
.assertColor("color")
.isChannelMissing(
(arguments[1].assertString("channel")..assertQuoted("channel"))
.text,
colorName: "color",
channelName: "channel"))),

_function(
"is-in-gamut",
r"$color, $space: null",
Expand Down Expand Up @@ -1142,29 +1154,9 @@ Value _parseChannels(String functionName, Value input,
{ColorSpace? space, String? name}) {
if (input.isVar) return _functionString(functionName, [input]);

Value components;
Value? alphaValue;
switch (input.assertCommonListStyle(name, allowSlash: true)) {
case [var components_, var alphaValue_]
when input.separator == ListSeparator.slash:
components = components_;
alphaValue = alphaValue_;

case var inputList when input.separator == ListSeparator.slash:
throw SassScriptException(
"Only 2 slash-separated elements allowed, but ${inputList.length} "
"${pluralize('was', inputList.length, plural: 'were')} passed.");

case [..., SassString(hasQuotes: false, :var text)] when text.contains('/'):
return _functionString(functionName, [input]);

case [...var initial, SassNumber(asSlash: (var before, var after))]:
components = SassList([...initial, before], ListSeparator.space);
alphaValue = after;

case _:
components = input;
}
var parsedSlash = _parseSlashChannels(input, name: name);
if (parsedSlash == null) return _functionString(functionName, [input]);
var (components, alphaValue) = parsedSlash;

List<Value> channels;
SassString? spaceName;
Expand Down Expand Up @@ -1221,11 +1213,13 @@ Value _parseChannels(String functionName, Value input,
: _functionString(functionName, [input]);
}

var alpha = alphaValue == null
? 1.0
: _percentageOrUnitless(alphaValue.assertNumber(name), 1, 'alpha')
.clamp(0, 1)
.toDouble();
var alpha = switch (alphaValue) {
null => 1.0,
SassString(hasQuotes: false, text: 'none') => null,
_ => _percentageOrUnitless(alphaValue.assertNumber(name), 1, 'alpha')
.clamp(0, 1)
.toDouble()
};

// `space` will be null if either `components` or `spaceName` is a `var()`.
// Again, we check this here rather than returning early in those cases so
Expand Down Expand Up @@ -1255,10 +1249,64 @@ Value _parseChannels(String functionName, Value input,
fromRgbFunction: space == ColorSpace.rgb);
}

/// Parses [input]'s slash-separated third number and alpha value, if one
/// exists.
///
/// Returns a single value that contains the space-separated list of components,
/// and an alpha value if one was specified. If this channel set couldn't be
/// parsed and should be returned as-is, returns null.
///
/// Throws a [SassScriptException] if [input] is invalid. If [input] came from a
/// function argument, [name] is the argument name (without the `$`). It's used
/// for error reporting.
(Value components, Value? alpha)? _parseSlashChannels(Value input,
{String? name}) =>
switch (input.assertCommonListStyle(name, allowSlash: true)) {
[var components, var alphaValue]
when input.separator == ListSeparator.slash =>
(components, alphaValue),
var inputList when input.separator == ListSeparator.slash =>
throw SassScriptException(
"Only 2 slash-separated elements allowed, but ${inputList.length} "
"${pluralize('was', inputList.length, plural: 'were')} passed.",
name),
[...var initial, SassString(hasQuotes: false, :var text)] => switch (
text.split('/')) {
[_] => (input, null),
[var channel3 && 'none', var alpha] ||
[var channel3, var alpha && 'none'] =>
switch ((_parseNumberOrNone(channel3), _parseNumberOrNone(alpha))) {
(var channel3Value?, var alphaValue?) => (
SassList([...initial, channel3Value], ListSeparator.space),
alphaValue
),
_ => null
},
_ => null
},
[...var initial, SassNumber(asSlash: (var before, var after))] => (
SassList([...initial, before], ListSeparator.space),
after
),
_ => (input, null)
};

/// Parses [text] as either a Sass number or the unquoted Sass string "none".
///
/// If neither matches, returns null.
Value? _parseNumberOrNone(String text) {
if (text == 'none') return SassString('none', quotes: false);
try {
return ScssParser(text).parseNumber();
} on SassFormatException {
return null;
}
}

/// Creates a [SassColor] for the given [space] from the given channel values,
/// or throws a [SassScriptException] if the channel values are invalid.
SassColor _colorFromChannels(ColorSpace space, SassNumber? channel0,
SassNumber? channel1, SassNumber? channel2, double alpha,
SassNumber? channel1, SassNumber? channel2, double? alpha,
{bool fromRgbFunction = false}) {
switch (space) {
case ColorSpace.hsl:
Expand Down
5 changes: 5 additions & 0 deletions lib/src/parse/stylesheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ abstract class StylesheetParser extends Parser {

Expression parseExpression() => _parseSingleProduction(_expression);

SassNumber parseNumber() {
var expression = _parseSingleProduction(_number);
return SassNumber(expression.value, expression.unit);
}

VariableDeclaration parseVariableDeclaration() =>
_parseSingleProduction(() => lookingAtIdentifier()
? _variableDeclarationWithNamespace()
Expand Down
26 changes: 17 additions & 9 deletions lib/src/visitor/serialize.dart
Original file line number Diff line number Diff line change
Expand Up @@ -570,30 +570,34 @@ final class _SerializeVisitor
..write(value.space)
..writeCharCode($lparen);
_writeChannel(value.channel0OrNull);
if (!_isCompressed && value.space == ColorSpace.lab) {
if (!_isCompressed &&
value.space == ColorSpace.lab &&
!value.isChannel0Missing) {
_buffer.writeCharCode($percent);
}
_buffer.writeCharCode($space);
_writeChannel(value.channel1OrNull);
_buffer.writeCharCode($space);
_writeChannel(value.channel2OrNull);
_maybeWriteSlashAlpha(value.alpha);
_maybeWriteSlashAlpha(value);
_buffer.writeCharCode($rparen);

case ColorSpace.lch || ColorSpace.oklch:
_buffer
..write(value.space)
..writeCharCode($lparen);
_writeChannel(value.channel0OrNull);
if (!_isCompressed && value.space == ColorSpace.lch) {
if (!_isCompressed &&
value.space == ColorSpace.lch &&
!value.isChannel0Missing) {
_buffer.writeCharCode($percent);
}
_buffer.writeCharCode($space);
_writeChannel(value.channel1OrNull);
_buffer.writeCharCode($space);
_writeChannel(value.channel2OrNull);
if (!_isCompressed && !value.isChannel2Missing) _buffer.write('deg');
_maybeWriteSlashAlpha(value.alpha);
_maybeWriteSlashAlpha(value);
_buffer.writeCharCode($rparen);

case _:
Expand All @@ -602,7 +606,7 @@ final class _SerializeVisitor
..write(value.space)
..writeCharCode($space);
_writeBetween(value.channelsOrNull, ' ', _writeChannel);
_maybeWriteSlashAlpha(value.alpha);
_maybeWriteSlashAlpha(value);
_buffer.writeCharCode($rparen);
}
}
Expand Down Expand Up @@ -812,13 +816,17 @@ final class _SerializeVisitor
_buffer.writeCharCode(hexCharFor(color & 0xF));
}

/// Writes the alpha component of a color if [alpha] isn't 1.
void _maybeWriteSlashAlpha(double alpha) {
if (fuzzyEquals(alpha, 1)) return;
/// Writes the alpha component of [color] if it isn't 1.
void _maybeWriteSlashAlpha(SassColor color) {
if (fuzzyEquals(color.alpha, 1)) return;
_writeOptionalSpace();
_buffer.writeCharCode($slash);
_writeOptionalSpace();
_writeNumber(alpha);
if (color.isAlphaMissing) {
_buffer.write('none');
} else {
_writeNumber(color.alpha);
}
}

void visitFunction(SassFunction function) {
Expand Down

0 comments on commit 3cc99eb

Please sign in to comment.