From be8b26191ded69dcda5804e1b9f74ac36e8c56c1 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Sun, 23 Oct 2016 17:01:21 -0700 Subject: [PATCH] Support import clauses. --- lib/src/ast/css/import.dart | 10 ++++- .../ast/sass/statement/plain_import_rule.dart | 23 +++++++++- lib/src/parse/stylesheet.dart | 44 +++++++++++++++++-- lib/src/visitor/perform.dart | 12 ++++- lib/src/visitor/serialize.dart | 11 +++++ 5 files changed, 93 insertions(+), 7 deletions(-) diff --git a/lib/src/ast/css/import.dart b/lib/src/ast/css/import.dart index c9375d42..11972ddd 100644 --- a/lib/src/ast/css/import.dart +++ b/lib/src/ast/css/import.dart @@ -5,6 +5,7 @@ import 'package:source_span/source_span.dart'; import '../../visitor/interface/css.dart'; +import 'media_query.dart'; import 'node.dart'; import 'value.dart'; @@ -15,9 +16,16 @@ class CssImport extends CssNode { /// This includes quotes. final CssValue url; + /// The supports condition attached to this import. + final CssValue supports; + + /// The media query attached to this import. + final List media; + final FileSpan span; - CssImport(this.url, this.span); + CssImport(this.url, this.span, {this.supports, Iterable media}) + : media = media == null ? null : new List.unmodifiable(media); /*=T*/ accept/**/(CssVisitor/**/ visitor) => visitor.visitImport(this); } diff --git a/lib/src/ast/sass/statement/plain_import_rule.dart b/lib/src/ast/sass/statement/plain_import_rule.dart index 148285f8..63f05665 100644 --- a/lib/src/ast/sass/statement/plain_import_rule.dart +++ b/lib/src/ast/sass/statement/plain_import_rule.dart @@ -2,11 +2,14 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +import 'package:charcode/charcode.dart'; import 'package:source_span/source_span.dart'; import '../../../visitor/interface/statement.dart'; import '../interpolation.dart'; +import '../media_query.dart'; import '../statement.dart'; +import '../supports_condition.dart'; /// A rule that produces a plain CSS `@import` rule. class PlainImportRule implements Statement { @@ -15,12 +18,28 @@ class PlainImportRule implements Statement { /// This already contains quotes. final Interpolation url; + /// The supports condition attached to this import, or `null` if no condition + /// is attached. + final SupportsCondition supports; + + /// The media query attached to this import, or `null` if no condition is + /// attached. + final List media; + final FileSpan span; - PlainImportRule(this.url, this.span); + PlainImportRule(this.url, this.span, + {this.supports, Iterable media}) + : media = media == null ? null : new List.unmodifiable(media); /*=T*/ accept/**/(StatementVisitor/**/ visitor) => visitor.visitPlainImportRule(this); - String toString() => "@import $url;"; + String toString() { + var buffer = new StringBuffer("@import $url"); + if (supports != null) buffer.write(" supports($supports)"); + if (media != null) buffer.write(" $media"); + buffer.writeCharCode($semicolon); + return buffer.toString(); + } } diff --git a/lib/src/parse/stylesheet.dart b/lib/src/parse/stylesheet.dart index 6809da0b..ec8aa406 100644 --- a/lib/src/parse/stylesheet.dart +++ b/lib/src/parse/stylesheet.dart @@ -625,23 +625,29 @@ abstract class StylesheetParser extends Parser { /// /// [start] should point before the `@`. Statement _importRule(LineScannerState start) { - // TODO: parse supports clauses, url(), and query lists var urlStart = scanner.state; var next = scanner.peekChar(); if (next == $u || next == $U) { var url = _dynamicUrl(); + whitespace(); + var queries = _tryImportQueries(); _expectStatementSeparator(); return new PlainImportRule( new Interpolation([url], scanner.spanFrom(urlStart)), - scanner.spanFrom(start)); + scanner.spanFrom(start), + supports: queries?.item1, + media: queries?.item2); } var url = string(); + whitespace(); + var queries = _tryImportQueries(); if (_isPlainImportUrl(url)) { var interpolation = new Interpolation( [scanner.substring(urlStart.position)], scanner.spanFrom(urlStart)); _expectStatementSeparator(); - return new PlainImportRule(interpolation, scanner.spanFrom(start)); + return new PlainImportRule(interpolation, scanner.spanFrom(start), + supports: queries?.item1, media: queries?.item2); } else if (_inControlDirective || _inMixin) { _disallowedAtRule(start); return null; @@ -666,6 +672,38 @@ abstract class StylesheetParser extends Parser { return url.startsWith("http://") || url.startsWith("https://"); } + /// Consumes a supports condition and/or a media query after an `@import`. + /// + /// Returns `null` if neither type of query can be found. + Tuple2> _tryImportQueries() { + SupportsCondition supports; + if (scanIdentifier("supports", ignoreCase: true)) { + scanner.expectChar($lparen); + var start = scanner.state; + if (scanIdentifier("not", ignoreCase: true)) { + whitespace(); + supports = new SupportsNegation( + _supportsConditionInParens(), scanner.spanFrom(start)); + } else { + var name = _expression(); + scanner.expectChar($colon); + whitespace(); + var value = _expression(); + supports = + new SupportsDeclaration(name, value, scanner.spanFrom(start)); + } + scanner.expectChar($rparen); + whitespace(); + } + + var media = + _lookingAtInterpolatedIdentifier() || scanner.peekChar() == $lparen + ? _mediaQueryList() + : null; + if (supports == null && media == null) return null; + return new Tuple2(supports, media); + } + /// Consumes an `@include` rule. /// /// [start] should point before the `@`. diff --git a/lib/src/visitor/perform.dart b/lib/src/visitor/perform.dart index 45e46c50..b79ec0d4 100644 --- a/lib/src/visitor/perform.dart +++ b/lib/src/visitor/perform.dart @@ -609,7 +609,17 @@ class _PerformVisitor } Value visitPlainImportRule(PlainImportRule node) { - _parent.addChild(new CssImport(_interpolationToValue(node.url), node.span)); + var url = _interpolationToValue(node.url); + var supports = node.supports; + var resolvedSupports = supports is SupportsDeclaration + ? "${supports.name.accept(this).toCssString()}: " + "${supports.value.accept(this).toCssString()})" + : (supports == null ? null : _visitSupportsCondition(supports)); + _parent.addChild(new CssImport(url, node.span, + supports: resolvedSupports == null + ? null + : new CssValue(resolvedSupports, node.supports.span), + media: node?.media?.map(_visitMediaQuery))); return null; } diff --git a/lib/src/visitor/serialize.dart b/lib/src/visitor/serialize.dart index 57f2e007..bd2acb01 100644 --- a/lib/src/visitor/serialize.dart +++ b/lib/src/visitor/serialize.dart @@ -141,6 +141,17 @@ class _SerializeCssVisitor _writeIndentation(); _buffer.write("@import "); _buffer.write(node.url.value); + + if (node.supports != null) { + _buffer.writeCharCode($space); + _buffer.write(node.supports.value); + } + + if (node.media != null) { + _buffer.writeCharCode($space); + _writeBetween(node.media, ', ', visitMediaQuery); + } + _buffer.writeCharCode($semicolon); }