LogoDart Code Generation

Analyzer API Reference#

Source of truth: analyzer package v10.0.1, used by dart_mappable_builder, json_serializable, and retrofit_generator. Import: package:analyzer/dart/element/element.dart and package:analyzer/dart/element/type.dart


Getting a Library#

In a Builder, the entry point is always the LibraryElement:

// Via BuildStep (most common)
final LibraryElement library = await buildStep.inputLibrary;

// Via Resolver (for other libraries)
final LibraryElement lib = await buildStep.resolver.libraryFor(
  AssetId('other_pkg', 'lib/other.dart'),
);

// Via source_gen's LibraryReader (wraps LibraryElement)
final reader = LibraryReader(library);

Element Hierarchy#

Element
├── LibraryElement          — a .dart file (or library directive)
├── ClassElementclass, abstract class, base class, interface, mixin, enum
│   ├── fields: List<FieldElement>
│   ├── methods: List<MethodElement>
│   ├── constructors: List<ConstructorElement>
│   ├── accessors: List<PropertyAccessorElement>
│   └── typeParameters: List<TypeParameterElement>
├── FieldElement            — a field or variable inside a class
├── MethodElement           — a method inside a class
├── ConstructorElement      — a constructor
├── ParameterElement        — a function/constructor parameter
│   ├── FieldFormalParameterElementthis.fieldName parameter
│   └── SuperFormalParameterElementsuper.fieldName parameter
├── PropertyAccessorElement — getter or setter
├── TypeAliasElementtypedef
├── FunctionElement         — top-level function
├── TopLevelVariableElement — top-level variable
└── TypeParameterElement    — generic type parameter <T>

LibraryElement#

abstract class LibraryElement implements Element {
  // All top-level elements: classes, functions, variables, typedefs
  List<Element> get topLevelElements;

  // Only declared class elements (does NOT include enums — use LibraryReader.enums for those)
  List<ClassElement> get classes;

  // Unit contains the AST for the library
  CompilationUnit get unit;

  // Package:uri identifier
  String get identifier;

  // Exported elements (what's visible to importers)
  List<ExportElement> get libraryExports;

  // All imported libraries
  List<ImportElement> get libraryImports;

  // Whether this is a part file (part of another library)
  bool get isPart;
}

Iterating top-level elements:

final library = await buildStep.inputLibrary;

// All classes (including abstract, enums, mixins)
for (final element in library.topLevelElements.whereType<ClassElement>()) {
  if (element.isEnum) { ... }
  if (element.isMixin) { ... }
  if (element.isAbstract) { ... }
}

ClassElement#

abstract class ClassElement implements Element {
  // Identity
  String get name;          // 'MyClass'
  String get displayName;   // 'MyClass' or 'MyClass<T>'

  // Members
  List<FieldElement> get fields;
  List<MethodElement> get methods;
  List<ConstructorElement> get constructors;
  List<PropertyAccessorElement> get accessors;

  // Type parameters
  List<TypeParameterElement> get typeParameters;  // [T, U]

  // Inheritance
  InterfaceType? get supertype;           // extends X
  List<InterfaceType> get mixins;         // with A, B
  List<InterfaceType> get interfaces;     // implements C, D
  InterfaceType get thisType;             // This class as an InterfaceType

  // Classification
  bool get isAbstract;
  bool get isEnum;
  bool get isMixin;
  bool get isMixinClass;
  bool get isSealed;
  bool get isFinal;     // final class
  bool get isBase;      // base class
  bool get isInterface; // interface

  // Find members
  FieldElement? getField(String name);
  MethodElement? getMethod(String name);
  ConstructorElement? getNamedConstructor(String name);
  PropertyAccessorElement? getGetter(String name);
  PropertyAccessorElement? getSetter(String name);

  // Annotations
  List<ElementAnnotation> get metadata;
}

ClassElement: finding the default constructor#

// Get default (unnamed) constructor
final defaultCtor = element.constructors
    .firstWhereOrNull((c) => c.isDefaultConstructor);

// Get named constructor
final fromJsonCtor = element.getNamedConstructor('fromJson');

// Get all non-factory constructors
final regularCtors = element.constructors.where((c) => !c.isFactory);

FieldElement#

