From 1865a35c736485bfaeb087e375d56322b77f6674 Mon Sep 17 00:00:00 2001 From: Konstantin Scheglov Date: Sat, 14 Dec 2024 13:48:44 -0800 Subject: [PATCH] Add APIs for using analyzer element model 2. (#3775) * Add APIs for using analyzer element model 2. * Update CHANGELOG.md --- build/CHANGELOG.md | 2 +- build/lib/src/analyzer/resolver.dart | 56 +++++++ build/lib/src/builder/build_step.dart | 16 ++ build/lib/src/builder/build_step_impl.dart | 32 ++++ build/pubspec.yaml | 2 +- build_resolvers/CHANGELOG.md | 1 + build_resolvers/lib/src/resolver.dart | 173 +++++++++++++++++---- build_resolvers/pubspec.yaml | 2 +- 8 files changed, 250 insertions(+), 34 deletions(-) diff --git a/build/CHANGELOG.md b/build/CHANGELOG.md index bd4b9ae70..aa21ff8f8 100644 --- a/build/CHANGELOG.md +++ b/build/CHANGELOG.md @@ -3,7 +3,7 @@ - Bump the min sdk to 3.6.0-228.0.dev. - Remove some unnecessary casts and non-null assertions now that we have private field promotion. -- Require analyzer ^6.9.0. +- Require analyzer ^7.0.0. - Fix analyzer deprecations. ## 2.4.1 diff --git a/build/lib/src/analyzer/resolver.dart b/build/lib/src/analyzer/resolver.dart index ed549baab..ecef720cc 100644 --- a/build/lib/src/analyzer/resolver.dart +++ b/build/lib/src/analyzer/resolver.dart @@ -5,6 +5,7 @@ import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/dart/analysis/session.dart'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/element2.dart'; import 'package:analyzer/error/error.dart'; import '../asset/id.dart'; @@ -29,6 +30,17 @@ abstract class Resolver { /// instance because due to imports or exports. Stream get libraries; + /// All libraries resolved by this resolver. + /// + /// This includes the following libraries: + /// - The primary input of this resolver (in other words, the + /// [BuildStep.inputId] of a build step). + /// - Libraries resolved with a direct [libraryFor] call. + /// - Every public `dart:` library part of the SDK. + /// - All libraries recursively accessible from the mentioned sources, for + /// instance because due to imports or exports. + Stream get libraries2; + /// Returns the parsed [AstNode] for [Element]. /// /// This should always be preferred over using the [AnalysisSession] @@ -42,6 +54,19 @@ abstract class Resolver { /// reason. Future astNodeFor(Element element, {bool resolve = false}); + /// Returns the parsed [AstNode] for [Element]. + /// + /// This should always be preferred over using the [AnalysisSession] + /// directly, because it avoids [InconsistentAnalysisException] issues. + /// + /// If [resolve] is `true` then you will get a resolved ast node, otherwise + /// it will only be a parsed ast node. + /// + /// Returns `null` if the ast node can not be found. This can happen if an + /// element is coming from a summary, or is unavailable for some other + /// reason. + Future astNodeFor2(Fragment element, {bool resolve = false}); + /// Returns a parsed AST structor representing the file defined in [assetId]. /// /// * If the [assetId] has syntax errors, and [allowSyntaxErrors] is set to @@ -60,6 +85,14 @@ abstract class Resolver { Future libraryFor(AssetId assetId, {bool allowSyntaxErrors = false}); + /// Returns a resolved library representing the file defined in [assetId]. + /// + /// * Throws [NonLibraryAssetException] if [assetId] is not a Dart library. + /// * If the [assetId] has syntax errors, and [allowSyntaxErrors] is set to + /// `false` (the default), throws a [SyntaxErrorInAssetException]. + Future libraryFor2(AssetId assetId, + {bool allowSyntaxErrors = false}); + /// Returns the first resolved library identified by [libraryName]. /// /// A library is resolved if it's recursively accessible from the entry point @@ -72,6 +105,19 @@ abstract class Resolver { /// being unique. Future findLibraryByName(String libraryName); + /// Returns the first resolved library identified by [libraryName]. + /// + /// A library is resolved if it's recursively accessible from the entry point + /// or subsequent calls to [libraryFor]. In other words, this searches for + /// libraries in [libraries]. + /// If no library can be found, returns `null`. + /// + /// **NOTE**: In general, its recommended to use [libraryFor] with an absolute + /// asset id instead of a named identifier that has the possibility of not + /// being unique. + Future findLibraryByName2( + String libraryName); + /// Returns the [AssetId] of the Dart library or part declaring [element]. /// /// If [element] is defined in the SDK or in a summary throws @@ -81,6 +127,16 @@ abstract class Resolver { /// The returned asset is not necessarily the asset that should be imported to /// use the element, it may be a part file instead of the library. Future assetIdForElement(Element element); + + /// Returns the [AssetId] of the Dart library or part declaring [element]. + /// + /// If [element] is defined in the SDK or in a summary throws + /// `UnresolvableAssetException`, although a non-throwing return here does not + /// guarantee that the asset is readable. + /// + /// The returned asset is not necessarily the asset that should be imported to + /// use the element, it may be a part file instead of the library. + Future assetIdForElement2(Element2 element); } /// A resolver that should be manually released at the end of a build step. diff --git a/build/lib/src/builder/build_step.dart b/build/lib/src/builder/build_step.dart index 3a003e1ef..5e35a132c 100644 --- a/build/lib/src/builder/build_step.dart +++ b/build/lib/src/builder/build_step.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/element2.dart'; import 'package:meta/meta.dart'; import 'package:package_config/package_config_types.dart'; @@ -39,6 +40,21 @@ abstract class BuildStep implements AssetReader, AssetWriter { /// ``` Future get inputLibrary; + /// Resolved library defined by [inputId]. + /// + /// Throws [NonLibraryAssetException] if [inputId] is not a Dart library file. + /// Throws [SyntaxErrorInAssetException] if [inputId] contains syntax errors. + /// If you want to support libraries with syntax errors, resolve the library + /// manually instead of using [inputLibrary]: + /// ```dart + /// Future build(BuildStep step) async { + /// // Resolve the input library, allowing syntax errors + /// final inputLibrary = + /// await step.resolver.libraryFor(step.inputId, allowSyntaxErrors: true); + /// } + /// ``` + Future get inputLibrary2; + /// Gets an instance provided by [resource] which is guaranteed to be unique /// within a single build, and may be reused across build steps within a /// build if the implementation allows. diff --git a/build/lib/src/builder/build_step_impl.dart b/build/lib/src/builder/build_step_impl.dart index ebc67a0fc..bac55fa37 100644 --- a/build/lib/src/builder/build_step_impl.dart +++ b/build/lib/src/builder/build_step_impl.dart @@ -7,6 +7,7 @@ import 'dart:convert'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/element2.dart'; import 'package:async/async.dart'; import 'package:crypto/crypto.dart'; import 'package:glob/glob.dart'; @@ -39,6 +40,12 @@ class BuildStepImpl implements BuildStep { return resolver.libraryFor(inputId); } + @override + Future get inputLibrary2 async { + if (_isComplete) throw BuildStepCompletedException(); + return resolver.libraryFor2(inputId); + } + /// The list of all outputs which are expected/allowed to be output from this /// step. @override @@ -220,10 +227,21 @@ class _DelayedResolver implements Resolver { return completer.stream; } + @override + Stream get libraries2 { + var completer = StreamCompleter(); + _delegate.then((r) => completer.setSourceStream(r.libraries2)); + return completer.stream; + } + @override Future astNodeFor(Element element, {bool resolve = false}) async => (await _delegate).astNodeFor(element, resolve: resolve); + @override + Future astNodeFor2(Fragment fragment, {bool resolve = false}) async => + (await _delegate).astNodeFor2(fragment, resolve: resolve); + @override Future compilationUnitFor(AssetId assetId, {bool allowSyntaxErrors = false}) async => @@ -236,11 +254,25 @@ class _DelayedResolver implements Resolver { (await _delegate) .libraryFor(assetId, allowSyntaxErrors: allowSyntaxErrors); + @override + Future libraryFor2(AssetId assetId, + {bool allowSyntaxErrors = false}) async => + (await _delegate) + .libraryFor2(assetId, allowSyntaxErrors: allowSyntaxErrors); + @override Future findLibraryByName(String libraryName) async => (await _delegate).findLibraryByName(libraryName); + @override + Future findLibraryByName2(String libraryName) async => + (await _delegate).findLibraryByName2(libraryName); + @override Future assetIdForElement(Element element) async => (await _delegate).assetIdForElement(element); + + @override + Future assetIdForElement2(Element2 element) async => + (await _delegate).assetIdForElement2(element); } diff --git a/build/pubspec.yaml b/build/pubspec.yaml index be3e1535a..2bb49350d 100644 --- a/build/pubspec.yaml +++ b/build/pubspec.yaml @@ -11,7 +11,7 @@ environment: sdk: ^3.6.0-228.0.dev dependencies: - analyzer: ^6.9.0 + analyzer: ^7.0.0 async: ^2.5.0 convert: ^3.0.0 crypto: ^3.0.0 diff --git a/build_resolvers/CHANGELOG.md b/build_resolvers/CHANGELOG.md index 38f15d4ef..6adb1672f 100644 --- a/build_resolvers/CHANGELOG.md +++ b/build_resolvers/CHANGELOG.md @@ -6,6 +6,7 @@ - Fix SDK summary reads when multiple isolates are using build resolvers (not recommended). - Fix analyzer deprecations. +- Require analyzer ^7.0.0. ## 2.4.2 diff --git a/build_resolvers/lib/src/resolver.dart b/build_resolvers/lib/src/resolver.dart index cd299cb6e..0274ba2e2 100644 --- a/build_resolvers/lib/src/resolver.dart +++ b/build_resolvers/lib/src/resolver.dart @@ -12,9 +12,12 @@ import 'package:analyzer/dart/analysis/features.dart'; import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/element2.dart'; import 'package:analyzer/error/error.dart'; // ignore: implementation_imports import 'package:analyzer/src/clients/build_resolvers/build_resolvers.dart'; +// ignore: implementation_imports +import 'package:analyzer/src/utilities/extensions/element.dart'; import 'package:async/async.dart'; import 'package:build/build.dart'; import 'package:build/experiments.dart'; @@ -42,11 +45,15 @@ class PerActionResolver implements ReleasableResolver { PerActionResolver( this._delegate, this._driverPool, this._readAndWritePool, this._step); - Stream get _librariesFromEntrypoints async* { + Stream get _librariesFromEntrypoints { + return _librariesFromEntrypoints2.map((e) => e.asElement); + } + + Stream get _librariesFromEntrypoints2 async* { await _resolveIfNecessary(_step.inputId, transitive: true); - final seen = {}; - final toVisit = Queue(); + final seen = {}; + final toVisit = Queue(); // keep a copy of entry points in case [_resolveIfNecessary] is called // before this stream is done. @@ -54,7 +61,7 @@ class PerActionResolver implements ReleasableResolver { for (final entryPoint in entryPoints) { if (!await _delegate.isLibrary(entryPoint)) continue; final library = - await _delegate.libraryFor(entryPoint, allowSyntaxErrors: true); + await _delegate.libraryFor2(entryPoint, allowSyntaxErrors: true); toVisit.add(library); seen.add(library); } @@ -64,10 +71,10 @@ class PerActionResolver implements ReleasableResolver { // `BuildStep.canRead`. They'd still be reachable by crawling the element // model manually. yield current; - final toCrawl = current.definingCompilationUnit.libraryImports - .map((import) => import.importedLibrary) - .followedBy(current.definingCompilationUnit.libraryExports - .map((export) => export.exportedLibrary)) + final toCrawl = current.firstFragment.libraryImports2 + .map((import) => import.importedLibrary2) + .followedBy(current.firstFragment.libraryExports2 + .map((export) => export.exportedLibrary2)) .nonNulls .where((library) => !seen.contains(library)) .toSet(); @@ -83,13 +90,29 @@ class PerActionResolver implements ReleasableResolver { } @override - Future findLibraryByName(String libraryName) => - _step.trackStage('findLibraryByName $libraryName', () async { - await for (final library in libraries) { - if (library.name == libraryName) return library; - } - return null; - }); + Stream get libraries2 async* { + yield* _delegate.sdkLibraries2; + yield* _librariesFromEntrypoints2.where((library) => !library.isInSdk); + } + + @override + Future findLibraryByName(String libraryName) async { + final element = await findLibraryByName2(libraryName); + if (element == null) { + return null; + } + return element.asElement; + } + + @override + Future findLibraryByName2(String libraryName) async { + return _step.trackStage('findLibraryByName $libraryName', () async { + await for (final library in libraries2) { + if (library.name3 == libraryName) return library; + } + return null; + }); + } @override Future isLibrary(AssetId assetId) => @@ -104,6 +127,11 @@ class PerActionResolver implements ReleasableResolver { _step.trackStage('astNodeFor $element', () => _delegate.astNodeFor(element, resolve: resolve)); + @override + Future astNodeFor2(Fragment fragment, {bool resolve = false}) => + _step.trackStage('astNodeFor $fragment', + () => _delegate.astNodeFor2(fragment, resolve: resolve)); + @override Future compilationUnitFor(AssetId assetId, {bool allowSyntaxErrors = false}) => @@ -118,15 +146,26 @@ class PerActionResolver implements ReleasableResolver { @override Future libraryFor(AssetId assetId, - {bool allowSyntaxErrors = false}) => - _step.trackStage('libraryFor $assetId', () async { - if (!await _step.canRead(assetId)) { - throw AssetNotFoundException(assetId); - } - await _resolveIfNecessary(assetId, transitive: true); - return _delegate.libraryFor(assetId, - allowSyntaxErrors: allowSyntaxErrors); - }); + {bool allowSyntaxErrors = false}) async { + final element = await libraryFor2( + assetId, + allowSyntaxErrors: allowSyntaxErrors, + ); + return element.asElement; + } + + @override + Future libraryFor2(AssetId assetId, + {bool allowSyntaxErrors = false}) async { + return _step.trackStage('libraryFor $assetId', () async { + if (!await _step.canRead(assetId)) { + throw AssetNotFoundException(assetId); + } + await _resolveIfNecessary(assetId, transitive: true); + return _delegate.libraryFor2(assetId, + allowSyntaxErrors: allowSyntaxErrors); + }); + } // Ensures we only resolve one entrypoint at a time from the same build step, // otherwise there are race conditions with `_entryPoints` being updated @@ -169,6 +208,10 @@ class PerActionResolver implements ReleasableResolver { @override Future assetIdForElement(Element element) => _delegate.assetIdForElement(element); + + @override + Future assetIdForElement2(Element2 element) => + _delegate.assetIdForElement2(element); } class AnalyzerResolver implements ReleasableResolver { @@ -177,7 +220,7 @@ class AnalyzerResolver implements ReleasableResolver { final Pool _driverPool; final SharedResourcePool _readAndWritePool; - Future>? _sdkLibraries; + Future>? _sdkLibraries; AnalyzerResolver(this._driver, this._driverPool, this._readAndWritePool, this._uriResolver); @@ -225,6 +268,39 @@ class AnalyzerResolver implements ReleasableResolver { }); } + @override + Future astNodeFor2(Fragment fragment, + {bool resolve = false}) async { + final library = fragment.libraryFragment?.element; + if (library == null) { + // Invalid elements (e.g. an MultiplyDefinedElement) are not part of any + // library and can't be resolved like this. + return null; + } + var path = library.firstFragment.source.fullName; + + return _driverPool.withResource(() async { + var session = _driver.currentSession; + if (resolve) { + final result = + await session.getResolvedLibrary(path) as ResolvedLibraryResult; + if (fragment is LibraryFragment) { + return result.unitWithPath(fragment.source.fullName)?.unit; + } + return result.getElementDeclaration2(fragment)?.node; + } else { + final result = session.getParsedLibrary(path) as ParsedLibraryResult; + if (fragment is LibraryFragment) { + final unitPath = fragment.source.fullName; + return result.units + .firstWhereOrNull((unit) => unit.path == unitPath) + ?.unit; + } + return result.getElementDeclaration2(fragment)?.node; + } + }); + } + @override Future compilationUnitFor(AssetId assetId, {bool allowSyntaxErrors = false}) { @@ -247,6 +323,14 @@ class AnalyzerResolver implements ReleasableResolver { @override Future libraryFor(AssetId assetId, {bool allowSyntaxErrors = false}) async { + var element = + await libraryFor2(assetId, allowSyntaxErrors: allowSyntaxErrors); + return element.asElement; + } + + @override + Future libraryFor2(AssetId assetId, + {bool allowSyntaxErrors = false}) async { // Since this calls `getLibraryByUri` it is a "read", and can use the shared // resource to allow concurrent reads. final library = await _readAndWritePool @@ -273,7 +357,7 @@ class AnalyzerResolver implements ReleasableResolver { } } - return library.element; + return library.element2; } /// Finds syntax errors in files related to the [element]. @@ -323,7 +407,18 @@ class AnalyzerResolver implements ReleasableResolver { throw UnimplementedError(); } + @override + Stream get libraries2 { + // We don't know what libraries to expose without leaking libraries written + // by later phases. + throw UnimplementedError(); + } + Stream get sdkLibraries { + return sdkLibraries2.map((e) => e.asElement); + } + + Stream get sdkLibraries2 { final loadLibraries = _sdkLibraries ??= Future.sync(() { final publicSdkUris = _driver.sdkLibraryUris.where((e) => !e.path.startsWith('_')); @@ -332,7 +427,7 @@ class AnalyzerResolver implements ReleasableResolver { return _driverPool.withResource(() async { final result = await _driver.currentSession .getLibraryByUri(uri.toString()) as LibraryElementResult; - return result.element; + return result.element2; }); })); }); @@ -348,16 +443,32 @@ class AnalyzerResolver implements ReleasableResolver { } @override - Future assetIdForElement(Element element) async { - final source = element.source; + Future findLibraryByName2(String libraryName) async { + // We don't know what libraries to expose without leaking libraries written + // by later phases. + throw UnimplementedError(); + } + + @override + Future assetIdForElement(Element element) { + return assetIdForElement2(element.asElement2!); + } + + @override + Future assetIdForElement2(Element2 element) async { + if (element is MultiplyDefinedElement2) { + throw UnresolvableAssetException('${element.name3} is ambiguous'); + } + + final source = element.firstFragment.libraryFragment?.source; if (source == null) { throw UnresolvableAssetException( - '${element.name} does not have a source'); + '${element.name3} does not have a source'); } final uri = source.uri; if (!uri.isScheme('package') && !uri.isScheme('asset')) { - throw UnresolvableAssetException('${element.name} in ${source.uri}'); + throw UnresolvableAssetException('${element.name3} in ${source.uri}'); } return AssetId.resolve(source.uri); } diff --git a/build_resolvers/pubspec.yaml b/build_resolvers/pubspec.yaml index 2e42bd1ce..a54e3e684 100644 --- a/build_resolvers/pubspec.yaml +++ b/build_resolvers/pubspec.yaml @@ -11,7 +11,7 @@ environment: sdk: ^3.6.0-228.0.dev dependencies: - analyzer: ^6.9.0 + analyzer: ^7.0.0 async: ^2.5.0 build: ^2.0.0 collection: ^1.17.0