Merge branch 'master' into master-into-use

This commit is contained in:
Natalie Weizenbaum 2019-05-20 16:52:20 -07:00
commit c76ef209af
50 changed files with 305 additions and 521 deletions

View File

@ -4,6 +4,12 @@
* Improve performance for stand-alone packages on Linux and Mac OS.
### JavaScript API
* Pass imports to custom importers before resolving them using `includePaths` or
the `SASS_PATH` environment variable. This matches Node Sass's behavior, so
it's considered a bug fix.
## 1.20.1
* No user-visible changes.

View File

@ -191,7 +191,7 @@ ways they can't share code.
To avoid colossal amounts of duplicated code, we have a few files that are
written in an asynchronous style originally and then compiled to their
synchronous equivalents using `tool/synchronize.dart`. In particular:
synchronous equivalents using `pub run grinder synchronize`. In particular:
* `lib/src/visitor/async_evaluate.dart` is compiled to
`lib/src/visitor/evaluate.dart`.

View File

@ -144,16 +144,19 @@ That's it!
## JavaScript API
When installed via npm, Dart Sass supports a JavaScript API that aims to be
compatible with [Node Sass](https://github.com/sass/node-sass#usage). Full
compatibility is a work in progress, but Dart Sass currently supports the
`render()` and `renderSync()` functions. Note however that by default,
**`renderSync()` is more than twice as fast as `render()`**, due to the overhead
of asynchronous callbacks.
When installed via npm, Dart Sass supports a JavaScript API that's fully
compatible with [Node Sass][] (with a few exceptions listed below), with support
for both the `render()` and `renderSync()` functions. See [the Sass
website][js api] for full API documentation!
To avoid this performance hit, `render()` can use the [`fibers`][fibers] package
to call asynchronous importers from the synchronous code path. To enable this,
pass the `Fiber` class to the `fiber` option:
[Node Sass]: https://github.com/sass/node-sass
[js api]: https://sass-lang.com/documentation/js-api
Note however that by default, **`renderSync()` is more than twice as fast as
`render()`** due to the overhead of asynchronous callbacks. To avoid this
performance hit, `render()` can use the [`fibers`][fibers] package to call
asynchronous importers from the synchronous code path. To enable this, pass the
`Fiber` class to the `fiber` option:
[fibers]: https://www.npmjs.com/package/fibers

View File

@ -54,8 +54,8 @@ class CssMediaQuery {
var theirType = other.type?.toLowerCase();
if (ourType == null && theirType == null) {
return MediaQuerySuccessfulMergeResult._(CssMediaQuery.condition(
this.features.toList()..addAll(other.features)));
return MediaQuerySuccessfulMergeResult._(
CssMediaQuery.condition([...this.features, ...other.features]));
}
String modifier;
@ -120,17 +120,17 @@ class CssMediaQuery {
// Omit the type if either input query did, since that indicates that they
// aren't targeting a browser that requires "all and".
type = (other.matchesAllTypes && ourType == null) ? null : theirType;
features = this.features.toList()..addAll(other.features);
features = [...this.features, ...other.features];
} else if (other.matchesAllTypes) {
modifier = ourModifier;
type = ourType;
features = this.features.toList()..addAll(other.features);
features = [...this.features, ...other.features];
} else if (ourType != theirType) {
return MediaQueryMergeResult.empty;
} else {
modifier = ourModifier ?? theirModifier;
type = ourType;
features = this.features.toList()..addAll(other.features);
features = [...this.features, ...other.features];
}
return MediaQuerySuccessfulMergeResult._(CssMediaQuery(

View File

@ -100,9 +100,8 @@ class ArgumentDeclaration implements SassNode {
return true;
}
String toString() {
var components = List.of(arguments.map((arg) => arg.toString()));
if (restArgument != null) components.add('$restArgument...');
return components.join(', ');
}
String toString() => [
for (var arg in arguments) arg.toString(),
if (restArgument != null) '$restArgument...'
].join(', ');
}

View File

@ -42,10 +42,12 @@ class ArgumentInvocation implements SassNode {
keywordRest = null;
String toString() {
var components = List<Object>.from(positional)
..addAll(named.keys.map((name) => "$name: ${named[name]}"));
if (rest != null) components.add("$rest...");
if (keywordRest != null) components.add("$keywordRest...");
var components = [
...positional,
for (var name in named.keys) "$name: ${named[name]}",
if (rest != null) "$rest...",
if (keywordRest != null) "$keywordRest..."
];
return "(${components.join(', ')})";
}
}

View File

@ -103,7 +103,7 @@ class SelectorList extends Selector {
if (!_complexContainsParentSelector(complex)) {
if (!implicitParent) return [complex];
return parent.components.map((parentComplex) => ComplexSelector(
parentComplex.components.toList()..addAll(complex.components),
[...parentComplex.components, ...complex.components],
lineBreak: complex.lineBreak || parentComplex.lineBreak));
}
@ -127,8 +127,7 @@ class SelectorList extends Selector {
for (var newComplex in previousComplexes) {
var lineBreak = previousLineBreaks[i++];
for (var resolvedComplex in resolved) {
newComplexes
.add(newComplex.toList()..addAll(resolvedComplex.components));
newComplexes.add([...newComplex, ...resolvedComplex.components]);
lineBreaks.add(lineBreak || resolvedComplex.lineBreak);
}
}
@ -204,18 +203,18 @@ class SelectorList extends Selector {
var last = lastComponent as CompoundSelector;
var suffix = (compound.components.first as ParentSelector).suffix;
if (suffix != null) {
last = CompoundSelector(
last.components.take(last.components.length - 1).toList()
..add(last.components.last.addSuffix(suffix))
..addAll(resolvedMembers.skip(1)));
last = CompoundSelector([
...last.components.take(last.components.length - 1),
last.components.last.addSuffix(suffix),
...resolvedMembers.skip(1)
]);
} else {
last = CompoundSelector(
last.components.toList()..addAll(resolvedMembers.skip(1)));
last =
CompoundSelector([...last.components, ...resolvedMembers.skip(1)]);
}
return ComplexSelector(
complex.components.take(complex.components.length - 1).toList()
..add(last),
[...complex.components.take(complex.components.length - 1), last],
lineBreak: complex.lineBreak);
});
}

View File

@ -25,9 +25,9 @@ class TypeSelector extends SimpleSelector {
if (compound.first is UniversalSelector || compound.first is TypeSelector) {
var unified = unifyUniversalAndElement(this, compound.first);
if (unified == null) return null;
return [unified]..addAll(compound.skip(1));
return [unified, ...compound.skip(1)];
} else {
return <SimpleSelector>[this]..addAll(compound);
return [this, ...compound];
}
}

View File

@ -27,12 +27,10 @@ class UniversalSelector extends SimpleSelector {
if (compound.first is UniversalSelector || compound.first is TypeSelector) {
var unified = unifyUniversalAndElement(this, compound.first);
if (unified == null) return null;
return [unified]..addAll(compound.skip(1));
return [unified, ...compound.skip(1)];
}
if (namespace != null && namespace != "*") {
return <SimpleSelector>[this]..addAll(compound);
}
if (namespace != null && namespace != "*") return [this, ...compound];
if (compound.isNotEmpty) return compound;
return [this];
}

View File

@ -77,24 +77,16 @@ class AsyncImportCache {
/// options into a single list of importers.
static List<AsyncImporter> _toImporters(Iterable<AsyncImporter> importers,
Iterable<String> loadPaths, SyncPackageResolver packageResolver) {
var list = importers?.toList() ?? [];
if (loadPaths != null) {
list.addAll(loadPaths.map((path) => FilesystemImporter(path)));
}
var sassPath = getEnvironmentVariable('SASS_PATH');
if (sassPath != null) {
list.addAll(sassPath
.split(isWindows ? ';' : ':')
.map((path) => FilesystemImporter(path)));
}
if (packageResolver != null) {
list.add(PackageImporter(packageResolver));
}
return list;
return [
...?importers,
if (loadPaths != null)
for (var path in loadPaths) FilesystemImporter(path),
if (sassPath != null)
for (var path in sassPath.split(isWindows ? ';' : ':'))
FilesystemImporter(path),
if (packageResolver != null) PackageImporter(packageResolver)
];
}
/// Creates a cache that contains no importers.

View File

@ -11,7 +11,7 @@ import '../value.dart';
import 'async.dart';
/// An [AsyncBuiltInCallable]'s callback.
typedef FutureOr<Value> _Callback(List<Value> arguments);
typedef _Callback = FutureOr<Value> Function(List<Value> arguments);
/// A callable defined in Dart code.
///

View File

@ -9,7 +9,7 @@ import '../callable.dart';
import '../value.dart';
import 'async_built_in.dart';
typedef Value _Callback(List<Value> arguments);
typedef _Callback = Value Function(List<Value> arguments);
/// A callable defined in Dart code.
///

View File

@ -3,7 +3,7 @@
// https://opensource.org/licenses/MIT.
// DO NOT EDIT. This file was generated from async_compile.dart.
// See tool/synchronize.dart for details.
// See tool/grind/synchronize.dart for details.
//
// Checksum: ea78ec4431055c1d222e52f4ea54a9659c4df11f
//

View File

@ -3,7 +3,7 @@
// https://opensource.org/licenses/MIT.
// DO NOT EDIT. This file was generated from async_environment.dart.
// See tool/synchronize.dart for details.
// See tool/grind/synchronize.dart for details.
//
// Checksum: a7e4f09ccb8a4997500abfe34e712b9c2ad212c6
//

View File

@ -371,18 +371,19 @@ class ExecutableOptions {
/// Returns the sub-map of [sourcesToDestinations] for the given [source] and
/// [destination] directories.
Map<String, String> _listSourceDirectory(String source, String destination) {
var map = <String, String>{};
for (var path in listDir(source, recursive: true)) {
var basename = p.basename(path);
if (basename.startsWith("_")) continue;
return {
for (var path in listDir(source, recursive: true))
if (_isEntrypoint(path))
path: p.join(destination,
p.setExtension(p.relative(path, from: source), '.css'))
};
}
var extension = p.extension(path);
if (extension != ".scss" && extension != ".sass") continue;
map[path] = p.join(
destination, p.setExtension(p.relative(path, from: source), '.css'));
}
return map;
/// Returns whether [path] is a Sass entrypoint (that is, not a partial).
bool _isEntrypoint(String path) {
if (p.basename(path).startsWith("_")) return false;
var extension = p.extension(path);
return extension == ".scss" || extension == ".sass";
}
/// Whether to emit a source map file.

View File

@ -20,10 +20,11 @@ import 'options.dart';
/// Watches all the files in [graph] for changes and updates them as necessary.
Future watch(ExecutableOptions options, StylesheetGraph graph) async {
var directoriesToWatch = <String>[]
..addAll(options.sourceDirectoriesToDestinations.keys)
..addAll(options.sourcesToDestinations.keys.map(p.dirname))
..addAll(options.loadPaths);
var directoriesToWatch = [
...options.sourceDirectoriesToDestinations.keys,
...options.sourcesToDestinations.keys.map(p.dirname),
...options.loadPaths
];
var dirWatcher = MultiDirWatcher(poll: options.poll);
await Future.wait(directoriesToWatch.map((dir) {

View File

@ -96,11 +96,10 @@ class Extender {
throw SassScriptException("Can't extend complex selector $complex.");
}
var extensions = <SimpleSelector, Map<ComplexSelector, Extension>>{};
var compound = complex.components.first as CompoundSelector;
for (var simple in compound.components) {
extensions[simple] = extenders;
}
var extensions = {
for (var simple in compound.components) simple: extenders
};
var extender = Extender._mode(mode);
if (!selector.isInvisible) {
@ -702,10 +701,7 @@ class Extender {
targetsUsed?.add(simple);
if (_mode == ExtendMode.replace) return extenders.values.toList();
var extenderList = List<Extension>(extenders.length + 1);
extenderList[0] = _extensionForSimple(simple);
extenderList.setRange(1, extenderList.length, extenders.values);
return extenderList;
return [_extensionForSimple(simple), ...extenders.values];
}
if (simple is PseudoSelector && simple.selector != null) {

View File

@ -21,8 +21,7 @@ import '../utils.dart';
/// subselectors of their arguments.
///
/// For example, `.foo` is a superselector of `:matches(.foo)`.
final _subselectorPseudos =
Set.of(['matches', 'any', 'nth-child', 'nth-last-child']);
final _subselectorPseudos = {'matches', 'any', 'nth-child', 'nth-last-child'};
/// Returns the contents of a [SelectorList] that matches only elements that are
/// matched by both [complex1] and [complex2].
@ -381,18 +380,16 @@ List<List<List<ComplexSelectorComponent>>> _mergeFinalCombinators(
[nextSiblingSelector, Combinator.nextSibling]
]);
} else {
var choices = [
var unified = unifyCompound(compound1.components, compound2.components);
result.addFirst([
[
followingSiblingSelector,
Combinator.followingSibling,
nextSiblingSelector,
Combinator.nextSibling
]
];
var unified = unifyCompound(compound1.components, compound2.components);
if (unified != null) choices.add([unified, Combinator.nextSibling]);
result.addFirst(choices);
],
if (unified != null) [unified, Combinator.nextSibling]
]);
}
} else if (combinator1 == Combinator.child &&
(combinator2 == Combinator.nextSibling ||
@ -451,12 +448,11 @@ List<List<List<ComplexSelectorComponent>>> _mergeFinalCombinators(
/// selector, such as an ID.
bool _mustUnify(List<ComplexSelectorComponent> complex1,
List<ComplexSelectorComponent> complex2) {
var uniqueSelectors = Set<SimpleSelector>();
for (var component in complex1) {
if (component is CompoundSelector) {
uniqueSelectors.addAll(component.components.where(_isUnique));
}
}
var uniqueSelectors = {
for (var component in complex1)
if (component is CompoundSelector)
...component.components.where(_isUnique)
};
if (uniqueSelectors.isEmpty) return false;
return complex2.any((component) =>
@ -496,7 +492,10 @@ List<List<T>> _chunks<T>(
if (chunk1.isEmpty && chunk2.isEmpty) return [];
if (chunk1.isEmpty) return [chunk2];
if (chunk2.isEmpty) return [chunk1];
return [chunk1.toList()..addAll(chunk2), chunk2..addAll(chunk1)];
return [
[...chunk1, ...chunk2],
[...chunk2, ...chunk1]
];
}
/// Returns a list of all possible paths through the given lists.
@ -512,7 +511,7 @@ List<List<T>> _chunks<T>(
List<List<T>> paths<T>(Iterable<List<T>> choices) => choices.fold(
[[]],
(paths, choice) => choice
.expand((option) => paths.map((path) => path.toList()..add(option)))
.expand((option) => paths.map((path) => [...path, option]))
.toList());
/// Returns [complex], grouped into sub-lists such that no sub-list contains two
@ -566,8 +565,7 @@ bool complexIsParentSuperselector(List<ComplexSelectorComponent> complex1,
// TODO(nweiz): There's got to be a way to do this without a bunch of extra
// allocations...
var base = CompoundSelector([PlaceholderSelector('<temp>')]);
return complexIsSuperselector(
complex1.toList()..add(base), complex2.toList()..add(base));
return complexIsSuperselector([...complex1, base], [...complex2, base]);
}
/// Returns whether [complex1] is a superselector of [complex2].
@ -732,11 +730,8 @@ bool _selectorPseudoIsSuperselector(
return pseudos.any((pseudo2) {
return pseudo1.selector.isSuperselector(pseudo2.selector);
}) ||
pseudo1.selector.components.any((complex1) {
var complex2 = (parents?.toList() ?? <ComplexSelectorComponent>[])
..add(compound2);
return complexIsSuperselector(complex1.components, complex2);
});
pseudo1.selector.components.any((complex1) => complexIsSuperselector(
complex1.components, [...?parents, compound2]));
case 'has':
case 'host':

View File

@ -21,13 +21,13 @@ import 'value.dart';
final _microsoftFilterStart = RegExp(r'^[a-zA-Z]+\s*=');
/// Feature names supported by Dart sass.
final _features = Set.of([
final _features = {
"global-variable-shadowing",
"extend-selector-pseudoclass",
"units-level-3",
"at-error",
"custom-property"
]);
};
/// A random number generator.
final _random = math.Random();
@ -627,7 +627,7 @@ final List<BuiltInCallable> coreFunctions = UnmodifiableListView([
? list1.hasBrackets
: bracketedParam.isTruthy;
var newList = list1.asList.toList()..addAll(list2.asList);
var newList = [...list1.asList, ...list2.asList];
return SassList(newList, separator, brackets: bracketed);
}),
@ -650,7 +650,7 @@ final List<BuiltInCallable> coreFunctions = UnmodifiableListView([
'\$$separator: Must be "space", "comma", or "auto".');
}
var newList = list.asList.toList()..add(value);
var newList = [...list.asList, value];
return list.changeListContents(newList, separator: separator);
}),
@ -694,7 +694,7 @@ final List<BuiltInCallable> coreFunctions = UnmodifiableListView([
BuiltInCallable("map-merge", r"$map1, $map2", (arguments) {
var map1 = arguments[0].assertMap("map1");
var map2 = arguments[1].assertMap("map2");
return SassMap(Map.of(map1.contents)..addAll(map2.contents));
return SassMap({...map1.contents, ...map2.contents});
}),
BuiltInCallable("map-remove", r"$map, $keys...", (arguments) {
@ -769,8 +769,7 @@ final List<BuiltInCallable> coreFunctions = UnmodifiableListView([
throw SassScriptException("Can't append $complex to $parent.");
}
return ComplexSelector(<ComplexSelectorComponent>[newCompound]
..addAll(complex.components.skip(1)));
return ComplexSelector([newCompound, ...complex.components.skip(1)]);
} else {
throw SassScriptException("Can't append $complex to $parent.");
}
@ -1139,12 +1138,12 @@ CompoundSelector _prependParent(CompoundSelector compound) {
if (first is UniversalSelector) return null;
if (first is TypeSelector) {
if (first.name.namespace != null) return null;
return CompoundSelector(<SimpleSelector>[
ParentSelector(suffix: first.name.name)
]..addAll(compound.components.skip(1)));
return CompoundSelector([
ParentSelector(suffix: first.name.name),
...compound.components.skip(1)
]);
} else {
return CompoundSelector(
<SimpleSelector>[ParentSelector()]..addAll(compound.components));
return CompoundSelector([ParentSelector(), ...compound.components]);
}
}

View File

@ -3,9 +3,9 @@
// https://opensource.org/licenses/MIT.
// DO NOT EDIT. This file was generated from async_import_cache.dart.
// See tool/synchronize.dart for details.
// See tool/grind/synchronize.dart for details.
//
// Checksum: 291afac7e0d5edc6610685b28741786f667f83e8
// Checksum: 1e51fceaa60c0a65fa9c447cfe25eda5109a819f
//
// ignore_for_file: unused_import
@ -82,24 +82,16 @@ class ImportCache {
/// options into a single list of importers.
static List<Importer> _toImporters(Iterable<Importer> importers,
Iterable<String> loadPaths, SyncPackageResolver packageResolver) {
var list = importers?.toList() ?? [];
if (loadPaths != null) {
list.addAll(loadPaths.map((path) => FilesystemImporter(path)));
}
var sassPath = getEnvironmentVariable('SASS_PATH');
if (sassPath != null) {
list.addAll(sassPath
.split(isWindows ? ';' : ':')
.map((path) => FilesystemImporter(path)));
}
if (packageResolver != null) {
list.add(PackageImporter(packageResolver));
}
return list;
return [
...?importers,
if (loadPaths != null)
for (var path in loadPaths) FilesystemImporter(path),
if (sassPath != null)
for (var path in sassPath.split(isWindows ? ';' : ':'))
FilesystemImporter(path),
if (packageResolver != null) PackageImporter(packageResolver)
];
}
/// Creates a cache that contains no importers.

View File

@ -34,10 +34,10 @@ import '../utils.dart';
/// * The order of import precedence is as follows:
///
/// 1. Filesystem imports relative to the base file.
/// 2. Filesystem imports relative to the working directory.
/// 3. Filesystem imports relative to an `includePaths` path.
/// 3. Filesystem imports relative to a `SASS_PATH` path.
/// 4. Custom importer imports.
/// 2. Custom importer imports.
/// 3. Filesystem imports relative to the working directory.
/// 4. Filesystem imports relative to an `includePaths` path.
/// 5. Filesystem imports relative to a `SASS_PATH` path.
class NodeImporter {
/// The `this` context in which importer functions are invoked.
final Object _context;
@ -69,7 +69,7 @@ class NodeImporter {
Tuple2<String, String> load(String url, Uri previous) {
var parsed = Uri.parse(url);
if (parsed.scheme == '' || parsed.scheme == 'file') {
var result = _resolvePath(p.fromUri(parsed), previous);
var result = _resolveRelativePath(p.fromUri(parsed), previous);
if (result != null) return result;
}
@ -81,7 +81,7 @@ class NodeImporter {
if (value != null) return _handleImportResult(url, previous, value);
}
return null;
return _resolveLoadPathFromUrl(parsed, previous);
}
/// Asynchronously loads the stylesheet at [url].
@ -92,7 +92,7 @@ class NodeImporter {
Future<Tuple2<String, String>> loadAsync(String url, Uri previous) async {
var parsed = Uri.parse(url);
if (parsed.scheme == '' || parsed.scheme == 'file') {
var result = _resolvePath(p.fromUri(parsed), previous);
var result = _resolveRelativePath(p.fromUri(parsed), previous);
if (result != null) return result;
}
@ -104,15 +104,14 @@ class NodeImporter {
if (value != null) return _handleImportResult(url, previous, value);
}
return null;
return _resolveLoadPathFromUrl(parsed, previous);
}
/// Tries to load a stylesheet at the given [path] using Node Sass's file path
/// resolution logic.
/// Tries to load a stylesheet at the given [path] relative to [previous].
///
/// Returns the stylesheet at that path and the URL used to load it, or `null`
/// if loading failed.
Tuple2<String, String> _resolvePath(String path, Uri previous) {
Tuple2<String, String> _resolveRelativePath(String path, Uri previous) {
if (p.isAbsolute(path)) return _tryPath(path);
// 1: Filesystem imports relative to the base file.
@ -120,7 +119,25 @@ class NodeImporter {
var result = _tryPath(p.join(p.dirname(p.fromUri(previous)), path));
if (result != null) return result;
}
return null;
}
/// Tries to load a stylesheet at the given [url] from a load path (including
/// the working directory), if that URL refers to the filesystem.
///
/// Returns the stylesheet at that path and the URL used to load it, or `null`
/// if loading failed.
Tuple2<String, String> _resolveLoadPathFromUrl(Uri url, Uri previous) =>
url.scheme == '' || url.scheme == 'file'
? _resolveLoadPath(p.fromUri(url), previous)
: null;
/// Tries to load a stylesheet at the given [path] from a load path (including
/// the working directory).
///
/// Returns the stylesheet at that path and the URL used to load it, or `null`
/// if loading failed.
Tuple2<String, String> _resolveLoadPath(String path, Uri previous) {
// 2: Filesystem imports relative to the working directory.
var cwdResult = _tryPath(p.absolute(path));
if (cwdResult != null) return cwdResult;
@ -154,7 +171,8 @@ class NodeImporter {
var result = value as NodeImporterResult;
if (result.file != null) {
var resolved = _resolvePath(result.file, previous);
var resolved = _resolveRelativePath(result.file, previous) ??
_resolveLoadPath(result.file, previous);
if (resolved != null) return resolved;
throw "Can't find stylesheet to import.";

View File

@ -68,11 +68,8 @@ List<String> _tryPathWithExtensions(String path) {
///
/// If neither exists, returns an empty list.
List<String> _tryPath(String path) {
var paths = <String>[];
var partial = p.join(p.dirname(path), "_${p.basename(path)}");
if (fileExists(partial)) paths.add(partial);
if (fileExists(path)) paths.add(path);
return paths;
return [if (fileExists(partial)) partial, if (fileExists(path)) path];
}
/// Returns the resolved index file for [path] if [path] is a directory and the

View File

@ -70,9 +70,8 @@ class InterpolationBuffer implements StringSink {
/// Creates an [Interpolation] with the given [span] from the contents of this
/// buffer.
Interpolation interpolation(FileSpan span) {
var contents = _contents.toList();
if (_text.isNotEmpty) contents.add(_text.toString());
return Interpolation(contents, span);
return Interpolation(
[..._contents, if (_text.isNotEmpty) _text.toString()], span);
}
String toString() {

View File

@ -204,12 +204,14 @@ List<AsyncCallable> _parseFunctions(RenderOptions options,
if (options.fiber != null) {
result.add(BuiltInCallable.parsed(tuple.item1, tuple.item2, (arguments) {
var fiber = options.fiber.current;
var jsArguments = arguments.map(wrapValue).toList()
..add(allowInterop(([result]) {
var jsArguments = [
...arguments.map(wrapValue),
allowInterop(([result]) {
// Schedule a microtask so we don't try to resume the running fiber
// if [importer] calls `done()` synchronously.
scheduleMicrotask(() => fiber.run(result));
}));
})
];
var result = Function.apply(callback as Function, jsArguments);
return unwrapValue(
isUndefined(result) ? options.fiber.yield() : result);
@ -224,8 +226,10 @@ List<AsyncCallable> _parseFunctions(RenderOptions options,
result.add(AsyncBuiltInCallable.parsed(tuple.item1, tuple.item2,
(arguments) async {
var completer = Completer();
var jsArguments = arguments.map(wrapValue).toList()
..add(allowInterop(([result]) => completer.complete(result)));
var jsArguments = [
...arguments.map(wrapValue),
allowInterop(([result]) => completer.complete(result))
];
var result = Function.apply(callback as Function, jsArguments);
return unwrapValue(
isUndefined(result) ? await completer.future : result);
@ -256,7 +260,7 @@ NodeImporter _parseImporter(RenderOptions options, DateTime start) {
file: options.file,
data: options.data,
includePaths:
([p.current]..addAll(includePaths)).join(isWindows ? ';' : ':'),
([p.current, ...includePaths]).join(isWindows ? ';' : ':'),
precision: SassNumber.precision,
style: 1,
indentType: options.indentType == 'tab' ? 1 : 0,

View File

@ -55,10 +55,8 @@ final Function mapConstructor =
},
'setValue': (_NodeSassMap thisArg, int index, value) {
var key = thisArg.dartValue.contents.keys.elementAt(index);
var mutable = Map.of(thisArg.dartValue.contents);
mutable[key] = unwrapValue(value);
thisArg.dartValue = SassMap(mutable);
thisArg.dartValue =
SassMap({...thisArg.dartValue.contents, key: unwrapValue(value)});
},
'toString': (_NodeSassMap thisArg) => thisArg.dartValue.toString()
});

View File

@ -11,11 +11,18 @@ import '../utils.dart';
import 'parser.dart';
/// Pseudo-class selectors that take unadorned selectors as arguments.
final _selectorPseudoClasses =
Set.of(["not", "matches", "current", "any", "has", "host", "host-context"]);
final _selectorPseudoClasses = {
"not",
"matches",
"current",
"any",
"has",
"host",
"host-context"
};
/// Pseudo-element selectors that take unadorned selectors as arguments.
final _selectorPseudoElements = Set.of(["slotted"]);
final _selectorPseudoElements = {"slotted"};
/// A parser for selectors.
class SelectorParser extends Parser {

View File

@ -94,13 +94,13 @@ class StylesheetGraph {
/// appears within [baseUrl] imported by [baseImporter].
Map<Uri, StylesheetNode> _upstreamNodes(
Stylesheet stylesheet, Importer baseImporter, Uri baseUrl) {
var active = Set.of([baseUrl]);
var upstream = <Uri, StylesheetNode>{};
for (var import in findImports(stylesheet)) {
var url = Uri.parse(import.url);
upstream[url] = _nodeFor(url, baseImporter, baseUrl, active);
}
return upstream;
var active = {baseUrl};
var upstreamUrls =
findImports(stylesheet).map((import) => Uri.parse(import.url));
return {
for (var url in upstreamUrls)
url: _nodeFor(url, baseImporter, baseUrl, active)
};
}
/// Re-parses the stylesheet at [canonicalUrl] and updates the dependency graph

View File

@ -406,13 +406,8 @@ void rotateSlice(List list, int start, int end) {
/// Like [Iterable.map] but for an asynchronous [callback].
Future<Iterable<F>> mapAsync<E, F>(
Iterable<E> iterable, Future<F> callback(E value)) async {
var result = <F>[];
for (var element in iterable) {
result.add(await callback(element));
}
return result;
}
Iterable<E> iterable, Future<F> callback(E value)) async =>
[for (var element in iterable) await callback(element)];
/// Like [Map.putIfAbsent], but for an asynchronous [ifAbsent].
///

View File

@ -42,7 +42,7 @@ import 'interface/modifiable_css.dart';
import 'interface/statement.dart';
/// A function that takes a callback with no arguments.
typedef Future _ScopeCallback(Future callback());
typedef _ScopeCallback = Future Function(Future Function() callback);
/// Converts [stylesheet] to a plain CSS tree.
///
@ -1072,8 +1072,7 @@ class _EvaluateVisitor
throw "Can't find stylesheet to import.";
}
} on SassException catch (error) {
var frames = error.trace.frames.toList()
..addAll(_stackTrace(span).frames);
var frames = [...error.trace.frames, ..._stackTrace(span).frames];
throw SassRuntimeException(error.message, error.span, Trace(frames));
} catch (error) {
String message;
@ -2416,10 +2415,10 @@ class _EvaluateVisitor
///
/// [span] is the current location, used for the bottom-most stack frame.
Trace _stackTrace(FileSpan span) {
var frames = _stack
.map((tuple) => _stackFrame(tuple.item1, tuple.item2.span))
.toList()
..add(_stackFrame(_member, span));
var frames = [
..._stack.map((tuple) => _stackFrame(tuple.item1, tuple.item2.span)),
_stackFrame(_member, span)
];
return Trace(frames.reversed);
}

View File

@ -3,9 +3,9 @@
// https://opensource.org/licenses/MIT.
// DO NOT EDIT. This file was generated from async_evaluate.dart.
// See tool/synchronize.dart for details.
// See tool/grind/synchronize.dart for details.
//
// Checksum: d52c30611ad55bb7764f570381b6d739e39e6f98
// Checksum: 4aeea9b6944414d06fecf6864ca53ff45c616814
//
// ignore_for_file: unused_import
@ -51,7 +51,7 @@ import 'interface/modifiable_css.dart';
import 'interface/statement.dart';
/// A function that takes a callback with no arguments.
typedef void _ScopeCallback(void callback());
typedef _ScopeCallback = void Function(void Function() callback);
/// Converts [stylesheet] to a plain CSS tree.
///
@ -1074,8 +1074,7 @@ class _EvaluateVisitor
throw "Can't find stylesheet to import.";
}
} on SassException catch (error) {
var frames = error.trace.frames.toList()
..addAll(_stackTrace(span).frames);
var frames = [...error.trace.frames, ..._stackTrace(span).frames];
throw SassRuntimeException(error.message, error.span, Trace(frames));
} catch (error) {
String message;
@ -2393,10 +2392,10 @@ class _EvaluateVisitor
///
/// [span] is the current location, used for the bottom-most stack frame.
Trace _stackTrace(FileSpan span) {
var frames = _stack
.map((tuple) => _stackFrame(tuple.item1, tuple.item2.span))
.toList()
..add(_stackFrame(_member, span));
var frames = [
..._stack.map((tuple) => _stackFrame(tuple.item1, tuple.item2.span)),
_stackFrame(_member, span)
];
return Trace(frames.reversed);
}

View File

@ -30,16 +30,16 @@ dependencies:
dev_dependencies:
archive: ">=1.0.0 <3.0.0"
analyzer: ">=0.30.0 <0.34.0"
analyzer: ">=0.30.0 <0.37.0"
crypto: ">=0.9.2 <3.0.0"
dart_style: "^1.2.0"
grinder: "^0.8.0"
http: "^0.11.0"
http: ">=0.11.0 <0.13.0"
js: "^0.6.0"
node_preamble: "^1.1.0"
pub_semver: "^1.0.0"
source_span: "^1.5.2"
stream_channel: "^1.0.0"
stream_channel: ">=1.0.0 <3.0.0"
test_descriptor: "^1.1.0"
test_process: "^1.0.0-rc.1"
test: ">=0.12.42 <2.0.0"

View File

@ -50,16 +50,17 @@ Future<TestProcess> runSass(Iterable<String> arguments,
var executable = _snapshotPaths.firstWhere((path) => File(path).existsSync(),
orElse: () => p.absolute("bin/sass.dart"));
var args = ["--enable-asserts"];
// Work around dart-lang/sdk#33622.
if (Platform.isWindows) args.add("--packages=${p.absolute('.packages')}");
return TestProcess.start(
Platform.executable,
args
..add(executable)
..addAll(arguments),
[
"--enable-asserts",
// Work around dart-lang/sdk#33622.
if (Platform.isWindows)
"--packages=${p.absolute('.packages')}",
executable,
...arguments
],
workingDirectory: d.sandbox,
environment: environment,
description: "sass");

View File

@ -32,8 +32,7 @@ void main() {
Future<TestProcess> runSass(Iterable<String> arguments,
{Map<String, String> environment}) =>
TestProcess.start(
"node", [p.absolute("build/npm/sass.js")]..addAll(arguments),
TestProcess.start("node", [p.absolute("build/npm/sass.js"), ...arguments],
workingDirectory: d.sandbox,
environment: environment,
description: "sass");

View File

@ -19,8 +19,7 @@ void sharedTests(
/// the contents of the output file match [expected].
Future expectCompiles(List<String> arguments, expected,
{Map<String, String> environment}) async {
var sass = await runSass(
arguments.toList()..add("out.css")..add("--no-source-map"),
var sass = await runSass([...arguments, "out.css", "--no-source-map"],
environment: environment);
await sass.shouldExit(0);
await d.file("out.css", expected).validate();

View File

@ -13,7 +13,7 @@ import '../../utils.dart';
/// Defines test that are shared between the Dart and Node.js CLI test suites.
void sharedTests(Future<TestProcess> runSass(Iterable<String> arguments)) {
Future<TestProcess> update(Iterable<String> arguments) =>
runSass(["--no-source-map", "--update"]..addAll(arguments));
runSass(["--no-source-map", "--update", ...arguments]);
group("updates CSS", () {
test("that doesn't exist yet", () async {

View File

@ -23,11 +23,8 @@ void sharedTests(Future<TestProcess> runSass(Iterable<String> arguments)) {
});
for (var poll in [true, false]) {
Future<TestProcess> watch(Iterable<String> arguments) {
var allArguments = ["--no-source-map", "--watch"]..addAll(arguments);
if (poll) allArguments.add("--poll");
return runSass(allArguments);
}
Future<TestProcess> watch(Iterable<String> arguments) => runSass(
["--no-source-map", "--watch", ...arguments, if (poll) "--poll"]);
/// Returns a future that completes after a delay if [poll] is `true`.
///

View File

@ -18,20 +18,21 @@ void ensureUpToDate(String path, String commandToRun) {
throw "$path does not exist. Run $commandToRun.";
}
var entriesToCheck = [
...Directory("lib").listSync(recursive: true),
// If we have a dependency override, "pub run" will touch the lockfile to
// mark it as newer than the pubspec, which makes it unsuitable to use for
// freshness checking.
if (File("pubspec.yaml")
.readAsStringSync()
.contains("dependency_overrides"))
File("pubspec.yaml")
else
File("pubspec.lock")
];
var lastModified = File(path).lastModifiedSync();
var entriesToCheck = Directory("lib").listSync(recursive: true).toList();
// If we have a dependency override, "pub run" will touch the lockfile to mark
// it as newer than the pubspec, which makes it unsuitable to use for
// freshness checking.
if (File("pubspec.yaml")
.readAsStringSync()
.contains("dependency_overrides")) {
entriesToCheck.add(File("pubspec.yaml"));
} else {
entriesToCheck.add(File("pubspec.lock"));
}
for (var entry in entriesToCheck) {
if (entry is File) {
var entryLastModified = entry.lastModifiedSync();

View File

@ -78,11 +78,24 @@ void main() {
var basePath = p.join(subDir, 'base.scss');
await writeTextFile(basePath, '@import "test"');
expect(renderSync(RenderOptions(file: basePath)),
expect(
renderSync(RenderOptions(
file: basePath,
importer: allowInterop(
(_, __) => NodeImporterResult(contents: "q {r: s}")))),
equalsIgnoringWhitespace('x { y: z; }'));
});
test("CWD is #2", () async {
test("importer is #2", () async {
expect(
renderSync(RenderOptions(
data: '@import "test"',
importer: allowInterop(
(_, __) => NodeImporterResult(contents: "x {y: z}")))),
equalsIgnoringWhitespace('x { y: z; }'));
});
test("CWD is #3", () async {
var subDir = p.join(sandbox, 'sub');
await createDirectory(subDir);
await writeTextFile(p.join(subDir, 'test.scss'), 'x {y: z}');
@ -94,13 +107,17 @@ void main() {
});
});
test("include path is #3", () async {
expect(
renderSync(RenderOptions(
data: '@import "test"',
includePaths: [sandbox],
importer: allowInterop(expectAsync2((_, __) {}, count: 0)))),
equalsIgnoringWhitespace('a { b: c; }'));
test("include path is #4", () async {
var subDir = p.join(sandbox, 'sub');
await createDirectory(subDir);
await writeTextFile(p.join(subDir, 'test.scss'), 'x {y: z}');
withSassPath([subDir], () {
expect(
renderSync(
RenderOptions(data: '@import "test"', includePaths: [sandbox])),
equalsIgnoringWhitespace('a { b: c; }'));
});
});
});

View File

@ -105,3 +105,15 @@ void runTestInSandbox() {
void setEnvironmentVariable(String name, String value) {
setProperty(_environment, name, value);
}
// Runs [callback] with the `SASS_PATH` environment variable set to [paths].
T withSassPath<T>(List<String> paths, T callback()) {
var oldSassPath =
setEnvironmentVariable("SASS_PATH", paths.join(isWindows ? ';' : ':'));
try {
return callback();
} finally {
setEnvironmentVariable("SASS_PATH", null);
}
}

View File

@ -12,7 +12,6 @@ import 'package:path/path.dart' as p;
import 'package:test/test.dart';
import 'package:sass/src/node/utils.dart';
import 'package:sass/src/io.dart';
import 'ensure_npm_package.dart';
import 'hybrid.dart';
@ -102,18 +101,12 @@ void main() {
await writeTextFile(p.join(sandbox, 'dir1', 'test1.scss'), 'a {b: c}');
await writeTextFile(p.join(sandbox, 'dir2', 'test2.scss'), 'x {y: z}');
var separator = isWindows ? ';' : ':';
setEnvironmentVariable("SASS_PATH",
p.join(sandbox, 'dir1') + separator + p.join(sandbox, 'dir2'));
try {
withSassPath([p.join(sandbox, 'dir1'), p.join(sandbox, 'dir2')], () {
expect(renderSync(RenderOptions(data: """
@import 'test1';
@import 'test2';
""")), equalsIgnoringWhitespace('a { b: c; } x { y: z; }'));
} finally {
setEnvironmentVariable("SASS_PATH", null);
}
});
});
test("load path takes precedence over SASS_PATH", () async {

View File

@ -795,7 +795,7 @@ void _expectMapMatches(
Map<String, SourceLocation> sourceLocations,
List<Tuple2<String, SourceLocation>> targetLocations) {
expect(sourceLocations.keys,
equals(Set.of(targetLocations.map((tuple) => tuple.item1))));
equals({for (var tuple in targetLocations) tuple.item1}));
String actualMap() =>
"\nActual map:\n\n" + _mapToString(map, sourceText, targetText) + "\n";
@ -808,7 +808,7 @@ void _expectMapMatches(
if (!entryIter.moveNext()) {
fail('Missing mapping "$name", expected '
'${_mapping(expectedSource, expectedTarget)}.\n' +
'${_mapping(expectedSource, expectedTarget)}.\n' +
actualMap());
}
@ -818,7 +818,7 @@ void _expectMapMatches(
expectedTarget.line != entry.target.line ||
expectedTarget.column != entry.target.column) {
fail('Mapping "$name" was ${_mapping(entry.source, entry.target)}, '
'expected ${_mapping(expectedSource, expectedTarget)}.\n' +
'expected ${_mapping(expectedSource, expectedTarget)}.\n' +
actualMap());
}
}

View File

@ -6,7 +6,6 @@
// normalization issues.
@TestOn('vm && !windows')
import 'dart:convert';
import 'dart:io';
import 'package:crypto/crypto.dart';

View File

@ -26,10 +26,11 @@ all() {}
@Task('Run the Dart formatter.')
format() {
Pub.run('dart_style',
script: 'format',
arguments: ['--overwrite', '--fix']
..addAll(existingSourceDirs.map((dir) => dir.path)));
Pub.run('dart_style', script: 'format', arguments: [
'--overwrite',
'--fix',
for (var dir in existingSourceDirs) dir.path
]);
}
@Task('Installs dependencies from npm.')

View File

@ -46,14 +46,14 @@ String _releaseMessage() {
"https://github.com/sass/dart-sass/blob/master/CHANGELOG.md#" +
version.replaceAll(".", "");
return "To install Dart Sass $version, download one of the packages above "
"and [add it to your PATH](https://katiek2.github.io/path-doc/), or see "
"[the Sass website](https://sass-lang.com/install) for full installation "
"instructions.\n\n"
"## Changes\n\n" +
"and [add it to your PATH](https://katiek2.github.io/path-doc/), or "
"see [the Sass website](https://sass-lang.com/install) for full "
"installation instructions.\n\n"
"## Changes\n\n" +
_lastChangelogSection() +
"\n\n"
"See the [full changelog]($changelogUrl) for changes in earlier "
"releases.";
"See the [full changelog]($changelogUrl) for changes in earlier "
"releases.";
}
/// A regular expression that matches a Markdown code block.

View File

@ -29,23 +29,19 @@ void _js({@required bool release}) {
ensureBuild();
var destination = File('build/sass.dart.js');
var args = [
Dart2js.compile(File('bin/sass.dart'), outFile: destination, extraArgs: [
'--categories=Server',
'-Dnode=true',
'-Dversion=$version',
'-Ddart-version=$dartVersion',
];
if (release) {
// We use O4 because:
//
// * We don't care about the string representation of types.
// * We expect our test coverage to ensure that nothing throws subtypes of
// Error.
// * We thoroughly test edge cases in user input.
args..add("-O4")..add("--fast-startup");
}
Dart2js.compile(File('bin/sass.dart'), outFile: destination, extraArgs: args);
if (release) ...["-O4", "--fast-startup"]
]);
var text = destination.readAsStringSync();
if (release) {
@ -71,13 +67,15 @@ npmReleasePackage() => _npm(release: true);
/// --trust-type-annotations. Otherwise, it compiles unminified with pessimistic
/// type checks.
void _npm({@required bool release}) {
var json = jsonDecode(File('package/package.json').readAsStringSync())
as Map<String, dynamic>;
json['version'] = version;
var json = {
...(jsonDecode(File('package/package.json').readAsStringSync())
as Map<String, dynamic>),
"version": version
};
_writeNpmPackage('build/npm', json);
if (release) {
_writeNpmPackage('build/npm-old', json..addAll({"name": "dart-sass"}));
_writeNpmPackage('build/npm-old', {...json, "name": "dart-sass"});
}
}

View File

@ -28,8 +28,11 @@ appSnapshot() => _appSnapshot();
@Task('Build a native-code Dart executable.')
nativeExecutable() {
ensureBuild();
run(p.join(sdkDir.path, 'bin/dart2aot'),
arguments: ['bin/sass.dart', 'build/sass.dart.native']);
run(p.join(sdkDir.path, 'bin/dart2aot'), arguments: [
'bin/sass.dart',
'-Dversion=$version',
'build/sass.dart.native'
]);
}
/// Compiles Sass to an application snapshot.

View File

@ -6,7 +6,9 @@ import 'dart:convert';
import 'dart:io';
import 'package:analyzer/analyzer.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:crypto/crypto.dart';
import 'package:dart_style/dart_style.dart';
import 'package:grinder/grinder.dart';
@ -75,7 +77,7 @@ class _Visitor extends RecursiveAstVisitor {
_buffer.writeln(_source.substring(0, afterHeader));
_buffer.writeln("""
// DO NOT EDIT. This file was generated from ${p.basename(path)}.
// See tool/synchronize.dart for details.
// See tool/grind/synchronize.dart for details.
//
// Checksum: ${sha1.convert(utf8.encode(_source))}
//

View File

@ -1,237 +0,0 @@
// Copyright 2017 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:convert';
import 'dart:io';
import 'package:analyzer/analyzer.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:crypto/crypto.dart';
import 'package:dart_style/dart_style.dart';
import 'package:grinder/grinder.dart';
import 'package:path/path.dart' as p;
/// The files to compile to synchronous versions.
final _sources = {
'lib/src/visitor/async_evaluate.dart': 'lib/src/visitor/evaluate.dart',
'lib/src/async_environment.dart': 'lib/src/environment.dart',
'lib/src/async_import_cache.dart': 'lib/src/import_cache.dart'
};
/// A map from source file basenames to imports which should be removed when
/// generating the output files.
final _removeImports = {
'async_import_cache.dart': ['utils.dart']
};
/// This is how we support both synchronous and asynchronous compilation modes.
///
/// Both modes are necessary. Synchronous mode is faster, works in sync-only
/// contexts, and allows us to support Node Sass's renderSync() method.
/// Asynchronous mode allows users to write async importers and functions in
/// both Dart and JS.
///
/// The logic for synchronous and asynchronous mode is identical, but the async
/// code needs to await every statement and expression evaluation in case they
/// do asynchronous work. To avoid duplicating logic, we hand-write asynchronous
/// code for the evaluator and the environment and use this task to compile it
/// to a synchronous equivalent.
@Task('Compile async code to synchronous code.')
synchronize() {
_sources.forEach((source, target) {
var visitor = _Visitor(File(source).readAsStringSync(), source);
parseDartFile(source).accept(visitor);
var formatted = DartFormatter().format(visitor.result);
File(target).writeAsStringSync(formatted);
});
}
/// The visitor that traverses the asynchronous parse tree and converts it to
/// synchronous code.
///
/// To preserve the original whitespace and comments, this copies text from the
/// original source where possible. It tracks the [_position] at the end of the
/// text that's been written, and writes from that position to the new position
/// whenever text needs to be emitted.
class _Visitor extends RecursiveAstVisitor {
/// The source of the original asynchronous file.
final String _source;
/// The path to the original asynchronous file.
final String _sourcePath;
/// The current position in [_source].
var _position = 0;
/// The buffer in which the text of the synchronous file is built up.
final _buffer = StringBuffer();
/// The synchronous text.
String get result {
_buffer.write(_source.substring(_position));
_position = _source.length;
return _buffer.toString();
}
_Visitor(this._source, this._sourcePath) {
var afterHeader = "\n".allMatches(_source).skip(3).first.end;
_buffer.writeln(_source.substring(0, afterHeader));
_buffer.writeln("""
// DO NOT EDIT. This file was generated from ${p.basename(_sourcePath)}.
// See tool/synchronize.dart for details.
//
// Checksum: ${sha1.convert(utf8.encode(_source))}
""");
if (p.basename(_sourcePath) == 'async_evaluate.dart') {
_buffer.writeln();
_buffer.writeln("import 'async_evaluate.dart' show EvaluateResult;");
_buffer.writeln("export 'async_evaluate.dart' show EvaluateResult;");
_buffer.writeln();
}
_position = afterHeader;
}
void visitAwaitExpression(AwaitExpression node) {
_skip(node.awaitKeyword);
// Skip the space after "await" to work around dart-lang/dart_style#226.
_position++;
node.expression.accept(this);
}
void visitParenthesizedExpression(ParenthesizedExpression node) {
if (node.expression is AwaitExpression) {
_skip(node.leftParenthesis);
node.expression.accept(this);
_skip(node.rightParenthesis);
} else {
node.expression.accept(this);
}
}
void visitBlockFunctionBody(BlockFunctionBody node) {
_skip(node.keyword);
node.visitChildren(this);
}
void visitClassDeclaration(ClassDeclaration node) {
if (node.name.name == 'EvaluateResult') {
_skipNode(node);
} else {
super.visitClassDeclaration(node);
}
}
void visitExpressionFunctionBody(ExpressionFunctionBody node) {
_skip(node.keyword);
node.visitChildren(this);
}
void visitMethodDeclaration(MethodDeclaration node) {
if (_synchronizeName(node.name.name) != node.name.name) {
// If the file defines any asynchronous versions of synchronous functions,
// remove them.
_skipNode(node);
} else {
super.visitMethodDeclaration(node);
}
}
void visitImportDirective(ImportDirective node) {
_skipNode(node);
var url = node.uri.stringValue;
if (url == "dart:async") return;
var removeImports = _removeImports[p.basename(_sourcePath)];
if (removeImports != null && removeImports.contains(url)) return;
_buffer.write(node.toString().replaceAll("async_", ""));
}
void visitMethodInvocation(MethodInvocation node) {
// Convert async utility methods to their synchronous equivalents.
if (node.target == null &&
["mapAsync", "putIfAbsentAsync"].contains(node.methodName.name)) {
_writeTo(node);
var arguments = node.argumentList.arguments;
_write(arguments.first);
_buffer.write(".${_synchronizeName(node.methodName.name)}");
if (node.typeArguments != null) _write(node.typeArguments);
_buffer.write("(");
_position = arguments[1].beginToken.offset;
for (var argument in arguments.skip(1)) {
argument.accept(this);
}
} else {
super.visitMethodInvocation(node);
}
}
void visitSimpleIdentifier(SimpleIdentifier node) {
_skip(node.token);
_buffer.write(_synchronizeName(node.name));
}
void visitTypeName(TypeName node) {
if (["Future", "FutureOr"].contains(node.name.name)) {
_skip(node.name.beginToken);
if (node.typeArguments != null) {
_skip(node.typeArguments.leftBracket);
node.typeArguments.arguments.first.accept(this);
_skip(node.typeArguments.rightBracket);
} else {
_buffer.write("void");
}
} else {
super.visitTypeName(node);
}
}
/// Writes [_source] to [_buffer] up to the beginning of [token], then puts
/// [_position] after [token] so it doesn't get written.
void _skip(Token token) {
if (token == null) return;
_buffer.write(_source.substring(_position, token.offset));
_position = token.end;
}
/// Writes [_source] to [_buffer] up to the beginning of [node], then puts
/// [_position] after [node] so it doesn't get written.
void _skipNode(AstNode node) {
if (node == null) return;
_writeTo(node);
_position = node.endToken.end;
}
/// Writes [_source] to [_buffer] up to the beginning of [node].
void _writeTo(AstNode node) {
_buffer.write(_source.substring(_position, node.beginToken.offset));
_position = node.beginToken.offset;
}
/// Writes the contents of [node] to [_buffer].
///
/// This leaves [_position] at the end of [node].
void _write(AstNode node) {
_position = node.beginToken.offset;
node.accept(this);
_buffer.write(_source.substring(_position, node.endToken.end));
_position = node.endToken.end;
}
/// Strips an "async" prefix or suffix from [name].
String _synchronizeName(String name) {
if (name.toLowerCase().startsWith('async')) {
return name.substring('async'.length);
} else if (name.toLowerCase().endsWith('async')) {
return name.substring(0, name.length - 'async'.length);
} else {
return name;
}
}
}

Binary file not shown.

View File

@ -5,8 +5,8 @@
# Decrypts the encrypted credentials using Travis's private key and saves them
# to credentials.tar.
function decrypt_credentials() {
openssl aes-256-cbc -K $encrypted_867f88017e77_key \
-iv $encrypted_867f88017e77_iv \
openssl aes-256-cbc -K $encrypted_b4541d8c554c_key \
-iv $encrypted_b4541d8c554c_iv \
-in tool/travis/credentials.tar.enc \
-out credentials.tar -d
}