From 117ab269024731235508b3489bfd7cb69d756de0 Mon Sep 17 00:00:00 2001 From: Konstantin Scheglov Date: Mon, 16 Dec 2024 14:56:21 -0800 Subject: [PATCH] Add APIs to expose analyzer elements2 models. (#735) * publish_to: none * Directives are not elements anymore, don't have annotations. --- analysis_options.yaml | 1 + .../src/member_count_library_generator.dart | 2 +- example/lib/src/multiplier_generator.dart | 8 +- .../lib/src/property_product_generator.dart | 3 +- example/lib/src/property_sum_generator.dart | 3 +- example/lib/src/utils.dart | 8 +- source_gen/lib/source_gen.dart | 2 +- source_gen/lib/src/builder.dart | 14 +- source_gen/lib/src/generator.dart | 18 +- .../lib/src/generator_for_annotation.dart | 33 ++- source_gen/lib/src/library.dart | 89 +++++--- source_gen/lib/src/span_for_element.dart | 57 ++++- source_gen/lib/src/type_checker.dart | 195 ++++++++++++++++-- source_gen/pubspec.yaml | 29 +++ .../test/external_only_type_checker_test.dart | 4 +- .../test/generator_for_annotation_test.dart | 41 ---- source_gen/test/library/find_type_test.dart | 2 +- source_gen/test/library/path_to_url_test.dart | 3 +- source_gen/test/type_checker_test.dart | 7 +- 19 files changed, 395 insertions(+), 124 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index edbe4ba2..e9ca8840 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -8,6 +8,7 @@ analyzer: linter: rules: + - analyzer_use_new_elements - avoid_bool_literals_in_conditional_expressions - avoid_classes_with_only_static_members - avoid_private_typedef_functions diff --git a/example/lib/src/member_count_library_generator.dart b/example/lib/src/member_count_library_generator.dart index 110b3ac6..a2a1dd89 100644 --- a/example/lib/src/member_count_library_generator.dart +++ b/example/lib/src/member_count_library_generator.dart @@ -13,7 +13,7 @@ class MemberCountLibraryGenerator extends Generator { final topLevelVarCount = topLevelNumVariables(library).length; return ''' -// Source library: ${library.element.source.uri} +// Source library: ${library.element2.uri} const topLevelNumVarCount = $topLevelVarCount; '''; } diff --git a/example/lib/src/multiplier_generator.dart b/example/lib/src/multiplier_generator.dart index c6ec4aed..60b34ca6 100644 --- a/example/lib/src/multiplier_generator.dart +++ b/example/lib/src/multiplier_generator.dart @@ -2,7 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/element2.dart'; import 'package:build/build.dart'; import 'package:source_gen/source_gen.dart'; @@ -10,13 +10,13 @@ import '../annotations.dart'; class MultiplierGenerator extends GeneratorForAnnotation { @override - String generateForAnnotatedElement( - Element element, + String generateForAnnotatedElement2( + Element2 element, ConstantReader annotation, BuildStep buildStep, ) { final numValue = annotation.read('value').literalValue as num; - return 'num ${element.name}Multiplied() => ${element.name} * $numValue;'; + return 'num ${element.name3}Multiplied() => ${element.name3} * $numValue;'; } } diff --git a/example/lib/src/property_product_generator.dart b/example/lib/src/property_product_generator.dart index 140131f1..48cb5460 100644 --- a/example/lib/src/property_product_generator.dart +++ b/example/lib/src/property_product_generator.dart @@ -11,7 +11,8 @@ class PropertyProductGenerator extends Generator { @override String generate(LibraryReader library, BuildStep buildStep) { final productNames = topLevelNumVariables(library) - .map((element) => element.name) + .map((element) => element.name3) + .nonNulls .join(' * '); return ''' diff --git a/example/lib/src/property_sum_generator.dart b/example/lib/src/property_sum_generator.dart index 67ebf885..eac5ad1c 100644 --- a/example/lib/src/property_sum_generator.dart +++ b/example/lib/src/property_sum_generator.dart @@ -11,7 +11,8 @@ class PropertySumGenerator extends Generator { @override String generate(LibraryReader library, BuildStep buildStep) { final sumNames = topLevelNumVariables(library) - .map((element) => element.name) + .map((element) => element.name3) + .nonNulls .join(' + '); return ''' diff --git a/example/lib/src/utils.dart b/example/lib/src/utils.dart index 68bc048f..41f5d2b6 100644 --- a/example/lib/src/utils.dart +++ b/example/lib/src/utils.dart @@ -2,13 +2,13 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/element2.dart'; import 'package:source_gen/source_gen.dart'; -/// Returns all [TopLevelVariableElement] members in [reader]'s library that +/// Returns all [TopLevelVariableElement2] members in [reader]'s library that /// have a type of [num]. -Iterable topLevelNumVariables(LibraryReader reader) => - reader.allElements.whereType().where( +Iterable topLevelNumVariables(LibraryReader reader) => + reader.allElements2.whereType().where( (element) => element.type.isDartCoreNum || element.type.isDartCoreInt || diff --git a/source_gen/lib/source_gen.dart b/source_gen/lib/source_gen.dart index af399f77..7e4acae8 100644 --- a/source_gen/lib/source_gen.dart +++ b/source_gen/lib/source_gen.dart @@ -10,6 +10,6 @@ export 'src/generator.dart' show Generator, InvalidGenerationSource, InvalidGenerationSourceError; export 'src/generator_for_annotation.dart' show GeneratorForAnnotation; export 'src/library.dart' show AnnotatedElement, LibraryReader; -export 'src/span_for_element.dart' show spanForElement; +export 'src/span_for_element.dart' show spanForElement, spanForElement2; export 'src/type_checker.dart' show TypeChecker, UnresolvedAnnotationException; export 'src/utils.dart' show typeNameOf; diff --git a/source_gen/lib/src/builder.dart b/source_gen/lib/src/builder.dart index 53b8c2f7..7f502243 100644 --- a/source_gen/lib/src/builder.dart +++ b/source_gen/lib/src/builder.dart @@ -6,6 +6,9 @@ import 'dart:convert'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/element2.dart'; +// ignore: implementation_imports +import 'package:analyzer/src/utilities/extensions/element.dart'; import 'package:build/build.dart'; import 'package:dart_style/dart_style.dart'; import 'package:pub_semver/pub_semver.dart'; @@ -103,16 +106,17 @@ class _Builder extends Builder { } final lib = await buildStep.resolver - .libraryFor(buildStep.inputId, allowSyntaxErrors: allowSyntaxErrors); + .libraryFor2(buildStep.inputId, allowSyntaxErrors: allowSyntaxErrors); await _generateForLibrary(lib, buildStep); } Future _generateForLibrary( - LibraryElement library, + LibraryElement2 library2, BuildStep buildStep, ) async { + final library = library2.asElement; final generatedOutputs = - await _generate(library, _generators, buildStep).toList(); + await _generate(library2, _generators, buildStep).toList(); // Don't output useless files. // @@ -353,11 +357,11 @@ class LibraryBuilder extends _Builder { } Stream _generate( - LibraryElement library, + LibraryElement2 library2, List generators, BuildStep buildStep, ) async* { - final libraryReader = LibraryReader(library); + final libraryReader = LibraryReader.v2(library2); for (var i = 0; i < generators.length; i++) { final gen = generators[i]; var msg = 'Running $gen'; diff --git a/source_gen/lib/src/generator.dart b/source_gen/lib/src/generator.dart index b29121c4..69e8b604 100644 --- a/source_gen/lib/src/generator.dart +++ b/source_gen/lib/src/generator.dart @@ -6,6 +6,9 @@ import 'dart:async'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/element2.dart'; +// ignore: implementation_imports +import 'package:analyzer/src/utilities/extensions/element.dart'; import 'package:build/build.dart'; import 'library.dart'; @@ -49,7 +52,7 @@ class InvalidGenerationSource implements Exception { /// /// May be `null` if the error had no associated element, or if the location /// was passed with [node]. - final Element? element; + final Element2? element2; /// The AST Node associated with this error. /// @@ -60,9 +63,18 @@ class InvalidGenerationSource implements Exception { InvalidGenerationSource( this.message, { this.todo = '', - this.element, + Element? element, this.node, - }); + }) : element2 = element?.asElement2; + + InvalidGenerationSource.v2( + this.message, { + this.todo = '', + Element2? element, + this.node, + }) : element2 = element; + + Element? get element => element2?.asElement; @override String toString() { diff --git a/source_gen/lib/src/generator_for_annotation.dart b/source_gen/lib/src/generator_for_annotation.dart index e3728c56..5a4bb715 100644 --- a/source_gen/lib/src/generator_for_annotation.dart +++ b/source_gen/lib/src/generator_for_annotation.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/element2.dart'; import 'package:build/build.dart'; import 'constants/reader.dart'; @@ -58,11 +59,16 @@ abstract class GeneratorForAnnotation extends Generator { typeChecker, throwOnUnresolved: throwOnUnresolved, )) { - final generatedValue = generateForAnnotatedElement( + var generatedValue = generateForAnnotatedElement( annotatedElement.element, annotatedElement.annotation, buildStep, ); + generatedValue ??= generateForAnnotatedElement2( + annotatedElement.element2, + annotatedElement.annotation, + buildStep, + ); await for (var value in normalizeGeneratorOutput(generatedValue)) { assert(value.length == value.trim().length); values.add(value); @@ -93,5 +99,28 @@ abstract class GeneratorForAnnotation extends Generator { Element element, ConstantReader annotation, BuildStep buildStep, - ); + ) {} + + /// Implement to return source code to generate for [element]. + /// + /// This method is invoked based on finding elements annotated with an + /// instance of [T]. The [annotation] is provided as a [ConstantReader]. + /// + /// Supported return values include a single [String] or multiple [String] + /// instances within an [Iterable] or [Stream]. It is also valid to return a + /// [Future] of [String], [Iterable], or [Stream]. When multiple values are + /// returned through an iterable or stream they will be deduplicated. + /// Typically each value will be an independent unit of code and the + /// deduplication prevents re-defining the same member multiple times. For + /// example if multiple annotated elements may need a specific utility method + /// available it can be output for each one, and the single deduplicated + /// definition can be shared. + /// + /// Implementations should return `null` when no content is generated. Empty + /// or whitespace-only [String] instances are also ignored. + dynamic generateForAnnotatedElement2( + Element2 element, + ConstantReader annotation, + BuildStep buildStep, + ) {} } diff --git a/source_gen/lib/src/library.dart b/source_gen/lib/src/library.dart index ccf74238..f2d03577 100644 --- a/source_gen/lib/src/library.dart +++ b/source_gen/lib/src/library.dart @@ -3,6 +3,9 @@ // BSD-style license that can be found in the LICENSE file. import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/element2.dart'; +// ignore: implementation_imports +import 'package:analyzer/src/utilities/extensions/element.dart'; import 'package:build/build.dart'; import 'package:path/path.dart' as p; @@ -13,25 +16,29 @@ import 'utils.dart'; /// Result of finding an [annotation] on [element] through [LibraryReader]. class AnnotatedElement { final ConstantReader annotation; - final Element element; + final Element2 element2; - const AnnotatedElement(this.annotation, this.element); + const AnnotatedElement(this.annotation, this.element2); + + Element get element => element2.asElement!; + + Metadata? get metadata2 { + if (element2 case final Annotatable annotatable) { + return annotatable.metadata2; + } + return null; + } } /// A high-level wrapper API with common functionality for [LibraryElement]. class LibraryReader { - final LibraryElement element; + final LibraryElement2 element2; - LibraryReader(this.element); + LibraryReader(LibraryElement element) : this.v2(element.asElement2); - /// Returns a top-level [ClassElement] publicly visible in by [name]. - /// - /// Unlike [LibraryElement.getClass], this also correctly traverses - /// identifiers that are accessible via one or more `export` directives. - ClassElement? findType(String name) { - final type = element.exportNamespace.get(name); - return type is ClassElement ? type : null; - } + LibraryReader.v2(this.element2); + + LibraryElement get element => element2.asElement; /// All of the declarations in this library. Iterable get allElements => [ @@ -42,6 +49,22 @@ class LibraryReader { ...element.definingCompilationUnit.parts, ]; + /// All of the declarations in this library. + Iterable get allElements2 => [element2, ...element2.children2]; + + /// All of the elements representing classes in this library. + Iterable get classes => + element.units.expand((cu) => cu.classes); + + /// All of the elements representing classes in this library. + Iterable get classes2 => element2.classes; + + /// All of the elements representing enums in this library. + Iterable get enums => element.units.expand((cu) => cu.enums); + + /// All of the elements representing enums in this library. + Iterable get enums2 => element2.enums; + /// All of the declarations in this library annotated with [checker]. Iterable annotatedWith( TypeChecker checker, { @@ -52,8 +75,14 @@ class LibraryReader { element, throwOnUnresolved: throwOnUnresolved, ); + + final element2 = element.asElement2; + if (element2 == null) { + return; + } + if (annotation != null) { - yield AnnotatedElement(ConstantReader(annotation), element); + yield AnnotatedElement(ConstantReader(annotation), element2); } } } @@ -69,11 +98,20 @@ class LibraryReader { throwOnUnresolved: throwOnUnresolved, ); if (annotation != null) { - yield AnnotatedElement(ConstantReader(annotation), element); + yield AnnotatedElement(ConstantReader(annotation), element.asElement2!); } } } + /// Returns a top-level [ClassElement] publicly visible in by [name]. + /// + /// Unlike [LibraryElement.getClass], this also correctly traverses + /// identifiers that are accessible via one or more `export` directives. + ClassElement? findType(String name) { + final type = element.exportNamespace.get(name); + return type is ClassElement ? type : null; + } + /// Returns a [Uri] from the current library to the target [asset]. /// /// This is a typed convenience function for using [pathToUrl], and the same @@ -86,6 +124,13 @@ class LibraryReader { /// API restrictions hold around supported schemes and relative paths. Uri pathToElement(Element element) => pathToUrl(element.source!.uri); + /// Returns a [Uri] from the current library to the target [element]. + /// + /// This is a typed convenience function for using [pathToUrl], and the same + /// API restrictions hold around supported schemes and relative paths. + Uri pathToElement2(Element2 element) => + pathToUrl(element.firstFragment.libraryFragment!.source.uri); + /// Returns a [Uri] from the current library to the one provided. /// /// If possible, a `package:` or `dart:` URL scheme will be used to reference @@ -131,15 +176,10 @@ class LibraryReader { return Uri(path: to.pathSegments.last); } final relative = p.toUri( - p.relative( - to.toString(), - from: from.toString(), - ), + p.relative(to.toString(), from: from.toString()), ); // We now have a URL like "../b.dart", but we just want "b.dart". - return relative.replace( - pathSegments: relative.pathSegments.skip(1), - ); + return relative.replace(pathSegments: relative.pathSegments.skip(1)); } throw ArgumentError.value(to, 'to', 'Not relative to $from'); } @@ -162,11 +202,4 @@ class LibraryReader { fromSegments[0] == toSegments[0] && fromSegments[1] == toSegments[1]; } - - /// All of the elements representing classes in this library. - Iterable get classes => - element.units.expand((cu) => cu.classes); - - /// All of the elements representing enums in this library. - Iterable get enums => element.units.expand((cu) => cu.enums); } diff --git a/source_gen/lib/src/span_for_element.dart b/source_gen/lib/src/span_for_element.dart index b92f676b..6be36f2e 100644 --- a/source_gen/lib/src/span_for_element.dart +++ b/source_gen/lib/src/span_for_element.dart @@ -4,6 +4,7 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/element2.dart'; import 'package:source_span/source_span.dart'; import 'utils.dart'; @@ -25,14 +26,8 @@ SourceSpan spanForElement(Element element, [SourceFile? file]) { final contents = element.source?.contents; if (contents == null) { return SourceSpan( - SourceLocation( - element.nameOffset, - sourceUrl: url, - ), - SourceLocation( - element.nameOffset + element.nameLength, - sourceUrl: url, - ), + SourceLocation(element.nameOffset, sourceUrl: url), + SourceLocation(element.nameOffset + element.nameLength, sourceUrl: url), element.name!, ); } @@ -53,6 +48,52 @@ SourceSpan spanForElement(Element element, [SourceFile? file]) { return file.span(element.nameOffset, element.nameOffset + element.nameLength); } +/// Returns a source span that spans the location where [element] is defined. +/// +/// May be used to emit user-friendly warning and error messages: +/// ```dart +/// void invalidClass(ClassElement class) { +/// log.warning(spanForElement.message('Cannot implement "Secret"')); +/// } +/// ``` +/// +/// Not all results from the analyzer API may return source information as part +/// of the element, so [file] may need to be manually provided in those cases. +SourceSpan spanForElement2(Element2 element, [SourceFile? file]) { + final fragment = element.firstFragment; + final url = assetToPackageUrl(fragment.libraryFragment!.source.uri); + if (file == null) { + final contents = fragment.libraryFragment?.source.contents; + if (contents == null) { + return SourceSpan( + SourceLocation(fragment.nameOffset2!, sourceUrl: url), + SourceLocation( + fragment.nameOffset2! + fragment.name2!.length, + sourceUrl: url, + ), + fragment.name2!, + ); + } + file = SourceFile.fromString(contents.data, url: url); + } + if (fragment.nameOffset2 == null) { + if (element is PropertyInducingElement2) { + if (element.getter2 != null) { + return spanForElement2(element.getter2!); + } + + if (element.setter2 != null) { + return spanForElement2(element.setter2!); + } + } + } + + return file.span( + fragment.nameOffset2!, + fragment.nameOffset2! + fragment.name2!.length, + ); +} + /// Returns a source span that spans the location where [node] is written. SourceSpan spanForNode(AstNode node) { final unit = node.thisOrAncestorOfType()!; diff --git a/source_gen/lib/src/type_checker.dart b/source_gen/lib/src/type_checker.dart index 46db2302..b504d1c6 100644 --- a/source_gen/lib/src/type_checker.dart +++ b/source_gen/lib/src/type_checker.dart @@ -8,7 +8,10 @@ import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/element2.dart'; import 'package:analyzer/dart/element/type.dart'; +// ignore: implementation_imports +import 'package:analyzer/src/utilities/extensions/element.dart'; import 'package:build/build.dart'; import 'package:source_span/source_span.dart'; @@ -68,8 +71,32 @@ abstract class TypeChecker { if (element.metadata.isEmpty) { return null; } - final results = - annotationsOf(element, throwOnUnresolved: throwOnUnresolved); + final results = annotationsOf( + element, + throwOnUnresolved: throwOnUnresolved, + ); + return results.isEmpty ? null : results.first; + } + + /// Returns the first constant annotating [element] assignable to this type. + /// + /// Otherwise returns `null`. + /// + /// Throws on unresolved annotations unless [throwOnUnresolved] is `false`. + DartObject? firstAnnotationOf2( + Element2 element, { + bool throwOnUnresolved = true, + }) { + if (element case final Annotatable annotatable) { + final annotations = annotatable.metadata2.annotations; + if (annotations.isEmpty) { + return null; + } + } + final results = annotationsOf2( + element, + throwOnUnresolved: throwOnUnresolved, + ); return results.isEmpty ? null : results.first; } @@ -79,6 +106,12 @@ abstract class TypeChecker { bool hasAnnotationOf(Element element, {bool throwOnUnresolved = true}) => firstAnnotationOf(element, throwOnUnresolved: throwOnUnresolved) != null; + /// Returns if a constant annotating [element] is assignable to this type. + /// + /// Throws on unresolved annotations unless [throwOnUnresolved] is `false`. + bool hasAnnotationOf2(Element2 element, {bool throwOnUnresolved = true}) => + firstAnnotationOf2(element, throwOnUnresolved: throwOnUnresolved) != null; + /// Returns the first constant annotating [element] that is exactly this type. /// /// Throws [UnresolvedAnnotationException] on unresolved annotations unless @@ -90,11 +123,35 @@ abstract class TypeChecker { if (element.metadata.isEmpty) { return null; } - final results = - annotationsOfExact(element, throwOnUnresolved: throwOnUnresolved); + final results = annotationsOfExact( + element, + throwOnUnresolved: throwOnUnresolved, + ); return results.isEmpty ? null : results.first; } + /// Returns the first constant annotating [element] that is exactly this type. + /// + /// Throws [UnresolvedAnnotationException] on unresolved annotations unless + /// [throwOnUnresolved] is explicitly set to `false` (default is `true`). + DartObject? firstAnnotationOfExact2( + Element2 element, { + bool throwOnUnresolved = true, + }) { + if (element case final Annotatable annotatable) { + final annotations = annotatable.metadata2.annotations; + if (annotations.isEmpty) { + return null; + } + final results = annotationsOfExact2( + element, + throwOnUnresolved: throwOnUnresolved, + ); + return results.isEmpty ? null : results.first; + } + return null; + } + /// Returns if a constant annotating [element] is exactly this type. /// /// Throws [UnresolvedAnnotationException] on unresolved annotations unless @@ -103,12 +160,39 @@ abstract class TypeChecker { firstAnnotationOfExact(element, throwOnUnresolved: throwOnUnresolved) != null; + /// Returns if a constant annotating [element] is exactly this type. + /// + /// Throws [UnresolvedAnnotationException] on unresolved annotations unless + /// [throwOnUnresolved] is explicitly set to `false` (default is `true`). + bool hasAnnotationOfExact2( + Element2 element, { + bool throwOnUnresolved = true, + }) => + firstAnnotationOfExact2(element, throwOnUnresolved: throwOnUnresolved) != + null; + DartObject? _computeConstantValue( Element element, int annotationIndex, { bool throwOnUnresolved = true, }) { final annotation = element.metadata[annotationIndex]; + final result = annotation.computeConstantValue(); + if (result == null && throwOnUnresolved) { + throw UnresolvedAnnotationException._from( + element.asElement2!, + annotationIndex, + ); + } + return result; + } + + DartObject? _computeConstantValue2( + Element2 element, + ElementAnnotation annotation, + int annotationIndex, { + bool throwOnUnresolved = true, + }) { final result = annotation.computeConstantValue(); if (result == null && throwOnUnresolved) { throw UnresolvedAnnotationException._from(element, annotationIndex); @@ -130,6 +214,20 @@ abstract class TypeChecker { throwOnUnresolved: throwOnUnresolved, ); + /// Returns annotating constants on [element] assignable to this type. + /// + /// Throws [UnresolvedAnnotationException] on unresolved annotations unless + /// [throwOnUnresolved] is explicitly set to `false` (default is `true`). + Iterable annotationsOf2( + Element2 element, { + bool throwOnUnresolved = true, + }) => + _annotationsWhere2( + element, + isAssignableFromType, + throwOnUnresolved: throwOnUnresolved, + ); + Iterable _annotationsWhere( Element element, bool Function(DartType) predicate, { @@ -147,6 +245,27 @@ abstract class TypeChecker { } } + Iterable _annotationsWhere2( + Element2 element, + bool Function(DartType) predicate, { + bool throwOnUnresolved = true, + }) sync* { + if (element case final Annotatable annotatable) { + final annotations = annotatable.metadata2.annotations; + for (var i = 0; i < annotations.length; i++) { + final value = _computeConstantValue2( + element, + annotations[i], + i, + throwOnUnresolved: throwOnUnresolved, + ); + if (value?.type != null && predicate(value!.type!)) { + yield value; + } + } + } + } + /// Returns annotating constants on [element] of exactly this type. /// /// Throws [UnresolvedAnnotationException] on unresolved annotations unless @@ -161,11 +280,31 @@ abstract class TypeChecker { throwOnUnresolved: throwOnUnresolved, ); + /// Returns annotating constants on [element] of exactly this type. + /// + /// Throws [UnresolvedAnnotationException] on unresolved annotations unless + /// [throwOnUnresolved] is explicitly set to `false` (default is `true`). + Iterable annotationsOfExact2( + Element2 element, { + bool throwOnUnresolved = true, + }) => + _annotationsWhere2( + element, + isExactlyType, + throwOnUnresolved: throwOnUnresolved, + ); + /// Returns `true` if the type of [element] can be assigned to this type. bool isAssignableFrom(Element element) => isExactly(element) || (element is InterfaceElement && element.allSupertypes.any(isExactlyType)); + /// Returns `true` if the type of [element] can be assigned to this type. + bool isAssignableFrom2(Element2 element) => + isExactly2(element) || + (element is InterfaceElement2 && + element.allSupertypes.any(isExactlyType)); + /// Returns `true` if [staticType] can be assigned to this type. bool isAssignableFromType(DartType staticType) { final element = staticType.element; @@ -175,6 +314,9 @@ abstract class TypeChecker { /// Returns `true` if representing the exact same class as [element]. bool isExactly(Element element); + /// Returns `true` if representing the exact same class as [element]. + bool isExactly2(Element2 element); + /// Returns `true` if representing the exact same type as [staticType]. /// /// This will always return false for types without a backingclass such as @@ -225,15 +367,19 @@ class _LibraryTypeChecker extends TypeChecker { bool isExactly(Element element) => element is InterfaceElement && element == _type.element; + @override + bool isExactly2(Element2 element) => + element is InterfaceElement2 && element == _type.element3; + @override String toString() => urlOfElement(_type.element!); } // Checks a runtime type against a static type. class _MirrorTypeChecker extends TypeChecker { - static Uri _uriOf(ClassMirror mirror) => - normalizeUrl((mirror.owner as LibraryMirror).uri) - .replace(fragment: MirrorSystem.getName(mirror.simpleName)); + static Uri _uriOf(ClassMirror mirror) => normalizeUrl( + (mirror.owner as LibraryMirror).uri, + ).replace(fragment: MirrorSystem.getName(mirror.simpleName)); // Precomputed type checker for types that already have been used. static final _cache = Expando(); @@ -248,6 +394,9 @@ class _MirrorTypeChecker extends TypeChecker { @override bool isExactly(Element element) => _computed.isExactly(element); + @override + bool isExactly2(Element2 element) => _computed.isExactly2(element); + @override String toString() => _computed.toString(); } @@ -280,6 +429,10 @@ class _UriTypeChecker extends TypeChecker { @override bool isExactly(Element element) => hasSameUrl(urlOfElement(element)); + @override + bool isExactly2(Element2 element) => + hasSameUrl(urlOfElement(element.asElement!)); + @override String toString() => '$uri'; } @@ -291,6 +444,10 @@ class _AnyChecker extends TypeChecker { @override bool isExactly(Element element) => _checkers.any((c) => c.isExactly(element)); + + @override + bool isExactly2(Element2 element) => + _checkers.any((c) => c.isExactly2(element)); } /// Exception thrown when [TypeChecker] fails to resolve a metadata annotation. @@ -301,22 +458,24 @@ class _AnyChecker extends TypeChecker { /// defined (for build systems such as Bazel). class UnresolvedAnnotationException implements Exception { /// Element that was annotated with something we could not resolve. - final Element annotatedElement; + final Element2 annotatedElement2; /// Source span of the annotation that was not resolved. /// /// May be `null` if the import library was not found. final SourceSpan? annotationSource; - static SourceSpan? _findSpan( - Element annotatedElement, - int annotationIndex, - ) { + Element get annotatedElement => annotatedElement2.asElement!; + + static SourceSpan? _findSpan(Element2 annotatedElement, int annotationIndex) { try { - final parsedLibrary = annotatedElement.session! - .getParsedLibraryByElement(annotatedElement.library!) - as ParsedLibraryResult; - final declaration = parsedLibrary.getElementDeclaration(annotatedElement); + final parsedLibrary = + annotatedElement.session!.getParsedLibraryByElement2( + annotatedElement.library2!, + ) as ParsedLibraryResult; + final declaration = parsedLibrary.getElementDeclaration2( + annotatedElement.firstFragment, + ); if (declaration == null) { return null; } @@ -360,7 +519,7 @@ the version of `package:source_gen`, `package:analyzer` from `pubspec.lock`. /// Creates an exception from an annotation ([annotationIndex]) that was not /// resolvable while traversing [Element.metadata] on [annotatedElement]. factory UnresolvedAnnotationException._from( - Element annotatedElement, + Element2 annotatedElement, int annotationIndex, ) { final sourceSpan = _findSpan(annotatedElement, annotationIndex); @@ -368,7 +527,7 @@ the version of `package:source_gen`, `package:analyzer` from `pubspec.lock`. } const UnresolvedAnnotationException._( - this.annotatedElement, + this.annotatedElement2, this.annotationSource, ); diff --git a/source_gen/pubspec.yaml b/source_gen/pubspec.yaml index 7d72a226..0487df05 100644 --- a/source_gen/pubspec.yaml +++ b/source_gen/pubspec.yaml @@ -4,6 +4,7 @@ description: >- Source code generation builders and utilities for the Dart build system repository: https://github.com/dart-lang/source_gen/tree/master/source_gen resolution: workspace +publish_to: none environment: sdk: ^3.6.0 @@ -26,3 +27,31 @@ dev_dependencies: dart_flutter_team_lints: ^3.1.0 term_glyph: ^1.2.0 test: ^1.16.0 + +dependency_overrides: + analyzer: ^7.1.0 + build: + git: + url: https://github.com/dart-lang/build.git + path: build + ref: resolver-2-methods + build_resolvers: + git: + url: https://github.com/dart-lang/build.git + path: build_resolvers + ref: resolver-2-methods + build_runner: + git: + url: https://github.com/dart-lang/build.git + path: build_runner + ref: resolver-2-methods + build_runner_core: + git: + url: https://github.com/dart-lang/build.git + path: build_runner_core + ref: resolver-2-methods + build_test: + git: + url: https://github.com/dart-lang/build.git + path: build_test + ref: resolver-2-methods diff --git a/source_gen/test/external_only_type_checker_test.dart b/source_gen/test/external_only_type_checker_test.dart index 59b7746c..76ca1cb3 100644 --- a/source_gen/test/external_only_type_checker_test.dart +++ b/source_gen/test/external_only_type_checker_test.dart @@ -28,8 +28,8 @@ void main() { export 'type_checker_test.dart' show NonPublic; ''', (resolver) async { - thisTest = LibraryReader( - await resolver.libraryFor( + thisTest = LibraryReader.v2( + await resolver.libraryFor2( AssetId('source_gen', 'test/external_only_type_checker_test.dart'), ), ); diff --git a/source_gen/test/generator_for_annotation_test.dart b/source_gen/test/generator_for_annotation_test.dart index e78f6ad4..5bf4f137 100644 --- a/source_gen/test/generator_for_annotation_test.dart +++ b/source_gen/test/generator_for_annotation_test.dart @@ -142,47 +142,6 @@ $dartFormatWidth ); }); - test('applies to annotated directives', () async { - final builder = LibraryBuilder( - _StubGenerator( - 'Deprecated', - (element) => '// ${element.runtimeType}', - ), - ); - await testBuilder( - builder, - { - 'a|lib/imported.dart': '', - 'a|lib/part.dart': 'part of \'file.dart\';', - 'a|lib/file.dart': ''' - library; - @deprecated - import 'imported.dart'; - @deprecated - export 'imported.dart'; - @deprecated - part 'part.dart'; - ''', - }, - outputs: { - 'a|lib/file.g.dart': ''' -$dartFormatWidth -// GENERATED CODE - DO NOT MODIFY BY HAND - -// ************************************************************************** -// Generator: Deprecated -// ************************************************************************** - -// LibraryImportElementImpl - -// LibraryExportElementImpl - -// PartElementImpl -''', - }, - ); - }); - group('Unresolved annotations', () { test('cause an error by default', () async { final builder = LibraryBuilder( diff --git a/source_gen/test/library/find_type_test.dart b/source_gen/test/library/find_type_test.dart index 3e2a4733..354140e4 100644 --- a/source_gen/test/library/find_type_test.dart +++ b/source_gen/test/library/find_type_test.dart @@ -34,7 +34,7 @@ void main() { setUpAll(() async { library = await resolveSources( {'a|source.dart': _source, 'a|part.dart': _partSource}, - (r) async => LibraryReader((await r.findLibraryByName('test_lib'))!), + (r) async => LibraryReader.v2((await r.findLibraryByName2('test_lib'))!), ); }); diff --git a/source_gen/test/library/path_to_url_test.dart b/source_gen/test/library/path_to_url_test.dart index ac859f5d..8da5a596 100644 --- a/source_gen/test/library/path_to_url_test.dart +++ b/source_gen/test/library/path_to_url_test.dart @@ -4,6 +4,7 @@ // Increase timeouts on this test which resolves source code and can be slow. import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/element2.dart'; import 'package:analyzer/source/source.dart'; import 'package:source_gen/source_gen.dart'; import 'package:test/test.dart'; @@ -163,7 +164,7 @@ void main() { }); } -class _FakeLibraryElement implements LibraryElement { +class _FakeLibraryElement implements LibraryElement, LibraryElement2 { final Uri _sourceUri; _FakeLibraryElement(this._sourceUri); diff --git a/source_gen/test/type_checker_test.dart b/source_gen/test/type_checker_test.dart index 134d2eb3..c6901030 100644 --- a/source_gen/test/type_checker_test.dart +++ b/source_gen/test/type_checker_test.dart @@ -57,9 +57,10 @@ void main() { (resolver) async { core = (await resolver.findLibraryByName('dart.core'))!; collection = (await resolver.findLibraryByName('dart.collection'))!; - sourceGen = LibraryReader( - await resolver - .libraryFor(AssetId('source_gen', 'lib/source_gen.dart')), + sourceGen = LibraryReader.v2( + await resolver.libraryFor2( + AssetId('source_gen', 'lib/source_gen.dart'), + ), ); testSource = await resolver .libraryFor(AssetId('source_gen', 'test/type_checker_test.dart'));