From 0797e9e374628072722ac80588830a889619f776 Mon Sep 17 00:00:00 2001 From: David Morgan Date: Mon, 3 Feb 2025 09:20:36 +0100 Subject: [PATCH] Make `resolver_test.dart` test `AnalysisDriverModel` exactly like it tests `BuildAssetUriResolver`. Add minimal implementation to `AnalysisDriverModel` to make the tests pass. Add TODOs where there is functionality not covered by tests that is therefore not added. --- build_resolvers/CHANGELOG.md | 3 +- .../lib/src/analysis_driver_model.dart | 132 ++++- .../analysis_driver_model_uri_resolver.dart | 1 + build_resolvers/lib/src/resolver.dart | 19 +- build_resolvers/test/resolver_test.dart | 457 ++++++++++-------- 5 files changed, 383 insertions(+), 229 deletions(-) diff --git a/build_resolvers/CHANGELOG.md b/build_resolvers/CHANGELOG.md index a38143154..961f00cb5 100644 --- a/build_resolvers/CHANGELOG.md +++ b/build_resolvers/CHANGELOG.md @@ -1,7 +1,8 @@ ## 2.4.4-wip - Refactor `BuildAssetUriResolver` into `AnalysisDriverModel` and - `AnalysisDriverModelUriResolver`. + `AnalysisDriverModelUriResolver`. Add new implementation of + `AnalysisDriverModel`. ## 2.4.3 diff --git a/build_resolvers/lib/src/analysis_driver_model.dart b/build_resolvers/lib/src/analysis_driver_model.dart index 72c1ac060..fec4635c5 100644 --- a/build_resolvers/lib/src/analysis_driver_model.dart +++ b/build_resolvers/lib/src/analysis_driver_model.dart @@ -3,11 +3,17 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; +import 'dart:collection'; +import 'package:analyzer/dart/analysis/utilities.dart'; +import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/file_system/memory_file_system.dart'; // ignore: implementation_imports import 'package:analyzer/src/clients/build_resolvers/build_resolvers.dart'; import 'package:build/build.dart'; +import 'package:path/path.dart' as p; + +import 'analysis_driver_model_uri_resolver.dart'; /// Manages analysis driver and related build state. /// @@ -19,36 +25,148 @@ import 'package:build/build.dart'; /// - Maintains an in-memory filesystem that is the analyzer's view of the /// build. /// - Notifies the analyzer of changes to that in-memory filesystem. -abstract class AnalysisDriverModel { +/// +/// TODO(davidmorgan): the implementation here is unfinished and not used +/// anywhere; finish it. See `build_asset_uri_resolver.dart` for the current +/// implementation. +class AnalysisDriverModel { /// In-memory filesystem for the analyzer. - abstract final MemoryResourceProvider resourceProvider; + final MemoryResourceProvider resourceProvider = + MemoryResourceProvider(context: p.posix); /// Notifies that [step] has completed. /// /// All build steps must complete before [reset] is called. - void notifyComplete(BuildStep step); + void notifyComplete(BuildStep step) { + // TODO(davidmorgan): add test coverage, fix implementation. + } /// Clear cached information specific to an individual build. - void reset(); + void reset() { + // TODO(davidmorgan): add test coverage, fix implementation. + } /// Attempts to parse [uri] into an [AssetId] and returns it if it is cached. /// /// Handles 'package:' or 'asset:' URIs, as well as 'file:' URIs of the form /// `/$packageName/$assetPath`. /// - /// Returns null if the Uri cannot be parsed or is not cached. - AssetId? lookupCachedAsset(Uri uri); + /// Returns null if the `Uri` cannot be parsed or is not cached. + AssetId? lookupCachedAsset(Uri uri) { + final assetId = AnalysisDriverModelUriResolver.parseAsset(uri); + // TODO(davidmorgan): not clear if this is the right "exists" check. + if (assetId == null || !resourceProvider.getFile(assetId.asPath).exists) { + return null; + } + + return assetId; + } /// Updates [resourceProvider] and the analysis driver given by /// `withDriverResource` with updated versions of [entryPoints]. /// /// If [transitive], then all the transitive imports from [entryPoints] are /// also updated. + /// + /// Notifies [buildStep] of all inputs that result from analysis. If + /// [transitive], this includes all transitive dependencies. + /// + /// If while finding transitive deps a `.transitive_deps` file is + /// encountered next to a source file then this cuts off the reporting + /// of deps to the [buildStep], but does not affect the reporting of + /// files to the analysis driver. Future performResolve( BuildStep buildStep, List entryPoints, Future Function( FutureOr Function(AnalysisDriverForPackageBuild)) withDriverResource, - {required bool transitive}); + {required bool transitive}) async { + /// TODO(davidmorgan): add test coverage for whether transitive + /// sources are read when [transitive] is false, fix the implementation + /// here. + /// TODO(davidmorgan): add test coverage for whether + /// `.transitive_deps` files cut off the reporting of deps to the + /// [buildStep], fix the implementation here. + + // Find transitive deps, this also informs [buildStep] of all inputs). + final ids = await _expandToTransitive(buildStep, entryPoints); + + // Apply changes to in-memory filesystem. + for (final id in ids) { + if (await buildStep.canRead(id)) { + final content = await buildStep.readAsString(id); + + /// TODO(davidmorgan): add test coverage for when a file is + /// modified rather than added, fix the implementation here. + resourceProvider.newFile(id.asPath, content); + } else { + if (resourceProvider.getFile(id.asPath).exists) { + resourceProvider.deleteFile(id.asPath); + } + } + } + + // Notify the analyzer of changes. + await withDriverResource((driver) async { + for (final id in ids) { + // TODO(davidmorgan): add test coverage for over-notification of + // changes, fix the implementaion here. + driver.changeFile(id.asPath); + } + await driver.applyPendingFileChanges(); + }); + } + + /// Walks the import graph from [ids], returns full transitive deps.s + Future> _expandToTransitive( + AssetReader reader, Iterable ids) async { + final result = {}; + await __expandToTransitive(reader, ids, result); + return result; + } + + /// Walks the import graph from [ids], ignoring nodes already in [result]. + /// + /// Call with [result] empty to add full transitive deps to [result]. + Future __expandToTransitive( + AssetReader reader, Iterable ids, Set result) async { + final nextIds = Queue.of(ids); + while (nextIds.isNotEmpty) { + final nextId = nextIds.removeFirst(); + + // Skip if already seen. + if (!result.add(nextId)) continue; + + // Skip if not readable. + if (!await reader.canRead(nextId)) continue; + + final content = await reader.readAsString(nextId); + final deps = _parseDependencies(content, nextId); + nextIds.addAll(deps.where((id) => !result.contains(id))); + } + } +} + +const _ignoredSchemes = ['dart', 'dart-ext']; + +/// Parses Dart source in [content], returns all depedencies: all assets +/// mentioned in directives, excluding `dart:` and `dart-ext` schemes. +List _parseDependencies(String content, AssetId from) => + parseString(content: content, throwIfDiagnostics: false) + .unit + .directives + .whereType() + .map((directive) => directive.uri.stringValue) + // Uri.stringValue can be null for strings that use interpolation. + .nonNulls + .where( + (uriContent) => !_ignoredSchemes.any(Uri.parse(uriContent).isScheme), + ) + .map((content) => AssetId.resolve(Uri.parse(content), from: from)) + .toList(); + +extension _AssetIdExtensions on AssetId { + /// Asset path for the in-memory filesystem. + String get asPath => AnalysisDriverModelUriResolver.assetPath(this); } diff --git a/build_resolvers/lib/src/analysis_driver_model_uri_resolver.dart b/build_resolvers/lib/src/analysis_driver_model_uri_resolver.dart index a477e4a0a..a13653568 100644 --- a/build_resolvers/lib/src/analysis_driver_model_uri_resolver.dart +++ b/build_resolvers/lib/src/analysis_driver_model_uri_resolver.dart @@ -67,6 +67,7 @@ class AnalysisDriverModelUriResolver implements UriResolver { return null; } + /// Path of [assetId] for the in-memory filesystem. static String assetPath(AssetId assetId) => p.posix.join('/${assetId.package}', assetId.path); } diff --git a/build_resolvers/lib/src/resolver.dart b/build_resolvers/lib/src/resolver.dart index eb5acc176..1bc6b5d97 100644 --- a/build_resolvers/lib/src/resolver.dart +++ b/build_resolvers/lib/src/resolver.dart @@ -412,16 +412,15 @@ class AnalyzerResolvers implements Resolvers { AnalysisOptions? analysisOptions, Future Function()? sdkSummaryGenerator, PackageConfig? packageConfig, - }) { - final buildAssetUriResolver = BuildAssetUriResolver(); - return AnalyzerResolvers._( - analysisOptions: analysisOptions, - sdkSummaryGenerator: sdkSummaryGenerator, - packageConfig: packageConfig, - // Custom resolvers get their own asset uri resolver, as there should - // always be a 1:1 relationship between them. - analysisDriverModel: buildAssetUriResolver); - } + AnalysisDriverModel? analysisDriverModel, + }) => + AnalyzerResolvers._( + analysisOptions: analysisOptions, + sdkSummaryGenerator: sdkSummaryGenerator, + packageConfig: packageConfig, + // Custom resolvers get their own asset uri resolver by default as + // there should always be a 1:1 relationship between them. + analysisDriverModel: analysisDriverModel ?? BuildAssetUriResolver()); /// See [AnalyzerResolvers.custom] for docs. @Deprecated('Use either the AnalyzerResolvers.custom constructor or the ' diff --git a/build_resolvers/test/resolver_test.dart b/build_resolvers/test/resolver_test.dart index 1f4874b2a..e4310f620 100644 --- a/build_resolvers/test/resolver_test.dart +++ b/build_resolvers/test/resolver_test.dart @@ -13,6 +13,8 @@ import 'package:analyzer/dart/element/type.dart'; import 'package:build/build.dart'; import 'package:build/experiments.dart'; import 'package:build_resolvers/src/analysis_driver.dart'; +import 'package:build_resolvers/src/analysis_driver_model.dart'; +import 'package:build_resolvers/src/build_asset_uri_resolver.dart'; import 'package:build_resolvers/src/resolver.dart'; import 'package:build_resolvers/src/sdk_summary.dart'; import 'package:build_test/build_test.dart'; @@ -22,235 +24,246 @@ import 'package:pub_semver/pub_semver.dart'; import 'package:test/test.dart'; void main() { + for (final resolversFactory in [ + BuildAssetUriResolversFactory(), + AnalysisDriverModelFactory() + ]) { + group('$resolversFactory', () { + runTests(resolversFactory); + }); + } +} + +void runTests(ResolversFactory resolversFactory) { final entryPoint = AssetId('a', 'web/main.dart'); + Resolvers createResolvers({PackageConfig? packageConfig}) => + resolversFactory.create(packageConfig: packageConfig); - group('Resolver', () { - test('should handle initial files', () { - return resolveSources({ - 'a|web/main.dart': ' main() {}', - }, (resolver) async { - var lib = await resolver.libraryFor(entryPoint); - expect(lib, isNotNull); - }, resolvers: AnalyzerResolvers()); - }); + test('should handle initial files', () { + return resolveSources({ + 'a|web/main.dart': ' main() {}', + }, (resolver) async { + var lib = await resolver.libraryFor(entryPoint); + expect(lib, isNotNull); + }, resolvers: createResolvers()); + }); - test('should follow imports', () { - return resolveSources({ - 'a|web/main.dart': ''' + test('should follow imports', () { + return resolveSources({ + 'a|web/main.dart': ''' import 'a.dart'; main() { } ''', - 'a|web/a.dart': ''' + 'a|web/a.dart': ''' library a; ''', - }, (resolver) async { - var lib = await resolver.libraryFor(entryPoint); - expect(lib.definingCompilationUnit.libraryImports.length, 2); - var libA = lib - ..definingCompilationUnit - .libraryImports - .where((l) => l.importedLibrary!.name == 'a') - .single; - expect(libA.getClass('Foo'), isNull); - }, resolvers: AnalyzerResolvers()); - }); + }, (resolver) async { + var lib = await resolver.libraryFor(entryPoint); + expect(lib.definingCompilationUnit.libraryImports.length, 2); + var libA = lib + ..definingCompilationUnit + .libraryImports + .where((l) => l.importedLibrary!.name == 'a') + .single; + expect(libA.getClass('Foo'), isNull); + }, resolvers: createResolvers()); + }); - test('should follow package imports', () { - return resolveSources({ - 'a|web/main.dart': ''' + test('should follow package imports', () { + return resolveSources({ + 'a|web/main.dart': ''' import 'package:b/b.dart'; main() { } ''', - 'b|lib/b.dart': ''' + 'b|lib/b.dart': ''' library b; ''', - }, (resolver) async { - var lib = await resolver.libraryFor(entryPoint); - expect(lib.definingCompilationUnit.libraryImports.length, 2); - var libB = lib - ..definingCompilationUnit - .libraryImports - .where((l) => l.importedLibrary!.name == 'b') - .single; - expect(libB.getClass('Foo'), isNull); - }, resolvers: AnalyzerResolvers()); - }); + }, (resolver) async { + var lib = await resolver.libraryFor(entryPoint); + expect(lib.definingCompilationUnit.libraryImports.length, 2); + var libB = lib + ..definingCompilationUnit + .libraryImports + .where((l) => l.importedLibrary!.name == 'b') + .single; + expect(libB.getClass('Foo'), isNull); + }, resolvers: createResolvers()); + }); - test('should still crawl transitively after a call to isLibrary', () { - return resolveSources({ - 'a|web/main.dart': ''' + test('should still crawl transitively after a call to isLibrary', () { + return resolveSources({ + 'a|web/main.dart': ''' import 'package:b/b.dart'; main() { } ''', - 'b|lib/b.dart': ''' + 'b|lib/b.dart': ''' library b; ''', - }, (resolver) async { - await resolver.isLibrary(entryPoint); - var libs = await resolver.libraries.toList(); - expect(libs, contains(predicate((LibraryElement l) => l.name == 'b'))); - }, resolvers: AnalyzerResolvers()); - }); + }, (resolver) async { + await resolver.isLibrary(entryPoint); + var libs = await resolver.libraries.toList(); + expect(libs, contains(predicate((LibraryElement l) => l.name == 'b'))); + }, resolvers: createResolvers()); + }); - test( - 'calling isLibrary does not include that library in the libraries ' - 'stream', () { - return resolveSources({ - 'a|web/main.dart': '', - 'b|lib/b.dart': ''' + test( + 'calling isLibrary does not include that library in the libraries ' + 'stream', () { + return resolveSources({ + 'a|web/main.dart': '', + 'b|lib/b.dart': ''' library b; ''', - }, (resolver) async { - await resolver.isLibrary(AssetId('b', 'lib/b.dart')); - await expectLater( - resolver.libraries, - neverEmits(isA().having((e) => e.name, 'name', 'b')), - ); - }, resolvers: AnalyzerResolvers()); - }); + }, (resolver) async { + await resolver.isLibrary(AssetId('b', 'lib/b.dart')); + await expectLater( + resolver.libraries, + neverEmits(isA().having((e) => e.name, 'name', 'b')), + ); + }, resolvers: createResolvers()); + }); - test('should still crawl transitively after a call to compilationUnitFor', - () { - return resolveSources({ - 'a|web/main.dart': ''' + test('should still crawl transitively after a call to compilationUnitFor', + () { + return resolveSources({ + 'a|web/main.dart': ''' import 'package:b/b.dart'; main() { } ''', - 'b|lib/b.dart': ''' + 'b|lib/b.dart': ''' library b; ''', + }, (resolver) async { + await resolver.compilationUnitFor(entryPoint); + var libs = await resolver.libraries.toList(); + expect(libs, contains(predicate((LibraryElement l) => l.name == 'b'))); + }, resolvers: createResolvers()); + }); + + group('language versioning', () { + test('gives a correct languageVersion based on comments', () async { + await resolveSources({ + 'a|web/main.dart': '// @dart=3.1\n\nmain() {}', }, (resolver) async { - await resolver.compilationUnitFor(entryPoint); - var libs = await resolver.libraries.toList(); - expect(libs, contains(predicate((LibraryElement l) => l.name == 'b'))); - }, resolvers: AnalyzerResolvers()); + var lib = await resolver.libraryFor(entryPoint); + expect(lib.languageVersion.effective.major, 3); + expect(lib.languageVersion.effective.minor, 1); + }, resolvers: createResolvers()); }); - group('language versioning', () { - test('gives a correct languageVersion based on comments', () async { - await resolveSources({ - 'a|web/main.dart': '// @dart=3.1\n\nmain() {}', - }, (resolver) async { - var lib = await resolver.libraryFor(entryPoint); - expect(lib.languageVersion.effective.major, 3); - expect(lib.languageVersion.effective.minor, 1); - }, resolvers: AnalyzerResolvers()); - }); + test('defaults to the current isolate package config', () async { + await resolveSources({ + 'a|web/main.dart': 'main() {}', + }, (resolver) async { + var buildResolversId = + AssetId('build_resolvers', 'lib/build_resolvers.dart'); + var lib = await resolver.libraryFor(buildResolversId); + var currentPackageConfig = + await loadPackageConfigUri((await Isolate.packageConfig)!); + var expectedVersion = + currentPackageConfig['build_resolvers']!.languageVersion!; + expect(lib.languageVersion.effective.major, expectedVersion.major); + expect(lib.languageVersion.effective.minor, expectedVersion.minor); + }, resolvers: createResolvers()); + }); - test('defaults to the current isolate package config', () async { - await resolveSources({ - 'a|web/main.dart': 'main() {}', - }, (resolver) async { - var buildResolversId = - AssetId('build_resolvers', 'lib/build_resolvers.dart'); - var lib = await resolver.libraryFor(buildResolversId); - var currentPackageConfig = - await loadPackageConfigUri((await Isolate.packageConfig)!); - var expectedVersion = - currentPackageConfig['build_resolvers']!.languageVersion!; - expect(lib.languageVersion.effective.major, expectedVersion.major); - expect(lib.languageVersion.effective.minor, expectedVersion.minor); - }, resolvers: AnalyzerResolvers()); - }); + test('uses the overridden package config if provided', () async { + // An arbitrary past version that could never be selected for this + // package. + var customVersion = LanguageVersion(2, 1); + var customPackageConfig = PackageConfig([ + Package('a', Uri.file('/fake/a/'), + packageUriRoot: Uri.file('/fake/a/lib/'), + languageVersion: customVersion) + ]); + await resolveSources({ + 'a|web/main.dart': 'main() {}', + }, (resolver) async { + var lib = await resolver.libraryFor(entryPoint); + expect(lib.languageVersion.effective.major, customVersion.major); + expect(lib.languageVersion.effective.minor, customVersion.minor); + }, resolvers: createResolvers(packageConfig: customPackageConfig)); + }); - test('uses the overridden package config if provided', () async { - // An arbitrary past version that could never be selected for this - // package. - var customVersion = LanguageVersion(2, 1); - var customPackageConfig = PackageConfig([ - Package('a', Uri.file('/fake/a/'), - packageUriRoot: Uri.file('/fake/a/lib/'), - languageVersion: customVersion) - ]); - await resolveSources({ - 'a|web/main.dart': 'main() {}', - }, (resolver) async { - var lib = await resolver.libraryFor(entryPoint); - expect(lib.languageVersion.effective.major, customVersion.major); - expect(lib.languageVersion.effective.minor, customVersion.minor); - }, resolvers: AnalyzerResolvers(null, null, customPackageConfig)); - }); + test('gives the current language version if not provided', () async { + var customPackageConfig = PackageConfig([ + Package('a', Uri.file('/fake/a/'), + packageUriRoot: Uri.file('/fake/a/lib/'), languageVersion: null), + ]); + await resolveSources({ + 'a|web/main.dart': 'main() {}', + }, (resolver) async { + var lib = await resolver.libraryFor(entryPoint); + expect(lib.languageVersion.effective.major, sdkLanguageVersion.major); + expect(lib.languageVersion.effective.minor, sdkLanguageVersion.minor); + }, resolvers: createResolvers(packageConfig: customPackageConfig)); + }); - test('gives the current language version if not provided', () async { - var customPackageConfig = PackageConfig([ - Package('a', Uri.file('/fake/a/'), - packageUriRoot: Uri.file('/fake/a/lib/'), languageVersion: null), - ]); - await resolveSources({ - 'a|web/main.dart': 'main() {}', - }, (resolver) async { - var lib = await resolver.libraryFor(entryPoint); - expect(lib.languageVersion.effective.major, sdkLanguageVersion.major); - expect(lib.languageVersion.effective.minor, sdkLanguageVersion.minor); - }, resolvers: AnalyzerResolvers(null, null, customPackageConfig)); + test('allows a version of analyzer compatibile with the current sdk', + skip: _skipOnPreRelease, () async { + var originalLevel = Logger.root.level; + Logger.root.level = Level.WARNING; + var listener = Logger.root.onRecord.listen((record) { + fail('Got an unexpected warning during analysis:\n\n$record'); }); - - test('allows a version of analyzer compatibile with the current sdk', - skip: _skipOnPreRelease, () async { - var originalLevel = Logger.root.level; - Logger.root.level = Level.WARNING; - var listener = Logger.root.onRecord.listen((record) { - fail('Got an unexpected warning during analysis:\n\n$record'); - }); - addTearDown(() { - Logger.root.level = originalLevel; - listener.cancel(); - }); - await resolveSources({ - 'a|web/main.dart': 'main() {}', - }, (resolver) async { - await resolver.libraryFor(entryPoint); - }, resolvers: AnalyzerResolvers()); + addTearDown(() { + Logger.root.level = originalLevel; + listener.cancel(); }); + await resolveSources({ + 'a|web/main.dart': 'main() {}', + }, (resolver) async { + await resolver.libraryFor(entryPoint); + }, resolvers: createResolvers()); }); + }); - group('assets that aren\'t a transitive import of input', () { - Future runWith(Future Function(Resolver) test) { - return resolveSources({ - 'a|web/main.dart': ''' + group('assets that aren\'t a transitive import of input', () { + Future runWith(Future Function(Resolver) test) { + return resolveSources({ + 'a|web/main.dart': ''' main() {} ''', - 'a|lib/other.dart': ''' + 'a|lib/other.dart': ''' library other; ''' - }, test, resolvers: AnalyzerResolvers()); - } + }, test, resolvers: createResolvers()); + } - final otherId = AssetId.parse('a|lib/other.dart'); + final otherId = AssetId.parse('a|lib/other.dart'); - test('can be resolved', () { - return runWith((resolver) async { - final main = await resolver.libraryFor(entryPoint); - expect(main, isNotNull); + test('can be resolved', () { + return runWith((resolver) async { + final main = await resolver.libraryFor(entryPoint); + expect(main, isNotNull); - final other = await resolver.libraryFor(otherId); - expect(other.name, 'other'); - }); + final other = await resolver.libraryFor(otherId); + expect(other.name, 'other'); }); + }); - test('are included in library stream', () { - return runWith((resolver) async { - await expectLater( - resolver.libraries.map((l) => l.name), neverEmits('other')); + test('are included in library stream', () { + return runWith((resolver) async { + await expectLater( + resolver.libraries.map((l) => l.name), neverEmits('other')); - await resolver.libraryFor(otherId); + await resolver.libraryFor(otherId); - await expectLater( - resolver.libraries.map((l) => l.name), emitsThrough('other')); - }); + await expectLater( + resolver.libraries.map((l) => l.name), emitsThrough('other')); }); + }); - test('can be found by name', () { - return runWith((resolver) async { - await resolver.libraryFor(otherId); + test('can be found by name', () { + return runWith((resolver) async { + await resolver.libraryFor(otherId); - await expectLater( - resolver.findLibraryByName('other'), completion(isNotNull)); - }); + await expectLater( + resolver.findLibraryByName('other'), completion(isNotNull)); }); }); @@ -270,11 +283,11 @@ void main() { lib.definingCompilationUnit.parts .whereType(), isEmpty); - }, resolvers: AnalyzerResolvers()); + }, resolvers: createResolvers()); }); test('handles discovering previously missing parts', () async { - var resolvers = AnalyzerResolvers(); + var resolvers = createResolvers(); await resolveSources({ 'a|web/main.dart': ''' part 'main.g.dart'; @@ -308,7 +321,7 @@ void main() { }); test('handles removing deleted parts', () async { - var resolvers = AnalyzerResolvers(); + var resolvers = createResolvers(); await resolveSources({ 'a|web/main.dart': ''' part 'main.g.dart'; @@ -375,7 +388,7 @@ void main() { 'a.c', 'a.d', ])); - }, resolvers: AnalyzerResolvers()); + }, resolvers: createResolvers()); }); test('should resolve types and library uris', () { @@ -407,7 +420,7 @@ void main() { var main = await resolver.findLibraryByName(''); expect(main, isNotNull); - }, resolvers: AnalyzerResolvers()); + }, resolvers: createResolvers()); }); test('resolves constants transitively', () { @@ -431,7 +444,7 @@ void main() { var meta = main.getClass('Foo')!.supertype!.element.metadata[0]; expect(meta, isNotNull); expect(meta.computeConstantValue()?.toIntValue(), 0); - }, resolvers: AnalyzerResolvers()); + }, resolvers: createResolvers()); }); test('handles circular imports', () { @@ -449,7 +462,7 @@ void main() { var libs = await resolver.libraries.map((lib) => lib.name).toList(); expect(libs.contains('a'), isTrue); expect(libs.contains('b'), isTrue); - }, resolvers: AnalyzerResolvers()); + }, resolvers: createResolvers()); }); test('assetIdForElement', () { @@ -470,7 +483,7 @@ void main() { .singleWhere((c) => c != null)!; expect(await resolver.assetIdForElement(classDefinition), AssetId('a', 'lib/b.dart')); - }, resolvers: AnalyzerResolvers()); + }, resolvers: createResolvers()); }); test('assetIdForElement throws for ambiguous elements', () { @@ -497,7 +510,7 @@ void main() { .element!; await expectLater(() => resolver.assetIdForElement(element), throwsA(isA())); - }, resolvers: AnalyzerResolvers()); + }, resolvers: createResolvers()); }); test('Respects withEnabledExperiments', skip: _skipOnPreRelease, () async { @@ -518,13 +531,13 @@ int? get x => 1; var errors = await lib.session.getErrors('/a/web/main.dart') as ErrorsResult; expect(errors.errors, isEmpty); - }, resolvers: AnalyzerResolvers()), + }, resolvers: createResolvers()), ['non-nullable']); }); test('can get a new analysis session after resolving additional assets', () async { - var resolvers = AnalyzerResolvers(); + var resolvers = createResolvers(); await resolveSources({ 'a|web/main.dart': '', 'a|web/other.dart': '', @@ -589,7 +602,7 @@ int? get x => 1; .having((e) => e.syntaxErrors, 'syntaxErrors', hasLength(1)), ), ); - }); + }, resolvers: createResolvers()); }); test('are not reported when disabled', () { @@ -612,7 +625,7 @@ int? get x => 1; allowSyntaxErrors: true), completion(isNotNull), ); - }); + }, resolvers: createResolvers()); }); test('are truncated if necessary', () { @@ -644,7 +657,7 @@ int? get x => 1; resolver.compilationUnitFor(AssetId.parse('a|errors.dart')), expectation, ); - }); + }, resolvers: createResolvers()); }); test('do not report semantic errors', () { @@ -666,30 +679,27 @@ int? get x => 1; resolver.compilationUnitFor(AssetId.parse('a|errors.dart')), completion(isNotNull), ); - }); + }, resolvers: createResolvers()); }); }); }); test('throws when reading a part-of file', () { - return resolveSources( - { - 'a|lib/a.dart': ''' + return resolveSources({ + 'a|lib/a.dart': ''' part 'b.dart'; ''', - 'a|lib/b.dart': ''' + 'a|lib/b.dart': ''' part of 'a.dart'; ''' - }, - (resolver) async { - final assetId = AssetId.parse('a|lib/b.dart'); - await expectLater( - () => resolver.libraryFor(assetId), - throwsA(const TypeMatcher() - .having((e) => e.assetId, 'assetId', equals(assetId))), - ); - }, - ); + }, (resolver) async { + final assetId = AssetId.parse('a|lib/b.dart'); + await expectLater( + () => resolver.libraryFor(assetId), + throwsA(const TypeMatcher() + .having((e) => e.assetId, 'assetId', equals(assetId))), + ); + }, resolvers: createResolvers()); }); test('Can resolve sdk libraries that are not imported', () async { @@ -720,7 +730,7 @@ int? get x => 1; everyElement(isA() .having((e) => e.isPrivate, 'isPrivate', isFalse)), ); - }, resolvers: AnalyzerResolvers()); + }, resolvers: createResolvers()); }); test('can resolve sdk libraries without seeing anything else', () async { @@ -735,11 +745,11 @@ int? get x => 1; allLibraries, everyElement(isA() .having((e) => e.isInSdk, 'isInSdk', isTrue))); - }, resolvers: AnalyzerResolvers()); + }, resolvers: createResolvers()); }); test('sdk libraries can still be resolved after seeing new assets', () async { - final resolvers = AnalyzerResolvers(); + final resolvers = createResolvers(); final builder = TestBuilder( buildExtensions: { '.dart': ['.txt'] @@ -791,7 +801,7 @@ int? get x => 1; } else { expect(color.type, isA()); } - }, resolvers: AnalyzerResolvers()); + }, resolvers: createResolvers()); }); }); @@ -819,7 +829,7 @@ int? get x => 1; '${buildStep.inputId} should not be considered a library'); } }); - var resolvers = AnalyzerResolvers(); + var resolvers = createResolvers(); await runBuilder(builder, [input], reader, writer, resolvers); await runBuilder( @@ -842,7 +852,7 @@ int? get x => 1; buildStep.inputId.changeExtension('doesnotexist.dart')), false); })); - var resolvers = AnalyzerResolvers(); + var resolvers = createResolvers(); await runBuilder(builder, [input], reader, writer, resolvers); }); @@ -865,7 +875,7 @@ int? get x => 1; expect(await buildStep.canRead(other), true); expect(await buildStep.resolver.isLibrary(other), false); })); - var resolvers = AnalyzerResolvers(); + var resolvers = createResolvers(); await runBuilder(builder, [input], reader, writer, resolvers); }); @@ -881,7 +891,7 @@ int? get x => 1; unit.declarations.first, isA() .having((d) => d.name.lexeme, 'main', 'main')); - }, resolvers: AnalyzerResolvers()); + }, resolvers: createResolvers()); }); }); @@ -895,7 +905,7 @@ int? get x => 1; expect(unit, isA()); expect(unit!.toSource(), 'main() {}'); expect((unit as FunctionDeclaration).declaredElement, isNull); - }, resolvers: AnalyzerResolvers()); + }, resolvers: createResolvers()); }); test('can return an resolved ast', () { @@ -911,7 +921,7 @@ int? get x => 1; .having((fd) => fd.toSource(), 'toSource()', 'main() {}') .having((fd) => fd.declaredElement, 'declaredElement', isNotNull), ); - }, resolvers: AnalyzerResolvers()); + }, resolvers: createResolvers()); }); test('can return a resolved compilation unit', () { @@ -941,3 +951,28 @@ final _skipOnPreRelease = Version.parse(Platform.version.split(' ').first).isPreRelease ? 'Skipped on prerelease sdks' : null; + +abstract class ResolversFactory { + Resolvers create({PackageConfig? packageConfig}); +} + +class BuildAssetUriResolversFactory implements ResolversFactory { + @override + Resolvers create({PackageConfig? packageConfig}) => AnalyzerResolvers.custom( + packageConfig: packageConfig, + analysisDriverModel: BuildAssetUriResolver.sharedInstance); + + @override + String toString() => 'Resolver'; +} + +class AnalysisDriverModelFactory implements ResolversFactory { + final AnalysisDriverModel sharedInstance = AnalysisDriverModel(); + + @override + Resolvers create({PackageConfig? packageConfig}) => AnalyzerResolvers.custom( + packageConfig: packageConfig, analysisDriverModel: sharedInstance); + + @override + String toString() => 'New resolver'; +}