@supports support.

This commit is contained in:
Natalie Weizenbaum 2016-08-30 01:00:49 -07:00 committed by Natalie Weizenbaum
parent 0e5f8040d2
commit 0fe62a04a6
15 changed files with 308 additions and 8 deletions

View File

@ -11,4 +11,5 @@ export 'css/media_rule.dart';
export 'css/node.dart';
export 'css/style_rule.dart';
export 'css/stylesheet.dart';
export 'css/supports_rule.dart';
export 'css/value.dart';

View File

@ -17,6 +17,4 @@ class CssMediaRule extends CssParentNode {
/*=T*/ accept/*<T>*/(CssVisitor/*<T>*/ visitor) =>
visitor.visitMediaRule(this);
String toString() => "@media ${queries.join(", ")} {${children.join(" ")}}";
}

View File

@ -0,0 +1,20 @@
// Copyright 2016 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'package:source_span/source_span.dart';
import '../../visitor/interface/css.dart';
import 'node.dart';
import 'value.dart';
class CssSupportsRule extends CssParentNode {
final CssValue<String> condition;
final FileSpan span;
CssSupportsRule(this.condition, this.span);
/*=T*/ accept/*<T>*/(CssVisitor/*<T>*/ visitor) =>
visitor.visitSupportsRule(this);
}

View File

@ -37,4 +37,10 @@ export 'sass/statement/plain_import.dart';
export 'sass/statement/return.dart';
export 'sass/statement/style_rule.dart';
export 'sass/statement/stylesheet.dart';
export 'sass/statement/supports_rule.dart';
export 'sass/statement/variable_declaration.dart';
export 'sass/supports_condition.dart';
export 'sass/supports_condition/declaration.dart';
export 'sass/supports_condition/interpolation.dart';
export 'sass/supports_condition/negation.dart';
export 'sass/supports_condition/operation.dart';

View File

@ -0,0 +1,25 @@
// Copyright 2016 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'package:source_span/source_span.dart';
import '../../../visitor/interface/statement.dart';
import '../statement.dart';
import '../supports_condition.dart';
class SupportsRule implements Statement {
final SupportsCondition condition;
final List<Statement> children;
final FileSpan span;
SupportsRule(this.condition, Iterable<Statement> children, this.span)
: children = new List.from(children);
/*=T*/ accept/*<T>*/(StatementVisitor/*<T>*/ visitor) =>
visitor.visitSupportsRule(this);
String toString() => "@supports $condition {${children.join(' ')}}";
}

View File

@ -0,0 +1,7 @@
// Copyright 2016 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'node.dart';
abstract class SupportsCondition extends SassNode {}

View File

@ -0,0 +1,20 @@
// Copyright 2016 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'package:source_span/source_span.dart';
import '../expression.dart';
import '../supports_condition.dart';
class SupportsDeclaration implements SupportsCondition {
final Expression name;
final Expression value;
final FileSpan span;
SupportsDeclaration(this.name, this.value, this.span);
String toString() => "($name: $value)";
}

View File

@ -0,0 +1,18 @@
// Copyright 2016 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'package:source_span/source_span.dart';
import '../expression.dart';
import '../supports_condition.dart';
class SupportsInterpolation implements SupportsCondition {
final Expression expression;
final FileSpan span;
SupportsInterpolation(this.expression, this.span);
String toString() => "#{$expression}";
}

View File

@ -0,0 +1,24 @@
// Copyright 2016 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'package:source_span/source_span.dart';
import '../supports_condition.dart';
import 'operation.dart';
class SupportsNegation implements SupportsCondition {
final SupportsCondition condition;
final FileSpan span;
SupportsNegation(this.condition, this.span);
String toString() {
if (condition is SupportsNegation || condition is SupportsOperation) {
return "not ($condition)";
} else {
return "not $condition";
}
}
}

View File

@ -0,0 +1,29 @@
// Copyright 2016 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'package:source_span/source_span.dart';
import '../supports_condition.dart';
import 'negation.dart';
class SupportsOperation implements SupportsCondition {
final SupportsCondition left;
final SupportsCondition right;
final String operator;
final FileSpan span;
SupportsOperation(this.left, this.right, this.operator, this.span);
String toString() =>
"${_parenthesize(left)} ${operator} ${_parenthesize(right)}";
String _parenthesize(SupportsCondition condition) =>
condition is SupportsNegation ||
(condition is SupportsOperation && condition.operator == operator)
? "($condition)"
: condition.toString();
}

View File

