Skip to content

Commit

Permalink
Generate builder if none is written by hand.
Browse files Browse the repository at this point in the history
toString appends commas.
Improve examples and tests.
Fix double null checking.
  • Loading branch information
davidmorgan committed Sep 19, 2016
1 parent f3bf23d commit 30a627b
Show file tree
Hide file tree
Showing 30 changed files with 978 additions and 446 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 0.1.3

- Generate builder if it's not written by hand.
- Make toString append commas for improved clarity.
- Improve examples and tests.
- Fix double null checking.

## 0.1.2

- Refactor generator to split into logical classes.
Expand Down
2 changes: 1 addition & 1 deletion built_value/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: built_value
version: 0.1.2
version: 0.1.3
description: >
Value types with builders. This library is the runtime dependency.
authors:
Expand Down
71 changes: 51 additions & 20 deletions built_value_generator/lib/src/source_class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ abstract class SourceClass implements Built<SourceClass, SourceClassBuilder> {
.map((constructor) => constructor.computeNode().toSource()))
..valueClassFactories.replace(classElement.constructors
.where((constructor) => constructor.isFactory)
.map((factory) => factory.computeNode().toSource()));
.map((factory) => factory.computeNode().toSource()))
..fields.replace(
SourceField.fromClassElements(classElement, builderClassElement));

