diff --git a/CHANGELOG.md b/CHANGELOG.md index 5951ee2e..24cc5f5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ -## 1.6.3 +## 1.7.0 + +* Emit deprecation warnings for tokens such as `#abcd` that are ambiguous + between ID strings and hex colors with alpha channels. These will be + interpreted as colors in a release on or after 19 September 2018. + +* Parse unambiguous hex colors with alpha channels as colors. * Fix a bug where relative imports from files on the load path could look in the incorrect location. diff --git a/lib/src/parse/stylesheet.dart b/lib/src/parse/stylesheet.dart index 8bb7a462..a28418fe 100644 --- a/lib/src/parse/stylesheet.dart +++ b/lib/src/parse/stylesheet.dart @@ -1742,7 +1742,20 @@ abstract class StylesheetParser extends Parser { var identifier = _interpolatedIdentifier(); if (_isHexColor(identifier)) { scanner.state = afterHash; - return new ColorExpression(_hexColorContents(start)); + var color = _hexColorContents(start); + + var plain = identifier.asPlain; + if (plain.length == 4 || plain.length == 8) { + logger.warn(''' +The value "$color" is currently parsed as a string, but it will be parsed as a +color in a release on or after 19 September 2018. + +To continue parsing it as a string, use "unquote('$color')". +To parse it as a color, use "${color.toStringAsRgb()}". +''', span: color.originalSpan, deprecation: true); + } else { + return new ColorExpression(color); + } } var buffer = new InterpolationBuffer(); @@ -1753,22 +1766,39 @@ abstract class StylesheetParser extends Parser { /// Consumes the contents of a hex color, after the `#`. SassColor _hexColorContents(LineScannerState start) { - var red = _hexDigit(); - var green = _hexDigit(); - var blue = _hexDigit(); + var digit1 = _hexDigit(); + var digit2 = _hexDigit(); + var digit3 = _hexDigit(); - var next = scanner.peekChar(); - if (next != null && isHex(next)) { - red = (red << 4) + green; - green = (blue << 4) + _hexDigit(); - blue = (_hexDigit() << 4) + _hexDigit(); + int red; + int green; + int blue; + num alpha = 1; + if (!isHex(scanner.peekChar())) { + // #abc + red = (digit1 << 4) + digit1; + green = (digit2 << 4) + digit2; + blue = (digit3 << 4) + digit3; } else { - red = (red << 4) + red; - green = (green << 4) + green; - blue = (blue << 4) + blue; + var digit4 = _hexDigit(); + if (!isHex(scanner.peekChar())) { + // #abcd + red = (digit1 << 4) + digit1; + green = (digit2 << 4) + digit2; + blue = (digit3 << 4) + digit3; + alpha = ((digit4 << 4) + digit4) / 0xff; + } else { + red = (digit1 << 4) + digit2; + green = (digit3 << 4) + digit4; + blue = (_hexDigit() << 4) + _hexDigit(); + + if (isHex(scanner.peekChar())) { + alpha = ((_hexDigit() << 4) + _hexDigit()) / 0xff; + } + } } - return new SassColor.rgb(red, green, blue, 1, scanner.spanFrom(start)); + return new SassColor.rgb(red, green, blue, alpha, scanner.spanFrom(start)); } /// Returns whether [interpolation] is a plain string that can be parsed as a @@ -1776,7 +1806,12 @@ abstract class StylesheetParser extends Parser { bool _isHexColor(Interpolation interpolation) { var plain = interpolation.asPlain; if (plain == null) return false; - if (plain.length != 3 && plain.length != 6) return false; + if (plain.length != 3 && + plain.length != 4 && + plain.length != 6 && + plain.length != 8) { + return false; + } return plain.codeUnits.every(isHex); } diff --git a/lib/src/util/character.dart b/lib/src/util/character.dart index 7ec51c6d..cdc1c57e 100644 --- a/lib/src/util/character.dart +++ b/lib/src/util/character.dart @@ -43,10 +43,13 @@ bool isName(int character) => isNameStart(character) || isDigit(character) || character == $minus; /// Returns whether [character] is a hexadeicmal digit. -bool isHex(int character) => - isDigit(character) || - (character >= $a && character <= $f) || - (character >= $A && character <= $F); +bool isHex(int character) { + if (character == null) return false; + if (isDigit(character)) return true; + if (character >= $a && character <= $f) return true; + if (character >= $A && character <= $F) return true; + return false; +} /// Returns whether [character] is the beginning of a UTF-16 surrogate pair. bool isHighSurrogate(int character) => diff --git a/lib/src/value/color.dart b/lib/src/value/color.dart index 7e93cb78..fde45824 100644 --- a/lib/src/value/color.dart +++ b/lib/src/value/color.dart @@ -201,4 +201,20 @@ class SassColor extends Value implements ext.SassColor { return fuzzyRound(result * 255); } + + /// Returns an `rgb()` or `rgba()` function call that will evaluate to this + /// color. + String toStringAsRgb() { + var isOpaque = fuzzyEquals(alpha, 1); + var buffer = new StringBuffer(isOpaque ? "rgb" : "rgba") + ..write("($red, $green, $blue"); + + if (!isOpaque) { + // Write the alpha as a SassNumber to ensure it's valid CSS. + buffer.write(", ${new SassNumber(alpha)}"); + } + + buffer.write(")"); + return buffer.toString(); + } } diff --git a/pubspec.yaml b/pubspec.yaml index 935cc8dc..dc6f16ed 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.6.3-dev +version: 1.7.0 description: A Sass implementation in Dart. author: Dart Team homepage: https://github.com/sass/dart-sass