@ -312,6 +312,8 @@ class Parser {
return _mixinDeclaration(start);
case "return":
return _disallowedAtRule(start);
case "supports":
return _supportsRule(start);
default:
return _unknownAtRule(start, name);
}
@ -468,9 +470,14 @@ class Parser {
hasContent: _mixinHasContent);
}
Return _return(LineScannerState start) {
Return _return(LineScannerState start) =>
new Return(_expression(), _scanner.spanFrom(start));
SupportsRule _supportsRule(LineScannerState start) {
var condition = _supportsCondition();
_ignoreComments();
return new Return(_expression(), _scanner.spanFrom(start));
return new SupportsRule(
condition, _children(_ruleChild), _scanner.spanFrom(start));
}
AtRule _unknownAtRule(LineScannerState start, String name) {
@ -1513,6 +1520,87 @@ class Parser {
return buffer.interpolation(_scanner.spanFrom(start));
}
// ## Supports Conditions
SupportsCondition _supportsCondition() {
var start = _scanner.state;
var first = _scanner.peekChar();
if (first != $lparen && first != $hash) {
var start = _scanner.state;
_expectCaseInsensitive("not");
_ignoreComments();
return new SupportsNegation(
_supportsConditionInParens(), _scanner.spanFrom(start));
}
var condition = _supportsConditionInParens();
_ignoreComments();
while (_lookingAtInterpolatedIdentifier()) {
String operator;
if (_scanCaseInsensitive("or")) {
operator = "or";
} else {
_expectCaseInsensitive("and");
operator = "and";
}
_ignoreComments();
var right = _supportsConditionInParens();
condition = new SupportsOperation(
condition, right, operator, _scanner.spanFrom(start));
_ignoreComments();
}
return condition;
}
SupportsCondition _supportsConditionInParens() {
var start = _scanner.state;
if (_scanner.peekChar() == $hash) {
return new SupportsInterpolation(
_singleInterpolation(), _scanner.spanFrom(start));
}
_scanner.expectChar($lparen);
_ignoreComments();
var next = _scanner.peekChar();
if (next == $lparen || next == $hash) {
var condition = _supportsCondition();
_ignoreComments();
_scanner.expectChar($rparen);
return condition;
}
if (next == $n || next == $N) {
var negation = _trySupportsNegation();
if (negation != null) return negation;
}
var name = _expression();
_scanner.expectChar($colon);
_ignoreComments();
var value = _expression();
_scanner.expectChar($rparen);
return new SupportsDeclaration(name, value, _scanner.spanFrom(start));
}
// If this fails, it puts the cursor back at the beginning.
SupportsNegation _trySupportsNegation() {
var start = _scanner.state;
if (!_scanCaseInsensitive("not") || _scanner.isDone) {
_scanner.state = start;
return null;
}
var next = _scanner.peekChar();
if (!isWhitespace(next) && next != $lparen) {
_scanner.state = start;
return null;
}
return new SupportsNegation(
_supportsConditionInParens(), _scanner.spanFrom(start));
}
// ## Tokens
String _commentText() => _rawText(_ignoreComments);

View File

@ -12,4 +12,5 @@ abstract class CssVisitor<T> {
T visitMediaRule(CssMediaRule node);
T visitStyleRule(CssStyleRule node);
T visitStylesheet(CssStylesheet node);
T visitSupportsRule(CssSupportsRule node);
}

View File

@ -20,5 +20,6 @@ abstract class StatementVisitor<T> {
T visitReturn(Return node);
T visitStyleRule(StyleRule node);
T visitStylesheet(Stylesheet node);
T visitSupportsRule(SupportsRule node);
T visitVariableDeclaration(VariableDeclaration node);
}

View File

@ -337,6 +337,60 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
}, through: (node) => node is CssStyleRule, removeIfEmpty: true);
}
void visitSupportsRule(SupportsRule node) {
if (_declarationName != null) {
throw node.span.message(
"Supports rules may not be used within nested declarations.");
}
var condition = new CssValue(
_visitSupportsCondition(node.condition), node.condition.span);
_withParent(new CssSupportsRule(condition, node.span), () {
if (_selector == null) {
for (var child in node.children) {
child.accept(this);
}
} else {
// If we're in a style rule, copy it into the supports rule so that
// declarations immediately inside @supports have somewhere to go.
//
// For example, "a {@supports (a: b) {b: c}}" should produce "@supports
// (a: b) {a {b: c}}".
_withParent(new CssStyleRule(_selector, _selector.span), () {
for (var child in node.children) {
child.accept(this);
}
}, removeIfEmpty: true);
}
}, through: (node) => node is CssStyleRule);
}
String _visitSupportsCondition(SupportsCondition condition) {
if (condition is SupportsOperation) {
return "${_parenthesize(condition.left, condition.operator)} "
"${condition.operator} "
"${_parenthesize(condition.right, condition.operator)}";
} else if (condition is SupportsNegation) {
return "not ${_parenthesize(condition.condition)}";
} else if (condition is SupportsInterpolation) {
return condition.expression.accept(this);
} else if (condition is SupportsDeclaration) {
return "(${condition.name.accept(this)}: ${condition.value.accept(this)})";
} else {
return null;
}
}
String _parenthesize(SupportsCondition condition, [String operator]) {
if ((condition is SupportsNegation) ||
(condition is SupportsOperation &&
(operator == null || operator != condition.operator))) {
return "(${_visitSupportsCondition(condition)})";
} else {
return _visitSupportsCondition(condition);
}
}
void visitVariableDeclaration(VariableDeclaration node) {
_environment.setVariable(node.name, node.expression.accept(this),
global: node.isGlobal);

View File

@ -42,14 +42,11 @@ String selectorToCss(Selector selector) {
class _SerializeCssVisitor
implements CssVisitor, ValueVisitor, SelectorVisitor {
final OutputStyle _style;
final _buffer = new StringBuffer();
var _indentation = 0;
_SerializeCssVisitor({OutputStyle style})
: _style = style ?? OutputStyle.expanded;
_SerializeCssVisitor({OutputStyle style});
void visitStylesheet(CssStylesheet node) {
for (var child in node.children) {
@ -134,6 +131,17 @@ class _SerializeCssVisitor
_buffer.writeln();
}
void visitSupportsRule(CssSupportsRule node) {
_writeIndentation();
_buffer.write("@supports ");
_buffer.write(node.condition.value);
_buffer.writeCharCode($space);
_visitChildren(node.children);
// TODO: only add an extra newline if this is a group end
_buffer.writeln();
}
void visitDeclaration(CssDeclaration node) {
_writeIndentation();
_buffer.write(node.name.value);