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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 8.12.8-wip

- Support generating from classes and enums that use the `new` syntax for
constructors.

# 8.12.7

- Allow `analyzer 13.0.0` and `analyzer 14.0.0`
Expand Down
8 changes: 6 additions & 2 deletions built_value_generator/lib/src/enum_source_class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,15 @@ abstract class EnumSourceClass
'const ${RegExp.escape(name)}._\\((?:final )?String name\\) : super\\(name\\);',
);
var expectedCode217 = RegExp(
'const ${RegExp.escape(name)}._\\(super.name\\);',
'const (?:${RegExp.escape(name)}\\.|new )_\\(super\\.name\\);',
);
var expectedCodeNew = RegExp(
'const new _\\((?:final )?String name\\) : super\\(name\\);',
);
return constructors.length == 1 &&
(constructors.single.contains(expectedCode) ||
constructors.single.contains(expectedCode217))
constructors.single.contains(expectedCode217) ||
constructors.single.contains(expectedCodeNew))
? <String>[]
: <String>[
'Have exactly one constructor: '
Expand Down
73 changes: 55 additions & 18 deletions built_value_generator/lib/src/value_source_class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,7 @@ abstract class ValueSourceClass
}

@memoized
BuiltList<ConstructorDeclaration> get valueClassConstructors =>
BuiltList<ConstructorDeclaration>(
BuiltList<AstNode> get valueClassConstructors => BuiltList<AstNode>(
element.constructors
.where(
(constructor) =>
Expand All @@ -296,25 +295,24 @@ abstract class ValueSourceClass
.map(
(constructor) => parsedLibrary
.getFragmentDeclaration(constructor.firstFragment)!
.node as ConstructorDeclaration,
.node,
),
);

@memoized
BuiltList<ConstructorDeclaration> get valueClassFactories =>
BuiltList<ConstructorDeclaration>(
BuiltList<AstNode> get valueClassFactories => BuiltList<AstNode>(
element.constructors.where((constructor) => constructor.isFactory).map(
(factory) => parsedLibrary
.getFragmentDeclaration(factory.firstFragment)!
.node as ConstructorDeclaration,
.node,
),
);

@memoized
bool get builderClassIsAbstract => builderElement!.isAbstract;

@memoized
BuiltList<String> get builderClassConstructors => BuiltList<String>(
BuiltList<AstNode> get builderClassConstructors => BuiltList<AstNode>(
builderElement!.constructors
.where(
(constructor) =>
Expand All @@ -323,8 +321,7 @@ abstract class ValueSourceClass
.map(
(constructor) => parsedLibrary
.getFragmentDeclaration(constructor.firstFragment)!
.node
.toSource(),
.node,
),
);

Expand Down Expand Up @@ -669,6 +666,14 @@ abstract class ValueSourceClass
}

final expectedConstructor = '$name._()';
bool constructorCheck(AstNode c) {
if (c is! ConstructorDeclaration) return false;
if (c.name?.lexeme != '_') return false;
if (c.parameters.parameters.isNotEmpty) return false;
if (c.typeName != null && c.typeName!.toSource() != name) return false;
return true;
}

if (valueClassConstructors.isEmpty) {
result.add(
GeneratorError(
Expand All @@ -683,7 +688,7 @@ abstract class ValueSourceClass
} else if (valueClassConstructors.length > 1) {
var found = false;
for (var constructor in valueClassConstructors) {
if (constructor.toSource().contains(expectedConstructor)) {
if (constructorCheck(constructor)) {
found = true;
} else {
result.add(
Expand All @@ -709,14 +714,18 @@ abstract class ValueSourceClass
),
);
}
} else if (!(valueClassConstructors.single.toSource().contains(
expectedConstructor,
))) {
} else if (!constructorCheck(valueClassConstructors.single)) {
final isExactPrimary = valueClassConstructors.single
is! ConstructorDeclaration &&
(valueClassConstructors.single.toSource() == expectedConstructor ||
valueClassConstructors.single.toSource() == '._()' ||
valueClassConstructors.single.toSource() == '_()');
result.add(
GeneratorError(
(b) => b
..message =
'Make class have exactly one constructor: $expectedConstructor'
..message = isExactPrimary
? '$expectedConstructor must go in the body.'
: 'Make class have exactly one constructor: $expectedConstructor'
..offset = valueClassConstructors.single.offset
..length = valueClassConstructors.single.length
..fix = expectedConstructor + ';',
Expand Down Expand Up @@ -816,8 +825,33 @@ abstract class ValueSourceClass

if (settings.instantiable) {
final expectedConstructor = '${name}Builder._()';
if (builderClassConstructors.length != 1 ||
!(builderClassConstructors.single.contains(expectedConstructor))) {
bool builderConstructorCheck(AstNode c) {
if (c is! ConstructorDeclaration) return false;
if (c.name?.lexeme != '_') return false;
if (c.parameters.parameters.isNotEmpty) return false;
if (c.typeName != null && c.typeName!.toSource() != '${name}Builder') {
return false;
}
return true;
}

if (builderClassConstructors.length == 1 &&
!builderConstructorCheck(builderClassConstructors.single)) {
final isExactPrimary =
builderClassConstructors.single is! ConstructorDeclaration &&
(builderClassConstructors.single.toSource() ==
expectedConstructor ||
builderClassConstructors.single.toSource() == '._()' ||
builderClassConstructors.single.toSource() == '_()');
result.add(
GeneratorError(
(b) => b
..message = isExactPrimary
? '$expectedConstructor must go in the body.'
: 'Make builder class have exactly one constructor: $expectedConstructor;',
),
);
} else if (builderClassConstructors.length != 1) {
result.add(
GeneratorError(
(b) => b
Expand All @@ -841,8 +875,11 @@ abstract class ValueSourceClass
if (settings.instantiable) {
final expectedFactory =
'factory ${name}Builder() = ${implName}Builder$_generics;';
final expectedFactoryNew =
'factory ${name}Builder.new() = ${implName}Builder$_generics;';
if (builderClassFactories.length != 1 ||
!builderClassFactories.single.contains(expectedFactory)) {
(!builderClassFactories.single.contains(expectedFactory) &&
!builderClassFactories.single.contains(expectedFactoryNew))) {
result.add(
GeneratorError(
(b) => b
Expand Down
12 changes: 6 additions & 6 deletions built_value_generator/lib/src/value_source_class.g.dart

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

147 changes: 147 additions & 0 deletions built_value_generator/test/built_value_generator_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,103 @@ abstract class ValueBuilder implements Builder<Value, ValueBuilder> {
}'''), contains('1. Make class have exactly one constructor: Value._();'));
});

test('to fix invalid primary constructor on value class', () async {
expect(await generate('''library value;
import 'package:built_value/built_value.dart';
part 'value.g.dart';
abstract class Value.other() implements Built<Value, ValueBuilder> {
factory Value([void Function(ValueBuilder) updates]) = _\$Value;
}
abstract class ValueBuilder implements Builder<Value, ValueBuilder> {
ValueBuilder._();
factory ValueBuilder() = _\$ValueBuilder;
}'''), contains('1. Make class have exactly one constructor: Value._()'));
});

test('to reject exact Value._() primary constructor', () async {
final result = await generate('''library value;
import 'package:built_value/built_value.dart';
part 'value.g.dart';
abstract class Value._() implements Built<Value, ValueBuilder> {
factory Value([void Function(ValueBuilder) updates]) = _\$Value;
}
abstract class ValueBuilder implements Builder<Value, ValueBuilder> {
ValueBuilder._();
factory ValueBuilder() = _\$ValueBuilder;
}''');
expect(result, contains('1. Value._() must go in the body.'));
});

test('to reject exact ValueBuilder._() primary constructor', () async {
final result = await generate('''library value;
import 'package:built_value/built_value.dart';
part 'value.g.dart';
abstract class Value implements Built<Value, ValueBuilder> {
Value._();
factory Value([void Function(ValueBuilder) updates]) = _\$Value;
}
abstract class ValueBuilder._() implements Builder<Value, ValueBuilder> {
factory ValueBuilder() = _\$ValueBuilder;
}''');
expect(result, contains('1. ValueBuilder._() must go in the body.'));
});

test('to fix primary constructor with parameters on value class',
() async {
expect(await generate('''library value;
import 'package:built_value/built_value.dart';
part 'value.g.dart';
abstract class Value._(int x) implements Built<Value, ValueBuilder> {
factory Value([void Function(ValueBuilder) updates]) = _\$Value;
}
abstract class ValueBuilder implements Builder<Value, ValueBuilder> {
ValueBuilder._();
factory ValueBuilder() = _\$ValueBuilder;
}'''), contains('1. Make class have exactly one constructor: Value._()'));
});

test('to fix new constructor with parameters on value class', () async {
expect(await generate('''library value;
import 'package:built_value/built_value.dart';
part 'value.g.dart';
abstract class Value implements Built<Value, ValueBuilder> {
new _(int x);
factory Value([void Function(ValueBuilder) updates]) = _\$Value;
}
abstract class ValueBuilder implements Builder<Value, ValueBuilder> {
ValueBuilder._();
factory ValueBuilder() = _\$ValueBuilder;
}'''), contains('1. Make class have exactly one constructor: Value._()'));
});

test('to remove primary constructor from non-instantiable value class',
() async {
expect(await generate('''library value;
import 'package:built_value/built_value.dart';
part 'value.g.dart';
@BuiltValue(instantiable: false)
abstract class Value._() implements Built<Value, ValueBuilder> {
}
abstract class ValueBuilder implements Builder<Value, ValueBuilder> {
}'''), contains('1. Remove all constructors or remove "instantiable: false".'));
});

test(
'to remove extra constructor when value class has primary constructor',
() async {
expect(await generate('''library value;
import 'package:built_value/built_value.dart';
part 'value.g.dart';
abstract class Value._() implements Built<Value, ValueBuilder> {
Value.other();
factory Value([void Function(ValueBuilder) updates]) = _\$Value;
}
abstract class ValueBuilder implements Builder<Value, ValueBuilder> {
ValueBuilder._();
factory ValueBuilder() = _\$ValueBuilder;
}'''), contains('1. Remove invalid constructor.'));
});

test('to add constructor when there is synthetic constructor', () async {
expect(await generate('''library value;
import 'package:built_value/built_value.dart';
Expand Down Expand Up @@ -578,6 +675,56 @@ abstract class ValueBuilder implements Builder<Value, ValueBuilder> {
'have exactly one constructor: ValueBuilder._();'));
});

test('to fix invalid primary constructor on builder class', () async {
expect(
await generate('''library value;
import 'package:built_value/built_value.dart';
part 'value.g.dart';
abstract class Value implements Built<Value, ValueBuilder> {
Value._();
factory Value([void Function(ValueBuilder) updates]) = _\$Value;
}
abstract class ValueBuilder.other() implements Builder<Value, ValueBuilder> {
factory ValueBuilder() = _\$ValueBuilder;
}'''),
contains('1. Make builder class '
'have exactly one constructor: ValueBuilder._();'));
});

test('to fix primary constructor with parameters on builder class',
() async {
expect(
await generate('''library value;
import 'package:built_value/built_value.dart';
part 'value.g.dart';
abstract class Value implements Built<Value, ValueBuilder> {
Value._();
factory Value([void Function(ValueBuilder) updates]) = _\$Value;
}
abstract class ValueBuilder._(int x) implements Builder<Value, ValueBuilder> {
factory ValueBuilder() = _\$ValueBuilder;
}'''),
contains('1. Make builder class '
'have exactly one constructor: ValueBuilder._();'));
});

test('to fix new constructor with parameters on builder class', () async {
expect(
await generate('''library value;
import 'package:built_value/built_value.dart';
part 'value.g.dart';
abstract class Value implements Built<Value, ValueBuilder> {
Value._();
factory Value([void Function(ValueBuilder) updates]) = _\$Value;
}
abstract class ValueBuilder implements Builder<Value, ValueBuilder> {
new _(int x);
factory ValueBuilder() = _\$ValueBuilder;
}'''),
contains('1. Make builder class '
'have exactly one constructor: ValueBuilder._();'));
});

test('constructor for builder class with synthetic constructor',
() async {
expect(
Expand Down
Loading