Skip to content

Commit 3218e40

Browse files
authored
Add a Constant helper class. (#171)
* Add a Constant helper class. * Address feedback. * Remove readX. * Rename to ConstantReader.
1 parent 2be8dd7 commit 3218e40

File tree

4 files changed

+251
-0
lines changed

4 files changed

+251
-0
lines changed

CHANGELOG.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,36 @@
4444
}
4545
```
4646

47+
* Added `ConstantReader`, a high-level API for reading from constant (static)
48+
values from Dart source code (usually represented by `DartObject` from the
49+
`analyzer` package):
50+
51+
```dart
52+
abstract class ConstantReader {
53+
factory ConstantReader(DartObject object) => ...
54+
55+
// Other methods and properties also exist.
56+
57+
/// Reads[ field] from the constant as another constant value.
58+
ConstantReader read(String field);
59+
60+
/// Reads [field] from the constant as a boolean.
61+
///
62+
/// If the resulting value is `null`, uses [defaultTo] if defined.
63+
bool readBool(String field, {bool defaultTo()});
64+
65+
/// Reads [field] from the constant as an int.
66+
///
67+
/// If the resulting value is `null`, uses [defaultTo] if defined.
68+
int readInt(String field, {int defaultTo()});
69+
70+
/// Reads [field] from the constant as a string.
71+
///
72+
/// If the resulting value is `null`, uses [defaultTo] if defined.
73+
String readString(String field, {String defaultTo()});
74+
}
75+
```
76+
4777
## 0.5.8
4878

4979
* Add `formatOutput` optional parameter to the `GeneratorBuilder` constructor.

lib/source_gen.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
library source_gen;
66

77
export 'src/builder.dart';
8+
export 'src/constants.dart' show ConstantReader;
89
export 'src/find_type.dart' show findType;
910
export 'src/generator.dart';
1011
export 'src/generator_for_annotation.dart';

lib/src/constants.dart

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analyzer/dart/constant/value.dart';
6+
7+
/// Returns whether or not [object] is or represents a `null` value.
8+
bool _isNull(DartObject object) => object?.isNull != false;
9+
10+
/// Similar to [DartObject.getField], but traverses super classes.
11+
///
12+
/// Returns `null` if ultimately [field] is never found.
13+
DartObject _getFieldRecursive(DartObject object, String field) {
14+
if (_isNull(object)) {
15+
return null;
16+
}
17+
final result = object.getField(field);
18+
if (_isNull(result)) {
19+
return _getFieldRecursive(object.getField('(super)'), field);
20+
}
21+
return result;
22+
}
23+
24+
/// A wrapper for analyzer's [DartObject] with a predictable high-level API.
25+
///
26+
/// Unlike [DartObject.getField], all `readX` methods attempt to access super
27+
/// classes for the field value if not found.
28+
abstract class ConstantReader {
29+
factory ConstantReader(DartObject object) =>
30+
_isNull(object) ? const _NullConstant() : new _Constant(object);
31+
32+
/// Returns whether this constant represents a `bool` literal.
33+
bool get isBool;
34+
35+
/// Returns this constant as a `bool` value.
36+
bool get boolValue;
37+
38+
/// Returns whether this constant represents an `int` literal.
39+
bool get isInt;
40+
41+
/// Returns this constant as an `int` value.
42+
///
43+
/// Throws [FormatException] if [isInt] is `false`.
44+
int get intValue;
45+
46+
/// Returns whether this constant represents a `String` literal.
47+
///
48+
/// If `true`, [stringValue] will return a `String` (not throw).
49+
bool get isString;
50+
51+
/// Returns this constant as an `String` value.
52+
///
53+
/// Throws [FormatException] if [isString] is `false`.
54+
String get stringValue;
55+
56+
/// Returns whether this constant represents `null`.
57+
bool get isNull;
58+
59+
/// Reads[ field] from the constant as another constant value.
60+
ConstantReader read(String field);
61+
}
62+
63+
/// Implements a [ConstantReader] representing a `null` value.
64+
class _NullConstant implements ConstantReader {
65+
const _NullConstant();
66+
67+
@override
68+
bool get boolValue => throw new FormatException('Not a bool', 'null');
69+
70+
@override
71+
int get intValue => throw new FormatException('Not an int', 'null');
72+
73+
@override
74+
String get stringValue => throw new FormatException('Not a String', 'null');
75+
76+
@override
77+
bool get isBool => false;
78+
79+
@override
80+
bool get isInt => false;
81+
82+
@override
83+
bool get isNull => true;
84+
85+
@override
86+
bool get isString => false;
87+
88+
@override
89+
ConstantReader read(_) => this;
90+
}
91+
92+
/// Default implementation of [ConstantReader].
93+
class _Constant implements ConstantReader {
94+
final DartObject _object;
95+
96+
const _Constant(this._object);
97+
98+
@override
99+
bool get boolValue => isBool
100+
? _object.toBoolValue()
101+
: throw new FormatException('Not a bool', _object);
102+
103+
@override
104+
int get intValue => isInt
105+
? _object.toIntValue()
106+
: throw new FormatException('Not an int', _object);
107+
108+
@override
109+
String get stringValue => isString
110+
? _object.toStringValue()
111+
: throw new FormatException('Not a String', _object);
112+
113+
@override
114+
bool get isBool => _object.toBoolValue() != null;
115+
116+
@override
117+
bool get isInt => _object.toIntValue() != null;
118+
119+
@override
120+
bool get isNull => _isNull(_object);
121+
122+
@override
123+
bool get isString => _object.toStringValue() != null;
124+
125+
@override
126+
ConstantReader read(String field) =>
127+
new ConstantReader(_getFieldRecursive(_object, field));
128+
}

test/constants_test.dart

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:build_test/build_test.dart';
6+
import 'package:source_gen/source_gen.dart';
7+
import 'package:test/test.dart';
8+
9+
void main() {
10+
group('Constant', () {
11+
List<ConstantReader> constants;
12+
13+
setUpAll(() async {
14+
final resolver = await resolveSource(r'''
15+
library test_lib;
16+
17+
const aString = 'Hello';
18+
const aInt = 1234;
19+
const aBool = true;
20+
const aNull = null;
21+
22+
@aString // [0]
23+
@aInt // [1]
24+
@aBool // [2]
25+
@aNull // [3]
26+
@Example( // [4]
27+
aString: aString,
28+
aInt: aInt,
29+
aBool: aBool,
30+
aNull: aNull,
31+
nested: const Exampe(),
32+
)
33+
@Super() // [5]
34+
class Example {
35+
final String aString;
36+
final int aInt;
37+
final bool aBool;
38+
final Example nested;
39+
40+
const Example({this.aString, this.aInt, this.aBool, this.nested});
41+
}
42+
43+
class Super extends Example {
44+
const Super() : super(aString: 'Super Hello');
45+
}
46+
''');
47+
constants = resolver
48+
.getLibraryByName('test_lib')
49+
.getType('Example')
50+
.metadata
51+
.map((e) => new ConstantReader(e.computeConstantValue()))
52+
.toList();
53+
});
54+
55+
test('should read a String', () {
56+
expect(constants[0].isString, isTrue);
57+
expect(constants[0].stringValue, 'Hello');
58+
});
59+
60+
test('should read an Int', () {
61+
expect(constants[1].isInt, isTrue);
62+
expect(constants[1].intValue, 1234);
63+
});
64+
65+
test('should read a Bool', () {
66+
expect(constants[2].isBool, isTrue);
67+
expect(constants[2].boolValue, true);
68+
});
69+
70+
test('should read a Null', () {
71+
expect(constants[3].isNull, isTrue);
72+
});
73+
74+
test('should read an arbitrary object', () {
75+
final constant = constants[4];
76+
expect(constant.read('aString').stringValue, 'Hello');
77+
expect(constant.read('aInt').intValue, 1234);
78+
expect(constant.read('aBool').boolValue, true);
79+
expect(constant.read('aNull').isNull, isTrue);
80+
81+
final nested = constant.read('nested');
82+
expect(nested.read('aString').isNull, isTrue);
83+
expect(nested.read('aInt').isNull, isTrue);
84+
expect(nested.read('aBool').isNull, isTrue);
85+
});
86+
87+
test('should read from a super object', () {
88+
final constant = constants[5];
89+
expect(constant.read('aString').stringValue, 'Super Hello');
90+
});
91+
});
92+
}

0 commit comments

Comments
 (0)