Skip to content

Commit f81f57d

Browse files
authored
Merge pull request #962 from sass/watch-bug
Fix a --watch bug
2 parents 3318efa + 116165f commit f81f57d

File tree

12 files changed

+224
-125
lines changed

12 files changed

+224
-125
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## 1.26.1
2+
3+
### Command Line Interface
4+
5+
* Fix a longstanding bug where `--watch` mode could enter into a state where
6+
recompilation would not occur after a syntax error was introduced into a
7+
dependency and then fixed.
8+
19
## 1.26.0
210

311
* **Potentially breaking bug fix:** `@use` rules whose URLs' basenames begin

lib/src/async_import_cache.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ class AsyncImportCache {
112112
/// canonicalize [url] (resolved relative to [baseUrl] if it's passed).
113113
///
114114
/// If any importers understand [url], returns that importer as well as the
115-
/// canonicalized URL. Otherwise, returns `null`.
115+
/// canonicalized URL and the original URL resolved relative to [baseUrl] if
116+
/// applicable. Otherwise, returns `null`.
116117
Future<Tuple3<AsyncImporter, Uri, Uri>> canonicalize(Uri url,
117118
{AsyncImporter baseImporter, Uri baseUrl, bool forImport = false}) async {
118119
if (baseImporter != null) {

lib/src/executable/watch.dart

Lines changed: 10 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import 'dart:async';
66
import 'dart:collection';
77

8-
import 'package:meta/meta.dart';
98
import 'package:path/path.dart' as p;
109
import 'package:stack_trace/stack_trace.dart';
1110
import 'package:stream_transform/stream_transform.dart';
@@ -44,7 +43,8 @@ Future<void> watch(ExecutableOptions options, StylesheetGraph graph) async {
4443
for (var source in options.sourcesToDestinations.keys) {
4544
var destination = options.sourcesToDestinations[source];
4645
graph.addCanonical(FilesystemImporter('.'), p.toUri(p.canonicalize(source)),
47-
p.toUri(source));
46+
p.toUri(source),
47+
recanonicalize: false);
4848
var success = await watcher.compile(source, destination, ifModified: true);
4949
if (!success && options.stopOnError) {
5050
dirWatcher.events.listen(null).cancel();
@@ -165,32 +165,26 @@ class _Watcher {
165165
///
166166
/// Returns whether all necessary recompilations succeeded.
167167
Future<bool> _handleAdd(String path) async {
168-
var success = await _retryPotentialImports(path);
169-
if (!success && _options.stopOnError) return false;
170-
171168
var destination = _destinationFor(path);
172-
if (destination == null) return true;
173169

174-
_graph.addCanonical(
170+
var success = destination == null || await compile(path, destination);
171+
var downstream = _graph.addCanonical(
175172
FilesystemImporter('.'), _canonicalize(path), p.toUri(path));
176-
177-
return await compile(path, destination);
173+
return await _recompileDownstream(downstream) && success;
178174
}
179175

180176
/// Handles a remove event for the stylesheet at [url].
181177
///
182178
/// Returns whether all necessary recompilations succeeded.
183179
Future<bool> _handleRemove(String path) async {
184180
var url = _canonicalize(path);
185-
var success = await _retryPotentialImports(path);
186-
if (!success && _options.stopOnError) return false;
187-
if (!_graph.nodes.containsKey(url)) return true;
188181

189-
var destination = _destinationFor(path);
190-
if (destination != null) _delete(destination);
182+
if (_graph.nodes.containsKey(url)) {
183+
var destination = _destinationFor(path);
184+
if (destination != null) _delete(destination);
185+
}
191186

192-
var downstream = _graph.nodes[url].downstream;
193-
_graph.remove(url);
187+
var downstream = _graph.remove(FilesystemImporter('.'), url);
194188
return await _recompileDownstream(downstream);
195189
}
196190

@@ -278,56 +272,4 @@ class _Watcher {
278272

279273
return null;
280274
}
281-
282-
/// Re-runs all imports in [_graph] that might refer to [path], and recompiles
283-
/// the files that contain those imports if they end up importing new
284-
/// stylesheets.
285-
///
286-
/// Returns whether all recompilations succeeded.
287-
Future<bool> _retryPotentialImports(String path) async {
288-
var name = _name(p.basename(path));
289-
var changed = <StylesheetNode>[];
290-
for (var node in _graph.nodes.values) {
291-
var importChanged = false;
292-
void recanonicalize(Uri url, StylesheetNode upstream,
293-
{@required bool forImport}) {
294-
if (_name(p.url.basename(url.path)) != name) return;
295-
_graph.clearCanonicalize(url);
296-
297-
// If the import produces a different canonicalized URL than it did
298-
// before, it changed and the stylesheet needs to be recompiled.
299-
if (!importChanged) {
300-
Uri newCanonicalUrl;
301-
try {
302-
newCanonicalUrl = _graph.importCache
303-
.canonicalize(url,
304-
baseImporter: node.importer,
305-
baseUrl: node.canonicalUrl,
306-
forImport: forImport)
307-
?.item2;
308-
} catch (_) {
309-
// If the call to canonicalize failed, do nothing. We'll surface
310-
// the error more nicely when we try to recompile the file.
311-
}
312-
importChanged = newCanonicalUrl != upstream?.canonicalUrl;
313-
}
314-
}
315-
316-
for (var entry in node.upstream.entries) {
317-
recanonicalize(entry.key, entry.value, forImport: false);
318-
}
319-
for (var entry in node.upstreamImports.entries) {
320-
recanonicalize(entry.key, entry.value, forImport: true);
321-
}
322-
if (importChanged) changed.add(node);
323-
}
324-
325-
return await _recompileDownstream(changed);
326-
}
327-
328-
/// Removes an extension from [extension], and a leading underscore if it has one.
329-
String _name(String basename) {
330-
basename = p.withoutExtension(basename);
331-
return basename.startsWith("_") ? basename.substring(1) : basename;
332-
}
333275
}

lib/src/import_cache.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// DO NOT EDIT. This file was generated from async_import_cache.dart.
66
// See tool/grind/synchronize.dart for details.
77
//
8-
// Checksum: 3ca2f221c3c1503c688be49d4f7501bd9be8031a
8+
// Checksum: c1dd0f2a41b03bd1227a2358688bf44499b9d90c
99
//
1010
// ignore_for_file: unused_import
1111

@@ -116,7 +116,8 @@ class ImportCache {
116116
/// canonicalize [url] (resolved relative to [baseUrl] if it's passed).
117117
///
118118
/// If any importers understand [url], returns that importer as well as the
119-
/// canonicalized URL. Otherwise, returns `null`.
119+
/// canonicalized URL and the original URL resolved relative to [baseUrl] if
120+
/// applicable. Otherwise, returns `null`.
120121
Tuple3<Importer, Uri, Uri> canonicalize(Uri url,
121122
{Importer baseImporter, Uri baseUrl, bool forImport = false}) {
122123
if (baseImporter != null) {

lib/src/importer.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,6 @@ abstract class Importer extends AsyncImporter {
3636
ImporterResult load(Uri url);
3737

3838
DateTime modificationTime(Uri url) => DateTime.now();
39+
40+
bool couldCanonicalize(Uri url, Uri canonicalUrl) => true;
3941
}

lib/src/importer/async.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,13 @@ abstract class AsyncImporter {
107107
/// If this throws an exception, the exception is ignored and the current time
108108
/// is used as the modification time.
109109
FutureOr<DateTime> modificationTime(Uri url) => DateTime.now();
110+
111+
/// Without accessing the filesystem, returns whether or not passing [url] to
112+
/// [canonicalize] could possibly return [canonicalUrl].
113+
///
114+
/// This is expected to be very efficient, and subclasses are allowed to
115+
/// return false positives if it would be inefficient to determine whether
116+
/// [url] would actually resolve to [canonicalUrl]. Subclasses are not allowed
117+
/// to return false negatives.
118+
FutureOr<bool> couldCanonicalize(Uri url, Uri canonicalUrl) => true;
110119
}

lib/src/importer/filesystem.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,19 @@ class FilesystemImporter extends Importer {
3939

4040
DateTime modificationTime(Uri url) => io.modificationTime(p.fromUri(url));
4141

42+
bool couldCanonicalize(Uri url, Uri canonicalUrl) {
43+
if (url.scheme != 'file' && url.scheme != '') return false;
44+
if (canonicalUrl.scheme != 'file') return false;
45+
46+
var basename = p.url.basename(url.path);
47+
var canonicalBasename = p.url.basename(canonicalUrl.path);
48+
if (!basename.startsWith("_") && canonicalBasename.startsWith("_")) {
49+
canonicalBasename = canonicalBasename.substring(1);
50+
}
51+
52+
return basename == canonicalBasename ||
53+
basename == p.url.withoutExtension(canonicalBasename);
54+
}
55+
4256
String toString() => _loadPath;
4357
}

lib/src/importer/no_op.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'result.dart';
1212
class NoOpImporter extends Importer {
1313
Uri canonicalize(Uri url) => null;
1414
ImporterResult load(Uri url) => null;
15+
bool couldCanonicalize(Uri url, Uri canonicalUrl) => false;
1516

1617
String toString() => "(unknown)";
1718
}

lib/src/importer/package.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,9 @@ class PackageImporter extends Importer {
4747
DateTime modificationTime(Uri url) =>
4848
_filesystemImporter.modificationTime(url);
4949

50+
bool couldCanonicalize(Uri url, Uri canonicalUrl) =>
51+
(url.scheme == 'file' || url.scheme == 'package' || url.scheme == '') &&
52+
_filesystemImporter.couldCanonicalize(Uri(path: url.path), canonicalUrl);
53+
5054
String toString() => "package:...";
5155
}

0 commit comments

Comments
 (0)