From 0aa4656d433ea9525d7039f008f9483ce585ebe8 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 17 Dec 2024 10:52:28 -0600 Subject: [PATCH 1/2] Support latest analyzer and dart_style, prepare v2 release (#736) --- example/pubspec.yaml | 2 +- source_gen/CHANGELOG.md | 7 ++++--- source_gen/pubspec.yaml | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 6a92438d..b0e39913 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -6,7 +6,7 @@ environment: sdk: ^3.6.0 dependencies: - analyzer: '>=5.2.0 <7.0.0' + analyzer: '>=6.9.0 <8.0.0' build: ^2.0.0 source_gen: any diff --git a/source_gen/CHANGELOG.md b/source_gen/CHANGELOG.md index 14b39d41..f299d63a 100644 --- a/source_gen/CHANGELOG.md +++ b/source_gen/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.0.0-wip +## 2.0.0 - **Breaking Change**: Change `formatOutput` function to accept a language version parameter. @@ -8,13 +8,14 @@ - Document deduplication behavior for the output of `GeneratorForAnnotation.generateForAnnotatedElement`. - Support all the glob quotes. -- Require `analyzer: ^6.9.0` -- Require Dart 3.6.0 +- Require `analyzer: '>=6.9.0 <8.0.0'` +- Support the latest `package:dart_style` - `LibraryBuilder`, `PartBuilder`, and `SharedPartBuilder` now take an optional `writeDescriptions` boolean. When set to `false`, headers and generator descriptions for the files will not be included in the builder output. - Include `//dart format width=80` comments in files generated by a `LibraryBuilder` or `PartBuilder` and formatted with the default callback. +- Require Dart 3.6.0 ## 1.5.0 diff --git a/source_gen/pubspec.yaml b/source_gen/pubspec.yaml index 0487df05..c24723e5 100644 --- a/source_gen/pubspec.yaml +++ b/source_gen/pubspec.yaml @@ -1,5 +1,5 @@ name: source_gen -version: 2.0.0-wip +version: 2.0.0 description: >- Source code generation builders and utilities for the Dart build system repository: https://github.com/dart-lang/source_gen/tree/master/source_gen @@ -10,10 +10,10 @@ environment: sdk: ^3.6.0 dependencies: - analyzer: ^6.9.0 + analyzer: '>=6.9.0 <8.0.0' async: ^2.5.0 build: ^2.1.0 - dart_style: ^2.3.7 + dart_style: '>=2.3.7 <4.0.0' glob: ^2.0.0 path: ^1.8.0 pub_semver: ^2.1.4 From c6f1dfbb4181ad412c84a243e5b4dd8fcdc2a0ad Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Wed, 1 Jan 2025 11:38:08 -0800 Subject: [PATCH 2/2] Elements. Support for generateForAnnotatedDirective() and related. --- source_gen/lib/source_gen.dart | 3 +- .../lib/src/generator_for_annotation.dart | 38 ++++++ source_gen/lib/src/library.dart | 34 +++++ source_gen/lib/src/type_checker.dart | 10 +- source_gen/pubspec.yaml | 3 +- .../test/generator_for_annotation_test.dart | 116 ++++++++++++++---- 6 files changed, 176 insertions(+), 28 deletions(-) diff --git a/source_gen/lib/source_gen.dart b/source_gen/lib/source_gen.dart index 7e4acae8..57010a22 100644 --- a/source_gen/lib/source_gen.dart +++ b/source_gen/lib/source_gen.dart @@ -9,7 +9,8 @@ export 'src/constants/revive.dart' show Revivable; 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/library.dart' + show AnnotatedDirective, AnnotatedElement, LibraryReader; 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/generator_for_annotation.dart b/source_gen/lib/src/generator_for_annotation.dart index 5a4bb715..f2a94092 100644 --- a/source_gen/lib/src/generator_for_annotation.dart +++ b/source_gen/lib/src/generator_for_annotation.dart @@ -55,6 +55,21 @@ abstract class GeneratorForAnnotation extends Generator { FutureOr generate(LibraryReader library, BuildStep buildStep) async { final values = {}; + for (var annotatedDirective in library.libraryDirectivesAnnotatedWith( + typeChecker, + throwOnUnresolved: throwOnUnresolved, + )) { + final generatedValue = generateForAnnotatedDirective( + annotatedDirective.directive, + annotatedDirective.annotation, + buildStep, + ); + await for (var value in normalizeGeneratorOutput(generatedValue)) { + assert(value.length == value.trim().length); + values.add(value); + } + } + for (var annotatedElement in library.annotatedWith( typeChecker, throwOnUnresolved: throwOnUnresolved, @@ -123,4 +138,27 @@ abstract class GeneratorForAnnotation extends Generator { ConstantReader annotation, BuildStep buildStep, ) {} + + /// Implement to return source code to generate for [directive]. + /// + /// This method is invoked based on finding directives 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 generateForAnnotatedDirective( + ElementDirective directive, + ConstantReader annotation, + BuildStep buildStep, + ) {} } diff --git a/source_gen/lib/src/library.dart b/source_gen/lib/src/library.dart index f2d03577..8e447851 100644 --- a/source_gen/lib/src/library.dart +++ b/source_gen/lib/src/library.dart @@ -13,6 +13,16 @@ import 'constants/reader.dart'; import 'type_checker.dart'; import 'utils.dart'; +/// Result of finding an [annotation] on [directive] through [LibraryReader]. +class AnnotatedDirective { + final ConstantReader annotation; + final ElementDirective directive; + + const AnnotatedDirective(this.annotation, this.directive); + + Metadata? get metadata2 => directive.metadata2; +} + /// Result of finding an [annotation] on [element] through [LibraryReader]. class AnnotatedElement { final ConstantReader annotation; @@ -87,6 +97,30 @@ class LibraryReader { } } + /// All of the directives in this library annotated with [checker]. + Iterable libraryDirectivesAnnotatedWith( + TypeChecker checker, { + bool throwOnUnresolved = true, + }) sync* { + final firstFragment = element2.firstFragment; + final directives = [ + ...firstFragment.libraryImports2, + ...firstFragment.libraryExports2, + ...firstFragment.partIncludes, + ]; + + for (final directive in directives) { + final annotation = checker.firstAnnotationOf2( + directive, + throwOnUnresolved: throwOnUnresolved, + ); + + if (annotation != null) { + yield AnnotatedDirective(ConstantReader(annotation), directive); + } + } + } + /// All of the declarations in this library annotated with exactly [checker]. Iterable annotatedWithExact( TypeChecker checker, { diff --git a/source_gen/lib/src/type_checker.dart b/source_gen/lib/src/type_checker.dart index b504d1c6..eda074ed 100644 --- a/source_gen/lib/src/type_checker.dart +++ b/source_gen/lib/src/type_checker.dart @@ -84,7 +84,7 @@ abstract class TypeChecker { /// /// Throws on unresolved annotations unless [throwOnUnresolved] is `false`. DartObject? firstAnnotationOf2( - Element2 element, { + Object element, { bool throwOnUnresolved = true, }) { if (element case final Annotatable annotatable) { @@ -188,13 +188,13 @@ abstract class TypeChecker { } DartObject? _computeConstantValue2( - Element2 element, + Object element, ElementAnnotation annotation, int annotationIndex, { bool throwOnUnresolved = true, }) { final result = annotation.computeConstantValue(); - if (result == null && throwOnUnresolved) { + if (result == null && throwOnUnresolved && element is Element2) { throw UnresolvedAnnotationException._from(element, annotationIndex); } return result; @@ -219,7 +219,7 @@ abstract class TypeChecker { /// Throws [UnresolvedAnnotationException] on unresolved annotations unless /// [throwOnUnresolved] is explicitly set to `false` (default is `true`). Iterable annotationsOf2( - Element2 element, { + Object element, { bool throwOnUnresolved = true, }) => _annotationsWhere2( @@ -246,7 +246,7 @@ abstract class TypeChecker { } Iterable _annotationsWhere2( - Element2 element, + Object element, bool Function(DartType) predicate, { bool throwOnUnresolved = true, }) sync* { diff --git a/source_gen/pubspec.yaml b/source_gen/pubspec.yaml index c24723e5..b2bfde5b 100644 --- a/source_gen/pubspec.yaml +++ b/source_gen/pubspec.yaml @@ -29,7 +29,8 @@ dev_dependencies: test: ^1.16.0 dependency_overrides: - analyzer: ^7.1.0 + analyzer: + path: /Users/scheglov/Source/Dart/sdk.git/sdk/pkg/analyzer build: git: url: https://github.com/dart-lang/build.git diff --git a/source_gen/test/generator_for_annotation_test.dart b/source_gen/test/generator_for_annotation_test.dart index 5bf4f137..1245f7ea 100644 --- a/source_gen/test/generator_for_annotation_test.dart +++ b/source_gen/test/generator_for_annotation_test.dart @@ -9,6 +9,7 @@ library; import 'package:analyzer/dart/analysis/utilities.dart'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/element2.dart'; import 'package:build/build.dart'; import 'package:build_test/build_test.dart'; import 'package:source_gen/source_gen.dart'; @@ -25,8 +26,10 @@ void main() { 'list with null, empty, and whitespace items': [null, '', '\n \t'], }.entries) { test(entry.key, () async { - final generator = - _StubGenerator('Value', (_) => entry.value); + final generator = _StubGenerator( + 'Value', + elementBehavior: (_) => entry.value, + ); final builder = LibraryBuilder(generator); await testBuilder(builder, _inputMap, outputs: {}); }); @@ -34,10 +37,13 @@ void main() { }); test('Supports and dedupes multiple return values', () async { - final generator = _StubGenerator('Repeating', (element) sync* { - yield '// There are deprecated values in this library!'; - yield '// ${element.name}'; - }); + final generator = _StubGenerator( + 'Repeating', + elementBehavior: (element) sync* { + yield '// There are deprecated values in this library!'; + yield '// ${element.name}'; + }, + ); final builder = LibraryBuilder(generator); await testBuilder( builder, @@ -65,13 +71,19 @@ $dartFormatWidth group('handles errors correctly', () { for (var entry in { - 'sync errors': _StubGenerator('Failing', (_) { - throw StateError('not supported!'); - }), - 'from iterable': _StubGenerator('FailingIterable', (_) sync* { - yield '// There are deprecated values in this library!'; - throw StateError('not supported!'); - }), + 'sync errors': _StubGenerator( + 'Failing', + elementBehavior: (_) { + throw StateError('not supported!'); + }, + ), + 'from iterable': _StubGenerator( + 'FailingIterable', + elementBehavior: (_) sync* { + yield '// There are deprecated values in this library!'; + throw StateError('not supported!'); + }, + ), }.entries) { test(entry.key, () async { final builder = LibraryBuilder(entry.value); @@ -92,8 +104,12 @@ $dartFormatWidth test('Does not resolve the library if there are no top level annotations', () async { - final builder = - LibraryBuilder(_StubGenerator('Deprecated', (_) => null)); + final builder = LibraryBuilder( + _StubGenerator( + 'Deprecated', + elementBehavior: (_) => null, + ), + ); final input = AssetId('a', 'lib/a.dart'); final assets = {input: 'main() {}'}; @@ -116,7 +132,7 @@ $dartFormatWidth final builder = LibraryBuilder( _StubGenerator( 'Deprecated', - (element) => '// ${element.displayName}', + elementBehavior: (element) => '// ${element.displayName}', ), ); await testBuilder( @@ -142,12 +158,54 @@ $dartFormatWidth ); }); + test('applies to annotated directives', () async { + final builder = LibraryBuilder( + _StubGenerator( + 'Deprecated', + directiveBehavior: (element) => '// ${element.runtimeType}', + elementBehavior: (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( _StubGenerator( 'Deprecated', - (element) => '// ${element.displayName}', + elementBehavior: (element) => '// ${element.displayName}', ), ); expect( @@ -169,7 +227,7 @@ $dartFormatWidth final builder = LibraryBuilder( _StubGenerator( 'Deprecated', - (element) => '// ${element.displayName}', + elementBehavior: (element) => '// ${element.displayName}', throwOnUnresolved: false, ), ); @@ -192,9 +250,23 @@ $dartFormatWidth class _StubGenerator extends GeneratorForAnnotation { final String _name; - final Object? Function(Element) _behavior; + final Object? Function(ElementDirective) directiveBehavior; + final Object? Function(Element) elementBehavior; + + const _StubGenerator( + this._name, { + this.directiveBehavior = _returnNull, + required this.elementBehavior, + super.throwOnUnresolved, + }); - const _StubGenerator(this._name, this._behavior, {super.throwOnUnresolved}); + @override + Object? generateForAnnotatedDirective( + ElementDirective directive, + ConstantReader annotation, + BuildStep buildStep, + ) => + directiveBehavior(directive); @override Object? generateForAnnotatedElement( @@ -202,10 +274,12 @@ class _StubGenerator extends GeneratorForAnnotation { ConstantReader annotation, BuildStep buildStep, ) => - _behavior(element); + elementBehavior(element); @override String toString() => _name; + + static Null _returnNull(Object _) => null; } const _inputMap = {