abstract class FieldElement implements Element {
  // Name and type
  String? get name;
  DartType get type;

  // Access
  PropertyAccessorElement? get getter;
  PropertyAccessorElement? get setter;

  // Modifiers
  bool get isFinal;
  bool get isConst;
  bool get isStatic;
  bool get isLate;
  bool get isAbstract;
  bool get isExternal;
  bool get isSynthetic;  // true for fields created by constructor initializers

  // Default value (as source code string, e.g. "'hello'" or "42")
  String? get defaultValueCode;

  // Annotations
  List<ElementAnnotation> get metadata;
}

Iterating class fields:

for (final field in classElement.fields) {
  if (field.isStatic) continue;    // Skip static fields
  if (field.isSynthetic) continue; // Skip synthetic getters
  if (!field.isPublic) continue;   // Skip private fields

  final typeName = field.type.getDisplayString(withNullability: true);
  final isNullable = field.type.nullabilitySuffix == NullabilitySuffix.question;
}

MethodElement#

abstract class MethodElement implements Element {
  String? get name;
  DartType get returnType;
  List<ParameterElement> get formalParameters;
  List<TypeParameterElement> get typeParameters;

  bool get isAbstract;
  bool get isStatic;
  bool get isExternal;
  bool get isAsyncGenerator;  // async*
  bool get isGenerator;       // sync*

  List<ElementAnnotation> get metadata;
}

ParameterElement#

abstract class ParameterElement implements Element {
  String? get name;
  DartType get type;

  // Parameter kind
  bool get isRequiredPositional;   // f(String x)
  bool get isOptionalPositional;   // f([String x])
  bool get isRequiredNamed;        // f({required String x})
  bool get isOptionalNamed;        // f({String? x})
  bool get isNamed;                // isRequiredNamed || isOptionalNamed

  // Default value as source code string
  String? get defaultValueCode;    // e.g. "'hello'" or "42"

  List<ElementAnnotation> get metadata;
}

// Specializations
abstract class FieldFormalParameterElement extends ParameterElement {
  // this.fieldName in constructor
  FieldElement? get field;
}

abstract class SuperFormalParameterElement extends ParameterElement {
  // super.fieldName in constructor
  ParameterElement? get superConstructorParameter;
}

ConstructorElement#

abstract class ConstructorElement implements Element {
  String? get name;        // null for default, 'fromJson' for named
  bool get isDefaultConstructor;  // true if name is null/empty
  bool get isFactory;
  bool get isConst;
  bool get isAbstract;

  List<ParameterElement> get formalParameters;

  ClassElement get enclosingElement;  // The class this constructor belongs to

  // Redirect target (for factory redirecting constructors)
  ConstructorElement? get redirectedConstructor;
}

TypeParameterElement#

abstract class TypeParameterElement implements Element {
  String? get name;    // 'T', 'K', etc.
  DartType? get bound; // bound in 'T extends Comparable<T>'
}

ElementAnnotation#

// From element.metadata
abstract class ElementAnnotation {
  // Compute the constant value (evaluate the annotation expression)
  DartObject? computeConstantValue();

  // The element (constructor or variable) being used as annotation
  Element? get element;

  // Check if it's a specific annotation
  bool isFromPackage(String packageName);
}

Reading annotation values:

// With TypeChecker (preferred — handles inheritance)
import 'package:source_gen/source_gen.dart';
final checker = TypeChecker.fromRuntime(MyAnnotation);

// Get first annotation of a type on an element
final DartObject? obj = checker.firstAnnotationOf(element);

// Get all annotations of a type
for (final annotation in checker.annotationsOf(element)) {
  // process annotation
}

// Read field values from DartObject
final stringVal = obj?.getField('name')?.toStringValue();
final intVal = obj?.getField('count')?.toIntValue();
final boolVal = obj?.getField('enabled')?.toBoolValue();
final doubleVal = obj?.getField('factor')?.toDoubleValue();
final listVal = obj?.getField('items')?.toListValue();
final mapVal = obj?.getField('mapping')?.toMapValue();
final typeVal = obj?.getField('type')?.toTypeValue();
// Enum field: access the enum string via revive
final enumAccessor = obj?.getField('myEnum')?.variable?.name;

DartType Hierarchy#

