| // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| /// Command to query for code dependencies. Currently this tool only |
| /// supports the `some_path` query, which gives you the shortest path for how |
| /// one function depends on another. |
| /// |
| /// You can run this tool as follows: |
| /// ```bash |
| /// pub global activate dart2js_info |
| /// dart2js_info code_deps some_path out.js.info.json main foo |
| /// ``` |
| /// |
| /// The arguments to the query are regular expressions that can be used to |
| /// select a single element in your program. If your regular expression is too |
| /// general and has more than one match, this tool will pick |
| /// the first match and ignore the rest. Regular expressions are matched against |
| /// a fully qualified element name, which includes the library and class name |
| /// (if any) that contains it. A typical qualified name is of this form: |
| /// |
| /// libraryName::ClassName.elementName |
| /// |
| /// If the name of a function your are looking for is unique enough, it might be |
| /// sufficient to just write that name as your regular expression. |
| library dart2js_info.bin.code_deps; |
| |
| import 'dart:collection'; |
| |
| import 'package:args/command_runner.dart'; |
| import 'package:collection/collection.dart'; |
| |
| import 'package:dart2js_info/info.dart'; |
| import 'package:dart2js_info/src/graph.dart'; |
| import 'package:dart2js_info/src/io.dart'; |
| import 'package:dart2js_info/src/util.dart'; |
| |
| import 'usage_exception.dart'; |
| |
| class CodeDepsCommand extends Command<void> with PrintUsageException { |
| @override |
| final String name = "code_deps"; |
| @override |
| final String description = ""; |
| |
| CodeDepsCommand() { |
| addSubcommand(_SomePathQuery()); |
| } |
| } |
| |
| class _SomePathQuery extends Command<void> with PrintUsageException { |
| @override |
| final String name = "some_path"; |
| @override |
| final String description = "find a call-graph path between two elements."; |
| |
| @override |
| void run() async { |
| var args = argResults!.rest; |
| if (args.length < 3) { |
| usageException("Missing arguments for some_path, expected: " |
| "info.data <element-regexp-1> <element-regexp-2>"); |
| } |
| |
| var info = await infoFromFile(args.first); |
| var graph = graphFromInfo(info); |
| |
| final source = |
| info.functions.firstWhereOrNull(_longNameMatcher(RegExp(args[1]))); |
| final target = |
| info.functions.firstWhereOrNull(_longNameMatcher(RegExp(args[2]))); |
| print('query: some_path'); |
| if (source == null) { |
| usageException("source '${args[1]}' not found in '${args[0]}'"); |
| } |
| print('source: ${longName(source)}'); |
| if (target == null) { |
| usageException("target '${args[2]}' not found in '${args[0]}'"); |
| } |
| print('target: ${longName(target)}'); |
| var path = SomePathQuery(source, target).run(graph); |
| if (path.isEmpty) { |
| print('result: no path found'); |
| } else { |
| print('result:'); |
| for (int i = 0; i < path.length; i++) { |
| print(' $i. ${longName(path[i])}'); |
| } |
| } |
| } |
| } |
| |
| /// A query supported by this tool. |
| abstract class Query { |
| run(Graph<Info> graph); |
| } |
| |
| /// Query that searches for a single path between two elements. |
| class SomePathQuery { |
| /// The info associated with the source element. |
| Info source; |
| |
| /// The info associated with the target element. |
| Info target; |
| |
| SomePathQuery(this.source, this.target); |
| |
| List<Info> run(Graph<Info?> graph) { |
| var seen = <Info?, Info?>{source: null}; |
| var queue = Queue<Info?>(); |
| queue.addLast(source); |
| while (queue.isNotEmpty) { |
| var node = queue.removeFirst(); |
| if (identical(node, target)) { |
| var result = Queue<Info>(); |
| while (node != null) { |
| result.addFirst(node); |
| node = seen[node]; |
| } |
| return result.toList(); |
| } |
| for (var neighbor in graph.targetsOf(node)) { |
| if (seen.containsKey(neighbor)) continue; |
| seen[neighbor] = node; |
| queue.addLast(neighbor); |
| } |
| } |
| return []; |
| } |
| } |
| |
| typedef LongNameMatcher = bool Function(FunctionInfo info); |
| |
| LongNameMatcher _longNameMatcher(RegExp regexp) => |
| (e) => regexp.hasMatch(longName(e)); |