Add annotation-based creation location tracking

Closes https://github.com/dart-lang/sdk/pull/63011

GitOrigin-RevId: 926534c2f5d3cd1e9629cf25f6b028ab34c00e64
Change-Id: I88460a060faabeae8f611e88f23b2eb5fc99f21a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/491702
Reviewed-by: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Jens Johansen <jensj@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Daco Harkes <dacoharkes@google.com>
This commit is contained in:
Kilian Schulte
2026-04-28 03:06:03 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent efff5f70c1
commit 9e8fdef17c
58 changed files with 1851 additions and 205 deletions
+23 -11
View File
@@ -12,7 +12,7 @@ import '../verifier.dart';
import 'changed_structure_notifier.dart';
class TargetFlags {
final bool trackWidgetCreation;
final bool trackCreationLocations;
final bool supportMirrors;
/// Whether the backend expects closure contexts to be present in the AST.
@@ -32,7 +32,7 @@ class TargetFlags {
final bool includeUnsupportedPlatformLibraryStubs;
const TargetFlags({
this.trackWidgetCreation = false,
this.trackCreationLocations = false,
this.supportMirrors = true,
this.isClosureContextLoweringEnabled = false,
this.constKeepLocalsIndicator,
@@ -43,7 +43,7 @@ class TargetFlags {
bool operator ==(other) {
if (identical(this, other)) return true;
return other is TargetFlags &&
trackWidgetCreation == other.trackWidgetCreation &&
trackCreationLocations == other.trackCreationLocations &&
supportMirrors == other.supportMirrors &&
includeUnsupportedPlatformLibraryStubs ==
other.includeUnsupportedPlatformLibraryStubs &&
@@ -53,7 +53,7 @@ class TargetFlags {
@override
int get hashCode {
int hash = 485786;
hash = 0x3fffffff & (hash * 31 + (hash ^ trackWidgetCreation.hashCode));
hash = 0x3fffffff & (hash * 31 + (hash ^ trackCreationLocations.hashCode));
hash = 0x3fffffff & (hash * 31 + (hash ^ supportMirrors.hashCode));
hash =
0x3fffffff &
@@ -340,7 +340,11 @@ abstract class Target {
/// This transformation is not applied when compiling full kernel programs to
/// prevent affecting the internal invariants of the compiler and accidentally
/// slowing down compilation.
void performOutlineTransformations(Component component) {}
void performOutlineTransformations(
Component component, {
List<Library>? libraries,
ChangedStructureNotifier? changedStructureNotifier,
}) {}
/// Perform target-specific operations on the [Component] storing the outlines
/// when generating summaries.
@@ -821,7 +825,7 @@ class TestTargetFlags extends TargetFlags {
final Set<String> unsupportedDartLibraries;
const TestTargetFlags({
bool trackWidgetCreation = false,
bool trackCreationLocations = false,
this.forceLateLoweringsForTesting,
this.forceLateLoweringSentinelForTesting,
this.forceStaticFieldLoweringForTesting,
@@ -831,7 +835,7 @@ class TestTargetFlags extends TargetFlags {
this.unsupportedDartLibraries = const {},
bool isClosureContextLoweringEnabled = false,
}) : super(
trackWidgetCreation: trackWidgetCreation,
trackCreationLocations: trackCreationLocations,
isClosureContextLoweringEnabled: isClosureContextLoweringEnabled,
);
}
@@ -1040,8 +1044,16 @@ class TargetWrapper extends Target {
}
@override
void performOutlineTransformations(Component component) {
_target.performOutlineTransformations(component);
void performOutlineTransformations(
Component component, {
List<Library>? libraries,
ChangedStructureNotifier? changedStructureNotifier,
}) {
_target.performOutlineTransformations(
component,
libraries: libraries,
changedStructureNotifier: changedStructureNotifier,
);
}
@override
@@ -1131,10 +1143,10 @@ mixin SummaryMixin on Target {
super.performOutlineComponentOperations(component);
if (!excludeNonSources) return;
List<Library> libraries = new List.of(component.libraries);
List<Library> componentLibraries = new List.of(component.libraries);
component.libraries.clear();
Set<Uri> include = sources.toSet();
for (Library library in libraries) {
for (Library library in componentLibraries) {
if (include.contains(library.importUri)) {
component.libraries.add(library);
} else {
@@ -61,7 +61,6 @@ void _maybeAddCreationLocationArgument(
Arguments arguments,
FunctionNode function,
Expression creationLocation,
Class locationClass,
) {
if (_hasNamedArgument(arguments, _creationLocationParameterName)) {
return;
@@ -120,15 +119,6 @@ bool _maybeAddNamedParameter(
/// and this is used as the location value for all Widget constructor
/// invocations within the method.
class _WidgetCallSiteTransformer extends Transformer {
/// The [Widget] class defined in the `package:flutter` library.
///
/// Used to perform is-tests to determine whether Dart constructor calls are
/// creating [Widget] objects.
final Class _widgetClass;
/// The _Location class defined in the `package:flutter` library.
final Class _locationClass;
final WidgetCreatorTracker _tracker;
/// The creation location parameter of the extension factory method enclosing
@@ -151,13 +141,8 @@ class _WidgetCallSiteTransformer extends Transformer {
/// of the library.
Library? _currentLibrary;
_WidgetCallSiteTransformer({
required Class widgetClass,
required Class locationClass,
required WidgetCreatorTracker tracker,
}) : _widgetClass = widgetClass,
_locationClass = locationClass,
_tracker = tracker;
_WidgetCallSiteTransformer({required WidgetCreatorTracker tracker})
: _tracker = tracker;
/// Builds a call to the const constructor of the _Location
/// object specifying the location where a constructor call was made and
@@ -169,7 +154,12 @@ class _WidgetCallSiteTransformer extends Transformer {
/// constructor call but it is convenient to bundle the location and names
/// of the parameters passed in so that tools can show parameter locations
/// without re-parsing the source code.
ConstructorInvocation _constructLocation(Location location, {String? name}) {
ConstructorInvocation _constructLocation(
Location location, {
required Class locationClass,
String? name,
}) {
final List<NamedExpression> arguments = <NamedExpression>[
new NamedExpression('file', new StringLiteral(location.file.toString())),
new NamedExpression('line', new IntLiteral(location.line)),
@@ -178,7 +168,7 @@ class _WidgetCallSiteTransformer extends Transformer {
];
return new ConstructorInvocation(
_locationClass.constructors.first,
locationClass.constructors.first,
new Arguments(<Expression>[], named: arguments),
isConst: true,
);
@@ -210,10 +200,6 @@ class _WidgetCallSiteTransformer extends Transformer {
return node;
}
bool _isSubclassOfWidget(Class clazz) {
return _tracker._isSubclassOf(clazz, _widgetClass);
}
bool _isWidgetFactory(Procedure node) {
return node.isExtensionMember &&
_hasNamedParameter(node.function, _creationLocationParameterName);
@@ -225,7 +211,10 @@ class _WidgetCallSiteTransformer extends Transformer {
final Procedure target = node.target;
if (target.isFactory) {
final Class constructedClass = target.enclosingClass!;
if (!_isSubclassOfWidget(constructedClass)) {
final _TrackingClasses? tracking = _tracker._getTrackingClasses(
constructedClass,
);
if (tracking == null) {
return node;
}
@@ -234,11 +223,16 @@ class _WidgetCallSiteTransformer extends Transformer {
target.function,
constructedClass: constructedClass,
isConst: node.isConst,
locationClass: tracking.locationClass,
);
return node;
}
if (_isWidgetFactory(target)) {
_addLocationArgument(node, target.function);
if (_isWidgetFactory(target) && _tracker._locationClass != null) {
_addLocationArgument(
node,
target.function,
locationClass: _tracker._locationClass!,
);
return node;
}
return node;
@@ -249,6 +243,7 @@ class _WidgetCallSiteTransformer extends Transformer {
FunctionNode function, {
Class? constructedClass,
bool isConst = false,
required Class locationClass,
}) {
Expression? location = _currentExtensionFactoryLocationParameter;
if (location == null ||
@@ -261,14 +256,10 @@ class _WidgetCallSiteTransformer extends Transformer {
function,
constructedClass,
isConst: isConst,
locationClass: locationClass,
);
}
_maybeAddCreationLocationArgument(
node.arguments,
function,
location,
_locationClass,
);
_maybeAddCreationLocationArgument(node.arguments, function, location);
}
@override
@@ -277,7 +268,10 @@ class _WidgetCallSiteTransformer extends Transformer {
final Constructor constructor = node.target;
final Class constructedClass = constructor.enclosingClass;
if (!_isSubclassOfWidget(constructedClass)) {
final _TrackingClasses? tracking = _tracker._getTrackingClasses(
constructedClass,
);
if (tracking == null) {
return node;
}
@@ -286,6 +280,7 @@ class _WidgetCallSiteTransformer extends Transformer {
constructor.function,
constructedClass: constructedClass,
isConst: node.isConst,
locationClass: tracking.locationClass,
);
return node;
}
@@ -295,6 +290,7 @@ class _WidgetCallSiteTransformer extends Transformer {
FunctionNode function,
Class? constructedClass, {
bool isConst = false,
required Class locationClass,
}) {
assert(constructedClass != null || !isConst);
@@ -302,9 +298,9 @@ class _WidgetCallSiteTransformer extends Transformer {
// argument to the factory constructor rather than the location
if (constructedClass != null &&
_currentFactory != null &&
_tracker._isSubclassOf(
_tracker._isSubclassWhere(
constructedClass,
_currentFactory!.enclosingClass!,
(Class c) => c == _currentFactory!.enclosingClass!,
) &&
// If the constructor invocation is constant we cannot refer to the
// location parameter of the surrounding factory since it isn't a
@@ -319,8 +315,13 @@ class _WidgetCallSiteTransformer extends Transformer {
}
}
if (node.location == null) {
return new NullLiteral();
}
return _constructLocation(
node.location!,
locationClass: locationClass,
name:
constructedClass?.name ??
// For extension factory methods we use the name of the method.
@@ -360,12 +361,20 @@ class _WidgetCallSiteTransformer extends Transformer {
/// invocations within the method.
class WidgetCreatorTracker {
bool _foundClasses = false;
late Class _widgetClass;
late Class _locationClass;
/// Marker interface indicating that a private _location field is
/// available.
late Class _hasCreationLocationClass;
/// The [Widget] class defined in the `package:flutter` library.
///
/// Used to perform is-tests to determine whether Dart constructor calls are
/// creating [Widget] objects.
Class? _widgetClass;
/// The _Location class defined in the `package:flutter` library.
Class? _locationClass;
/// The _HasCreationLocation class defined in the `package:flutter` library.
///
/// Marker interface indicating that a private _location field is available.
Class? _hasCreationLocationClass;
/// Annotation class used to mark an extension method as a "Widget factory".
///
@@ -374,20 +383,27 @@ class WidgetCreatorTracker {
/// method.
Class? _widgetFactoryClass;
void _resolveFlutterClasses(Iterable<Library> libraries) {
/// The _HasCreationLocation class defined in the `dart:developer` library.
Class? _developerHasCreationLocationClass;
/// The CreationLocation class defined in the `dart:developer` library.
Class? _developerCreationLocationClass;
void _resolveWellKnownClasses(Iterable<Library> libraries) {
// If the Widget or Debug location classes have been updated we need to get
// the latest version
bool foundWidgetClass = false;
bool foundHasCreationLocationClass = false;
bool foundLocationClass = false;
for (Library library in libraries) {
final Uri importUri = library.importUri;
// Legacy Case: Search for hardcoded Flutter classes.
// TODO(http://dartbug.com/63225): Remove this once Flutter is migrated
// to the new API.
if (importUri.isScheme('package')) {
if (importUri.path == 'flutter/src/widgets/framework.dart') {
for (Class class_ in library.classes) {
if (class_.name == 'Widget') {
_widgetClass = class_;
foundWidgetClass = true;
}
}
} else {
@@ -395,10 +411,8 @@ class WidgetCreatorTracker {
for (Class class_ in library.classes) {
if (class_.name == '_HasCreationLocation') {
_hasCreationLocationClass = class_;
foundHasCreationLocationClass = true;
} else if (class_.name == '_Location') {
_locationClass = class_;
foundLocationClass = true;
} else if (class_.name == '_WidgetFactory') {
_widgetFactoryClass = class_;
}
@@ -406,43 +420,62 @@ class WidgetCreatorTracker {
}
}
}
// New Case: Search for classes in `dart:developer`
if (importUri.isScheme('dart') && importUri.path == 'developer') {
for (Class class_ in library.classes) {
if (class_.name == '_HasCreationLocation') {
_developerHasCreationLocationClass = class_;
} else if (class_.name == 'CreationLocation') {
_developerCreationLocationClass = class_;
}
}
}
}
// TODO(johnniwinther): Require the [_widgetFactoryClass] once the
// `widgetFactory` is stable in flutter.
_foundClasses =
foundWidgetClass && foundHasCreationLocationClass && foundLocationClass;
(_widgetClass != null &&
_hasCreationLocationClass != null &&
_locationClass != null) ||
(_developerHasCreationLocationClass != null &&
_developerCreationLocationClass != null);
}
/// Modify [clazz] to add a field named [_locationFieldName] that is the
/// first parameter of all constructors of the class.
/// Modify [clazz] to add the location field that is
/// the first parameter of all constructors of the class.
///
/// This method should only be called for classes that implement but do not
/// extend [Widget].
void _transformClassImplementingWidget(
/// extend the tracking base class.
void _transformClassImplementingTrackingClass(
Class clazz,
_TrackingClasses tracking,
ChangedStructureNotifier? changedStructureNotifier,
) {
if (clazz.fields.any(
(Field field) => field.name.text == _locationFieldName,
(Field field) => field.name.text == tracking.locationFieldName,
)) {
// This class has already been transformed. Skip
return;
}
clazz.implementedTypes.add(
new Supertype(_hasCreationLocationClass, <DartType>[]),
new Supertype(tracking.hasCreationLocationClass, <DartType>[]),
);
changedStructureNotifier?.registerClassHierarchyChange(clazz);
// We intentionally use the library context of the _HasCreationLocation
// class for the private field even if [clazz] is in a different library
// so that all classes implementing Widget behave consistently.
// so that all classes implementing the tracking class behave consistently.
final Name fieldName = new Name(
_locationFieldName,
_hasCreationLocationClass.enclosingLibrary,
tracking.locationFieldName,
tracking.hasCreationLocationClass.enclosingLibrary,
);
final Field locationField = new Field.immutable(
fieldName,
type: new InterfaceType(_locationClass, clazz.enclosingLibrary.nullable),
type: new InterfaceType(
tracking.locationClass,
clazz.enclosingLibrary.nullable,
),
isFinal: true,
fieldReference: clazz.reference.canonicalName
?.getChildFromFieldWithName(fieldName)
@@ -470,7 +503,7 @@ class WidgetCreatorTracker {
final VariableDeclaration variable = new VariableDeclaration(
_creationLocationParameterName,
type: new InterfaceType(
_locationClass,
tracking.locationClass,
clazz.enclosingLibrary.nullable,
),
initializer: new NullLiteral(),
@@ -492,7 +525,6 @@ class WidgetCreatorTracker {
initializer.arguments,
initializer.target.function,
new VariableGet(variable),
_locationClass,
);
hasRedirectingInitializer = true;
break;
@@ -512,7 +544,7 @@ class WidgetCreatorTracker {
// constructor.initializers.add(new AssertInitializer(
// new AssertStatement(
// new IsExpression(
// new VariableGet(variable), _locationClass.thisType),
// new VariableGet(variable), tracking.locationClass.thisType),
// conditionStartOffset: constructor.fileOffset,
// conditionEndOffset: constructor.fileOffset,
// )));
@@ -525,7 +557,7 @@ class WidgetCreatorTracker {
/// Transform the given [libraries].
///
/// The libraries from [module] is searched for the Widget class,
/// The [moduleLibraries] are searched for the Widget class,
/// the _Location class, the _HasCreationLocation class and the
/// _WidgetFactory class.
/// If the component does not contain them, the ones from a previous run is
@@ -537,18 +569,18 @@ class WidgetCreatorTracker {
/// compilation where the class hierarchy is kept between compiles and thus
/// has to be kept up to date.
void transform(
Component module,
List<Library> libraries,
List<Library> moduleLibraries,
ChangedStructureNotifier? changedStructureNotifier,
) {
if (libraries.isEmpty) {
return;
}
_resolveFlutterClasses(module.libraries);
_resolveWellKnownClasses(moduleLibraries);
if (!_foundClasses) {
// This application doesn't actually use the package:flutter library.
// Neither package:flutter nor dart:developer tracking classes found.
return;
}
@@ -579,11 +611,7 @@ class WidgetCreatorTracker {
// Transform call sites to pass the location parameter.
final _WidgetCallSiteTransformer callsiteTransformer =
new _WidgetCallSiteTransformer(
widgetClass: _widgetClass,
locationClass: _locationClass,
tracker: this,
);
new _WidgetCallSiteTransformer(tracker: this);
for (Library library in libraries) {
callsiteTransformer.enterLibrary(library);
@@ -592,26 +620,110 @@ class WidgetCreatorTracker {
}
}
bool _isSubclassOfWidget(Class clazz) => _isSubclassOf(clazz, _widgetClass);
_TrackingClasses? _getTrackingClasses(Class clazz) {
// Legacy Case: Check for widget class.
if (_isSubclassOfWidget(clazz)) {
if (_hasCreationLocationClass != null && _locationClass != null) {
return new _TrackingClasses(
hasCreationLocationClass: _hasCreationLocationClass!,
locationClass: _locationClass!,
locationFieldName: _locationFieldName,
);
}
}
bool _isSubclassOf(Class a, Class b) {
// New Case: Check for 'pragma('track-creation-locations')' annotation.
if (_hasTrackCreationLocationsPragmaAnnotation(clazz)) {
if (_developerHasCreationLocationClass != null &&
_developerCreationLocationClass != null) {
return new _TrackingClasses(
hasCreationLocationClass: _developerHasCreationLocationClass!,
locationClass: _developerCreationLocationClass!,
locationFieldName: _locationFieldName,
);
}
}
return null;
}
bool _isSubclassOfWidget(Class clazz) {
if (_widgetClass == null) return false;
return _isSubclassWhere(clazz, (Class c) => c == _widgetClass);
}
bool _hasTrackCreationLocationsPragmaAnnotation(Class clazz) {
if (_developerHasCreationLocationClass == null) return false;
return _isSubclassWhere(clazz, (Class c) {
for (Expression annotation in c.annotations) {
// Case before constant evaluation (newly compiled modules).
if (annotation is RedirectingFactoryInvocation) {
final expression = annotation.expression;
if (expression is ConstructorInvocation) {
final Class enclosingClass = expression.target.enclosingClass;
if (enclosingClass.name == 'pragma' &&
enclosingClass.enclosingLibrary.importUri.toString() ==
'dart:core') {
if (expression.arguments.positional.isNotEmpty) {
final Expression firstArg =
expression.arguments.positional.first;
if (firstArg is StringLiteral &&
firstArg.value == 'track-creation-locations') {
return true;
}
}
}
}
}
// Case after constant evaluation (incremental or modular compilation).
if (annotation case ConstantExpression(
constant: final InstanceConstant constant,
)) {
final Class enclosingClass = constant.classNode;
if (enclosingClass.name == 'pragma' &&
enclosingClass.enclosingLibrary.importUri.toString() ==
'dart:core') {
for (final Constant value in constant.fieldValues.values) {
if (value case StringConstant(
value: 'track-creation-locations',
)) {
return true;
}
}
}
}
}
return false;
});
}
bool _isSubclassWhere(Class a, bool Function(Class b) predicate) {
// TODO(askesc): Cache results.
// TODO(askesc): Test for subtype rather than subclass.
Class? current = a;
while (current != null) {
if (current == b) return true;
if (predicate(current)) return true;
current = current.superclass;
}
return false;
}
bool _hasWidgetFactoryAnnotation(Procedure node) =>
_isAnnotatedWithNamedValueOfType(node, _widgetFactoryClass!);
_isAnnotatedWithNamedValueOfType(
node,
_widgetFactoryClass,
_widgetFactoryClass,
);
bool _isAnnotatedWithNamedValueOfType(
Annotatable node,
Class annotationClass,
Class? annotationClass,
Class? typeClass,
) {
if (annotationClass == null || typeClass == null) return false;
return node.annotations.any((annotation) {
if (annotation is! StaticGet) {
return false;
@@ -627,7 +739,7 @@ class WidgetCreatorTracker {
if (type.nullability == Nullability.nullable) {
return false;
}
return type.classNode == _widgetFactoryClass;
return type.classNode == typeClass;
});
}
@@ -637,7 +749,8 @@ class WidgetCreatorTracker {
Class clazz,
ChangedStructureNotifier? changedStructureNotifier,
) {
if (!_isSubclassOfWidget(clazz) ||
final _TrackingClasses? tracking = _getTrackingClasses(clazz);
if (tracking == null ||
!librariesToBeTransformed.contains(clazz.enclosingLibrary) ||
!transformedClasses.add(clazz)) {
return;
@@ -661,7 +774,7 @@ class WidgetCreatorTracker {
new VariableDeclaration(
_creationLocationParameterName,
type: new InterfaceType(
_locationClass,
tracking.locationClass,
clazz.enclosingLibrary.nullable,
),
initializer: new NullLiteral(),
@@ -672,8 +785,14 @@ class WidgetCreatorTracker {
// Handle the widget class and classes that implement but do not extend the
// widget class.
if (!_isSubclassOfWidget(clazz.superclass!)) {
_transformClassImplementingWidget(clazz, changedStructureNotifier);
if (clazz.superclass == null ||
_getTrackingClasses(clazz.superclass!)?.hasCreationLocationClass !=
tracking.hasCreationLocationClass) {
_transformClassImplementingTrackingClass(
clazz,
tracking,
changedStructureNotifier,
);
return;
}
@@ -688,7 +807,7 @@ class WidgetCreatorTracker {
final VariableDeclaration variable = new VariableDeclaration(
_creationLocationParameterName,
type: new InterfaceType(
_locationClass,
tracking.locationClass,
clazz.enclosingLibrary.nullable,
),
initializer: new NullLiteral(),
@@ -717,16 +836,17 @@ class WidgetCreatorTracker {
initializer.arguments,
initializer.target.function,
new VariableGet(variable),
_locationClass,
);
} else if (initializer is SuperInitializer &&
_isSubclassOfWidget(initializer.target.enclosingClass)) {
_maybeAddCreationLocationArgument(
initializer.arguments,
initializer.target.function,
new VariableGet(variable),
_locationClass,
);
} else if (initializer is SuperInitializer) {
final Class superclass = initializer.target.enclosingClass;
if (_getTrackingClasses(superclass)?.hasCreationLocationClass ==
tracking.hasCreationLocationClass) {
_maybeAddCreationLocationArgument(
initializer.arguments,
initializer.target.function,
new VariableGet(variable),
);
}
}
}
}
@@ -761,7 +881,7 @@ class WidgetCreatorTracker {
new VariableDeclaration(
_creationLocationParameterName,
type: new InterfaceType(
_locationClass,
_locationClass!,
extension.enclosingLibrary.nullable,
),
initializer: new NullLiteral(),
@@ -775,7 +895,7 @@ class WidgetCreatorTracker {
new VariableDeclaration(
_creationLocationParameterName,
type: new InterfaceType(
_locationClass,
_locationClass!,
extension.enclosingLibrary.nullable,
),
initializer: new NullLiteral(),
@@ -785,3 +905,17 @@ class WidgetCreatorTracker {
}
}
}
/// Holds the set of creation location classes, either
/// from `package:flutter` or `dart:developer`.
class _TrackingClasses {
final Class hasCreationLocationClass;
final Class locationClass;
final String locationFieldName;
_TrackingClasses({
required this.hasCreationLocationClass,
required this.locationClass,
required this.locationFieldName,
});
}
@@ -0,0 +1,131 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// 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:expect/expect.dart';
import 'package:kernel/ast.dart';
import 'package:kernel/transformations/track_widget_constructor_locations.dart';
void main() {
final Uri developerUri = Uri.parse('dart:developer');
final Library developerLib = new Library(developerUri, fileUri: developerUri);
final Class hasCreationLocationClass = new Class(
name: '_HasCreationLocation',
isAbstract: true,
fileUri: developerUri,
);
developerLib.addClass(hasCreationLocationClass);
final Class creationLocationClass = new Class(
name: 'CreationLocation',
fileUri: developerUri,
);
final Constructor creationLocationConstructor = new Constructor(
new FunctionNode(
null,
namedParameters: [
new VariableDeclaration('file', type: const DynamicType()),
new VariableDeclaration('line', type: const DynamicType()),
new VariableDeclaration('column', type: const DynamicType()),
new VariableDeclaration('name', type: const DynamicType()),
],
),
name: new Name('_', developerLib),
fileUri: developerUri,
);
creationLocationClass.addConstructor(creationLocationConstructor);
developerLib.addClass(creationLocationClass);
final Uri coreUri = Uri.parse('dart:core');
final Library coreLib = new Library(coreUri, fileUri: coreUri);
final Class pragmaClass = new Class(name: 'pragma', fileUri: coreUri);
coreLib.addClass(pragmaClass);
final Field pragmaNameField = new Field.immutable(
new Name('name'),
fileUri: coreUri,
);
pragmaClass.addField(pragmaNameField);
final Uri testUri = Uri.parse('package:test/test.dart');
final Library testLib = new Library(testUri, fileUri: testUri);
final Class myWidgetClass = new Class(name: 'MyWidget', fileUri: testUri);
myWidgetClass.addAnnotation(
new ConstantExpression(
new InstanceConstant(
pragmaClass.reference,
<DartType>[],
<Reference, Constant>{
pragmaNameField.fieldReference: new StringConstant(
'track-creation-locations',
),
},
),
),
);
myWidgetClass.addConstructor(
new Constructor(
new FunctionNode(new Block([])),
name: new Name(''),
fileUri: testUri,
),
);
testLib.addClass(myWidgetClass);
const int fileOffset = 100;
final Procedure mainProcedure = new Procedure(
new Name('main'),
ProcedureKind.Method,
new FunctionNode(
new Block([
new ExpressionStatement(
new ConstructorInvocation(
myWidgetClass.constructors.first,
new Arguments([]),
)..fileOffset = fileOffset,
),
]),
),
isStatic: true,
fileUri: testUri,
);
testLib.addProcedure(mainProcedure);
final WidgetCreatorTracker tracker = new WidgetCreatorTracker();
tracker.transform([testLib], [developerLib, testLib], null);
// Verification
Expect.isTrue(
myWidgetClass.implementedTypes.any(
(s) => s.classNode == hasCreationLocationClass,
),
);
Expect.isTrue(myWidgetClass.fields.any((f) => f.name.text == '_location'));
final Constructor constructor = myWidgetClass.constructors.first;
const String creationLocationPrefix = r'$creationLocation';
Expect.isTrue(
constructor.function.namedParameters.any(
(p) => p.name!.startsWith(creationLocationPrefix),
),
);
final Block body = mainProcedure.function.body as Block;
final ExpressionStatement stmt = body.statements.first as ExpressionStatement;
final ConstructorInvocation invocation =
stmt.expression as ConstructorInvocation;
Expect.isTrue(
invocation.arguments.named.any(
(n) => n.name.startsWith(creationLocationPrefix),
),
);
final NamedExpression namedArg = invocation.arguments.named.firstWhere(
(n) => n.name.startsWith(creationLocationPrefix),
);
Expect.isTrue(namedArg.value is ConstructorInvocation);
final ConstructorInvocation locInvocation =
namedArg.value as ConstructorInvocation;
Expect.equals(creationLocationClass, locInvocation.target.enclosingClass);
}