From 2ebe9421fc540353315c60de96e5aa264cb1776d Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 20 Jun 2017 13:42:21 -0700 Subject: [PATCH 1/4] Add a Constant helper class. --- CHANGELOG.md | 30 +++++++ lib/source_gen.dart | 1 + lib/src/constants.dart | 171 +++++++++++++++++++++++++++++++++++++++ test/constants_test.dart | 91 +++++++++++++++++++++ 4 files changed, 293 insertions(+) create mode 100644 lib/src/constants.dart create mode 100644 test/constants_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 2299714c..ae8b8c53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,36 @@ } ``` +* Added `Constant`, 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 Constant { + factory Constant(DartObject object) => ... + + // Other methods and properties also exist. + + /// Reads[ field] from the constant as another constant value. + Constant 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. diff --git a/lib/source_gen.dart b/lib/source_gen.dart index 966bef4a..0ed8fbe9 100644 --- a/lib/source_gen.dart +++ b/lib/source_gen.dart @@ -5,6 +5,7 @@ library source_gen; export 'src/builder.dart'; +export 'src/constants.dart' show Constant; export 'src/find_type.dart' show findType; export 'src/generator.dart'; export 'src/generator_for_annotation.dart'; diff --git a/lib/src/constants.dart b/lib/src/constants.dart new file mode 100644 index 00000000..f2787c44 --- /dev/null +++ b/lib/src/constants.dart @@ -0,0 +1,171 @@ +// 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'; + +/// Always returns `null` (used as a default for `defaultTo` methods). +Null _alwaysNull() => null; + +/// 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. +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 Constant { + factory Constant(DartObject object) => + _isNull(object) ? const _NullConstant() : new _Constant(object); + + /// Returns whether this constant represents a `bool` literal. + /// + /// If `true`, [boolValue] will return either `true` or `false` (not throw). + bool get isBool; + + /// Returns this constant as a `bool` value. + /// + /// Throws [FormatException] if [isBool] is `false`. + bool get boolValue; + + /// Returns whether this constant represents an `int` literal. + /// + /// If `true`, [intValue] will return an `int` (not throw). + 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. + Constant 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()}); +} + +/// Implements a [Constant] representing a `null` value. +class _NullConstant implements Constant { + 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 + Constant read(_) => this; + + @override + bool readBool(_, {bool defaultTo(): _alwaysNull}) => defaultTo(); + + @override + int readInt(_, {int defaultTo(): _alwaysNull}) => defaultTo(); + + @override + String readString(_, {String defaultTo(): _alwaysNull}) => defaultTo(); +} + +/// Default implementation of [Constant]. +class _Constant implements Constant { + 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 => false; + + @override + bool get isString => _object.toStringValue() != null; + + @override + Constant read(String field) => + new Constant(_getFieldRecursive(_object, field)); + + @override + bool readBool(String field, {bool defaultTo()}) => + _getFieldRecursive(_object, field)?.toBoolValue() ?? defaultTo(); + + @override + int readInt(String field, {int defaultTo()}) => + _getFieldRecursive(_object, field)?.toIntValue() ?? defaultTo(); + + @override + String readString(String field, {String defaultTo()}) => + _getFieldRecursive(_object, field)?.toStringValue() ?? defaultTo(); +} diff --git a/test/constants_test.dart b/test/constants_test.dart new file mode 100644 index 00000000..e454caa7 --- /dev/null +++ b/test/constants_test.dart @@ -0,0 +1,91 @@ +// 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 constants; + + setUpAll(() async { + constants = (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'); + } + ''')) + .getLibraryByName('test_lib') + .getType('Example') + .metadata + .map((e) => new Constant(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.readString('aString'), 'Hello'); + expect(constant.readInt('aInt'), 1234); + expect(constant.readBool('aBool'), true); + expect(constant.read('aNull').isNull, isTrue); + + final nested = constant.read('nested'); + expect(nested.readString('aString', defaultTo: () => 'Nope'), 'Nope'); + expect(nested.readInt('aInt', defaultTo: () => 5678), 5678); + expect(nested.readBool('aBool', defaultTo: () => false), isFalse); + }); + + test('should read from a super object', () { + final constant = constants[5]; + expect(constant.readString('aString'), 'Super Hello'); + }); + }); +} From f0fb9c0d71cbff49ffdb6be3a81fd46bdc6d82ae Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 20 Jun 2017 15:44:16 -0700 Subject: [PATCH 2/4] Address feedback. --- lib/src/constants.dart | 10 +++------- test/constants_test.dart | 5 +++-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/src/constants.dart b/lib/src/constants.dart index f2787c44..8aa98612 100644 --- a/lib/src/constants.dart +++ b/lib/src/constants.dart @@ -11,6 +11,8 @@ Null _alwaysNull() => null; bool _isNull(DartObject object) => object?.isNull != false; /// Similar to [DartObject.getField], but traverses super classes. +/// +/// Returns `null` if ultimately [field] is never found. DartObject _getFieldRecursive(DartObject object, String field) { if (_isNull(object)) { return null; @@ -31,18 +33,12 @@ abstract class Constant { _isNull(object) ? const _NullConstant() : new _Constant(object); /// Returns whether this constant represents a `bool` literal. - /// - /// If `true`, [boolValue] will return either `true` or `false` (not throw). bool get isBool; /// Returns this constant as a `bool` value. - /// - /// Throws [FormatException] if [isBool] is `false`. bool get boolValue; /// Returns whether this constant represents an `int` literal. - /// - /// If `true`, [intValue] will return an `int` (not throw). bool get isInt; /// Returns this constant as an `int` value. @@ -148,7 +144,7 @@ class _Constant implements Constant { bool get isInt => _object.toIntValue() != null; @override - bool get isNull => false; + bool get isNull => _isNull(_object); @override bool get isString => _object.toStringValue() != null; diff --git a/test/constants_test.dart b/test/constants_test.dart index e454caa7..ba66c479 100644 --- a/test/constants_test.dart +++ b/test/constants_test.dart @@ -11,7 +11,7 @@ void main() { List constants; setUpAll(() async { - constants = (await resolveSource(r''' + final resolver = await resolveSource(r''' library test_lib; const aString = 'Hello'; @@ -43,7 +43,8 @@ void main() { class Super extends Example { const Super() : super(aString: 'Super Hello'); } - ''')) + '''); + constants = resolver .getLibraryByName('test_lib') .getType('Example') .metadata From faad379fcdec564ee46696620bdbe48b303aaf55 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 20 Jun 2017 15:48:13 -0700 Subject: [PATCH 3/4] Remove readX. --- lib/src/constants.dart | 39 --------------------------------------- test/constants_test.dart | 14 +++++++------- 2 files changed, 7 insertions(+), 46 deletions(-) diff --git a/lib/src/constants.dart b/lib/src/constants.dart index 8aa98612..173949d3 100644 --- a/lib/src/constants.dart +++ b/lib/src/constants.dart @@ -4,9 +4,6 @@ import 'package:analyzer/dart/constant/value.dart'; -/// Always returns `null` (used as a default for `defaultTo` methods). -Null _alwaysNull() => null; - /// Returns whether or not [object] is or represents a `null` value. bool _isNull(DartObject object) => object?.isNull != false; @@ -61,21 +58,6 @@ abstract class Constant { /// Reads[ field] from the constant as another constant value. Constant 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()}); } /// Implements a [Constant] representing a `null` value. @@ -105,15 +87,6 @@ class _NullConstant implements Constant { @override Constant read(_) => this; - - @override - bool readBool(_, {bool defaultTo(): _alwaysNull}) => defaultTo(); - - @override - int readInt(_, {int defaultTo(): _alwaysNull}) => defaultTo(); - - @override - String readString(_, {String defaultTo(): _alwaysNull}) => defaultTo(); } /// Default implementation of [Constant]. @@ -152,16 +125,4 @@ class _Constant implements Constant { @override Constant read(String field) => new Constant(_getFieldRecursive(_object, field)); - - @override - bool readBool(String field, {bool defaultTo()}) => - _getFieldRecursive(_object, field)?.toBoolValue() ?? defaultTo(); - - @override - int readInt(String field, {int defaultTo()}) => - _getFieldRecursive(_object, field)?.toIntValue() ?? defaultTo(); - - @override - String readString(String field, {String defaultTo()}) => - _getFieldRecursive(_object, field)?.toStringValue() ?? defaultTo(); } diff --git a/test/constants_test.dart b/test/constants_test.dart index ba66c479..9e7ea62c 100644 --- a/test/constants_test.dart +++ b/test/constants_test.dart @@ -73,20 +73,20 @@ void main() { test('should read an arbitrary object', () { final constant = constants[4]; - expect(constant.readString('aString'), 'Hello'); - expect(constant.readInt('aInt'), 1234); - expect(constant.readBool('aBool'), true); + 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.readString('aString', defaultTo: () => 'Nope'), 'Nope'); - expect(nested.readInt('aInt', defaultTo: () => 5678), 5678); - expect(nested.readBool('aBool', defaultTo: () => false), isFalse); + 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.readString('aString'), 'Super Hello'); + expect(constant.read('aString').stringValue, 'Super Hello'); }); }); } From e56bca242125f9ead4fb9ab459a1a84109064ffa Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 20 Jun 2017 21:25:18 -0700 Subject: [PATCH 4/4] Rename to ConstantReader. --- CHANGELOG.md | 10 +++++----- lib/source_gen.dart | 2 +- lib/src/constants.dart | 20 ++++++++++---------- test/constants_test.dart | 4 ++-- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae8b8c53..171bf0b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,18 +44,18 @@ } ``` -* Added `Constant`, a high-level API for reading from constant (static) values - from Dart source code (usually represented by `DartObject` from the +* 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 Constant { - factory Constant(DartObject object) => ... + abstract class ConstantReader { + factory ConstantReader(DartObject object) => ... // Other methods and properties also exist. /// Reads[ field] from the constant as another constant value. - Constant read(String field); + ConstantReader read(String field); /// Reads [field] from the constant as a boolean. /// diff --git a/lib/source_gen.dart b/lib/source_gen.dart index 0ed8fbe9..219a7405 100644 --- a/lib/source_gen.dart +++ b/lib/source_gen.dart @@ -5,7 +5,7 @@ library source_gen; export 'src/builder.dart'; -export 'src/constants.dart' show Constant; +export 'src/constants.dart' show ConstantReader; export 'src/find_type.dart' show findType; export 'src/generator.dart'; export 'src/generator_for_annotation.dart'; diff --git a/lib/src/constants.dart b/lib/src/constants.dart index 173949d3..b1b1fae5 100644 --- a/lib/src/constants.dart +++ b/lib/src/constants.dart @@ -25,8 +25,8 @@ DartObject _getFieldRecursive(DartObject object, String field) { /// /// Unlike [DartObject.getField], all `readX` methods attempt to access super /// classes for the field value if not found. -abstract class Constant { - factory Constant(DartObject object) => +abstract class ConstantReader { + factory ConstantReader(DartObject object) => _isNull(object) ? const _NullConstant() : new _Constant(object); /// Returns whether this constant represents a `bool` literal. @@ -57,11 +57,11 @@ abstract class Constant { bool get isNull; /// Reads[ field] from the constant as another constant value. - Constant read(String field); + ConstantReader read(String field); } -/// Implements a [Constant] representing a `null` value. -class _NullConstant implements Constant { +/// Implements a [ConstantReader] representing a `null` value. +class _NullConstant implements ConstantReader { const _NullConstant(); @override @@ -86,11 +86,11 @@ class _NullConstant implements Constant { bool get isString => false; @override - Constant read(_) => this; + ConstantReader read(_) => this; } -/// Default implementation of [Constant]. -class _Constant implements Constant { +/// Default implementation of [ConstantReader]. +class _Constant implements ConstantReader { final DartObject _object; const _Constant(this._object); @@ -123,6 +123,6 @@ class _Constant implements Constant { bool get isString => _object.toStringValue() != null; @override - Constant read(String field) => - new Constant(_getFieldRecursive(_object, field)); + ConstantReader read(String field) => + new ConstantReader(_getFieldRecursive(_object, field)); } diff --git a/test/constants_test.dart b/test/constants_test.dart index 9e7ea62c..81d7511c 100644 --- a/test/constants_test.dart +++ b/test/constants_test.dart @@ -8,7 +8,7 @@ import 'package:test/test.dart'; void main() { group('Constant', () { - List constants; + List constants; setUpAll(() async { final resolver = await resolveSource(r''' @@ -48,7 +48,7 @@ void main() { .getLibraryByName('test_lib') .getType('Example') .metadata - .map((e) => new Constant(e.computeConstantValue())) + .map((e) => new ConstantReader(e.computeConstantValue())) .toList(); });