Skip to content

Commit

Permalink
Merge pull request #32 from davidmorgan/generate-builder
Browse files Browse the repository at this point in the history
Generate builder if none is written by hand.
  • Loading branch information
davidmorgan authored Sep 19, 2016
2 parents 2bdc05e + 30a627b commit ce49057
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 ce49057

Please sign in to comment.