Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,36 @@
}
```

* Added `ConstantReader`, a high-level API for reading from constant (static)
values from Dart source code (usually represented by `DartObject` from the
`analyzer` package):

```dart
abstract class ConstantReader {
factory ConstantReader(DartObject object) => ...

// Other methods and properties also exist.

/// Reads[ field] from the constant as another constant value.
ConstantReader read(String field);

/// Reads [field] from the constant as a boolean.
///
/// If the resulting value is `null`, uses [defaultTo] if defined.
bool readBool(String field, {bool defaultTo()});

/// Reads [field] from the constant as an int.
///
/// If the resulting value is `null`, uses [defaultTo] if defined.
int readInt(String field, {int defaultTo()});

/// Reads [field] from the constant as a string.
///
/// If the resulting value is `null`, uses [defaultTo] if defined.
String readString(String field, {String defaultTo()});
}
```

## 0.5.8

* Add `formatOutput` optional parameter to the `GeneratorBuilder` constructor.
Expand Down
1 change: 1 addition & 0 deletions lib/source_gen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
library source_gen;

export 'src/builder.dart';
export 'src/constants.dart' show ConstantReader;
export 'src/find_type.dart' show findType;
export 'src/generator.dart';
export 'src/generator_for_annotation.dart';
Expand Down
128 changes: 128 additions & 0 deletions lib/src/constants.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright (c) 2017, 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:analyzer/dart/constant/value.dart';

/// Returns whether or not [object] is or represents a `null` value.
bool _isNull(DartObject object) => object?.isNull != false;

/// Similar to [DartObject.getField], but traverses super classes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets call out that this may return null.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

///
/// Returns `null` if ultimately [field] is never found.
DartObject _getFieldRecursive(DartObject object, String field) {
if (_isNull(object)) {
return null;
}
final result = object.getField(field);
if (_isNull(result)) {
return _getFieldRecursive(object.getField('(super)'), field);
}
return result;
}

/// A wrapper for analyzer's [DartObject] with a predictable high-level API.
///
/// Unlike [DartObject.getField], all `readX` methods attempt to access super
/// classes for the field value if not found.
abstract class ConstantReader {
factory ConstantReader(DartObject object) =>
_isNull(object) ? const _NullConstant() : new _Constant(object);

/// Returns whether this constant represents a `bool` literal.
bool get isBool;

/// Returns this constant as a `bool` value.
bool get boolValue;

/// Returns whether this constant represents an `int` literal.
bool get isInt;

/// Returns this constant as an `int` value.
///
/// Throws [FormatException] if [isInt] is `false`.
int get intValue;

/// Returns whether this constant represents a `String` literal.
///
/// If `true`, [stringValue] will return a `String` (not throw).
bool get isString;

/// Returns this constant as an `String` value.
///
/// Throws [FormatException] if [isString] is `false`.
String get stringValue;

/// Returns whether this constant represents `null`.
bool get isNull;

/// Reads[ field] from the constant as another constant value.
ConstantReader read(String field);
}

/// Implements a [ConstantReader] representing a `null` value.
class _NullConstant implements ConstantReader {
const _NullConstant();

@override
bool get boolValue => throw new FormatException('Not a bool', 'null');

@override
int get intValue => throw new FormatException('Not an int', 'null');

@override
String get stringValue => throw new FormatException('Not a String', 'null');

@override
bool get isBool => false;

@override
bool get isInt => false;

@override
bool get isNull => true;

@override
bool get isString => false;

@override
ConstantReader read(_) => this;
}

/// Default implementation of [ConstantReader].
class _Constant implements ConstantReader {
final DartObject _object;

const _Constant(this._object);

@override
bool get boolValue => isBool
? _object.toBoolValue()
: throw new FormatException('Not a bool', _object);

@override
int get intValue => isInt
? _object.toIntValue()
: throw new FormatException('Not an int', _object);

@override
String get stringValue => isString
? _object.toStringValue()
: throw new FormatException('Not a String', _object);

@override
bool get isBool => _object.toBoolValue() != null;

@override
bool get isInt => _object.toIntValue() != null;

@override
bool get isNull => _isNull(_object);

@override
bool get isString => _object.toStringValue() != null;

@override
ConstantReader read(String field) =>
new ConstantReader(_getFieldRecursive(_object, field));
}
92 changes: 92 additions & 0 deletions test/constants_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (c) 2017, 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:build_test/build_test.dart';
import 'package:source_gen/source_gen.dart';
import 'package:test/test.dart';

void main() {
group('Constant', () {
List<ConstantReader> constants;

setUpAll(() async {
final resolver = await resolveSource(r'''
library test_lib;

const aString = 'Hello';
const aInt = 1234;
const aBool = true;
const aNull = null;

@aString // [0]
@aInt // [1]
@aBool // [2]
@aNull // [3]
@Example( // [4]
aString: aString,
aInt: aInt,
aBool: aBool,
aNull: aNull,
nested: const Exampe(),
)
@Super() // [5]
class Example {
final String aString;
final int aInt;
final bool aBool;
final Example nested;

const Example({this.aString, this.aInt, this.aBool, this.nested});
}

class Super extends Example {
const Super() : super(aString: 'Super Hello');
}
''');
constants = resolver
.getLibraryByName('test_lib')
.getType('Example')
.metadata
.map((e) => new ConstantReader(e.computeConstantValue()))
.toList();
});

test('should read a String', () {
expect(constants[0].isString, isTrue);
expect(constants[0].stringValue, 'Hello');
});

test('should read an Int', () {
expect(constants[1].isInt, isTrue);
expect(constants[1].intValue, 1234);
});

test('should read a Bool', () {
expect(constants[2].isBool, isTrue);
expect(constants[2].boolValue, true);
});

test('should read a Null', () {
expect(constants[3].isNull, isTrue);
});

test('should read an arbitrary object', () {
final constant = constants[4];
expect(constant.read('aString').stringValue, 'Hello');
expect(constant.read('aInt').intValue, 1234);
expect(constant.read('aBool').boolValue, true);
expect(constant.read('aNull').isNull, isTrue);

final nested = constant.read('nested');
expect(nested.read('aString').isNull, isTrue);
expect(nested.read('aInt').isNull, isTrue);
expect(nested.read('aBool').isNull, isTrue);
});

test('should read from a super object', () {
final constant = constants[5];
expect(constant.read('aString').stringValue, 'Super Hello');
});
});
}