diff --git a/benchmark_di/bin/main.dart b/benchmark_di/bin/main.dart index 985adbd..a063bbf 100644 --- a/benchmark_di/bin/main.dart +++ b/benchmark_di/bin/main.dart @@ -2,4 +2,4 @@ import 'package:benchmark_di/cli/benchmark_cli.dart'; Future main(List args) async { await BenchmarkCliRunner().run(args); -} \ No newline at end of file +} diff --git a/benchmark_di/lib/benchmarks/universal_chain_benchmark.dart b/benchmark_di/lib/benchmarks/universal_chain_benchmark.dart index b7eb19f..7f7b506 100644 --- a/benchmark_di/lib/benchmarks/universal_chain_benchmark.dart +++ b/benchmark_di/lib/benchmarks/universal_chain_benchmark.dart @@ -73,7 +73,8 @@ class UniversalChainBenchmark extends BenchmarkBase { _childDi!.resolve(); break; case UniversalScenario.asyncChain: - throw UnsupportedError('asyncChain supported only in UniversalChainAsyncBenchmark'); + throw UnsupportedError( + 'asyncChain supported only in UniversalChainAsyncBenchmark'); } } } diff --git a/benchmark_di/lib/cli/benchmark_cli.dart b/benchmark_di/lib/cli/benchmark_cli.dart index 1680f9d..0c68cdc 100644 --- a/benchmark_di/lib/cli/benchmark_cli.dart +++ b/benchmark_di/lib/cli/benchmark_cli.dart @@ -36,8 +36,11 @@ class BenchmarkCliRunner { if (config.di == 'getit') { final di = GetItAdapter(); if (scenario == UniversalScenario.asyncChain) { - final benchAsync = UniversalChainAsyncBenchmark(di, - chainCount: c, nestingDepth: d, mode: mode, + final benchAsync = UniversalChainAsyncBenchmark( + di, + chainCount: c, + nestingDepth: d, + mode: mode, ); benchResult = await BenchmarkRunner.runAsync( benchmark: benchAsync, @@ -45,8 +48,12 @@ class BenchmarkCliRunner { repeats: config.repeats, ); } else { - final benchSync = UniversalChainBenchmark(di, - chainCount: c, nestingDepth: d, mode: mode, scenario: scenario, + final benchSync = UniversalChainBenchmark( + di, + chainCount: c, + nestingDepth: d, + mode: mode, + scenario: scenario, ); benchResult = await BenchmarkRunner.runSync( benchmark: benchSync, @@ -57,8 +64,12 @@ class BenchmarkCliRunner { } else if (config.di == 'riverpod') { final di = RiverpodAdapter(); if (scenario == UniversalScenario.asyncChain) { - final benchAsync = UniversalChainAsyncBenchmark>>(di, - chainCount: c, nestingDepth: d, mode: mode, + final benchAsync = UniversalChainAsyncBenchmark< + Map>>( + di, + chainCount: c, + nestingDepth: d, + mode: mode, ); benchResult = await BenchmarkRunner.runAsync( benchmark: benchAsync, @@ -66,8 +77,13 @@ class BenchmarkCliRunner { repeats: config.repeats, ); } else { - final benchSync = UniversalChainBenchmark>>(di, - chainCount: c, nestingDepth: d, mode: mode, scenario: scenario, + final benchSync = UniversalChainBenchmark< + Map>>( + di, + chainCount: c, + nestingDepth: d, + mode: mode, + scenario: scenario, ); benchResult = await BenchmarkRunner.runSync( benchmark: benchSync, @@ -78,8 +94,11 @@ class BenchmarkCliRunner { } else { final di = CherrypickDIAdapter(); if (scenario == UniversalScenario.asyncChain) { - final benchAsync = UniversalChainAsyncBenchmark(di, - chainCount: c, nestingDepth: d, mode: mode, + final benchAsync = UniversalChainAsyncBenchmark( + di, + chainCount: c, + nestingDepth: d, + mode: mode, ); benchResult = await BenchmarkRunner.runAsync( benchmark: benchAsync, @@ -87,8 +106,12 @@ class BenchmarkCliRunner { repeats: config.repeats, ); } else { - final benchSync = UniversalChainBenchmark(di, - chainCount: c, nestingDepth: d, mode: mode, scenario: scenario, + final benchSync = UniversalChainBenchmark( + di, + chainCount: c, + nestingDepth: d, + mode: mode, + scenario: scenario, ); benchResult = await BenchmarkRunner.runSync( benchmark: benchSync, @@ -103,7 +126,11 @@ class BenchmarkCliRunner { var median = timings[timings.length ~/ 2]; var minVal = timings.first; var maxVal = timings.last; - var stddev = timings.isEmpty ? 0 : sqrt(timings.map((x) => pow(x - mean, 2)).reduce((a, b) => a + b) / timings.length); + var stddev = timings.isEmpty + ? 0 + : sqrt( + timings.map((x) => pow(x - mean, 2)).reduce((a, b) => a + b) / + timings.length); results.add({ 'benchmark': 'Universal_$bench', 'chainCount': c, @@ -128,6 +155,7 @@ class BenchmarkCliRunner { 'json': JsonReport(), 'markdown': MarkdownReport(), }; - print(reportGenerators[config.format]?.render(results) ?? PrettyReport().render(results)); + print(reportGenerators[config.format]?.render(results) ?? + PrettyReport().render(results)); } -} \ No newline at end of file +} diff --git a/benchmark_di/lib/cli/parser.dart b/benchmark_di/lib/cli/parser.dart index 3c93ede..0b4b911 100644 --- a/benchmark_di/lib/cli/parser.dart +++ b/benchmark_di/lib/cli/parser.dart @@ -8,14 +8,19 @@ import 'package:benchmark_di/scenarios/universal_scenario.dart'; enum UniversalBenchmark { /// Simple singleton registration benchmark registerSingleton, + /// Chain of singleton dependencies chainSingleton, + /// Chain using factories chainFactory, + /// Async chain resolution chainAsync, + /// Named registration benchmark named, + /// Override/child-scope benchmark override, } @@ -65,23 +70,32 @@ T parseEnum(String value, List values, T defaultValue) { } /// Parses comma-separated integer list from [s]. -List parseIntList(String s) => - s.split(',').map((e) => int.tryParse(e.trim()) ?? 0).where((x) => x > 0).toList(); +List parseIntList(String s) => s + .split(',') + .map((e) => int.tryParse(e.trim()) ?? 0) + .where((x) => x > 0) + .toList(); /// CLI config describing what and how to benchmark. class BenchmarkCliConfig { /// Benchmarks enabled to run (scenarios). final List benchesToRun; + /// List of chain counts (parallel, per test). final List chainCounts; + /// List of nesting depths (max chain length, per test). final List nestDepths; + /// How many times to repeat each trial. final int repeats; + /// How many times to warm-up before measuring. final int warmups; + /// Output report format. final String format; + /// Name of DI implementation ("cherrypick" or "getit") final String di; BenchmarkCliConfig({ @@ -105,7 +119,9 @@ BenchmarkCliConfig parseBenchmarkCli(List args) { ..addOption('repeat', abbr: 'r', defaultsTo: '2') ..addOption('warmup', abbr: 'w', defaultsTo: '1') ..addOption('format', abbr: 'f', defaultsTo: 'pretty') - ..addOption('di', defaultsTo: 'cherrypick', help: 'DI implementation: cherrypick, getit or riverpod') + ..addOption('di', + defaultsTo: 'cherrypick', + help: 'DI implementation: cherrypick, getit or riverpod') ..addFlag('help', abbr: 'h', negatable: false, help: 'Show help'); final result = parser.parse(args); if (result['help'] == true) { @@ -127,4 +143,4 @@ BenchmarkCliConfig parseBenchmarkCli(List args) { format: result['format'] as String, di: result['di'] as String? ?? 'cherrypick', ); -} \ No newline at end of file +} diff --git a/benchmark_di/lib/cli/report/csv_report.dart b/benchmark_di/lib/cli/report/csv_report.dart index 6379889..e6662ef 100644 --- a/benchmark_di/lib/cli/report/csv_report.dart +++ b/benchmark_di/lib/cli/report/csv_report.dart @@ -5,20 +5,32 @@ class CsvReport extends ReportGenerator { /// List of all keys/columns to include in the CSV output. @override final List keys = [ - 'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', - 'min_us','max_us','trials','timings_us','memory_diff_kb','delta_peak_kb','peak_rss_kb' + 'benchmark', + 'chainCount', + 'nestingDepth', + 'mean_us', + 'median_us', + 'stddev_us', + 'min_us', + 'max_us', + 'trials', + 'timings_us', + 'memory_diff_kb', + 'delta_peak_kb', + 'peak_rss_kb' ]; + /// Renders rows as a CSV table string. @override String render(List> rows) { final header = keys.join(','); - final lines = rows.map((r) => - keys.map((k) { - final v = r[k]; - if (v is List) return '"${v.join(';')}"'; - return (v ?? '').toString(); - }).join(',') - ).toList(); + final lines = rows + .map((r) => keys.map((k) { + final v = r[k]; + if (v is List) return '"${v.join(';')}"'; + return (v ?? '').toString(); + }).join(',')) + .toList(); return ([header] + lines).join('\n'); } -} \ No newline at end of file +} diff --git a/benchmark_di/lib/cli/report/json_report.dart b/benchmark_di/lib/cli/report/json_report.dart index fb75d67..33dafe6 100644 --- a/benchmark_di/lib/cli/report/json_report.dart +++ b/benchmark_di/lib/cli/report/json_report.dart @@ -5,9 +5,10 @@ class JsonReport extends ReportGenerator { /// No specific keys; outputs all fields in raw map. @override List get keys => []; + /// Renders all result rows as a pretty-printed JSON array. @override String render(List> rows) { return '[\n${rows.map((r) => ' $r').join(',\n')}\n]'; } -} \ No newline at end of file +} diff --git a/benchmark_di/lib/cli/report/markdown_report.dart b/benchmark_di/lib/cli/report/markdown_report.dart index cf97ecc..4a2d1cf 100644 --- a/benchmark_di/lib/cli/report/markdown_report.dart +++ b/benchmark_di/lib/cli/report/markdown_report.dart @@ -7,25 +7,46 @@ class MarkdownReport extends ReportGenerator { /// List of columns (keys) to show in the Markdown table. @override final List keys = [ - 'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', - 'min_us','max_us','trials','memory_diff_kb','delta_peak_kb','peak_rss_kb' + 'benchmark', + 'chainCount', + 'nestingDepth', + 'mean_us', + 'median_us', + 'stddev_us', + 'min_us', + 'max_us', + 'trials', + 'memory_diff_kb', + 'delta_peak_kb', + 'peak_rss_kb' ]; /// Friendly display names for each benchmark type. static const nameMap = { - 'Universal_UniversalBenchmark.registerSingleton':'RegisterSingleton', - 'Universal_UniversalBenchmark.chainSingleton':'ChainSingleton', - 'Universal_UniversalBenchmark.chainFactory':'ChainFactory', - 'Universal_UniversalBenchmark.chainAsync':'AsyncChain', - 'Universal_UniversalBenchmark.named':'Named', - 'Universal_UniversalBenchmark.override':'Override', + 'Universal_UniversalBenchmark.registerSingleton': 'RegisterSingleton', + 'Universal_UniversalBenchmark.chainSingleton': 'ChainSingleton', + 'Universal_UniversalBenchmark.chainFactory': 'ChainFactory', + 'Universal_UniversalBenchmark.chainAsync': 'AsyncChain', + 'Universal_UniversalBenchmark.named': 'Named', + 'Universal_UniversalBenchmark.override': 'Override', }; /// Renders all results as a formatted Markdown table with aligned columns and a legend. @override String render(List> rows) { final headers = [ - 'Benchmark', 'Chain Count', 'Depth', 'Mean (us)', 'Median', 'Stddev', 'Min', 'Max', 'N', 'ΔRSS(KB)', 'ΔPeak(KB)', 'PeakRSS(KB)' + 'Benchmark', + 'Chain Count', + 'Depth', + 'Mean (us)', + 'Median', + 'Stddev', + 'Min', + 'Max', + 'N', + 'ΔRSS(KB)', + 'ΔPeak(KB)', + 'PeakRSS(KB)' ]; final dataRows = rows.map((r) { final readableName = nameMap[r['benchmark']] ?? r['benchmark']; @@ -73,6 +94,6 @@ class MarkdownReport extends ReportGenerator { > `PeakRSS(KB)` – Max observed RSS memory (KB) '''; - return '$legend\n\n${([headerLine, divider] + lines).join('\n')}' ; + return '$legend\n\n${([headerLine, divider] + lines).join('\n')}'; } -} \ No newline at end of file +} diff --git a/benchmark_di/lib/cli/report/pretty_report.dart b/benchmark_di/lib/cli/report/pretty_report.dart index 36688ef..3d46d46 100644 --- a/benchmark_di/lib/cli/report/pretty_report.dart +++ b/benchmark_di/lib/cli/report/pretty_report.dart @@ -7,25 +7,46 @@ class PrettyReport extends ReportGenerator { /// List of columns to output in the pretty report. @override final List keys = [ - 'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', - 'min_us','max_us','trials','memory_diff_kb','delta_peak_kb','peak_rss_kb' + 'benchmark', + 'chainCount', + 'nestingDepth', + 'mean_us', + 'median_us', + 'stddev_us', + 'min_us', + 'max_us', + 'trials', + 'memory_diff_kb', + 'delta_peak_kb', + 'peak_rss_kb' ]; /// Mappings from internal benchmark IDs to display names. static const nameMap = { - 'Universal_UniversalBenchmark.registerSingleton': 'RegisterSingleton', - 'Universal_UniversalBenchmark.chainSingleton': 'ChainSingleton', - 'Universal_UniversalBenchmark.chainFactory': 'ChainFactory', - 'Universal_UniversalBenchmark.chainAsync': 'AsyncChain', - 'Universal_UniversalBenchmark.named': 'Named', - 'Universal_UniversalBenchmark.override': 'Override', + 'Universal_UniversalBenchmark.registerSingleton': 'RegisterSingleton', + 'Universal_UniversalBenchmark.chainSingleton': 'ChainSingleton', + 'Universal_UniversalBenchmark.chainFactory': 'ChainFactory', + 'Universal_UniversalBenchmark.chainAsync': 'AsyncChain', + 'Universal_UniversalBenchmark.named': 'Named', + 'Universal_UniversalBenchmark.override': 'Override', }; /// Renders the results as a header + tab-separated value table. @override String render(List> rows) { final headers = [ - 'Benchmark', 'Chain Count', 'Depth', 'Mean (us)', 'Median', 'Stddev', 'Min', 'Max', 'N', 'ΔRSS(KB)', 'ΔPeak(KB)', 'PeakRSS(KB)' + 'Benchmark', + 'Chain Count', + 'Depth', + 'Mean (us)', + 'Median', + 'Stddev', + 'Min', + 'Max', + 'N', + 'ΔRSS(KB)', + 'ΔPeak(KB)', + 'PeakRSS(KB)' ]; final header = headers.join('\t'); final lines = rows.map((r) { diff --git a/benchmark_di/lib/cli/report/report_generator.dart b/benchmark_di/lib/cli/report/report_generator.dart index 59a1e98..5e80aad 100644 --- a/benchmark_di/lib/cli/report/report_generator.dart +++ b/benchmark_di/lib/cli/report/report_generator.dart @@ -4,6 +4,7 @@ abstract class ReportGenerator { /// Renders the given [results] as a formatted string (table, markdown, csv, etc). String render(List> results); + /// List of output columns/keys included in the export (or [] for auto/all). List get keys; -} \ No newline at end of file +} diff --git a/benchmark_di/lib/cli/runner.dart b/benchmark_di/lib/cli/runner.dart index ae6835d..2f5a376 100644 --- a/benchmark_di/lib/cli/runner.dart +++ b/benchmark_di/lib/cli/runner.dart @@ -7,10 +7,13 @@ import 'package:benchmark_di/benchmarks/universal_chain_async_benchmark.dart'; class BenchmarkResult { /// List of timings for each run (in microseconds). final List timings; + /// Difference in memory (RSS, in KB) after running. final int memoryDiffKb; + /// Difference between peak RSS and initial RSS (in KB). final int deltaPeakKb; + /// Peak RSS memory observed (in KB). final int peakRssKb; BenchmarkResult({ @@ -19,6 +22,7 @@ class BenchmarkResult { required this.deltaPeakKb, required this.peakRssKb, }); + /// Computes a BenchmarkResult instance from run timings and memory data. factory BenchmarkResult.collect({ required List timings, @@ -64,7 +68,8 @@ class BenchmarkRunner { rssValues.add(ProcessInfo.currentRss); benchmark.teardown(); } - return BenchmarkResult.collect(timings: timings, rssValues: rssValues, memBefore: memBefore); + return BenchmarkResult.collect( + timings: timings, rssValues: rssValues, memBefore: memBefore); } /// Runs an asynchronous benchmark ([UniversalChainAsyncBenchmark]) for a given number of [warmups] and [repeats]. @@ -91,6 +96,7 @@ class BenchmarkRunner { rssValues.add(ProcessInfo.currentRss); await benchmark.teardown(); } - return BenchmarkResult.collect(timings: timings, rssValues: rssValues, memBefore: memBefore); + return BenchmarkResult.collect( + timings: timings, rssValues: rssValues, memBefore: memBefore); } -} \ No newline at end of file +} diff --git a/benchmark_di/lib/di_adapters/cherrypick_adapter.dart b/benchmark_di/lib/di_adapters/cherrypick_adapter.dart index 0bf06cd..09c3eb6 100644 --- a/benchmark_di/lib/di_adapters/cherrypick_adapter.dart +++ b/benchmark_di/lib/di_adapters/cherrypick_adapter.dart @@ -4,7 +4,6 @@ import 'package:benchmark_di/scenarios/universal_service.dart'; import 'package:cherrypick/cherrypick.dart'; import 'di_adapter.dart'; - /// Test module that generates a chain of service bindings for benchmarking. /// /// Configurable by chain count, nesting depth, binding mode, and scenario @@ -12,10 +11,13 @@ import 'di_adapter.dart'; class UniversalChainModule extends Module { /// Number of chains to create. final int chainCount; + /// Depth of each chain. final int nestingDepth; + /// How modules are registered (factory/singleton/async). final UniversalBindingMode bindingMode; + /// Which di scenario to generate (chained, named, etc). final UniversalScenario scenario; @@ -38,17 +40,18 @@ class UniversalChainModule extends Module { final prevDepName = '${chain}_${level - 1}'; final depName = '${chain}_$level'; bind() - .toProvideAsync(() async { - final prev = level > 1 - ? await currentScope.resolveAsync(named: prevDepName) - : null; - return UniversalServiceImpl( - value: depName, - dependency: prev, - ); - }) - .withName(depName) - .singleton(); + .toProvideAsync(() async { + final prev = level > 1 + ? await currentScope.resolveAsync( + named: prevDepName) + : null; + return UniversalServiceImpl( + value: depName, + dependency: prev, + ); + }) + .withName(depName) + .singleton(); } } return; @@ -58,13 +61,18 @@ class UniversalChainModule extends Module { case UniversalScenario.register: // Simple singleton registration. bind() - .toProvide(() => UniversalServiceImpl(value: 'reg', dependency: null)) + .toProvide( + () => UniversalServiceImpl(value: 'reg', dependency: null)) .singleton(); break; case UniversalScenario.named: // Named factory registration for two distinct objects. - bind().toProvide(() => UniversalServiceImpl(value: 'impl1')).withName('impl1'); - bind().toProvide(() => UniversalServiceImpl(value: 'impl2')).withName('impl2'); + bind() + .toProvide(() => UniversalServiceImpl(value: 'impl1')) + .withName('impl1'); + bind() + .toProvide(() => UniversalServiceImpl(value: 'impl2')) + .withName('impl2'); break; case UniversalScenario.chain: // Chain of nested services, with dependency on previous level by name. @@ -79,7 +87,8 @@ class UniversalChainModule extends Module { bind() .toProvide(() => UniversalServiceImpl( value: depName, - dependency: currentScope.tryResolve(named: prevDepName), + dependency: currentScope.tryResolve( + named: prevDepName), )) .withName(depName) .singleton(); @@ -88,7 +97,8 @@ class UniversalChainModule extends Module { bind() .toProvide(() => UniversalServiceImpl( value: depName, - dependency: currentScope.tryResolve(named: prevDepName), + dependency: currentScope.tryResolve( + named: prevDepName), )) .withName(depName); break; @@ -96,7 +106,9 @@ class UniversalChainModule extends Module { bind() .toProvideAsync(() async => UniversalServiceImpl( value: depName, - dependency: await currentScope.resolveAsync(named: prevDepName), + dependency: + await currentScope.resolveAsync( + named: prevDepName), )) .withName(depName) .singleton(); @@ -107,14 +119,16 @@ class UniversalChainModule extends Module { // Регистрация алиаса без имени (на последний элемент цепочки) final depName = '${chainCount}_$nestingDepth'; bind() - .toProvide(() => currentScope.resolve(named: depName)) + .toProvide( + () => currentScope.resolve(named: depName)) .singleton(); break; case UniversalScenario.override: // handled at benchmark level, но алиас нужен прямо в этом scope! final depName = '${chainCount}_$nestingDepth'; bind() - .toProvide(() => currentScope.resolve(named: depName)) + .toProvide( + () => currentScope.resolve(named: depName)) .singleton(); break; case UniversalScenario.asyncChain: @@ -124,7 +138,6 @@ class UniversalChainModule extends Module { } } - class CherrypickDIAdapter extends DIAdapter { Scope? _scope; final bool _isSubScope; @@ -158,7 +171,8 @@ class CherrypickDIAdapter extends DIAdapter { ]); }; } - throw UnsupportedError('Scenario $scenario not supported by CherrypickDIAdapter'); + throw UnsupportedError( + 'Scenario $scenario not supported by CherrypickDIAdapter'); } @override diff --git a/benchmark_di/lib/di_adapters/di_adapter.dart b/benchmark_di/lib/di_adapters/di_adapter.dart index 938d8f9..71901e9 100644 --- a/benchmark_di/lib/di_adapters/di_adapter.dart +++ b/benchmark_di/lib/di_adapters/di_adapter.dart @@ -1,4 +1,5 @@ import 'package:benchmark_di/scenarios/universal_binding_mode.dart'; + /// Универсальная абстракция для DI-адаптера с унифицированной функцией регистрации. /// Теперь для каждого адаптера задаём строгий generic тип контейнера. typedef Registration = void Function(TContainer); diff --git a/benchmark_di/lib/di_adapters/get_it_adapter.dart b/benchmark_di/lib/di_adapters/get_it_adapter.dart index 0396161..fe00d51 100644 --- a/benchmark_di/lib/di_adapters/get_it_adapter.dart +++ b/benchmark_di/lib/di_adapters/get_it_adapter.dart @@ -80,9 +80,11 @@ class GetItAdapter extends DIAdapter { getIt.registerSingletonAsync( () async { final prev = level > 1 - ? await getIt.getAsync(instanceName: prevDepName) + ? await getIt.getAsync( + instanceName: prevDepName) : null; - return UniversalServiceImpl(value: depName, dependency: prev); + return UniversalServiceImpl( + value: depName, dependency: prev); }, instanceName: depName, ); @@ -90,11 +92,16 @@ class GetItAdapter extends DIAdapter { } break; case UniversalScenario.register: - getIt.registerSingleton(UniversalServiceImpl(value: 'reg', dependency: null)); + getIt.registerSingleton( + UniversalServiceImpl(value: 'reg', dependency: null)); break; case UniversalScenario.named: - getIt.registerFactory(() => UniversalServiceImpl(value: 'impl1'), instanceName: 'impl1'); - getIt.registerFactory(() => UniversalServiceImpl(value: 'impl2'), instanceName: 'impl2'); + getIt.registerFactory( + () => UniversalServiceImpl(value: 'impl1'), + instanceName: 'impl1'); + getIt.registerFactory( + () => UniversalServiceImpl(value: 'impl2'), + instanceName: 'impl2'); break; case UniversalScenario.chain: for (int chain = 1; chain <= chainCount; chain++) { @@ -107,8 +114,8 @@ class GetItAdapter extends DIAdapter { UniversalServiceImpl( value: depName, dependency: level > 1 - ? getIt(instanceName: prevDepName) - : null, + ? getIt(instanceName: prevDepName) + : null, ), instanceName: depName, ); @@ -129,8 +136,9 @@ class GetItAdapter extends DIAdapter { () async => UniversalServiceImpl( value: depName, dependency: level > 1 - ? await getIt.getAsync(instanceName: prevDepName) - : null, + ? await getIt.getAsync( + instanceName: prevDepName) + : null, ), instanceName: depName, ); @@ -143,7 +151,8 @@ class GetItAdapter extends DIAdapter { // handled at benchmark level break; } - if (scenario == UniversalScenario.chain || scenario == UniversalScenario.override) { + if (scenario == UniversalScenario.chain || + scenario == UniversalScenario.override) { final depName = '${chainCount}_$nestingDepth'; getIt.registerSingleton( getIt(instanceName: depName), diff --git a/benchmark_di/lib/di_adapters/riverpod_adapter.dart b/benchmark_di/lib/di_adapters/riverpod_adapter.dart index 6e893df..b9821d1 100644 --- a/benchmark_di/lib/di_adapters/riverpod_adapter.dart +++ b/benchmark_di/lib/di_adapters/riverpod_adapter.dart @@ -20,7 +20,9 @@ class RiverpodAdapter extends DIAdapter>> { _parent = parent; @override - void setupDependencies(void Function(Map> container) registration) { + void setupDependencies( + void Function(Map> container) + registration) { _container ??= _parent == null ? rp.ProviderContainer() : rp.ProviderContainer(parent: _parent); @@ -76,7 +78,8 @@ class RiverpodAdapter extends DIAdapter>> { } @override - Registration>> universalRegistration({ + Registration>> + universalRegistration({ required S scenario, required int chainCount, required int nestingDepth, @@ -86,25 +89,34 @@ class RiverpodAdapter extends DIAdapter>> { return (providers) { switch (scenario) { case UniversalScenario.register: - providers['UniversalService'] = rp.Provider((ref) => UniversalServiceImpl(value: 'reg', dependency: null)); + providers['UniversalService'] = rp.Provider( + (ref) => UniversalServiceImpl(value: 'reg', dependency: null)); break; case UniversalScenario.named: - providers['impl1'] = rp.Provider((ref) => UniversalServiceImpl(value: 'impl1')); - providers['impl2'] = rp.Provider((ref) => UniversalServiceImpl(value: 'impl2')); + providers['impl1'] = rp.Provider( + (ref) => UniversalServiceImpl(value: 'impl1')); + providers['impl2'] = rp.Provider( + (ref) => UniversalServiceImpl(value: 'impl2')); break; case UniversalScenario.chain: for (int chain = 1; chain <= chainCount; chain++) { for (int level = 1; level <= nestingDepth; level++) { final prevDepName = '${chain}_${level - 1}'; final depName = '${chain}_$level'; - providers[depName] = rp.Provider((ref) => UniversalServiceImpl( - value: depName, - dependency: level > 1 ? ref.watch(providers[prevDepName] as rp.ProviderBase) : null, - )); + providers[depName] = + rp.Provider((ref) => UniversalServiceImpl( + value: depName, + dependency: level > 1 + ? ref.watch(providers[prevDepName] + as rp.ProviderBase) + : null, + )); } } final depName = '${chainCount}_$nestingDepth'; - providers['UniversalService'] = rp.Provider((ref) => ref.watch(providers[depName] as rp.ProviderBase)); + providers['UniversalService'] = rp.Provider( + (ref) => ref.watch( + providers[depName] as rp.ProviderBase)); break; case UniversalScenario.override: // handled at benchmark level @@ -114,24 +126,31 @@ class RiverpodAdapter extends DIAdapter>> { for (int level = 1; level <= nestingDepth; level++) { final prevDepName = '${chain}_${level - 1}'; final depName = '${chain}_$level'; - providers[depName] = rp.FutureProvider((ref) async { + providers[depName] = + rp.FutureProvider((ref) async { return UniversalServiceImpl( value: depName, dependency: level > 1 - ? await ref.watch((providers[prevDepName] as rp.FutureProvider).future) as UniversalService? + ? await ref.watch((providers[prevDepName] + as rp.FutureProvider) + .future) as UniversalService? : null, ); }); } } final depName = '${chainCount}_$nestingDepth'; - providers['UniversalService'] = rp.FutureProvider((ref) async { - return await ref.watch((providers[depName] as rp.FutureProvider).future); + providers['UniversalService'] = + rp.FutureProvider((ref) async { + return await ref.watch( + (providers[depName] as rp.FutureProvider) + .future); }); break; } }; } - throw UnsupportedError('Scenario $scenario not supported by RiverpodAdapter'); + throw UnsupportedError( + 'Scenario $scenario not supported by RiverpodAdapter'); } } diff --git a/benchmark_di/lib/scenarios/universal_scenario.dart b/benchmark_di/lib/scenarios/universal_scenario.dart index 59857aa..c92002e 100644 --- a/benchmark_di/lib/scenarios/universal_scenario.dart +++ b/benchmark_di/lib/scenarios/universal_scenario.dart @@ -2,12 +2,16 @@ enum UniversalScenario { /// Single registration. register, + /// Chain of dependencies. chain, + /// Named registrations. named, + /// Child-scope override scenario. override, + /// Asynchronous chain scenario. asyncChain, } diff --git a/benchmark_di/lib/scenarios/universal_service.dart b/benchmark_di/lib/scenarios/universal_service.dart index 910201f..0eb78dd 100644 --- a/benchmark_di/lib/scenarios/universal_service.dart +++ b/benchmark_di/lib/scenarios/universal_service.dart @@ -1,4 +1,3 @@ - /// Base interface for any universal service in the benchmarks. /// /// Represents an object in the dependency chain with an identifiable value @@ -6,6 +5,7 @@ abstract class UniversalService { /// String ID for this service instance (e.g. chain/level info). final String value; + /// Optional reference to dependency service in the chain. final UniversalService? dependency; UniversalService({required this.value, this.dependency}); @@ -14,4 +14,4 @@ abstract class UniversalService { /// Default implementation for [UniversalService] used in service chains. class UniversalServiceImpl extends UniversalService { UniversalServiceImpl({required super.value, super.dependency}); -} \ No newline at end of file +} diff --git a/benchmark_di/pubspec.lock b/benchmark_di/pubspec.lock index b523a47..1fc8da8 100644 --- a/benchmark_di/pubspec.lock +++ b/benchmark_di/pubspec.lock @@ -47,7 +47,7 @@ packages: path: "../cherrypick" relative: true source: path - version: "3.0.0-dev.8" + version: "3.0.0-dev.9" collection: dependency: transitive description: diff --git a/cherrypick/example/bin/main.dart b/cherrypick/example/bin/main.dart index eaf083f..fc36d2b 100644 --- a/cherrypick/example/bin/main.dart +++ b/cherrypick/example/bin/main.dart @@ -47,19 +47,19 @@ class FeatureModule extends Module { Future main() async { try { - final scope = CherryPick.openRootScope().installModules([AppModule()]); + final scope = CherryPick.openRootScope().installModules([AppModule()]); final subScope = scope .openSubScope("featureScope") .installModules([FeatureModule(isMock: true)]); - // Asynchronous instance resolution - final dataBloc = await subScope.resolveAsync(); - dataBloc.data.listen( - (d) => print('Received data: $d'), - onError: (e) => print('Error: $e'), - onDone: () => print('DONE'), - ); + // Asynchronous instance resolution + final dataBloc = await subScope.resolveAsync(); + dataBloc.data.listen( + (d) => print('Received data: $d'), + onError: (e) => print('Error: $e'), + onDone: () => print('DONE'), + ); await dataBloc.fetchData(); } catch (e) { diff --git a/cherrypick/example/cherrypick_helper_example.dart b/cherrypick/example/cherrypick_helper_example.dart index 7d1ee0d..3a9d82a 100644 --- a/cherrypick/example/cherrypick_helper_example.dart +++ b/cherrypick/example/cherrypick_helper_example.dart @@ -8,7 +8,7 @@ class DatabaseService { class ApiService { final DatabaseService database; ApiService(this.database); - + void fetchData() { database.connect(); print('📡 Fetching data via API'); @@ -18,7 +18,7 @@ class ApiService { class UserService { final ApiService apiService; UserService(this.apiService); - + void getUser(String id) { apiService.fetchData(); print('👤 Fetching user: $id'); @@ -36,18 +36,16 @@ class DatabaseModule extends Module { class ApiModule extends Module { @override void builder(Scope currentScope) { - bind().toProvide(() => ApiService( - currentScope.resolve() - )); + bind() + .toProvide(() => ApiService(currentScope.resolve())); } } class UserModule extends Module { @override void builder(Scope currentScope) { - bind().toProvide(() => UserService( - currentScope.resolve() - )); + bind() + .toProvide(() => UserService(currentScope.resolve())); } } @@ -65,74 +63,75 @@ class CircularServiceB { class CircularModuleA extends Module { @override void builder(Scope currentScope) { - bind().toProvide(() => CircularServiceA( - currentScope.resolve() - )); + bind().toProvide( + () => CircularServiceA(currentScope.resolve())); } } class CircularModuleB extends Module { @override void builder(Scope currentScope) { - bind().toProvide(() => CircularServiceB( - currentScope.resolve() - )); + bind().toProvide( + () => CircularServiceB(currentScope.resolve())); } } void main() { print('=== Improved CherryPick Helper Demonstration ===\n'); - + // Example 1: Global enabling of cycle detection print('1. Globally enable cycle detection:'); - + CherryPick.enableGlobalCycleDetection(); - print('✅ Global cycle detection enabled: ${CherryPick.isGlobalCycleDetectionEnabled}'); - + print( + '✅ Global cycle detection enabled: ${CherryPick.isGlobalCycleDetectionEnabled}'); + // All new scopes will automatically have cycle detection enabled final globalScope = CherryPick.openRootScope(); - print('✅ Root scope has cycle detection enabled: ${globalScope.isCycleDetectionEnabled}'); - + print( + '✅ Root scope has cycle detection enabled: ${globalScope.isCycleDetectionEnabled}'); + // Install modules without circular dependencies globalScope.installModules([ DatabaseModule(), ApiModule(), UserModule(), ]); - + final userService = globalScope.resolve(); userService.getUser('user123'); print(''); - + // Example 2: Safe scope creation print('2. Creating safe scopes:'); - + CherryPick.closeRootScope(); // Закрываем предыдущий скоуп CherryPick.disableGlobalCycleDetection(); // Отключаем глобальную настройку - + // Создаем безопасный скоуп (с автоматически включенным обнаружением) final safeScope = CherryPick.openSafeRootScope(); - print('✅ Safe scope created with cycle detection: ${safeScope.isCycleDetectionEnabled}'); - + print( + '✅ Safe scope created with cycle detection: ${safeScope.isCycleDetectionEnabled}'); + safeScope.installModules([ DatabaseModule(), ApiModule(), UserModule(), ]); - + final safeUserService = safeScope.resolve(); safeUserService.getUser('safe_user456'); print(''); - + // Example 3: Detecting cycles print('3. Detecting circular dependencies:'); - + final cyclicScope = CherryPick.openSafeRootScope(); cyclicScope.installModules([ CircularModuleA(), CircularModuleB(), ]); - + try { cyclicScope.resolve(); print('❌ This should not be executed'); @@ -144,87 +143,96 @@ void main() { } } print(''); - + // Example 4: Managing detection for specific scopes print('4. Managing detection for specific scopes:'); - + CherryPick.closeRootScope(); - + // Создаем скоуп без обнаружения // ignore: unused_local_variable final specificScope = CherryPick.openRootScope(); - print(' Detection in root scope: ${CherryPick.isCycleDetectionEnabledForScope()}'); - + print( + ' Detection in root scope: ${CherryPick.isCycleDetectionEnabledForScope()}'); + // Включаем обнаружение для конкретного скоупа CherryPick.enableCycleDetectionForScope(); - print('✅ Detection enabled for root scope: ${CherryPick.isCycleDetectionEnabledForScope()}'); - + print( + '✅ Detection enabled for root scope: ${CherryPick.isCycleDetectionEnabledForScope()}'); + // Создаем дочерний скоуп // ignore: unused_local_variable final featureScope = CherryPick.openScope(scopeName: 'feature.auth'); - print(' Detection in feature.auth scope: ${CherryPick.isCycleDetectionEnabledForScope(scopeName: 'feature.auth')}'); - + print( + ' Detection in feature.auth scope: ${CherryPick.isCycleDetectionEnabledForScope(scopeName: 'feature.auth')}'); + // Включаем обнаружение для дочернего скоупа CherryPick.enableCycleDetectionForScope(scopeName: 'feature.auth'); - print('✅ Detection enabled for feature.auth scope: ${CherryPick.isCycleDetectionEnabledForScope(scopeName: 'feature.auth')}'); + print( + '✅ Detection enabled for feature.auth scope: ${CherryPick.isCycleDetectionEnabledForScope(scopeName: 'feature.auth')}'); print(''); - + // Example 5: Creating safe child scopes print('5. Creating safe child scopes:'); - - final safeFeatureScope = CherryPick.openSafeScope(scopeName: 'feature.payments'); - print('✅ Safe feature scope created: ${safeFeatureScope.isCycleDetectionEnabled}'); - + + final safeFeatureScope = + CherryPick.openSafeScope(scopeName: 'feature.payments'); + print( + '✅ Safe feature scope created: ${safeFeatureScope.isCycleDetectionEnabled}'); + // You can create a complex hierarchy of scopes - final complexScope = CherryPick.openSafeScope(scopeName: 'app.feature.auth.login'); + final complexScope = + CherryPick.openSafeScope(scopeName: 'app.feature.auth.login'); print('✅ Complex scope created: ${complexScope.isCycleDetectionEnabled}'); print(''); - + // Example 6: Tracking resolution chains print('6. Tracking dependency resolution chains:'); - + final trackingScope = CherryPick.openSafeRootScope(); trackingScope.installModules([ DatabaseModule(), ApiModule(), UserModule(), ]); - + print(' Chain before resolve: ${CherryPick.getCurrentResolutionChain()}'); - + // The chain is populated during resolution, but cleared after completion // ignore: unused_local_variable final trackedUserService = trackingScope.resolve(); print(' Chain after resolve: ${CherryPick.getCurrentResolutionChain()}'); print(''); - + // Example 7: Usage recommendations print('7. Recommended usage:'); print(''); - + print('🔧 Development mode:'); print(' CherryPick.enableGlobalCycleDetection(); // Enable globally'); print(' or'); print(' final scope = CherryPick.openSafeRootScope(); // Safe scope'); print(''); - + print('🚀 Production mode:'); - print(' CherryPick.disableGlobalCycleDetection(); // Disable for performance'); + print( + ' CherryPick.disableGlobalCycleDetection(); // Disable for performance'); print(' final scope = CherryPick.openRootScope(); // Regular scope'); print(''); - + print('🧪 Testing:'); print(' setUp(() => CherryPick.enableGlobalCycleDetection());'); print(' tearDown(() => CherryPick.closeRootScope());'); print(''); - + print('🎯 Feature-specific:'); - print(' CherryPick.enableCycleDetectionForScope(scopeName: "feature.critical");'); + print( + ' CherryPick.enableCycleDetectionForScope(scopeName: "feature.critical");'); print(' // Enable only for critical features'); - + // Cleanup CherryPick.closeRootScope(); CherryPick.disableGlobalCycleDetection(); - + print('\n=== Demonstration complete ==='); } diff --git a/cherrypick/example/cycle_detection_example.dart b/cherrypick/example/cycle_detection_example.dart index df0bfc3..ee6c566 100644 --- a/cherrypick/example/cycle_detection_example.dart +++ b/cherrypick/example/cycle_detection_example.dart @@ -3,9 +3,9 @@ import 'package:cherrypick/cherrypick.dart'; // Пример сервисов с циклической зависимостью class UserService { final OrderService orderService; - + UserService(this.orderService); - + void createUser(String name) { print('Creating user: $name'); // Пытаемся получить заказы пользователя, что создает циклическую зависимость @@ -15,9 +15,9 @@ class UserService { class OrderService { final UserService userService; - + OrderService(this.userService); - + void getOrdersForUser(String userName) { print('Getting orders for user: $userName'); // Пытаемся получить информацию о пользователе, что создает циклическую зависимость @@ -29,18 +29,16 @@ class OrderService { class UserModule extends Module { @override void builder(Scope currentScope) { - bind().toProvide(() => UserService( - currentScope.resolve() - )); + bind() + .toProvide(() => UserService(currentScope.resolve())); } } class OrderModule extends Module { @override void builder(Scope currentScope) { - bind().toProvide(() => OrderService( - currentScope.resolve() - )); + bind() + .toProvide(() => OrderService(currentScope.resolve())); } } @@ -49,7 +47,7 @@ class UserRepository { void createUser(String name) { print('Creating user in repository: $name'); } - + String getUserInfo(String name) { return 'User info for: $name'; } @@ -59,7 +57,7 @@ class OrderRepository { void createOrder(String orderId, String userName) { print('Creating order $orderId for user: $userName'); } - + List getOrdersForUser(String userName) { return ['order1', 'order2', 'order3']; } @@ -67,13 +65,13 @@ class OrderRepository { class ImprovedUserService { final UserRepository userRepository; - + ImprovedUserService(this.userRepository); - + void createUser(String name) { userRepository.createUser(name); } - + String getUserInfo(String name) { return userRepository.getUserInfo(name); } @@ -82,17 +80,17 @@ class ImprovedUserService { class ImprovedOrderService { final OrderRepository orderRepository; final ImprovedUserService userService; - + ImprovedOrderService(this.orderRepository, this.userService); - + void createOrder(String orderId, String userName) { // Проверяем, что пользователь существует final userInfo = userService.getUserInfo(userName); print('User exists: $userInfo'); - + orderRepository.createOrder(orderId, userName); } - + List getOrdersForUser(String userName) { return orderRepository.getOrdersForUser(userName); } @@ -103,9 +101,8 @@ class ImprovedUserModule extends Module { @override void builder(Scope currentScope) { bind().singleton().toProvide(() => UserRepository()); - bind().toProvide(() => ImprovedUserService( - currentScope.resolve() - )); + bind().toProvide( + () => ImprovedUserService(currentScope.resolve())); } } @@ -114,81 +111,80 @@ class ImprovedOrderModule extends Module { void builder(Scope currentScope) { bind().singleton().toProvide(() => OrderRepository()); bind().toProvide(() => ImprovedOrderService( - currentScope.resolve(), - currentScope.resolve() - )); + currentScope.resolve(), + currentScope.resolve())); } } void main() { print('=== Circular Dependency Detection Example ===\n'); - + // Example 1: Demonstrate circular dependency print('1. Attempt to create a scope with circular dependencies:'); try { final scope = CherryPick.openRootScope(); - scope.enableCycleDetection(); // Включаем обнаружение циклических зависимостей - + scope + .enableCycleDetection(); // Включаем обнаружение циклических зависимостей + scope.installModules([ UserModule(), OrderModule(), ]); - + // Это должно выбросить CircularDependencyException final userService = scope.resolve(); print('UserService created: $userService'); } catch (e) { print('❌ Circular dependency detected: $e\n'); } - + // Example 2: Without circular dependency detection (dangerous!) print('2. Same code without circular dependency detection:'); try { final scope = CherryPick.openRootScope(); // НЕ включаем обнаружение циклических зависимостей - + scope.installModules([ UserModule(), OrderModule(), ]); - + // Это приведет к StackOverflowError при попытке использования final userService = scope.resolve(); print('UserService создан: $userService'); - + // Попытка использовать сервис приведет к бесконечной рекурсии // userService.createUser('John'); // Раскомментируйте для демонстрации StackOverflow print('⚠️ UserService created, but using it will cause StackOverflow\n'); } catch (e) { print('❌ Error: $e\n'); } - + // Example 3: Correct architecture without circular dependencies print('3. Correct architecture without circular dependencies:'); try { final scope = CherryPick.openRootScope(); scope.enableCycleDetection(); // Включаем для безопасности - + scope.installModules([ ImprovedUserModule(), ImprovedOrderModule(), ]); - + final userService = scope.resolve(); final orderService = scope.resolve(); - + print('✅ Services created successfully'); - + // Демонстрация работы userService.createUser('John'); orderService.createOrder('ORD-001', 'John'); final orders = orderService.getOrdersForUser('John'); print('✅ Orders for user John: $orders'); - } catch (e) { print('❌ Error: $e'); } - + print('\n=== Recommendations ==='); print('1. Always enable circular dependency detection in development mode.'); print('2. Use repositories and services to separate concerns.'); diff --git a/cherrypick/lib/src/cycle_detector.dart b/cherrypick/lib/src/cycle_detector.dart index 8106675..8a15edf 100644 --- a/cherrypick/lib/src/cycle_detector.dart +++ b/cherrypick/lib/src/cycle_detector.dart @@ -77,7 +77,8 @@ class CycleDetector { ); if (_resolutionStack.contains(dependencyKey)) { final cycleStartIndex = _resolutionHistory.indexOf(dependencyKey); - final cycle = _resolutionHistory.sublist(cycleStartIndex)..add(dependencyKey); + final cycle = _resolutionHistory.sublist(cycleStartIndex) + ..add(dependencyKey); _observer.onCycleDetected(cycle); _observer.onError('Cycle detected for $dependencyKey', null, null); throw CircularDependencyException( @@ -99,7 +100,8 @@ class CycleDetector { ); _resolutionStack.remove(dependencyKey); // Only remove from history if it's the last one - if (_resolutionHistory.isNotEmpty && _resolutionHistory.last == dependencyKey) { + if (_resolutionHistory.isNotEmpty && + _resolutionHistory.last == dependencyKey) { _resolutionHistory.removeLast(); } } @@ -124,7 +126,8 @@ class CycleDetector { } /// Gets the current dependency resolution chain (for diagnostics or debugging). - List get currentResolutionChain => List.unmodifiable(_resolutionHistory); + List get currentResolutionChain => + List.unmodifiable(_resolutionHistory); /// Returns a unique string key for type [T] (+name). String _createDependencyKey(String? named) { @@ -200,12 +203,13 @@ mixin CycleDetectionMixin { return action(); } - final dependencyKey = named != null - ? '${dependencyType.toString()}@$named' + final dependencyKey = named != null + ? '${dependencyType.toString()}@$named' : dependencyType.toString(); if (_cycleDetector!._resolutionStack.contains(dependencyKey)) { - final cycleStartIndex = _cycleDetector!._resolutionHistory.indexOf(dependencyKey); + final cycleStartIndex = + _cycleDetector!._resolutionHistory.indexOf(dependencyKey); final cycle = _cycleDetector!._resolutionHistory.sublist(cycleStartIndex) ..add(dependencyKey); observer.onCycleDetected(cycle); @@ -223,7 +227,7 @@ mixin CycleDetectionMixin { return action(); } finally { _cycleDetector!._resolutionStack.remove(dependencyKey); - if (_cycleDetector!._resolutionHistory.isNotEmpty && + if (_cycleDetector!._resolutionHistory.isNotEmpty && _cycleDetector!._resolutionHistory.last == dependencyKey) { _cycleDetector!._resolutionHistory.removeLast(); } @@ -231,6 +235,6 @@ mixin CycleDetectionMixin { } /// Gets the current active dependency resolution chain. - List get currentResolutionChain => + List get currentResolutionChain => _cycleDetector?.currentResolutionChain ?? []; } diff --git a/cherrypick/lib/src/global_cycle_detector.dart b/cherrypick/lib/src/global_cycle_detector.dart index 47e7ab9..ac74e25 100644 --- a/cherrypick/lib/src/global_cycle_detector.dart +++ b/cherrypick/lib/src/global_cycle_detector.dart @@ -14,7 +14,6 @@ import 'dart:collection'; import 'package:cherrypick/cherrypick.dart'; - /// GlobalCycleDetector detects and prevents circular dependencies across an entire DI scope hierarchy. /// /// This is particularly important for modular/feature-based applications @@ -45,13 +44,16 @@ class GlobalCycleDetector { final List _globalResolutionHistory = []; // Map of active detectors for subscopes (rarely used directly) - final Map _scopeDetectors = HashMap(); + final Map _scopeDetectors = + HashMap(); - GlobalCycleDetector._internal({required CherryPickObserver observer}): _observer = observer; + GlobalCycleDetector._internal({required CherryPickObserver observer}) + : _observer = observer; /// Returns the singleton global detector instance, initializing it if needed. static GlobalCycleDetector get instance { - _instance ??= GlobalCycleDetector._internal(observer: CherryPick.globalObserver); + _instance ??= + GlobalCycleDetector._internal(observer: CherryPick.globalObserver); return _instance!; } @@ -70,9 +72,11 @@ class GlobalCycleDetector { if (_globalResolutionStack.contains(dependencyKey)) { final cycleStartIndex = _globalResolutionHistory.indexOf(dependencyKey); - final cycle = _globalResolutionHistory.sublist(cycleStartIndex)..add(dependencyKey); + final cycle = _globalResolutionHistory.sublist(cycleStartIndex) + ..add(dependencyKey); _observer.onCycleDetected(cycle, scopeName: scopeId); - _observer.onError('Global circular dependency detected for $dependencyKey', null, null); + _observer.onError( + 'Global circular dependency detected for $dependencyKey', null, null); throw CircularDependencyException( 'Global circular dependency detected for $dependencyKey', cycle, @@ -88,7 +92,8 @@ class GlobalCycleDetector { final dependencyKey = _createDependencyKeyFromType(T, named, scopeId); _globalResolutionStack.remove(dependencyKey); - if (_globalResolutionHistory.isNotEmpty && _globalResolutionHistory.last == dependencyKey) { + if (_globalResolutionHistory.isNotEmpty && + _globalResolutionHistory.last == dependencyKey) { _globalResolutionHistory.removeLast(); } } @@ -101,13 +106,16 @@ class GlobalCycleDetector { String? scopeId, T Function() action, ) { - final dependencyKey = _createDependencyKeyFromType(dependencyType, named, scopeId); + final dependencyKey = + _createDependencyKeyFromType(dependencyType, named, scopeId); if (_globalResolutionStack.contains(dependencyKey)) { final cycleStartIndex = _globalResolutionHistory.indexOf(dependencyKey); - final cycle = _globalResolutionHistory.sublist(cycleStartIndex)..add(dependencyKey); + final cycle = _globalResolutionHistory.sublist(cycleStartIndex) + ..add(dependencyKey); _observer.onCycleDetected(cycle, scopeName: scopeId); - _observer.onError('Global circular dependency detected for $dependencyKey', null, null); + _observer.onError( + 'Global circular dependency detected for $dependencyKey', null, null); throw CircularDependencyException( 'Global circular dependency detected for $dependencyKey', cycle, @@ -121,7 +129,8 @@ class GlobalCycleDetector { return action(); } finally { _globalResolutionStack.remove(dependencyKey); - if (_globalResolutionHistory.isNotEmpty && _globalResolutionHistory.last == dependencyKey) { + if (_globalResolutionHistory.isNotEmpty && + _globalResolutionHistory.last == dependencyKey) { _globalResolutionHistory.removeLast(); } } @@ -129,7 +138,8 @@ class GlobalCycleDetector { /// Get per-scope detector (not usually needed by consumers). CycleDetector getScopeDetector(String scopeId) { - return _scopeDetectors.putIfAbsent(scopeId, () => CycleDetector(observer: CherryPick.globalObserver)); + return _scopeDetectors.putIfAbsent( + scopeId, () => CycleDetector(observer: CherryPick.globalObserver)); } /// Remove detector for a given scope. @@ -144,7 +154,8 @@ class GlobalCycleDetector { } /// Get current global dependency resolution chain (for debugging or diagnostics). - List get globalResolutionChain => List.unmodifiable(_globalResolutionHistory); + List get globalResolutionChain => + List.unmodifiable(_globalResolutionHistory); /// Clears all global and per-scope state in this detector. void clear() { @@ -157,7 +168,8 @@ class GlobalCycleDetector { void _detectorClear(detector) => detector.clear(); /// Creates a unique dependency key string including scope and name (for diagnostics/cycle checks). - String _createDependencyKeyFromType(Type type, String? named, String? scopeId) { + String _createDependencyKeyFromType( + Type type, String? named, String? scopeId) { final typeName = type.toString(); final namePrefix = named != null ? '@$named' : ''; final scopePrefix = scopeId != null ? '[$scopeId]' : ''; diff --git a/cherrypick/lib/src/helper.dart b/cherrypick/lib/src/helper.dart index 877f53e..eca3464 100644 --- a/cherrypick/lib/src/helper.dart +++ b/cherrypick/lib/src/helper.dart @@ -16,7 +16,6 @@ import 'package:cherrypick/src/global_cycle_detector.dart'; import 'package:cherrypick/src/observer.dart'; import 'package:meta/meta.dart'; - Scope? _rootScope; /// Global logger for all [Scope]s managed by [CherryPick]. @@ -80,7 +79,8 @@ class CherryPick { if (_globalCycleDetectionEnabled && !_rootScope!.isCycleDetectionEnabled) { _rootScope!.enableCycleDetection(); } - if (_globalCrossScopeCycleDetectionEnabled && !_rootScope!.isGlobalCycleDetectionEnabled) { + if (_globalCrossScopeCycleDetectionEnabled && + !_rootScope!.isGlobalCycleDetectionEnabled) { _rootScope!.enableGlobalCycleDetection(); } return _rootScope!; @@ -96,7 +96,8 @@ class CherryPick { /// ``` static Future closeRootScope() async { if (_rootScope != null) { - await _rootScope!.dispose(); // Автоматический вызов dispose для rootScope! + await _rootScope! + .dispose(); // Автоматический вызов dispose для rootScope! _rootScope = null; } } @@ -141,13 +142,15 @@ class CherryPick { /// ```dart /// CherryPick.enableCycleDetectionForScope(scopeName: 'api.feature'); /// ``` - static void enableCycleDetectionForScope({String scopeName = '', String separator = '.'}) { + static void enableCycleDetectionForScope( + {String scopeName = '', String separator = '.'}) { final scope = _getScope(scopeName, separator); scope.enableCycleDetection(); } /// Disables cycle detection for a given scope. See [enableCycleDetectionForScope]. - static void disableCycleDetectionForScope({String scopeName = '', String separator = '.'}) { + static void disableCycleDetectionForScope( + {String scopeName = '', String separator = '.'}) { final scope = _getScope(scopeName, separator); scope.disableCycleDetection(); } @@ -158,7 +161,8 @@ class CherryPick { /// ```dart /// CherryPick.isCycleDetectionEnabledForScope(scopeName: 'feature.api'); /// ``` - static bool isCycleDetectionEnabledForScope({String scopeName = '', String separator = '.'}) { + static bool isCycleDetectionEnabledForScope( + {String scopeName = '', String separator = '.'}) { final scope = _getScope(scopeName, separator); return scope.isCycleDetectionEnabled; } @@ -171,7 +175,8 @@ class CherryPick { /// ```dart /// print(CherryPick.getCurrentResolutionChain(scopeName: 'feature.api')); /// ``` - static List getCurrentResolutionChain({String scopeName = '', String separator = '.'}) { + static List getCurrentResolutionChain( + {String scopeName = '', String separator = '.'}) { final scope = _getScope(scopeName, separator); return scope.currentResolutionChain; } @@ -229,14 +234,13 @@ class CherryPick { if (nameParts.isEmpty) { throw Exception('Can not open sub scope because scopeName can not split'); } - final scope = nameParts.fold( - openRootScope(), - (Scope previous, String element) => previous.openSubScope(element) - ); + final scope = nameParts.fold(openRootScope(), + (Scope previous, String element) => previous.openSubScope(element)); if (_globalCycleDetectionEnabled && !scope.isCycleDetectionEnabled) { scope.enableCycleDetection(); } - if (_globalCrossScopeCycleDetectionEnabled && !scope.isGlobalCycleDetectionEnabled) { + if (_globalCrossScopeCycleDetectionEnabled && + !scope.isGlobalCycleDetectionEnabled) { scope.enableGlobalCycleDetection(); } return scope; @@ -252,21 +256,21 @@ class CherryPick { /// CherryPick.closeScope(scopeName: 'network.super.api'); /// ``` @experimental - static Future closeScope({String scopeName = '', String separator = '.'}) async { + static Future closeScope( + {String scopeName = '', String separator = '.'}) async { if (scopeName.isEmpty) { await closeRootScope(); return; } final nameParts = scopeName.split(separator); if (nameParts.isEmpty) { - throw Exception('Can not close sub scope because scopeName can not split'); + throw Exception( + 'Can not close sub scope because scopeName can not split'); } if (nameParts.length > 1) { final lastPart = nameParts.removeLast(); - final scope = nameParts.fold( - openRootScope(), - (Scope previous, String element) => previous.openSubScope(element) - ); + final scope = nameParts.fold(openRootScope(), + (Scope previous, String element) => previous.openSubScope(element)); await scope.closeSubScope(lastPart); } else { await openRootScope().closeSubScope(nameParts.first); @@ -316,7 +320,8 @@ class CherryPick { /// print('Global cross-scope detection is ON'); /// } /// ``` - static bool get isGlobalCrossScopeCycleDetectionEnabled => _globalCrossScopeCycleDetectionEnabled; + static bool get isGlobalCrossScopeCycleDetectionEnabled => + _globalCrossScopeCycleDetectionEnabled; /// Returns the current global dependency resolution chain (across all scopes). /// @@ -367,10 +372,11 @@ class CherryPick { /// ```dart /// final featureScope = CherryPick.openGlobalSafeScope(scopeName: 'featureA.api'); /// ``` - static Scope openGlobalSafeScope({String scopeName = '', String separator = '.'}) { + static Scope openGlobalSafeScope( + {String scopeName = '', String separator = '.'}) { final scope = openScope(scopeName: scopeName, separator: separator); scope.enableCycleDetection(); scope.enableGlobalCycleDetection(); return scope; } -} \ No newline at end of file +} diff --git a/cherrypick/lib/src/module.dart b/cherrypick/lib/src/module.dart index 8559583..2389425 100644 --- a/cherrypick/lib/src/module.dart +++ b/cherrypick/lib/src/module.dart @@ -16,13 +16,13 @@ import 'package:cherrypick/src/binding.dart'; import 'package:cherrypick/src/scope.dart'; /// Represents a DI module—a reusable group of dependency bindings. -/// +/// /// Extend [Module] to declaratively group related [Binding] definitions, /// then install your module(s) into a [Scope] for dependency resolution. -/// +/// /// Modules make it easier to organize your DI configuration for features, layers, /// infrastructure, or integration, and support modular app architecture. -/// +/// /// Usage example: /// ```dart /// class AppModule extends Module { @@ -33,12 +33,12 @@ import 'package:cherrypick/src/scope.dart'; /// bind().toInstance(Config.dev()); /// } /// } -/// +/// /// // Installing the module into the root DI scope: /// final rootScope = CherryPick.openRootScope(); /// rootScope.installModules([AppModule()]); /// ``` -/// +/// /// Combine several modules and submodules to implement scalable architectures. /// abstract class Module { diff --git a/cherrypick/lib/src/observer.dart b/cherrypick/lib/src/observer.dart index 93718d3..0ac3aa5 100644 --- a/cherrypick/lib/src/observer.dart +++ b/cherrypick/lib/src/observer.dart @@ -49,7 +49,8 @@ abstract class CherryPickObserver { /// ```dart /// observer.onInstanceCreated('MyService', MyService, instance, scopeName: 'root'); /// ``` - void onInstanceCreated(String name, Type type, Object instance, {String? scopeName}); + void onInstanceCreated(String name, Type type, Object instance, + {String? scopeName}); /// Called when an instance is disposed (removed from cache and/or finalized). /// @@ -57,7 +58,8 @@ abstract class CherryPickObserver { /// ```dart /// observer.onInstanceDisposed('MyService', MyService, instance, scopeName: 'root'); /// ``` - void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName}); + void onInstanceDisposed(String name, Type type, Object instance, + {String? scopeName}); // === Module events === /// Called when modules are installed into the container. @@ -157,19 +159,23 @@ class PrintCherryPickObserver implements CherryPickObserver { print('[request][CherryPick] $name — $type (scope: $scopeName)'); @override - void onInstanceCreated(String name, Type type, Object instance, {String? scopeName}) => - print('[create][CherryPick] $name — $type => $instance (scope: $scopeName)'); + void onInstanceCreated(String name, Type type, Object instance, + {String? scopeName}) => + print( + '[create][CherryPick] $name — $type => $instance (scope: $scopeName)'); @override - void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName}) => - print('[dispose][CherryPick] $name — $type => $instance (scope: $scopeName)'); + void onInstanceDisposed(String name, Type type, Object instance, + {String? scopeName}) => + print( + '[dispose][CherryPick] $name — $type => $instance (scope: $scopeName)'); @override - void onModulesInstalled(List modules, {String? scopeName}) => - print('[modules installed][CherryPick] ${modules.join(', ')} (scope: $scopeName)'); + void onModulesInstalled(List modules, {String? scopeName}) => print( + '[modules installed][CherryPick] ${modules.join(', ')} (scope: $scopeName)'); @override - void onModulesRemoved(List modules, {String? scopeName}) => - print('[modules removed][CherryPick] ${modules.join(', ')} (scope: $scopeName)'); + void onModulesRemoved(List modules, {String? scopeName}) => print( + '[modules removed][CherryPick] ${modules.join(', ')} (scope: $scopeName)'); @override void onScopeOpened(String name) => print('[scope opened][CherryPick] $name'); @@ -178,8 +184,8 @@ class PrintCherryPickObserver implements CherryPickObserver { void onScopeClosed(String name) => print('[scope closed][CherryPick] $name'); @override - void onCycleDetected(List chain, {String? scopeName}) => - print('[cycle][CherryPick] Detected: ${chain.join(' -> ')}${scopeName != null ? ' (scope: $scopeName)' : ''}'); + void onCycleDetected(List chain, {String? scopeName}) => print( + '[cycle][CherryPick] Detected: ${chain.join(' -> ')}${scopeName != null ? ' (scope: $scopeName)' : ''}'); @override void onCacheHit(String name, Type type, {String? scopeName}) => @@ -210,9 +216,11 @@ class SilentCherryPickObserver implements CherryPickObserver { @override void onInstanceRequested(String name, Type type, {String? scopeName}) {} @override - void onInstanceCreated(String name, Type type, Object instance, {String? scopeName}) {} + void onInstanceCreated(String name, Type type, Object instance, + {String? scopeName}) {} @override - void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName}) {} + void onInstanceDisposed(String name, Type type, Object instance, + {String? scopeName}) {} @override void onModulesInstalled(List modules, {String? scopeName}) {} @override diff --git a/cherrypick/lib/src/scope.dart b/cherrypick/lib/src/scope.dart index 5fb9145..ce5bbe4 100644 --- a/cherrypick/lib/src/scope.dart +++ b/cherrypick/lib/src/scope.dart @@ -68,7 +68,8 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { final Map _scopeMap = HashMap(); - Scope(this._parentScope, {required CherryPickObserver observer}) : _observer = observer { + Scope(this._parentScope, {required CherryPickObserver observer}) + : _observer = observer { setScopeId(_generateScopeId()); observer.onScopeOpened(scopeId ?? 'NO_ID'); observer.onDiagnostic( @@ -87,7 +88,6 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { // индекс для мгновенного поиска binding’ов final Map> _bindingResolvers = {}; - /// Generates a unique identifier string for this scope instance. /// /// Used internally for diagnostics, logging and global scope tracking. @@ -280,7 +280,8 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { return withCycleDetection(T, named, () { var resolved = _tryResolveInternal(named: named, params: params); if (resolved != null) { - observer.onInstanceCreated(T.toString(), T, resolved, scopeName: scopeId); + observer.onInstanceCreated(T.toString(), T, resolved, + scopeName: scopeId); observer.onDiagnostic( 'Successfully resolved: $T', details: { @@ -360,10 +361,12 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { T result; if (isGlobalCycleDetectionEnabled) { result = await withGlobalCycleDetection>(T, named, () async { - return await _resolveAsyncWithLocalDetection(named: named, params: params); + return await _resolveAsyncWithLocalDetection( + named: named, params: params); }); } else { - result = await _resolveAsyncWithLocalDetection(named: named, params: params); + result = await _resolveAsyncWithLocalDetection( + named: named, params: params); } _trackDisposable(result); return result; @@ -371,11 +374,14 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { /// Resolves [T] asynchronously using local cycle detector. Throws if not found. /// Internal implementation for async [resolveAsync]. - Future _resolveAsyncWithLocalDetection({String? named, dynamic params}) async { + Future _resolveAsyncWithLocalDetection( + {String? named, dynamic params}) async { return withCycleDetection>(T, named, () async { - var resolved = await _tryResolveAsyncInternal(named: named, params: params); + var resolved = + await _tryResolveAsyncInternal(named: named, params: params); if (resolved != null) { - observer.onInstanceCreated(T.toString(), T, resolved, scopeName: scopeId); + observer.onInstanceCreated(T.toString(), T, resolved, + scopeName: scopeId); observer.onDiagnostic( 'Successfully async resolved: $T', details: { @@ -410,10 +416,12 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { T? result; if (isGlobalCycleDetectionEnabled) { result = await withGlobalCycleDetection>(T, named, () async { - return await _tryResolveAsyncWithLocalDetection(named: named, params: params); + return await _tryResolveAsyncWithLocalDetection( + named: named, params: params); }); } else { - result = await _tryResolveAsyncWithLocalDetection(named: named, params: params); + result = await _tryResolveAsyncWithLocalDetection( + named: named, params: params); } if (result != null) _trackDisposable(result); return result; @@ -421,7 +429,8 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { /// Attempts to resolve [T] asynchronously using local cycle detector. Returns null if missing. /// Internal implementation for async [tryResolveAsync]. - Future _tryResolveAsyncWithLocalDetection({String? named, dynamic params}) async { + Future _tryResolveAsyncWithLocalDetection( + {String? named, dynamic params}) async { if (isCycleDetectionEnabled) { return withCycleDetection>(T, named, () async { return await _tryResolveAsyncInternal(named: named, params: params); @@ -432,7 +441,8 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { } /// Direct async resolution for [T] without cycle check. Returns null if missing. Internal use only. - Future _tryResolveAsyncInternal({String? named, dynamic params}) async { + Future _tryResolveAsyncInternal( + {String? named, dynamic params}) async { final resolver = _findBindingResolver(named); // 1 - Try from own modules; 2 - Fallback to parent return resolver?.resolveAsync(params) ?? diff --git a/cherrypick/test/logger_integration_test.dart b/cherrypick/test/logger_integration_test.dart index 6bd513b..ca03503 100644 --- a/cherrypick/test/logger_integration_test.dart +++ b/cherrypick/test/logger_integration_test.dart @@ -12,6 +12,7 @@ class DummyModule extends Module { } class A {} + class B {} class CyclicModule extends Module { @@ -52,10 +53,13 @@ void main() { throwsA(isA()), ); // Проверяем, что цикл зафиксирован либо в errors, либо в diagnostics либо cycles - final foundInErrors = observer.errors.any((m) => m.contains('cycle detected')); - final foundInDiagnostics = observer.diagnostics.any((m) => m.contains('cycle detected')); + final foundInErrors = + observer.errors.any((m) => m.contains('cycle detected')); + final foundInDiagnostics = + observer.diagnostics.any((m) => m.contains('cycle detected')); final foundCycleNotified = observer.cycles.isNotEmpty; expect(foundInErrors || foundInDiagnostics || foundCycleNotified, isTrue, - reason: 'Ожидаем хотя бы один лог о цикле! errors: ${observer.errors}\ndiag: ${observer.diagnostics}\ncycles: ${observer.cycles}'); + reason: + 'Ожидаем хотя бы один лог о цикле! errors: ${observer.errors}\ndiag: ${observer.diagnostics}\ncycles: ${observer.cycles}'); }); -} \ No newline at end of file +} diff --git a/cherrypick/test/mock_logger.dart b/cherrypick/test/mock_logger.dart index 67aedac..8bf9666 100644 --- a/cherrypick/test/mock_logger.dart +++ b/cherrypick/test/mock_logger.dart @@ -15,9 +15,8 @@ class MockObserver implements CherryPickObserver { void onWarning(String message, {Object? details}) => warnings.add(message); @override - void onError(String message, Object? error, StackTrace? stackTrace) => - errors.add( - '$message${error != null ? ' $error' : ''}${stackTrace != null ? '\n$stackTrace' : ''}'); + void onError(String message, Object? error, StackTrace? stackTrace) => errors.add( + '$message${error != null ? ' $error' : ''}${stackTrace != null ? '\n$stackTrace' : ''}'); @override void onCycleDetected(List chain, {String? scopeName}) => @@ -30,9 +29,11 @@ class MockObserver implements CherryPickObserver { @override void onInstanceRequested(String name, Type type, {String? scopeName}) {} @override - void onInstanceCreated(String name, Type type, Object instance, {String? scopeName}) {} + void onInstanceCreated(String name, Type type, Object instance, + {String? scopeName}) {} @override - void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName}) {} + void onInstanceDisposed(String name, Type type, Object instance, + {String? scopeName}) {} @override void onModulesInstalled(List moduleNames, {String? scopeName}) {} @override diff --git a/cherrypick/test/src/cross_scope_cycle_test.dart b/cherrypick/test/src/cross_scope_cycle_test.dart index af87a68..a7b0de4 100644 --- a/cherrypick/test/src/cross_scope_cycle_test.dart +++ b/cherrypick/test/src/cross_scope_cycle_test.dart @@ -30,7 +30,7 @@ void main() { final rootScope = CherryPick.openSafeRootScope(); final level1Scope = rootScope.openSubScope('level1'); final level2Scope = level1Scope.openSubScope('level2'); - + level1Scope.enableCycleDetection(); level2Scope.enableCycleDetection(); @@ -46,14 +46,16 @@ void main() { ); }); - test('current implementation limitation - may not detect cross-scope cycles', () { + test( + 'current implementation limitation - may not detect cross-scope cycles', + () { // Этот тест демонстрирует ограничение текущей реализации final parentScope = CherryPick.openRootScope(); parentScope.enableCycleDetection(); - + final childScope = parentScope.openSubScope('child'); // НЕ включаем cycle detection для дочернего скоупа - + parentScope.installModules([ParentScopeModule()]); childScope.installModules([ChildScopeModule()]); diff --git a/cherrypick/test/src/cycle_detector_test.dart b/cherrypick/test/src/cycle_detector_test.dart index f04fc47..ba4f873 100644 --- a/cherrypick/test/src/cycle_detector_test.dart +++ b/cherrypick/test/src/cycle_detector_test.dart @@ -18,7 +18,7 @@ void main() { test('should detect simple circular dependency', () { detector.startResolving(); - + expect( () => detector.startResolving(), throwsA(isA()), @@ -27,7 +27,7 @@ void main() { test('should detect circular dependency with named bindings', () { detector.startResolving(named: 'test'); - + expect( () => detector.startResolving(named: 'test'), throwsA(isA()), @@ -37,7 +37,7 @@ void main() { test('should allow different types to be resolved simultaneously', () { detector.startResolving(); detector.startResolving(); - + expect(() => detector.finishResolving(), returnsNormally); expect(() => detector.finishResolving(), returnsNormally); }); @@ -46,32 +46,31 @@ void main() { detector.startResolving(); detector.startResolving(); detector.startResolving(); - + expect( () => detector.startResolving(), - throwsA(predicate((e) => - e is CircularDependencyException && - e.dependencyChain.contains('String') && - e.dependencyChain.length > 1 - )), + throwsA(predicate((e) => + e is CircularDependencyException && + e.dependencyChain.contains('String') && + e.dependencyChain.length > 1)), ); }); test('should clear state properly', () { detector.startResolving(); detector.clear(); - + expect(() => detector.startResolving(), returnsNormally); }); test('should track resolution history correctly', () { detector.startResolving(); detector.startResolving(); - + expect(detector.currentResolutionChain, contains('String')); expect(detector.currentResolutionChain, contains('int')); expect(detector.currentResolutionChain.length, equals(2)); - + detector.finishResolving(); expect(detector.currentResolutionChain.length, equals(1)); expect(detector.currentResolutionChain, contains('String')); @@ -82,7 +81,7 @@ void main() { test('should detect circular dependency in real scenario', () { final scope = CherryPick.openRootScope(); scope.enableCycleDetection(); - + // Создаем циклическую зависимость: A зависит от B, B зависит от A scope.installModules([ CircularModuleA(), @@ -98,7 +97,7 @@ void main() { test('should work normally without cycle detection enabled', () { final scope = CherryPick.openRootScope(); // Не включаем обнаружение циклических зависимостей - + scope.installModules([ SimpleModule(), ]); @@ -111,7 +110,7 @@ void main() { final scope = CherryPick.openRootScope(); scope.enableCycleDetection(); expect(scope.isCycleDetectionEnabled, isTrue); - + scope.disableCycleDetection(); expect(scope.isCycleDetectionEnabled, isFalse); }); @@ -119,7 +118,7 @@ void main() { test('should handle named dependencies in cycle detection', () { final scope = CherryPick.openRootScope(); scope.enableCycleDetection(); - + scope.installModules([ NamedCircularModule(), ]); @@ -133,7 +132,7 @@ void main() { test('should detect cycles in async resolution', () async { final scope = CherryPick.openRootScope(); scope.enableCycleDetection(); - + scope.installModules([ AsyncCircularModule(), ]); @@ -161,14 +160,16 @@ class ServiceB { class CircularModuleA extends Module { @override void builder(Scope currentScope) { - bind().toProvide(() => ServiceA(currentScope.resolve())); + bind() + .toProvide(() => ServiceA(currentScope.resolve())); } } class CircularModuleB extends Module { @override void builder(Scope currentScope) { - bind().toProvide(() => ServiceB(currentScope.resolve())); + bind() + .toProvide(() => ServiceB(currentScope.resolve())); } } @@ -210,7 +211,7 @@ class AsyncCircularModule extends Module { final serviceB = await currentScope.resolveAsync(); return AsyncServiceA(serviceB); }); - + // ignore: deprecated_member_use_from_same_package bind().toProvideAsync(() async { final serviceA = await currentScope.resolveAsync(); diff --git a/cherrypick/test/src/global_cycle_detection_test.dart b/cherrypick/test/src/global_cycle_detection_test.dart index 1d0c84b..45f7748 100644 --- a/cherrypick/test/src/global_cycle_detection_test.dart +++ b/cherrypick/test/src/global_cycle_detection_test.dart @@ -22,50 +22,57 @@ void main() { group('Global Cross-Scope Cycle Detection', () { test('should enable global cross-scope cycle detection', () { expect(CherryPick.isGlobalCrossScopeCycleDetectionEnabled, isFalse); - + CherryPick.enableGlobalCrossScopeCycleDetection(); - + expect(CherryPick.isGlobalCrossScopeCycleDetectionEnabled, isTrue); }); test('should disable global cross-scope cycle detection', () { CherryPick.enableGlobalCrossScopeCycleDetection(); expect(CherryPick.isGlobalCrossScopeCycleDetectionEnabled, isTrue); - + CherryPick.disableGlobalCrossScopeCycleDetection(); - + expect(CherryPick.isGlobalCrossScopeCycleDetectionEnabled, isFalse); }); - test('should automatically enable global cycle detection for new root scope', () { + test( + 'should automatically enable global cycle detection for new root scope', + () { CherryPick.enableGlobalCrossScopeCycleDetection(); - + final scope = CherryPick.openRootScope(); - + expect(scope.isGlobalCycleDetectionEnabled, isTrue); }); - test('should automatically enable global cycle detection for existing root scope', () { + test( + 'should automatically enable global cycle detection for existing root scope', + () { final scope = CherryPick.openRootScope(); expect(scope.isGlobalCycleDetectionEnabled, isFalse); - + CherryPick.enableGlobalCrossScopeCycleDetection(); - + expect(scope.isGlobalCycleDetectionEnabled, isTrue); }); }); group('Global Safe Scope Creation', () { - test('should create global safe root scope with both detections enabled', () { + test('should create global safe root scope with both detections enabled', + () { final scope = CherryPick.openGlobalSafeRootScope(); - + expect(scope.isCycleDetectionEnabled, isTrue); expect(scope.isGlobalCycleDetectionEnabled, isTrue); }); - test('should create global safe sub-scope with both detections enabled', () { - final scope = CherryPick.openGlobalSafeScope(scopeName: 'feature.global'); - + test('should create global safe sub-scope with both detections enabled', + () { + final scope = + CherryPick.openGlobalSafeScope(scopeName: 'feature.global'); + expect(scope.isCycleDetectionEnabled, isTrue); expect(scope.isGlobalCycleDetectionEnabled, isTrue); }); @@ -104,7 +111,7 @@ void main() { test('should provide detailed global resolution chain in exception', () { final scope = CherryPick.openGlobalSafeRootScope(); scope.installModules([GlobalParentModule()]); - + final childScope = scope.openSubScope('child'); childScope.installModules([GlobalChildModule()]); @@ -114,11 +121,11 @@ void main() { } catch (e) { expect(e, isA()); final circularError = e as CircularDependencyException; - + // Проверяем, что цепочка содержит информацию о скоупах expect(circularError.dependencyChain, isNotEmpty); expect(circularError.dependencyChain.length, greaterThan(1)); - + // Цепочка должна содержать оба сервиса final chainString = circularError.dependencyChain.join(' -> '); expect(chainString, contains('GlobalServiceA')); @@ -144,11 +151,11 @@ void main() { CherryPick.enableGlobalCrossScopeCycleDetection(); // ignore: unused_local_variable final scope = CherryPick.openGlobalSafeRootScope(); - + expect(CherryPick.getGlobalResolutionChain(), isEmpty); - + CherryPick.clearGlobalCycleDetector(); - + // После очистки детектор должен быть сброшен expect(CherryPick.getGlobalResolutionChain(), isEmpty); }); @@ -157,10 +164,10 @@ void main() { group('Inheritance of Global Settings', () { test('should inherit global cycle detection in child scopes', () { CherryPick.enableGlobalCrossScopeCycleDetection(); - + final parentScope = CherryPick.openRootScope(); final childScope = parentScope.openSubScope('child'); - + expect(parentScope.isGlobalCycleDetectionEnabled, isTrue); expect(childScope.isGlobalCycleDetectionEnabled, isTrue); }); @@ -168,9 +175,9 @@ void main() { test('should inherit both local and global cycle detection', () { CherryPick.enableGlobalCycleDetection(); CherryPick.enableGlobalCrossScopeCycleDetection(); - + final scope = CherryPick.openScope(scopeName: 'feature.test'); - + expect(scope.isCycleDetectionEnabled, isTrue); expect(scope.isGlobalCycleDetectionEnabled, isTrue); }); diff --git a/cherrypick/test/src/helper_cycle_detection_test.dart b/cherrypick/test/src/helper_cycle_detection_test.dart index 4eddbcb..dde1925 100644 --- a/cherrypick/test/src/helper_cycle_detection_test.dart +++ b/cherrypick/test/src/helper_cycle_detection_test.dart @@ -24,53 +24,59 @@ void main() { group('Global Cycle Detection', () { test('should enable global cycle detection', () { expect(CherryPick.isGlobalCycleDetectionEnabled, isFalse); - + CherryPick.enableGlobalCycleDetection(); - + expect(CherryPick.isGlobalCycleDetectionEnabled, isTrue); }); test('should disable global cycle detection', () { CherryPick.enableGlobalCycleDetection(); expect(CherryPick.isGlobalCycleDetectionEnabled, isTrue); - + CherryPick.disableGlobalCycleDetection(); - + expect(CherryPick.isGlobalCycleDetectionEnabled, isFalse); }); - test('should automatically enable cycle detection for new root scope when global is enabled', () { + test( + 'should automatically enable cycle detection for new root scope when global is enabled', + () { CherryPick.enableGlobalCycleDetection(); - + final scope = CherryPick.openRootScope(); - + expect(scope.isCycleDetectionEnabled, isTrue); }); - test('should automatically enable cycle detection for existing root scope when global is enabled', () { + test( + 'should automatically enable cycle detection for existing root scope when global is enabled', + () { final scope = CherryPick.openRootScope(); expect(scope.isCycleDetectionEnabled, isFalse); - + CherryPick.enableGlobalCycleDetection(); - + expect(scope.isCycleDetectionEnabled, isTrue); }); - test('should automatically disable cycle detection for existing root scope when global is disabled', () { + test( + 'should automatically disable cycle detection for existing root scope when global is disabled', + () { CherryPick.enableGlobalCycleDetection(); final scope = CherryPick.openRootScope(); expect(scope.isCycleDetectionEnabled, isTrue); - + CherryPick.disableGlobalCycleDetection(); - + expect(scope.isCycleDetectionEnabled, isFalse); }); test('should apply global setting to sub-scopes', () { CherryPick.enableGlobalCycleDetection(); - + final scope = CherryPick.openScope(scopeName: 'test.subscope'); - + expect(scope.isCycleDetectionEnabled, isTrue); }); }); @@ -79,9 +85,9 @@ void main() { test('should enable cycle detection for root scope', () { final scope = CherryPick.openRootScope(); expect(scope.isCycleDetectionEnabled, isFalse); - + CherryPick.enableCycleDetectionForScope(); - + expect(CherryPick.isCycleDetectionEnabledForScope(), isTrue); expect(scope.isCycleDetectionEnabled, isTrue); }); @@ -89,91 +95,103 @@ void main() { test('should disable cycle detection for root scope', () { CherryPick.enableCycleDetectionForScope(); expect(CherryPick.isCycleDetectionEnabledForScope(), isTrue); - + CherryPick.disableCycleDetectionForScope(); - + expect(CherryPick.isCycleDetectionEnabledForScope(), isFalse); }); test('should enable cycle detection for specific scope', () { final scopeName = 'feature.auth'; CherryPick.openScope(scopeName: scopeName); - - expect(CherryPick.isCycleDetectionEnabledForScope(scopeName: scopeName), isFalse); - + + expect(CherryPick.isCycleDetectionEnabledForScope(scopeName: scopeName), + isFalse); + CherryPick.enableCycleDetectionForScope(scopeName: scopeName); - - expect(CherryPick.isCycleDetectionEnabledForScope(scopeName: scopeName), isTrue); + + expect(CherryPick.isCycleDetectionEnabledForScope(scopeName: scopeName), + isTrue); }); test('should disable cycle detection for specific scope', () { final scopeName = 'feature.auth'; CherryPick.enableCycleDetectionForScope(scopeName: scopeName); - expect(CherryPick.isCycleDetectionEnabledForScope(scopeName: scopeName), isTrue); - + expect(CherryPick.isCycleDetectionEnabledForScope(scopeName: scopeName), + isTrue); + CherryPick.disableCycleDetectionForScope(scopeName: scopeName); - - expect(CherryPick.isCycleDetectionEnabledForScope(scopeName: scopeName), isFalse); + + expect(CherryPick.isCycleDetectionEnabledForScope(scopeName: scopeName), + isFalse); }); }); group('Safe Scope Creation', () { test('should create safe root scope with cycle detection enabled', () { final scope = CherryPick.openSafeRootScope(); - + expect(scope.isCycleDetectionEnabled, isTrue); }); test('should create safe sub-scope with cycle detection enabled', () { final scope = CherryPick.openSafeScope(scopeName: 'feature.safe'); - + expect(scope.isCycleDetectionEnabled, isTrue); }); test('safe scope should work independently of global setting', () { // Глобальная настройка отключена expect(CherryPick.isGlobalCycleDetectionEnabled, isFalse); - - final scope = CherryPick.openSafeScope(scopeName: 'feature.independent'); - + + final scope = + CherryPick.openSafeScope(scopeName: 'feature.independent'); + expect(scope.isCycleDetectionEnabled, isTrue); }); }); group('Resolution Chain Tracking', () { - test('should return empty resolution chain for scope without cycle detection', () { + test( + 'should return empty resolution chain for scope without cycle detection', + () { CherryPick.openRootScope(); - + final chain = CherryPick.getCurrentResolutionChain(); - + expect(chain, isEmpty); }); - test('should return empty resolution chain for scope with cycle detection but no active resolution', () { + test( + 'should return empty resolution chain for scope with cycle detection but no active resolution', + () { CherryPick.enableCycleDetectionForScope(); - + final chain = CherryPick.getCurrentResolutionChain(); - + expect(chain, isEmpty); }); test('should track resolution chain for specific scope', () { final scopeName = 'feature.tracking'; CherryPick.enableCycleDetectionForScope(scopeName: scopeName); - - final chain = CherryPick.getCurrentResolutionChain(scopeName: scopeName); - + + final chain = + CherryPick.getCurrentResolutionChain(scopeName: scopeName); + expect(chain, isEmpty); // Пустая, так как нет активного разрешения }); }); group('Integration with Circular Dependencies', () { - test('should detect circular dependency with global cycle detection enabled', () { + test( + 'should detect circular dependency with global cycle detection enabled', + () { CherryPick.enableGlobalCycleDetection(); - + final scope = CherryPick.openRootScope(); scope.installModules([CircularTestModule()]); - + expect( () => scope.resolve(), throwsA(isA()), @@ -183,44 +201,54 @@ void main() { test('should detect circular dependency with safe scope', () { final scope = CherryPick.openSafeRootScope(); scope.installModules([CircularTestModule()]); - + expect( () => scope.resolve(), throwsA(isA()), ); }); - test('should not detect circular dependency when cycle detection is disabled', () { + test( + 'should not detect circular dependency when cycle detection is disabled', + () { final scope = CherryPick.openRootScope(); scope.installModules([CircularTestModule()]); - + // Без обнаружения циклических зависимостей не будет выброшено CircularDependencyException, // но может произойти StackOverflowError при попытке создания объекта - expect(() => scope.resolve(), - throwsA(isA())); + expect(() => scope.resolve(), + throwsA(isA())); }); }); group('Scope Name Handling', () { test('should handle empty scope name as root scope', () { CherryPick.enableCycleDetectionForScope(scopeName: ''); - - expect(CherryPick.isCycleDetectionEnabledForScope(scopeName: ''), isTrue); + + expect( + CherryPick.isCycleDetectionEnabledForScope(scopeName: ''), isTrue); expect(CherryPick.isCycleDetectionEnabledForScope(), isTrue); }); test('should handle complex scope names', () { final complexScopeName = 'app.feature.auth.login'; CherryPick.enableCycleDetectionForScope(scopeName: complexScopeName); - - expect(CherryPick.isCycleDetectionEnabledForScope(scopeName: complexScopeName), isTrue); + + expect( + CherryPick.isCycleDetectionEnabledForScope( + scopeName: complexScopeName), + isTrue); }); test('should handle custom separator', () { final scopeName = 'app/feature/auth'; - CherryPick.enableCycleDetectionForScope(scopeName: scopeName, separator: '/'); - - expect(CherryPick.isCycleDetectionEnabledForScope(scopeName: scopeName, separator: '/'), isTrue); + CherryPick.enableCycleDetectionForScope( + scopeName: scopeName, separator: '/'); + + expect( + CherryPick.isCycleDetectionEnabledForScope( + scopeName: scopeName, separator: '/'), + isTrue); }); }); }); @@ -240,7 +268,9 @@ class CircularServiceB { class CircularTestModule extends Module { @override void builder(Scope currentScope) { - bind().toProvide(() => CircularServiceA(currentScope.resolve())); - bind().toProvide(() => CircularServiceB(currentScope.resolve())); + bind().toProvide( + () => CircularServiceA(currentScope.resolve())); + bind().toProvide( + () => CircularServiceB(currentScope.resolve())); } } diff --git a/cherrypick/test/src/scope_test.dart b/cherrypick/test/src/scope_test.dart index 0466408..ed968ab 100644 --- a/cherrypick/test/src/scope_test.dart +++ b/cherrypick/test/src/scope_test.dart @@ -1,4 +1,5 @@ -import 'package:cherrypick/cherrypick.dart' show Disposable, Module, Scope, CherryPick; +import 'package:cherrypick/cherrypick.dart' + show Disposable, Module, Scope, CherryPick; import 'dart:async'; import 'package:test/test.dart'; import '../mock_logger.dart'; @@ -18,7 +19,9 @@ class AsyncExampleDisposable implements Disposable { class AsyncExampleModule extends Module { @override void builder(Scope scope) { - bind().toProvide(() => AsyncExampleDisposable()).singleton(); + bind() + .toProvide(() => AsyncExampleDisposable()) + .singleton(); } } @@ -49,7 +52,9 @@ class CountingDisposable implements Disposable { class ModuleCountingDisposable extends Module { @override void builder(Scope scope) { - bind().toProvide(() => CountingDisposable()).singleton(); + bind() + .toProvide(() => CountingDisposable()) + .singleton(); } } @@ -97,10 +102,9 @@ class AsyncModule extends Module { bind() // ignore: deprecated_member_use_from_same_package .toProvideAsync(() async { - await Future.delayed(Duration(milliseconds: 10)); - return AsyncCreatedDisposable(); - }) - .singleton(); + await Future.delayed(Duration(milliseconds: 10)); + return AsyncCreatedDisposable(); + }).singleton(); } } @@ -119,7 +123,8 @@ void main() { final scope = Scope(null, observer: observer); expect(Scope(scope, observer: observer), isNotNull); // эквивалент }); - test('closeSubScope removes subscope so next openSubScope returns new', () async { + test('closeSubScope removes subscope so next openSubScope returns new', + () async { final observer = MockObserver(); final scope = Scope(null, observer: observer); final subScope = scope.openSubScope("child"); @@ -181,7 +186,8 @@ void main() { }); test("After dropModules resolves fail", () { final observer = MockObserver(); - final scope = Scope(null, observer: observer)..installModules([TestModule(value: 5)]); + final scope = Scope(null, observer: observer) + ..installModules([TestModule(value: 5)]); expect(scope.resolve(), 5); scope.dropModules(); expect(() => scope.resolve(), throwsA(isA())); @@ -294,7 +300,8 @@ void main() { await scope.dispose(); expect(t.disposed, isTrue); }); - test('scope.disposeAsync calls dispose on all unique disposables', () async { + test('scope.disposeAsync calls dispose on all unique disposables', + () async { final scope = Scope(null, observer: MockObserver()); scope.installModules([ModuleWithDisposable()]); final t1 = scope.resolve(); @@ -305,7 +312,8 @@ void main() { expect(t1.disposed, isTrue); expect(t2.disposed, isTrue); }); - test('calling disposeAsync twice does not throw and not call twice', () async { + test('calling disposeAsync twice does not throw and not call twice', + () async { final scope = CherryPick.openRootScope(); scope.installModules([ModuleWithDisposable()]); final t = scope.resolve(); @@ -313,7 +321,8 @@ void main() { await scope.dispose(); expect(t.disposed, isTrue); }); - test('Non-disposable dependency is ignored by scope.disposeAsync', () async { + test('Non-disposable dependency is ignored by scope.disposeAsync', + () async { final scope = CherryPick.openRootScope(); scope.installModules([ModuleWithDisposable()]); final s = scope.resolve(); @@ -327,7 +336,8 @@ void main() { group('Scope/subScope dispose edge cases', () { test('Dispose called in closed subScope only', () async { final root = CherryPick.openRootScope(); - final sub = root.openSubScope('feature')..installModules([ModuleCountingDisposable()]); + final sub = root.openSubScope('feature') + ..installModules([ModuleCountingDisposable()]); final d = sub.resolve(); expect(d.disposeCount, 0); @@ -339,7 +349,8 @@ void main() { expect(d.disposeCount, 1); // Повторное открытие subScope создает NEW instance (dispose на старый не вызовется снова) - final sub2 = root.openSubScope('feature')..installModules([ModuleCountingDisposable()]); + final sub2 = root.openSubScope('feature') + ..installModules([ModuleCountingDisposable()]); final d2 = sub2.resolve(); expect(identical(d, d2), isFalse); await root.closeSubScope('feature'); @@ -347,8 +358,14 @@ void main() { }); test('Dispose for all nested subScopes on root disposeAsync', () async { final root = CherryPick.openRootScope(); - root.openSubScope('a').openSubScope('b').installModules([ModuleCountingDisposable()]); - final d = root.openSubScope('a').openSubScope('b').resolve(); + root + .openSubScope('a') + .openSubScope('b') + .installModules([ModuleCountingDisposable()]); + final d = root + .openSubScope('a') + .openSubScope('b') + .resolve(); await root.dispose(); expect(d.disposeCount, 1); }); @@ -357,11 +374,12 @@ void main() { // -------------------------------------------------------------------------- group('Async disposable (Future test)', () { test('Async Disposable is awaited on disposeAsync', () async { - final scope = CherryPick.openRootScope()..installModules([AsyncExampleModule()]); + final scope = CherryPick.openRootScope() + ..installModules([AsyncExampleModule()]); final d = scope.resolve(); expect(d.disposed, false); await scope.dispose(); expect(d.disposed, true); }); }); -} \ No newline at end of file +} diff --git a/cherrypick_flutter/lib/src/cherrypick_provider.dart b/cherrypick_flutter/lib/src/cherrypick_provider.dart index a12989b..b7cb9d9 100644 --- a/cherrypick_flutter/lib/src/cherrypick_provider.dart +++ b/cherrypick_flutter/lib/src/cherrypick_provider.dart @@ -21,7 +21,7 @@ import 'package:flutter/widgets.dart'; /// Place `CherryPickProvider` at the top of your widget subtree to make a /// [Scope] (or its descendants) available via `CherryPickProvider.of(context)`. /// -/// This is the recommended entry point for connecting CherryPick DI to your +/// This is the recommended entry point for connecting CherryPick DI to your /// Flutter app or feature area, enabling context-based scope management and /// DI resolution in child widgets. /// @@ -36,7 +36,7 @@ import 'package:flutter/widgets.dart'; /// ), /// ); /// } -/// +/// /// // In any widget: /// final provider = CherryPickProvider.of(context); /// final scope = provider.openRootScope(); diff --git a/cherrypick_generator/lib/inject_generator.dart b/cherrypick_generator/lib/inject_generator.dart index 33df2f3..4fff301 100644 --- a/cherrypick_generator/lib/inject_generator.dart +++ b/cherrypick_generator/lib/inject_generator.dart @@ -248,7 +248,6 @@ class _ParsedInjectField { /// Name qualifier for named resolution, or null if not set. final String? namedValue; - _ParsedInjectField({ required this.fieldName, required this.coreType, diff --git a/cherrypick_generator/lib/src/bind_spec.dart b/cherrypick_generator/lib/src/bind_spec.dart index 280bec3..5ebb857 100644 --- a/cherrypick_generator/lib/src/bind_spec.dart +++ b/cherrypick_generator/lib/src/bind_spec.dart @@ -23,6 +23,7 @@ import 'annotation_validator.dart'; enum BindingType { /// Direct instance returned from the method (@instance). instance, + /// Provider/factory function (@provide). provide; } diff --git a/cherrypick_generator/test/bind_spec_test.dart b/cherrypick_generator/test/bind_spec_test.dart index 64295b6..b3be02f 100644 --- a/cherrypick_generator/test/bind_spec_test.dart +++ b/cherrypick_generator/test/bind_spec_test.dart @@ -244,8 +244,7 @@ void main() { final result = bindSpec.generateBind(4); expect( result, - equals( - " bind()\n" + equals(" bind()\n" " .toProvideAsync(() => createApiClient())\n" " .withName('mainApi')\n" " .singleton();")); diff --git a/examples/client_app/pubspec.lock b/examples/client_app/pubspec.lock index 46cc56a..8e3fb00 100644 --- a/examples/client_app/pubspec.lock +++ b/examples/client_app/pubspec.lock @@ -127,28 +127,28 @@ packages: path: "../../cherrypick" relative: true source: path - version: "3.0.0-dev.8" + version: "3.0.0-dev.9" cherrypick_annotations: dependency: "direct main" description: path: "../../cherrypick_annotations" relative: true source: path - version: "1.1.1" + version: "1.1.2-dev.0" cherrypick_flutter: dependency: "direct main" description: path: "../../cherrypick_flutter" relative: true source: path - version: "1.1.3-dev.8" + version: "1.1.3-dev.9" cherrypick_generator: dependency: "direct dev" description: path: "../../cherrypick_generator" relative: true source: path - version: "1.1.1" + version: "2.0.0-dev.0" clock: dependency: transitive description: diff --git a/examples/postly/lib/app.dart b/examples/postly/lib/app.dart index 94012ec..1f5c3d7 100644 --- a/examples/postly/lib/app.dart +++ b/examples/postly/lib/app.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:talker_flutter/talker_flutter.dart'; - import 'domain/repository/post_repository.dart'; import 'presentation/bloc/post_bloc.dart'; import 'router/app_router.dart'; @@ -14,9 +13,11 @@ part 'app.inject.cherrypick.g.dart'; class TalkerProvider extends InheritedWidget { final Talker talker; const TalkerProvider({required this.talker, required super.child, super.key}); - static Talker of(BuildContext context) => context.dependOnInheritedWidgetOfExactType()!.talker; + static Talker of(BuildContext context) => + context.dependOnInheritedWidgetOfExactType()!.talker; @override - bool updateShouldNotify(TalkerProvider oldWidget) => oldWidget.talker != talker; + bool updateShouldNotify(TalkerProvider oldWidget) => + oldWidget.talker != talker; } @injectable() diff --git a/examples/postly/lib/di/app_module.dart b/examples/postly/lib/di/app_module.dart index c6df93b..2ef5ac3 100644 --- a/examples/postly/lib/di/app_module.dart +++ b/examples/postly/lib/di/app_module.dart @@ -15,14 +15,16 @@ abstract class AppModule extends Module { @provide() @singleton() TalkerDioLoggerSettings talkerDioLoggerSettings() => TalkerDioLoggerSettings( - printRequestHeaders: true, - printResponseHeaders: true, - printResponseMessage: true, - ); + printRequestHeaders: true, + printResponseHeaders: true, + printResponseMessage: true, + ); @provide() @singleton() - TalkerDioLogger talkerDioLogger(Talker talker, TalkerDioLoggerSettings settings) => TalkerDioLogger(talker: talker, settings: settings); + TalkerDioLogger talkerDioLogger( + Talker talker, TalkerDioLoggerSettings settings) => + TalkerDioLogger(talker: talker, settings: settings); @instance() int timeout() => 1000; diff --git a/examples/postly/lib/di/core_module.dart b/examples/postly/lib/di/core_module.dart index 63e024a..d65fd10 100644 --- a/examples/postly/lib/di/core_module.dart +++ b/examples/postly/lib/di/core_module.dart @@ -5,9 +5,9 @@ class CoreModule extends Module { final Talker _talker; CoreModule({required Talker talker}) : _talker = talker; - + @override void builder(Scope currentScope) { bind().toProvide(() => _talker).singleton(); } -} \ No newline at end of file +} diff --git a/examples/postly/lib/main.dart b/examples/postly/lib/main.dart index 5bc2176..d26f3d3 100644 --- a/examples/postly/lib/main.dart +++ b/examples/postly/lib/main.dart @@ -13,7 +13,6 @@ void main() { final talker = Talker(); final talkerLogger = TalkerCherryPickObserver(talker); - Bloc.observer = TalkerBlocObserver(talker: talker); CherryPick.setGlobalObserver(talkerLogger); @@ -24,7 +23,10 @@ void main() { } // Используем safe root scope для гарантии защиты - CherryPick.openRootScope().installModules([CoreModule(talker: talker), $AppModule()]); + CherryPick.openRootScope() + .installModules([CoreModule(talker: talker), $AppModule()]); - runApp(MyApp(talker: talker,)); + runApp(MyApp( + talker: talker, + )); } diff --git a/examples/postly/pubspec.lock b/examples/postly/pubspec.lock index eda5d36..8e1a4c9 100644 --- a/examples/postly/pubspec.lock +++ b/examples/postly/pubspec.lock @@ -175,21 +175,21 @@ packages: path: "../../cherrypick" relative: true source: path - version: "3.0.0-dev.8" + version: "3.0.0-dev.9" cherrypick_annotations: dependency: "direct main" description: path: "../../cherrypick_annotations" relative: true source: path - version: "1.1.1" + version: "1.1.2-dev.0" cherrypick_generator: dependency: "direct main" description: path: "../../cherrypick_generator" relative: true source: path - version: "1.1.1" + version: "2.0.0-dev.0" cli_launcher: dependency: transitive description: @@ -864,7 +864,7 @@ packages: path: "../../talker_cherrypick_logger" relative: true source: path - version: "1.0.0" + version: "1.1.0-dev.3" talker_dio_logger: dependency: "direct main" description: diff --git a/pubspec.lock b/pubspec.lock index eb70210..89c1b0a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,23 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77" + sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" url: "https://pub.dev" source: hosted - version: "73.0.0" + version: "76.0.0" _macros: dependency: transitive description: dart source: sdk - version: "0.3.2" + version: "0.3.3" analyzer: dependency: transitive description: name: analyzer - sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a" + sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" url: "https://pub.dev" source: hosted - version: "6.8.0" + version: "6.11.0" ansi_styles: dependency: transitive description: @@ -298,10 +298,10 @@ packages: dependency: transitive description: name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" url: "https://pub.dev" source: hosted - version: "0.1.2-main.4" + version: "0.1.3-main.0" matcher: dependency: transitive description: diff --git a/talker_cherrypick_logger/lib/src/talker_cherrypick_observer.dart b/talker_cherrypick_logger/lib/src/talker_cherrypick_observer.dart index 8ef3ef5..f1f5610 100644 --- a/talker_cherrypick_logger/lib/src/talker_cherrypick_observer.dart +++ b/talker_cherrypick_logger/lib/src/talker_cherrypick_observer.dart @@ -69,26 +69,32 @@ class TalkerCherryPickObserver implements CherryPickObserver { /// Called when a new instance is created. @override - void onInstanceCreated(String name, Type type, Object instance, {String? scopeName}) { - talker.info('[create][CherryPick] $name — $type => $instance (scope: $scopeName)'); + void onInstanceCreated(String name, Type type, Object instance, + {String? scopeName}) { + talker.info( + '[create][CherryPick] $name — $type => $instance (scope: $scopeName)'); } /// Called when an instance is disposed. @override - void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName}) { - talker.info('[dispose][CherryPick] $name — $type => $instance (scope: $scopeName)'); + void onInstanceDisposed(String name, Type type, Object instance, + {String? scopeName}) { + talker.info( + '[dispose][CherryPick] $name — $type => $instance (scope: $scopeName)'); } /// Called when modules are installed. @override void onModulesInstalled(List modules, {String? scopeName}) { - talker.info('[modules installed][CherryPick] ${modules.join(', ')} (scope: $scopeName)'); + talker.info( + '[modules installed][CherryPick] ${modules.join(', ')} (scope: $scopeName)'); } /// Called when modules are removed. @override void onModulesRemoved(List modules, {String? scopeName}) { - talker.info('[modules removed][CherryPick] ${modules.join(', ')} (scope: $scopeName)'); + talker.info( + '[modules removed][CherryPick] ${modules.join(', ')} (scope: $scopeName)'); } /// Called when a DI scope is opened. @@ -106,7 +112,8 @@ class TalkerCherryPickObserver implements CherryPickObserver { /// Called if the DI container detects a cycle in the dependency graph. @override void onCycleDetected(List chain, {String? scopeName}) { - talker.warning('[cycle][CherryPick] Detected: ${chain.join(' -> ')}${scopeName != null ? ' (scope: $scopeName)' : ''}'); + talker.warning( + '[cycle][CherryPick] Detected: ${chain.join(' -> ')}${scopeName != null ? ' (scope: $scopeName)' : ''}'); } /// Called when an instance is found in the cache. @@ -136,6 +143,7 @@ class TalkerCherryPickObserver implements CherryPickObserver { /// Called for error events with optional stack trace. @override void onError(String message, Object? error, StackTrace? stackTrace) { - talker.handle(error ?? '[CherryPick] $message', stackTrace, '[error][CherryPick] $message'); + talker.handle(error ?? '[CherryPick] $message', stackTrace, + '[error][CherryPick] $message'); } -} \ No newline at end of file +} diff --git a/talker_cherrypick_logger/test/talker_cherrypick_logger_test.dart b/talker_cherrypick_logger/test/talker_cherrypick_logger_test.dart index 6152119..df93e32 100644 --- a/talker_cherrypick_logger/test/talker_cherrypick_logger_test.dart +++ b/talker_cherrypick_logger/test/talker_cherrypick_logger_test.dart @@ -15,7 +15,8 @@ void main() { test('onInstanceRequested logs info', () { observer.onInstanceRequested('A', String, scopeName: 'test'); final log = talker.history.last; - expect(log.message, contains('[request][CherryPick] A — String (scope: test)')); + expect(log.message, + contains('[request][CherryPick] A — String (scope: test)')); }); test('onCycleDetected logs warning', () {