Skip to content

Commit 598cf5f

Browse files
Merge pull request #1002 from Workiva/remove-mirrors
FED-4507 Analyzer plugin: fix in Dart 3, support analyzer 6.x
2 parents 977fc82 + f04148d commit 598cf5f

20 files changed

Lines changed: 213 additions & 597 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# OverReact Changelog
22

3+
## Unreleased
4+
- Analyzer plugin
5+
- Fix startup error (AOT compilation) in Dart 3
6+
- Allow analyzer 6.x, allowing parsing language versions <=3.7
7+
38
## 5.6.1
49
- Allow w_common 4 #1004 https://github.com/Workiva/over_react/pull/1004
510

lib/src/builder/parsing/meta.dart

Lines changed: 118 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,38 +12,27 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
import 'dart:mirrors' as mirrors;
16-
1715
import 'package:analyzer/dart/ast/ast.dart';
1816
import 'package:build/build.dart' show log;
1917
import 'package:collection/collection.dart' show IterableExtension;
20-
import 'package:over_react/src/builder/vendor/transformer_utils/transformer_utils.dart';
2118

22-
import 'ast_util.dart';
19+
import 'package:over_react/src/component_declaration/annotations.dart' as a;
2320

24-
/// Uses reflection to instantiate and return the first annotation on [member] of type
25-
/// [T], or null if no matching annotations are found.
26-
///
27-
/// > See [instantiateAnnotation] for more information.
28-
T? instantiateAnnotationTyped<T>(AnnotatedNode member,
29-
{dynamic Function(Expression argument)? onUnsupportedArgument}) {
30-
return instantiateAnnotation(member, T, onUnsupportedArgument: onUnsupportedArgument) as T?;
31-
}
21+
import '../vendor/transformer_utils/src/analyzer_helpers.dart';
3222

33-
/// Returns the first annotation AST node on [member] of type [annotationType],
23+
/// Returns the first annotation AST node on [node] of type [T],
3424
/// or null if no matching annotations are found.
35-
Annotation? _getMatchingAnnotation(AnnotatedNode member, Type annotationType) {
36-
// Be sure to use `originalDeclaration` so that generic parameters work.
37-
final classMirror = mirrors.reflectClass(annotationType).originalDeclaration;
38-
final className = mirrors.MirrorSystem.getName(classMirror.simpleName);
39-
return member.getAnnotationWithName(className);
40-
}
25+
Annotation? _getMatchingAnnotationFromGeneric<T extends Object>(AnnotatedNode node) =>
26+
_getMatchingAnnotation(_AnnotationClass.fromGeneric<T>(), node);
27+
28+
/// Returns the first annotation AST node on [node] of type [annotationClass],
29+
/// or null if no matching annotations are found.
30+
Annotation? _getMatchingAnnotation(_AnnotationClass annotationClass, AnnotatedNode node) =>
31+
node.metadata.firstWhereOrNull((m) => m.name.name == annotationClass.className);
4132

