Update to null safety, change list* apis (#39)
Also renames the list* apis on Glob to listFileSystem* and they now require a FileSystem object from package:file.
There is a new convenience import, package:glob/list_local_fs.dart which provides the old methods as extensions, and automatically passes a LocalFileSystem. Most migrations should be just importing this new file.
Note that when calling listFileSystem if the file system has changed then I chose to just throw away the old _ListTree. We could cache these by file system but I worry about memory leaks if people pass new instances of file systems repeatedly on the same glob.
diff --git a/.travis.yml b/.travis.yml
index 9adf49d..5a9725d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,7 +2,6 @@
dart:
- dev
- - 2.2.0
# See https://docs.travis-ci.com/user/languages/dart/ for details.
dart_task:
@@ -11,9 +10,6 @@
jobs:
include:
- - dart: 2.2.0
- dart_task:
- test: -p node
- dart: dev
dart_task:
test: -p node
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1d60efb..54ca50f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,16 @@
+## 2.0.0-nullsafety.dev
+
+* Migrate to null_safety
+
+### Breaking Change
+
+The `list*` apis on `Glob` have been renamed to `listFileSystem*` and they now
+require a `FileSystem` object from `package:file`.
+
+There is a new convenience import, `package:glob/list_local_fs.dart` which
+provides the old methods as extensions, and automatically passes a
+`LocalFileSystem`.
+
## 1.2.0
* Support running on Node.js.
diff --git a/lib/glob.dart b/lib/glob.dart
index 75bedd6..c241057 100644
--- a/lib/glob.dart
+++ b/lib/glob.dart
@@ -2,10 +2,11 @@
// 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 'package:file/file.dart';
+import 'package:file/memory.dart';
import 'package:path/path.dart' as p;
import 'src/ast.dart';
-import 'src/io.dart';
import 'src/list_tree.dart';
import 'src/parser.dart';
import 'src/utils.dart';
@@ -47,25 +48,34 @@
/// The parsed AST of the glob.
final AstNode _ast;
- ListTree _listTree;
+ /// The underlying object used to implement [list] and [listSync].
+ ///
+ /// This should not be read directly outside of [_listTreeForFileSystem].
+ ListTree? _listTree;
+
+ /// Keeps track of the previous file system used. If this changes then the
+ /// [_listTree] must be invalidated.
+ ///
+ /// This is handled inside of [_listTreeForFileSystem].
+ FileSystem? _previousFileSystem;
/// Whether [context]'s current directory is absolute.
bool get _contextIsAbsolute =>
_contextIsAbsoluteCache ??= context.isAbsolute(context.current);
- bool _contextIsAbsoluteCache;
+ bool? _contextIsAbsoluteCache;
/// Whether [pattern] could match absolute paths.
bool get _patternCanMatchAbsolute =>
_patternCanMatchAbsoluteCache ??= _ast.canMatchAbsolute;
- bool _patternCanMatchAbsoluteCache;
+ bool? _patternCanMatchAbsoluteCache;
/// Whether [pattern] could match relative paths.
bool get _patternCanMatchRelative =>
_patternCanMatchRelativeCache ??= _ast.canMatchRelative;
- bool _patternCanMatchRelativeCache;
+ bool? _patternCanMatchRelativeCache;
/// Returns [contents] with characters that are meaningful in globs
/// backslash-escaped.
@@ -85,7 +95,7 @@
/// regardless of case. This defaults to `false` when [context] is Windows and
/// `true` otherwise.
factory Glob(String pattern,
- {p.Context context, bool recursive = false, bool caseSensitive}) {
+ {p.Context? context, bool recursive = false, bool? caseSensitive}) {
context ??= p.context;
caseSensitive ??= context.style == p.Style.windows ? false : true;
if (recursive) pattern += '{,/**}';
@@ -96,7 +106,8 @@
Glob._(this.pattern, this.context, this._ast, this.recursive);
- /// Lists all [FileSystemEntity]s beneath [root] that match the glob.
+ /// Lists all [FileSystemEntity]s beneath [root] that match the glob in the
+ /// provided [fileSystem].
///
/// This works much like [Directory.list], but it only lists directories that
/// could contain entities that match the glob. It provides no guarantees
@@ -106,18 +117,19 @@
/// [root] defaults to the current working directory.
///
/// [followLinks] works the same as for [Directory.list].
- Stream<FileSystemEntity> list({String root, bool followLinks = true}) {
+ Stream<FileSystemEntity> listFileSystem(FileSystem fileSystem,
+ {String? root, bool followLinks = true}) {
if (context.style != p.style) {
throw StateError("Can't list glob \"$this\"; it matches "
'${context.style} paths, but this platform uses ${p.style} paths.');
}
- _listTree ??= ListTree(_ast);
- return _listTree.list(root: root, followLinks: followLinks);
+ return _listTreeForFileSystem(fileSystem)
+ .list(root: root, followLinks: followLinks);
}
/// Synchronously lists all [FileSystemEntity]s beneath [root] that match the
- /// glob.
+ /// glob in the provided [fileSystem].
///
/// This works much like [Directory.listSync], but it only lists directories
/// that could contain entities that match the glob. It provides no guarantees
@@ -127,21 +139,22 @@
/// [root] defaults to the current working directory.
///
/// [followLinks] works the same as for [Directory.list].
- List<FileSystemEntity> listSync({String root, bool followLinks = true}) {
+ List<FileSystemEntity> listFileSystemSync(FileSystem fileSystem,
+ {String? root, bool followLinks = true}) {
if (context.style != p.style) {
throw StateError("Can't list glob \"$this\"; it matches "
'${context.style} paths, but this platform uses ${p.style} paths.');
}
- _listTree ??= ListTree(_ast);
- return _listTree.listSync(root: root, followLinks: followLinks);
+ return _listTreeForFileSystem(fileSystem)
+ .listSync(root: root, followLinks: followLinks);
}
/// Returns whether this glob matches [path].
bool matches(String path) => matchAsPrefix(path) != null;
@override
- Match matchAsPrefix(String path, [int start = 0]) {
+ Match? matchAsPrefix(String path, [int start = 0]) {
// Globs are like anchored RegExps in that they only match entire paths, so
// if the match starts anywhere after the first character it can't succeed.
if (start != 0) return null;
@@ -172,4 +185,18 @@
@override
String toString() => pattern;
+
+ /// Handles getting a possibly cached [ListTree] for a [fileSystem].
+ ListTree _listTreeForFileSystem(FileSystem fileSystem) {
+ // Don't use cached trees for in memory file systems to avoid memory leaks.
+ if (fileSystem is MemoryFileSystem) return ListTree(_ast, fileSystem);
+
+ // Throw away our cached `_listTree` if the file system is different.
+ if (fileSystem != _previousFileSystem) {
+ _listTree = null;
+ _previousFileSystem = fileSystem;
+ }
+
+ return _listTree ??= ListTree(_ast, fileSystem);
+ }
}
diff --git a/lib/list_local_fs.dart b/lib/list_local_fs.dart
new file mode 100644
index 0000000..946596a
--- /dev/null
+++ b/lib/list_local_fs.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// 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 'package:file/file.dart';
+import 'package:file/local.dart';
+import 'package:glob/glob.dart';
+
+/// Platform specific extensions for where `dart:io` exists, which use the
+/// local file system.
+extension ListLocalFileSystem on Glob {
+ /// Convenience method for [Glob.listFileSystem] which uses the local file
+ /// system.
+ Stream<FileSystemEntity> list({String? root, bool followLinks = true}) =>
+ listFileSystem(const LocalFileSystem(),
+ root: root, followLinks: followLinks);
+
+ /// Convenience method for [Glob.listFileSystemSync] which uses the local
+ /// file system.
+ List<FileSystemEntity> listSync({String? root, bool followLinks = true}) =>
+ listFileSystemSync(const LocalFileSystem(),
+ root: root, followLinks: followLinks);
+}
diff --git a/lib/src/ast.dart b/lib/src/ast.dart
index 1ee0fcf..ac283d5 100644
--- a/lib/src/ast.dart
+++ b/lib/src/ast.dart
@@ -12,7 +12,7 @@
/// A node in the abstract syntax tree for a glob.
abstract class AstNode {
/// The cached regular expression that this AST was compiled into.
- RegExp _regExp;
+ RegExp? _regExp;
/// Whether this node matches case-sensitively or not.
final bool caseSensitive;
@@ -43,10 +43,9 @@
], caseSensitive: caseSensitive);
/// Returns whether this glob matches [string].
- bool matches(String string) {
- _regExp ??= RegExp('^${_toRegExp()}\$', caseSensitive: caseSensitive);
- return _regExp.hasMatch(string);
- }
+ bool matches(String string) =>
+ (_regExp ??= RegExp('^${_toRegExp()}\$', caseSensitive: caseSensitive))
+ .hasMatch(string);
/// Subclasses should override this to return a regular expression component.
String _toRegExp();
@@ -97,9 +96,7 @@
}
combined[combined.length - 1] = LiteralNode(
- // TODO(nweiz): Avoid casting when sdk#25565 is fixed.
- (combined.last as LiteralNode).text +
- (node as LiteralNode).text,
+ (combined.last as LiteralNode).text + node.text,
caseSensitive: caseSensitive);
return combined;
}),
@@ -120,17 +117,16 @@
/// glob.
List<SequenceNode> split(p.Context context) {
var componentsToReturn = <SequenceNode>[];
- List<AstNode> currentComponent;
+ List<AstNode>? currentComponent;
void addNode(AstNode node) {
- currentComponent ??= [];
- currentComponent.add(node);
+ (currentComponent ??= []).add(node);
}
void finishComponent() {
if (currentComponent == null) return;
componentsToReturn
- .add(SequenceNode(currentComponent, caseSensitive: caseSensitive));
+ .add(SequenceNode(currentComponent!, caseSensitive: caseSensitive));
currentComponent = null;
}
@@ -140,14 +136,12 @@
continue;
}
- // TODO(nweiz): Avoid casting when sdk#25565 is fixed.
- var literal = node as LiteralNode;
- if (!literal.text.contains('/')) {
- addNode(literal);
+ if (!node.text.contains('/')) {
+ addNode(node);
continue;
}
- var text = literal.text;
+ var text = node.text;
if (context.style == p.Style.windows) text = text.replaceAll('/', '\\');
Iterable<String> components = context.split(text);
@@ -182,7 +176,7 @@
// For the final component, only end its sequence (by adding a new empty
// sequence) if it ends with a separator.
addNode(LiteralNode(components.last, caseSensitive: caseSensitive));
- if (literal.text.endsWith('/')) finishComponent();
+ if (node.text.endsWith('/')) finishComponent();
}
finishComponent();
@@ -294,7 +288,8 @@
/// Whether this range was negated.
final bool negated;
- RangeNode(Iterable<Range> ranges, {this.negated, bool caseSensitive = true})
+ RangeNode(Iterable<Range> ranges,
+ {required this.negated, bool caseSensitive = true})
: ranges = ranges.toSet(),
super._(caseSensitive);
@@ -411,19 +406,19 @@
/// The path context for the glob.
///
/// This is used to determine whether this could match an absolute path.
- final p.Context _context;
+ final p.Context? _context;
@override
bool get canMatchAbsolute {
var nativeText =
- _context.style == p.Style.windows ? text.replaceAll('/', '\\') : text;
- return _context.isAbsolute(nativeText);
+ _context!.style == p.Style.windows ? text.replaceAll('/', '\\') : text;
+ return _context!.isAbsolute(nativeText);
}
@override
bool get canMatchRelative => !canMatchAbsolute;
- LiteralNode(this.text, {p.Context context, bool caseSensitive = true})
+ LiteralNode(this.text, {p.Context? context, bool caseSensitive = true})
: _context = context,
super._(caseSensitive);
diff --git a/lib/src/io.dart b/lib/src/io.dart
deleted file mode 100644
index 20e532b..0000000
--- a/lib/src/io.dart
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
-// 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.
-
-// These libraries don't expose *exactly* the same API, but they overlap in all
-// the cases we care about.
-export 'io_export.dart'
- // We don't actually support the web - exporting dart:io gives a reasonably
- // clear signal to users about that since it doesn't exist.
- if (dart.library.html) 'io_export.dart'
- if (dart.library.js) 'package:node_io/node_io.dart';
diff --git a/lib/src/io_export.dart b/lib/src/io_export.dart
deleted file mode 100644
index 7f339cf..0000000
--- a/lib/src/io_export.dart
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
-// 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.
-
-/// This library exists only to satisfy build_runner, which doesn't allow
-/// sdk libraries to be conditional imported or exported directly.
-library glob.src.io_export;
-
-export 'dart:io';
diff --git a/lib/src/list_tree.dart b/lib/src/list_tree.dart
index 82f12c5..5d362a9 100644
--- a/lib/src/list_tree.dart
+++ b/lib/src/list_tree.dart
@@ -5,11 +5,11 @@
import 'dart:async';
import 'package:async/async.dart';
+import 'package:file/file.dart';
import 'package:path/path.dart' as p;
import 'package:pedantic/pedantic.dart';
import 'ast.dart';
-import 'io.dart';
import 'utils.dart';
/// The errno for a file or directory not existing on Mac and Linux.
@@ -56,19 +56,26 @@
/// A map from filesystem roots to the list tree for those roots.
///
/// A relative glob will use `.` as its root.
- final _trees = <String, _ListTreeNode>{};
+ final Map<String, _ListTreeNode> _trees;
/// Whether paths listed might overlap.
///
/// If they do, we need to filter out overlapping paths.
- bool _canOverlap;
+ final bool _canOverlap;
- ListTree(AstNode glob) {
+ /// The file system to operate on.
+ final FileSystem _fileSystem;
+
+ ListTree._(this._trees, this._fileSystem)
+ : _canOverlap = _computeCanOverlap(_trees);
+
+ factory ListTree(AstNode glob, FileSystem fileSystem) {
// The first step in constructing a tree from the glob is to simplify the
// problem by eliminating options. [glob.flattenOptions] bubbles all options
// (and certain ranges) up to the top level of the glob so we can deal with
// them one at a time.
var options = glob.flattenOptions();
+ var trees = <String, _ListTreeNode>{};
for (var option in options.options) {
// Since each option doesn't include its own options, we can safely split
@@ -81,7 +88,8 @@
// root's just ".".
if (firstNode is LiteralNode) {
var text = firstNode.text;
- if (Platform.isWindows) text.replaceAll('/', '\\');
+ // Platform agnostic way of checking for Windows without `dart:io`.
+ if (p.context == p.windows) text.replaceAll('/', '\\');
if (p.isAbsolute(text)) {
// If the path is absolute, the root should be the only thing in the
// first component.
@@ -91,21 +99,22 @@
}
}
- _addGlob(root, components);
+ _addGlob(root, components, trees);
}
- _canOverlap = _computeCanOverlap();
+ return ListTree._(trees, fileSystem);
}
/// Add the glob represented by [components] to the tree under [root].
- void _addGlob(String root, List<SequenceNode> components) {
+ static void _addGlob(String root, List<SequenceNode> components,
+ Map<String, _ListTreeNode> trees) {
// The first [parent] represents the root directory itself. It may be null
// here if this is the first option with this particular [root]. If so,
// we'll create it below.
//
// As we iterate through [components], [parent] will be set to
// progressively more nested nodes.
- var parent = _trees[root];
+ var parent = trees[root];
for (var i = 0; i < components.length; i++) {
var component = components[i];
var recursive = component.nodes.any((node) => node is DoubleStarNode);
@@ -138,42 +147,46 @@
// to [parent]'s children and not its validator. Since we process
// each option's components separately, the same component is never
// both a validator and a child.
- if (!parent.children.containsKey(component)) {
- parent.children[component] = _ListTreeNode();
+ var children = parent.children!;
+ if (!children.containsKey(component)) {
+ children[component] = _ListTreeNode();
}
- parent = parent.children[component];
+ parent = children[component];
}
} else if (recursive) {
- _trees[root] = _ListTreeNode.recursive(_join(components.sublist(i)));
+ trees[root] = _ListTreeNode.recursive(_join(components.sublist(i)));
return;
} else if (complete) {
- _trees[root] = _ListTreeNode()..addOption(component);
+ trees[root] = _ListTreeNode()..addOption(component);
} else {
- _trees[root] = _ListTreeNode();
- _trees[root].children[component] = _ListTreeNode();
- parent = _trees[root].children[component];
+ var rootNode = _ListTreeNode();
+ trees[root] = rootNode;
+ var rootChildren = rootNode.children!;
+ rootChildren[component] = _ListTreeNode();
+ parent = rootChildren[component];
}
}
}
/// Computes the value for [_canOverlap].
- bool _computeCanOverlap() {
+ static bool _computeCanOverlap(Map<String, _ListTreeNode> trees) {
// If this can list a relative path and an absolute path, the former may be
// contained within the latter.
- if (_trees.length > 1 && _trees.containsKey('.')) return true;
+ if (trees.length > 1 && trees.containsKey('.')) return true;
// Otherwise, this can only overlap if the tree beneath any given root could
// overlap internally.
- return _trees.values.any((node) => node.canOverlap);
+ return trees.values.any((node) => node.canOverlap);
}
/// List all entities that match this glob beneath [root].
- Stream<FileSystemEntity> list({String root, bool followLinks = true}) {
+ Stream<FileSystemEntity> list({String? root, bool followLinks = true}) {
root ??= '.';
var group = StreamGroup<FileSystemEntity>();
for (var rootDir in _trees.keys) {
var dir = rootDir == '.' ? root : rootDir;
- group.add(_trees[rootDir].list(dir, followLinks: followLinks));
+ group.add(
+ _trees[rootDir]!.list(dir, _fileSystem, followLinks: followLinks));
}
group.close();
@@ -186,11 +199,12 @@
}
/// Synchronosuly list all entities that match this glob beneath [root].
- List<FileSystemEntity> listSync({String root, bool followLinks = true}) {
+ List<FileSystemEntity> listSync({String? root, bool followLinks = true}) {
root ??= '.';
var result = _trees.keys.expand((rootDir) {
- var dir = rootDir == '.' ? root : rootDir;
- return _trees[rootDir].listSync(dir, followLinks: followLinks);
+ var dir = rootDir == '.' ? root! : rootDir;
+ return _trees[rootDir]!
+ .listSync(dir, _fileSystem, followLinks: followLinks);
});
if (!_canOverlap) return result.toList();
@@ -210,13 +224,13 @@
///
/// This may be `null`, indicating that this node should be listed
/// recursively.
- Map<SequenceNode, _ListTreeNode> children;
+ Map<SequenceNode, _ListTreeNode>? children;
/// This node's validator.
///
/// This determines which entities will ultimately be emitted when [list] is
/// called.
- OptionsNode _validator;
+ OptionsNode? _validator;
/// Whether this node is recursive.
///
@@ -224,10 +238,9 @@
bool get isRecursive => children == null;
bool get _caseSensitive {
- if (_validator != null) return _validator.caseSensitive;
- if (children == null) return true;
- if (children.isEmpty) return true;
- return children.keys.first.caseSensitive;
+ if (_validator != null) return _validator!.caseSensitive;
+ if (children?.isEmpty != false) return true;
+ return children!.keys.first.caseSensitive;
}
/// Whether this node doesn't itself need to be listed.
@@ -237,7 +250,7 @@
/// its children.
bool get _isIntermediate {
if (_validator != null) return false;
- return children.keys.every((sequence) =>
+ return children!.keys.every((sequence) =>
sequence.nodes.length == 1 && sequence.nodes.first is LiteralNode);
}
@@ -251,17 +264,17 @@
// If there's more than one child node and at least one of the children is
// dynamic (that is, matches more than just a literal string), there may be
// overlap.
- if (children.length > 1) {
+ if (children!.length > 1) {
// Case-insensitivity means that even literals may match multiple entries.
if (!_caseSensitive) return true;
- if (children.keys.any((sequence) =>
+ if (children!.keys.any((sequence) =>
sequence.nodes.length > 1 || sequence.nodes.single is! LiteralNode)) {
return true;
}
}
- return children.values.any((node) => node.canOverlap);
+ return children!.values.any((node) => node.canOverlap);
}
/// Creates a node with no children and no validator.
@@ -279,12 +292,12 @@
/// validator.
void makeRecursive() {
if (isRecursive) return;
- _validator = OptionsNode(children.keys.map((sequence) {
- var child = children[sequence];
- child.makeRecursive();
- return _join([sequence, child._validator]);
+ var children = this.children!;
+ _validator = OptionsNode(children.entries.map((entry) {
+ entry.value.makeRecursive();
+ return _join([entry.key, entry.value._validator!]);
}), caseSensitive: _caseSensitive);
- children = null;
+ this.children = null;
}
/// Adds [validator] to this node's existing validator.
@@ -293,7 +306,7 @@
_validator =
OptionsNode([validator], caseSensitive: validator.caseSensitive);
} else {
- _validator.options.add(validator);
+ _validator!.options.add(validator);
}
}
@@ -301,9 +314,11 @@
///
/// This may return duplicate entities. These will be filtered out in
/// [ListTree.list].
- Stream<FileSystemEntity> list(String dir, {bool followLinks = true}) {
+ Stream<FileSystemEntity> list(String dir, FileSystem fileSystem,
+ {bool followLinks = true}) {
if (isRecursive) {
- return Directory(dir)
+ return fileSystem
+ .directory(dir)
.list(recursive: true, followLinks: followLinks)
.where((entity) => _matches(p.relative(entity.path, from: dir)));
}
@@ -312,9 +327,10 @@
// which subdirectories we're interested in.
if (_isIntermediate && _caseSensitive) {
var resultGroup = StreamGroup<FileSystemEntity>();
- children.forEach((sequence, child) {
+ children!.forEach((sequence, child) {
resultGroup.add(child.list(
p.join(dir, (sequence.nodes.single as LiteralNode).text),
+ fileSystem,
followLinks: followLinks));
});
resultGroup.close();
@@ -322,9 +338,11 @@
}
return StreamCompleter.fromFuture(() async {
- var entities =
- await Directory(dir).list(followLinks: followLinks).toList();
- await _validateIntermediateChildrenAsync(dir, entities);
+ var entities = await fileSystem
+ .directory(dir)
+ .list(followLinks: followLinks)
+ .toList();
+ await _validateIntermediateChildrenAsync(dir, entities, fileSystem);
var resultGroup = StreamGroup<FileSystemEntity>();
var resultController = StreamController<FileSystemEntity>(sync: true);
@@ -333,19 +351,19 @@
var basename = p.relative(entity.path, from: dir);
if (_matches(basename)) resultController.add(entity);
- children.forEach((sequence, child) {
+ children!.forEach((sequence, child) {
if (entity is! Directory) return;
if (!sequence.matches(basename)) return;
var stream = child
- .list(p.join(dir, basename), followLinks: followLinks)
+ .list(p.join(dir, basename), fileSystem, followLinks: followLinks)
.handleError((_) {}, test: (error) {
// Ignore errors from directories not existing. We do this here so
// that we only ignore warnings below wild cards. For example, the
// glob "foo/bar/*/baz" should fail if "foo/bar" doesn't exist but
// succeed if "foo/bar/qux/baz" doesn't exist.
return error is FileSystemException &&
- (error.osError.errorCode == _enoent ||
- error.osError.errorCode == _enoentWin);
+ (error.osError!.errorCode == _enoent ||
+ error.osError!.errorCode == _enoentWin);
});
resultGroup.add(stream);
});
@@ -362,12 +380,13 @@
///
/// This ensures that listing "foo/bar/*" fails on case-sensitive systems if
/// "foo/bar" doesn't exist.
- Future _validateIntermediateChildrenAsync(
- String dir, List<FileSystemEntity> entities) async {
+ Future _validateIntermediateChildrenAsync(String dir,
+ List<FileSystemEntity> entities, FileSystem fileSystem) async {
if (_caseSensitive) return;
- for (var sequence in children.keys) {
- var child = children[sequence];
+ for (var entry in children!.entries) {
+ var child = entry.value;
+ var sequence = entry.key;
if (!child._isIntermediate) continue;
if (entities.any(
(entity) => sequence.matches(p.relative(entity.path, from: dir)))) {
@@ -377,7 +396,8 @@
// We know this will fail, we're just doing it to force dart:io to emit
// the exception it would if we were listing case-sensitively.
await child
- .list(p.join(dir, (sequence.nodes.single as LiteralNode).text))
+ .list(p.join(dir, (sequence.nodes.single as LiteralNode).text),
+ fileSystem)
.toList();
}
}
@@ -387,9 +407,11 @@
///
/// This may return duplicate entities. These will be filtered out in
/// [ListTree.listSync].
- Iterable<FileSystemEntity> listSync(String dir, {bool followLinks = true}) {
+ Iterable<FileSystemEntity> listSync(String dir, FileSystem fileSystem,
+ {bool followLinks = true}) {
if (isRecursive) {
- return Directory(dir)
+ return fileSystem
+ .directory(dir)
.listSync(recursive: true, followLinks: followLinks)
.where((entity) => _matches(p.relative(entity.path, from: dir)));
}
@@ -397,15 +419,18 @@
// Don't spawn extra [Directory.listSync] calls when we already know exactly
// which subdirectories we're interested in.
if (_isIntermediate && _caseSensitive) {
- return children.keys.expand((sequence) {
- return children[sequence].listSync(
+ return children!.entries.expand((entry) {
+ var sequence = entry.key;
+ var child = entry.value;
+ return child.listSync(
p.join(dir, (sequence.nodes.single as LiteralNode).text),
+ fileSystem,
followLinks: followLinks);
});
}
- var entities = Directory(dir).listSync(followLinks: followLinks);
- _validateIntermediateChildrenSync(dir, entities);
+ var entities = fileSystem.directory(dir).listSync(followLinks: followLinks);
+ _validateIntermediateChildrenSync(dir, entities, fileSystem);
return entities.expand((entity) {
var entities = <FileSystemEntity>[];
@@ -413,20 +438,21 @@
if (_matches(basename)) entities.add(entity);
if (entity is! Directory) return entities;
- entities.addAll(children.keys
+ entities.addAll(children!.keys
.where((sequence) => sequence.matches(basename))
.expand((sequence) {
try {
- return children[sequence]
- .listSync(p.join(dir, basename), followLinks: followLinks)
+ return children![sequence]!
+ .listSync(p.join(dir, basename), fileSystem,
+ followLinks: followLinks)
.toList();
} on FileSystemException catch (error) {
// Ignore errors from directories not existing. We do this here so
// that we only ignore warnings below wild cards. For example, the
// glob "foo/bar/*/baz" should fail if "foo/bar" doesn't exist but
// succeed if "foo/bar/qux/baz" doesn't exist.
- if (error.osError.errorCode == _enoent ||
- error.osError.errorCode == _enoentWin) {
+ if (error.osError!.errorCode == _enoent ||
+ error.osError!.errorCode == _enoentWin) {
return const [];
} else {
rethrow;
@@ -445,10 +471,10 @@
/// This ensures that listing "foo/bar/*" fails on case-sensitive systems if
/// "foo/bar" doesn't exist.
void _validateIntermediateChildrenSync(
- String dir, List<FileSystemEntity> entities) {
+ String dir, List<FileSystemEntity> entities, FileSystem fileSystem) {
if (_caseSensitive) return;
- children.forEach((sequence, child) {
+ children!.forEach((sequence, child) {
if (!child._isIntermediate) return;
if (entities.any(
(entity) => sequence.matches(p.relative(entity.path, from: dir)))) {
@@ -459,15 +485,14 @@
// directory to force `dart:io` to throw an error. This allows us to
// ensure that listing "foo/bar/*" fails on case-sensitive systems if
// "foo/bar" doesn't exist.
- child.listSync(p.join(dir, (sequence.nodes.single as LiteralNode).text));
+ child.listSync(
+ p.join(dir, (sequence.nodes.single as LiteralNode).text), fileSystem);
});
}
/// Returns whether the native [path] matches [_validator].
- bool _matches(String path) {
- if (_validator == null) return false;
- return _validator.matches(toPosixPath(p.context, path));
- }
+ bool _matches(String path) =>
+ _validator?.matches(toPosixPath(p.context, path)) ?? false;
@override
String toString() => '($_validator) $children';
diff --git a/lib/src/parser.dart b/lib/src/parser.dart
index 20ade31..e76f7c5 100644
--- a/lib/src/parser.dart
+++ b/lib/src/parser.dart
@@ -69,7 +69,7 @@
/// Tries to parse a [StarNode] or a [DoubleStarNode].
///
/// Returns `null` if there's not one to parse.
- AstNode _parseStar() {
+ AstNode? _parseStar() {
if (!_scanner.scan('*')) return null;
return _scanner.scan('*')
? DoubleStarNode(_context, caseSensitive: _caseSensitive)
@@ -79,7 +79,7 @@
/// Tries to parse an [AnyCharNode].
///
/// Returns `null` if there's not one to parse.
- AstNode _parseAnyChar() {
+ AstNode? _parseAnyChar() {
if (!_scanner.scan('?')) return null;
return AnyCharNode(caseSensitive: _caseSensitive);
}
@@ -87,7 +87,7 @@
/// Tries to parse an [RangeNode].
///
/// Returns `null` if there's not one to parse.
- AstNode _parseRange() {
+ AstNode? _parseRange() {
if (!_scanner.scan('[')) return null;
if (_scanner.matches(']')) _scanner.error('unexpected "]".');
var negated = _scanner.scan('!') || _scanner.scan('^');
@@ -134,7 +134,7 @@
/// Tries to parse an [OptionsNode].
///
/// Returns `null` if there's not one to parse.
- AstNode _parseOptions() {
+ AstNode? _parseOptions() {
if (!_scanner.scan('{')) return null;
if (_scanner.matches('}')) _scanner.error('unexpected "}".');
@@ -157,12 +157,12 @@
var regExp = RegExp(inOptions ? r'[^*{[?\\}\],()]*' : r'[^*{[?\\}\]()]*');
_scanner.scan(regExp);
- var buffer = StringBuffer()..write(_scanner.lastMatch[0]);
+ var buffer = StringBuffer()..write(_scanner.lastMatch![0]);
while (_scanner.scan('\\')) {
buffer.writeCharCode(_scanner.readChar());
_scanner.scan(regExp);
- buffer.write(_scanner.lastMatch[0]);
+ buffer.write(_scanner.lastMatch![0]);
}
for (var char in const [']', '(', ')']) {
diff --git a/pubspec.yaml b/pubspec.yaml
index 7454ccd..54bef3f 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,21 +1,30 @@
name: glob
-version: 1.2.1-dev
+version: 2.0.0-nullsafety.dev
description: Bash-style filename globbing.
author: Dart Team <misc@dartlang.org>
homepage: https://github.com/dart-lang/glob
+# Can't be published until we confirm we can land this change internally and in
+# the sdk.
+publish_to: none
+
environment:
- sdk: '>=2.2.0 <3.0.0'
+ sdk: '>=2.12.0-0 <3.0.0'
dependencies:
- async: '>=1.2.0 <3.0.0'
- collection: ^1.1.0
- node_io: ^1.0.0
- path: ^1.3.0
- pedantic: ^1.2.0
- string_scanner: '>=0.1.0 <2.0.0'
+ async: ^2.5.0-nullsafety
+ collection: ^1.15.0-nullsafety
+ file: ^6.0.0-nullsafety
+ path: ^1.8.0-nullsafety
+ pedantic: ^1.10.0-nullsafety
+ string_scanner: ^1.1.0-nullsafety
dev_dependencies:
- test: ^1.6.0
- test_descriptor: ^1.0.0
+ test: ^1.16.0-nullsafety.9
+ test_descriptor: ^2.0.0-nullsafety
+
+# Required to get a version solve due to cyclic deps
+dependency_overrides:
+ analyzer: ">=0.36.0 <0.41.0"
+ test_core: ^0.3.12-nullsafety.9
diff --git a/test/glob_test.dart b/test/glob_test.dart
index 281a413..85c3fac 100644
--- a/test/glob_test.dart
+++ b/test/glob_test.dart
@@ -63,7 +63,7 @@
group('GlobMatch', () {
var glob = Glob('foo*');
- var match = glob.matchAsPrefix('foobar');
+ var match = glob.matchAsPrefix('foobar')!;
test('returns the string as input', () {
expect(match.input, equals('foobar'));
diff --git a/test/list_test.dart b/test/list_test.dart
index 5fac113..512d901 100644
--- a/test/list_test.dart
+++ b/test/list_test.dart
@@ -7,6 +7,7 @@
import 'dart:io';
import 'package:glob/glob.dart';
+import 'package:glob/list_local_fs.dart';
import 'package:glob/src/utils.dart';
import 'package:path/path.dart' as p;
import 'package:test/test.dart';
@@ -314,7 +315,7 @@
}
typedef ListFn = FutureOr<List<String>> Function(String glob,
- {bool recursive, bool followLinks, bool caseSensitive});
+ {bool recursive, bool followLinks, bool? caseSensitive});
/// Runs [callback] in two groups with two values of [listFn]: one that uses
/// [Glob.list], one that uses [Glob.listSync].