Skip to content

Commit 3bc0a58

Browse files
committed
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.
1 parent 72b4f5b commit 3bc0a58

File tree

4 files changed

+381
-228
lines changed

4 files changed

+381
-228
lines changed

build_resolvers/lib/src/analysis_driver_model.dart

Lines changed: 125 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'dart:async';
6+
import 'dart:collection';
67

8+
import 'package:analyzer/dart/analysis/utilities.dart';
9+
import 'package:analyzer/dart/ast/ast.dart';
710
import 'package:analyzer/file_system/memory_file_system.dart';
811
// ignore: implementation_imports
912
import 'package:analyzer/src/clients/build_resolvers/build_resolvers.dart';
1013
import 'package:build/build.dart';
14+
import 'package:path/path.dart' as p;
15+
16+
import 'analysis_driver_model_uri_resolver.dart';
1117

1218
/// Manages analysis driver and related build state.
1319
///
@@ -19,36 +25,148 @@ import 'package:build/build.dart';
1925
/// - Maintains an in-memory filesystem that is the analyzer's view of the
2026
/// build.
2127
/// - Notifies the analyzer of changes to that in-memory filesystem.
22-
abstract class AnalysisDriverModel {
28+
///
29+
/// TODO(davidmorgan): the implementation here is unfinished and not used
30+
/// anywhere; finish it. See `build_asset_uri_resolver.dart` for the current
31+
/// implementation.
32+
class AnalysisDriverModel {
2333
/// In-memory filesystem for the analyzer.
24-
abstract final MemoryResourceProvider resourceProvider;
34+
final MemoryResourceProvider resourceProvider =
35+
MemoryResourceProvider(context: p.posix);
2536

2637
/// Notifies that [step] has completed.
2738
///
2839
/// All build steps must complete before [reset] is called.
29-
void notifyComplete(BuildStep step);
40+
void notifyComplete(BuildStep step) {
41+
// TODO(davidmorgan): add test coverage, fix implementation.
42+
}
3043

3144
/// Clear cached information specific to an individual build.
32-
void reset();
45+
void reset() {
46+
// TODO(davidmorgan): add test coverage, fix implementation.
47+
}
3348

3449
/// Attempts to parse [uri] into an [AssetId] and returns it if it is cached.
3550
///
3651
/// Handles 'package:' or 'asset:' URIs, as well as 'file:' URIs of the form
3752
/// `/$packageName/$assetPath`.
3853
///
39-
/// Returns null if the Uri cannot be parsed or is not cached.
40-
AssetId? lookupCachedAsset(Uri uri);
54+
/// Returns null if the `Uri` cannot be parsed or is not cached.
55+
AssetId? lookupCachedAsset(Uri uri) {
56+
final assetId = AnalysisDriverModelUriResolver.parseAsset(uri);
57+
// TODO(davidmorgan): not clear if this is the right "exists" check.
58+
if (assetId == null || !resourceProvider.getFile(assetId.asPath).exists) {
59+
return null;
60+
}
61+
62+
return assetId;
63+
}
4164

4265
/// Updates [resourceProvider] and the analysis driver given by
4366
/// `withDriverResource` with updated versions of [entryPoints].
4467
///
4568
/// If [transitive], then all the transitive imports from [entryPoints] are
4669
/// also updated.
70+
///
71+
/// Notifies [buildStep] of all inputs that result from analysis. If
72+
/// [transitive], this includes all transitive dependencies.
73+
///
74+
/// If while finding transitive deps a `.transitive_deps` file is
75+
/// encountered next to a source file then this cuts off the reporting
76+
/// of deps to the [buildStep], but does not affect the reporting of
77+
/// files to the analysis driver.
4778
Future<void> performResolve(
4879
BuildStep buildStep,
4980
List<AssetId> entryPoints,
5081
Future<void> Function(
5182
FutureOr<void> Function(AnalysisDriverForPackageBuild))
5283
withDriverResource,
53-
{required bool transitive});
84+
{required bool transitive}) async {
85+
/// TODO(davidmorgan): add test coverage for whether transitive
86+
/// sources are read when [transitive] is false, fix the implementation
87+
/// here.
88+
/// TODO(davidmorgan): add test coverage for whether
89+
/// `.transitive_deps` files cut off the reporting of deps to the
90+
/// [buildStep], fix the implementation here.
91+
92+
// Find transitive deps, this also informs [buildStep] of all inputs).
93+
final ids = await _expandToTransitive(buildStep, entryPoints);
94+
95+
// Apply changes to in-memory filesystem.
96+
for (final id in ids) {
97+
if (await buildStep.canRead(id)) {
98+
final content = await buildStep.readAsString(id);
99+
100+
/// TODO(davidmorgan): add test coverage for when a file is
101+
/// modified rather than added, fix the implementation here.
102+
resourceProvider.newFile(id.asPath, content);
103+
} else {
104+
if (resourceProvider.getFile(id.asPath).exists) {
105+
resourceProvider.deleteFile(id.asPath);
106+
}
107+
}
108+
}
109+
110+
// Notify the analyzer of changes.
111+
await withDriverResource((driver) async {
112+
for (final id in ids) {
113+
// TODO(davidmorgan): add test coverage for over-notification of
114+
// changes, fix the implementaion here.
115+
driver.changeFile(id.asPath);
116+
}
117+
await driver.applyPendingFileChanges();
118+
});
119+
}
120+
121+
/// Walks the import graph from [ids], returns full transitive deps.s
122+
Future<Set<AssetId>> _expandToTransitive(
123+
AssetReader reader, Iterable<AssetId> ids) async {
124+
final result = <AssetId>{};
125+
await __expandToTransitive(reader, ids, result);
126+
return result;
127+
}
128+
129+
/// Walks the import graph from [ids], ignoring nodes already in [result].
130+
///
131+
/// Call with [result] empty to add full transitive deps to [result].
132+
Future<void> __expandToTransitive(
133+
AssetReader reader, Iterable<AssetId> ids, Set<AssetId> result) async {
134+
final nextIds = Queue.of(ids);
135+
while (nextIds.isNotEmpty) {
136+
final nextId = nextIds.removeFirst();
137+
138+
// Skip if already seen.
139+
if (!result.add(nextId)) continue;
140+
141+
// Skip if not readable.
142+
if (!await reader.canRead(nextId)) continue;
143+
144+
final content = await reader.readAsString(nextId);
145+
final deps = _parseDependencies(content, nextId);
146+
nextIds.addAll(deps.where((id) => !result.contains(id)));
147+
}
148+
}
149+
}
150+
151+
const _ignoredSchemes = ['dart', 'dart-ext'];
152+
153+
/// Parses Dart source in [content], returns all depedencies: all assets
154+
/// mentioned in directives, excluding `dart:` and `dart-ext` schemes.
155+
List<AssetId> _parseDependencies(String content, AssetId from) =>
156+
parseString(content: content, throwIfDiagnostics: false)
157+
.unit
158+
.directives
159+
.whereType<UriBasedDirective>()
160+
.map((directive) => directive.uri.stringValue)
161+
// Uri.stringValue can be null for strings that use interpolation.
162+
.nonNulls
163+
.where(
164+
(uriContent) => !_ignoredSchemes.any(Uri.parse(uriContent).isScheme),
165+
)
166+
.map((content) => AssetId.resolve(Uri.parse(content), from: from))
167+
.toList();
168+
169+
extension _AssetIdExtensions on AssetId {
170+
/// Asset path for the in-memory filesystem.
171+
String get asPath => AnalysisDriverModelUriResolver.assetPath(this);
54172
}

