@@ -247,7 +247,6 @@ class _ListTreeNode {
247247 /// its children.
248248 bool get _isIntermediate {
249249 if (_validator != null ) return false ;
250- if (! _caseSensitive) return false ;
251250 return children.keys.every ((sequence) =>
252251 sequence.nodes.length == 1 && sequence.nodes.first is LiteralNode );
253252 }
@@ -318,11 +317,10 @@ class _ListTreeNode {
318317 .where ((entity) => _matches (p.relative (entity.path, from: dir)));
319318 }
320319
321- var resultGroup = new StreamGroup <FileSystemEntity >();
322-
323320 // Don't spawn extra [Directory.list] calls when we already know exactly
324321 // which subdirectories we're interested in.
325- if (_isIntermediate) {
322+ if (_isIntermediate && _caseSensitive) {
323+ var resultGroup = new StreamGroup <FileSystemEntity >();
326324 children.forEach ((sequence, child) {
327325 resultGroup.add (child.list (
328326 p.join (dir, (sequence.nodes.single as LiteralNode ).text),
@@ -332,35 +330,65 @@ class _ListTreeNode {
332330 return resultGroup.stream;
333331 }
334332
335- var resultController = new StreamController <FileSystemEntity >(sync : true );
336- resultGroup.add (resultController.stream);
337- new Directory (dir).list (followLinks: followLinks).listen ((entity) {
338- var basename = p.relative (entity.path, from: dir);
339- if (_matches (basename)) resultController.add (entity);
340-
341- children.forEach ((sequence, child) {
342- if (entity is ! Directory ) return ;
343- if (! sequence.matches (basename)) return ;
344- var stream = child.list (p.join (dir, basename), followLinks: followLinks)
345- .handleError ((_) {}, test: (error) {
346- // Ignore errors from directories not existing. We do this here so
347- // that we only ignore warnings below wild cards. For example, the
348- // glob "foo/bar/*/baz" should fail if "foo/bar" doesn't exist but
349- // succeed if "foo/bar/qux/baz" doesn't exist.
350- return error is FileSystemException &&
351- (error.osError.errorCode == _ENOENT ||
352- error.osError.errorCode == _ENOENT_WIN );
353- });
354- resultGroup.add (stream);
355- });
356- },
357- onError: resultController.addError,
358- onDone: () {
359- resultController.close ();
360- resultGroup.close ();
333+ return StreamCompleter .fromFuture (() async {
334+ var entities = await new Directory (dir)
335+ .list (followLinks: followLinks).toList ();
336+ await _validateIntermediateChildrenAsync (dir, entities);
337+
338+ var resultGroup = new StreamGroup <FileSystemEntity >();
339+ var resultController = new StreamController <FileSystemEntity >(sync : true );
340+ resultGroup.add (resultController.stream);
341+ for (var entity in entities) {
342+ var basename = p.relative (entity.path, from: dir);
343+ if (_matches (basename)) resultController.add (entity);
344+
345+ children.forEach ((sequence, child) {
346+ if (entity is ! Directory ) return ;
347+ if (! sequence.matches (basename)) return ;
348+ var stream = child
349+ .list (p.join (dir, basename), followLinks: followLinks)
350+ .handleError ((_) {}, test: (error) {
351+ // Ignore errors from directories not existing. We do this here so
352+ // that we only ignore warnings below wild cards. For example, the
353+ // glob "foo/bar/*/baz" should fail if "foo/bar" doesn't exist but
354+ // succeed if "foo/bar/qux/baz" doesn't exist.
355+ return error is FileSystemException &&
356+ (error.osError.errorCode == _ENOENT ||
357+ error.osError.errorCode == _ENOENT_WIN );
358+ });
359+ resultGroup.add (stream);
361360 });
361+ }
362+ resultController.close ();
363+ resultGroup.close ();
364+ return resultGroup.stream;
365+ }());
366+ }
362367
363- return resultGroup.stream;
368+ /// If this is a case-insensitive list, validates that all intermediate
369+ /// children (according to [_isIntermediate] ) match at least one entity in
370+ /// [entities] .
371+ ///
372+ /// This ensures that listing "foo/bar/*" fails on case-sensitive systems if
373+ /// "foo/bar" doesn't exist.
374+ Future _validateIntermediateChildrenAsync (String dir,
375+ List <FileSystemEntity > entities) async {
376+ if (_caseSensitive) return ;
377+
378+ for (var sequence in children.keys) {
379+ var child = children[sequence];
380+ if (! child._isIntermediate) continue ;
381+ if (entities.any ((entity) =>
382+ sequence.matches (p.relative (entity.path, from: dir)))) {
383+ continue ;
384+ }
385+
386+ // We know this will fail, we're just doing it to force dart:io to emit
387+ // the exception it would if we were listing case-sensitively.
388+ await child
389+ .list (p.join (dir, (sequence.nodes.single as LiteralNode ).text))
390+ .toList ();
391+ }
364392 }
365393
366394 /// Synchronously lists all entities within [dir] matching this node or its
@@ -377,16 +405,18 @@ class _ListTreeNode {
377405
378406 // Don't spawn extra [Directory.listSync] calls when we already know exactly
379407 // which subdirectories we're interested in.
380- if (_isIntermediate) {
408+ if (_isIntermediate && _caseSensitive ) {
381409 return children.keys.expand ((sequence) {
382410 return children[sequence].listSync (
383411 p.join (dir, (sequence.nodes.single as LiteralNode ).text),
384412 followLinks: followLinks);
385413 });
386414 }
387415
388- return new Directory (dir).listSync (followLinks: followLinks)
389- .expand ((entity) {
416+ var entities = new Directory (dir).listSync (followLinks: followLinks);
417+ _validateIntermediateChildrenSync (dir, entities);
418+
419+ return entities.expand ((entity) {
390420 var entities = < FileSystemEntity > [];
391421 var basename = p.relative (entity.path, from: dir);
392422 if (_matches (basename)) entities.add (entity);
@@ -416,6 +446,32 @@ class _ListTreeNode {
416446 });
417447 }
418448
449+ /// If this is a case-insensitive list, validates that all intermediate
450+ /// children (according to [_isIntermediate] ) match at least one entity in
451+ /// [entities] .
452+ ///
453+ /// This ensures that listing "foo/bar/*" fails on case-sensitive systems if
454+ /// "foo/bar" doesn't exist.
455+ void _validateIntermediateChildrenSync (String dir,
456+ List <FileSystemEntity > entities) {
457+ if (_caseSensitive) return ;
458+
459+ children.forEach ((sequence, child) {
460+ if (! child._isIntermediate) return ;
461+ if (entities.any ((entity) =>
462+ sequence.matches (p.relative (entity.path, from: dir)))) {
463+ return ;
464+ }
465+
466+ // If there are no [entities] that match [sequence], manually list the
467+ // directory to force `dart:io` to throw an error. This allows us to
468+ // ensure that listing "foo/bar/*" fails on case-sensitive systems if
469+ // "foo/bar" doesn't exist.
470+ child
471+ .listSync (p.join (dir, (sequence.nodes.single as LiteralNode ).text));
472+ });
473+ }
474+
419475 /// Returns whether the native [path] matches [_validator] .
420476 bool _matches (String path) {
421477 if (_validator == null ) return false ;
0 commit comments