// package:analyzer/dart/element/type.dart
DartType
├── InterfaceTypeclass/interface/mixin/enum types
├── TypeParameterTypeT, K, V (unresolved generic parameter)
├── FunctionTypeFunction(int) => String
├── RecordType(int, String, {bool flag})
├── DynamicTypedynamic
├── VoidTypevoid
├── NeverTypeNever
└── InvalidType           — error in source code

DartType — Common Properties#

abstract class DartType {
  Element? get element;      // ClassElement for InterfaceType, etc.

  // Nullability
  NullabilitySuffix get nullabilitySuffix;
  // NullabilitySuffix.question → T?
  // NullabilitySuffix.none → T (non-nullable)
  // NullabilitySuffix.star → T* (legacy/pre-NNBD)

  // Quick checks
  bool get isDynamic;
  bool get isVoid;
  bool get isBottom;         // Never

  // Get display string
  // withNullability defaults to true and is deprecated as a parameter;
  // use promoteToNonNull.getDisplayString() to strip '?'
  String getDisplayString({@Deprecated('...') bool withNullability = true});
  // getDisplayString()                      → 'List<String?>' (includes nullability)
  // promoteToNonNull.getDisplayString()     → 'List<String>'  (strip nullability)

  // Strip nullability
  DartType get promoteToNonNull;
}

InterfaceType#

abstract class InterfaceType implements DartType {
  ClassElement get element;               // The class definition

  // Generic type arguments: List<String> → [String]
  List<DartType> get typeArguments;

  // Inheritance
  InterfaceType? get superclass;
  List<InterfaceType> get interfaces;
  List<InterfaceType> get mixins;

  // Access class members
  List<MethodElement> get methods;
  List<PropertyAccessorElement> get accessors;

  // Substitute type parameters
  InterfaceType? asInstanceOf(ClassElement superclass);
  // Gets how this type implements superclass with concrete type args
  // e.g. List<String>.asInstanceOf(Iterable) → Iterable<String>

  // Check if is a specific class
  bool get isDartCoreList;
  bool get isDartCoreMap;
  bool get isDartCoreSet;
  bool get isDartCoreIterable;
  bool get isDartCoreString;
  bool get isDartCoreInt;
  bool get isDartCoreDouble;
  bool get isDartCoreNum;
  bool get isDartCoreBool;
  bool get isDartCoreObject;
  bool get isDartCoreNull;
  bool get isDartAsyncFuture;
  bool get isDartAsyncFutureOr;
  bool get isDartAsyncStream;
}

TypeParameterType#

abstract class TypeParameterType implements DartType {
  TypeParameterElement get element;  // The type parameter definition
  DartType get bound;                // The declared bound (or Object if none)
}

FunctionType#

abstract class FunctionType implements DartType {
  DartType get returnType;
  List<ParameterElement> get parameters;
  List<TypeParameterElement> get typeFormals;  // Generic type params: <T>() => T
}

RecordType (Dart 3.0+)#

abstract class RecordType implements DartType {
  List<RecordTypePositionalField> get positionalFields;  // (int, String)
  List<RecordTypeNamedField> get namedFields;            // {bool flag}
}

abstract class RecordTypePositionalField {
  DartType get type;
}

abstract class RecordTypeNamedField {
  String get name;
  DartType get type;
}

Nullability#

// Check if a type is nullable
final isNullable = type.nullabilitySuffix == NullabilitySuffix.question;

// Get the non-nullable version
final nonNullType = type.promoteToNonNull;

// Display with '?' (default — withNullability parameter is deprecated)
final display = type.getDisplayString();                      // 'String?'
// Display without '?'
final displayNoNull = type.promoteToNonNull.getDisplayString(); // 'String'

InheritanceManager3#

Used to find inherited concrete members (from superclasses and mixins):

import 'package:analyzer/src/dart/element/inheritance_manager3.dart';

final manager = InheritanceManager3();

// Get all concrete members this class inherits.
// Accepts InterfaceElement (ClassElement is a subtype — both work).
final inheritedMap = manager.getInheritedConcreteMap(classElement);
// Key: Name (contains String name + optional library URI for private names)
// Value: ExecutableElement (MethodElement or PropertyAccessorElement/GetterElement)

