Merge branch 'v1_x'
diff --git a/pkgs/glob/.github/workflows/test-package.yml b/pkgs/glob/.github/workflows/test-package.yml new file mode 100644 index 0000000..df0041d --- /dev/null +++ b/pkgs/glob/.github/workflows/test-package.yml
@@ -0,0 +1,67 @@ +name: Dart CI + +on: + # Run on PRs and pushes to the default branch. + push: + branches: [ master ] + pull_request: + branches: [ master ] + schedule: + - cron: "0 0 * * 0" + +env: + PUB_ENVIRONMENT: bot.github + +jobs: + # Check code formatting and static analysis on a single OS (linux) + # against Dart dev. + analyze: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sdk: [dev] + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v1.0 + with: + channel: ${{ matrix.sdk }} + - id: install + name: Install dependencies + run: dart pub get + - name: Check formatting + run: dart format --output=none --set-exit-if-changed . + if: always() && steps.install.outcome == 'success' + - name: Analyze code + run: dart analyze --fatal-infos + if: always() && steps.install.outcome == 'success' + + # Run tests on a matrix consisting of two dimensions: + # 1. OS: ubuntu-latest, (macos-latest, windows-latest) + # 2. release channel: dev + test: + needs: analyze + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + # Add macos-latest and/or windows-latest if relevant for this package. + os: [ubuntu-latest] + sdk: [2.15.0, dev] + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v1.0 + with: + channel: ${{ matrix.sdk }} + - id: install + name: Install dependencies + run: dart pub get + - name: Run VM tests + run: dart test --platform vm + if: always() && steps.install.outcome == 'success' + - name: Run Chrome tests + run: dart test --platform chrome + if: always() && steps.install.outcome == 'success' + - name: Run Node tests + run: dart test --platform node + if: always() && steps.install.outcome == 'success'
diff --git a/pkgs/glob/.test_config b/pkgs/glob/.test_config deleted file mode 100644 index 531426a..0000000 --- a/pkgs/glob/.test_config +++ /dev/null
@@ -1,5 +0,0 @@ -{ - "test_package": { - "platforms": ["vm"] - } -} \ No newline at end of file
diff --git a/pkgs/glob/.travis.yml b/pkgs/glob/.travis.yml deleted file mode 100644 index 9adf49d..0000000 --- a/pkgs/glob/.travis.yml +++ /dev/null
@@ -1,34 +0,0 @@ -language: dart - -dart: - - dev - - 2.2.0 - -# See https://docs.travis-ci.com/user/languages/dart/ for details. -dart_task: - - test: --platform vm,chrome - - dartanalyzer: --fatal-warnings --fatal-infos . - -jobs: - include: - - dart: 2.2.0 - dart_task: - test: -p node - - dart: dev - dart_task: - test: -p node - - dart: dev - dart_task: dartfmt - allow_failures: - # The node package needs to be updated for breaking changes in Dart 2.8 - - dart: dev - dart_task: - test: -p node - -# Only building master means that we don't run two builds for each pull request. -branches: - only: [master] - -cache: - directories: - - $HOME/.pub-cache
diff --git a/pkgs/glob/CHANGELOG.md b/pkgs/glob/CHANGELOG.md index 98ab5c2..391fe8b 100644 --- a/pkgs/glob/CHANGELOG.md +++ b/pkgs/glob/CHANGELOG.md
@@ -1,3 +1,38 @@ +## 2.1.1-dev + +* Bump dependencies + +## 2.1.0 + +* Return empty results instead of throwing when trying to list a path that does + not exist. + +## 2.0.2 + +* Drop package:pedantic dependency, use package:lints instead. +* Update SDK lower bound to `2.15.0` + +## 2.0.1 + +* Update example in README for new import. + +## 2.0.0 + +* Stable null safety release. + +## 2.0.0-nullsafety.0 + +* 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.1 * Add an empty list_local_fs.dart to ease upgrade from 1x to 2x
diff --git a/pkgs/glob/LICENSE b/pkgs/glob/LICENSE index 5c60afe..000cd7b 100644 --- a/pkgs/glob/LICENSE +++ b/pkgs/glob/LICENSE
@@ -1,4 +1,5 @@ -Copyright 2014, the Dart project authors. All rights reserved. +Copyright 2014, the Dart project authors. + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -9,7 +10,7 @@ copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Google Inc. nor the names of its + * Neither the name of Google LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
diff --git a/pkgs/glob/README.md b/pkgs/glob/README.md index 300e73e..75f2b00 100644 --- a/pkgs/glob/README.md +++ b/pkgs/glob/README.md
@@ -6,7 +6,7 @@ ## Usage -To construct a glob, just use `new Glob()`. As with `RegExp`s, it's a good idea +To construct a glob, just use `Glob()`. As with `RegExp`s, it's a good idea to keep around a glob if you'll be using it more than once so that it doesn't have to be compiled over and over. You can check whether a path matches the glob using `Glob.matches()`: @@ -14,7 +14,7 @@ ```dart import 'package:glob/glob.dart'; -final dartFile = new Glob("**.dart"); +final dartFile = Glob("**.dart"); // Print all command-line arguments that are Dart files. void main(List<String> arguments) { @@ -29,8 +29,9 @@ ```dart import 'package:glob/glob.dart'; +import 'package:glob/list_local_fs.dart'; -final dartFile = new Glob("**.dart"); +final dartFile = Glob("**.dart"); // Recursively list all Dart files in the current directory. void main(List<String> arguments) {
diff --git a/pkgs/glob/analysis_options.yaml b/pkgs/glob/analysis_options.yaml index aefc435..fe4989b 100644 --- a/pkgs/glob/analysis_options.yaml +++ b/pkgs/glob/analysis_options.yaml
@@ -1,4 +1,4 @@ -include: package:pedantic/analysis_options.yaml +include: package:lints/recommended.yaml analyzer: strong-mode: implicit-casts: false @@ -13,6 +13,7 @@ - cancel_subscriptions - constant_identifier_names - control_flow_in_finally + - depend_on_referenced_packages - directives_ordering - empty_catches - empty_constructor_bodies
diff --git a/pkgs/glob/lib/glob.dart b/pkgs/glob/lib/glob.dart index 75bedd6..a7b92f6 100644 --- a/pkgs/glob/lib/glob.dart +++ b/pkgs/glob/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'; @@ -17,7 +18,7 @@ /// /// A glob matches an entire string as a path. Although the glob pattern uses /// POSIX syntax, it can match against POSIX, Windows, or URL paths. The format -/// it expects paths to use is based on the `context` parameter to [new Glob]; +/// it expects paths to use is based on the `context` parameter to [Glob.new]; /// it defaults to the current system's syntax. /// /// Paths are normalized before being matched against a glob, so for example the @@ -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/pkgs/glob/lib/list_local_fs.dart b/pkgs/glob/lib/list_local_fs.dart index d59e216..946596a 100644 --- a/pkgs/glob/lib/list_local_fs.dart +++ b/pkgs/glob/lib/list_local_fs.dart
@@ -1,4 +1,23 @@ -/// This file is intentionally empty. It exists to ease transition from -/// glob 1x to 2x. By having this file here you can add the import to -/// it that provides backward compatibility in 2x while still on glob 1x. -library list_local_fs; +// 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/pkgs/glob/lib/src/ast.dart b/pkgs/glob/lib/src/ast.dart index 1ee0fcf..ac283d5 100644 --- a/pkgs/glob/lib/src/ast.dart +++ b/pkgs/glob/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/pkgs/glob/lib/src/io.dart b/pkgs/glob/lib/src/io.dart deleted file mode 100644 index 20e532b..0000000 --- a/pkgs/glob/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/pkgs/glob/lib/src/io_export.dart b/pkgs/glob/lib/src/io_export.dart deleted file mode 100644 index 7f339cf..0000000 --- a/pkgs/glob/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/pkgs/glob/lib/src/list_tree.dart b/pkgs/glob/lib/src/list_tree.dart index 82f12c5..2a6c020 100644 --- a/pkgs/glob/lib/src/list_tree.dart +++ b/pkgs/glob/lib/src/list_tree.dart
@@ -5,11 +5,10 @@ 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 +55,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 +87,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 +98,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 +146,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 +198,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 +223,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 +237,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 +249,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 +263,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 +291,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 +305,7 @@ _validator = OptionsNode([validator], caseSensitive: validator.caseSensitive); } else { - _validator.options.add(validator); + _validator!.options.add(validator); } } @@ -301,10 +313,13 @@ /// /// 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) + .ignoreMissing() .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,12 @@ } 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) + .ignoreMissing() + .toList(); + await _validateIntermediateChildrenAsync(dir, entities, fileSystem); var resultGroup = StreamGroup<FileSystemEntity>(); var resultController = StreamController<FileSystemEntity>(sync: true); @@ -333,20 +352,11 @@ 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) - .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); - }); + var stream = child.list(p.join(dir, basename), fileSystem, + followLinks: followLinks); resultGroup.add(stream); }); } @@ -362,12 +372,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 +388,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,25 +399,41 @@ /// /// 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) - .listSync(recursive: true, followLinks: followLinks) - .where((entity) => _matches(p.relative(entity.path, from: dir))); + try { + return fileSystem + .directory(dir) + .listSync(recursive: true, followLinks: followLinks) + .where((entity) => _matches(p.relative(entity.path, from: dir))); + } on FileSystemException catch (error) { + if (error.isMissing) return const []; + rethrow; + } } // 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); + List<FileSystemEntity> entities; + try { + entities = fileSystem.directory(dir).listSync(followLinks: followLinks); + } on FileSystemException catch (error) { + if (error.isMissing) return const []; + rethrow; + } + _validateIntermediateChildrenSync(dir, entities, fileSystem); return entities.expand((entity) { var entities = <FileSystemEntity>[]; @@ -413,25 +441,13 @@ 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) - .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) { - return const []; - } else { - rethrow; - } - } + return children![sequence]! + .listSync(p.join(dir, basename), fileSystem, + followLinks: followLinks) + .toList(); })); return entities; @@ -445,10 +461,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 +475,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'; @@ -485,3 +500,15 @@ } return SequenceNode(nodes, caseSensitive: first.caseSensitive); } + +extension on Stream<FileSystemEntity> { + Stream<FileSystemEntity> ignoreMissing() => handleError((_) {}, + test: (error) => error is FileSystemException && error.isMissing); +} + +extension on FileSystemException { + bool get isMissing { + final errorCode = osError?.errorCode; + return errorCode == _enoent || errorCode == _enoentWin; + } +}
diff --git a/pkgs/glob/lib/src/parser.dart b/pkgs/glob/lib/src/parser.dart index 20ade31..e76f7c5 100644 --- a/pkgs/glob/lib/src/parser.dart +++ b/pkgs/glob/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/pkgs/glob/pubspec.yaml b/pkgs/glob/pubspec.yaml index ef34d02..eae44a6 100644 --- a/pkgs/glob/pubspec.yaml +++ b/pkgs/glob/pubspec.yaml
@@ -1,20 +1,20 @@ name: glob -version: 1.2.1 +version: 2.1.0 description: Bash-style filename globbing. -homepage: https://github.com/dart-lang/glob +repository: https://github.com/dart-lang/glob environment: - sdk: '>=2.2.0 <3.0.0' + sdk: '>=2.15.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 + collection: ^1.15.0 + file: ^6.1.3 + path: ^1.8.0 + string_scanner: ^1.1.0 dev_dependencies: - test: ^1.6.0 - test_descriptor: ^1.0.0 + lints: ^1.0.0 + test: ^1.17.0 + test_descriptor: ^2.0.0
diff --git a/pkgs/glob/test/glob_test.dart b/pkgs/glob/test/glob_test.dart index 281a413..85c3fac 100644 --- a/pkgs/glob/test/glob_test.dart +++ b/pkgs/glob/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/pkgs/glob/test/list_test.dart b/pkgs/glob/test/list_test.dart index 5fac113..5714f25 100644 --- a/pkgs/glob/test/list_test.dart +++ b/pkgs/glob/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'; @@ -25,15 +26,17 @@ expect(Glob('*', context: p.url).list, throwsStateError); }); - test('reports exceptions for non-existent case-sensitive directories', () { - expect(Glob('non/existent/**', caseSensitive: true).list().toList(), - throwsA(isA<FileSystemException>())); + test('returns empty list for non-existent case-sensitive directories', + () async { + expect(await Glob('non/existent/**', caseSensitive: true).list().toList(), + []); }); - test('reports exceptions for non-existent case-insensitive directories', - () { - expect(Glob('non/existent/**', caseSensitive: false).list().toList(), - throwsA(isA<FileSystemException>())); + test('returns empty list for non-existent case-insensitive directories', + () async { + expect( + await Glob('non/existent/**', caseSensitive: false).list().toList(), + []); }); }); @@ -42,32 +45,27 @@ expect(Glob('*', context: p.url).listSync, throwsStateError); }); - test('reports exceptions for non-existent case-sensitive directories', () { - expect(Glob('non/existent/**', caseSensitive: true).listSync, - throwsA(isA<FileSystemException>())); + test('returns empty list for non-existent case-sensitive directories', () { + expect(Glob('non/existent/**', caseSensitive: true).listSync(), []); }); - test('reports exceptions for non-existent case-insensitive directories', + test('returns empty list for non-existent case-insensitive directories', () { - expect(Glob('non/existent/**', caseSensitive: false).listSync, - throwsA(isA<FileSystemException>())); + expect(Glob('non/existent/**', caseSensitive: false).listSync(), []); }); }); group('when case-sensitive', () { test('lists literals case-sensitively', () { - expect(Glob('foo/BAZ/qux', caseSensitive: true).listSync, - throwsA(isA<FileSystemException>())); + expect(Glob('foo/BAZ/qux', caseSensitive: true).listSync(), []); }); test('lists ranges case-sensitively', () { - expect(Glob('foo/[BX][A-Z]z/qux', caseSensitive: true).listSync, - throwsA(isA<FileSystemException>())); + expect(Glob('foo/[BX][A-Z]z/qux', caseSensitive: true).listSync(), []); }); test('options preserve case-sensitivity', () { - expect(Glob('foo/{BAZ,ZAP}/qux', caseSensitive: true).listSync, - throwsA(isA<FileSystemException>())); + expect(Glob('foo/{BAZ,ZAP}/qux', caseSensitive: true).listSync(), []); }); }); @@ -314,7 +312,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].