Nested properties.

This commit is contained in:
Natalie Weizenbaum 2016-08-15 21:15:04 -07:00
parent dbf2c18ae6
commit cf086471c6
4 changed files with 102 additions and 19 deletions

View File

@ -4,7 +4,6 @@
import 'package:source_span/source_span.dart';
import '../../utils.dart';
import '../../visitor/interface/statement.dart';
import 'expression.dart';
import 'expression/interpolation.dart';
@ -15,9 +14,12 @@ class Declaration implements Statement {
final Expression value;
FileSpan get span => spanForList([name, value]);
final List<Statement> children;
Declaration(this.name, this.value);
final FileSpan span;
Declaration(this.name, {this.value, Iterable<Statement> children, this.span})
: children = children == null ? null : new List.unmodifiable(children);
/*=T*/ accept/*<T>*/(StatementVisitor/*<T>*/ visitor) =>
visitor.visitDeclaration(this);

View File

@ -182,7 +182,6 @@ class Parser {
new InterpolationExpression([], span: _scanner.emptySpan));
}
// TODO: parse static values specially?
return _expression();
}
@ -234,7 +233,7 @@ class Parser {
/// attempted; or it can return a [Declaration], indicating that it
/// successfully consumed a declaration.
dynamic _declarationOrBuffer() {
var nameStart = _scanner.state;
var start = _scanner.state;
var nameBuffer = new InterpolationBuffer();
// Allow the "*prop: val", ":prop: val", "#prop: val", and ".prop: val"
@ -256,14 +255,14 @@ class Parser {
midBuffer.writeCharCode($colon);
// Parse custom properties as declarations no matter what.
var name = nameBuffer.interpolation(_scanner.spanFrom(nameStart));
var name = nameBuffer.interpolation(_scanner.spanFrom(start));
if (name.initialPlain.startsWith('--')) {
var value = _declarationValue();
var next = _scanner.peekChar();
if (next != $semicolon && next != $rbrace) {
_scanner.expectChar($semicolon);
}
return new Declaration(name, value);
return new Declaration(name, value: value);
}
if (_scanner.scanChar($colon)) {
@ -271,6 +270,12 @@ class Parser {
}
var postColonWhitespace = _commentText();
if (_scanner.peekChar() == $lbrace) {
return new Declaration(name,
children: _declarationChildren(),
span: _scanner.spanFrom(start));
}
midBuffer.write(postColonWhitespace);
var couldBeSelector =
postColonWhitespace.isEmpty && _lookingAtInterpolatedIdentifier();
@ -283,7 +288,7 @@ class Parser {
// Properties that are ambiguous with selectors can't have additional
// properties nested beneath them, so we force an error.
if (couldBeSelector) _scanner.expectChar($semicolon);
} else if (next != $semicolon && next != $rbrace) {
} else if (next != $semicolon && next != $lbrace && next != $rbrace) {
// Force an exception if there isn't a valid end-of-property character
// but don't consume that character.
_scanner.expectChar($semicolon);
@ -301,9 +306,68 @@ class Parser {
return nameBuffer;
}
return new Declaration(
name,
value: value,
children: _scanner.peekChar() == $lbrace
? _declarationChildren()
: null,
span: _scanner.spanFrom(start));
}
List<Statement> _declarationChildren() {
_scanner.expectChar($lbrace);
var children = <Statement>[];
loop: while (true) {
children.addAll(_comments());
switch (_scanner.peekChar()) {
case $dollar:
children.add(_variableDeclaration());
break;
case $at:
children.add(_atRule());
break;
case $semicolon:
_scanner.readChar();
break;
case $rbrace:
break loop;
default:
children.add(_declaration());
break;
}
}
children.addAll(_comments());
_scanner.expectChar($rbrace);
return children;
}
Declaration _declaration() {
var start = _scanner.state;
var name = _interpolatedIdentifier();
_ignoreComments();
// TODO: nested properties
return new Declaration(name, value);
_scanner.expectChar($colon);
_ignoreComments();
if (_scanner.peekChar() == $lbrace) {
return new Declaration(name,
children: _declarationChildren(),
span: _scanner.spanFrom(start));
}
var value = _declarationExpression();
return new Declaration(
name,
value: value,
children: _scanner.peekChar() == $lbrace
? _declarationChildren()
: null,
span: _scanner.spanFrom(start));
}
/// Consumes whitespace if available and returns any comments it contained.

View File

@ -6,10 +6,17 @@ import '../../ast/sass/statement.dart';
abstract class StatementVisitor<T> {
T visitComment(Comment node) => null;
T visitDeclaration(Declaration node) => null;
T visitExtendRule(ExtendRule node) => null;
T visitVariableDeclaration(VariableDeclaration node) => null;
T visitDeclaration(Declaration node) {
if (node.children == null) return null;
for (var child in node.children) {
child.accept(this);
}
return null;
}
T visitAtRule(AtRule node) {
if (node.children == null) return null;
for (var child in node.children) {

View File

@ -29,6 +29,9 @@ class PerformVisitor extends StatementVisitor {
/// The current parent node in the output CSS tree.
CssParentNode _parent;
/// The name of the current declaration parent.
String _declarationName;
final _extender = new Extender();
PerformVisitor() : this._(new Environment());
@ -53,17 +56,24 @@ class PerformVisitor extends StatementVisitor {
void visitDeclaration(Declaration node) {
var name = _performInterpolation(node.name);
var cssValue = _performExpression(node.value);
var value = cssValue.value;
if (_declarationName != null) {
name = new CssValue("$_declarationName-${name.value}", span: name.span);
}
var cssValue = node.value == null ? null : _performExpression(node.value);
// Don't abort for an empty list because converting it to CSS will throw an
// error that we want to user to see.
if (value.isBlank &&
!(value is SassList && value.contents.isEmpty)) {
return;
// If the value is an empty list, preserve it, because converting it to CSS
// will throw an error that we want the user to see.
if (cssValue != null &&
(!cssValue.value.isBlank || cssValue.value is SassList)) {
_parent.addChild(new CssDeclaration(name, cssValue, span: node.span));
}
_parent.addChild(new CssDeclaration(name, cssValue, span: node.span));
if (node.children != null) {
var oldDeclarationName = _declarationName;
_declarationName = name.value;
super.visitDeclaration(node);
_declarationName = oldDeclarationName;
}
}
void visitExtendRule(ExtendRule node) {