From 8eacba62e434393b19081b426167ba71651700ae Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 3 Sep 2023 18:12:44 -0300 Subject: [PATCH 01/23] v1.2.0 - `findAll`, `ImportPath` class and use of `ascii_art_tree`. - Moved code to class `ImportPath`: - Allows integration with other packages. - Facilitates tests. - Using `ascii_art_tree` to show the output tree, with styles `dots` (original) and `elegant`. - CLI: - Added options: - `--regexp`: to use `RegExp` to match the target import. - `--all`: to find all the import paths. - `-q`: for a quiet output (only displays found paths). - `-s`: strips the search root directory from displayed import paths. - `--elegant`: use `elegant` style for the output tree. - `--dots`: use `dots` style for the output tree. - Improved help with examples. - Updated `README.md` to show CLI and Library usage. - Added tests and coverage (80%). - Added GitHub Dart CI. - Updated dependencies compatible with Dart `2.14.0` (was already dependent to SDK `2.14.0`): - sdk: '>=2.14.0 <3.0.0' - package_config: ^2.1.0 - path: ^1.8.3 - ascii_art_tree: ^1.0.2 - test: ^1.21.4 --- .github/workflows/dart.yml | 63 ++++++++ .gitignore | 5 + CHANGELOG.md | 26 ++++ README.md | 72 ++++++++- bin/import_path.dart | 150 +++++++----------- example/example.dart | 34 ++-- lib/import_path.dart | 4 + lib/src/import_path_base.dart | 285 ++++++++++++++++++++++++++++++++++ pubspec.yaml | 13 +- run_coverage.sh | 8 + test/import_path_test.dart | 140 +++++++++++++++++ 11 files changed, 687 insertions(+), 113 deletions(-) create mode 100644 .github/workflows/dart.yml create mode 100644 lib/import_path.dart create mode 100644 lib/src/import_path_base.dart create mode 100755 run_coverage.sh create mode 100644 test/import_path_test.dart diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml new file mode 100644 index 0000000..f01d4d9 --- /dev/null +++ b/.github/workflows/dart.yml @@ -0,0 +1,63 @@ +name: Dart CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: dart-lang/setup-dart@v1 + - name: Dart version + run: | + dart --version + uname -a + - name: Install dependencies + run: dart pub get + - name: Upgrade dependencies + run: dart pub upgrade + - name: dart format + run: dart format -o none --set-exit-if-changed . + - name: dart analyze + run: dart analyze --fatal-infos --fatal-warnings . + - name: dependency_validator + run: dart run dependency_validator + - name: dart pub publish --dry-run + run: dart pub publish --dry-run + + test_vm: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: dart-lang/setup-dart@v1 + - name: Dart version + run: | + dart --version + uname -a + - name: Install dependencies + run: dart pub get + - name: Upgrade dependencies + run: dart pub upgrade + - name: Run tests + run: dart run test --platform vm --coverage=./coverage + - name: Generate coverage report + run: | + dart pub global activate coverage + dart pub global run coverage:format_coverage --packages=.dart_tool/package_config.json --report-on=lib --lcov -o ./coverage/lcov.info -i ./coverage +# - name: Upload coverage to Codecov +# uses: codecov/codecov-action@v3 +# env: +# CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} +# with: +# directory: ./coverage/ +# flags: unittests +# env_vars: OS,DART +# fail_ci_if_error: true +# verbose: true + diff --git a/.gitignore b/.gitignore index 102eaf4..dd07ba1 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,8 @@ build/ # If you're building an application, you may want to check-in your pubspec.lock pubspec.lock + +# Coverage files +coverage/ + +.DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cf0abc..06e5a7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,29 @@ +# 1.2.0 + +- Moved code to class `ImportPath`: + - Allows integration with other packages. + - Facilitates tests. +- Using `ascii_art_tree` to show the output tree, with styles `dots` (original) and `elegant`. +- CLI: + - Added options: + - `--regexp`: to use `RegExp` to match the target import. + - `--all`: to find all the import paths. + - `-q`: for a quiet output (only displays found paths). + - `-s`: strips the search root directory from displayed import paths. + - `--elegant`: use `elegant` style for the output tree. + - `--dots`: use `dots` style for the output tree. + - Improved help with examples. +- Updated `README.md` to show CLI and Library usage. +- Added tests and coverage (80%). +- Added GitHub Dart CI. + +- Updated dependencies compatible with Dart `2.14.0` (was already dependent to SDK `2.14.0`): + - sdk: '>=2.14.0 <3.0.0' + - package_config: ^2.1.0 + - path: ^1.8.3 + - ascii_art_tree: ^1.0.2 + - test: ^1.21.4 + # 1.1.1 - Add explicit executables config to the pubspec.yaml. diff --git a/README.md b/README.md index 05f571a..c6d0deb 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,87 @@ +# import_path + A tool to find the shortest import path from one dart file to another. -## Usage +## CLI Usage First, globally activate the package: -`dart pub global activate import_path` +```shell +dart pub global activate import_path +``` Then run it, the first argument is the library or application that you want to start searching from, and the second argument is the import you want to search for. -`import_path ` +```shell +import_path +``` Files should be specified as dart import uris, so relative or absolute file paths, as well as `package:` and `dart:` uris are supported. -## Example +## Examples From the root of this package, you can do: -``` +```shell pub global activate import_path + import_path bin/import_path.dart package:analyzer/dart/ast/ast.dart ``` + +To find all the `dart:io` imports from a `web/main.dart`: + +```shell +import_path web/main.dart dart:io --all +``` + +Search for all the imports for "dart:io" and "dart:html" using `RegExp`: + +```shell +import_path web/main.dart "dart:(io|html)" --regexp --all +``` +For help or more usage examples: + +```shell +import_path --help +``` + +## Library Usage + +You can also use the class `ImportPath` from your code: + +```dart +import 'package:import_path/import_path.dart'; + +void main(List args) async { + var strip = args.any((a) => a == '--strip' || a == '-s'); + + var importPath = ImportPath( + Uri.base.resolve('bin/import_path.dart'), + 'package:analyzer/dart/ast/ast.dart', + strip: strip, + ); + + await importPath.execute(); +} +``` + +## Features and bugs + +Please file feature requests and bugs at the [issue tracker][tracker]. + +[tracker]: https://github.com/jakemac53/import_path/issues + +## Authors + +- Jacob MacDonald: [jakemac53][github_jakemac53]. +- Graciliano M. Passos: [gmpassos][github_gmpassos]. + +[github_jakemac53]: https://github.com/jakemac53 +[github_gmpassos]: https://github.com/gmpassos + +## License + +Dart free & open-source [license](https://github.com/dart-lang/stagehand/blob/master/LICENSE). diff --git a/bin/import_path.dart b/bin/import_path.dart index a355e56..35c4068 100644 --- a/bin/import_path.dart +++ b/bin/import_path.dart @@ -2,105 +2,69 @@ // All rights reserved. Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -import 'dart:collection'; -import 'dart:io'; - -import 'package:analyzer/dart/analysis/utilities.dart'; -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:package_config/package_config.dart'; -import 'package:path/path.dart' as p; - -// Assigned early on in `main`. -late PackageConfig packageConfig; - -main(List args) async { - if (args.length != 2) { - print(''' -Expected exactly two Dart files as arguments, a file to start -searching from and an import to search for. +import 'package:import_path/import_path.dart'; + +void _showHelp() { + print(''' +╔═════════════════════╗ +║ import_path - CLI ║ +╚═════════════════════╝ + +USAGE: + + import_path %startSearchFile %targetImport -s -q --all + +OPTIONS: + + --regexp # Parses `%targetImport` as a `RegExp`. + --all # Searches for all the import paths. + -s # Strips the search root directory from displayed import paths. + -q # Quiet output (only displays found paths). + --elegant # Use `elegant` style for the output tree (default). + --dots # Use `dots` style for the output tree. + +EXAMPLES: + + # Search for the shortest import path of `dart:io` in a `web` directory: + import_path web/main.dart dart:io + + # Search all the import paths of a deferred library, + # stripping the search root directory from the output: + import_path web/main.dart web/lib/deferred_lib.dart --all -s + + # For a quiet output (no headers or warnings, only displays found paths): + import_path web/main.dart dart:io -q + + # Search for all the imports for "dart:io" and "dart:html" using `RegExp`: + import_path web/main.dart "dart:(io|html)" --regexp --all + '''); +} + +void main(List args) async { + var help = args.length < 2 || args.any((a) => a == '--help' || a == '-h'); + if (help) { + _showHelp(); return; } var from = Uri.base.resolve(args[0]); - var importToFind = Uri.base.resolve(args[1]); - packageConfig = (await findPackageConfig(Directory.current))!; - - var root = from.scheme == 'package' ? packageConfig.resolve(from)! : from; - var queue = Queue()..add(root); - - // Contains the closest parent to the root of the app for a given uri. - var parents = {root.toString(): null}; - while (queue.isNotEmpty) { - var parent = queue.removeFirst(); - var newImports = _importsFor(parent) - .where((uri) => !parents.containsKey(uri.toString())); - queue.addAll(newImports); - for (var import in newImports) { - parents[import.toString()] = parent.toString(); - if (importToFind == import) { - _printImportPath(import.toString(), parents, root.toString()); - return; - } - } - } - print('Unable to find an import path from $from to $importToFind'); -} + dynamic importToFind = args[1]; -final generatedDir = p.join('.dart_tool/build/generated'); - -List _importsFor(Uri uri) { - if (uri.scheme == 'dart') return []; - - var file = File((uri.scheme == 'package' ? packageConfig.resolve(uri) : uri)! - .toFilePath()); - // Check the generated dir for package:build - if (!file.existsSync()) { - var package = uri.scheme == 'package' - ? packageConfig[uri.pathSegments.first] - : packageConfig.packageOf(uri); - if (package == null) { - print('Warning: unable to read file at $uri, skipping it'); - return []; - } - var path = uri.scheme == 'package' - ? p.joinAll(uri.pathSegments.skip(1)) - : p.relative(uri.path, from: package.root.path); - file = File(p.join(generatedDir, package.name, path)); - if (!file.existsSync()) { - print('Warning: unable to read file at $uri, skipping it'); - return []; - } - } - var contents = file.readAsStringSync(); - - var parsed = parseString(content: contents, throwIfDiagnostics: false); - return parsed.unit.directives - .whereType() - .where((directive) { - if (directive.uri.stringValue == null) { - print('Empty uri content: ${directive.uri}'); - } - return directive.uri.stringValue != null; - }) - .map((directive) => uri.resolve(directive.uri.stringValue!)) - .toList(); -} + var regexp = args.length > 2 && args.any((a) => a == '--regexp'); + var findAll = args.length > 2 && args.any((a) => a == '--all'); + var quiet = args.length > 2 && args.any((a) => a == '-q'); + var strip = args.length > 2 && args.any((a) => a == '-s'); + var dots = args.length > 2 && args.any((a) => a == '--dots'); -void _printImportPath( - String import, Map parents, String root) { - var path = []; - String? next = import; - path.add(next); - while (next != root && next != null) { - next = parents[next]; - if (next != null) { - path.add(next); - } - } - var spacer = ''; - for (var import in path.reversed) { - print('$spacer$import'); - spacer += '..'; + if (regexp) { + importToFind = RegExp(importToFind); + } else { + importToFind = Uri.base.resolve(importToFind); } + + var importPath = ImportPath(from, importToFind, + findAll: findAll, quiet: quiet, strip: strip); + + await importPath.execute(dots: dots); } diff --git a/example/example.dart b/example/example.dart index 5973e3d..b70a06f 100644 --- a/example/example.dart +++ b/example/example.dart @@ -1,11 +1,25 @@ -import 'dart:io'; - -void main() async { - var result = await Process.run('pub', [ - 'run', - 'import_path', - 'bin/import_path.dart', - 'package:analyzer/dart/ast/ast.dart' - ]); - print(result.stdout); +import 'package:import_path/import_path.dart'; + +void main(List args) async { + var strip = args.any((a) => a == '--strip' || a == '-s'); + + var importPath = ImportPath( + Uri.base.resolve('bin/import_path.dart'), + 'package:analyzer/dart/ast/ast.dart', + strip: strip, + ); + + await importPath.execute(); } + +///////////////////////////////////// +// OUTPUT: with argument `--strip` // +///////////////////////////////////// +// » Search entry point: file:///workspace/import_path/bin/import_path.dart +// » Stripping search root from displayed imports: file:///workspace/import_path/ +// » Searching for the shortest import path for `package:analyzer/dart/ast/ast.dart`... +// +// bin/import_path.dart +// └─┬─ package:import_path/import_path.dart +// └─┬─ package:import_path/src/import_path_base.dart +// └──> package:analyzer/dart/ast/ast.dart diff --git a/lib/import_path.dart b/lib/import_path.dart new file mode 100644 index 0000000..19ee28e --- /dev/null +++ b/lib/import_path.dart @@ -0,0 +1,4 @@ +/// Import Path search library. +library import_path; + +export 'src/import_path_base.dart'; diff --git a/lib/src/import_path_base.dart b/lib/src/import_path_base.dart new file mode 100644 index 0000000..e393aef --- /dev/null +++ b/lib/src/import_path_base.dart @@ -0,0 +1,285 @@ +// Copyright (c) 2020, Google Inc. 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 'dart:io'; + +import 'package:analyzer/dart/analysis/utilities.dart'; +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:ascii_art_tree/ascii_art_tree.dart'; +import 'package:package_config/package_config.dart'; +import 'package:path/path.dart' as p; + +/// An Import Path search tool. +class ImportPath { + /// The entry point to start the search. + final Uri from; + + /// The import to find. Can be an [Uri] or a [RegExp]. + dynamic importToFind; + + /// If `true` searches for all import matches. + final bool findAll; + + /// If quiet won't call [printMessage] while searching. + final bool quiet; + + /// If `true` remove from paths the [searchRoot]. + final bool strip; + + String? _searchRoot; + + /// The function to print messages/text. Default: [print]. + /// Called by [printMessage]. + void Function(Object? m) messagePrinter; + + ImportPath(this.from, this.importToFind, + {this.findAll = false, + this.quiet = false, + this.strip = false, + String? searchRoot, + this.messagePrinter = print}) + : _searchRoot = searchRoot { + if (importToFind is String) { + importToFind = Uri.parse(importToFind); + } + + if (importToFind is! Uri && importToFind is! RegExp) { + throw ArgumentError( + "Invalid `importToFind`, not an `Uri` or `RegExp`: $importToFind"); + } + } + + /// The search root to strip from the displayed import paths. + /// - If `searchRoot` is not provided at construction it's resolved + /// using [from] parent directory (see [resolveSearchRoot]). + /// - See [strip] and [stripSearchRoot]. + String get searchRoot => _searchRoot ??= resolveSearchRoot(); + + set searchRoot(String value) => _searchRoot = value; + + /// This list contains common Dart root directories. + /// These names are preserved by [resolveSearchRoot] to prevent + /// them from being stripped. Default: `'web', 'bin', 'src', 'test', 'example'` + List commonRootDirectories = ['web', 'bin', 'src', 'test', 'example']; + + /// Resolves the [searchRoot] using [from] parent. + /// See [commonRootDirectories]. + String resolveSearchRoot() { + var rootPath = p.dirname(from.path); + var rootDirName = p.split(rootPath).last; + + if (commonRootDirectories.contains(rootDirName)) { + var rootPath2 = p.dirname(rootPath); + if (rootPath2.isNotEmpty) { + rootPath = rootPath2; + } + } + + var rootUri = from.replace(path: rootPath).toString(); + return rootUri.endsWith('/') ? rootUri : '$rootUri/'; + } + + /// Return the search root to [strip] from the displayed import paths. + /// If [strip] is `false` returns `null`. + /// See [searchRoot]. + String? get stripSearchRoot => strip ? searchRoot : null; + + /// Prints a message/text. + /// - Called by [execute]. + /// - See [messagePrinter]. + void printMessage(Object? m) => messagePrinter(m); + + /// Executes the import search and prints the results. + /// - If `dots` is `true` it prints the tree in `dots` style + /// - See [printMessage] and [ASCIIArtTree]. + Future execute({bool dots = false}) async { + if (!quiet) { + printMessage('» Search entry point: $from'); + + if (strip) { + printMessage( + '» Stripping search root from displayed imports: $searchRoot'); + } + + if (findAll) { + printMessage( + '» Searching for all import paths for `$importToFind`...\n'); + } else { + printMessage( + '» Searching for the shortest import path for `$importToFind`...\n'); + } + } + + var tree = await search(dots: dots); + + if (tree != null) { + var treeText = tree.generate(); + printMessage(treeText); + } + + if (!quiet) { + if (tree == null) { + printMessage( + '» Unable to find an import path from $from to $importToFind'); + } else { + var totalFoundPaths = tree.totalLeafs; + if (totalFoundPaths > 1) { + printMessage( + '» Found $totalFoundPaths import paths from $from to $importToFind\n'); + } + } + } + + return tree; + } + + /// Performs the imports search and returns the tree. + /// - If [dots] is `true` uses the `dots` style for the tree. + /// - See [ASCIIArtTree]. + Future search({bool dots = false}) async { + var foundPaths = await searchPaths(); + if (foundPaths == null || foundPaths.isEmpty) return null; + + var asciiArtTree = ASCIIArtTree.fromPaths( + foundPaths, + stripPrefix: stripSearchRoot, + style: dots ? ASCIIArtTreeStyle.dots : ASCIIArtTreeStyle.elegant, + ); + + return asciiArtTree; + } + + late PackageConfig _packageConfig; + late String _generatedDir; + + /// Performs the imports search and returns the found import paths. + Future>?> searchPaths() async { + var currentDir = Directory.current; + _packageConfig = (await findPackageConfig(currentDir))!; + _generatedDir = p.join('.dart_tool/build/generated'); + + var root = from.scheme == 'package' ? _packageConfig.resolve(from)! : from; + + var foundPaths = _searchImportPaths(root, stripSearchRoot: stripSearchRoot); + + var foundCount = foundPaths.length; + if (foundCount <= 0) { + return null; + } + + if (!findAll) { + foundPaths.sort((a, b) => a.length.compareTo(b.length)); + var shortest = foundPaths.first; + return [shortest]; + } else { + return foundPaths; + } + } + + List> _searchImportPaths( + Uri node, { + String? stripSearchRoot, + Set? walked, + List? parents, + List>? found, + }) { + found ??= []; + + if (walked == null) { + walked = {node}; + } else if (!walked.add(node)) { + return found; + } + + final nodePath = node.toString(); + if (parents == null) { + parents = [nodePath]; + } else { + parents.add(nodePath); + } + + var newImports = _importsFor(node, quiet: quiet) + .where((uri) => !walked!.contains(uri)) + .toList(growable: false); + + for (var import in newImports) { + if (_matchesImport(importToFind, import)) { + var foundPath = [...parents, import.toString()]; + found.add(foundPath); + } else { + _searchImportPaths(import, + stripSearchRoot: stripSearchRoot, + walked: walked, + parents: parents, + found: found); + } + } + + var rm = parents.removeLast(); + assert(rm == nodePath); + + return found; + } + + bool _matchesImport(Object importToFind, Uri import) { + if (importToFind is RegExp) { + return importToFind.hasMatch(import.toString()); + } else { + return importToFind == import; + } + } + + List _importsFor(Uri uri, {required bool quiet}) { + if (uri.scheme == 'dart') return []; + + var filePath = (uri.scheme == 'package' ? _packageConfig.resolve(uri) : uri) + ?.toFilePath(); + + if (filePath == null) { + if (!quiet) { + printMessage('» [WARNING] Unable to resolve Uri $uri, skipping it'); + } + return []; + } + + var file = File(filePath); + // Check the generated dir for package:build + if (!file.existsSync()) { + var package = uri.scheme == 'package' + ? _packageConfig[uri.pathSegments.first] + : _packageConfig.packageOf(uri); + if (package == null) { + if (!quiet) { + printMessage('» [WARNING] Unable to read file at $uri, skipping it'); + } + return []; + } + + var path = uri.scheme == 'package' + ? p.joinAll(uri.pathSegments.skip(1)) + : p.relative(uri.path, from: package.root.path); + file = File(p.join(_generatedDir, package.name, path)); + if (!file.existsSync()) { + if (!quiet) { + printMessage('» [WARNING] Unable to read file at $uri, skipping it'); + } + return []; + } + } + + var contents = file.readAsStringSync(); + + var parsed = parseString(content: contents, throwIfDiagnostics: false); + return parsed.unit.directives + .whereType() + .where((directive) { + if (directive.uri.stringValue == null && !quiet) { + printMessage('Empty uri content: ${directive.uri}'); + } + return directive.uri.stringValue != null; + }) + .map((directive) => uri.resolve(directive.uri.stringValue!)) + .toList(); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 9425146..2633e97 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,15 +1,18 @@ name: import_path -version: 1.1.1 -description: A tool to find the shortest import path from one dart file to another. +version: 1.2.0 +description: A tool to find the shortest import path or listing all import paths between two Dart files. It also supports the use of `RegExp` to match imports. homepage: https://github.com/jakemac53/import_path environment: - sdk: '>=2.12.0 <3.0.0' + sdk: '>=2.14.0 <4.0.0' dependencies: analyzer: '>=3.0.0 <5.0.0' - package_config: ^2.0.0 - path: ^1.8.0 + package_config: ^2.1.0 + path: ^1.8.3 + ascii_art_tree: ^1.0.2 + test: ^1.24.3 + executables: import_path: import_path diff --git a/run_coverage.sh b/run_coverage.sh new file mode 100755 index 0000000..fd9a349 --- /dev/null +++ b/run_coverage.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +dart run test --coverage=./coverage -x build + +dart pub global run coverage:format_coverage --packages=.dart_tool/package_config.json --report-on=lib --lcov -o ./coverage/lcov.info -i ./coverage + +genhtml -o ./coverage/report ./coverage/lcov.info +open ./coverage/report/index.html diff --git a/test/import_path_test.dart b/test/import_path_test.dart new file mode 100644 index 0000000..edd8e38 --- /dev/null +++ b/test/import_path_test.dart @@ -0,0 +1,140 @@ +import 'dart:io'; + +import 'package:import_path/import_path.dart'; +import 'package:path/path.dart' as pack_path; +import 'package:test/test.dart'; + +void main() { + group('ImportPath[shortest]', () { + test( + 'strip: false ; dots: false ; quiet: false', + () => doSearchTest( + strip: false, dots: false, quiet: false, expectedTreeText: r''' +file://.../import_path.dart + └─┬─ package:import_path/import_path.dart + └─┬─ package:import_path/src/import_path_base.dart + └──> package:analyzer/dart/ast/ast.dart +''')); + + test( + 'strip: false ; dots: false ; quiet: true', + () => doSearchTest( + strip: false, dots: false, quiet: true, expectedTreeText: r''' +file://.../import_path.dart + └─┬─ package:import_path/import_path.dart + └─┬─ package:import_path/src/import_path_base.dart + └──> package:analyzer/dart/ast/ast.dart +''')); + + test( + 'strip: false ; dots: true ; quiet: false', + () => doSearchTest( + strip: false, dots: true, quiet: false, expectedTreeText: r''' +file://.../import_path.dart +..package:import_path/import_path.dart +....package:import_path/src/import_path_base.dart +......package:analyzer/dart/ast/ast.dart +''')); + + test( + 'strip: true ; dots: true ; quiet: false', + () => doSearchTest( + strip: true, dots: true, quiet: false, expectedTreeText: r''' +bin/import_path.dart +..package:import_path/import_path.dart +....package:import_path/src/import_path_base.dart +......package:analyzer/dart/ast/ast.dart +''')); + }); + + group('ImportPath[all]', () { + test( + 'strip: true ; dots: true ; quiet: false', + () => doSearchTest( + strip: true, + dots: true, + quiet: false, + all: true, + expectedTreeText: RegExp(r'''^bin/import_path.dart +\..package:import_path/import_path.dart +\....package:import_path/src/import_path_base.dart +\......package:analyzer/dart/analysis/utilities.dart +.*?\........package:analyzer/dart/ast/ast.dart.*? +\......package:analyzer/dart/ast/ast.dart\s*$''', dotAll: true))); + }); +} + +Future doSearchTest( + {required bool strip, + required bool dots, + required bool quiet, + bool all = false, + required Pattern expectedTreeText}) async { + var output = []; + + var importPath = ImportPath( + _resolveFileUri('bin/import_path.dart'), + 'package:analyzer/dart/ast/ast.dart', + strip: strip, + quiet: quiet, + findAll: all, + messagePrinter: (m) { + output.add(m); + print(m); + }, + ); + + var tree = await importPath.execute(dots: dots); + expect(tree, isNotNull); + + var outputIdx = 0; + if (!quiet) { + expect(output[outputIdx++], startsWith('» Search entry point:')); + if (strip) { + expect(output[outputIdx++], + startsWith('» Stripping search root from displayed imports:')); + } + + if (all) { + expect( + output[outputIdx++], + equals( + '» Searching for all import paths for `package:analyzer/dart/ast/ast.dart`...\n')); + } else { + expect( + output[outputIdx++], + equals( + '» Searching for the shortest import path for `package:analyzer/dart/ast/ast.dart`...\n')); + } + } + + var treeText = output[outputIdx++] + .toString() + .replaceAll(RegExp(r'file://.*?/import_path/bin'), 'file://...'); + + expect(treeText, matches(expectedTreeText)); + + if (all) { + expect( + output[outputIdx++], matches(RegExp(r'» Found \d+ import paths from'))); + } + + expect(output.length, equals(outputIdx)); +} + +Uri _resolveFileUri(String targetFilePath) { + var possiblePaths = [ + './', + '../', + './import_path', + ]; + + for (var p in possiblePaths) { + var file = File(pack_path.join(p, targetFilePath)); + if (file.existsSync()) { + return file.absolute.uri; + } + } + + return Uri.base.resolve(targetFilePath); +} From eaba85a263c511fe78cc0d08c3ff76bbe78a3b62 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 3 Sep 2023 18:17:55 -0300 Subject: [PATCH 02/23] pubspec.yaml: adjusted `dev_dependencies:` --- CHANGELOG.md | 3 +++ analysis_options.yaml | 30 ++++++++++++++++++++++++++++++ pubspec.yaml | 6 +++++- 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 analysis_options.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 06e5a7d..2e023f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,10 @@ - package_config: ^2.1.0 - path: ^1.8.3 - ascii_art_tree: ^1.0.2 + - lints: ^1.0.1 + - dependency_validator: ^3.2.2 - test: ^1.21.4 + - coverage: ^1.2.0 # 1.1.1 diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..dee8927 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/pubspec.yaml b/pubspec.yaml index 2633e97..d920ff2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,8 +11,12 @@ dependencies: package_config: ^2.1.0 path: ^1.8.3 ascii_art_tree: ^1.0.2 - test: ^1.24.3 +dev_dependencies: + lints: ^1.0.1 + dependency_validator: ^3.2.2 + test: ^1.21.4 + coverage: ^1.2.0 executables: import_path: import_path From 4fb8a35f216df85509e17b3df2b3db4eb696c360 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 3 Sep 2023 18:31:02 -0300 Subject: [PATCH 03/23] Clean code --- lib/src/import_path_base.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/src/import_path_base.dart b/lib/src/import_path_base.dart index e393aef..7a90534 100644 --- a/lib/src/import_path_base.dart +++ b/lib/src/import_path_base.dart @@ -162,9 +162,7 @@ class ImportPath { var root = from.scheme == 'package' ? _packageConfig.resolve(from)! : from; var foundPaths = _searchImportPaths(root, stripSearchRoot: stripSearchRoot); - - var foundCount = foundPaths.length; - if (foundCount <= 0) { + if (foundPaths.isEmpty) { return null; } From 1b686fa8b7823fcf36142682b008c000580be196 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 3 Sep 2023 18:36:02 -0300 Subject: [PATCH 04/23] `README.md`: add badges --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index c6d0deb..872f2fe 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,14 @@ # import_path +[![pub package](https://img.shields.io/pub/v/import_path.svg?logo=dart&logoColor=00b9fc)](https://pub.dartlang.org/packages/import_path) +[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) +[![Dart CI](https://github.com/jakemac53/import_path/actions/workflows/dart.yml/badge.svg?branch=master)](https://github.com/jakemac53/import_path/actions/workflows/dart.yml) +[![New Commits](https://img.shields.io/github/commits-since/jakemac53/import_path/latest?logo=git&logoColor=white)](https://github.com/jakemac53/import_path/network) +[![Last Commits](https://img.shields.io/github/last-commit/jakemac53/import_path?logo=git&logoColor=white)](https://github.com/jakemac53/import_path/commits/master) +[![Pull Requests](https://img.shields.io/github/issues-pr/jakemac53/import_path?logo=github&logoColor=white)](https://github.com/jakemac53/import_path/pulls) +[![Code size](https://img.shields.io/github/languages/code-size/jakemac53/import_path?logo=github&logoColor=white)](https://github.com/jakemac53/import_path) +[![License](https://img.shields.io/github/license/jakemac53/import_path?logo=open-source-initiative&logoColor=green)](https://github.com/jakemac53/import_path/blob/master/LICENSE) + A tool to find the shortest import path from one dart file to another. ## CLI Usage From 91f38c8b37b17d63ca49152fde606d0137e3c222 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 3 Sep 2023 18:37:03 -0300 Subject: [PATCH 05/23] `README.md`: add badges --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 872f2fe..75ec989 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![pub package](https://img.shields.io/pub/v/import_path.svg?logo=dart&logoColor=00b9fc)](https://pub.dartlang.org/packages/import_path) [![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) [![Dart CI](https://github.com/jakemac53/import_path/actions/workflows/dart.yml/badge.svg?branch=master)](https://github.com/jakemac53/import_path/actions/workflows/dart.yml) -[![New Commits](https://img.shields.io/github/commits-since/jakemac53/import_path/latest?logo=git&logoColor=white)](https://github.com/jakemac53/import_path/network) +[![GitHub Tag](https://img.shields.io/github/v/tag/jakemac53/import_path?logo=git&logoColor=white)](https://github.com/jakemac53/import_path/releases) [![Last Commits](https://img.shields.io/github/last-commit/jakemac53/import_path?logo=git&logoColor=white)](https://github.com/jakemac53/import_path/commits/master) [![Pull Requests](https://img.shields.io/github/issues-pr/jakemac53/import_path?logo=github&logoColor=white)](https://github.com/jakemac53/import_path/pulls) [![Code size](https://img.shields.io/github/languages/code-size/jakemac53/import_path?logo=github&logoColor=white)](https://github.com/jakemac53/import_path) From a7f54f65304e55f8793de170c405991a92fb99bf Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 3 Sep 2023 18:44:59 -0300 Subject: [PATCH 06/23] Dart CI: added windows and macos tests --- .github/workflows/dart.yml | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index f01d4d9..da387f9 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -9,7 +9,6 @@ on: jobs: build: runs-on: ubuntu-latest - steps: - uses: actions/checkout@v3 - uses: dart-lang/setup-dart@v1 @@ -30,9 +29,9 @@ jobs: - name: dart pub publish --dry-run run: dart pub publish --dry-run - test_vm: - runs-on: ubuntu-latest + test_linux: + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dart-lang/setup-dart@v1 @@ -61,3 +60,36 @@ jobs: # fail_ci_if_error: true # verbose: true + test_windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + - uses: dart-lang/setup-dart@v1 + - name: Dart version + run: | + dart --version + uname -a + - name: Install dependencies + run: dart pub get + - name: Upgrade dependencies + run: dart pub upgrade + - name: Run tests + run: dart run test --platform vm + + + test_macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + - uses: dart-lang/setup-dart@v1 + - name: Dart version + run: | + dart --version + uname -a + - name: Install dependencies + run: dart pub get + - name: Upgrade dependencies + run: dart pub upgrade + - name: Run tests + run: dart run test --platform vm + From d341af60268c6ff6a8a4f75b6c9d7fb78ce9b2cc Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 3 Sep 2023 18:50:35 -0300 Subject: [PATCH 07/23] `README.md`: update package description --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 75ec989..6a4e8d4 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,9 @@ [![Code size](https://img.shields.io/github/languages/code-size/jakemac53/import_path?logo=github&logoColor=white)](https://github.com/jakemac53/import_path) [![License](https://img.shields.io/github/license/jakemac53/import_path?logo=open-source-initiative&logoColor=green)](https://github.com/jakemac53/import_path/blob/master/LICENSE) -A tool to find the shortest import path from one dart file to another. +A tool to find the shortest import path or listing +all import paths between two Dart files. +It also supports the use of `RegExp` to match imports. ## CLI Usage From 5e8036784a1a61a6958e6948152b2f1464d61403 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Mon, 4 Sep 2023 04:49:15 -0300 Subject: [PATCH 08/23] ascii_art_tree: ^1.0.4 --- CHANGELOG.md | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e023f9..15c6b7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ - sdk: '>=2.14.0 <3.0.0' - package_config: ^2.1.0 - path: ^1.8.3 - - ascii_art_tree: ^1.0.2 + - ascii_art_tree: ^1.0.4 - lints: ^1.0.1 - dependency_validator: ^3.2.2 - test: ^1.21.4 diff --git a/pubspec.yaml b/pubspec.yaml index d920ff2..6336a74 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: analyzer: '>=3.0.0 <5.0.0' package_config: ^2.1.0 path: ^1.8.3 - ascii_art_tree: ^1.0.2 + ascii_art_tree: ^1.0.4 dev_dependencies: lints: ^1.0.1 From 01b94b030899ae193618afdde2c5f0ce82074599 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Tue, 5 Sep 2023 21:46:19 -0300 Subject: [PATCH 09/23] Using package `args` to resolve command flags and usage. --- CHANGELOG.md | 1 + bin/import_path.dart | 67 +++++++++++++++++++++++++++++++++----------- pubspec.yaml | 1 + 3 files changed, 52 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15c6b7c..b8facae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - sdk: '>=2.14.0 <3.0.0' - package_config: ^2.1.0 - path: ^1.8.3 + - args: ^2.4.2 - ascii_art_tree: ^1.0.4 - lints: ^1.0.1 - dependency_validator: ^3.2.2 diff --git a/bin/import_path.dart b/bin/import_path.dart index 35c4068..250d1c8 100644 --- a/bin/import_path.dart +++ b/bin/import_path.dart @@ -2,9 +2,13 @@ // 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:args/args.dart'; import 'package:import_path/import_path.dart'; -void _showHelp() { +void _showHelp(ArgParser argsParser) { + var usage = argsParser.usage + .replaceAllMapped(RegExp(r'(^|\n)'), (m) => '${m.group(1)} '); + print(''' ╔═════════════════════╗ ║ import_path - CLI ║ @@ -16,12 +20,7 @@ USAGE: OPTIONS: - --regexp # Parses `%targetImport` as a `RegExp`. - --all # Searches for all the import paths. - -s # Strips the search root directory from displayed import paths. - -q # Quiet output (only displays found paths). - --elegant # Use `elegant` style for the output tree (default). - --dots # Use `dots` style for the output tree. +$usage EXAMPLES: @@ -37,25 +36,59 @@ EXAMPLES: # Search for all the imports for "dart:io" and "dart:html" using `RegExp`: import_path web/main.dart "dart:(io|html)" --regexp --all - '''); } void main(List args) async { - var help = args.length < 2 || args.any((a) => a == '--help' || a == '-h'); + var argsParser = ArgParser(); + + argsParser.addFlag('help', + abbr: 'h', negatable: false, help: "Show usage information"); + + argsParser.addFlag('all', + abbr: 'a', negatable: false, help: "Searches for all the import paths."); + + argsParser.addFlag('strip', + abbr: 's', + negatable: false, + help: "Strips the search root directory from displayed import paths."); + + argsParser.addFlag('regexp', + abbr: 'r', + negatable: false, + help: "Parses `%targetImport` as a `RegExp`."); + + argsParser.addFlag('quiet', + abbr: 'q', + negatable: false, + help: "Quiet output (only displays found paths)."); + + argsParser.addFlag('elegant', + abbr: 'e', + negatable: false, + help: "Use `elegant` style for the output tree (default)."); + + argsParser.addFlag('dots', + abbr: 'd', + negatable: false, + help: "Use `dots` style for the output tree."); + + var argsResult = argsParser.parse(args); + + var help = argsResult.arguments.isEmpty || argsResult['help']; if (help) { - _showHelp(); + _showHelp(argsParser); return; } - var from = Uri.base.resolve(args[0]); - dynamic importToFind = args[1]; + var from = Uri.base.resolve(argsResult.rest[0]); + dynamic importToFind = argsResult.rest[1]; - var regexp = args.length > 2 && args.any((a) => a == '--regexp'); - var findAll = args.length > 2 && args.any((a) => a == '--all'); - var quiet = args.length > 2 && args.any((a) => a == '-q'); - var strip = args.length > 2 && args.any((a) => a == '-s'); - var dots = args.length > 2 && args.any((a) => a == '--dots'); + var regexp = argsResult['regexp'] as bool; + var findAll = argsResult['all'] as bool; + var quiet = argsResult['quiet'] as bool; + var strip = argsResult['strip'] as bool; + var dots = argsResult['dots'] as bool; if (regexp) { importToFind = RegExp(importToFind); diff --git a/pubspec.yaml b/pubspec.yaml index 6336a74..45c90ea 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,6 +10,7 @@ dependencies: analyzer: '>=3.0.0 <5.0.0' package_config: ^2.1.0 path: ^1.8.3 + args: ^2.4.2 ascii_art_tree: ^1.0.4 dev_dependencies: From 43e89404c5e5f9ce7f6c3d36458f532cb75cf517 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Tue, 5 Sep 2023 21:49:41 -0300 Subject: [PATCH 10/23] README.md: adjust license link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6a4e8d4..9e1f370 100644 --- a/README.md +++ b/README.md @@ -95,4 +95,4 @@ Please file feature requests and bugs at the [issue tracker][tracker]. ## License -Dart free & open-source [license](https://github.com/dart-lang/stagehand/blob/master/LICENSE). +Dart free & open-source [license](https://github.com/jakemac53/import_path/blob/master/LICENSE). From 9fd4252f2b0443b0898a5a2c55d19bfbc16fe88a Mon Sep 17 00:00:00 2001 From: gmpassos Date: Tue, 5 Sep 2023 21:56:41 -0300 Subject: [PATCH 11/23] `ImportPath`: make field `importToFind` final and `Object`. --- bin/import_path.dart | 13 +++++-------- lib/src/import_path_base.dart | 11 ++++++++--- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/bin/import_path.dart b/bin/import_path.dart index 250d1c8..76062a6 100644 --- a/bin/import_path.dart +++ b/bin/import_path.dart @@ -81,20 +81,17 @@ void main(List args) async { return; } - var from = Uri.base.resolve(argsResult.rest[0]); - dynamic importToFind = argsResult.rest[1]; - var regexp = argsResult['regexp'] as bool; var findAll = argsResult['all'] as bool; var quiet = argsResult['quiet'] as bool; var strip = argsResult['strip'] as bool; var dots = argsResult['dots'] as bool; - if (regexp) { - importToFind = RegExp(importToFind); - } else { - importToFind = Uri.base.resolve(importToFind); - } + var from = Uri.base.resolve(argsResult.rest[0]); + + var importToFindArg = argsResult.rest[1]; + var importToFind = + regexp ? RegExp(importToFindArg) : Uri.base.resolve(importToFindArg); var importPath = ImportPath(from, importToFind, findAll: findAll, quiet: quiet, strip: strip); diff --git a/lib/src/import_path_base.dart b/lib/src/import_path_base.dart index 7a90534..0d1312a 100644 --- a/lib/src/import_path_base.dart +++ b/lib/src/import_path_base.dart @@ -16,7 +16,7 @@ class ImportPath { final Uri from; /// The import to find. Can be an [Uri] or a [RegExp]. - dynamic importToFind; + final Object importToFind; /// If `true` searches for all import matches. final bool findAll; @@ -33,13 +33,16 @@ class ImportPath { /// Called by [printMessage]. void Function(Object? m) messagePrinter; - ImportPath(this.from, this.importToFind, + ImportPath(this.from, Object importToFind, {this.findAll = false, this.quiet = false, this.strip = false, String? searchRoot, this.messagePrinter = print}) - : _searchRoot = searchRoot { + : importToFind = _resolveImportToFind(importToFind), + _searchRoot = searchRoot; + + static Object _resolveImportToFind(Object importToFind) { if (importToFind is String) { importToFind = Uri.parse(importToFind); } @@ -48,6 +51,8 @@ class ImportPath { throw ArgumentError( "Invalid `importToFind`, not an `Uri` or `RegExp`: $importToFind"); } + + return importToFind; } /// The search root to strip from the displayed import paths. From 891a1b5ab37abdc2a8df0c7bb78e565adb436f23 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Tue, 5 Sep 2023 21:59:24 -0300 Subject: [PATCH 12/23] only show `ImportPath` --- lib/import_path.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/import_path.dart b/lib/import_path.dart index 19ee28e..c13e9d8 100644 --- a/lib/import_path.dart +++ b/lib/import_path.dart @@ -1,4 +1,4 @@ /// Import Path search library. library import_path; -export 'src/import_path_base.dart'; +export 'src/import_path_base.dart' show ImportPath; From 359f31f4308f25471650cce3e0a78f295ff902ce Mon Sep 17 00:00:00 2001 From: gmpassos Date: Tue, 5 Sep 2023 22:27:30 -0300 Subject: [PATCH 13/23] New class `ImportToFind` for import matching. --- lib/import_path.dart | 3 +- lib/src/import_path_base.dart | 80 +++++++++++++++++++++++------------ 2 files changed, 56 insertions(+), 27 deletions(-) diff --git a/lib/import_path.dart b/lib/import_path.dart index c13e9d8..4d1f404 100644 --- a/lib/import_path.dart +++ b/lib/import_path.dart @@ -1,4 +1,5 @@ /// Import Path search library. library import_path; -export 'src/import_path_base.dart' show ImportPath; +export 'src/import_path_base.dart' + show ImportPath, ImportToFind, ImportToFindURI, ImportToFindRegExp; diff --git a/lib/src/import_path_base.dart b/lib/src/import_path_base.dart index 0d1312a..5a63ef2 100644 --- a/lib/src/import_path_base.dart +++ b/lib/src/import_path_base.dart @@ -10,18 +10,67 @@ import 'package:ascii_art_tree/ascii_art_tree.dart'; import 'package:package_config/package_config.dart'; import 'package:path/path.dart' as p; +/// The import to find using [ImportPath]. +/// - See [ImportToFindURI] and [ImportToFindRegExp]. +abstract class ImportToFind { + factory ImportToFind.from(Object o) { + if (o is ImportToFind) { + return o; + } else if (o is Uri) { + return ImportToFindURI(o); + } else if (o is RegExp) { + return ImportToFindRegExp(o); + } else if (o is String) { + return ImportToFindURI(Uri.parse(o)); + } else { + throw ArgumentError("Can't resolve: $o"); + } + } + + ImportToFind(); + + /// Returns `true` if [importUri] matches the import to find. + bool matches(Uri importUri); +} + +/// An [Uri] implementation of [ImportToFind]. +class ImportToFindURI extends ImportToFind { + final Uri uri; + + ImportToFindURI(this.uri); + + @override + bool matches(Uri importUri) => uri == importUri; + + @override + String toString() => uri.toString(); +} + +/// A [RegExp] implementation of [ImportToFind]. +class ImportToFindRegExp extends ImportToFind { + final RegExp regExp; + + ImportToFindRegExp(this.regExp); + + @override + bool matches(Uri importUri) => regExp.hasMatch(importUri.toString()); + + @override + String toString() => regExp.toString(); +} + /// An Import Path search tool. class ImportPath { /// The entry point to start the search. final Uri from; /// The import to find. Can be an [Uri] or a [RegExp]. - final Object importToFind; + final ImportToFind importToFind; /// If `true` searches for all import matches. final bool findAll; - /// If quiet won't call [printMessage] while searching. + /// If `true`, we won't call [printMessage] while searching. final bool quiet; /// If `true` remove from paths the [searchRoot]. @@ -39,22 +88,9 @@ class ImportPath { this.strip = false, String? searchRoot, this.messagePrinter = print}) - : importToFind = _resolveImportToFind(importToFind), + : importToFind = ImportToFind.from(importToFind), _searchRoot = searchRoot; - static Object _resolveImportToFind(Object importToFind) { - if (importToFind is String) { - importToFind = Uri.parse(importToFind); - } - - if (importToFind is! Uri && importToFind is! RegExp) { - throw ArgumentError( - "Invalid `importToFind`, not an `Uri` or `RegExp`: $importToFind"); - } - - return importToFind; - } - /// The search root to strip from the displayed import paths. /// - If `searchRoot` is not provided at construction it's resolved /// using [from] parent directory (see [resolveSearchRoot]). @@ -96,7 +132,7 @@ class ImportPath { void printMessage(Object? m) => messagePrinter(m); /// Executes the import search and prints the results. - /// - If `dots` is `true` it prints the tree in `dots` style + /// - If [dots] is `true` it prints the tree in `dots` style /// - See [printMessage] and [ASCIIArtTree]. Future execute({bool dots = false}) async { if (!quiet) { @@ -207,7 +243,7 @@ class ImportPath { .toList(growable: false); for (var import in newImports) { - if (_matchesImport(importToFind, import)) { + if (importToFind.matches(import)) { var foundPath = [...parents, import.toString()]; found.add(foundPath); } else { @@ -225,14 +261,6 @@ class ImportPath { return found; } - bool _matchesImport(Object importToFind, Uri import) { - if (importToFind is RegExp) { - return importToFind.hasMatch(import.toString()); - } else { - return importToFind == import; - } - } - List _importsFor(Uri uri, {required bool quiet}) { if (uri.scheme == 'dart') return []; From cc2a7f1893dfa00fedd8beb0a6cc11937ce73749 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Tue, 5 Sep 2023 22:36:13 -0300 Subject: [PATCH 14/23] `ImportPath`: resolve field `searchRoot` during construction. --- lib/src/import_path_base.dart | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/lib/src/import_path_base.dart b/lib/src/import_path_base.dart index 5a63ef2..5e2c824 100644 --- a/lib/src/import_path_base.dart +++ b/lib/src/import_path_base.dart @@ -76,7 +76,11 @@ class ImportPath { /// If `true` remove from paths the [searchRoot]. final bool strip; - String? _searchRoot; + /// The search root to strip from the displayed import paths. + /// - If `searchRoot` is not provided at construction it's resolved + /// using [from] parent directory (see [resolveSearchRoot]). + /// - See [strip] and [stripSearchRoot]. + late String searchRoot; /// The function to print messages/text. Default: [print]. /// Called by [printMessage]. @@ -88,16 +92,9 @@ class ImportPath { this.strip = false, String? searchRoot, this.messagePrinter = print}) - : importToFind = ImportToFind.from(importToFind), - _searchRoot = searchRoot; - - /// The search root to strip from the displayed import paths. - /// - If `searchRoot` is not provided at construction it's resolved - /// using [from] parent directory (see [resolveSearchRoot]). - /// - See [strip] and [stripSearchRoot]. - String get searchRoot => _searchRoot ??= resolveSearchRoot(); - - set searchRoot(String value) => _searchRoot = value; + : importToFind = ImportToFind.from(importToFind) { + this.searchRoot = searchRoot ?? resolveSearchRoot(); + } /// This list contains common Dart root directories. /// These names are preserved by [resolveSearchRoot] to prevent From bed45b8a23a4f6b4b67e5ae4952f7a45480e8d2c Mon Sep 17 00:00:00 2001 From: gmpassos Date: Tue, 5 Sep 2023 22:47:07 -0300 Subject: [PATCH 15/23] `ImportPath`: make `_stripSearchRoot` private. --- lib/src/import_path_base.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/import_path_base.dart b/lib/src/import_path_base.dart index 5e2c824..9a6f279 100644 --- a/lib/src/import_path_base.dart +++ b/lib/src/import_path_base.dart @@ -79,7 +79,6 @@ class ImportPath { /// The search root to strip from the displayed import paths. /// - If `searchRoot` is not provided at construction it's resolved /// using [from] parent directory (see [resolveSearchRoot]). - /// - See [strip] and [stripSearchRoot]. late String searchRoot; /// The function to print messages/text. Default: [print]. @@ -121,7 +120,7 @@ class ImportPath { /// Return the search root to [strip] from the displayed import paths. /// If [strip] is `false` returns `null`. /// See [searchRoot]. - String? get stripSearchRoot => strip ? searchRoot : null; + String? get _stripSearchRoot => strip ? searchRoot : null; /// Prints a message/text. /// - Called by [execute]. @@ -181,7 +180,7 @@ class ImportPath { var asciiArtTree = ASCIIArtTree.fromPaths( foundPaths, - stripPrefix: stripSearchRoot, + stripPrefix: _stripSearchRoot, style: dots ? ASCIIArtTreeStyle.dots : ASCIIArtTreeStyle.elegant, ); @@ -199,7 +198,8 @@ class ImportPath { var root = from.scheme == 'package' ? _packageConfig.resolve(from)! : from; - var foundPaths = _searchImportPaths(root, stripSearchRoot: stripSearchRoot); + var foundPaths = + _searchImportPaths(root, stripSearchRoot: _stripSearchRoot); if (foundPaths.isEmpty) { return null; } From 92636b2d7df73b720f5319108c8944863a1fb02b Mon Sep 17 00:00:00 2001 From: gmpassos Date: Tue, 5 Sep 2023 23:14:35 -0300 Subject: [PATCH 16/23] Added enum `ImportPathStyle` (dots, elegant, json) ; added JSON output. --- bin/import_path.dart | 21 +++++++------ lib/import_path.dart | 9 +++++- lib/src/import_path_base.dart | 55 +++++++++++++++++++++++++++++++---- test/import_path_test.dart | 27 +++++++++++++++-- 4 files changed, 92 insertions(+), 20 deletions(-) diff --git a/bin/import_path.dart b/bin/import_path.dart index 76062a6..e3d4d30 100644 --- a/bin/import_path.dart +++ b/bin/import_path.dart @@ -63,15 +63,11 @@ void main(List args) async { negatable: false, help: "Quiet output (only displays found paths)."); - argsParser.addFlag('elegant', - abbr: 'e', - negatable: false, - help: "Use `elegant` style for the output tree (default)."); - - argsParser.addFlag('dots', - abbr: 'd', - negatable: false, - help: "Use `dots` style for the output tree."); + argsParser.addOption('format', + abbr: 'f', + allowed: ['elegant', 'dots', 'json'], + defaultsTo: 'elegant', + help: "The output format"); var argsResult = argsParser.parse(args); @@ -85,7 +81,10 @@ void main(List args) async { var findAll = argsResult['all'] as bool; var quiet = argsResult['quiet'] as bool; var strip = argsResult['strip'] as bool; - var dots = argsResult['dots'] as bool; + + var format = argsResult['format'] as String; + + var style = parseImportPathStyle(format) ?? ImportPathStyle.elegant; var from = Uri.base.resolve(argsResult.rest[0]); @@ -96,5 +95,5 @@ void main(List args) async { var importPath = ImportPath(from, importToFind, findAll: findAll, quiet: quiet, strip: strip); - await importPath.execute(dots: dots); + await importPath.execute(style: style); } diff --git a/lib/import_path.dart b/lib/import_path.dart index 4d1f404..1710cba 100644 --- a/lib/import_path.dart +++ b/lib/import_path.dart @@ -2,4 +2,11 @@ library import_path; export 'src/import_path_base.dart' - show ImportPath, ImportToFind, ImportToFindURI, ImportToFindRegExp; + show + ImportPath, + ImportPathStyle, + parseImportPathStyle, + ImportPathStyleExtension, + ImportToFind, + ImportToFindURI, + ImportToFindRegExp; diff --git a/lib/src/import_path_base.dart b/lib/src/import_path_base.dart index 9a6f279..6cb0deb 100644 --- a/lib/src/import_path_base.dart +++ b/lib/src/import_path_base.dart @@ -2,6 +2,7 @@ // All rights reserved. Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +import 'dart:convert'; import 'dart:io'; import 'package:analyzer/dart/analysis/utilities.dart'; @@ -59,6 +60,41 @@ class ImportToFindRegExp extends ImportToFind { String toString() => regExp.toString(); } +/// The [ImportPath] output style. +enum ImportPathStyle { + dots, + elegant, + json, +} + +ImportPathStyle? parseImportPathStyle(String s) { + s = s.toLowerCase().trim(); + + switch (s) { + case 'dots': + return ImportPathStyle.dots; + case 'elegant': + return ImportPathStyle.elegant; + case 'json': + return ImportPathStyle.json; + default: + return null; + } +} + +extension ImportPathStyleExtension on ImportPathStyle { + ASCIIArtTreeStyle? get asASCIIArtTreeStyle { + switch (this) { + case ImportPathStyle.dots: + return ASCIIArtTreeStyle.dots; + case ImportPathStyle.elegant: + return ASCIIArtTreeStyle.elegant; + default: + return null; + } + } +} + /// An Import Path search tool. class ImportPath { /// The entry point to start the search. @@ -130,7 +166,8 @@ class ImportPath { /// Executes the import search and prints the results. /// - If [dots] is `true` it prints the tree in `dots` style /// - See [printMessage] and [ASCIIArtTree]. - Future execute({bool dots = false}) async { + Future execute( + {ImportPathStyle style = ImportPathStyle.elegant}) async { if (!quiet) { printMessage('» Search entry point: $from'); @@ -148,11 +185,16 @@ class ImportPath { } } - var tree = await search(dots: dots); + var tree = await search(style: style); if (tree != null) { - var treeText = tree.generate(); - printMessage(treeText); + if (style == ImportPathStyle.json) { + var j = JsonEncoder.withIndent(' ').convert(tree.toJson()); + printMessage(j); + } else { + var treeText = tree.generate(); + printMessage(treeText); + } } if (!quiet) { @@ -174,14 +216,15 @@ class ImportPath { /// Performs the imports search and returns the tree. /// - If [dots] is `true` uses the `dots` style for the tree. /// - See [ASCIIArtTree]. - Future search({bool dots = false}) async { + Future search( + {ImportPathStyle style = ImportPathStyle.elegant}) async { var foundPaths = await searchPaths(); if (foundPaths == null || foundPaths.isEmpty) return null; var asciiArtTree = ASCIIArtTree.fromPaths( foundPaths, stripPrefix: _stripSearchRoot, - style: dots ? ASCIIArtTreeStyle.dots : ASCIIArtTreeStyle.elegant, + style: style.asASCIIArtTreeStyle ?? ASCIIArtTreeStyle.elegant, ); return asciiArtTree; diff --git a/test/import_path_test.dart b/test/import_path_test.dart index edd8e38..68ff6dc 100644 --- a/test/import_path_test.dart +++ b/test/import_path_test.dart @@ -1,6 +1,7 @@ import 'dart:io'; - +import 'dart:convert' show JsonEncoder; import 'package:import_path/import_path.dart'; +import 'package:import_path/src/import_path_base.dart'; import 'package:path/path.dart' as pack_path; import 'package:test/test.dart'; @@ -84,7 +85,8 @@ Future doSearchTest( }, ); - var tree = await importPath.execute(dots: dots); + var tree = await importPath.execute( + style: dots ? ImportPathStyle.dots : ImportPathStyle.elegant); expect(tree, isNotNull); var outputIdx = 0; @@ -120,6 +122,27 @@ Future doSearchTest( } expect(output.length, equals(outputIdx)); + + { + var output2 = []; + + var importPath2 = ImportPath( + _resolveFileUri('bin/import_path.dart'), + 'package:analyzer/dart/ast/ast.dart', + strip: strip, + quiet: true, + findAll: all, + messagePrinter: (m) => output2.add(m), + ); + + var tree2 = await importPath2.execute(style: ImportPathStyle.json); + + expect( + output2, + equals([ + JsonEncoder.withIndent(' ').convert(tree2?.toJson()), + ])); + } } Uri _resolveFileUri(String targetFilePath) { From 1793d72a03b00c5fa7d6aa7ec7404c2371edfd28 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Tue, 5 Sep 2023 23:18:25 -0300 Subject: [PATCH 17/23] `ImportPath`: fix `style` parameter documentation. --- lib/src/import_path_base.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/import_path_base.dart b/lib/src/import_path_base.dart index 6cb0deb..bbc3565 100644 --- a/lib/src/import_path_base.dart +++ b/lib/src/import_path_base.dart @@ -164,7 +164,7 @@ class ImportPath { void printMessage(Object? m) => messagePrinter(m); /// Executes the import search and prints the results. - /// - If [dots] is `true` it prints the tree in `dots` style + /// - [style] defines the output format. /// - See [printMessage] and [ASCIIArtTree]. Future execute( {ImportPathStyle style = ImportPathStyle.elegant}) async { @@ -214,7 +214,7 @@ class ImportPath { } /// Performs the imports search and returns the tree. - /// - If [dots] is `true` uses the `dots` style for the tree. + /// - [style] defines [ASCIIArtTree.style]. /// - See [ASCIIArtTree]. Future search( {ImportPathStyle style = ImportPathStyle.elegant}) async { From faeb219ad5404985a9a52d9d330bf479e7b0d32e Mon Sep 17 00:00:00 2001 From: gmpassos Date: Tue, 5 Sep 2023 23:20:17 -0300 Subject: [PATCH 18/23] `ImportPath`: fix `style` parameter documentation. --- lib/src/import_path_base.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/import_path_base.dart b/lib/src/import_path_base.dart index bbc3565..68be71d 100644 --- a/lib/src/import_path_base.dart +++ b/lib/src/import_path_base.dart @@ -164,7 +164,7 @@ class ImportPath { void printMessage(Object? m) => messagePrinter(m); /// Executes the import search and prints the results. - /// - [style] defines the output format. + /// - [style] defines the output format. Default: [ImportPathStyle.elegant] /// - See [printMessage] and [ASCIIArtTree]. Future execute( {ImportPathStyle style = ImportPathStyle.elegant}) async { @@ -214,7 +214,7 @@ class ImportPath { } /// Performs the imports search and returns the tree. - /// - [style] defines [ASCIIArtTree.style]. + /// - [style] defines [ASCIIArtTree] style. Default: [ImportPathStyle.elegant] /// - See [ASCIIArtTree]. Future search( {ImportPathStyle style = ImportPathStyle.elegant}) async { From ae1ddb08e85766a791df48c1c40b39c05a89554c Mon Sep 17 00:00:00 2001 From: gmpassos Date: Thu, 7 Sep 2023 15:21:13 -0300 Subject: [PATCH 19/23] `_searchImportPaths`: rename `node` to `currentNode` --- lib/src/import_path_base.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/import_path_base.dart b/lib/src/import_path_base.dart index 68be71d..dff606f 100644 --- a/lib/src/import_path_base.dart +++ b/lib/src/import_path_base.dart @@ -257,7 +257,7 @@ class ImportPath { } List> _searchImportPaths( - Uri node, { + Uri currentNode, { String? stripSearchRoot, Set? walked, List? parents, @@ -266,19 +266,19 @@ class ImportPath { found ??= []; if (walked == null) { - walked = {node}; - } else if (!walked.add(node)) { + walked = {currentNode}; + } else if (!walked.add(currentNode)) { return found; } - final nodePath = node.toString(); + final nodePath = currentNode.toString(); if (parents == null) { parents = [nodePath]; } else { parents.add(nodePath); } - var newImports = _importsFor(node, quiet: quiet) + var newImports = _importsFor(currentNode, quiet: quiet) .where((uri) => !walked!.contains(uri)) .toList(growable: false); From 955c8c88bf519046e60b70c6906932f6ee9edcce Mon Sep 17 00:00:00 2001 From: gmpassos Date: Mon, 11 Sep 2023 06:49:27 -0300 Subject: [PATCH 20/23] - Moved code to class `ImportPath`, `ImportPathScanner` and `ImportParser`: - `ImportParser`: - Added faster parser. - Added support to conditional imports. - `ImportPathScanner`: - Added support to final all the target imports. - Optimized resolution of paths graph. - CLI: - `--format`: Defines the style for the output tree (elegant, dots, json). - `--fast`: to enable a fast import parser. - ascii_art_tree: ^1.0.5 - graph_explorer: ^1.0.0 --- AUTHORS | 4 +- CHANGELOG.md | 19 ++- LICENSE | 2 +- bin/import_path.dart | 9 +- example/example.dart | 4 +- lib/import_path.dart | 6 +- lib/src/import_path_base.dart | 260 +++++++++++-------------------- lib/src/import_path_parser.dart | 190 ++++++++++++++++++++++ lib/src/import_path_scanner.dart | 93 +++++++++++ pubspec.yaml | 3 +- test/import_path_test.dart | 55 +++++-- 11 files changed, 447 insertions(+), 198 deletions(-) create mode 100644 lib/src/import_path_parser.dart create mode 100644 lib/src/import_path_scanner.dart diff --git a/AUTHORS b/AUTHORS index b4661b6..4aedb8c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,6 +1,6 @@ # Below is a list of people and organizations that have contributed # to the project. Names should be added to the list like so: # -# Name/Organization +# Name/Organization -Jacob MacDonald +Graciliano M. Passos: gmpassos @ GitHub diff --git a/CHANGELOG.md b/CHANGELOG.md index b8facae..e253723 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # 1.2.0 -- Moved code to class `ImportPath`: +- Moved code to class `ImportPath`, `ImportPathScanner` and `ImportParser`: - Allows integration with other packages. - Facilitates tests. - Using `ascii_art_tree` to show the output tree, with styles `dots` (original) and `elegant`. @@ -8,11 +8,17 @@ - Added options: - `--regexp`: to use `RegExp` to match the target import. - `--all`: to find all the import paths. - - `-q`: for a quiet output (only displays found paths). - - `-s`: strips the search root directory from displayed import paths. - - `--elegant`: use `elegant` style for the output tree. - - `--dots`: use `dots` style for the output tree. + - `--quiet`: for a quiet output (only displays found paths). + - `--strip`: strips the search root directory from displayed import paths. + - `--format`: Defines the style for the output tree (elegant, dots, json). + - `--fast`: to enable a fast import parser. - Improved help with examples. +- `ImportParser`: + - Added faster parser. + - Added support to conditional imports. +- `ImportPathScanner`: + - Added support to final all the target imports. + - Optimized resolution of paths graph. - Updated `README.md` to show CLI and Library usage. - Added tests and coverage (80%). - Added GitHub Dart CI. @@ -22,7 +28,8 @@ - package_config: ^2.1.0 - path: ^1.8.3 - args: ^2.4.2 - - ascii_art_tree: ^1.0.4 + - ascii_art_tree: ^1.0.5 + - graph_explorer: ^1.0.0 - lints: ^1.0.1 - dependency_validator: ^3.2.2 - test: ^1.21.4 diff --git a/LICENSE b/LICENSE index d7815a0..d081fba 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2020, Google Inc. All rights reserved. +Copyright 2020, Graciliano M. P. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/bin/import_path.dart b/bin/import_path.dart index e3d4d30..41cdc8f 100644 --- a/bin/import_path.dart +++ b/bin/import_path.dart @@ -58,6 +58,12 @@ void main(List args) async { negatable: false, help: "Parses `%targetImport` as a `RegExp`."); + argsParser.addFlag('fast', + abbr: 'z', + negatable: false, + help: + "Uses a fast Dart parser (only parses the import directives at the top)."); + argsParser.addFlag('quiet', abbr: 'q', negatable: false, @@ -81,6 +87,7 @@ void main(List args) async { var findAll = argsResult['all'] as bool; var quiet = argsResult['quiet'] as bool; var strip = argsResult['strip'] as bool; + var fast = argsResult['fast'] as bool; var format = argsResult['format'] as String; @@ -93,7 +100,7 @@ void main(List args) async { regexp ? RegExp(importToFindArg) : Uri.base.resolve(importToFindArg); var importPath = ImportPath(from, importToFind, - findAll: findAll, quiet: quiet, strip: strip); + findAll: findAll, quiet: quiet, strip: strip, fastParser: fast); await importPath.execute(style: style); } diff --git a/example/example.dart b/example/example.dart index b70a06f..a49c6fc 100644 --- a/example/example.dart +++ b/example/example.dart @@ -18,8 +18,10 @@ void main(List args) async { // » Search entry point: file:///workspace/import_path/bin/import_path.dart // » Stripping search root from displayed imports: file:///workspace/import_path/ // » Searching for the shortest import path for `package:analyzer/dart/ast/ast.dart`... +// » Search finished [total time: 299 ms, resolve paths time: 5 ms] // // bin/import_path.dart // └─┬─ package:import_path/import_path.dart -// └─┬─ package:import_path/src/import_path_base.dart +// └─┬─ package:import_path/src/import_path_parser.dart // └──> package:analyzer/dart/ast/ast.dart +// diff --git a/lib/import_path.dart b/lib/import_path.dart index 1710cba..e794a63 100644 --- a/lib/import_path.dart +++ b/lib/import_path.dart @@ -3,10 +3,12 @@ library import_path; export 'src/import_path_base.dart' show - ImportPath, ImportPathStyle, parseImportPathStyle, ImportPathStyleExtension, ImportToFind, ImportToFindURI, - ImportToFindRegExp; + ImportToFindRegExp, + ImportPath; +export 'src/import_path_parser.dart' show ImportParser; +export 'src/import_path_scanner.dart' show ImportPathScanner; diff --git a/lib/src/import_path_base.dart b/lib/src/import_path_base.dart index dff606f..607b3f4 100644 --- a/lib/src/import_path_base.dart +++ b/lib/src/import_path_base.dart @@ -1,19 +1,49 @@ // Copyright (c) 2020, Google Inc. 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 'dart:convert'; +// +// Based on the original work of: +// - Jacob MacDonald: jakemac53 on GitHub +// +// Conversion to library: +// - Graciliano M. Passos: gmpassos @ GitHub +// + +import 'dart:convert' show JsonEncoder; import 'dart:io'; -import 'package:analyzer/dart/analysis/utilities.dart'; -import 'package:analyzer/dart/ast/ast.dart'; import 'package:ascii_art_tree/ascii_art_tree.dart'; -import 'package:package_config/package_config.dart'; +import 'package:graph_explorer/graph_explorer.dart'; import 'package:path/path.dart' as p; +import 'import_path_parser.dart'; +import 'import_path_scanner.dart'; + +typedef MessagePrinter = void Function(Object? m); + +/// Base class [ImportPath], [ImportPathScanner] and [ImportParser]. +abstract class ImportWidget { + /// If `true`, we won't call [printMessage]. + final bool quiet; + + /// The function to print messages/text. Default: [print]. + /// Called by [printMessage]. + MessagePrinter messagePrinter; + + ImportWidget({this.quiet = false, this.messagePrinter = print}); + + /// Prints a message/text. + /// - See [messagePrinter]. + void printMessage(Object? m) => messagePrinter(m); +} + /// The import to find using [ImportPath]. /// - See [ImportToFindURI] and [ImportToFindRegExp]. -abstract class ImportToFind { +abstract class ImportToFind extends NodeMatcher { + /// Resolves [o] (an [Uri], [String], [RegExp] or [ImportToFind]) + /// and returns an [ImportToFind]. + /// - If [o] is a [String] it should be a valid [Uri]. + /// - See [ImportToFindURI] and [ImportToFindRegExp]. factory ImportToFind.from(Object o) { if (o is ImportToFind) { return o; @@ -30,6 +60,9 @@ abstract class ImportToFind { ImportToFind(); + @override + bool matchesValue(Uri value) => matches(value); + /// Returns `true` if [importUri] matches the import to find. bool matches(Uri importUri); } @@ -95,39 +128,46 @@ extension ImportPathStyleExtension on ImportPathStyle { } } -/// An Import Path search tool. -class ImportPath { +/// Import Path search tool. +class ImportPath extends ImportWidget { /// The entry point to start the search. final Uri from; /// The import to find. Can be an [Uri] or a [RegExp]. + /// See [ImportToFind.from]. final ImportToFind importToFind; /// If `true` searches for all import matches. + /// See [ImportPathScanner.findAll]. final bool findAll; - /// If `true`, we won't call [printMessage] while searching. - final bool quiet; - /// If `true` remove from paths the [searchRoot]. final bool strip; + /// If `true`, it will use a fast parser that attempts to + /// parse only the import section of Dart files. Default: `false`. + /// See [ImportPathScanner.fastParser]. + final bool fastParser; + + /// If `true`, it will also scan imports that depend on an `if` resolution. Default: `true`. + /// See [ImportPathScanner.includeConditionalImports]. + final bool includeConditionalImports; + /// The search root to strip from the displayed import paths. /// - If `searchRoot` is not provided at construction it's resolved /// using [from] parent directory (see [resolveSearchRoot]). late String searchRoot; - /// The function to print messages/text. Default: [print]. - /// Called by [printMessage]. - void Function(Object? m) messagePrinter; - ImportPath(this.from, Object importToFind, {this.findAll = false, - this.quiet = false, + bool quiet = false, this.strip = false, + this.fastParser = false, + this.includeConditionalImports = true, String? searchRoot, - this.messagePrinter = print}) - : importToFind = ImportToFind.from(importToFind) { + MessagePrinter messagePrinter = print}) + : importToFind = ImportToFind.from(importToFind), + super(quiet: quiet, messagePrinter: messagePrinter) { this.searchRoot = searchRoot ?? resolveSearchRoot(); } @@ -158,36 +198,21 @@ class ImportPath { /// See [searchRoot]. String? get _stripSearchRoot => strip ? searchRoot : null; - /// Prints a message/text. - /// - Called by [execute]. - /// - See [messagePrinter]. - void printMessage(Object? m) => messagePrinter(m); - /// Executes the import search and prints the results. /// - [style] defines the output format. Default: [ImportPathStyle.elegant] - /// - See [printMessage] and [ASCIIArtTree]. + /// - See [search], [printMessage] and [ASCIIArtTree]. Future execute( - {ImportPathStyle style = ImportPathStyle.elegant}) async { - if (!quiet) { - printMessage('» Search entry point: $from'); + {ImportPathStyle style = ImportPathStyle.elegant, + Directory? packageDirectory}) async { + var tree = await search( + style: style.asASCIIArtTreeStyle ?? ASCIIArtTreeStyle.elegant, + packageDirectory: packageDirectory); - if (strip) { - printMessage( - '» Stripping search root from displayed imports: $searchRoot'); - } - - if (findAll) { - printMessage( - '» Searching for all import paths for `$importToFind`...\n'); - } else { - printMessage( - '» Searching for the shortest import path for `$importToFind`...\n'); + if (tree != null) { + if (!quiet) { + printMessage(''); } - } - - var tree = await search(style: style); - if (tree != null) { if (style == ImportPathStyle.json) { var j = JsonEncoder.withIndent(' ').convert(tree.toJson()); printMessage(j); @@ -202,7 +227,7 @@ class ImportPath { printMessage( '» Unable to find an import path from $from to $importToFind'); } else { - var totalFoundPaths = tree.totalLeafs; + var totalFoundPaths = tree.totalLeaves; if (totalFoundPaths > 1) { printMessage( '» Found $totalFoundPaths import paths from $from to $importToFind\n'); @@ -214,143 +239,38 @@ class ImportPath { } /// Performs the imports search and returns the tree. - /// - [style] defines [ASCIIArtTree] style. Default: [ImportPathStyle.elegant] - /// - See [ASCIIArtTree]. + /// - [style] defines [ASCIIArtTree] style. Default: [ASCIIArtTreeStyle.elegant] + /// - See [searchPaths] and [ASCIIArtTree]. Future search( - {ImportPathStyle style = ImportPathStyle.elegant}) async { - var foundPaths = await searchPaths(); - if (foundPaths == null || foundPaths.isEmpty) return null; + {ASCIIArtTreeStyle style = ASCIIArtTreeStyle.elegant, + Directory? packageDirectory}) async { + var foundPaths = await searchPaths(packageDirectory: packageDirectory); + if (foundPaths.isEmpty) return null; var asciiArtTree = ASCIIArtTree.fromPaths( foundPaths, stripPrefix: _stripSearchRoot, - style: style.asASCIIArtTreeStyle ?? ASCIIArtTreeStyle.elegant, + style: style, ); return asciiArtTree; } - late PackageConfig _packageConfig; - late String _generatedDir; - /// Performs the imports search and returns the found import paths. - Future>?> searchPaths() async { - var currentDir = Directory.current; - _packageConfig = (await findPackageConfig(currentDir))!; - _generatedDir = p.join('.dart_tool/build/generated'); - - var root = from.scheme == 'package' ? _packageConfig.resolve(from)! : from; - - var foundPaths = - _searchImportPaths(root, stripSearchRoot: _stripSearchRoot); - if (foundPaths.isEmpty) { - return null; - } - - if (!findAll) { - foundPaths.sort((a, b) => a.length.compareTo(b.length)); - var shortest = foundPaths.first; - return [shortest]; - } else { - return foundPaths; - } - } - - List> _searchImportPaths( - Uri currentNode, { - String? stripSearchRoot, - Set? walked, - List? parents, - List>? found, - }) { - found ??= []; - - if (walked == null) { - walked = {currentNode}; - } else if (!walked.add(currentNode)) { - return found; - } - - final nodePath = currentNode.toString(); - if (parents == null) { - parents = [nodePath]; - } else { - parents.add(nodePath); - } - - var newImports = _importsFor(currentNode, quiet: quiet) - .where((uri) => !walked!.contains(uri)) - .toList(growable: false); - - for (var import in newImports) { - if (importToFind.matches(import)) { - var foundPath = [...parents, import.toString()]; - found.add(foundPath); - } else { - _searchImportPaths(import, - stripSearchRoot: stripSearchRoot, - walked: walked, - parents: parents, - found: found); - } - } - - var rm = parents.removeLast(); - assert(rm == nodePath); - - return found; - } - - List _importsFor(Uri uri, {required bool quiet}) { - if (uri.scheme == 'dart') return []; - - var filePath = (uri.scheme == 'package' ? _packageConfig.resolve(uri) : uri) - ?.toFilePath(); - - if (filePath == null) { - if (!quiet) { - printMessage('» [WARNING] Unable to resolve Uri $uri, skipping it'); - } - return []; - } - - var file = File(filePath); - // Check the generated dir for package:build - if (!file.existsSync()) { - var package = uri.scheme == 'package' - ? _packageConfig[uri.pathSegments.first] - : _packageConfig.packageOf(uri); - if (package == null) { - if (!quiet) { - printMessage('» [WARNING] Unable to read file at $uri, skipping it'); - } - return []; - } - - var path = uri.scheme == 'package' - ? p.joinAll(uri.pathSegments.skip(1)) - : p.relative(uri.path, from: package.root.path); - file = File(p.join(_generatedDir, package.name, path)); - if (!file.existsSync()) { - if (!quiet) { - printMessage('» [WARNING] Unable to read file at $uri, skipping it'); - } - return []; - } - } - - var contents = file.readAsStringSync(); - - var parsed = parseString(content: contents, throwIfDiagnostics: false); - return parsed.unit.directives - .whereType() - .where((directive) { - if (directive.uri.stringValue == null && !quiet) { - printMessage('Empty uri content: ${directive.uri}'); - } - return directive.uri.stringValue != null; - }) - .map((directive) => uri.resolve(directive.uri.stringValue!)) - .toList(); + /// - [packageDirectory] is used to resolve . Default: [Directory.current]. + /// - Uses [ImportPathScanner]. + Future>> searchPaths({Directory? packageDirectory}) async { + var importPathScanner = ImportPathScanner( + findAll: findAll, + fastParser: fastParser, + messagePrinter: messagePrinter, + quiet: quiet, + includeConditionalImports: includeConditionalImports); + + var foundPaths = await importPathScanner.searchPaths(from, importToFind, + stripSearchRoot: _stripSearchRoot, packageDirectory: packageDirectory); + + var foundPathsStr = foundPaths.toListOfStringPaths(); + return foundPathsStr; } } diff --git a/lib/src/import_path_parser.dart b/lib/src/import_path_parser.dart new file mode 100644 index 0000000..11ea7d0 --- /dev/null +++ b/lib/src/import_path_parser.dart @@ -0,0 +1,190 @@ +// Copyright (c) 2020, Google Inc. 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. +// +// Based on the original work of: +// - Jacob MacDonald: jakemac53 on GitHub +// +// Faster parser by: +// - Graciliano M. Passos: gmpassos @ GitHub +// + +import 'dart:io'; + +import 'package:analyzer/dart/analysis/results.dart'; +import 'package:analyzer/dart/analysis/utilities.dart'; +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:package_config/package_config.dart'; +import 'package:path/path.dart' as p; + +import 'import_path_base.dart'; + +/// Dart import parser. +/// Parses for import directives in Dart files. +class ImportParser extends ImportWidget { + /// The Dart package [Directory], root of [PackageConfig]. + final Directory packageDirectory; + + /// If `true`, it will include the imports that + /// depend on an `if` resolution. Default: `true`. + final bool includeConditionalImports; + + /// If `true`, it will use a fast parser that attempts to + /// parse only the import section of Dart files. Default: `false`. + /// + /// The faster parser is usually 2-3 times faster. + /// To perform fast parsing, it first tries to detect the last import line + /// using a simple [RegExp] match, then parses the file only up to the last + /// import. If it fails, it falls back to full-file parsing. + final bool fastParser; + + final PackageConfig _packageConfig; + final String _generatedDir; + + ImportParser(this.packageDirectory, this._packageConfig, + {this.includeConditionalImports = true, + this.fastParser = false, + bool quiet = false, + MessagePrinter messagePrinter = print}) + : _generatedDir = p.join('.dart_tool/build/generated'), + super(quiet: quiet, messagePrinter: messagePrinter); + + static Future from(Directory packageDirectory, + {bool includeConditionalImports = true, + bool fastParser = false, + bool quiet = false, + MessagePrinter messagePrinter = print}) async { + var packageConfig = (await findPackageConfig(packageDirectory))!; + return ImportParser(packageDirectory, packageConfig, + includeConditionalImports: includeConditionalImports, + fastParser: fastParser, + quiet: quiet, + messagePrinter: messagePrinter); + } + + /// Resolves an [uri] from [packageDirectory]. + Uri resolveUri(Uri uri) => + uri.scheme == 'package' ? _packageConfig.resolve(uri)! : uri; + + /// Returns the imports for [uri]. + List importsFor(Uri uri) { + if (uri.scheme == 'dart') return []; + + final isSchemePackage = uri.scheme == 'package'; + + var filePath = + (isSchemePackage ? _packageConfig.resolve(uri) : uri)?.toFilePath(); + + if (filePath == null) { + if (!quiet) { + printMessage('» [WARNING] Unable to resolve Uri $uri, skipping it'); + } + return []; + } + + var file = File(filePath); + + // Check the [_generatedDir] for package:build + if (!file.existsSync()) { + var package = isSchemePackage + ? _packageConfig[uri.pathSegments.first] + : _packageConfig.packageOf(uri); + + if (package == null) { + if (!quiet) { + printMessage('» [WARNING] Unable to read file at $uri, skipping it'); + } + return []; + } + + var path = isSchemePackage + ? p.joinAll(uri.pathSegments.skip(1)) + : p.relative(uri.path, from: package.root.path); + + file = File(p.join(_generatedDir, package.name, path)); + if (!file.existsSync()) { + if (!quiet) { + printMessage('» [WARNING] Unable to read file at $uri, skipping it'); + } + return []; + } + } + + var content = file.readAsStringSync(); + + var importDirectives = _parseImportDirectives(content, quiet); + var importsUris = _filterImportDirectiveUris(importDirectives, uri); + + return importsUris; + } + + List _filterImportDirectiveUris( + Iterable importDirectives, Uri uri) => + importDirectives + .expand((directive) { + var mainUri = directive.uri.stringValue!; + + if (includeConditionalImports && + directive.configurations.isNotEmpty) { + var conditional = directive.configurations + .map((e) => e.uri.stringValue) + .whereType() + .toList(); + + var multiple = [mainUri, ...conditional]; + return multiple; + } else { + return [mainUri]; + } + }) + .map((directiveUriStr) => uri.resolve(directiveUriStr)) + .toList(); + + Iterable _parseImportDirectives( + String content, bool quiet) { + if (fastParser) { + var header = _extractHeader(content); + if (header != null) { + var headerParsed = + parseString(content: header, throwIfDiagnostics: false); + if (headerParsed.errors.isEmpty) { + return _filterImportDirectives(headerParsed, quiet); + } + } + } + + var parsed = parseString(content: content, throwIfDiagnostics: false); + return _filterImportDirectives(parsed, quiet); + } + + Iterable _filterImportDirectives( + ParseStringResult parsed, bool quiet) { + var importDirectives = parsed.unit.directives + .whereType() + .where((directive) { + var uriNull = directive.uri.stringValue == null; + if (uriNull && !quiet) { + printMessage('Empty uri content: ${directive.uri}'); + } + return !uriNull; + }); + + return importDirectives; + } + + static final _regExpImport = RegExp( + r'''(?:^|\n)[ \t]*(?:import|export)\s*['"][^\r\n'"]+?['"]\s*.*?;''', + dotAll: true); + + String? _extractHeader(String content) { + var importMatches = + _regExpImport.allMatches(content).toList(growable: false); + + if (importMatches.isEmpty) return null; + + var headEndIdx = importMatches.last.end; + var header = content.substring(0, headEndIdx); + + return header.isNotEmpty ? header : null; + } +} diff --git a/lib/src/import_path_scanner.dart b/lib/src/import_path_scanner.dart new file mode 100644 index 0000000..49e5191 --- /dev/null +++ b/lib/src/import_path_scanner.dart @@ -0,0 +1,93 @@ +// Copyright (c) 2020, Google Inc. 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. +// +// Based on the original work of (shortest path): +// - Jacob MacDonald: jakemac53 on GitHub +// +// Resolve all import paths and optimizations by: +// - Graciliano M. Passos: gmpassos @ GitHub +// + +import 'dart:io'; + +import 'package:graph_explorer/graph_explorer.dart'; + +import 'import_path_base.dart'; +import 'import_path_parser.dart'; + +/// Import Path Scanner tool. +class ImportPathScanner extends ImportWidget { + /// If `true` searches for all import matches. + final bool findAll; + + /// If `true`, it will use a fast parser that attempts to + /// parse only the import section of Dart files. Default: `false`. + /// See [ImportParser.fastParser]. + final bool fastParser; + + /// If `true`, it will also scan imports that depend on an `if` resolution. Default: `true`. + /// See [ImportParser.includeConditionalImports]. + final bool includeConditionalImports; + + ImportPathScanner( + {this.findAll = false, + bool quiet = false, + this.fastParser = false, + this.includeConditionalImports = true, + MessagePrinter messagePrinter = print}) + : super(quiet: quiet, messagePrinter: messagePrinter); + + Future>>> searchPaths(Uri from, ImportToFind importToFind, + {Directory? packageDirectory, String? stripSearchRoot}) async { + packageDirectory ??= Directory.current; + + final importParser = await ImportParser.from(packageDirectory, + includeConditionalImports: includeConditionalImports, + fastParser: fastParser, + quiet: quiet, + messagePrinter: messagePrinter); + + var scanner = GraphScanner(findAll: findAll); + + if (!quiet) { + printMessage('» Search entry point: $from'); + + if (stripSearchRoot != null) { + printMessage( + '» Stripping search root from displayed imports: $stripSearchRoot'); + } + + var msgSearching = fastParser ? 'Fast searching' : 'Searching'; + + if (findAll) { + printMessage( + '» $msgSearching for all import paths for `$importToFind`...'); + } else { + printMessage( + '» $msgSearching for the shortest import path for `$importToFind`...'); + } + } + + var result = await scanner.scanPathsFrom( + from, + importToFind, + outputsProvider: (graph, node) => importParser + .importsFor(node.value) + .map((uri) => graph.node(uri)) + .toList(), + ); + + var paths = result.paths; + if (!findAll) { + paths = paths.shortestPaths(); + } + + if (!quiet) { + printMessage( + "» Search finished [total time: ${result.time.inMilliseconds} ms, resolve paths time: ${result.resolvePathsTime.inMilliseconds} ms]"); + } + + return paths; + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 45c90ea..3012bb9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,8 @@ dependencies: package_config: ^2.1.0 path: ^1.8.3 args: ^2.4.2 - ascii_art_tree: ^1.0.4 + ascii_art_tree: ^1.0.5 + graph_explorer: ^1.0.0 dev_dependencies: lints: ^1.0.1 diff --git a/test/import_path_test.dart b/test/import_path_test.dart index 68ff6dc..5348e1c 100644 --- a/test/import_path_test.dart +++ b/test/import_path_test.dart @@ -1,7 +1,7 @@ -import 'dart:io'; import 'dart:convert' show JsonEncoder; +import 'dart:io'; + import 'package:import_path/import_path.dart'; -import 'package:import_path/src/import_path_base.dart'; import 'package:path/path.dart' as pack_path; import 'package:test/test.dart'; @@ -13,7 +13,7 @@ void main() { strip: false, dots: false, quiet: false, expectedTreeText: r''' file://.../import_path.dart └─┬─ package:import_path/import_path.dart - └─┬─ package:import_path/src/import_path_base.dart + └─┬─ package:import_path/src/import_path_parser.dart └──> package:analyzer/dart/ast/ast.dart ''')); @@ -23,7 +23,7 @@ file://.../import_path.dart strip: false, dots: false, quiet: true, expectedTreeText: r''' file://.../import_path.dart └─┬─ package:import_path/import_path.dart - └─┬─ package:import_path/src/import_path_base.dart + └─┬─ package:import_path/src/import_path_parser.dart └──> package:analyzer/dart/ast/ast.dart ''')); @@ -33,7 +33,7 @@ file://.../import_path.dart strip: false, dots: true, quiet: false, expectedTreeText: r''' file://.../import_path.dart ..package:import_path/import_path.dart -....package:import_path/src/import_path_base.dart +....package:import_path/src/import_path_parser.dart ......package:analyzer/dart/ast/ast.dart ''')); @@ -43,7 +43,7 @@ file://.../import_path.dart strip: true, dots: true, quiet: false, expectedTreeText: r''' bin/import_path.dart ..package:import_path/import_path.dart -....package:import_path/src/import_path_base.dart +....package:import_path/src/import_path_parser.dart ......package:analyzer/dart/ast/ast.dart ''')); }); @@ -58,10 +58,10 @@ bin/import_path.dart all: true, expectedTreeText: RegExp(r'''^bin/import_path.dart \..package:import_path/import_path.dart -\....package:import_path/src/import_path_base.dart -\......package:analyzer/dart/analysis/utilities.dart -.*?\........package:analyzer/dart/ast/ast.dart.*? -\......package:analyzer/dart/ast/ast.dart\s*$''', dotAll: true))); +\....package:import_path/src/import_path_parser.dart +\......package:analyzer/dart/ast/ast.dart +.*? +\.....\.+package:analyzer/dart/ast/ast.dart\s*''', dotAll: true))); }); } @@ -85,8 +85,9 @@ Future doSearchTest( }, ); - var tree = await importPath.execute( - style: dots ? ImportPathStyle.dots : ImportPathStyle.elegant); + var style = dots ? ImportPathStyle.dots : ImportPathStyle.elegant; + + var tree = await importPath.execute(style: style); expect(tree, isNotNull); var outputIdx = 0; @@ -101,13 +102,19 @@ Future doSearchTest( expect( output[outputIdx++], equals( - '» Searching for all import paths for `package:analyzer/dart/ast/ast.dart`...\n')); + '» Searching for all import paths for `package:analyzer/dart/ast/ast.dart`...')); } else { expect( output[outputIdx++], equals( - '» Searching for the shortest import path for `package:analyzer/dart/ast/ast.dart`...\n')); + '» Searching for the shortest import path for `package:analyzer/dart/ast/ast.dart`...')); } + + expect( + output[outputIdx++], + matches(RegExp( + r'» Search finished \[total time: \d+ ms, resolve paths time: \d+ ms\]'))); + expect(output[outputIdx++], equals('')); } var treeText = output[outputIdx++] @@ -143,6 +150,26 @@ Future doSearchTest( JsonEncoder.withIndent(' ').convert(tree2?.toJson()), ])); } + + { + var output3 = []; + + var importPath3 = ImportPath( + _resolveFileUri('bin/import_path.dart'), + 'package:analyzer/dart/ast/ast.dart', + strip: strip, + quiet: false, + findAll: all, + fastParser: true, + messagePrinter: (m) => output3.add(m), + ); + + var tree3 = await importPath3.execute(style: style); + + expect(tree3?.generate(), equals(tree?.generate())); + + expect(output3, contains(startsWith("» Fast searching "))); + } } Uri _resolveFileUri(String targetFilePath) { From 944b3e712d2f6626ca0f1be723fe3de5a1d5e83a Mon Sep 17 00:00:00 2001 From: gmpassos Date: Mon, 11 Sep 2023 06:50:16 -0300 Subject: [PATCH 21/23] rollback license. --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index d081fba..d7815a0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2020, Graciliano M. P. All rights reserved. +Copyright 2020, Google Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: From 750acb1b6f4e4713f883e5e986f06282fe7756e0 Mon Sep 17 00:00:00 2001 From: Graciliano Monteiro Passos Date: Tue, 12 Sep 2023 17:04:57 -0300 Subject: [PATCH 22/23] Update CHANGELOG.md Co-authored-by: Jacob MacDonald --- CHANGELOG.md | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e253723..79807be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,7 @@ # 1.2.0 -- Moved code to class `ImportPath`, `ImportPathScanner` and `ImportParser`: - - Allows integration with other packages. - - Facilitates tests. -- Using `ascii_art_tree` to show the output tree, with styles `dots` (original) and `elegant`. - CLI: + - Using `ascii_art_tree` to show the output tree, with styles `dots` (original) and `elegant`. - Added options: - `--regexp`: to use `RegExp` to match the target import. - `--all`: to find all the import paths. @@ -13,27 +10,9 @@ - `--format`: Defines the style for the output tree (elegant, dots, json). - `--fast`: to enable a fast import parser. - Improved help with examples. -- `ImportParser`: - - Added faster parser. - - Added support to conditional imports. -- `ImportPathScanner`: - - Added support to final all the target imports. - - Optimized resolution of paths graph. +- Added support for conditional imports. +- Added public libraries to facilitate integration with other packages. - Updated `README.md` to show CLI and Library usage. -- Added tests and coverage (80%). -- Added GitHub Dart CI. - -- Updated dependencies compatible with Dart `2.14.0` (was already dependent to SDK `2.14.0`): - - sdk: '>=2.14.0 <3.0.0' - - package_config: ^2.1.0 - - path: ^1.8.3 - - args: ^2.4.2 - - ascii_art_tree: ^1.0.5 - - graph_explorer: ^1.0.0 - - lints: ^1.0.1 - - dependency_validator: ^3.2.2 - - test: ^1.21.4 - - coverage: ^1.2.0 # 1.1.1 From 6e272093a5bbc39b4d16a6184837566ce2741200 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Tue, 19 Sep 2023 04:03:21 -0300 Subject: [PATCH 23/23] `ImportParser.importsFor`: added internal cache ; ascii_art_tree: ^1.0.6 ; graph_explorer: ^1.0.1 --- lib/src/import_path_parser.dart | 19 ++++++++++++++++++- lib/src/import_path_scanner.dart | 1 + pubspec.yaml | 4 ++-- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/src/import_path_parser.dart b/lib/src/import_path_parser.dart index 11ea7d0..ba78c0c 100644 --- a/lib/src/import_path_parser.dart +++ b/lib/src/import_path_parser.dart @@ -9,6 +9,7 @@ // - Graciliano M. Passos: gmpassos @ GitHub // +import 'dart:collection'; import 'dart:io'; import 'package:analyzer/dart/analysis/results.dart'; @@ -66,8 +67,24 @@ class ImportParser extends ImportWidget { Uri resolveUri(Uri uri) => uri.scheme == 'package' ? _packageConfig.resolve(uri)! : uri; + final Map> _importsCache = {}; + + /// Disposes the internal imports cache. + void disposeCache() { + _importsCache.clear(); + } + /// Returns the imports for [uri]. - List importsFor(Uri uri) { + /// - If [cached] is `true` will use the internal cache of resolved imports. + List importsFor(Uri uri, {bool cached = true}) { + if (cached) { + return UnmodifiableListView(_importsCache[uri] ??= _importsForImpl(uri)); + } else { + return _importsForImpl(uri); + } + } + + List _importsForImpl(Uri uri) { if (uri.scheme == 'dart') return []; final isSchemePackage = uri.scheme == 'package'; diff --git a/lib/src/import_path_scanner.dart b/lib/src/import_path_scanner.dart index 49e5191..3055d1e 100644 --- a/lib/src/import_path_scanner.dart +++ b/lib/src/import_path_scanner.dart @@ -76,6 +76,7 @@ class ImportPathScanner extends ImportWidget { .importsFor(node.value) .map((uri) => graph.node(uri)) .toList(), + maxExpansion: 100, ); var paths = result.paths; diff --git a/pubspec.yaml b/pubspec.yaml index 3012bb9..a342fd4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,8 +11,8 @@ dependencies: package_config: ^2.1.0 path: ^1.8.3 args: ^2.4.2 - ascii_art_tree: ^1.0.5 - graph_explorer: ^1.0.0 + ascii_art_tree: ^1.0.6 + graph_explorer: ^1.0.1 dev_dependencies: lints: ^1.0.1