mirror of
https://github.com/danog/dart-sass.git
synced 2024-11-27 04:34:59 +01:00
Make CssNodes directly mutable.
This shaves off a significant amount of time, since now we don't have to do what is essentially a full copy of the CSS tree. We may want to use interfaces to provide an immutable view of the CSS tree for use outside of the perform visitor.
This commit is contained in:
parent
a4a7ec1bd9
commit
e8756eb02d
@ -5,29 +5,21 @@
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import '../../visitor/css.dart';
|
||||
import '../parent.dart';
|
||||
import 'node.dart';
|
||||
import 'value.dart';
|
||||
|
||||
class CssAtRule implements CssNode, Parent<CssNode, CssAtRule> {
|
||||
class CssAtRule extends CssParentNode {
|
||||
final String name;
|
||||
|
||||
final CssValue<String> value;
|
||||
|
||||
final List<CssNode> children;
|
||||
|
||||
final FileSpan span;
|
||||
|
||||
// TODO: validate that children contains only at-rule and declaration nodes?
|
||||
CssAtRule(this.name, {this.value, Iterable<CssNode> children, this.span})
|
||||
: children = children == null ? null : new List.unmodifiable(children);
|
||||
CssAtRule(this.name, {this.value, this.span});
|
||||
|
||||
/*=T*/ accept/*<T>*/(CssVisitor/*<T>*/ visitor) =>
|
||||
visitor.visitAtRule(this);
|
||||
|
||||
CssAtRule withChildren(Iterable<CssNode> children) =>
|
||||
new CssAtRule(name, value: value, children: children, span: span);
|
||||
|
||||
String toString() {
|
||||
var buffer = new StringBuffer("@$name");
|
||||
if (value != null) buffer.write(" $value");
|
||||
|
@ -7,7 +7,7 @@ import 'package:source_span/source_span.dart';
|
||||
import '../../visitor/css.dart';
|
||||
import 'node.dart';
|
||||
|
||||
class CssComment implements CssNode {
|
||||
class CssComment extends CssNode {
|
||||
final String text;
|
||||
|
||||
final FileSpan span;
|
||||
|
@ -9,7 +9,7 @@ import '../../visitor/css.dart';
|
||||
import 'node.dart';
|
||||
import 'value.dart';
|
||||
|
||||
class CssDeclaration implements CssNode {
|
||||
class CssDeclaration extends CssNode {
|
||||
final CssValue<String> name;
|
||||
|
||||
final CssValue<Value> value;
|
||||
|
@ -5,27 +5,18 @@
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import '../../visitor/css.dart';
|
||||
import '../parent.dart';
|
||||
import 'node.dart';
|
||||
|
||||
class CssMediaRule implements CssNode, Parent<CssNode, CssMediaRule> {
|
||||
class CssMediaRule extends CssParentNode {
|
||||
final List<CssMediaQuery> queries;
|
||||
|
||||
final List<CssNode> children;
|
||||
|
||||
final FileSpan span;
|
||||
|
||||
// TODO: validate that children contains only at-rule and style rule nodes?
|
||||
CssMediaRule(Iterable<CssMediaQuery> queries, Iterable<CssNode> children,
|
||||
{this.span})
|
||||
: queries = new List.unmodifiable(queries),
|
||||
children = new List.unmodifiable(children);
|
||||
CssMediaRule(this.queries, {this.span});
|
||||
|
||||
/*=T*/ accept/*<T>*/(CssVisitor/*<T>*/ visitor) =>
|
||||
visitor.visitMediaRule(this);
|
||||
|
||||
CssMediaRule withChildren(Iterable<CssNode> children) =>
|
||||
new CssMediaRule(queries, children, span: span);
|
||||
|
||||
String toString() => "@media ${queries.join(", ")} {${children.join(" ")}}";
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'dart:collection';
|
||||
|
||||
import '../../visitor/css.dart';
|
||||
import '../node.dart';
|
||||
|
||||
@ -15,5 +17,39 @@ export 'stylesheet.dart';
|
||||
export 'value.dart';
|
||||
|
||||
abstract class CssNode extends AstNode {
|
||||
CssParentNode get parent => _parent;
|
||||
CssParentNode _parent;
|
||||
|
||||
int _indexInParent;
|
||||
|
||||
/*=T*/ accept/*<T>*/(CssVisitor/*<T>*/ visitor);
|
||||
|
||||
void remove() {
|
||||
if (_parent == null) {
|
||||
throw new StateError("Can't remove a node without a parent.");
|
||||
}
|
||||
|
||||
_parent._children.removeAt(_indexInParent);
|
||||
for (var i = _indexInParent; i < _parent._children.length; i++) {
|
||||
_parent._children[i]._indexInParent--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class CssParentNode extends CssNode {
|
||||
final List<CssNode> children;
|
||||
final List<CssNode> _children;
|
||||
|
||||
CssParentNode() : this._([]);
|
||||
|
||||
CssParentNode._(List<CssNode> children)
|
||||
: _children = children,
|
||||
children = new UnmodifiableListView<CssNode>(children);
|
||||
|
||||
void addChild(CssNode child) {
|
||||
// TODO: validate that children are valid?
|
||||
child._parent = this;
|
||||
child._indexInParent = _children.length;
|
||||
_children.add(child);
|
||||
}
|
||||
}
|
||||
|
@ -5,27 +5,19 @@
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import '../../visitor/css.dart';
|
||||
import '../parent.dart';
|
||||
import '../selector.dart';
|
||||
import 'node.dart';
|
||||
import 'value.dart';
|
||||
|
||||
class CssStyleRule implements CssNode, Parent<CssNode, CssStyleRule> {
|
||||
class CssStyleRule extends CssParentNode {
|
||||
final CssValue<SelectorList> selector;
|
||||
|
||||
final List<CssNode> children;
|
||||
|
||||
final FileSpan span;
|
||||
|
||||
// TODO: validate that children contains only at-rule and declaration nodes?
|
||||
CssStyleRule(this.selector, Iterable<CssNode> children, {this.span})
|
||||
: children = new List.unmodifiable(children);
|
||||
CssStyleRule(this.selector, {this.span});
|
||||
|
||||
/*=T*/ accept/*<T>*/(CssVisitor/*<T>*/ visitor) =>
|
||||
visitor.visitStyleRule(this);
|
||||
|
||||
CssStyleRule withChildren(Iterable<CssNode> children) =>
|
||||
new CssStyleRule(selector, children, span: span);
|
||||
|
||||
String toString() => "$selector {${children.join(" ")}}";
|
||||
}
|
@ -5,22 +5,15 @@
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import '../../visitor/css.dart';
|
||||
import '../parent.dart';
|
||||
import 'node.dart';
|
||||
|
||||
class CssStylesheet implements CssNode, Parent<CssNode, CssStylesheet> {
|
||||
final List<CssNode> children;
|
||||
|
||||
class CssStylesheet extends CssParentNode {
|
||||
final FileSpan span;
|
||||
|
||||
CssStylesheet(Iterable<CssNode> children, {this.span})
|
||||
: children = new List.unmodifiable(children);
|
||||
CssStylesheet({this.span});
|
||||
|
||||
/*=T*/ accept/*<T>*/(CssVisitor/*<T>*/ visitor) =>
|
||||
visitor.visitStylesheet(this);
|
||||
|
||||
CssStylesheet withChildren(Iterable<CssNode> children) =>
|
||||
new CssStylesheet(children, span: span);
|
||||
|
||||
String toString() => children.join(" ");
|
||||
}
|
@ -4,17 +4,14 @@
|
||||
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import '../../visitor/css.dart';
|
||||
import 'node.dart';
|
||||
import '../node.dart';
|
||||
|
||||
class CssValue<T> implements CssNode {
|
||||
class CssValue<T> implements AstNode {
|
||||
final T value;
|
||||
|
||||
final FileSpan span;
|
||||
|
||||
CssValue(this.value, {this.span});
|
||||
|
||||
/*=T*/ accept/*<T>*/(CssVisitor/*<T>*/ visitor) => visitor.visitValue(this);
|
||||
|
||||
String toString() => value.toString();
|
||||
}
|
||||
|
@ -1,11 +0,0 @@
|
||||
// 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 Parent<T extends AstNode, N extends T> extends AstNode {
|
||||
List<T> get children;
|
||||
|
||||
N withChildren(Iterable<T> children);
|
||||
}
|
@ -5,11 +5,10 @@
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import '../../visitor/sass/statement.dart';
|
||||
import '../parent.dart';
|
||||
import 'expression/interpolation.dart';
|
||||
import 'statement.dart';
|
||||
|
||||
class AtRule implements Statement, Parent<Statement, AtRule> {
|
||||
class AtRule implements Statement {
|
||||
final String name;
|
||||
|
||||
final InterpolationExpression value;
|
||||
@ -26,9 +25,6 @@ class AtRule implements Statement, Parent<Statement, AtRule> {
|
||||
/*=T*/ accept/*<T>*/(StatementVisitor/*<T>*/ visitor) =>
|
||||
visitor.visitAtRule(this);
|
||||
|
||||
AtRule withChildren(Iterable<Statement> children) =>
|
||||
new AtRule(name, value: value, children: children, span: span);
|
||||
|
||||
String toString() {
|
||||
var buffer = new StringBuffer("@$name");
|
||||
if (value != null) buffer.write(" $value");
|
||||
|
@ -5,10 +5,9 @@
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import '../../visitor/sass/statement.dart';
|
||||
import '../parent.dart';
|
||||
import 'statement.dart';
|
||||
|
||||
class MediaRule implements Statement, Parent<Statement, MediaRule> {
|
||||
class MediaRule implements Statement {
|
||||
final List<MediaQuery> queries;
|
||||
|
||||
final List<Statement> children;
|
||||
@ -23,8 +22,5 @@ class MediaRule implements Statement, Parent<Statement, MediaRule> {
|
||||
/*=T*/ accept/*<T>*/(StatementVisitor/*<T>*/ visitor) =>
|
||||
visitor.visitMediaRule(this);
|
||||
|
||||
MediaRule withChildren(Iterable<Statement> children) =>
|
||||
new MediaRule(queries, children, span: span);
|
||||
|
||||
String toString() => "@media ${queries.join(", ")} {${children.join(" ")}}";
|
||||
}
|
||||
|
@ -5,11 +5,10 @@
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import '../../visitor/sass/statement.dart';
|
||||
import '../parent.dart';
|
||||
import 'expression/interpolation.dart';
|
||||
import 'statement.dart';
|
||||
|
||||
class StyleRule implements Statement, Parent<Statement, StyleRule> {
|
||||
class StyleRule implements Statement {
|
||||
final InterpolationExpression selector;
|
||||
|
||||
final List<Statement> children;
|
||||
@ -24,8 +23,5 @@ class StyleRule implements Statement, Parent<Statement, StyleRule> {
|
||||
/*=T*/ accept/*<T>*/(StatementVisitor/*<T>*/ visitor) =>
|
||||
visitor.visitStyleRule(this);
|
||||
|
||||
StyleRule withChildren(Iterable<Statement> children) =>
|
||||
new StyleRule(selector, children, span: span);
|
||||
|
||||
String toString() => "$selector {${children.join(" ")}}";
|
||||
}
|
@ -5,10 +5,9 @@
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import '../../visitor/sass/statement.dart';
|
||||
import '../parent.dart';
|
||||
import 'statement.dart';
|
||||
|
||||
class Stylesheet implements Statement, Parent<Statement, Stylesheet> {
|
||||
class Stylesheet implements Statement {
|
||||
final List<Statement> children;
|
||||
|
||||
final FileSpan span;
|
||||
@ -19,8 +18,5 @@ class Stylesheet implements Statement, Parent<Statement, Stylesheet> {
|
||||
/*=T*/ accept/*<T>*/(StatementVisitor/*<T>*/ visitor) =>
|
||||
visitor.visitStylesheet(this);
|
||||
|
||||
Stylesheet withChildren(Iterable<Statement> children) =>
|
||||
new Stylesheet(children, span: span);
|
||||
|
||||
String toString() => children.join(" ");
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
// 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 'dart:collection';
|
||||
|
||||
import 'ast/node.dart';
|
||||
import 'ast/parent.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
class MutableNode<T extends AstNode, N extends T> {
|
||||
N get node => _node;
|
||||
|
||||
void set node(N value) {
|
||||
_node = value;
|
||||
_checkNode();
|
||||
}
|
||||
|
||||
N _node;
|
||||
|
||||
final MutableNode<T, T> parent;
|
||||
|
||||
LinkedListValue<MutableNode<T, N>> _linkedListValue;
|
||||
|
||||
Iterable<MutableNode<T, T>> get children =>
|
||||
_children.map((value) => value.value);
|
||||
final _children = new LinkedList<LinkedListValue<MutableNode<T, T>>>();
|
||||
|
||||
MutableNode.root(this._node) : parent = null {
|
||||
_checkNode();
|
||||
}
|
||||
|
||||
MutableNode._(this.parent, this._node) {
|
||||
_checkNode();
|
||||
}
|
||||
|
||||
MutableNode<T, T/*=S*/> add/*<S extends T>*/(T/*=S*/ child) {
|
||||
var node = new MutableNode<T, T/*=S*/>._(this, child);
|
||||
node._linkedListValue = new LinkedListValue(node);
|
||||
_children.add(node._linkedListValue);
|
||||
return node;
|
||||
}
|
||||
|
||||
void remove() {
|
||||
_linkedListValue.unlink();
|
||||
}
|
||||
|
||||
void _checkNode() {
|
||||
if (node is! Parent) return;
|
||||
var parent = node as Parent;
|
||||
if (parent.children == null) return;
|
||||
if (parent.children.isEmpty) return;
|
||||
|
||||
throw new StateError("A mutable node can't have immutable children.");
|
||||
}
|
||||
|
||||
N build() {
|
||||
if (children.isEmpty) return node;
|
||||
if (node is Parent<T, N>) {
|
||||
return (node as Parent<T, N>)
|
||||
.withChildren(children.map((entry) => entry.build()));
|
||||
}
|
||||
|
||||
throw new StateError("Non-Parent $node may not have children.");
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import '../ast/css/node.dart';
|
||||
import '../value.dart';
|
||||
import 'value.dart';
|
||||
|
||||
abstract class CssVisitor<T> extends ValueVisitor<T> {
|
||||
@ -38,6 +37,4 @@ abstract class CssVisitor<T> extends ValueVisitor<T> {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
T visitValue(CssValue node) => node is Value ? node.accept(this) : null;
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import '../../ast/css/node.dart';
|
||||
import '../../util/character.dart';
|
||||
import '../../value.dart';
|
||||
import '../css.dart';
|
||||
import '../../parser.dart';
|
||||
|
||||
String toCss(CssNode node) {
|
||||
var visitor = new _SerializeCssVisitor();
|
||||
|
@ -7,7 +7,6 @@ import '../../../ast/sass/expression.dart';
|
||||
import '../../../ast/sass/statement.dart';
|
||||
import '../../../ast/selector.dart';
|
||||
import '../../../environment.dart';
|
||||
import '../../../mutable_node.dart';
|
||||
import '../../../parser.dart';
|
||||
import '../../../value.dart';
|
||||
import '../expression/perform.dart';
|
||||
@ -27,11 +26,11 @@ class PerformVisitor extends StatementVisitor {
|
||||
/// This will always have an empty list of children.
|
||||
CssMediaRule _mediaRule;
|
||||
|
||||
/// A mutable view of the root stylesheet node.
|
||||
MutableNode<CssNode, CssStylesheet> _root;
|
||||
/// The root stylesheet node.
|
||||
CssStylesheet _root;
|
||||
|
||||
/// A mutable view of the current parent node in the output CSS tree.
|
||||
MutableNode<CssNode, CssNode> _parent;
|
||||
/// The current parent node in the output CSS tree.
|
||||
CssParentNode _parent;
|
||||
|
||||
PerformVisitor() : this._(new Environment());
|
||||
|
||||
@ -42,16 +41,15 @@ class PerformVisitor extends StatementVisitor {
|
||||
void visit(Statement node) => node.accept(this);
|
||||
|
||||
CssStylesheet visitStylesheet(Stylesheet node) {
|
||||
_root = new MutableNode<CssNode, CssStylesheet>.root(
|
||||
new CssStylesheet(const [], span: node.span));
|
||||
_root = new CssStylesheet(span: node.span);
|
||||
_parent = _root;
|
||||
super.visitStylesheet(node);
|
||||
return _root.build();
|
||||
return _root;
|
||||
}
|
||||
|
||||
void visitComment(Comment node) {
|
||||
if (node.isSilent) return;
|
||||
_parent.add(new CssComment(node.text, span: node.span));
|
||||
_addChild(new CssComment(node.text, span: node.span));
|
||||
}
|
||||
|
||||
void visitDeclaration(Declaration node) {
|
||||
@ -66,7 +64,7 @@ class PerformVisitor extends StatementVisitor {
|
||||
return;
|
||||
}
|
||||
|
||||
_parent.add(new CssDeclaration(name, cssValue, span: node.span));
|
||||
_addChild(new CssDeclaration(name, cssValue, span: node.span));
|
||||
}
|
||||
|
||||
void visitAtRule(AtRule node) {
|
||||
@ -75,7 +73,7 @@ class PerformVisitor extends StatementVisitor {
|
||||
: _performInterpolation(node.value, trim: true);
|
||||
|
||||
if (node.children == null) {
|
||||
_parent.add(new CssAtRule(node.name, value: value, span: node.span));
|
||||
_addChild(new CssAtRule(node.name, value: value, span: node.span));
|
||||
}
|
||||
|
||||
_withParent(
|
||||
@ -85,24 +83,25 @@ class PerformVisitor extends StatementVisitor {
|
||||
}
|
||||
|
||||
void visitMediaRule(MediaRule node) {
|
||||
var queries = node.queries.map(_visitMediaQuery);
|
||||
if (_mediaRule != null) {
|
||||
queries = _mergeMediaQueries(_mediaRule.queries, queries);
|
||||
}
|
||||
var queryIterable = node.queries.map(_visitMediaQuery);
|
||||
var queries = _mediaRule == null
|
||||
? new List<CssMediaQuery>.unmodifiable(queryIterable)
|
||||
: _mergeMediaQueries(_mediaRule.queries, queryIterable);
|
||||
if (queries.isEmpty) return;
|
||||
|
||||
var rule = new CssMediaRule(queries, const [], span: node.span);
|
||||
_withParent(rule, () {
|
||||
_withMediaRule(rule, () => super.visitMediaRule(node));
|
||||
if (_parent.children.isEmpty) _parent.remove();
|
||||
}, through: (node) => node is CssStyleRule || node is CssMediaRule);
|
||||
var rule = new CssMediaRule(queries, span: node.span);
|
||||
_withParent(
|
||||
rule,
|
||||
() => _withMediaRule(rule, () => super.visitMediaRule(node)),
|
||||
through: (node) => node is CssStyleRule || node is CssMediaRule,
|
||||
removeIfEmpty: true);
|
||||
}
|
||||
|
||||
List<CssMediaQuery> _mergeMediaQueries(
|
||||
Iterable<CssMediaQuery> queries1, Iterable<CssMediaQuery> queries2) {
|
||||
return queries1.expand/*<CssMediaQuery>*/((query1) {
|
||||
return new List.unmodifiable(queries1.expand/*<CssMediaQuery>*/((query1) {
|
||||
return queries2.map((query2) => query1.merge(query2));
|
||||
}).where((query) => query != null).toList();
|
||||
}).where((query) => query != null));
|
||||
}
|
||||
|
||||
CssMediaQuery _visitMediaQuery(MediaQuery query) {
|
||||
@ -136,11 +135,12 @@ class PerformVisitor extends StatementVisitor {
|
||||
new Parser(selectorText.value).parseSelector(),
|
||||
span: node.selector.span);
|
||||
|
||||
var rule = new CssStyleRule(selector, const [], span: node.span);
|
||||
_withParent(rule, () {
|
||||
_withStyleRule(rule, () => super.visitStyleRule(node));
|
||||
if (_parent.children.isEmpty) _parent.remove();
|
||||
}, through: (node) => node is CssStyleRule);
|
||||
var rule = new CssStyleRule(selector, span: node.span);
|
||||
_withParent(
|
||||
rule,
|
||||
() => _withStyleRule(rule, () => super.visitStyleRule(node)),
|
||||
through: (node) => node is CssStyleRule,
|
||||
removeIfEmpty: true);
|
||||
}
|
||||
|
||||
void visitVariableDeclaration(VariableDeclaration node) {
|
||||
@ -159,20 +159,27 @@ class PerformVisitor extends StatementVisitor {
|
||||
CssValue<Value> _performExpression(Expression expression) =>
|
||||
new CssValue(expression.accept(_expressionVisitor));
|
||||
|
||||
/*=T*/ _withParent/*<S extends CssNode, T>*/(CssNode/*=S*/ node,
|
||||
/*=T*/ callback(), {bool through(CssNode node)}) {
|
||||
void _addChild(CssNode node) {
|
||||
_parent.addChild(node);
|
||||
}
|
||||
|
||||
/*=T*/ _withParent/*<S extends CssParentNode, T>*/(
|
||||
/*=S*/ node, /*=T*/ callback(),
|
||||
{bool through(CssNode node), bool removeIfEmpty: false}) {
|
||||
var oldParent = _parent;
|
||||
|
||||
// Go up through parents that match [through].
|
||||
var parent = _parent;
|
||||
if (through != null) {
|
||||
while (through(parent.node)) {
|
||||
while (through(parent)) {
|
||||
parent = parent.parent;
|
||||
}
|
||||
}
|
||||
|
||||
_parent = parent.add(node);
|
||||
parent.addChild(node);
|
||||
_parent = node;
|
||||
var result = _environment.scope(callback);
|
||||
if (removeIfEmpty && node.children.isEmpty) node.remove();
|
||||
_parent = oldParent;
|
||||
|
||||
return result;
|
||||
|
Loading…
Reference in New Issue
Block a user