Even more API docs.

This commit is contained in:
Natalie Weizenbaum 2016-10-09 20:57:10 -07:00
parent 3a0c5ce09c
commit 3e10e24341
21 changed files with 355 additions and 91 deletions

View File

@ -75,10 +75,12 @@ Future<String> _loadVersion() async {
var version = const String.fromEnvironment('version');
if (version != null) return version;
var libDir = p.fromUri(
await Isolate.resolvePackageUri(Uri.parse('package:sass/')));
var libDir =
p.fromUri(await Isolate.resolvePackageUri(Uri.parse('package:sass/')));
var pubspec = readFile(p.join(libDir, '..', 'pubspec.yaml'));
return pubspec.split("\n")
return pubspec
.split("\n")
.firstWhere((line) => line.startsWith('version: '))
.split(" ").last;
.split(" ")
.last;
}

View File

@ -25,7 +25,7 @@ class MediaRule implements Statement {
: queries = new List.unmodifiable(queries),
children = new List.unmodifiable(children) {
if (this.queries.isEmpty) {
throw new ArgumentException("queries may not be empty.");
throw new ArgumentError("queries may not be empty.");
}
}

View File

@ -25,7 +25,7 @@ class SupportsOperation implements SupportsCondition {
SupportsOperation(this.left, this.right, this.operator, this.span) {
var lowerOperator = operator.toLowerCase();
if (lowerOperator != "and" && lowerOperator != "or") {
throw new ArgumentDeclaration(
throw new ArgumentError.value(
operator, 'operator', 'may only be "and" or "or".');
}
}

View File

@ -195,7 +195,7 @@ class SelectorList extends Selector {
if (suffix != null) {
last = new CompoundSelector(
last.components.take(last.components.length - 1).toList()
..add(_addSuffix(last.components.last, suffix))
..add(last.components.last.addSuffix(suffix))
..addAll(resolvedMembers.skip(1)));
} else {
last = new CompoundSelector(
@ -209,32 +209,6 @@ class SelectorList extends Selector {
});
}
/// Returns a new [SimpleSelector] based on [simple], as though it had been
/// written with [suffix] at the end.
///
/// Assumes [suffix] is a valid identifier suffix. If this wouldn't produce a
/// valid [SimpleSelector], throws an [InternalException].
SimpleSelector _addSuffix(SimpleSelector simple, String suffix) {
if (simple is ClassSelector) {
return new ClassSelector(simple.name + suffix);
} else if (simple is IDSelector) {
return new IDSelector(simple.name + suffix);
} else if (simple is PlaceholderSelector) {
return new PlaceholderSelector(simple.name + suffix);
} else if (simple is TypeSelector) {
return new TypeSelector(new QualifiedName(
simple.name.name + suffix,
namespace: simple.name.namespace));
} else if (simple is PseudoSelector &&
simple.argument == null &&
simple.selector == null) {
return new PseudoSelector(simple.name + suffix, simple.type);
}
throw new InternalException(
'Parent "$simple" is incompatible with this selector.');
}
/// Whether this is a superselector of [other].
///
/// That is, whether this matches every element that [other] matches, as well

View File

@ -5,7 +5,16 @@
import '../../visitor/interface/selector.dart';
import '../selector.dart';
/// A selector that matches the parent in the Sass stylesheet.
///
/// This is not a plain CSS selectorit should be removed before emitting a CSS
/// document.
class ParentSelector extends SimpleSelector {
/// The suffix that will be added to the parent selector after it's been
/// resolved.
///
/// This is assumed to be a valid identifier suffix. It may be `null`,
/// indicating that the parent selector will not be modified.
final String suffix;
ParentSelector({this.suffix});

View File

@ -5,7 +5,13 @@
import '../../visitor/interface/selector.dart';
import '../selector.dart';
/// A placeholder selector.
///
/// This doesn't match any elements. It's intended to be extended using
/// `@extend`. It's not a plain CSS selector—it should be removed before
/// emitting a CSS document.
class PlaceholderSelector extends SimpleSelector {
/// The name of the placeholder.
final String name;
PlaceholderSelector(this.name);

View File

@ -8,15 +8,39 @@ import '../../utils.dart';
import '../../visitor/interface/selector.dart';
import '../selector.dart';
/// A pseudo-class or pseudo-element selector.
///
/// The semantics of a specific pseudo selector depends on its name. Some
/// selectors take arguments, including other selectors. Sass manually encodes
/// logic for each pseudo selector that takes a selector as an argument, to
/// ensure that extension and other selector operations work properly.
class PseudoSelector extends SimpleSelector {
/// The name of this selector.
final String name;
/// Like [name], but without any vendor prefixes.
final String normalizedName;
final PseudoType type;
/// Whether this is a pseudo-class selector.
///
/// This is `true` if and only if [isElement] is `false`.
final bool isClass;
/// Whether this is a pseudo-element selector.
///
/// This is `true` if and only if [isClass] is `false`.
bool get isElement => !isClass;
/// The non-selector argument passed to this selector.
///
/// This is `null` if there's no argument. If [argument] and [selector] are
/// both non-`null`, the selector follows the argument.
final String argument;
/// The selector argument passed to this selector.
///
/// This is `null` if there's no selector. If [argument] and [selector] are
/// both non-`null`, the selector follows the argument.
final SelectorList selector;
int get minSpecificity {
@ -33,16 +57,20 @@ class PseudoSelector extends SimpleSelector {
int _maxSpecificity;
PseudoSelector(String name, this.type, {this.argument, this.selector})
: name = name,
PseudoSelector(String name,
{bool element: false, this.argument, this.selector})
: isClass = !element,
name = name,
normalizedName = unvendor(name);
PseudoSelector withSelector(SelectorList selector) =>
new PseudoSelector(name, type, argument: argument, selector: selector);
/// Returns a new [PseudoSelector] based on this, but with the selector
/// replaced with [selector].
PseudoSelector withSelector(SelectorList selector) => new PseudoSelector(name,
element: isElement, argument: argument, selector: selector);
PseudoSelector addSuffix(String suffix) {
if (argument != null || selector != null) super.addSuffix(suffix);
return new PseudoSelector(name + suffix, type);
return new PseudoSelector(name + suffix, element: isElement);
}
List<SimpleSelector> unify(List<SimpleSelector> compound) {
@ -51,10 +79,10 @@ class PseudoSelector extends SimpleSelector {
var result = <SimpleSelector>[];
var addedThis = false;
for (var simple in compound) {
if (simple is PseudoSelector && simple.type == PseudoType.element) {
if (simple is PseudoSelector && simple.isElement) {
// A given compound selector may only contain one pseudo element. If
// [compound] has a different one than [this], unification fails.
if (this.type == PseudoType.element) return null;
if (this.isElement) return null;
// Otherwise, this is a pseudo selector and should come before pseduo
// elements.
@ -69,8 +97,9 @@ class PseudoSelector extends SimpleSelector {
return result;
}
/// Computes [_minSpecificity] and [_maxSpecificity].
void _computeSpecificity() {
if (type == PseudoType.element) {
if (isElement) {
_minSpecificity = 1;
_maxSpecificity = 1;
return;
@ -107,21 +136,13 @@ class PseudoSelector extends SimpleSelector {
bool operator ==(other) =>
other is PseudoSelector &&
other.name == name &&
other.type == type &&
other.isClass == isClass &&
other.argument == argument &&
other.selector == selector;
int get hashCode =>
name.hashCode ^ type.hashCode ^ argument.hashCode ^ selector.hashCode;
}
class PseudoType {
static const element = const PseudoType._("::");
static const klass = const PseudoType._(":");
final String _text;
const PseudoType._(this._text);
String toString() => _text;
name.hashCode ^
isElement.hashCode ^
argument.hashCode ^
selector.hashCode;
}

View File

@ -6,24 +6,55 @@ import '../../exception.dart';
import '../../parse/selector.dart';
import '../selector.dart';
/// An abstract superclass for simple selectors.
abstract class SimpleSelector extends Selector {
// 1000 is the base used for calculating selector specificity.
//
// The spec says this should be "sufficiently high"; it's extremely unlikely
// that any single selector sequence will contain 1,000 simple selectors.
/// The minimum possible specificity that this selector can have.
///
/// Pseudo selectors that contain selectors, like `:not()` and `:matches()`,
/// can have a range of possible specificities.
///
/// Specifity is represented in base 1000. The spec says this should be
/// "sufficiently high"; it's extremely unlikely that any single selector
/// sequence will contain 1000 simple selectors.
int get minSpecificity => 1000;
/// The maximum possible specificity that this selector can have.
///
/// Pseudo selectors that contain selectors, like `:not()` and `:matches()`,
/// can have a range of possible specificities.
int get maxSpecificity => minSpecificity;
SimpleSelector();
/// Parses a simple selector from [contents].
///
/// If passed, [url] is the name of the file from which [contents] comes.
/// [allowParent] controls whether a [ParentSelector] is allowed in this
/// selector.
///
/// Throws a [SassFormatException] if parsing fails.
factory SimpleSelector.parse(String contents,
{url, bool allowParent: true}) =>
new SelectorParser(contents, url: url, allowParent: allowParent)
.parseSimpleSelector();
/// Returns a new [SimpleSelector] based on [this], as though it had been
/// written with [suffix] at the end.
///
/// Assumes [suffix] is a valid identifier suffix. If this wouldn't produce a
/// valid [SimpleSelector], throws an [InternalException].
SimpleSelector addSuffix(String suffix) =>
throw new InternalException('Invalid parent selector "$this"');
/// Returns the compoments of a [CompoundSelector] that matches only elements
/// matched by both this and [compound].
///
/// By default, this just returns a copy of [compound] with this selector
/// added to the end, or returns the original array if this selector already
/// exists in it.
///
/// Returns `null` if unification is impossiblefor example, if there are
/// multiple ID selectors.
List<SimpleSelector> unify(List<SimpleSelector> compound) {
if (compound.contains(this)) return compound;

View File

@ -6,6 +6,9 @@ import '../../extend/functions.dart';
import '../../visitor/interface/selector.dart';
import '../selector.dart';
/// A type selector.
///
/// This selects elements whose name equals the given name.
class TypeSelector extends SimpleSelector {
final QualifiedName name;

View File

@ -6,7 +6,14 @@ import '../../extend/functions.dart';
import '../../visitor/interface/selector.dart';
import '../selector.dart';
/// Matches any element in the given namespace.
class UniversalSelector extends SimpleSelector {
/// The selector namespace.
///
/// If this is `null`, this matches all elements in the default namespace. If
/// it's the empty string, this matches all elements that aren't in any
/// namespace. If it's `*`, this matches all elements in any namespace.
/// Otherwise, it matches all elements in the given namespace.
final String namespace;
int get minSpecificity => 0;

View File

@ -5,6 +5,16 @@
export 'callable/built_in.dart';
export 'callable/user_defined.dart';
/// An interface for objects, such as functions and mixins, that can be invoked
/// from Sass by passing in arguments.
abstract class Callable {
/// The callable's name.
String get name;
// TODO(nweiz): I'd like to include the argument declaration on this interface
// as well, but supporting overloads for built-in callables makes that more
// difficult. Ideally, we'd define overloads as purely an implementation
// detail of functions, using a helper method. But that would need to
// duplicate a lot of the logic in PerformVisitor, and I can't find an elegant
// way to do that.
}

View File

@ -6,20 +6,55 @@ import '../ast/sass.dart';
import '../callable.dart';
import '../value.dart';
/// A [BuiltInCallable]'s callback.
typedef Value _Callback(List<Value> arguments);
/// A callable defined in Dart code.
///
/// Unlike user-defined callables, built-in callables support overloads. They
/// may declare multiple different callbacks with multiple different sets of
/// arguments. When the callable is invoked, the first callback with matching
/// arguments is invoked.
class BuiltInCallable implements Callable {
final String name;
/// The arguments declared for this callable.
///
/// The declaration at index `i` corresponds to the callback at index `i` in
/// [callbacks].
final List<ArgumentDeclaration> overloads;
/// The callbacks declared for this callable.
///
/// The callback at index `i` corresponds to the arguments at index `i` in
/// [overloads].
final List<_Callback> callbacks;
/// Creates a callable with a single [arguments] declaration and a single
/// [callback].
///
/// The argument declaration is parsed from [arguments], which should not
/// include parentheses. Throws a [SassFormatException] if parsing fails.
BuiltInCallable(
String name, String arguments, Value callback(List<Value> arguments))
: this.overloaded(name, [arguments], [callback]);
/// Creates a callable that declares multiple overloads.
///
/// The argument declarations are parsed from [overloads], whose contents
/// should not include parentheses. Throws a [SassFormatException] if parsing
/// fails.
///
/// Throws an [ArgumentError] if [overloads] doesn't have the same length as
/// [callbacks].
BuiltInCallable.overloaded(
this.name, Iterable<String> overloads, Iterable<_Callback> callbacks)
: overloads = new List.unmodifiable(overloads
.map((overload) => new ArgumentDeclaration.parse("$overload"))),
callbacks = new List.unmodifiable(callbacks);
callbacks = new List.unmodifiable(callbacks) {
if (this.overloads.length != this.callbacks.length) {
throw new ArgumentError(
"overloads must be the same length as callbacks.");
}
}
}

View File

@ -6,13 +6,15 @@ import '../ast/sass.dart';
import '../callable.dart';
import '../environment.dart';
/// A callback defined in the user's Sass stylesheet.
class UserDefinedCallable implements Callable {
/// The declaration.
final CallableDeclaration declaration;
/// The environment in which this callable was declared.
final Environment environment;
String get name => declaration.name;
ArgumentDeclaration get arguments => declaration.arguments;
UserDefinedCallable(this.declaration, this.environment);
}

View File

@ -5,6 +5,7 @@
import 'utils.dart';
import 'value.dart';
/// A map from (lowercase) color names to their color values.
final colorsByName = normalizedMap()
..addAll({
'aliceblue': new SassColor.rgb(0xF0, 0xF8, 0xFF),

View File

@ -8,32 +8,78 @@ import 'functions.dart';
import 'value.dart';
import 'utils.dart';
// Lexical environment only
/// The lexical environment in which Sass is executed.
///
/// This tracks lexically-scoped information, such as variables, functions, and
/// mixins.
class Environment {
/// Base is global scope.
/// A list of variables defined at each lexical scope level.
///
/// Each scope maps the names of declared variables to their values. These
/// maps are *normalized*, meaning that they treat hyphens and underscores in
/// its keys interchangeably.
///
/// The first element is the global scope, and each successive element is
/// deeper in the tree.
final List<Map<String, Value>> _variables;
// Note: this is not necessarily complete
/// A map of variable names to their indices in [_variables].
///
/// This map is *normalized*, meaning that it treats hyphens and underscores
/// in its keys interchangeably.
///
/// This map is filled in as-needed, and may not be complete.
final Map<String, int> _variableIndices;
/// A list of functions defined at each lexical scope level.
///
/// Each scope maps the names of declared functions to their values. These
/// maps are *normalized*, meaning that they treat hyphens and underscores in
/// its keys interchangeably.
///
/// The first element is the global scope, and each successive element is
/// deeper in the tree.
final List<Map<String, Callable>> _functions;
// Note: this is not necessarily complete
/// A map of function names to their indices in [_functions].
///
/// This map is *normalized*, meaning that it treats hyphens and underscores
/// in its keys interchangeably.
///
/// This map is filled in as-needed, and may not be complete.
final Map<String, int> _functionIndices;
/// A list of mixins defined at each lexical scope level.
///
/// Each scope maps the names of declared mixins to their values. These
/// maps are *normalized*, meaning that they treat hyphens and underscores in
/// its keys interchangeably.
///
/// The first element is the global scope, and each successive element is
/// deeper in the tree.
final List<Map<String, Callable>> _mixins;
// Note: this is not necessarily complete
/// A map of mixin names to their indices in [_mixins].
///
/// This map is *normalized*, meaning that it treats hyphens and underscores
/// in its keys interchangeably.
///
/// This map is filled in as-needed, and may not be complete.
final Map<String, int> _mixinIndices;
/// The content block passed to the lexically-current mixin, if any.
/// The content block passed to the lexically-enclosing mixin, or `null` if this is not
/// in a mixin, or if no content block was passed.
List<Statement> get contentBlock => _contentBlock;
List<Statement> _contentBlock;
/// The environment for [_contentBlock].
/// The environment in which [_contentBlock] should be executed.
Environment get contentEnvironment => _contentEnvironment;
Environment _contentEnvironment;
/// Whether the environment is currently in a semi-global scope.
///
/// A semi-global scope can assign to global variables, but it doesn't declare
/// them by default.
var _inSemiGlobalScope = false;
Environment()
@ -56,6 +102,11 @@ class Environment {
this._contentBlock,
this._contentEnvironment);
/// Creates a closure based on this environment.
///
/// Any scope changes in this environment will not affect the closure.
/// However, any new declarations or assignments in scopes that are visible
/// when the closure was created will be reflected.
Environment closure() => new Environment._(
_variables.toList(),
new Map.from(_variableIndices),
@ -66,9 +117,22 @@ class Environment {
_contentBlock,
_contentEnvironment);
Environment global() => new Environment._([_variables.first], {},
[_functions.first], {}, [_mixins.first], {}, null, null);
/// Returns a new environment.
///
/// The returned environment shares this environment's global, but is
/// otherwise independent.
Environment global() => new Environment._(
[_variables.first],
new Map.fromIterable(_variables.first.keys, value: (_) => 1),
[_functions.first],
new Map.fromIterable(_functions.first.keys, value: (_) => 1),
[_mixins.first],
new Map.fromIterable(_mixins.first.keys, value: (_) => 1),
null,
null);
/// Returns the value of the variable named [name], or `null` if no such
/// variable is declared.
Value getVariable(String name) {
var index = _variableIndices[name];
if (index != null) _variables[index][name];
@ -80,10 +144,14 @@ class Environment {
return _variables[index][name];
}
/// Returns whether a variable named [name] exists.
bool variableExists(String name) => getVariable(name) != null;
/// Returns whether a global variable named [name] exists.
bool globalVariableExists(String name) => _variables.first.containsKey(name);
/// Returns the index of the last map in [_variables] that has a [name] key,
/// or `null` if none exists.
int _variableIndex(String name) {
for (var i = _variables.length - 1; i >= 0; i--) {
if (_variables[i].containsKey(name)) return i;
@ -91,6 +159,11 @@ class Environment {
return null;
}
/// Sets the variable named [name] to [value].
///
/// If [global] is `true`, this sets the variable at the top-level scope.
/// Otherwise, if the variable was already defined, it'll set it in the
/// previous scope. If it's undefined, it'll set it in the current scope.
void setVariable(String name, Value value, {bool global: false}) {
var index = global || _variables.length == 1
? 0
@ -99,12 +172,18 @@ class Environment {
_variables[index][name] = value;
}
/// Sets the variable named [name] to [value] in the current scope.
///
/// Unlike [setVariable], this will declare the variable in the current scope
/// even if a declaration already exists in an outer scope.
void setLocalVariable(String name, Value value) {
var index = _variables.length - 1;
_variableIndices[name] = index;
_variables[index][name] = value;
}
/// Returns the value of the function named [name], or `null` if no such
/// function is declared.
Callable getFunction(String name) {
var index = _functionIndices[name];
if (index != null) _functions[index][name];
@ -116,6 +195,8 @@ class Environment {
return _functions[index][name];
}
/// Returns the index of the last map in [_functions] that has a [name] key,
/// or `null` if none exists.
int _functionIndex(String name) {
for (var i = _functions.length - 1; i >= 0; i--) {
if (_functions[i].containsKey(name)) return i;
@ -123,16 +204,23 @@ class Environment {
return null;
}
/// Returns whether a function named [name] exists.
bool functionExists(String name) => getFunction(name) != null;
/// Sets the variable named [name] to [value] in the current scope.
void setFunction(Callable callable) {
_functions[_functions.length - 1][callable.name] = callable;
var index = _functions.length - 1;
_functionIndices[callable.name] = index;
_functions[index][callable.name] = callable;
}
/// Shorthand for passing [new BuiltInCallable] to [setFunction].
void defineFunction(String name, String arguments,
Value callback(List<Value> arguments)) =>
setFunction(new BuiltInCallable(name, arguments, callback));
/// Returns the value of the mixin named [name], or `null` if no such mixin is
/// declared.
Callable getMixin(String name) {
var index = _mixinIndices[name];
if (index != null) _mixins[index][name];
@ -144,6 +232,8 @@ class Environment {
return _mixins[index][name];
}
/// Returns the index of the last map in [_mixins] that has a [name] key, or
/// `null` if none exists.
int _mixinIndex(String name) {
for (var i = _mixins.length - 1; i >= 0; i--) {
if (_mixins[i].containsKey(name)) return i;
@ -151,12 +241,18 @@ class Environment {
return null;
}
/// Returns whether a mixin named [name] exists.
bool mixinExists(String name) => getFunction(name) != null;
/// Sets the variable named [name] to [value] in the current scope.
void setMixin(Callable callable) {
_mixins[_mixins.length - 1][callable.name] = callable;
var index = _mixins.length - 1;
_mixinIndices[callable.name] = index;
_mixins[index][callable.name] = callable;
}
/// Sets [block] and [environment] as [contentBlock] and [contentEnvironment],
/// respectively, for the duration of [callback].
void withContent(
List<Statement> block, Environment environment, void callback()) {
var oldBlock = _contentBlock;
@ -168,6 +264,11 @@ class Environment {
_contentEnvironment = oldEnvironment;
}
/// Runs [callback] in a new scope.
///
/// 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);

View File

@ -5,13 +5,16 @@
import 'package:source_span/source_span.dart';
import 'package:stack_trace/stack_trace.dart';
/// An exception thrown by Sass.
class SassException extends SourceSpanException {
FileSpan get span => super.span as FileSpan;
SassException(String message, FileSpan span) : super(message, span);
}
/// An exception thrown by Sass while evaluating a stylesheet.
class SassRuntimeException extends SassException {
/// The Sass stack trace at the point this exception was thrown.
final Trace trace;
SassRuntimeException(String message, FileSpan span, this.trace)
@ -28,6 +31,7 @@ class SassRuntimeException extends SassException {
}
}
/// An exception thrown when Sass parsing has failed.
class SassFormatException extends SourceSpanFormatException
implements SassException {
FileSpan get span => super.span as FileSpan;
@ -37,7 +41,13 @@ class SassFormatException extends SourceSpanFormatException
SassFormatException(String message, FileSpan span) : super(message, span);
}
/// An exception thrown by SassScript that doesn't yet have a [FileSpan]
/// associated with it.
///
/// This is caught by Sass and converted to a [SassRuntimeException] with a
/// source span and a stack trace.
class InternalException {
/// The error message.
final String message;
InternalException(this.message);

View File

@ -13,6 +13,7 @@ import '../exception.dart';
import 'source.dart';
import 'functions.dart';
/// Tracks style rules and extensions, and applies the latter to the former.
class Extender {
/// A map from all simple selectors in the stylesheet to the rules that
/// contain them.
@ -20,10 +21,23 @@ class Extender {
/// This is used to find which rules an `@extend` applies to.
final _selectors = <SimpleSelector, Set<CssStyleRule>>{};
/// A map from all extended simple selectors to the sources of those
/// extensions.
final _extensions = <SimpleSelector, Set<ExtendSource>>{};
/// An expando from [SimpleSelector]s to [ComplexSelector]s.
///
/// This tracks the [ComplexSelector]s that originally contained each
/// [SimpleSelector]. This allows us to ensure that we don't trim any
/// selectors that need to exist to satisfy the [second law of extend][].
///
/// [second law of extend]: https://github.com/sass/sass/issues/324#issuecomment-4607184
final _sources = new Expando<ComplexSelector>();
/// Extends [selector] with [source] extender and [target] the extendee.
///
/// This works as though `source {@extend target}` were written in
/// the stylesheet.
static SelectorList extend(
SelectorList selector, SelectorList source, SimpleSelector target) =>
new Extender()._extendList(selector, {
@ -31,6 +45,7 @@ class Extender {
..add(new ExtendSource(new CssValue(source, null), null))
});
/// Returns a copy of [selector] with [source] replaced by [target].
static SelectorList replace(
SelectorList selector, SelectorList source, SimpleSelector target) =>
new Extender()._extendList(
@ -41,6 +56,11 @@ class Extender {
},
replace: true);
/// Adds [selector] to this extender, associated with [span].
///
/// Extends [selector] using any registered extensions, then returns an empty
/// [CssStyleRule] with the resulting selector. If any more relevant
/// extensions are added, the returned rule is automatically updated.
CssStyleRule addSelector(CssValue<SelectorList> selector, FileSpan span) {
for (var complex in selector.value.components) {
for (var component in complex.components) {
@ -62,6 +82,8 @@ class Extender {
return rule;
}
/// Registers the [SimpleSelector]s in [list] to point to [rule] in
/// [_selectors].
void _registerSelector(SelectorList list, CssStyleRule rule) {
for (var complex in list.components) {
for (var component in complex.components) {
@ -78,9 +100,14 @@ class Extender {
}
}
/// Adds an extension to this extender.
///
/// The [extender] is the selector for the style rule in which the extension
/// is defined, and [target] is the selector passed to `@extend`. The [extend]
/// provides the extend span and indicates whether the extension is optional.
void addExtension(CssValue<SelectorList> extender, SimpleSelector target,
ExtendRule extend) {
var source = new ExtendSource(extender, extend.span);
var source = new ExtendSource(extender, target, extend.span);
source.isUsed = extend.isOptional;
_extensions.putIfAbsent(target, () => new Set()).add(source);
@ -93,6 +120,8 @@ class Extender {
}
}
/// Throws a [SassException] if any (non-optional) extensions failed to match
/// any selectors.
void finalize() {
for (var sources in _extensions.values) {
for (var source in sources) {
@ -105,6 +134,9 @@ class Extender {
}
}
/// Extends [list] using [extensions].
///
/// If [replace] is `true`, this doesn't preserve the original selectors.
SelectorList _extendList(
SelectorList list, Map<SimpleSelector, Set<ExtendSource>> extensions,
{bool replace: false}) {
@ -128,6 +160,10 @@ class Extender {
return new SelectorList(newList.where((complex) => complex != null));
}
/// Extends [complex] using [extensions], and returns the contents of a
/// [SelectorList].
///
/// If [replace] is `true`, this doesn't preserve the original selectors.
Iterable<ComplexSelector> _extendComplex(ComplexSelector complex,
Map<SimpleSelector, Set<ExtendSource>> extensions,
{bool replace: false}) {
@ -179,6 +215,10 @@ class Extender {
return result;
}
/// Extends [compound] using [extensions], and returns the contents of a
/// [SelectorList].
///
/// If [replace] is `true`, this doesn't preserve the original selectors.
List<ComplexSelector> _extendCompound(CompoundSelector compound,
Map<SimpleSelector, Set<ExtendSource>> extensions,
{bool replace: false}) {
@ -249,6 +289,10 @@ class Extender {
return extended;
}
/// Extends [pseudo] using [extensions], and returns a list of resulting
/// pseudo selectors.
///
/// If [replace] is `true`, this doesn't preserve the original selectors.
List<PseudoSelector> _extendPseudo(
PseudoSelector pseudo, Map<SimpleSelector, Set<ExtendSource>> extensions,
{bool replace: false}) {
@ -330,6 +374,12 @@ class Extender {
}
}
// Removes redundant selectors from [lists].
//
// Each individual list in [lists] is assumed to have no redundancy within
// itself. A selector is only removed if it's redundant with a selector in
// another list. "Redundant" here means that one selector is a superselector
// of the other. The more specific selector is removed.
List<ComplexSelector> _trim(List<List<ComplexSelector>> lists) {
// Avoid truly horrific quadratic behavior.
//

View File

@ -9,6 +9,10 @@ import 'package:collection/collection.dart';
import '../ast/selector.dart';
import '../utils.dart';
/// Names of pseudo selectors that take selectors as arguments, and that are
/// subselectors of their arguments.
///
/// For example, `.foo` is a superselector of `:matches(.foo)`.
final _subselectorPseudos =
new Set.from(['matches', 'any', 'nth-child', 'nth-last-child']);
@ -381,8 +385,7 @@ bool _mustUnify(List<ComplexSelectorComponent> complex1,
}
bool _isUnique(SimpleSelector simple) =>
simple is IDSelector ||
(simple is PseudoSelector && simple.type == PseudoType.element);
simple is IDSelector || (simple is PseudoSelector && simple.isElement);
List<List/*<T>*/ > _chunks/*<T>*/(
Queue/*<T>*/ queue1, Queue/*<T>*/ queue2, bool done(Queue/*<T>*/ queue)) {
@ -425,7 +428,7 @@ QueueList<List<ComplexSelectorComponent>> _groupSelectors(
bool _hasRoot(CompoundSelector compound) => compound.components.any((simple) =>
simple is PseudoSelector &&
simple.type == PseudoType.klass &&
simple.isClass &&
simple.normalizedName == 'root');
bool listIsSuperslector(
@ -542,7 +545,7 @@ bool compoundIsSuperselector(
// that [compound2] doesn't share.
for (var simple2 in compound2.components) {
if (simple2 is PseudoSelector &&
simple2.type == PseudoType.element &&
simple2.isElement &&
!_simpleIsSuperselectorOfCompound(simple2, compound1)) {
return false;
}
@ -637,6 +640,6 @@ Iterable<PseudoSelector> _selectorPseudosNamed(
CompoundSelector compound, String name) =>
compound.components.where((simple) =>
simple is PseudoSelector &&
simple.type == PseudoType.klass &&
simple.isClass &&
simple.selector != null &&
simple.name == name);

View File

@ -246,18 +246,18 @@ class SelectorParser extends Parser {
PseudoSelector _pseudoSelector() {
scanner.expectChar($colon);
var type = scanner.scanChar($colon) ? PseudoType.element : PseudoType.klass;
var element = scanner.scanChar($colon);
var name = identifier();
if (!scanner.scanChar($lparen)) {
return new PseudoSelector(name, type);
return new PseudoSelector(name, element: element);
}
whitespace();
var unvendored = unvendor(name);
String argument;
SelectorList selector;
if (type == PseudoType.element) {
if (element) {
argument = declarationValue();
} else if (_selectorPseudoClasses.contains(unvendored)) {
selector = _selectorList();
@ -275,8 +275,8 @@ class SelectorParser extends Parser {
}
scanner.expectChar($rparen);
return new PseudoSelector(name, type,
argument: argument, selector: selector);
return new PseudoSelector(name,
element: element, argument: argument, selector: selector);
}
void _aNPlusB() {
@ -337,8 +337,7 @@ class SelectorParser extends Parser {
if (scanner.scanChar($asterisk)) {
return new UniversalSelector(namespace: "");
} else {
return new TypeSelector(
new QualifiedName(identifier(), namespace: ""));
return new TypeSelector(new QualifiedName(identifier(), namespace: ""));
}
}

View File

@ -747,13 +747,13 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
return _withStackFrame(callable.name + "()", invocation.span, () {
return _withEnvironment(callable.environment, () {
return _environment.scope(() {
_verifyArguments(
positional.length, named, callable.arguments, invocation.span);
_verifyArguments(positional.length, named,
callable.declaration.arguments, invocation.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.arguments.arguments;
var declaredArguments = callable.declaration.arguments.arguments;
var minLength = math.min(positional.length, declaredArguments.length);
for (var i = 0; i < minLength; i++) {
_environment.setVariable(declaredArguments[i].name, positional[i]);
@ -768,7 +768,7 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
}
SassArgumentList argumentList;
if (callable.arguments.restArgument != null) {
if (callable.declaration.arguments.restArgument != null) {
var rest = positional.length > declaredArguments.length
? positional.sublist(declaredArguments.length)
: const <Value>[];
@ -779,7 +779,7 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
? ListSeparator.comma
: separator);
_environment.setVariable(
callable.arguments.restArgument, argumentList);
callable.declaration.arguments.restArgument, argumentList);
}
var result = run();

View File

@ -464,7 +464,7 @@ class _SerializeCssVisitor
void visitPseudoSelector(PseudoSelector pseudo) {
_buffer.writeCharCode($colon);
if (pseudo.type == PseudoType.element) _buffer.writeCharCode($colon);
if (pseudo.isElement) _buffer.writeCharCode($colon);
_buffer.write(pseudo.name);
if (pseudo.argument == null && pseudo.selector == null) return;