Merge pull request #200 from sass/no-unecessary-scope

Don't create scopes when nothing is declared
This commit is contained in:
Natalie Weizenbaum 2017-12-02 16:37:04 -08:00 committed by GitHub
commit 6d83c13603
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 226 additions and 131 deletions

View File

@ -7,22 +7,20 @@ import 'package:source_span/source_span.dart';
import '../../../visitor/interface/statement.dart';
import '../interpolation.dart';
import '../statement.dart';
import 'parent.dart';
/// An `@at-root` rule.
///
/// This moves it contents "up" the tree through parent nodes.
class AtRootRule implements Statement {
class AtRootRule extends ParentStatement {
/// The query specifying which statements this should move its contents
/// through.
final Interpolation query;
/// The statements contained in [this].
final List<Statement> children;
final FileSpan span;
AtRootRule(Iterable<Statement> children, this.span, {this.query})
: children = new List.from(children);
: super(new List.unmodifiable(children));
T accept<T>(StatementVisitor<T> visitor) => visitor.visitAtRootRule(this);

View File

@ -8,9 +8,10 @@ import '../../../utils.dart';
import '../../../visitor/interface/statement.dart';
import '../interpolation.dart';
import '../statement.dart';
import 'parent.dart';
/// An unknown at-rule.
class AtRule implements Statement {
class AtRule extends ParentStatement {
/// The name of this rule.
final String name;
@ -20,18 +21,12 @@ class AtRule implements Statement {
/// The value of this rule.
final Interpolation value;
/// The children of this rule.
///
/// If [children] is empty, [this] was declared with empty brackets. If
/// [children] is null, it was declared without brackets.
final List<Statement> children;
final FileSpan span;
AtRule(String name, this.span, {this.value, Iterable<Statement> children})
: name = name,
normalizedName = unvendor(name),
children = children == null ? null : new List.unmodifiable(children);
super(children == null ? null : new List.unmodifiable(children));
T accept<T>(StatementVisitor<T> visitor) => visitor.visitAtRule(this);

View File

@ -8,24 +8,21 @@ import '../../../visitor/interface/statement.dart';
import '../expression.dart';
import '../interpolation.dart';
import '../statement.dart';
import 'parent.dart';
/// A declaration (that is, a `name: value` pair).
class Declaration implements Statement {
class Declaration extends ParentStatement {
/// The name of this declaration.
final Interpolation name;
/// The value of this declaration.
final Expression value;
/// The children of this declaration.
///
/// This is `null` if the declaration has no children.
final List<Statement> children;
final FileSpan span;
Declaration(this.name, this.span, {this.value, Iterable<Statement> children})
: children = children == null ? null : new List.unmodifiable(children);
: super(children =
children == null ? null : new List.unmodifiable(children));
T accept<T>(StatementVisitor<T> visitor) => visitor.visitDeclaration(this);

View File

@ -7,26 +7,24 @@ import 'package:source_span/source_span.dart';
import '../../../visitor/interface/statement.dart';
import '../expression.dart';
import '../statement.dart';
import 'parent.dart';
/// An `@each` rule.
///
/// This iterates over values in a list or map.
class EachRule implements Statement {
class EachRule extends ParentStatement {
/// The variables assigned for each iteration.
final List<String> variables;
/// The expression whose value this iterates through.
final Expression list;
/// The child statements executed for each iteration.
final List<Statement> children;
final FileSpan span;
EachRule(Iterable<String> variables, this.list, Iterable<Statement> children,
this.span)
: variables = new List.unmodifiable(variables),
children = new List.unmodifiable(children);
super(new List.unmodifiable(children));
T accept<T>(StatementVisitor<T> visitor) => visitor.visitEachRule(this);

View File

@ -7,11 +7,12 @@ import 'package:source_span/source_span.dart';
import '../../../visitor/interface/statement.dart';
import '../expression.dart';
import '../statement.dart';
import 'parent.dart';
/// A `@for` rule.
///
/// This iterates a set number of times.
class ForRule implements Statement {
class ForRule extends ParentStatement {
/// The name of the variable that will contain the index value.
final String variable;
@ -24,16 +25,13 @@ class ForRule implements Statement {
/// Whether [to] is exclusive.
final bool isExclusive;
/// The child statements executed for each iteration.
final List<Statement> children;
final FileSpan span;
ForRule(this.variable, this.from, this.to, Iterable<Statement> children,
this.span,
{bool exclusive: true})
: children = new List.unmodifiable(children),
isExclusive = exclusive;
: isExclusive = exclusive,
super(new List.unmodifiable(children));
T accept<T>(StatementVisitor<T> visitor) => visitor.visitForRule(this);

View File

@ -3,11 +3,13 @@
// https://opensource.org/licenses/MIT.
import 'package:source_span/source_span.dart';
import 'package:tuple/tuple.dart';
import '../../../visitor/interface/statement.dart';
import '../expression.dart';
import '../statement.dart';
import 'function_rule.dart';
import 'mixin_rule.dart';
import 'variable_declaration.dart';
/// An `@if` rule.
///
@ -18,30 +20,58 @@ class IfRule implements Statement {
/// The first clause whose expression evaluates to `true` will have its
/// statements executed. If no expression evaluates to `true`, `lastClause`
/// will be executed if it's not `null`.
final List<Tuple2<Expression, List<Statement>>> clauses;
final List<IfClause> clauses;
/// The final, unconditional `@else` clause.
///
/// This is `null` if there is no unconditional `@else`.
final List<Statement> lastClause;
final IfClause lastClause;
final FileSpan span;
IfRule(Iterable<Tuple2<Expression, Iterable<Statement>>> clauses, this.span,
{Iterable<Statement> lastClause})
: clauses = new List.unmodifiable(clauses.map((pair) =>
new Tuple2(pair.item1, new List.unmodifiable(pair.item2)))),
lastClause =
lastClause == null ? null : new List.unmodifiable(lastClause);
IfRule(Iterable<IfClause> clauses, this.span, {this.lastClause})
: clauses = new List.unmodifiable(clauses) {
assert(clauses.every((clause) => clause.expression != null));
assert(lastClause?.expression == null);
}
T accept<T>(StatementVisitor<T> visitor) => visitor.visitIfRule(this);
String toString() {
var first = true;
return clauses.map((pair) {
return clauses.map((clause) {
var name = first ? 'if' : 'else';
first = false;
return '@$name ${pair.item1} {${pair.item2.join(" ")}}';
return '@$name ${clause.expression} {${clause.children.join(" ")}}';
}).join(' ');
}
}
/// A single clause in an `@if` rule.
class IfClause {
/// The expression to evaluate to determine whether to run this rule, or
/// `null` if this is the final unconditional `@else` clause.
final Expression expression;
/// The statements to evaluate if this clause matches.
final List<Statement> children;
/// Whether any of [children] is a variable, function, or mixin declaration.
final bool hasDeclarations;
IfClause(Expression expression, Iterable<Statement> children)
: this._(expression, new List.unmodifiable(children));
IfClause.last(Iterable<Statement> children)
: this._(null, new List.unmodifiable(children));
IfClause._(this.expression, this.children)
: hasDeclarations = children.any((child) =>
child is VariableDeclaration ||
child is FunctionRule ||
child is MixinRule);
String toString() =>
(expression == null ? "@else" : "@if $expression") +
" {${children.join(' ')}}";
}

View File

@ -7,21 +7,19 @@ import 'package:source_span/source_span.dart';
import '../../../visitor/interface/statement.dart';
import '../interpolation.dart';
import '../statement.dart';
import 'parent.dart';
/// A `@media` rule.
class MediaRule implements Statement {
class MediaRule extends ParentStatement {
/// The query that determines on which platforms the styles will be in effect.
///
/// This is only parsed after the interpolation has been resolved.
final Interpolation query;
/// The contents of this rule.
final List<Statement> children;
final FileSpan span;
MediaRule(this.query, Iterable<Statement> children, this.span)
: children = new List.unmodifiable(children);
: super(new List.unmodifiable(children));
T accept<T>(StatementVisitor<T> visitor) => visitor.visitMediaRule(this);

View File

@ -0,0 +1,24 @@
// Copyright 2017 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 '../statement.dart';
import 'function_rule.dart';
import 'mixin_rule.dart';
import 'variable_declaration.dart';
/// A [Statement] that can have child statements.
abstract class ParentStatement implements Statement {
/// The child statements of this statement.
final List<Statement> children;
/// Whether any of [children] is a variable, function, or mixin declaration.
final bool hasDeclarations;
ParentStatement(this.children)
: hasDeclarations = children?.any((child) =>
child is VariableDeclaration ||
child is FunctionRule ||
child is MixinRule) ??
false;
}

View File

@ -7,23 +7,21 @@ import 'package:source_span/source_span.dart';
import '../../../visitor/interface/statement.dart';
import '../interpolation.dart';
import '../statement.dart';
import 'parent.dart';
/// A style rule.
///
/// This applies style declarations to elements that match a given selector.
class StyleRule implements Statement {
class StyleRule extends ParentStatement {
/// The selector to which the declaration will be applied.
///
/// This is only parsed after the interpolation has been resolved.
final Interpolation selector;
/// The declarations associated with the selector.
final List<Statement> children;
final FileSpan span;
StyleRule(this.selector, Iterable<Statement> children, this.span)
: children = new List.unmodifiable(children);
: super(new List.unmodifiable(children));
T accept<T>(StatementVisitor<T> visitor) => visitor.visitStyleRule(this);

View File

@ -8,18 +8,16 @@ import '../../../visitor/interface/statement.dart';
import '../../../parse/sass.dart';
import '../../../parse/scss.dart';
import '../statement.dart';
import 'parent.dart';
/// A Sass stylesheet.
///
/// This is the root Sass node. It contains top-level statements.
class Stylesheet implements Statement {
/// The top-level statements of this Sass stylesheet.
final List<Statement> children;
class Stylesheet extends ParentStatement {
final FileSpan span;
Stylesheet(Iterable<Statement> children, this.span)
: children = new List.unmodifiable(children);
: super(new List.unmodifiable(children));
/// Parses an indented-syntax stylesheet from [contents].
///

View File

@ -7,19 +7,17 @@ import 'package:source_span/source_span.dart';
import '../../../visitor/interface/statement.dart';
import '../statement.dart';
import '../supports_condition.dart';
import 'parent.dart';
/// A `@supports` rule.
class SupportsRule implements Statement {
class SupportsRule extends ParentStatement {
/// The condition that selects what browsers this rule targets.
final SupportsCondition condition;
/// The contents of this rule.
final List<Statement> children;
final FileSpan span;
SupportsRule(this.condition, Iterable<Statement> children, this.span)
: children = new List.from(children);
: super(new List.unmodifiable(children));
T accept<T>(StatementVisitor<T> visitor) => visitor.visitSupportsRule(this);

View File

@ -7,22 +7,20 @@ import 'package:source_span/source_span.dart';
import '../../../visitor/interface/statement.dart';
import '../expression.dart';
import '../statement.dart';
import 'parent.dart';
/// A `@while` rule.
///
/// This repeatedly executes a block of code as long as a statement evaluates to
/// `true`.
class WhileRule implements Statement {
class WhileRule extends ParentStatement {
/// The condition that determines whether the block will be executed.
final Expression condition;
/// The code to execute repeatedly.
final List<Statement> children;
final FileSpan span;
WhileRule(this.condition, Iterable<Statement> children, this.span)
: children = new List.unmodifiable(children);
: super(new List.unmodifiable(children));
T accept<T>(StatementVisitor<T> visitor) => visitor.visitWhileRule(this);

View File

@ -82,11 +82,11 @@ class AsyncEnvironment {
bool get inMixin => _inMixin;
var _inMixin = false;
/// Whether the environment is currently in a semi-global scope.
/// Whether the environment is currently in a global or semi-global scope.
///
/// A semi-global scope can assign to global variables, but it doesn't declare
/// them by default.
var _inSemiGlobalScope = false;
var _inSemiGlobalScope = true;
AsyncEnvironment()
: _variables = [normalizedMap()],
@ -279,10 +279,32 @@ class AsyncEnvironment {
/// Variables, functions, and mixins declared in a given scope are
/// inaccessible outside of it. If [semiGlobal] is passed, this scope can
/// assign to global variables without a `!global` declaration.
Future<T> scope<T>(Future<T> callback(), {bool semiGlobal: false}) async {
semiGlobal = semiGlobal && (_inSemiGlobalScope || _variables.length == 1);
///
/// If [when] is false, this doesn't create a new scope and instead just
/// executes [callback] and returns its result.
Future<T> scope<T>(Future<T> callback(),
{bool semiGlobal: false, bool when: true}) async {
if (!when) {
// We still have to track semi-globalness so that
//
// div {
// @if ... {
// $x: y;
// }
// }
//
// doesn't assign to the global scope.
var wasInSemiGlobalScope = _inSemiGlobalScope;
_inSemiGlobalScope = semiGlobal;
try {
return await callback();
} finally {
_inSemiGlobalScope = wasInSemiGlobalScope;
}
}
semiGlobal = semiGlobal && _inSemiGlobalScope;
// TODO: avoid creating a new scope if no variables are declared.
var wasInSemiGlobalScope = _inSemiGlobalScope;
_inSemiGlobalScope = semiGlobal;
_variables.add(normalizedMap());

View File

@ -5,7 +5,7 @@
// DO NOT EDIT. This file was generated from async_environment.dart.
// See tool/synchronize.dart for details.
//
// Checksum: 97410abbd78c3bbc9899f3ac460cc0736218bfe3
// Checksum: 4669f41a70664bd5f391c6b8627264a5d0ad8f6c
import 'ast/sass.dart';
import 'callable.dart';
@ -85,11 +85,11 @@ class Environment {
bool get inMixin => _inMixin;
var _inMixin = false;
/// Whether the environment is currently in a semi-global scope.
/// Whether the environment is currently in a global or semi-global scope.
///
/// A semi-global scope can assign to global variables, but it doesn't declare
/// them by default.
var _inSemiGlobalScope = false;
var _inSemiGlobalScope = true;
Environment()
: _variables = [normalizedMap()],
@ -282,10 +282,31 @@ class Environment {
/// Variables, functions, and mixins declared in a given scope are
/// inaccessible outside of it. If [semiGlobal] is passed, this scope can
/// assign to global variables without a `!global` declaration.
T scope<T>(T callback(), {bool semiGlobal: false}) {
semiGlobal = semiGlobal && (_inSemiGlobalScope || _variables.length == 1);
///
/// If [when] is false, this doesn't create a new scope and instead just
/// executes [callback] and returns its result.
T scope<T>(T callback(), {bool semiGlobal: false, bool when: true}) {
if (!when) {
// We still have to track semi-globalness so that
//
// div {
// @if ... {
// $x: y;
// }
// }
//
// doesn't assign to the global scope.
var wasInSemiGlobalScope = _inSemiGlobalScope;
_inSemiGlobalScope = semiGlobal;
try {
return callback();
} finally {
_inSemiGlobalScope = wasInSemiGlobalScope;
}
}
semiGlobal = semiGlobal && _inSemiGlobalScope;
// TODO: avoid creating a new scope if no variables are declared.
var wasInSemiGlobalScope = _inSemiGlobalScope;
_inSemiGlobalScope = semiGlobal;
_variables.add(normalizedMap());

View File

@ -671,16 +671,16 @@ abstract class StylesheetParser extends Parser {
var expression = _expression();
var children = this.children(child);
var clauses = [new Tuple2(expression, children)];
List<Statement> lastClause;
var clauses = [new IfClause(expression, children)];
IfClause lastClause;
while (scanElse(ifIndentation)) {
whitespace();
if (scanIdentifier("if")) {
whitespace();
clauses.add(new Tuple2(_expression(), this.children(child)));
clauses.add(new IfClause(_expression(), this.children(child)));
} else {
lastClause = this.children(child);
lastClause = new IfClause.last(this.children(child));
break;
}
}

View File

@ -327,7 +327,7 @@ class _EvaluateVisitor
for (var child in node.children) {
await child.accept(this);
}
});
}, when: node.hasDeclarations);
return null;
}
@ -341,7 +341,7 @@ class _EvaluateVisitor
}
if (outerCopy != null) root.addChild(outerCopy);
await _scopeForAtRoot(innerCopy ?? root, query, included)(() async {
await _scopeForAtRoot(node, innerCopy ?? root, query, included)(() async {
for (var child in node.children) {
await child.accept(this);
}
@ -386,14 +386,14 @@ class _EvaluateVisitor
/// This returns a callback that adjusts various instance variables for its
/// duration, based on which rules are excluded by [query]. It always assigns
/// [_parent] to [newParent].
_ScopeCallback _scopeForAtRoot(CssParentNode newParent, AtRootQuery query,
List<CssParentNode> included) {
_ScopeCallback _scopeForAtRoot(AtRootRule node, CssParentNode newParent,
AtRootQuery query, List<CssParentNode> included) {
var scope = (Future callback()) async {
// We can't use [_withParent] here because it'll add the node to the tree
// in the wrong place.
var oldParent = _parent;
_parent = newParent;
await _environment.scope(callback);
await _environment.scope(callback, when: node.hasDeclarations);
_parent = oldParent;
};
@ -488,7 +488,7 @@ class _EvaluateVisitor
for (var child in node.children) {
await child.accept(this);
}
});
}, when: node.hasDeclarations);
_declarationName = oldDeclarationName;
}
@ -588,9 +588,11 @@ class _EvaluateVisitor
for (var child in node.children) {
await child.accept(this);
}
});
}, scopeWhen: false);
}
}, through: (node) => node is CssStyleRule);
},
through: (node) => node is CssStyleRule,
scopeWhen: node.hasDeclarations);
_inUnknownAtRule = wasInUnknownAtRule;
_inKeyframes = wasInKeyframes;
@ -634,17 +636,19 @@ class _EvaluateVisitor
Future<Value> visitIfRule(IfRule node) async {
var clause = node.lastClause;
for (var pair in node.clauses) {
if ((await pair.item1.accept(this)).isTruthy) {
clause = pair.item2;
for (var clauseToCheck in node.clauses) {
if ((await clauseToCheck.expression.accept(this)).isTruthy) {
clause = clauseToCheck;
break;
}
}
if (clause == null) return null;
return await _environment.scope(
() => _handleReturn<Statement>(clause, (child) => child.accept(this)),
semiGlobal: true);
() => _handleReturn<Statement>(
clause.children, (child) => child.accept(this)),
semiGlobal: true,
when: clause.hasDeclarations);
}
Future<Value> visitImportRule(ImportRule node) async {
@ -877,10 +881,12 @@ class _EvaluateVisitor
for (var child in node.children) {
await child.accept(this);
}
});
}, scopeWhen: false);
}
});
}, through: (node) => node is CssStyleRule || node is CssMediaRule);
},
through: (node) => node is CssStyleRule || node is CssMediaRule,
scopeWhen: node.hasDeclarations);
return null;
}
@ -930,7 +936,9 @@ class _EvaluateVisitor
for (var child in node.children) {
await child.accept(this);
}
}, through: (node) => node is CssStyleRule);
},
through: (node) => node is CssStyleRule,
scopeWhen: node.hasDeclarations);
return null;
}
@ -954,7 +962,9 @@ class _EvaluateVisitor
await child.accept(this);
}
});
}, through: (node) => node is CssStyleRule);
},
through: (node) => node is CssStyleRule,
scopeWhen: node.hasDeclarations);
_atRootExcludingStyleRule = oldAtRootExcludingStyleRule;
if (!_inStyleRule) {
@ -991,7 +1001,9 @@ class _EvaluateVisitor
}
});
}
}, through: (node) => node is CssStyleRule);
},
through: (node) => node is CssStyleRule,
scopeWhen: node.hasDeclarations);
return null;
}
@ -1067,7 +1079,7 @@ class _EvaluateVisitor
if (result != null) return result;
}
return null;
}, semiGlobal: true);
}, semiGlobal: true, when: node.hasDeclarations);
}
// ## Expressions
@ -1249,9 +1261,6 @@ class _EvaluateVisitor
_verifyArguments(
positional.length, named, callable.declaration.arguments, span);
// TODO: if we get here and there are no rest params involved, mark
// the callable as fast-path and don't do error checking or extra
// allocations for future calls.
var declaredArguments = callable.declaration.arguments.arguments;
var minLength = math.min(positional.length, declaredArguments.length);
for (var i = 0; i < minLength; i++) {
@ -1632,9 +1641,11 @@ class _EvaluateVisitor
/// If [through] is passed, [node] is added as a child of the first parent for
/// which [through] returns `false`. That parent is copied unless it's the
/// lattermost child of its parent.
///
/// Runs [callback] in a new environment scope unless [scopeWhen] is false.
Future<T> _withParent<S extends CssParentNode, T>(
S node, Future<T> callback(),
{bool through(CssNode node)}) async {
{bool through(CssNode node), bool scopeWhen: true}) async {
var oldParent = _parent;
// Go up through parents that match [through].
@ -1656,7 +1667,7 @@ class _EvaluateVisitor
parent.addChild(node);
_parent = node;
var result = await _environment.scope(callback);
var result = await _environment.scope(callback, when: scopeWhen);
_parent = oldParent;
return result;

View File

@ -5,7 +5,7 @@
// DO NOT EDIT. This file was generated from async_evaluate.dart.
// See tool/synchronize.dart for details.
//
// Checksum: ae64ba442752642066f0e9e038f5f2c1bbfa866b
// Checksum: cdeeec2634c97638aef571043c0be1e99f22d7d5
import 'dart:math' as math;
@ -328,7 +328,7 @@ class _EvaluateVisitor
for (var child in node.children) {
child.accept(this);
}
});
}, when: node.hasDeclarations);
return null;
}
@ -342,7 +342,7 @@ class _EvaluateVisitor
}
if (outerCopy != null) root.addChild(outerCopy);
_scopeForAtRoot(innerCopy ?? root, query, included)(() {
_scopeForAtRoot(node, innerCopy ?? root, query, included)(() {
for (var child in node.children) {
child.accept(this);
}
@ -387,14 +387,14 @@ class _EvaluateVisitor
/// This returns a callback that adjusts various instance variables for its
/// duration, based on which rules are excluded by [query]. It always assigns
/// [_parent] to [newParent].
_ScopeCallback _scopeForAtRoot(CssParentNode newParent, AtRootQuery query,
List<CssParentNode> included) {
_ScopeCallback _scopeForAtRoot(AtRootRule node, CssParentNode newParent,
AtRootQuery query, List<CssParentNode> included) {
var scope = (void callback()) {
// We can't use [_withParent] here because it'll add the node to the tree
// in the wrong place.
var oldParent = _parent;
_parent = newParent;
_environment.scope(callback);
_environment.scope(callback, when: node.hasDeclarations);
_parent = oldParent;
};
@ -487,7 +487,7 @@ class _EvaluateVisitor
for (var child in node.children) {
child.accept(this);
}
});
}, when: node.hasDeclarations);
_declarationName = oldDeclarationName;
}
@ -583,9 +583,11 @@ class _EvaluateVisitor
for (var child in node.children) {
child.accept(this);
}
});
}, scopeWhen: false);
}
}, through: (node) => node is CssStyleRule);
},
through: (node) => node is CssStyleRule,
scopeWhen: node.hasDeclarations);
_inUnknownAtRule = wasInUnknownAtRule;
_inKeyframes = wasInKeyframes;
@ -629,17 +631,19 @@ class _EvaluateVisitor
Value visitIfRule(IfRule node) {
var clause = node.lastClause;
for (var pair in node.clauses) {
if (pair.item1.accept(this).isTruthy) {
clause = pair.item2;
for (var clauseToCheck in node.clauses) {
if (clauseToCheck.expression.accept(this).isTruthy) {
clause = clauseToCheck;
break;
}
}
if (clause == null) return null;
return _environment.scope(
() => _handleReturn<Statement>(clause, (child) => child.accept(this)),
semiGlobal: true);
() => _handleReturn<Statement>(
clause.children, (child) => child.accept(this)),
semiGlobal: true,
when: clause.hasDeclarations);
}
Value visitImportRule(ImportRule node) {
@ -871,10 +875,12 @@ class _EvaluateVisitor
for (var child in node.children) {
child.accept(this);
}
});
}, scopeWhen: false);
}
});
}, through: (node) => node is CssStyleRule || node is CssMediaRule);
},
through: (node) => node is CssStyleRule || node is CssMediaRule,
scopeWhen: node.hasDeclarations);
return null;
}
@ -921,7 +927,9 @@ class _EvaluateVisitor
for (var child in node.children) {
child.accept(this);
}
}, through: (node) => node is CssStyleRule);
},
through: (node) => node is CssStyleRule,
scopeWhen: node.hasDeclarations);
return null;
}
@ -945,7 +953,9 @@ class _EvaluateVisitor
child.accept(this);
}
});
}, through: (node) => node is CssStyleRule);
},
through: (node) => node is CssStyleRule,
scopeWhen: node.hasDeclarations);
_atRootExcludingStyleRule = oldAtRootExcludingStyleRule;
if (!_inStyleRule) {
@ -982,7 +992,9 @@ class _EvaluateVisitor
}
});
}
}, through: (node) => node is CssStyleRule);
},
through: (node) => node is CssStyleRule,
scopeWhen: node.hasDeclarations);
return null;
}
@ -1057,7 +1069,7 @@ class _EvaluateVisitor
if (result != null) return result;
}
return null;
}, semiGlobal: true);
}, semiGlobal: true, when: node.hasDeclarations);
}
// ## Expressions
@ -1230,9 +1242,6 @@ class _EvaluateVisitor
_verifyArguments(
positional.length, named, callable.declaration.arguments, span);
// TODO: if we get here and there are no rest params involved, mark
// the callable as fast-path and don't do error checking or extra
// allocations for future calls.
var declaredArguments = callable.declaration.arguments.arguments;
var minLength = math.min(positional.length, declaredArguments.length);
for (var i = 0; i < minLength; i++) {
@ -1603,8 +1612,10 @@ class _EvaluateVisitor
/// If [through] is passed, [node] is added as a child of the first parent for
/// which [through] returns `false`. That parent is copied unless it's the
/// lattermost child of its parent.
///
/// Runs [callback] in a new environment scope unless [scopeWhen] is false.
T _withParent<S extends CssParentNode, T>(S node, T callback(),
{bool through(CssNode node)}) {
{bool through(CssNode node), bool scopeWhen: true}) {
var oldParent = _parent;
// Go up through parents that match [through].
@ -1626,7 +1637,7 @@ class _EvaluateVisitor
parent.addChild(node);
_parent = node;
var result = _environment.scope(callback);
var result = _environment.scope(callback, when: scopeWhen);
_parent = oldParent;
return result;