blob: 5464d1213bce2a72dabbc57c4609ccecbc7190e0 [file] [log] [blame] [view]
# The Element Model
The element model, together with the [type][type] model, describes the semantic
(as opposed to syntactic) structure of Dart code. The syntactic structure of the
code is modeled by the [AST][ast].
Generally speaking, an element represents something that is declared in the
code, such as a class, method, or variable. Elements can be explicitly declared,
such as the class defined by a class declaration, or implicitly declared, such
as the default constructor defined for classes that do not have any explicit
constructor declarations. Elements that are implicitly declared are referred to
as _synthetic_ elements.
There are a few elements that represent entities that are not declared. For
example, there is an element representing a compilation unit (`.dart` file) and
another representing a library.
## The Structure of the Element Model
Elements are organized in a tree structure in which the children of an element
are the elements that are logically (and often syntactically) part of the
declaration of the parent. For example, the elements representing the methods
and fields in a class are children of the element representing the class.
Every complete element structure is rooted by an instance of the class
`LibraryElement`. A library element represents a single Dart library. Every
library is defined by one or more compilation units (the library and all of its
parts). The compilation units are represented by the class
`CompilationUnitElement` and are children of the library that is defined by
them. Each compilation unit can contain zero or more top-level declarations,
such as classes, functions, and variables. Each of these is in turn
represented as an element that is a child of the compilation unit. Classes
contain methods and fields, methods can contain local variables, etc.
The element model does not contain everything in the code, only those things
that are declared by the code. For example, it does not include any
representation of the statements in a method body, but if one of those
statements declares a local variable then the local variable will be represented
by an element.
## Getting a Compilation Unit Element
If you have followed the steps in [Performing Analysis][analysis], and you want
to get the compilation unit element for a file at a known `path`, then you can
ask the analysis session for the compilation unit representing that file.
```dart
void analyzeSingleFile(AnalysisSession session, String path) async {
UnitElementResult result = await session.getUnitElement(path);
CompilationUnitElement element = result.element;
}
```
(If you also need the resolved AST for the file, you can ask the session to
`getResolvedAst`, and the returned result will have the library element for the
library containing the compilation unit.)
## Traversing the Structure
There are two ways to traverse the structure of an AST: getters and visitors.
### Getters
Every element defines getters for accessing the parent and the children of that
element. Those getters can be used to traverse the structure, and are often the
most efficient way of doing so. For example, if you wanted to write a utility to
print the names of all of the members of each class in a given compilation unit,
it might look something like this:
```dart
void printMembers(CompilationUnitElement unitElement) {
for (ClassElement classElement in unitElement.types) {
print(classElement.name);
for (ConstructorElement constructorElement in classElement.constructors) {
if (!constructorElement.isSynthetic) {
if (constructorElement.name == null) {
print(' ${constructorElement.name}');
} else {
print(' ${classElement.name}.${constructorElement.name}');
}
}
}
for (FieldElement fieldElement in classElement.fields) {
if (!fieldElement.isSynthetic) {
print(' ${fieldElement.name}');
}
}
for (PropertyAccessorElement accessorElement in classElement.accessors) {
if (!accessorElement.isSynthetic) {
print(' ${accessorElement.name}');
}
}
for (MethodElement methodElement in classElement.methods) {
if (!methodElement.isSynthetic) {
print(' ${methodElement.name}');
}
}
}
}
```
### Visitors
Getters work well for most uses, but there might be times when it is easier to
use a visitor pattern.
Getters work well for cases like the above because compilation units cannot be
nested inside other compilation units, classes cannot be nested inside other
classes, etc. But when you're dealing with a structure that can be nested inside
similar structures (such as functions), then nested loops don't work as well.
For those cases, the analyzer package provides a visitor pattern.
There is a single visitor API, defined by the abstract class `ElementVisitor`.
It defines a separate visit method for each class of element. For example, the
method `visitClassElement` is used to visit a `ClassElement`. If you ask an
element to accept a visitor, it will invoke the corresponding method on the
visitor interface.
If you want to define a visitor, you'll probably want to subclass one of the
concrete implementations of `ElementVisitor`. The concrete subclasses are
defined in `package:analyzer/dart/element/visitor.dart`. A couple of the most
useful include
- `SimpleElementVisitor` which implements every visit method by doing nothing,
- `RecursiveElementVisitor` which will cause every element in a structure to be
visited, and
- `GeneralizingElementVisitor` which makes it easy to visit kinds of nodes, such
as visiting any executable element (method, function, accessor, or
constructor).
As an example, let's assume you want to write some code to compute the largest
number of parameters defined for any function or method in a given structure.
You need to visit every element because functions can be nested inside
functions. But because methods and functions are represented by different
classes of elements, it would be useful to use a visitor that will generalize
both to allow you to just visit executable elements (those that have
parameters). Hence, you'd want to create a subclass of
`GeneralizingElementVisitor`.
```dart
class ParameterCounter extends GeneralizingElementVisitor<void> {
int maxParameterCount = 0;
@override
void visitExecutableElement(ExecutableElement element) {
maxParameterCount = math.max(maxParameterCount, element.parameters.length);
super.visitExecutableElement(element);
}
}
```
[analysis]: analysis.md
[ast]: ast.md
[type]: type.md