Skip to content

Commit

Permalink
Add APIs to expose analyzer elements2 models. (dart-lang#735)
Browse files Browse the repository at this point in the history
* publish_to: none

* Directives are not elements anymore, don't have annotations.
  • Loading branch information
scheglov authored Dec 16, 2024
1 parent 49a96d8 commit 117ab26
Show file tree
Hide file tree
Showing 19 changed files with 395 additions and 124 deletions.
1 change: 1 addition & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion example/lib/src/member_count_library_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
''';
}
Expand Down
8 changes: 4 additions & 4 deletions example/lib/src/multiplier_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@
// 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';

import '../annotations.dart';

class MultiplierGenerator extends GeneratorForAnnotation<Multiplier> {
@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;';
}
}
3 changes: 2 additions & 1 deletion example/lib/src/property_product_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 '''
Expand Down
3 changes: 2 additions & 1 deletion example/lib/src/property_sum_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 '''
Expand Down
8 changes: 4 additions & 4 deletions example/lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<TopLevelVariableElement> topLevelNumVariables(LibraryReader reader) =>
reader.allElements.whereType<TopLevelVariableElement>().where(
Iterable<TopLevelVariableElement2> topLevelNumVariables(LibraryReader reader) =>
reader.allElements2.whereType<TopLevelVariableElement2>().where(
(element) =>
element.type.isDartCoreNum ||
element.type.isDartCoreInt ||
Expand Down
2 changes: 1 addition & 1 deletion source_gen/lib/source_gen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
14 changes: 9 additions & 5 deletions source_gen/lib/src/builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<void> _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.
//
Expand Down Expand Up @@ -353,11 +357,11 @@ class LibraryBuilder extends _Builder {
}

Stream<GeneratedOutput> _generate(
LibraryElement library,
LibraryElement2 library2,
List<Generator> 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';
Expand Down
18 changes: 15 additions & 3 deletions source_gen/lib/src/generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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.
///
Expand All @@ -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() {
Expand Down
33 changes: 31 additions & 2 deletions source_gen/lib/src/generator_for_annotation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -58,11 +59,16 @@ abstract class GeneratorForAnnotation<T> 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);
Expand Down Expand Up @@ -93,5 +99,28 @@ abstract class GeneratorForAnnotation<T> 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,
) {}
}
89 changes: 61 additions & 28 deletions source_gen/lib/src/library.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<Element> get allElements => [
Expand All @@ -42,6 +49,22 @@ class LibraryReader {
...element.definingCompilationUnit.parts,
];

/// All of the declarations in this library.
Iterable<Element2> get allElements2 => [element2, ...element2.children2];

/// All of the elements representing classes in this library.
Iterable<ClassElement> get classes =>
element.units.expand((cu) => cu.classes);

/// All of the elements representing classes in this library.
Iterable<ClassElement2> get classes2 => element2.classes;

/// All of the elements representing enums in this library.
Iterable<EnumElement> get enums => element.units.expand((cu) => cu.enums);

/// All of the elements representing enums in this library.
Iterable<EnumElement2> get enums2 => element2.enums;

/// All of the declarations in this library annotated with [checker].
Iterable<AnnotatedElement> annotatedWith(
TypeChecker checker, {
Expand All @@ -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);
}
}
}
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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');
}
Expand All @@ -162,11 +202,4 @@ class LibraryReader {
fromSegments[0] == toSegments[0] &&
fromSegments[1] == toSegments[1];
}

/// All of the elements representing classes in this library.
Iterable<ClassElement> get classes =>
element.units.expand((cu) => cu.classes);

/// All of the elements representing enums in this library.
Iterable<EnumElement> get enums => element.units.expand((cu) => cu.enums);
}
Loading

0 comments on commit 117ab26

Please sign in to comment.