-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
--path or -p to specify the path to the module --max-depth or -d to specify the max depth (previously unavailable)
- Loading branch information
Showing
7 changed files
with
198 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,33 +1,11 @@ | ||
import 'dart:io'; | ||
|
||
import 'package:cyclic_dependency_checks/cycle_detection/cycle_detector.dart'; | ||
import 'package:cyclic_dependency_checks/cycle_detection/module_dependency.dart'; | ||
import 'package:cyclic_dependency_checks/cycle_detection/module_dependency_graph.dart'; | ||
import 'package:cyclic_dependency_checks/cycle_detection/cycle_detector_runner.dart'; | ||
|
||
void main(List<String> args) async { | ||
await _run(path: args.firstOrNull ?? '.'); | ||
} | ||
|
||
Future _run({required String path}) async { | ||
final stopwatch = Stopwatch()..start(); | ||
final cycles = await CycleDetector().detect(path); | ||
stopwatch.stop(); | ||
final success = await CycleDetectorRunner().run(args); | ||
|
||
final formattedTime = stopwatch.elapsed.toString().substring(0, 11); | ||
|
||
if (cycles.isNotEmpty) { | ||
stderr.writeln('Detected cycles after ${formattedTime}'); | ||
for (final cycle in cycles) { | ||
cycle.printError(); | ||
} | ||
if (!success) { | ||
exit(1); | ||
} | ||
|
||
stdout.writeln('No import cycles were detected after ${formattedTime}'); | ||
} | ||
|
||
extension ErrorPrinting on List<ModuleDependency> { | ||
void printError() { | ||
stderr.writeln(path().join(' -> ')); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import 'dart:io'; | ||
|
||
import 'package:args/args.dart'; | ||
|
||
import 'cycle_detector.dart'; | ||
import 'module_dependency_graph.dart'; | ||
|
||
class Printer { | ||
out(String text) => stdout.writeln(text); | ||
|
||
err(String text) => stderr.writeln(text); | ||
} | ||
|
||
class CycleDetectorRunner { | ||
final CycleDetector detector; | ||
final Printer printer; | ||
|
||
CycleDetectorRunner({ | ||
CycleDetector? detector, | ||
Printer? printer, | ||
}) : detector = detector ?? CycleDetector(), | ||
printer = printer ?? Printer(); | ||
|
||
Future<bool> run(List<String> args) async { | ||
final parser = ArgParser() | ||
..addOption('path', abbr: 'p') | ||
..addOption('max-depth', abbr: 'd'); | ||
|
||
final parsedArgs = parser.tryParse(args); | ||
if (parsedArgs == null) { | ||
return false; | ||
} | ||
|
||
final path = parsedArgs.getString('path', fallback: '.'); | ||
final maxDepth = parsedArgs.tryGetInt('max-depth'); | ||
|
||
final stopwatch = Stopwatch()..start(); | ||
final cycles = await detector.detect(path, maxDepth: maxDepth); | ||
|
||
stopwatch.stop(); | ||
|
||
final formattedTime = stopwatch.elapsed.toString().substring(0, 11); | ||
|
||
if (cycles.isNotEmpty) { | ||
printer.err('Detected cycles after ${formattedTime}'); | ||
|
||
for (final cycle in cycles) { | ||
printer.err(cycle.path().join(' -> ')); | ||
} | ||
|
||
return false; | ||
} | ||
|
||
printer.out('No import cycles were detected after ${formattedTime}'); | ||
|
||
return true; | ||
} | ||
} | ||
|
||
extension _SafeParse on ArgParser { | ||
ArgResults? tryParse(List<String> args, {Printer? printer}) { | ||
try { | ||
return parse(args); | ||
} catch (e) { | ||
printer?.err('Failed to parse arguments'); | ||
printer?.err(usage); | ||
return null; | ||
} | ||
} | ||
} | ||
|
||
extension _TypeSafeArgs on ArgResults { | ||
String getString(String name, {required String fallback}) { | ||
return this[name] ?? fallback; | ||
} | ||
|
||
int? tryGetInt(String name) { | ||
final value = this[name]; | ||
|
||
if (value == null) { | ||
return null; | ||
} | ||
|
||
return int.tryParse(value); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,3 +7,5 @@ environment: | |
|
||
dev_dependencies: | ||
test: ^1.24.5 | ||
dependencies: | ||
args: ^2.4.2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import 'package:cyclic_dependency_checks/cycle_detection/cycle_detector.dart'; | ||
import 'package:cyclic_dependency_checks/cycle_detection/cycle_detector_runner.dart'; | ||
import 'package:cyclic_dependency_checks/cycle_detection/module_dependency.dart'; | ||
import 'package:test/test.dart'; | ||
|
||
class TestCycleDetector extends CycleDetector { | ||
List<List<ModuleDependency>> stubbedCycles = []; | ||
String? recordedPackagePath; | ||
int? recordedMaxDepth; | ||
|
||
@override | ||
Future<List<List<ModuleDependency>>> detect(String packagePath, {int? maxDepth}) async { | ||
recordedPackagePath = packagePath; | ||
recordedMaxDepth = maxDepth; | ||
return stubbedCycles; | ||
} | ||
} | ||
|
||
class RecordingPrinter extends Printer { | ||
final List<String> outLog = []; | ||
final List<String> errLog = []; | ||
|
||
out(String text) => outLog.add(text); | ||
|
||
err(String text) => errLog.add(text); | ||
} | ||
|
||
void main() { | ||
test('with no arguments, on success', () async { | ||
final printer = RecordingPrinter(); | ||
final detector = TestCycleDetector(); | ||
final runner = CycleDetectorRunner(detector: detector, printer: printer); | ||
|
||
final success = await runner.run([]); | ||
|
||
expect(success, isTrue); | ||
expect(printer.errLog, isEmpty); | ||
expect(detector.recordedPackagePath, equals('.')); | ||
expect(detector.recordedMaxDepth, isNull); | ||
}); | ||
|
||
test('with no arguments, on error', () async { | ||
final printer = RecordingPrinter(); | ||
final detector = TestCycleDetector() | ||
..stubbedCycles.add([ | ||
ModuleDependency(from: Module('a'), to: Module('b')), | ||
ModuleDependency(from: Module('b'), to: Module('a')), | ||
]); | ||
final runner = CycleDetectorRunner(detector: detector, printer: printer); | ||
|
||
final success = await runner.run([]); | ||
|
||
expect(success, isFalse); | ||
expect(printer.errLog, contains('module:a -> module:b -> module:a')); | ||
}); | ||
|
||
test('with full length args', () async { | ||
final printer = RecordingPrinter(); | ||
final detector = TestCycleDetector(); | ||
final runner = CycleDetectorRunner(detector: detector, printer: printer); | ||
|
||
await runner.run([ | ||
'--path', '/some/path', | ||
'--max-depth', '10', | ||
]); | ||
|
||
expect(detector.recordedPackagePath, equals('/some/path')); | ||
expect(detector.recordedMaxDepth, equals(10)); | ||
}); | ||
|
||
test('with short name args', () async { | ||
final printer = RecordingPrinter(); | ||
final detector = TestCycleDetector(); | ||
final runner = CycleDetectorRunner(detector: detector, printer: printer); | ||
|
||
await runner.run([ | ||
'-p', '/some/path', | ||
'-d', '10', | ||
]); | ||
|
||
expect(detector.recordedPackagePath, equals('/some/path')); | ||
expect(detector.recordedMaxDepth, equals(10)); | ||
}); | ||
|
||
test('with invalid arguments', () async { | ||
final printer = RecordingPrinter(); | ||
final detector = TestCycleDetector(); | ||
final runner = CycleDetectorRunner(detector: detector, printer: printer); | ||
|
||
final success = await runner.run([ | ||
'--oops' | ||
]); | ||
|
||
expect(success, equals(false)); | ||
expect(detector.recordedPackagePath, isNull); | ||
expect(detector.recordedMaxDepth, isNull); | ||
}); | ||
} |