for (final MapEntry(key: _, value: member) in inheritedMap.entries) {
  if (member is GetterElement) {
    final variable = member.variable;
    if (variable is FieldElement && !variable.isStatic) {
      // process inherited field
    }
  }
}

Note: InheritanceManager3 is in package:analyzer/src/ (internal API). It's used by json_serializable for createSortedFieldSet().


Getting Fields Including Inherited#

json_serializable pattern for getting all fields including inherited ones:

// InheritanceManager3.getInheritedConcreteMap takes InterfaceElement;
// ClassElement is a subtype so it can be passed directly.
Map<String, FieldElement> _getAllFields(ClassElement element) {
  // Direct instance fields
  final ownFields = {
    for (final f in element.fields)
      if (!f.isStatic) f.name!: f,
  };

  // Inherited concrete fields via InheritanceManager3
  final manager = InheritanceManager3();
  final inheritedMap = manager.getInheritedConcreteMap(element);
  final inheritedFields = <String, FieldElement>{};
  for (final member in inheritedMap.values) {
    if (member is GetterElement) {
      final variable = member.variable;
      if (variable is FieldElement) {
        inheritedFields[variable.name!] = variable;
      }
    }
  }

  return {...inheritedFields, ...ownFields}; // own overrides inherited
}

Reading AST Nodes (Source-Level Analysis)#

For cases where constant evaluation isn't enough (e.g., reading complex annotation expressions), you can get the AST node:

// Via Resolver — astNodeFor takes a Fragment (use element.firstFragment)
final astNode = await buildStep.resolver.astNodeFor(
  element.firstFragment,
  resolve: true,
);

// The AstNode type depends on the element:
// ClassElement    → ClassDeclaration     (via element.firstFragment)
// FieldElement    → VariableDeclaration  (via element.firstFragment)
// MethodElement   → MethodDeclaration    (via element.firstFragment)
// ParameterElement → FormalParameter    (via element.firstFragment)

// Cast to specific types
if (astNode is ClassDeclaration) {
  final members = astNode.members;
  final metadata = astNode.metadata;
}

Type Checking Utilities#

Used extensively to check type relationships:

import 'package:source_gen/source_gen.dart';

// Check if an element is of a specific type
final listChecker = TypeChecker.fromRuntime(List);
final isExactlyList = listChecker.isExactlyType(type);      // is exactly List
final isAssignableToList = listChecker.isAssignableFromType(type);  // is List or subtype
final isSuperOfList = listChecker.isSuperTypeOf(type);      // is supertype of List

// SDK types need special handling
final intChecker = TypeChecker.typeNamed(int, inPackage: 'core', inSdk: true);
final futureChecker = TypeChecker.typeNamed(Future, inPackage: 'async', inSdk: true);

// Direct InterfaceType checks (no TypeChecker needed)
if (type is InterfaceType) {
  type.isDartCoreList    // type is List<...>
  type.isDartCoreMap     // type is Map<...>
  type.isDartAsyncFuture // type is Future<...>
}

Traversing Type Arguments#

// Get the inner type of List<T>, Future<T>, etc.
DartType? getInnerType(DartType type) {
  if (type is InterfaceType && type.typeArguments.isNotEmpty) {
    return type.typeArguments.first;
  }
  return null;
}

// Get K and V from Map<K, V>
(DartType, DartType)? getMapTypes(DartType type) {
  if (type is InterfaceType &&
      type.isDartCoreMap &&
      type.typeArguments.length == 2) {
    return (type.typeArguments[0], type.typeArguments[1]);
  }
  return null;
}

// Get display string for a type with proper nullability
String displayType(DartType type) =>
    type.getDisplayString(withNullability: true);

Resolving Inherited Generic Types#

When a subclass inherits from a generic superclass, use asInstanceOf to get concrete type arguments:

// class MyList extends List<String>
// Resolving type of inherited `length` getter:
final myListType = classElement.thisType;
final listType = myListType.asInstanceOf(listElement);
// listType is List<String> — the concrete parameterization

Common Imports#

import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/constant/value.dart';  // DartObject
import 'package:analyzer/dart/ast/ast.dart';          // AstNode, ClassDeclaration, etc.
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';  // InheritanceManager3