mirror of
https://github.com/danog/dart-sass.git
synced 2025-01-22 13:51:31 +01:00
Merge remote-tracking branch 'origin/master' into feature.hwb
This commit is contained in:
commit
b0f213c22f
84
CHANGELOG.md
84
CHANGELOG.md
@ -1,4 +1,4 @@
|
||||
## 1.27.0
|
||||
## 1.28.0
|
||||
|
||||
### Dart API
|
||||
|
||||
@ -7,6 +7,85 @@
|
||||
|
||||
[HWB]: https://en.wikipedia.org/wiki/HWB_color_model
|
||||
|
||||
## 1.27.0
|
||||
|
||||
* Adds an overload to `map.merge()` that supports merging a nested map.
|
||||
|
||||
`map.merge($map1, $keys..., $map2)`: The `$keys` form a path to the nested map
|
||||
in `$map1`, into which `$map2` gets merged.
|
||||
|
||||
See [the Sass documentation][map-merge] for more details.
|
||||
|
||||
[map-merge]: https://sass-lang.com/documentation/modules/map#merge
|
||||
|
||||
* Adds an overloaded `map.set()` function.
|
||||
|
||||
`map.set($map, $key, $value)`: Adds to or updates `$map` with the specified
|
||||
`$key` and `$value`.
|
||||
|
||||
`map.set($map, $keys..., $value)`: Adds to or updates a map that is nested
|
||||
within `$map`. The `$keys` form a path to the nested map in `$map`, into
|
||||
which `$value` is inserted.
|
||||
|
||||
See [the Sass documentation][map-set] for more details.
|
||||
|
||||
[map-set]: https://sass-lang.com/documentation/modules/map#set
|
||||
|
||||
* Add support for nested maps to `map.get()`.
|
||||
For example, `map.get((a: (b: (c: d))), a, b, c)` would return `d`.
|
||||
See [the documentation][map-get] for more details.
|
||||
|
||||
[map-get]: https://sass-lang.com/documentation/modules/map#get
|
||||
|
||||
* Add support for nested maps in `map.has-key`.
|
||||
For example, `map.has-key((a: (b: (c: d))), a, b, c)` would return true.
|
||||
See [the documentation][map-has-key] for more details.
|
||||
|
||||
[map-has-key]: https://sass-lang.com/documentation/modules/map#has-key
|
||||
|
||||
* Add a `map.deep-merge()` function. This works like `map.merge()`, except that
|
||||
nested map values are *also* recursively merged. For example:
|
||||
|
||||
```
|
||||
map.deep-merge(
|
||||
(color: (primary: red, secondary: blue),
|
||||
(color: (secondary: teal)
|
||||
) // => (color: (primary: red, secondary: teal))
|
||||
```
|
||||
|
||||
See [the Sass documentation][map-deep-merge] for more details.
|
||||
|
||||
[map-deep-merge]: https://sass-lang.com/documentation/modules/map#deep-merge
|
||||
|
||||
* Add a `map.deep-remove()` function. This allows you to remove keys from
|
||||
nested maps by passing multiple keys. For example:
|
||||
|
||||
```
|
||||
map.deep-remove(
|
||||
(color: (primary: red, secondary: blue)),
|
||||
color, primary
|
||||
) // => (color: (secondary: blue))
|
||||
```
|
||||
|
||||
See [the Sass documentation][map-deep-remove] for more details.
|
||||
|
||||
[map-deep-remove]: https://sass-lang.com/documentation/modules/map#deep-remove
|
||||
|
||||
* Fix a bug where custom property values in plain CSS were being parsed as
|
||||
normal property values.
|
||||
|
||||
### Dart API
|
||||
|
||||
* Add a `Value.tryMap()` function which returns the `Value` as a `SassMap` if
|
||||
it's a valid map, or `null` otherwise. This allows function authors to safely
|
||||
retrieve maps even if they're internally stored as empty lists, without having
|
||||
to catch exceptions from `Value.assertMap()`.
|
||||
|
||||
## 1.26.12
|
||||
|
||||
* Fix a bug where nesting properties beneath a Sass-syntax custom property
|
||||
(written as `#{--foo}: ...`) would crash.
|
||||
|
||||
## 1.26.11
|
||||
|
||||
* **Potentially breaking bug fix:** `selector.nest()` now throws an error
|
||||
@ -20,6 +99,9 @@
|
||||
|
||||
* Don't crash when writing `Infinity` in JS mode.
|
||||
|
||||
* Produce a better error message for positional arguments following named
|
||||
arguments.
|
||||
|
||||
## 1.26.10
|
||||
|
||||
* Fixes a bug where two adjacent combinators could cause an error.
|
||||
|
@ -2,7 +2,6 @@
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
|
@ -5,8 +5,6 @@
|
||||
/// We strongly recommend importing this library with the prefix `sass`.
|
||||
library sass;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:source_maps/source_maps.dart';
|
||||
|
||||
import 'src/async_import_cache.dart';
|
||||
|
@ -24,5 +24,16 @@ abstract class CssDeclaration extends CssNode {
|
||||
/// the variable was used. Otherwise, this is identical to [value.span].
|
||||
FileSpan get valueSpanForMap;
|
||||
|
||||
T accept<T>(CssVisitor<T> visitor) => visitor.visitCssDeclaration(this);
|
||||
/// Returns whether this is a CSS Custom Property declaration.
|
||||
bool get isCustomProperty;
|
||||
|
||||
/// Whether this is was originally parsed as a custom property declaration, as
|
||||
/// opposed to using something like `#{--foo}: ...` to cause it to be parsed
|
||||
/// as a normal Sass declaration.
|
||||
///
|
||||
/// If this is `true`, [isCustomProperty] will also be `true` and [value] will
|
||||
/// contain a [SassString].
|
||||
bool get parsedAsCustomProperty;
|
||||
|
||||
T accept<T>(CssVisitor<T> visitor);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import '../../../value.dart';
|
||||
@ -15,13 +16,32 @@ class ModifiableCssDeclaration extends ModifiableCssNode
|
||||
implements CssDeclaration {
|
||||
final CssValue<String> name;
|
||||
final CssValue<Value> value;
|
||||
final bool parsedAsCustomProperty;
|
||||
final FileSpan valueSpanForMap;
|
||||
final FileSpan span;
|
||||
|
||||
bool get isCustomProperty => name.value.startsWith('--');
|
||||
|
||||
/// Returns a new CSS declaration with the given properties.
|
||||
ModifiableCssDeclaration(this.name, this.value, this.span,
|
||||
{FileSpan valueSpanForMap})
|
||||
: valueSpanForMap = valueSpanForMap ?? span;
|
||||
{@required bool parsedAsCustomProperty, FileSpan valueSpanForMap})
|
||||
: parsedAsCustomProperty = parsedAsCustomProperty,
|
||||
valueSpanForMap = valueSpanForMap ?? span {
|
||||
if (parsedAsCustomProperty) {
|
||||
if (!isCustomProperty) {
|
||||
throw ArgumentError(
|
||||
'parsedAsCustomProperty must be false if name doesn\'t begin with '
|
||||
'"--".');
|
||||
} else if (value.value is! SassString) {
|
||||
throw ArgumentError(
|
||||
'If parsedAsCustomProperty is true, value must contain a SassString '
|
||||
'(was `$value` of type ${value.value.runtimeType}).');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
T accept<T>(ModifiableCssVisitor<T> visitor) =>
|
||||
visitor.visitCssDeclaration(this);
|
||||
|
||||
String toString() => "$name: $value;";
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import 'package:source_span/source_span.dart';
|
||||
|
||||
import '../../../visitor/interface/statement.dart';
|
||||
import '../expression.dart';
|
||||
import '../expression/string.dart';
|
||||
import '../interpolation.dart';
|
||||
import '../statement.dart';
|
||||
import 'parent.dart';
|
||||
@ -20,10 +21,24 @@ class Declaration extends ParentStatement {
|
||||
|
||||
final FileSpan span;
|
||||
|
||||
/// Returns whether this is a CSS Custom Property declaration.
|
||||
///
|
||||
/// Note that this can return `false` for declarations that will ultimately be
|
||||
/// serialized as custom properties if they aren't *parsed as* custom
|
||||
/// properties, such as `#{--foo}: ...`.
|
||||
///
|
||||
/// If this is `true`, then `value` will be a [StringExpression].
|
||||
bool get isCustomProperty => name.initialPlain.startsWith('--');
|
||||
|
||||
Declaration(this.name, this.span, {this.value, Iterable<Statement> children})
|
||||
: super(children = children == null ? null : List.unmodifiable(children));
|
||||
: super(
|
||||
children = children == null ? null : List.unmodifiable(children)) {
|
||||
if (isCustomProperty && value is! StringExpression) {
|
||||
throw ArgumentError(
|
||||
'Declarations whose names begin with "--" must have StringExpression '
|
||||
'values (was `${value}` of type ${value.runtimeType}).');
|
||||
}
|
||||
}
|
||||
|
||||
T accept<T>(StatementVisitor<T> visitor) => visitor.visitDeclaration(this);
|
||||
|
||||
String toString() => "$name: $value;";
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
|
@ -2,7 +2,6 @@
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
@ -2,8 +2,6 @@
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
@ -5,7 +5,7 @@
|
||||
// DO NOT EDIT. This file was generated from async_compile.dart.
|
||||
// See tool/grind/synchronize.dart for details.
|
||||
//
|
||||
// Checksum: 193c9bc1905022e881636f4d6359c906146abcb3
|
||||
// Checksum: bca3a79dd4a5c3905b07003b123172f3c876d2de
|
||||
//
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
// DO NOT EDIT. This file was generated from async_environment.dart.
|
||||
// See tool/grind/synchronize.dart for details.
|
||||
//
|
||||
// Checksum: d304e1c5208019bca99df0678c3fdb4d09776a2b
|
||||
// Checksum: 9f4ee98a1c9e90d8d5277e0c2b0355460cda8788
|
||||
//
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
|
@ -2,7 +2,6 @@
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:cli_repl/cli_repl.dart';
|
||||
|
@ -7,6 +7,7 @@ import 'dart:collection';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
import '../callable.dart';
|
||||
import '../exception.dart';
|
||||
import '../module/built_in.dart';
|
||||
import '../value.dart';
|
||||
|
||||
@ -21,19 +22,89 @@ final global = UnmodifiableListView([
|
||||
]);
|
||||
|
||||
/// The Sass map module.
|
||||
final module = BuiltInModule("map",
|
||||
functions: [_get, _merge, _remove, _keys, _values, _hasKey]);
|
||||
final module = BuiltInModule("map", functions: [
|
||||
_get,
|
||||
_set,
|
||||
_merge,
|
||||
_remove,
|
||||
_keys,
|
||||
_values,
|
||||
_hasKey,
|
||||
_deepMerge,
|
||||
_deepRemove
|
||||
]);
|
||||
|
||||
final _get = _function("get", r"$map, $key", (arguments) {
|
||||
final _get = _function("get", r"$map, $key, $keys...", (arguments) {
|
||||
var map = arguments[0].assertMap("map");
|
||||
var key = arguments[1];
|
||||
return map.contents[key] ?? sassNull;
|
||||
var keys = [arguments[1], ...arguments[2].asList];
|
||||
for (var key in keys.take(keys.length - 1)) {
|
||||
var value = map.contents[key];
|
||||
if (value is SassMap) {
|
||||
map = value;
|
||||
} else {
|
||||
return sassNull;
|
||||
}
|
||||
}
|
||||
return map.contents[keys.last] ?? sassNull;
|
||||
});
|
||||
|
||||
final _merge = _function("merge", r"$map1, $map2", (arguments) {
|
||||
final _set = BuiltInCallable.overloadedFunction("set", {
|
||||
r"$map, $key, $value": (arguments) {
|
||||
var map = arguments[0].assertMap("map");
|
||||
return _modify(map, [arguments[1]], (_) => arguments[2]);
|
||||
},
|
||||
r"$map, $args...": (arguments) {
|
||||
var map = arguments[0].assertMap("map");
|
||||
var args = arguments[1].asList;
|
||||
if (args.isEmpty) {
|
||||
throw SassScriptException("Expected \$args to contain a key.");
|
||||
} else if (args.length == 1) {
|
||||
throw SassScriptException("Expected \$args to contain a value.");
|
||||
}
|
||||
return _modify(map, args.sublist(0, args.length - 1), (_) => args.last);
|
||||
},
|
||||
});
|
||||
|
||||
final _merge = BuiltInCallable.overloadedFunction("merge", {
|
||||
r"$map1, $map2": (arguments) {
|
||||
var map1 = arguments[0].assertMap("map1");
|
||||
var map2 = arguments[1].assertMap("map2");
|
||||
return SassMap({...map1.contents, ...map2.contents});
|
||||
},
|
||||
r"$map1, $args...": (arguments) {
|
||||
var map1 = arguments[0].assertMap("map1");
|
||||
var args = arguments[1].asList;
|
||||
if (args.isEmpty) {
|
||||
throw SassScriptException("Expected \$args to contain a key.");
|
||||
} else if (args.length == 1) {
|
||||
throw SassScriptException("Expected \$args to contain a map.");
|
||||
}
|
||||
var map2 = args.last.assertMap("map2");
|
||||
return _modify(map1, args.take(args.length - 1), (oldValue) {
|
||||
var nestedMap = oldValue?.tryMap();
|
||||
if (nestedMap == null) return map2;
|
||||
return SassMap({...nestedMap.contents, ...map2.contents});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
final _deepMerge = _function("deep-merge", r"$map1, $map2", (arguments) {
|
||||
var map1 = arguments[0].assertMap("map1");
|
||||
var map2 = arguments[1].assertMap("map2");
|
||||
return SassMap({...map1.contents, ...map2.contents});
|
||||
return _deepMergeImpl(map1, map2);
|
||||
});
|
||||
|
||||
final _deepRemove =
|
||||
_function("deep-remove", r"$map, $key, $keys...", (arguments) {
|
||||
var map = arguments[0].assertMap("map");
|
||||
var keys = [arguments[1], ...arguments[2].asList];
|
||||
return _modify(map, keys.take(keys.length - 1), (value) {
|
||||
var nestedMap = value?.tryMap();
|
||||
if (nestedMap?.contents?.containsKey(keys.last) ?? false) {
|
||||
return SassMap(Map.of(nestedMap.contents)..remove(keys.last));
|
||||
}
|
||||
return value;
|
||||
});
|
||||
});
|
||||
|
||||
final _remove = BuiltInCallable.overloadedFunction("remove", {
|
||||
@ -67,12 +138,100 @@ final _values = _function(
|
||||
(arguments) => SassList(
|
||||
arguments[0].assertMap("map").contents.values, ListSeparator.comma));
|
||||
|
||||
final _hasKey = _function("has-key", r"$map, $key", (arguments) {
|
||||
final _hasKey = _function("has-key", r"$map, $key, $keys...", (arguments) {
|
||||
var map = arguments[0].assertMap("map");
|
||||
var key = arguments[1];
|
||||
return SassBoolean(map.contents.containsKey(key));
|
||||
var keys = [arguments[1], ...arguments[2].asList];
|
||||
for (var key in keys.take(keys.length - 1)) {
|
||||
var value = map.contents[key];
|
||||
if (value is SassMap) {
|
||||
map = value;
|
||||
} else {
|
||||
return sassFalse;
|
||||
}
|
||||
}
|
||||
return SassBoolean(map.contents.containsKey(keys.last));
|
||||
});
|
||||
|
||||
/// Updates the specified value in [map] by applying the [modify] callback to
|
||||
/// it, then returns the resulting map.
|
||||
///
|
||||
/// If more than one key is provided, this means the map targeted for update is
|
||||
/// nested within [map]. The multiple [keys] form a path of nested maps that
|
||||
/// leads to the targeted map. If any value along the path is not a map, and
|
||||
/// `modify(null)` returns null, this inserts a new map at that key and
|
||||
/// overwrites the current value. Otherwise, this fails and returns [map] with
|
||||
/// no changes.
|
||||
///
|
||||
/// If no keys are provided, this passes [map] directly to modify and returns
|
||||
/// the result.
|
||||
Value _modify(SassMap map, Iterable<Value> keys, Value modify(Value old)) {
|
||||
var keyIterator = keys.iterator;
|
||||
SassMap _modifyNestedMap(SassMap map, [Value newValue]) {
|
||||
var mutableMap = Map.of(map.contents);
|
||||
var key = keyIterator.current;
|
||||
|
||||
if (!keyIterator.moveNext()) {
|
||||
mutableMap[key] = newValue ?? modify(mutableMap[key]);
|
||||
return SassMap(mutableMap);
|
||||
}
|
||||
|
||||
var nestedMap = mutableMap[key]?.tryMap();
|
||||
if (nestedMap == null) {
|
||||
// We pass null to `modify` here to indicate there's no existing value.
|
||||
newValue = modify(null);
|
||||
if (newValue == null) return SassMap(mutableMap);
|
||||
}
|
||||
|
||||
nestedMap ??= const SassMap.empty();
|
||||
mutableMap[key] = _modifyNestedMap(nestedMap, newValue);
|
||||
return SassMap(mutableMap);
|
||||
}
|
||||
|
||||
return keyIterator.moveNext() ? _modifyNestedMap(map) : modify(map);
|
||||
}
|
||||
|
||||
/// Merges [map1] and [map2], with values in [map2] taking precedence.
|
||||
///
|
||||
/// If both [map1] and [map2] have a map value associated with the same key,
|
||||
/// this recursively merges those maps as well.
|
||||
SassMap _deepMergeImpl(SassMap map1, SassMap map2) {
|
||||
if (map2.contents.isEmpty) return map1;
|
||||
|
||||
// Avoid making a mutable copy of `map2` if it would totally overwrite `map1`
|
||||
// anyway.
|
||||
var mutable = false;
|
||||
var result = map2.contents;
|
||||
void _ensureMutable() {
|
||||
if (mutable) return;
|
||||
mutable = true;
|
||||
result = Map.of(result);
|
||||
}
|
||||
|
||||
// Because values in `map2` take precedence over `map1`, we just check if any
|
||||
// entires in `map1` don't have corresponding keys in `map2`, or if they're
|
||||
// maps that need to be merged in their own right.
|
||||
map1.contents.forEach((key, value) {
|
||||
var resultValue = result[key];
|
||||
if (resultValue == null) {
|
||||
_ensureMutable();
|
||||
result[key] = value;
|
||||
} else {
|
||||
var resultMap = resultValue.tryMap();
|
||||
var valueMap = value.tryMap();
|
||||
|
||||
if (resultMap != null && valueMap != null) {
|
||||
var merged = _deepMergeImpl(valueMap, resultMap);
|
||||
if (identical(merged, resultMap)) return;
|
||||
|
||||
_ensureMutable();
|
||||
result[key] = merged;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return mutable ? SassMap(result) : map2;
|
||||
}
|
||||
|
||||
/// Like [new BuiltInCallable.function], but always sets the URL to `sass:map`.
|
||||
BuiltInCallable _function(
|
||||
String name, String arguments, Value callback(List<Value> arguments)) =>
|
||||
|
@ -5,7 +5,7 @@
|
||||
// DO NOT EDIT. This file was generated from async_import_cache.dart.
|
||||
// See tool/grind/synchronize.dart for details.
|
||||
//
|
||||
// Checksum: 8f54034c56e0d38fc8c90ad4d5f017628cab6190
|
||||
// Checksum: 5293a11e290c86829547ddd982ee3b1b1536dc73
|
||||
//
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
|
@ -2,8 +2,6 @@
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class NodeImporter {
|
||||
|
@ -2,8 +2,6 @@
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:watcher/watcher.dart';
|
||||
|
||||
/// An output sink that writes to this process's standard error.
|
||||
|
@ -503,8 +503,11 @@ abstract class StylesheetParser extends Parser {
|
||||
/// This is only used in contexts where declarations are allowed but style
|
||||
/// rules are not, such as nested declarations. Otherwise,
|
||||
/// [_declarationOrStyleRule] is used instead.
|
||||
@protected
|
||||
Statement _propertyOrVariableDeclaration() {
|
||||
///
|
||||
/// If [parseCustomProperties] is `true`, properties that begin with `--` will
|
||||
/// be parsed using custom property parsing rules.
|
||||
Statement _propertyOrVariableDeclaration(
|
||||
{bool parseCustomProperties = true}) {
|
||||
var start = scanner.state;
|
||||
|
||||
Interpolation name;
|
||||
@ -533,6 +536,13 @@ abstract class StylesheetParser extends Parser {
|
||||
|
||||
whitespace();
|
||||
scanner.expectChar($colon);
|
||||
|
||||
if (parseCustomProperties && name.initialPlain.startsWith('--')) {
|
||||
var value = _interpolatedDeclarationValue();
|
||||
expectStatementSeparator("custom property");
|
||||
return Declaration(name, scanner.spanFrom(start), value: value);
|
||||
}
|
||||
|
||||
whitespace();
|
||||
|
||||
if (lookingAtChildren()) {
|
||||
@ -562,7 +572,7 @@ abstract class StylesheetParser extends Parser {
|
||||
/// Consumes a statement that's allowed within a declaration.
|
||||
Statement _declarationChild() {
|
||||
if (scanner.peekChar() == $at) return _declarationAtRule();
|
||||
return _propertyOrVariableDeclaration();
|
||||
return _propertyOrVariableDeclaration(parseCustomProperties: false);
|
||||
}
|
||||
|
||||
// ## At Rules
|
||||
@ -1581,7 +1591,8 @@ relase. For details, see http://bit.ly/moz-document.
|
||||
break;
|
||||
}
|
||||
} else if (named.isNotEmpty) {
|
||||
scanner.expect("...");
|
||||
error("Positional arguments must come before keyword arguments.",
|
||||
expression.span);
|
||||
} else {
|
||||
positional.add(expression);
|
||||
}
|
||||
|
@ -2,8 +2,6 @@
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
class SyncPackageResolver {
|
||||
static final _error =
|
||||
UnsupportedError('SyncPackageResolver is not supported in JS.');
|
||||
|
@ -2,7 +2,6 @@
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:charcode/charcode.dart';
|
||||
|
@ -98,6 +98,8 @@ abstract class Value implements ext.Value {
|
||||
SassMap assertMap([String name]) =>
|
||||
throw _exception("$this is not a map.", name);
|
||||
|
||||
SassMap tryMap() => null;
|
||||
|
||||
SassNumber assertNumber([String name]) =>
|
||||
throw _exception("$this is not a number.", name);
|
||||
|
||||
|
4
lib/src/value/external/value.dart
vendored
4
lib/src/value/external/value.dart
vendored
@ -104,6 +104,10 @@ abstract class Value {
|
||||
/// (without the `$`). It's used for error reporting.
|
||||
SassMap assertMap([String name]);
|
||||
|
||||
/// Returns [this] as a [SassMap] if it is one (including empty lists, which
|
||||
/// count as empty maps) or returns `null` if it's not.
|
||||
SassMap tryMap();
|
||||
|
||||
/// Throws a [SassScriptException] if [this] isn't a number.
|
||||
///
|
||||
/// If this came from a function argument, [name] is the argument name
|
||||
|
@ -46,6 +46,8 @@ class SassList extends Value implements ext.SassList {
|
||||
SassMap assertMap([String name]) =>
|
||||
asList.isEmpty ? const SassMap.empty() : super.assertMap(name);
|
||||
|
||||
SassMap tryMap() => asList.isEmpty ? const SassMap.empty() : null;
|
||||
|
||||
bool operator ==(Object other) =>
|
||||
(other is SassList &&
|
||||
other.separator == separator &&
|
||||
|
@ -32,6 +32,8 @@ class SassMap extends Value implements ext.SassMap {
|
||||
|
||||
SassMap assertMap([String name]) => this;
|
||||
|
||||
SassMap tryMap() => this;
|
||||
|
||||
bool operator ==(Object other) =>
|
||||
(other is SassMap && mapEquals(other.contents, contents)) ||
|
||||
(contents.isEmpty && other is SassList && other.asList.isEmpty);
|
||||
|
@ -1039,8 +1039,9 @@ class _EvaluateVisitor
|
||||
if (cssValue != null &&
|
||||
(!cssValue.value.isBlank || _isEmptyList(cssValue.value))) {
|
||||
_parent.addChild(ModifiableCssDeclaration(name, cssValue, node.span,
|
||||
parsedAsCustomProperty: node.isCustomProperty,
|
||||
valueSpanForMap: _expressionNode(node.value)?.span));
|
||||
} else if (name.value.startsWith('--')) {
|
||||
} else if (name.value.startsWith('--') && node.children == null) {
|
||||
throw _exception(
|
||||
"Custom property values may not be empty.", node.value.span);
|
||||
}
|
||||
@ -2559,6 +2560,7 @@ class _EvaluateVisitor
|
||||
|
||||
Future<void> visitCssDeclaration(CssDeclaration node) async {
|
||||
_parent.addChild(ModifiableCssDeclaration(node.name, node.value, node.span,
|
||||
parsedAsCustomProperty: node.isCustomProperty,
|
||||
valueSpanForMap: node.valueSpanForMap));
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,7 @@ class _CloneCssVisitor implements CssVisitor<ModifiableCssNode> {
|
||||
|
||||
ModifiableCssDeclaration visitCssDeclaration(CssDeclaration node) =>
|
||||
ModifiableCssDeclaration(node.name, node.value, node.span,
|
||||
parsedAsCustomProperty: node.parsedAsCustomProperty,
|
||||
valueSpanForMap: node.valueSpanForMap);
|
||||
|
||||
ModifiableCssImport visitCssImport(CssImport node) =>
|
||||
|
@ -5,7 +5,7 @@
|
||||
// DO NOT EDIT. This file was generated from async_evaluate.dart.
|
||||
// See tool/grind/synchronize.dart for details.
|
||||
//
|
||||
// Checksum: f6fe6645ccec58216ef623851bd2594de291a360
|
||||
// Checksum: 651a7e9f78b68bfd440241304301cf78711553a4
|
||||
//
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
@ -1041,8 +1041,9 @@ class _EvaluateVisitor
|
||||
if (cssValue != null &&
|
||||
(!cssValue.value.isBlank || _isEmptyList(cssValue.value))) {
|
||||
_parent.addChild(ModifiableCssDeclaration(name, cssValue, node.span,
|
||||
parsedAsCustomProperty: node.isCustomProperty,
|
||||
valueSpanForMap: _expressionNode(node.value)?.span));
|
||||
} else if (name.value.startsWith('--')) {
|
||||
} else if (name.value.startsWith('--') && node.children == null) {
|
||||
throw _exception(
|
||||
"Custom property values may not be empty.", node.value.span);
|
||||
}
|
||||
@ -2541,6 +2542,7 @@ class _EvaluateVisitor
|
||||
|
||||
void visitCssDeclaration(CssDeclaration node) {
|
||||
_parent.addChild(ModifiableCssDeclaration(node.name, node.value, node.span,
|
||||
parsedAsCustomProperty: node.isCustomProperty,
|
||||
valueSpanForMap: node.valueSpanForMap));
|
||||
}
|
||||
|
||||
|
@ -333,7 +333,10 @@ class _SerializeVisitor
|
||||
_write(node.name);
|
||||
_buffer.writeCharCode($colon);
|
||||
|
||||
if (_isParsedCustomProperty(node)) {
|
||||
// If `node` is a custom property that was parsed as a normal Sass-syntax
|
||||
// property (such as `#{--foo}: ...`), we serialize its value using the
|
||||
// normal Sass property logic as well.
|
||||
if (node.isCustomProperty && node.parsedAsCustomProperty) {
|
||||
_for(node.value, () {
|
||||
if (_isCompressed) {
|
||||
_writeFoldedValue(node);
|
||||
@ -355,20 +358,6 @@ class _SerializeVisitor
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether [node] is a custom property that was parsed as a custom
|
||||
/// property (rather than being dynamically generated, as in `#{--foo}: ...`).
|
||||
///
|
||||
/// We only re-indent custom property values that were parsed as custom
|
||||
/// properties, which we detect as unquoted strings. It's possible to have
|
||||
/// false positives here, since someone could write `#{--foo}: unquoted`, but
|
||||
/// that's unlikely enough that we can spare the extra time a no-op
|
||||
/// reindenting will take.
|
||||
bool _isParsedCustomProperty(CssDeclaration node) {
|
||||
if (!node.name.value.startsWith("--")) return false;
|
||||
var value = node.value.value;
|
||||
return value is SassString && !value.hasQuotes;
|
||||
}
|
||||
|
||||
/// Emits the value of [node], with all newlines followed by whitespace
|
||||
void _writeFoldedValue(CssDeclaration node) {
|
||||
var scanner = StringScanner((node.value.value as SassString).text);
|
||||
|
@ -1,5 +1,5 @@
|
||||
name: sass
|
||||
version: 1.27.0-dev
|
||||
version: 1.28.0-dev
|
||||
description: A Sass implementation in Dart.
|
||||
author: Sass Team
|
||||
homepage: https://github.com/sass/dart-sass
|
||||
|
@ -31,6 +31,7 @@ void main() {
|
||||
expect(value.assertColor, throwsSassScriptException);
|
||||
expect(value.assertFunction, throwsSassScriptException);
|
||||
expect(value.assertMap, throwsSassScriptException);
|
||||
expect(value.tryMap(), isNull);
|
||||
expect(value.assertNumber, throwsSassScriptException);
|
||||
expect(value.assertString, throwsSassScriptException);
|
||||
});
|
||||
@ -56,6 +57,7 @@ void main() {
|
||||
expect(value.assertColor, throwsSassScriptException);
|
||||
expect(value.assertFunction, throwsSassScriptException);
|
||||
expect(value.assertMap, throwsSassScriptException);
|
||||
expect(value.tryMap(), isNull);
|
||||
expect(value.assertNumber, throwsSassScriptException);
|
||||
expect(value.assertString, throwsSassScriptException);
|
||||
});
|
||||
|
@ -185,6 +185,7 @@ void main() {
|
||||
expect(value.assertBoolean, throwsSassScriptException);
|
||||
expect(value.assertFunction, throwsSassScriptException);
|
||||
expect(value.assertMap, throwsSassScriptException);
|
||||
expect(value.tryMap(), isNull);
|
||||
expect(value.assertNumber, throwsSassScriptException);
|
||||
expect(value.assertString, throwsSassScriptException);
|
||||
});
|
||||
|
@ -31,6 +31,7 @@ void main() {
|
||||
expect(value.assertBoolean, throwsSassScriptException);
|
||||
expect(value.assertColor, throwsSassScriptException);
|
||||
expect(value.assertMap, throwsSassScriptException);
|
||||
expect(value.tryMap(), isNull);
|
||||
expect(value.assertNumber, throwsSassScriptException);
|
||||
expect(value.assertString, throwsSassScriptException);
|
||||
});
|
||||
|
@ -110,6 +110,7 @@ void main() {
|
||||
expect(value.assertColor, throwsSassScriptException);
|
||||
expect(value.assertFunction, throwsSassScriptException);
|
||||
expect(value.assertMap, throwsSassScriptException);
|
||||
expect(value.tryMap(), isNull);
|
||||
expect(value.assertNumber, throwsSassScriptException);
|
||||
expect(value.assertString, throwsSassScriptException);
|
||||
});
|
||||
@ -140,6 +141,7 @@ void main() {
|
||||
expect(value.assertColor, throwsSassScriptException);
|
||||
expect(value.assertFunction, throwsSassScriptException);
|
||||
expect(value.assertMap, throwsSassScriptException);
|
||||
expect(value.tryMap(), isNull);
|
||||
expect(value.assertNumber, throwsSassScriptException);
|
||||
expect(value.assertString, throwsSassScriptException);
|
||||
});
|
||||
@ -167,6 +169,7 @@ void main() {
|
||||
|
||||
test("counts as an empty map", () {
|
||||
expect(value.assertMap().contents, isEmpty);
|
||||
expect(value.tryMap().contents, isEmpty);
|
||||
});
|
||||
|
||||
test("isn't any other type", () {
|
||||
|
@ -128,6 +128,7 @@ void main() {
|
||||
|
||||
test("is a map", () {
|
||||
expect(value.assertMap(), equals(value));
|
||||
expect(value.tryMap(), equals(value));
|
||||
});
|
||||
|
||||
test("isn't any other type", () {
|
||||
|
@ -27,6 +27,7 @@ void main() {
|
||||
expect(value.assertColor, throwsSassScriptException);
|
||||
expect(value.assertFunction, throwsSassScriptException);
|
||||
expect(value.assertMap, throwsSassScriptException);
|
||||
expect(value.tryMap(), isNull);
|
||||
expect(value.assertNumber, throwsSassScriptException);
|
||||
expect(value.assertString, throwsSassScriptException);
|
||||
});
|
||||
|
@ -102,6 +102,7 @@ void main() {
|
||||
expect(value.assertColor, throwsSassScriptException);
|
||||
expect(value.assertFunction, throwsSassScriptException);
|
||||
expect(value.assertMap, throwsSassScriptException);
|
||||
expect(value.tryMap(), isNull);
|
||||
expect(value.assertString, throwsSassScriptException);
|
||||
});
|
||||
});
|
||||
|
@ -37,6 +37,7 @@ void main() {
|
||||
expect(value.assertColor, throwsSassScriptException);
|
||||
expect(value.assertFunction, throwsSassScriptException);
|
||||
expect(value.assertMap, throwsSassScriptException);
|
||||
expect(value.tryMap(), isNull);
|
||||
expect(value.assertNumber, throwsSassScriptException);
|
||||
});
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user