build_resolvers/lib/src/analysis_driver_model_uri_resolver.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class AnalysisDriverModelUriResolver implements UriResolver {
6767
return null;
6868
}
6969

70+
/// Path of [assetId] for the in-memory filesystem.
7071
static String assetPath(AssetId assetId) =>
7172
p.posix.join('/${assetId.package}', assetId.path);
7273
}

build_resolvers/lib/src/resolver.dart

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -412,16 +412,15 @@ class AnalyzerResolvers implements Resolvers {
412412
AnalysisOptions? analysisOptions,
413413
Future<String> Function()? sdkSummaryGenerator,
414414
PackageConfig? packageConfig,
415-
}) {
416-
final buildAssetUriResolver = BuildAssetUriResolver();
417-
return AnalyzerResolvers._(
418-
analysisOptions: analysisOptions,
419-
sdkSummaryGenerator: sdkSummaryGenerator,
420-
packageConfig: packageConfig,
421-
// Custom resolvers get their own asset uri resolver, as there should
422-
// always be a 1:1 relationship between them.
423-
analysisDriverModel: buildAssetUriResolver);
424-
}
415+
AnalysisDriverModel? analysisDriverModel,
416+
}) =>
417+
AnalyzerResolvers._(
418+
analysisOptions: analysisOptions,
419+
sdkSummaryGenerator: sdkSummaryGenerator,
420+
packageConfig: packageConfig,
421+
// Custom resolvers get their own asset uri resolver by default as
422+
// there should always be a 1:1 relationship between them.
423+
analysisDriverModel: analysisDriverModel ?? BuildAssetUriResolver());
425424

426425
/// See [AnalyzerResolvers.custom] for docs.
427426
@Deprecated('Use either the AnalyzerResolvers.custom constructor or the '

0 commit comments

Comments
 (0)