if (hasBuilder) {
result
..fields.replace(
SourceField.fromClassElements(classElement, builderClassElement))
..builderClassIsAbstract = builderClassElement.isAbstract
..builderClassConstructors.replace(builderClassElement.constructors
.where((constructor) =>
Expand Down Expand Up @@ -127,10 +127,7 @@ abstract class SourceClass implements Built<SourceClass, SourceClassBuilder> {

Iterable<String> _checkBuilderClass() {
final result = <String>[];

if (!hasBuilder) {
return <String>['Add abstract class: ${name}Builder'];
}
if (!hasBuilder) return result;

if (!builderClassIsAbstract) {
result.add('Make builder class abstract.');
Expand All @@ -154,6 +151,7 @@ abstract class SourceClass implements Built<SourceClass, SourceClassBuilder> {
}

Iterable<String> _checkFieldList() {
if (!hasBuilder) return <String>[];
return fields.any((field) => !field.builderFieldExists)
? [
'Make builder have exactly these fields: ' +
Expand All @@ -172,6 +170,7 @@ abstract class SourceClass implements Built<SourceClass, SourceClassBuilder> {
for (final field in fields) {
result.writeln('final ${field.type} ${field.name};');
}
result.writeln();

if (fields.isEmpty) {
result.write('_\$$name._() : super._() {');
Expand All @@ -184,14 +183,25 @@ abstract class SourceClass implements Built<SourceClass, SourceClassBuilder> {
result.writeln("if (${field.name} == null) "
"throw new ArgumentError('null ${field.name}');");
}
result.write('}');
result.writeln('}');
result.writeln();

result.writeln('factory _\$$name([updates(${name}Builder b)]) '
'=> (new ${name}Builder()..update(updates)).build();');
result.writeln();

result.writeln('$name rebuild(updates(${name}Builder b)) '
'=> (toBuilder()..update(updates)).build();');
result.writeln('_\$${name}Builder toBuilder() '
'=> new _\$${name}Builder()..replace(this);');
result.writeln();

if (hasBuilder) {
result.writeln('_\$${name}Builder toBuilder() '
'=> new _\$${name}Builder()..replace(this);');
} else {
result.writeln('${name}Builder toBuilder() '
'=> new ${name}Builder()..replace(this);');
}
result.writeln();

result.writeln('bool operator==(other) {');
result.writeln(' if (other is! $name) return false;');
Expand All @@ -205,6 +215,7 @@ abstract class SourceClass implements Built<SourceClass, SourceClassBuilder> {
result.writeln(';');
}
result.writeln('}');
result.writeln();

result.writeln('int get hashCode {');
if (fields.length == 0) {
Expand All @@ -215,42 +226,62 @@ abstract class SourceClass implements Built<SourceClass, SourceClassBuilder> {
result.writeln(']);');
}
result.writeln('}');
result.writeln();

result.writeln('String toString() {');
if (fields.length == 0) {
result.writeln("return '$name {}';");
} else {
result.writeln("return '$name {'");
result.writeln(fields
.map((field) => "'${field.name}=\${${field.name}.toString()}\\n'")
.map((field) => "'${field.name}=\${${field.name}.toString()},\\n'")
.join(''));
result.writeln("'}'");
result.writeln(';');
}
result.writeln('}');
result.writeln();

result.writeln('}');

result.writeln('class _\$${name}Builder extends ${name}Builder {');
result.writeln();
result.writeln('_\$${name}Builder() : super._();');
if (hasBuilder) {
result.writeln('class _\$${name}Builder extends ${name}Builder {');
result.writeln('_\$${name}Builder() : super._();');
} else {
result.writeln(
'class ${name}Builder implements Builder<$name, ${name}Builder>{');
result.writeln('${name}Builder();');
}

if (!hasBuilder) {
for (final field in fields) {
// Nested builders are initialized to an empty builder, unless the
// field is nullable.
if (field.isNestedBuilder && !field.isNullable) {
result.writeln('${field.typeInBuilder} ${field.name} = new ${field.typeInBuilder}();');
} else {
result.writeln('${field.typeInBuilder} ${field.name};');
}
}
result.writeln();
}

result.writeln('void replace(${name} other) {');
result.writeln((fields.map((field) {
final superOrThis = hasBuilder ? 'super' : 'this';
return field.isNestedBuilder
? 'super.${field.name} = other.${field.name}?.toBuilder();'
: 'super.${field.name} = other.${field.name};';
? '$superOrThis.${field.name} = other.${field.name}?.toBuilder();'
: '$superOrThis.${field.name} = other.${field.name};';
}))
.join('\n'));
result.writeln('}');
result.writeln();

result.writeln('void update(updates(${name}Builder b)) {'
' if (updates != null) updates(this); }');
result.writeln();

result.writeln('$name build() {');
for (final field in fields.where((field) => !field.isNullable)) {
result.writeln("if (${field.name} == null) "
"throw new ArgumentError('null ${field.name}');");
}
result.writeln('return new _\$$name._(');
result.write(fields.map((field) {
return field.isNestedBuilder
Expand Down
45 changes: 16 additions & 29 deletions built_value_generator/lib/src/source_class.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 63 additions & 14 deletions built_value_generator/lib/src/source_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,27 @@
library built_value_generator.source_field;

import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';

part 'source_field.g.dart';

BuiltSet<String> _builtCollectionNames = new BuiltSet<String>([
'BuiltList',
'BuiltListMultimap',
'BuiltMap',
'BuiltSet',
'BuiltSetMultimap',
]);

abstract class SourceField implements Built<SourceField, SourceFieldBuilder> {
String get name;
String get type;
bool get isGetter;
bool get isNullable;
bool get builderFieldExists;
bool get builderFieldIsNormalField;
@nullable
String get typeInBuilder;
bool get isNestedBuilder;

Expand All @@ -26,22 +34,35 @@ abstract class SourceField implements Built<SourceField, SourceFieldBuilder> {

factory SourceField.fromFieldElements(
FieldElement fieldElement, FieldElement builderFieldElement) {
return new SourceField((b) => b
final result = new SourceFieldBuilder();
final builderFieldExists = builderFieldElement != null;
final type = fieldElement.getter.returnType.displayName;
result
..name = fieldElement.displayName
..type = fieldElement.getter.returnType.displayName
..typeInBuilder = builderFieldElement?.getter?.returnType?.displayName
..type = type
..isGetter =
fieldElement.getter != null && !fieldElement.getter.isSynthetic
..isNullable = fieldElement.getter.metadata.any(
(metadata) => metadata.constantValue.toStringValue() == 'nullable')
..builderFieldExists = builderFieldElement != null
..builderFieldIsNormalField = builderFieldElement != null &&
builderFieldElement.getter != null &&
!builderFieldElement.getter.isAbstract &&
builderFieldElement.getter.isSynthetic
..isNestedBuilder = builderFieldElement?.getter?.returnType?.displayName
?.contains('Builder') ??
false);
(metadata) => metadata.constantValue.toStringValue() == 'nullable');

if (builderFieldExists) {
result
..builderFieldExists = true
..builderFieldIsNormalField = builderFieldElement.getter != null &&
!builderFieldElement.getter.isAbstract &&
builderFieldElement.getter.isSynthetic
..typeInBuilder = builderFieldElement.getter?.returnType?.displayName
..isNestedBuilder = builderFieldElement.getter?.returnType?.displayName
?.contains('Builder') ??
false;
} else {
result
..builderFieldExists = false
..builderFieldIsNormalField = true
..typeInBuilder = _toBuilderType(fieldElement.getter.returnType)
..isNestedBuilder = _needsNestedBuilder(fieldElement.getter.returnType);
}
return result.build();
}

static BuiltList<SourceField> fromClassElements(
Expand All @@ -52,14 +73,42 @@ abstract class SourceField implements Built<SourceField, SourceFieldBuilder> {
if (!field.isStatic &&
field.getter != null &&
(field.getter.isAbstract || field.getter.isSynthetic)) {
final builderField = builderClassElement.getField(field.name);
final builderField = builderClassElement?.getField(field.name);
result.add(new SourceField.fromFieldElements(field, builderField));
}
}

return result.build();
}

static bool _needsNestedBuilder(DartType type) {
return _isBuiltValue(type) || _isBuiltCollection(type);
}

static bool _isBuiltValue(DartType type) {
if (type.element is! ClassElement) return false;
return (type.element as ClassElement)
.allSupertypes
.any((interfaceType) => interfaceType.name == 'Built');
}

static bool _isBuiltCollection(DartType type) {
return _builtCollectionNames
.any((name) => type.displayName.startsWith('${name}<'));
}

static String _toBuilderType(DartType type) {
if (_isBuiltCollection(type)) {
return type.displayName
.replaceFirst('Built', '')
.replaceFirst('<', 'Builder<');
} else if (_isBuiltValue(type)) {
return '${type.displayName}Builder';
} else {
return type.displayName;
}
}

Iterable<String> computeErrors() {
final result = <String>[];

Expand Down
Loading

0 comments on commit 30a627b

Please sign in to comment.