diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 2e2ed302..d152bb05 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a + uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf with: path: "~/.pub-cache/hosted" key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable" @@ -29,7 +29,7 @@ jobs: os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest - name: Setup Dart SDK - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 + uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: stable - id: checkout @@ -44,7 +44,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a + uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf with: path: "~/.pub-cache/hosted" key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.6.0;packages:source_gen;commands:analyze_1" @@ -54,7 +54,7 @@ jobs: os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest - name: Setup Dart SDK - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 + uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: "3.6.0" - id: checkout @@ -74,7 +74,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a + uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf with: path: "~/.pub-cache/hosted" key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:_test_annotations-example-example_usage-source_gen;commands:format-analyze_0" @@ -84,7 +84,7 @@ jobs: os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest - name: Setup Dart SDK - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 + uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: dev - id: checkout @@ -147,7 +147,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a + uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf with: path: "~/.pub-cache/hosted" key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.6.0;packages:example_usage;commands:test_0" @@ -157,7 +157,7 @@ jobs: os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest - name: Setup Dart SDK - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 + uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: "3.6.0" - id: checkout @@ -181,7 +181,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a + uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf with: path: "~/.pub-cache/hosted" key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.6.0;packages:source_gen;commands:test_1" @@ -191,7 +191,7 @@ jobs: os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest - name: Setup Dart SDK - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 + uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: "3.6.0" - id: checkout @@ -215,7 +215,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a + uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf with: path: "~/.pub-cache/hosted" key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:example_usage;commands:test_0" @@ -225,7 +225,7 @@ jobs: os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest - name: Setup Dart SDK - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 + uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: dev - id: checkout @@ -249,7 +249,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a + uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf with: path: "~/.pub-cache/hosted" key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:source_gen;commands:test_1" @@ -259,7 +259,7 @@ jobs: os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest - name: Setup Dart SDK - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 + uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: dev - id: checkout @@ -283,7 +283,7 @@ jobs: runs-on: windows-latest steps: - name: Setup Dart SDK - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 + uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: "3.6.0" - id: checkout @@ -307,7 +307,7 @@ jobs: runs-on: windows-latest steps: - name: Setup Dart SDK - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 + uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: dev - id: checkout diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yml index ab1ac498..1f33ec62 100644 --- a/.github/workflows/no-response.yml +++ b/.github/workflows/no-response.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest if: ${{ github.repository_owner == 'dart-lang' }} steps: - - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e + - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 with: # Don't automatically mark inactive issues+PRs as stale. days-before-stale: -1 diff --git a/analysis_options.yaml b/analysis_options.yaml index e9ca8840..edbe4ba2 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -8,7 +8,6 @@ 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/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/example_usage/test/ensure_build_test.dart b/example_usage/test/ensure_build_test.dart index c324e985..fc659599 100644 --- a/example_usage/test/ensure_build_test.dart +++ b/example_usage/test/ensure_build_test.dart @@ -13,6 +13,6 @@ import 'package:test/test.dart'; void main() { test( 'ensure_build', - () => expectBuildClean(packageRelativeDirectory: 'example_usage'), + () async => expectBuildClean(packageRelativeDirectory: 'example_usage'), ); } diff --git a/source_gen/CHANGELOG.md b/source_gen/CHANGELOG.md index 14b39d41..44514898 100644 --- a/source_gen/CHANGELOG.md +++ b/source_gen/CHANGELOG.md @@ -1,5 +1,7 @@ -## 2.0.0-wip +## 2.0.0 +- **DO NOT PUBLISH**: Only land into `analyzer-element2` branch. + When all clients migrate, replace all APIs. - **Breaking Change**: Change `formatOutput` function to accept a language version parameter. - **Formatting Change**: Generated code will no longer apply any fixes by @@ -8,13 +10,17 @@ - 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: '>=7.2.0 <8.0.0'` +- Support the latest `package:dart_style` +- Add `generateForAnnotatedDirective`, which now must be used instead of + `generateForAnnotatedElement` in order to support annotations on directives + (imports, exports, parts). - `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/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/constants/reader.dart b/source_gen/lib/src/constants/reader.dart index bf53938c..dba2d052 100644 --- a/source_gen/lib/src/constants/reader.dart +++ b/source_gen/lib/src/constants/reader.dart @@ -3,7 +3,7 @@ // BSD-style license that can be found in the LICENSE file. 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'; import '../type_checker.dart'; @@ -268,7 +268,7 @@ class _DartObjectConstant extends ConstantReader { ConstantReader read(String field) { final reader = peek(field); if (reader == null) { - assertHasField(objectValue.type!.element as InterfaceElement, field); + assertHasField2(objectValue.type!.element3 as InterfaceElement2, field); return const _NullConstant(); } return reader; diff --git a/source_gen/lib/src/constants/utils.dart b/source_gen/lib/src/constants/utils.dart index e1846fe4..669664f9 100644 --- a/source_gen/lib/src/constants/utils.dart +++ b/source_gen/lib/src/constants/utils.dart @@ -4,10 +4,12 @@ import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/element2.dart'; /// Throws a [FormatException] if [root] does not have a given field [name]. /// /// Super types [InterfaceElement.supertype] are also checked before throwing. +@Deprecated('Use assertHasField2() instead') void assertHasField(InterfaceElement root, String name) { InterfaceElement? element = root; while (element != null) { @@ -28,6 +30,29 @@ void assertHasField(InterfaceElement root, String name) { ); } +/// Throws a [FormatException] if [root] does not have a given field [name]. +/// +/// Super types [InterfaceElement.supertype] are also checked before throwing. +void assertHasField2(InterfaceElement2 root, String name) { + InterfaceElement2? element = root; + while (element != null) { + final field = element.getField2(name); + if (field != null) { + return; + } + element = element.supertype?.element3; + } + final allFields = { + ...root.fields2, + for (var t in root.allSupertypes) ...t.element3.fields2, + }; + + throw FormatException( + 'Class ${root.name3} does not have field "$name".', + 'Fields: \n - ${allFields.map((e) => e.name3).join('\n - ')}', + ); +} + /// Returns whether or not [object] is or represents a `null` value. bool isNullLike(DartObject? object) => object?.isNull != false; diff --git a/source_gen/lib/src/generator_for_annotation.dart b/source_gen/lib/src/generator_for_annotation.dart index 5a4bb715..f8300514 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,30 @@ abstract class GeneratorForAnnotation extends Generator { ConstantReader annotation, BuildStep buildStep, ) {} + + /// Implement to return source code to generate for [directive]: + /// - [LibraryImport] + /// - [LibraryExport] + /// - [PartInclude] + /// + /// 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..33a8958f 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, { @@ -107,11 +141,21 @@ class LibraryReader { /// /// Unlike [LibraryElement.getClass], this also correctly traverses /// identifiers that are accessible via one or more `export` directives. + @Deprecated('Use findType2() instead') ClassElement? findType(String name) { final type = element.exportNamespace.get(name); return type is ClassElement ? type : null; } + /// 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. + ClassElement2? findType2(String name) { + final type = element.exportNamespace.get2(name); + return type is ClassElement2 ? 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 diff --git a/source_gen/lib/src/output_helpers.dart b/source_gen/lib/src/output_helpers.dart index 2e028b40..082a65da 100644 --- a/source_gen/lib/src/output_helpers.dart +++ b/source_gen/lib/src/output_helpers.dart @@ -11,6 +11,7 @@ Stream normalizeGeneratorOutput(Object? value) { if (value == null) { return const Stream.empty(); } else if (value is Future) { + // ignore:discarded_futures return StreamCompleter.fromFuture(value.then(normalizeGeneratorOutput)); } else if (value is String) { value = [value]; diff --git a/source_gen/lib/src/span_for_element.dart b/source_gen/lib/src/span_for_element.dart index 6be36f2e..53246d6a 100644 --- a/source_gen/lib/src/span_for_element.dart +++ b/source_gen/lib/src/span_for_element.dart @@ -97,9 +97,9 @@ SourceSpan spanForElement2(Element2 element, [SourceFile? file]) { /// Returns a source span that spans the location where [node] is written. SourceSpan spanForNode(AstNode node) { final unit = node.thisOrAncestorOfType()!; - final element = unit.declaredElement!; - final contents = element.source.contents.data; - final url = assetToPackageUrl(element.source.uri); + final unitFragment = unit.declaredFragment!; + final contents = unitFragment.source.contents.data; + final url = assetToPackageUrl(unitFragment.source.uri); final file = SourceFile.fromString(contents, url: url); return file.span(node.offset, node.offset + node.length); } diff --git a/source_gen/lib/src/type_checker.dart b/source_gen/lib/src/type_checker.dart index b504d1c6..5f5d4083 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* { @@ -307,8 +307,8 @@ abstract class TypeChecker { /// Returns `true` if [staticType] can be assigned to this type. bool isAssignableFromType(DartType staticType) { - final element = staticType.element; - return element != null && isAssignableFrom(element); + final element = staticType.element3; + return element != null && isAssignableFrom2(element); } /// Returns `true` if representing the exact same class as [element]. @@ -322,9 +322,9 @@ abstract class TypeChecker { /// This will always return false for types without a backingclass such as /// `void` or function types. bool isExactlyType(DartType staticType) { - final element = staticType.element; + final element = staticType.element3; if (element != null) { - return isExactly(element); + return isExactly2(element); } else { return false; } @@ -350,11 +350,31 @@ abstract class TypeChecker { return false; } + /// Returns `true` if representing a super class of [element]. + /// + /// This check only takes into account the *extends* hierarchy. If you wish + /// to check mixins and interfaces, use [isAssignableFrom]. + bool isSuperOf2(Element2 element) { + if (element is InterfaceElement2) { + var theSuper = element.supertype; + + do { + if (isExactlyType(theSuper!)) { + return true; + } + + theSuper = theSuper.superclass; + } while (theSuper != null); + } + + return false; + } + /// Returns `true` if representing a super type of [staticType]. /// /// This only takes into account the *extends* hierarchy. If you wish /// to check mixins and interfaces, use [isAssignableFromType]. - bool isSuperTypeOf(DartType staticType) => isSuperOf(staticType.element!); + bool isSuperTypeOf(DartType staticType) => isSuperOf2(staticType.element3!); } // Checks a static type against another static type; @@ -363,6 +383,7 @@ class _LibraryTypeChecker extends TypeChecker { const _LibraryTypeChecker(this._type) : super._(); + @Deprecated('Use isExactly2() instead') @override bool isExactly(Element element) => element is InterfaceElement && element == _type.element; @@ -372,7 +393,7 @@ class _LibraryTypeChecker extends TypeChecker { element is InterfaceElement2 && element == _type.element3; @override - String toString() => urlOfElement(_type.element!); + String toString() => urlOfElement2(_type.element3!); } // Checks a runtime type against a static type. @@ -473,7 +494,7 @@ class UnresolvedAnnotationException implements Exception { annotatedElement.session!.getParsedLibraryByElement2( annotatedElement.library2!, ) as ParsedLibraryResult; - final declaration = parsedLibrary.getElementDeclaration2( + final declaration = parsedLibrary.getFragmentDeclaration( annotatedElement.firstFragment, ); if (declaration == null) { diff --git a/source_gen/lib/src/utils.dart b/source_gen/lib/src/utils.dart index 0f258e60..c01a8616 100644 --- a/source_gen/lib/src/utils.dart +++ b/source_gen/lib/src/utils.dart @@ -6,6 +6,7 @@ import 'dart:io'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/element2.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:build/build.dart'; import 'package:path/path.dart' as p; @@ -20,20 +21,20 @@ import 'package:yaml/yaml.dart'; /// typedef VoidFunc = void Function(); /// ``` /// -/// This function will return `'VoidFunc'`, unlike [DartType.element]`.name`. +/// This function will return `'VoidFunc'`, unlike [DartType.element3]`.name3`. String typeNameOf(DartType type) { - final aliasElement = type.alias?.element; + final aliasElement = type.alias?.element2; if (aliasElement != null) { - return aliasElement.name; + return aliasElement.name3!; } if (type is DynamicType) { return 'dynamic'; } if (type is InterfaceType) { - return type.element.name; + return type.element3.name3!; } if (type is TypeParameterType) { - return type.element.name; + return type.element3.name3!; } throw UnimplementedError('(${type.runtimeType}) $type'); } @@ -64,6 +65,14 @@ String urlOfElement(Element element) => element.kind == ElementKind.DYNAMIC .replace(fragment: element.name) .toString(); +/// Returns a URL representing [element]. +String urlOfElement2(Element2 element) => element.kind == ElementKind.DYNAMIC + ? 'dart:core#dynamic' + // using librarySource.uri – in case the element is in a part + : normalizeUrl(element.library2!.uri) + .replace(fragment: element.name3) + .toString(); + Uri normalizeUrl(Uri url) => switch (url.scheme) { 'dart' => normalizeDartUrl(url), 'package' => _packageToAssetUrl(url), diff --git a/source_gen/pubspec.yaml b/source_gen/pubspec.yaml index 0487df05..182e3787 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: '>=7.2.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 @@ -29,7 +29,14 @@ dev_dependencies: test: ^1.16.0 dependency_overrides: - analyzer: ^7.1.0 + _fe_analyzer_shared: + git: + url: https://github.com/dart-lang/sdk.git + path: pkg/_fe_analyzer_shared + analyzer: + git: + url: https://github.com/dart-lang/sdk.git + path: pkg/analyzer build: git: url: https://github.com/dart-lang/build.git diff --git a/source_gen/test/builder_test.dart b/source_gen/test/builder_test.dart index 87261476..70ac3dc9 100644 --- a/source_gen/test/builder_test.dart +++ b/source_gen/test/builder_test.dart @@ -174,7 +174,7 @@ $dartFormatWidth test( 'Simple Generator test for library', - () => _generateTest( + () async => _generateTest( const CommentGenerator(forClasses: false, forLibrary: true), _testGenPartContentForLibrary, ), @@ -182,7 +182,7 @@ $dartFormatWidth test( 'Simple Generator test for classes and library', - () => _generateTest( + () async => _generateTest( const CommentGenerator(forLibrary: true), _testGenPartContentForClassesAndLibrary, ), diff --git a/source_gen/test/constants/utils_test.dart b/source_gen/test/constants/utils_test.dart index 24220a1b..d1d96e81 100644 --- a/source_gen/test/constants/utils_test.dart +++ b/source_gen/test/constants/utils_test.dart @@ -35,16 +35,19 @@ void main() { test('should not throw when a class contains a field', () { final $A = testLib.getClass('A')!; + // ignore: deprecated_member_use_from_same_package expect(() => assertHasField($A, 'a'), returnsNormally); }); test('should not throw when a super class contains a field', () { final $B = testLib.getClass('B')!; + // ignore: deprecated_member_use_from_same_package expect(() => assertHasField($B, 'a'), returnsNormally); }); test('should throw when a class does not contain a field', () { final $C = testLib.getClass('C')!; + // ignore: deprecated_member_use_from_same_package expect(() => assertHasField($C, 'a'), throwsFormatException); }); }); diff --git a/source_gen/test/constants_test.dart b/source_gen/test/constants_test.dart index fe97e07c..2e5dbe5b 100644 --- a/source_gen/test/constants_test.dart +++ b/source_gen/test/constants_test.dart @@ -155,7 +155,7 @@ void main() { test('should read a Type', () { expect(constants[11].isType, isTrue); - expect(constants[11].typeValue.element!.name, 'DateTime'); + expect(constants[11].typeValue.element3!.name3, 'DateTime'); expect(constants[11].isLiteral, isFalse); expect(() => constants[11].literalValue, throwsFormatException); }); diff --git a/source_gen/test/external_only_type_checker_test.dart b/source_gen/test/external_only_type_checker_test.dart index 76ca1cb3..78a424cb 100644 --- a/source_gen/test/external_only_type_checker_test.dart +++ b/source_gen/test/external_only_type_checker_test.dart @@ -37,7 +37,7 @@ void main() { inputId: AssetId('source_gen', 'test/example.dart'), ); - staticNonPublic = thisTest.findType('NonPublic')!.instantiate( + staticNonPublic = thisTest.findType2('NonPublic')!.instantiate( typeArguments: const [], nullabilitySuffix: NullabilitySuffix.none, ); @@ -53,7 +53,7 @@ void main() { expect( checkNonPublic().isExactlyType(staticNonPublic), isTrue, - reason: '${checkNonPublic()} != ${staticNonPublic.element.name}', + reason: '${checkNonPublic()} != ${staticNonPublic.element3.name3}', ); }); @@ -62,7 +62,7 @@ void main() { checkNonPublic().isAssignableFromType(staticNonPublic), isTrue, reason: '${checkNonPublic()} is not assignable from ' - '${staticNonPublic.element.name}', + '${staticNonPublic.element3.name3}', ); }); }); diff --git a/source_gen/test/generator_for_annotation_test.dart b/source_gen/test/generator_for_annotation_test.dart index 5bf4f137..c140235d 100644 --- a/source_gen/test/generator_for_annotation_test.dart +++ b/source_gen/test/generator_for_annotation_test.dart @@ -8,7 +8,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 +25,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 +36,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.name3}'; + }, + ); final builder = LibraryBuilder(generator); await testBuilder( builder, @@ -65,13 +70,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 +103,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 +131,7 @@ $dartFormatWidth final builder = LibraryBuilder( _StubGenerator( 'Deprecated', - (element) => '// ${element.displayName}', + elementBehavior: (element) => '// ${element.displayName}', ), ); await testBuilder( @@ -142,12 +157,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 +226,7 @@ $dartFormatWidth final builder = LibraryBuilder( _StubGenerator( 'Deprecated', - (element) => '// ${element.displayName}', + elementBehavior: (element) => '// ${element.displayName}', throwOnUnresolved: false, ), ); @@ -192,20 +249,36 @@ $dartFormatWidth class _StubGenerator extends GeneratorForAnnotation { final String _name; - final Object? Function(Element) _behavior; + final Object? Function(ElementDirective) directiveBehavior; + final Object? Function(Element2) 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( - Element element, + Object? generateForAnnotatedElement2( + Element2 element, ConstantReader annotation, BuildStep buildStep, ) => - _behavior(element); + elementBehavior(element); @override String toString() => _name; + + static Null _returnNull(Object _) => null; } const _inputMap = { @@ -244,7 +317,7 @@ class _TestingResolver implements ReleasableResolver { } @override - Future libraryFor( + Future libraryFor2( AssetId assetId, { bool allowSyntaxErrors = false, }) async { diff --git a/source_gen/test/library/find_type_test.dart b/source_gen/test/library/find_type_test.dart index 354140e4..fcbc79cb 100644 --- a/source_gen/test/library/find_type_test.dart +++ b/source_gen/test/library/find_type_test.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_test/build_test.dart'; import 'package:source_gen/source_gen.dart'; import 'package:test/test.dart'; @@ -47,24 +47,24 @@ void main() { }); test('should return a type not exported', () { - expect(library.findType('Example'), _isClassElement); + expect(library.findType2('Example'), _isClassElement); }); test('should return a type from a part', () { - expect(library.findType('PartClass'), _isClassElement); + expect(library.findType2('PartClass'), _isClassElement); }); test('should return a type exported from dart:', () { - expect(library.findType('LinkedHashMap'), _isClassElement); + expect(library.findType2('LinkedHashMap'), _isClassElement); }); test('should return a type exported from package:', () { - expect(library.findType('Generator'), _isClassElement); + expect(library.findType2('Generator'), _isClassElement); }); test('should not return a type imported', () { - expect(library.findType('Stream'), isNull); + expect(library.findType2('Stream'), isNull); }); } -const _isClassElement = TypeMatcher(); +const _isClassElement = TypeMatcher(); diff --git a/source_gen/test/type_checker_test.dart b/source_gen/test/type_checker_test.dart index c6901030..82a3eb63 100644 --- a/source_gen/test/type_checker_test.dart +++ b/source_gen/test/type_checker_test.dart @@ -11,6 +11,7 @@ library; import 'dart:collection'; import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/element2.dart'; import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:build/build.dart'; @@ -91,28 +92,28 @@ void main() { ); staticEnumChecker = TypeChecker.fromStatic(staticEnum); staticEnumMixin = - (testSource.exportNamespace.get('MyEnumMixin')! as InterfaceElement) + (testSource.exportNamespace.get2('MyEnumMixin')! as InterfaceElement2) .instantiate( typeArguments: [], nullabilitySuffix: NullabilitySuffix.none, ); staticEnumMixinChecker = TypeChecker.fromStatic(staticEnumMixin); staticMapMixin = - (testSource.exportNamespace.get('MyMapMixin')! as InterfaceElement) + (testSource.exportNamespace.get2('MyMapMixin')! as InterfaceElement2) .instantiate( typeArguments: [], nullabilitySuffix: NullabilitySuffix.none, ); staticMapMixinChecker = TypeChecker.fromStatic(staticMapMixin); staticMyEnum = - (testSource.exportNamespace.get('MyEnum')! as InterfaceElement) + (testSource.exportNamespace.get2('MyEnum')! as InterfaceElement2) .instantiate( typeArguments: [], nullabilitySuffix: NullabilitySuffix.none, ); - staticMyEnumWithMixin = - (testSource.exportNamespace.get('MyEnumWithMixin')! as InterfaceElement) - .instantiate( + staticMyEnumWithMixin = (testSource.exportNamespace.get2('MyEnumWithMixin')! + as InterfaceElement2) + .instantiate( typeArguments: [], nullabilitySuffix: NullabilitySuffix.none, ); @@ -131,13 +132,13 @@ void main() { nullabilitySuffix: NullabilitySuffix.none, ); - staticGenerator = sourceGen.findType('Generator')!.instantiate( + staticGenerator = sourceGen.findType2('Generator')!.instantiate( typeArguments: [], nullabilitySuffix: NullabilitySuffix.none, ); staticGeneratorChecker = TypeChecker.fromStatic(staticGenerator); staticGeneratorForAnnotation = - sourceGen.findType('GeneratorForAnnotation')!.instantiate( + sourceGen.findType2('GeneratorForAnnotation')!.instantiate( typeArguments: [core.typeProvider.dynamicType], nullabilitySuffix: NullabilitySuffix.none, ); @@ -201,7 +202,7 @@ void main() { () { test('should equal MapMixin class', () { expect(checkMapMixin().isExactlyType(staticMapMixin), isTrue); - expect(checkMapMixin().isExactly(staticMapMixin.element), isTrue); + expect(checkMapMixin().isExactly2(staticMapMixin.element3), isTrue); }); }, onPlatform: const { @@ -214,7 +215,7 @@ void main() { expect( checkMap().isExactlyType(staticMap), isTrue, - reason: '${checkMap()} != ${staticMap.element.name}', + reason: '${checkMap()} != ${staticMap.element3.name3}', ); }); @@ -246,7 +247,7 @@ void main() { test('should be assignable from Map', () { // Using Uri.queryParameters to get a Map final stringStringMapType = - staticUri.getGetter('queryParameters')!.returnType; + staticUri.getGetter2('queryParameters')!.returnType; expect(checkMap().isAssignableFromType(stringStringMapType), isTrue); expect(checkMap().isExactlyType(stringStringMapType), isTrue); @@ -276,7 +277,7 @@ void main() { expect( checkGenerator().isExactlyType(staticGenerator), isTrue, - reason: '${checkGenerator()} != ${staticGenerator.element.name}', + reason: '${checkGenerator()} != ${staticGenerator.element3.name3}', ); }); @@ -285,7 +286,7 @@ void main() { checkGenerator().isSuperTypeOf(staticGenerator), isFalse, reason: '${checkGenerator()} is super of ' - '${staticGenerator.element.name}', + '${staticGenerator.element3.name3}', ); }); @@ -294,7 +295,7 @@ void main() { checkGenerator().isSuperTypeOf(staticGeneratorForAnnotation), isTrue, reason: '${checkGenerator()} is not super of ' - '${staticGeneratorForAnnotation.element.name}', + '${staticGeneratorForAnnotation.element3.name3}', ); }); @@ -303,7 +304,7 @@ void main() { checkGenerator().isAssignableFromType(staticGeneratorForAnnotation), isTrue, reason: '${checkGenerator()} is not assignable from ' - '${staticGeneratorForAnnotation.element.name}', + '${staticGeneratorForAnnotation.element3.name3}', ); }); }); @@ -465,34 +466,34 @@ void main() { test('of a single @A', () { expect($A.hasAnnotationOf($ExampleOfA), isTrue); final aAnnotation = $A.firstAnnotationOf($ExampleOfA)!; - expect(aAnnotation.type!.element!.name, 'A'); + expect(aAnnotation.type!.element3!.name3, 'A'); expect($B.annotationsOf($ExampleOfA), isEmpty); expect($C.annotationsOf($ExampleOfA), isEmpty); }); test('of a multiple @A', () { final aAnnotations = $A.annotationsOf($ExampleOfMultiA); - expect(aAnnotations.map((a) => a.type!.element!.name), ['A', 'A']); + expect(aAnnotations.map((a) => a.type!.element3!.name3), ['A', 'A']); expect($B.annotationsOf($ExampleOfA), isEmpty); expect($C.annotationsOf($ExampleOfA), isEmpty); }); test('of a single @A + single @B', () { final aAnnotations = $A.annotationsOf($ExampleOfAPlusB); - expect(aAnnotations.map((a) => a.type!.element!.name), ['A']); + expect(aAnnotations.map((a) => a.type!.element3!.name3), ['A']); final bAnnotations = $B.annotationsOf($ExampleOfAPlusB); - expect(bAnnotations.map((a) => a.type!.element!.name), ['B']); + expect(bAnnotations.map((a) => a.type!.element3!.name3), ['B']); expect($C.annotationsOf($ExampleOfAPlusB), isEmpty); }); test('of a single @B + single @C', () { final cAnnotations = $C.annotationsOf($ExampleOfBPlusC); - expect(cAnnotations.map((a) => a.type!.element!.name), ['C']); + expect(cAnnotations.map((a) => a.type!.element3!.name3), ['C']); final bAnnotations = $B.annotationsOf($ExampleOfBPlusC); - expect(bAnnotations.map((a) => a.type!.element!.name), ['B', 'C']); + expect(bAnnotations.map((a) => a.type!.element3!.name3), ['B', 'C']); expect($B.hasAnnotationOfExact($ExampleOfBPlusC), isTrue); final bExact = $B.annotationsOfExact($ExampleOfBPlusC); - expect(bExact.map((a) => a.type!.element!.name), ['B']); + expect(bExact.map((a) => a.type!.element3!.name3), ['B']); }); }); @@ -573,28 +574,28 @@ void main() { $A .firstAnnotationOf($ExampleOfA, throwOnUnresolved: false)! .type! - .element! - .name, + .element3! + .name3, 'A', ); expect( $A .annotationsOf($ExampleOfA, throwOnUnresolved: false) - .map((a) => a.type!.element!.name), + .map((a) => a.type!.element3!.name3), ['A'], ); expect( $A .firstAnnotationOfExact($ExampleOfA, throwOnUnresolved: false)! .type! - .element! - .name, + .element3! + .name3, 'A', ); expect( $A .annotationsOfExact($ExampleOfA, throwOnUnresolved: false) - .map((a) => a.type!.element!.name), + .map((a) => a.type!.element3!.name3), ['A'], ); });