4233
/// Utility class that allows partial instantiation of annotations, to support reading
4334
/// annotation data in a context without a resolved AST. See [isIncomplete] for more info.
44-
///
45-
/// Based off of [NodeWithMeta].
46-
class InstantiatedMeta<TMeta> {
35+
class InstantiatedMeta<TMeta extends Object> {
4736
/// The node of the [TMeta] annotation, if it exists.
4837
final Annotation metaNode;
4938

@@ -62,8 +51,8 @@ class InstantiatedMeta<TMeta> {
6251
/// The original node will be available via [node].
6352
///
6453
/// The instantiated annotation will be available via [value].
65-
static InstantiatedMeta<T>? fromNode<T>(AnnotatedNode node) {
66-
final metaNode = _getMatchingAnnotation(node, T);
54+
static InstantiatedMeta<T>? fromNode<T extends Object>(AnnotatedNode node) {
55+
final metaNode = _getMatchingAnnotationFromGeneric<T>(node);
6756
if (metaNode == null) return null;
6857

6958
final unsupportedArguments = <Expression>[];
@@ -99,7 +88,7 @@ class InstantiatedMeta<TMeta> {
9988
/// Utility that allows partial instantiation of a `Component`/`Component2` annotation.
10089
///
10190
/// See superclass for more information.
102-
class InstantiatedComponentMeta<TMeta> extends InstantiatedMeta<TMeta> {
91+
class InstantiatedComponentMeta<TMeta extends Object> extends InstantiatedMeta<TMeta> {
10392
static const String _subtypeOfParamName = 'subtypeOf';
10493

10594
final Identifier? subtypeOfValue;
@@ -108,7 +97,7 @@ class InstantiatedComponentMeta<TMeta> extends InstantiatedMeta<TMeta> {
10897
Annotation metaNode, TMeta meta, List<Expression> unsupportedArguments, this.subtypeOfValue)
10998
: super._(metaNode, meta, unsupportedArguments);
11099

111-
static InstantiatedComponentMeta<T>? fromNode<T>(AnnotatedNode node) {
100+
static InstantiatedComponentMeta<T>? fromNode<T extends Object>(AnnotatedNode node) {
112101
try {
113102
final instantiated = InstantiatedMeta.fromNode<T>(node);
114103
if (instantiated == null) return null;
@@ -140,3 +129,106 @@ class InstantiatedComponentMeta<TMeta> extends InstantiatedMeta<TMeta> {
140129
}
141130
}
142131
}
132+
133+
T? instantiateAnnotationTyped<T extends Object>(
134+
AnnotatedNode node, {
135+
dynamic Function(Expression argument)? onUnsupportedArgument,
136+
}) {
137+
final annotationClass = _AnnotationClass.fromGeneric<T>();
138+
139+
final annotation = _getMatchingAnnotation(annotationClass, node);
140+
if (annotation == null) return null;
141+
142+
final args = parseAnnotationArgs(annotation, onUnsupportedArgument: onUnsupportedArgument);
143+
S namedArg<S>(String name) => args.named[name] as S;
144+
145+
switch (annotationClass) {
146+
case _AnnotationClass.props:
147+
return a.Props(
148+
keyNamespace: namedArg('keyNamespace'),
149+
disableRequiredPropValidation: namedArg('disableRequiredPropValidation'),
150+
disableValidationForClassDefaultProps:
151+
namedArg('disableValidationForClassDefaultProps') ?? true,
152+
) as T;
153+
case _AnnotationClass.abstractProps:
154+
return a.AbstractProps(
155+
keyNamespace: namedArg('keyNamespace'),
156+
) as T;
157+
case _AnnotationClass.propsMixin:
158+
// ignore: deprecated_member_use_from_same_package
159+
return a.PropsMixin(
160+
keyNamespace: namedArg('keyNamespace'),
161+
) as T;
162+
case _AnnotationClass.state:
163+
return a.State(
164+
keyNamespace: namedArg('keyNamespace'),
165+
) as T;
166+
case _AnnotationClass.abstractState:
167+
return a.AbstractState(
168+
keyNamespace: namedArg('keyNamespace'),
169+
) as T;
170+
case _AnnotationClass.stateMixin:
171+
// ignore: deprecated_member_use_from_same_package
172+
return a.StateMixin(
173+
keyNamespace: namedArg('keyNamespace'),
174+
) as T;
175+
case _AnnotationClass.component2:
176+
return a.Component2(
177+
isWrapper: namedArg('isWrapper') ?? false,
178+
subtypeOf: namedArg('subtypeOf'),
179+
isErrorBoundary: namedArg('isErrorBoundary') ?? false,
180+
) as T;
181+
case _AnnotationClass.component:
182+
// ignore: deprecated_member_use_from_same_package
183+
return a.Component(
184+
isWrapper: namedArg('isWrapper') ?? false,
185+
subtypeOf: namedArg('subtypeOf'),
186+
) as T;
187+
case _AnnotationClass.accessor:
188+
return a.Accessor(
189+
key: namedArg('key'),
190+
keyNamespace: namedArg('keyNamespace'),
191+
isRequired: namedArg('isRequired') ?? false,
192+
isNullable: namedArg('isNullable') ?? false,
193+
requiredErrorMessage: namedArg('requiredErrorMessage'),
194+
doNotGenerate: namedArg('doNotGenerate') ?? false,
195+
) as T;
196+
}
197+
}
198+
199+
enum _AnnotationClass {
200+
props('Props'),
201+
abstractProps('AbstractProps'),
202+
propsMixin('PropsMixin'),
203+
state('State'),
204+
abstractState('AbstractState'),
205+
stateMixin('StateMixin'),
206+
component2('Component2'),
207+
component('Component'),
208+
accessor('Accessor');
209+
210+
final String className;
211+
212+
const _AnnotationClass(this.className);
213+
214+
static _AnnotationClass fromGeneric<T>() {
215+
if (_isSubtypeOf<T, a.Props>()) return _AnnotationClass.props;
216+
if (_isSubtypeOf<T, a.AbstractProps>()) return _AnnotationClass.abstractProps;
217+
// ignore: deprecated_member_use_from_same_package
218+
if (_isSubtypeOf<T, a.PropsMixin>()) return _AnnotationClass.propsMixin;
219+
if (_isSubtypeOf<T, a.State>()) return _AnnotationClass.state;
220+
if (_isSubtypeOf<T, a.AbstractState>()) return _AnnotationClass.abstractState;
221+
// ignore: deprecated_member_use_from_same_package
222+
if (_isSubtypeOf<T, a.StateMixin>()) return _AnnotationClass.stateMixin;
223+
if (_isSubtypeOf<T, a.Component2>()) return _AnnotationClass.component2;
224+
// ignore: deprecated_member_use_from_same_package
225+
if (_isSubtypeOf<T, a.Component>()) return _AnnotationClass.component;
226+
if (_isSubtypeOf<T, a.Accessor>()) return _AnnotationClass.accessor;
227+
228+
throw ArgumentError('Unsupported generic: $T. Must correspond to a type in this enum.');
229+
}
230+
}
231+
232+
bool _isSubtypeOf<T, S>() => _SubtypeOfHelper<T>() is _SubtypeOfHelper<S>;
233+
234+
class _SubtypeOfHelper<T> {}

lib/src/builder/util.dart

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
import 'dart:mirrors';
16-
1715
import 'package:analyzer/dart/ast/ast.dart';
1816
import 'package:build/build.dart' show AssetId;
1917
import 'package:over_react/src/builder/parsing.dart';
@@ -26,10 +24,6 @@ const privateSourcePrefix = r'_$';
2624
const privatePrefix = r'_';
2725
const publicGeneratedPrefix = r'$';
2826

29-
String getName(Type type) {
30-
return MirrorSystem.getName(reflectType(type).simpleName);
31-
}
32-
3327
/// Converts [id] to a "package:" URI.
3428
///
3529
/// This will return a schemeless URI if [id] doesn't represent a library in

lib/src/builder/vendor/transformer_utils/src/analyzer_helpers.dart

Lines changed: 17 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,7 @@
1616

1717
library;
1818

19-
import 'dart:mirrors' as mirrors;
20-
2119
import 'package:analyzer/dart/ast/ast.dart';
22-
import 'package:collection/collection.dart' show IterableExtension;
2320

2421
/// Returns a copy of a class [member] declaration with [body] as a new
2522
/// implementation.
@@ -38,19 +35,6 @@ String copyClassMember(ClassMember? member, String body) {
3835
'Only FieldDeclaration and MethodDeclaration are supported.');
3936
}
4037

41-
/// Finds and returns all declarations within a compilation [unit] that are
42-
/// annotated with the given [annotation] class.
43-
///
44-
/// If this is being leveraged within a transformer, you can associate the
45-
/// returned [DeclarationWithMeta] instance with the asset in which it is
46-
/// located by passing in an [assetId].
47-
Iterable<CompilationUnitMember> getDeclarationsAnnotatedBy(CompilationUnit unit, Type annotation) {
48-
var annotationName = _getReflectedName(annotation);
49-
return unit.declarations.where((member) {
50-
return member.metadata.any((meta) => meta.name.name == annotationName);
51-
});
52-
}
53-
5438
/// Returns the value of the specified [expression] AST node if it represents a literal.
5539
///
5640
/// For non-literal nodes, the return value will be the result of calling [onUnsupportedExpression] with [expression].
@@ -85,78 +69,42 @@ dynamic getValue(Expression expression,
8569
'Must be a uninterpolated string, boolean, integer, or null literal.');
8670
}
8771

88-
/// Returns the first annotation AST node on [member] of type [annotationType],
89-
/// or null if no matching annotations are found.
90-
Annotation? getMatchingAnnotation(AnnotatedNode member, Type annotationType) {
91-
// Be sure to use `originalDeclaration` so that generic parameters work.
92-
final classMirror =
93-
mirrors.reflectClass(annotationType).originalDeclaration as mirrors.ClassMirror;
94-
String className = mirrors.MirrorSystem.getName(classMirror.simpleName);
95-
96-
// Find the annotation that matches [type]'s name.
97-
return member.metadata.firstWhereOrNull((annotation) {
98-
return _getClassName(annotation) == className;
99-
});
72+
/// The parsed arguments of an annotation constructor call, from [parseAnnotationArgs].
73+
class AnnotationArgs {
74+
final Map<String, dynamic> named;
75+
final List<dynamic> positional;
76+
77+
AnnotationArgs({required this.named, required this.positional});
10078
}
10179

102-
/// Uses reflection to instantiate and returns the first annotation on [member] of type
103-
/// [annotationType], or null if no matching annotations are found.
104-
///
105-
/// Annotation constructors are currently limited to the values supported by [getValue].
106-
///
107-
/// Naively assumes that the name of the [annotationType] class is canonical.
108-
dynamic instantiateAnnotation(AnnotatedNode member, Type annotationType,
80+
/// Returns annotation class constructor arguments parsed from [annotation],
81+
/// converting supported literal values via [getValue], and passing unsupported
82+
/// argument values through [onUnsupportedArgument].
83+
AnnotationArgs parseAnnotationArgs(Annotation annotation,
10984
{dynamic Function(Expression argument)? onUnsupportedArgument}) {
110-
final matchingAnnotation = getMatchingAnnotation(member, annotationType);
111-
112-
// If no annotation is found, return null.
113-
if (matchingAnnotation == null) {
114-
return null;
115-
}
116-
117-
final matchingAnnotationArgs = matchingAnnotation.arguments;
118-
if (matchingAnnotationArgs == null) {
119-
throw Exception('Annotation not invocation of constructor: `$matchingAnnotation`. '
120-
'This is likely due to invalid usage of the annotation class, but could'
121-
'also be a name conflict with the specified type `$annotationType`');
122-
}
123-
124-
// Get the parameters from the annotation's AST.
125-
Map<Symbol, dynamic> namedParameters = {};
85+
Map<String, dynamic> namedParameters = {};
12686
List positionalParameters = [];
12787

128-
matchingAnnotationArgs.arguments.forEach((argument) {
88+
annotation.arguments?.arguments.forEach((argument) {
12989
var onUnsupportedExpression =
13090
onUnsupportedArgument == null ? null : (_) => onUnsupportedArgument(argument);
13191

13292
if (argument is NamedExpression) {
13393
var name = argument.name.label.name;
13494
var value = getValue(argument.expression, onUnsupportedExpression: onUnsupportedExpression);
13595

136-
namedParameters[Symbol(name)] = value;
96+
namedParameters[name] = value;
13797
} else {
13898
var value = getValue(argument, onUnsupportedExpression: onUnsupportedExpression);
13999

140100
positionalParameters.add(value);
141101
}
142102
});
143103

144-
// Instantiate and return an instance of the annotation using reflection.
145-
String constructorName = _getConstructorName(matchingAnnotation) ?? '';
146-
147-
// Be sure to use `originalDeclaration` so that generic parameters work.
148-
final classMirror =
149-
mirrors.reflectClass(annotationType).originalDeclaration as mirrors.ClassMirror;
150-
151-
try {
152-
var instanceMirror =
153-
classMirror.newInstance(Symbol(constructorName), positionalParameters, namedParameters);
154-
return instanceMirror.reflectee;
155-
} catch (e, stacktrace) {
156-
throw Exception('Unable to instantiate annotation: $matchingAnnotation. This is '
157-
'likely due to improper usage, or a naming conflict with '
158-
'annotationType $annotationType. Original error: $e. Stacktrace: $stacktrace');
159-
}
104+
return AnnotationArgs(
105+
named: namedParameters,
106+
positional: positionalParameters,
107+
);
160108
}
161109

162110
String _copyFieldDeclaration(FieldDeclaration decl, String initializer) {
@@ -221,31 +169,3 @@ String _copyMethodDeclaration(MethodDeclaration decl, String body) {
221169
result = '$result {\n$body\n }';
222170
return result;
223171
}
224-
225-
/// Returns the name of the class being instantiated for [annotation], or null
226-
/// if the annotation is not the invocation of a constructor.
227-
///
228-
/// Workaround for a Dart analyzer issue where the constructor name is included
229-
/// in [annotation.name].
230-
String _getClassName(Annotation annotation) => annotation.name.name.split('.').first;
231-
232-
/// Returns the name of the constructor being instantiated for [annotation], or
233-
/// null if the annotation is not the invocation of a named constructor.
234-
///
235-
/// Workaround for a Dart analyzer issue where the constructor name is included
236-
/// in [annotation.name].
237-
String? _getConstructorName(Annotation annotation) {
238-
var constructorName = annotation.constructorName?.name;
239-
if (constructorName == null) {
240-
var periodIndex = annotation.name.name.indexOf('.');
241-
if (periodIndex != -1) {
242-
constructorName = annotation.name.name.substring(periodIndex + 1);
243-
}
244-
}
245-
246-
return constructorName;
247-
}
248-
249-
/// Get the name of a [type] via reflection.
250-
String _getReflectedName(Type type) =>
251-
mirrors.MirrorSystem.getName(mirrors.reflectType(type).simpleName);

0 commit comments

Comments
 (0)