mirror of
https://github.com/danog/dart-sass.git
synced 2024-11-30 04:39:03 +01:00
parent
dab524d277
commit
96c46a242e
@ -5,6 +5,9 @@
|
||||
* Add a `--poll` flag to make `--watch` mode repeatedly check the filesystem for
|
||||
updates rather than relying on native filesystem notifications.
|
||||
|
||||
* Add a `--stop-on-error` flag to stop compiling additional files once an error
|
||||
is encountered.
|
||||
|
||||
## 1.7.3
|
||||
|
||||
* No user-visible changes.
|
||||
|
@ -80,12 +80,14 @@ main(List<String> args) async {
|
||||
//
|
||||
// We let exitCode 66 take precedence for deterministic behavior.
|
||||
if (exitCode != 66) exitCode = 65;
|
||||
if (options.stopOnError) return;
|
||||
} on FileSystemException catch (error, stackTrace) {
|
||||
printError("Error reading ${p.relative(error.path)}: ${error.message}.",
|
||||
options.trace ? stackTrace : null);
|
||||
|
||||
// Error 66 indicates no input.
|
||||
exitCode = 66;
|
||||
if (options.stopOnError) return;
|
||||
}
|
||||
}
|
||||
} on UsageException catch (error) {
|
||||
|
@ -79,6 +79,8 @@ class ExecutableOptions {
|
||||
help: 'Manually check for changes rather than using a native '
|
||||
'watcher.\n'
|
||||
'Only valid with --watch.')
|
||||
..addFlag('stop-on-error',
|
||||
help: "Don't compile more files once an error is encountered.")
|
||||
..addFlag('interactive',
|
||||
abbr: 'i',
|
||||
help: 'Run an interactive SassScript shell.',
|
||||
@ -177,6 +179,10 @@ class ExecutableOptions {
|
||||
/// Whether to manually poll for changes when watching.
|
||||
bool get poll => _options['poll'] as bool;
|
||||
|
||||
/// Whether to stop compiling additional files once one file produces an
|
||||
/// error.
|
||||
bool get stopOnError => _options['stop-on-error'] as bool;
|
||||
|
||||
/// A map from source paths to the destination paths where the compiled CSS
|
||||
/// should be written.
|
||||
///
|
||||
|
@ -41,7 +41,11 @@ Future watch(ExecutableOptions options, StylesheetGraph graph) async {
|
||||
var destination = options.sourcesToDestinations[source];
|
||||
graph.addCanonical(new FilesystemImporter('.'),
|
||||
p.toUri(p.canonicalize(source)), p.toUri(source));
|
||||
await watcher.compile(source, destination, ifModified: true);
|
||||
var success = await watcher.compile(source, destination, ifModified: true);
|
||||
if (!success && options.stopOnError) {
|
||||
dirWatcher.events.listen(null).cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
print("Sass is watching for changes. Press Ctrl-C to stop.\n");
|
||||
@ -51,25 +55,34 @@ Future watch(ExecutableOptions options, StylesheetGraph graph) async {
|
||||
/// Holds state that's shared across functions that react to changes on the
|
||||
/// filesystem.
|
||||
class _Watcher {
|
||||
/// The options for the Sass executable.
|
||||
final ExecutableOptions _options;
|
||||
|
||||
/// The graph of stylesheets being compiled.
|
||||
final StylesheetGraph _graph;
|
||||
|
||||
_Watcher(this._options, this._graph);
|
||||
|
||||
/// Compiles the stylesheet at [source] to [destination], and prints any
|
||||
/// errors that occur.
|
||||
Future compile(String source, String destination,
|
||||
///
|
||||
/// Returns whether or not compilation succeeded.
|
||||
Future<bool> compile(String source, String destination,
|
||||
{bool ifModified: false}) async {
|
||||
try {
|
||||
await compileStylesheet(_options, _graph, source, destination,
|
||||
ifModified: ifModified);
|
||||
return true;
|
||||
} on SassException catch (error, stackTrace) {
|
||||
_delete(destination);
|
||||
_printError(error.toString(color: _options.color), stackTrace);
|
||||
exitCode = 65;
|
||||
return false;
|
||||
} on FileSystemException catch (error, stackTrace) {
|
||||
_printError("Error reading ${p.relative(error.path)}: ${error.message}.",
|
||||
stackTrace);
|
||||
exitCode = 66;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,7 +110,7 @@ class _Watcher {
|
||||
stderr.writeln(new Trace.from(stackTrace).terse.toString().trimRight());
|
||||
}
|
||||
|
||||
stderr.writeln();
|
||||
if (!_options.stopOnError) stderr.writeln();
|
||||
}
|
||||
|
||||
/// Listens to `watcher.events` and updates the filesystem accordingly.
|
||||
@ -119,11 +132,13 @@ class _Watcher {
|
||||
// from the graph.
|
||||
var node = _graph.nodes[url];
|
||||
_graph.reload(url);
|
||||
await _recompileDownstream([node]);
|
||||
var success = await _recompileDownstream([node]);
|
||||
if (!success && _options.stopOnError) return;
|
||||
break;
|
||||
|
||||
case ChangeType.ADD:
|
||||
await _retryPotentialImports(event.path);
|
||||
var success = await _retryPotentialImports(event.path);
|
||||
if (!success && _options.stopOnError) return;
|
||||
|
||||
var destination = _destinationFor(event.path);
|
||||
if (destination == null) continue loop;
|
||||
@ -131,11 +146,13 @@ class _Watcher {
|
||||
_graph.addCanonical(
|
||||
new FilesystemImporter('.'), url, p.toUri(event.path));
|
||||
|
||||
await compile(event.path, destination);
|
||||
success = await compile(event.path, destination);
|
||||
if (!success && _options.stopOnError) return;
|
||||
break;
|
||||
|
||||
case ChangeType.REMOVE:
|
||||
await _retryPotentialImports(event.path);
|
||||
var success = await _retryPotentialImports(event.path);
|
||||
if (!success && _options.stopOnError) return;
|
||||
if (!_graph.nodes.containsKey(url)) continue loop;
|
||||
|
||||
var destination = _destinationFor(event.path);
|
||||
@ -143,7 +160,8 @@ class _Watcher {
|
||||
|
||||
var downstream = _graph.nodes[url].downstream;
|
||||
_graph.remove(url);
|
||||
await _recompileDownstream(downstream);
|
||||
success = await _recompileDownstream(downstream);
|
||||
if (!success && _options.stopOnError) return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -176,29 +194,38 @@ class _Watcher {
|
||||
|
||||
/// Recompiles [nodes] and everything that transitively imports them, if
|
||||
/// necessary.
|
||||
Future _recompileDownstream(Iterable<StylesheetNode> nodes) async {
|
||||
///
|
||||
/// Returns whether all recompilations succeeded.
|
||||
Future<bool> _recompileDownstream(Iterable<StylesheetNode> nodes) async {
|
||||
var seen = new Set<StylesheetNode>();
|
||||
var toRecompile = new Queue.of(nodes);
|
||||
|
||||
var allSucceeded = true;
|
||||
while (!toRecompile.isEmpty) {
|
||||
var node = toRecompile.removeFirst();
|
||||
if (!seen.add(node)) continue;
|
||||
|
||||
await _compileIfEntrypoint(node.canonicalUrl);
|
||||
var success = await _compileIfEntrypoint(node.canonicalUrl);
|
||||
allSucceeded = allSucceeded && success;
|
||||
if (!success && _options.stopOnError) return false;
|
||||
|
||||
toRecompile.addAll(node.downstream);
|
||||
}
|
||||
return allSucceeded;
|
||||
}
|
||||
|
||||
/// Compiles the stylesheet at [url] to CSS if it's an entrypoint that's being
|
||||
/// watched.
|
||||
Future _compileIfEntrypoint(Uri url) async {
|
||||
if (url.scheme != 'file') return;
|
||||
///
|
||||
/// Returns `false` if compilation failed, `true` otherwise.
|
||||
Future<bool> _compileIfEntrypoint(Uri url) async {
|
||||
if (url.scheme != 'file') return true;
|
||||
|
||||
var source = p.fromUri(url);
|
||||
var destination = _destinationFor(source);
|
||||
if (destination == null) return;
|
||||
if (destination == null) return true;
|
||||
|
||||
await compile(source, destination);
|
||||
return await compile(source, destination);
|
||||
}
|
||||
|
||||
/// If a Sass file at [source] should be compiled to CSS, returns the path to
|
||||
@ -223,7 +250,9 @@ class _Watcher {
|
||||
/// Re-runs all imports in [_graph] that might refer to [path], and recompiles
|
||||
/// the files that contain those imports if they end up importing new
|
||||
/// stylesheets.
|
||||
Future _retryPotentialImports(String path) async {
|
||||
///
|
||||
/// Returns whether all recompilations succeeded.
|
||||
Future<bool> _retryPotentialImports(String path) async {
|
||||
var name = _name(p.basename(path));
|
||||
var changed = <StylesheetNode>[];
|
||||
for (var node in _graph.nodes.values) {
|
||||
@ -250,7 +279,7 @@ class _Watcher {
|
||||
if (importChanged) changed.add(node);
|
||||
}
|
||||
|
||||
await _recompileDownstream(changed);
|
||||
return await _recompileDownstream(changed);
|
||||
}
|
||||
|
||||
/// Removes an extension from [extension], and a leading underscore if it has one.
|
||||
|
@ -263,7 +263,9 @@ Future<Stream<WatchEvent>> watchDir(String path, {bool poll: false}) {
|
||||
|
||||
var completer = new Completer<Stream<WatchEvent>>();
|
||||
watcher.on('ready', allowInterop(() {
|
||||
controller = new StreamController<WatchEvent>();
|
||||
controller = new StreamController<WatchEvent>(onCancel: () {
|
||||
watcher.close();
|
||||
});
|
||||
completer.complete(controller.stream);
|
||||
}));
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
name: sass
|
||||
version: 1.8.0-dev
|
||||
version: 1.8.0
|
||||
description: A Sass implementation in Dart.
|
||||
author: Dart Team <misc@dartlang.org>
|
||||
homepage: https://github.com/sass/dart-sass
|
||||
|
@ -68,6 +68,41 @@ void sharedTests(Future<TestProcess> runSass(Iterable<String> arguments)) {
|
||||
await d.file("out2.css.map", contains("test2.scss")).validate();
|
||||
});
|
||||
|
||||
test("continues compiling after an error", () async {
|
||||
await d.file("test1.scss", "a {b: }").create();
|
||||
await d.file("test2.scss", "x {y: z}").create();
|
||||
|
||||
var sass = await runSass(
|
||||
["--no-source-map", "test1.scss:out1.css", "test2.scss:out2.css"]);
|
||||
await expectLater(sass.stderr, emits('Error: Expected expression.'));
|
||||
await expectLater(sass.stderr, emitsThrough(contains('test1.scss 1:7')));
|
||||
await sass.shouldExit(65);
|
||||
|
||||
await d.nothing("out1.css").validate();
|
||||
await d
|
||||
.file("out2.css", equalsIgnoringWhitespace("x { y: z; }"))
|
||||
.validate();
|
||||
});
|
||||
|
||||
test("stops compiling after an error with --stop-on-error", () async {
|
||||
await d.file("test1.scss", "a {b: }").create();
|
||||
await d.file("test2.scss", "x {y: z}").create();
|
||||
|
||||
var sass = await runSass(
|
||||
["--stop-on-error", "test1.scss:out1.css", "test2.scss:out2.css"]);
|
||||
await expectLater(
|
||||
sass.stderr,
|
||||
emitsInOrder([
|
||||
'Error: Expected expression.',
|
||||
emitsThrough(contains('test1.scss 1:7')),
|
||||
emitsDone
|
||||
]));
|
||||
await sass.shouldExit(65);
|
||||
|
||||
await d.nothing("out1.css").validate();
|
||||
await d.nothing("out2.css").validate();
|
||||
});
|
||||
|
||||
group("with a directory argument", () {
|
||||
test("compiles all the stylesheets in the directory", () async {
|
||||
await d.dir("in", [
|
||||
|
@ -64,6 +64,48 @@ void sharedTests(Future<TestProcess> runSass(Iterable<String> arguments)) {
|
||||
|
||||
await d.file("out.css", "x {y: z}").validate();
|
||||
});
|
||||
|
||||
test("continues compiling after an error", () async {
|
||||
await d.file("test1.scss", "a {b: }").create();
|
||||
await d.file("test2.scss", "x {y: z}").create();
|
||||
|
||||
var sass =
|
||||
await watch(["test1.scss:out1.css", "test2.scss:out2.css"]);
|
||||
await expectLater(sass.stderr, emits('Error: Expected expression.'));
|
||||
await expectLater(
|
||||
sass.stderr, emitsThrough(contains('test1.scss 1:7')));
|
||||
await expectLater(
|
||||
sass.stdout, emitsThrough('Compiled test2.scss to out2.css.'));
|
||||
await expectLater(sass.stdout, _watchingForChanges);
|
||||
await sass.kill();
|
||||
|
||||
await d.nothing("out1.css").validate();
|
||||
await d
|
||||
.file("out2.css", equalsIgnoringWhitespace("x { y: z; }"))
|
||||
.validate();
|
||||
});
|
||||
|
||||
test("stops compiling after an error with --stop-on-error", () async {
|
||||
await d.file("test1.scss", "a {b: }").create();
|
||||
await d.file("test2.scss", "x {y: z}").create();
|
||||
|
||||
var sass = await watch([
|
||||
"--stop-on-error",
|
||||
"test1.scss:out1.css",
|
||||
"test2.scss:out2.css"
|
||||
]);
|
||||
await expectLater(
|
||||
sass.stderr,
|
||||
emitsInOrder([
|
||||
'Error: Expected expression.',
|
||||
emitsThrough(contains('test1.scss 1:7')),
|
||||
emitsDone
|
||||
]));
|
||||
await sass.shouldExit(65);
|
||||
|
||||
await d.nothing("out1.css").validate();
|
||||
await d.nothing("out2.css").validate();
|
||||
});
|
||||
});
|
||||
|
||||
group("recompiles a watched file", () {
|
||||
@ -168,6 +210,28 @@ void sharedTests(Future<TestProcess> runSass(Iterable<String> arguments)) {
|
||||
await d.nothing("out.css").validate();
|
||||
});
|
||||
|
||||
test("stops compiling after an error with --stop-on-error", () async {
|
||||
await d.file("test.scss", "a {b: c}").create();
|
||||
|
||||
var sass = await watch(["--stop-on-error", "test.scss:out.css"]);
|
||||
await expectLater(
|
||||
sass.stdout, emits('Compiled test.scss to out.css.'));
|
||||
await expectLater(sass.stdout, _watchingForChanges);
|
||||
await tickIfPoll();
|
||||
|
||||
await d.file("test.scss", "a {b: }").create();
|
||||
await expectLater(
|
||||
sass.stderr,
|
||||
emitsInOrder([
|
||||
'Error: Expected expression.',
|
||||
emitsThrough(contains('test.scss 1:7')),
|
||||
emitsDone
|
||||
]));
|
||||
await sass.shouldExit(65);
|
||||
|
||||
await d.nothing("out.css").validate();
|
||||
});
|
||||
|
||||
group("when its dependency is deleted", () {
|
||||
test("and removes the output", () async {
|
||||
await d.file("_other.scss", "a {b: c}").create();
|
||||
|
Loading…
Reference in New Issue
Block a user