From e7024437d860f0b5b6236d526684f4eff52ace8b Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 18 Oct 2016 15:17:46 -0700 Subject: [PATCH] Use Ruby Sass string semantics. --- lib/src/ast/sass/expression/string.dart | 10 +++++++-- lib/src/parse/stylesheet.dart | 29 +++++++++++++++++++++++-- lib/src/value/string.dart | 12 ++++++++-- lib/src/visitor/serialize.dart | 5 ----- 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/lib/src/ast/sass/expression/string.dart b/lib/src/ast/sass/expression/string.dart index c5bd574e..ba5e0b14 100644 --- a/lib/src/ast/sass/expression/string.dart +++ b/lib/src/ast/sass/expression/string.dart @@ -13,8 +13,7 @@ import '../interpolation.dart'; /// A string literal. class StringExpression implements Expression { - /// Interpolation that, when evaluated, produces the semantic content of this - /// string. + /// Interpolation that, when evaluated, produces the contents of this string. /// /// Unlike [asInterpolation], escapes are resolved and quotes are not /// included. @@ -41,7 +40,14 @@ class StringExpression implements Expression { /// /// Unlike [text], his doesn't resolve escapes and does include quotes for /// quoted strings. + /// + /// If [static] is true, this escapes any `#{` sequences in the string. If + /// [quote] is passed and this is a quoted string, it uses that character as + /// the quote mark; otherwise, it determines the best quote to add by looking + /// at the string. Interpolation asInterpolation({bool static: false, int quote}) { + if (!hasQuotes) return text; + quote ??= hasQuotes ? null : _bestQuote(); var buffer = new InterpolationBuffer(); if (quote != null) buffer.writeCharCode(quote); diff --git a/lib/src/parse/stylesheet.dart b/lib/src/parse/stylesheet.dart index 81221a4a..6b393185 100644 --- a/lib/src/parse/stylesheet.dart +++ b/lib/src/parse/stylesheet.dart @@ -1882,7 +1882,7 @@ abstract class StylesheetParser extends Parser { } else if (isNameStart(first)) { buffer.writeCharCode(scanner.readChar()); } else if (first == $backslash) { - buffer.writeCharCode(escape()); + _scanEscapeText(buffer); } else if (first == $hash && scanner.peekChar(1) == $lbrace) { buffer.add(singleInterpolation()); } @@ -1897,7 +1897,7 @@ abstract class StylesheetParser extends Parser { next >= 0x0080) { buffer.writeCharCode(scanner.readChar()); } else if (next == $backslash) { - buffer.writeCharCode(escape()); + _scanEscapeText(buffer); } else if (next == $hash && scanner.peekChar(1) == $lbrace) { buffer.add(singleInterpolation()); } else { @@ -1908,6 +1908,31 @@ abstract class StylesheetParser extends Parser { return buffer.interpolation(scanner.spanFrom(start)); } + /// Consumes an escape sequence and writes the characters that compose it to + /// [buffer]. + void _scanEscapeText(StringSink buffer) { + scanner.expectChar($backslash); + buffer.writeCharCode($backslash); + + var first = scanner.peekChar(); + if (first == null) { + return; + } else if (isNewline(first)) { + scanner.error("Expected escape sequence."); + } else if (isHex(first)) { + for (var i = 0; i < 6; i++) { + var next = scanner.peekChar(); + if (next == null || !isHex(next)) break; + buffer.writeCharCode(scanner.readChar()); + } + if (isWhitespace(scanner.peekChar())) { + buffer.writeCharCode(scanner.readChar()); + } + } else { + buffer.writeCharCode(scanner.readChar()); + } + } + /// Consumes interpolation. Expression singleInterpolation() { scanner.expect('#{'); diff --git a/lib/src/value/string.dart b/lib/src/value/string.dart index e06287b5..13af63da 100644 --- a/lib/src/value/string.dart +++ b/lib/src/value/string.dart @@ -14,8 +14,16 @@ import '../value.dart'; class SassString extends Value { /// The contents of the string. /// - /// This is the semantic content—any escape sequences are resolved to their - /// Unicode values. + /// For quoted strings, this is the semantic content—any escape sequences that + /// were been written in the source text are resolved to their Unicode values. + /// For unquoted strings, though, escape sequences are preserved as literal + /// backslashes. + /// + /// This difference allows us to distinguish between identifiers with escapes, + /// such as `url\u28 http://example.com\u29`, and unquoted strings that + /// contain characters that aren't valid in identifiers, such as + /// `url(http://example.com)`. Unfortunately, it also means that we don't + /// consider `foo` and `f\6F\6F` the same string. final String text; /// Whether this string has quotes. diff --git a/lib/src/visitor/serialize.dart b/lib/src/visitor/serialize.dart index 5d691321..a24593a3 100644 --- a/lib/src/visitor/serialize.dart +++ b/lib/src/visitor/serialize.dart @@ -594,11 +594,6 @@ class _SerializeCssVisitor for (var i = 0; i < string.length; i++) { var char = string.codeUnitAt(i); switch (char) { - case $backslash: - _buffer.writeCharCode($backslash); - _buffer.writeCharCode($backslash); - break; - case $lf: _buffer.writeCharCode($space); break;