mirror of
https://github.com/danog/dart-sass.git
synced 2025-01-21 21:31:11 +01:00
Even more API docs.
This commit is contained in:
parent
3a0c5ce09c
commit
3e10e24341
@ -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;
|
||||
}
|
||||
|
@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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".');
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 selector—it 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});
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 impossible—for example, if there are
|
||||
/// multiple ID selectors.
|
||||
List<SimpleSelector> unify(List<SimpleSelector> compound) {
|
||||
if (compound.contains(this)) return compound;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
}
|
||||
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
//
|
||||
|
@ -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);
|
||||
|
@ -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: ""));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user