Skip to content

Commit c071586

Browse files
committed
experiment: generalize parameter makeGeneratedClassesFinal to classModifiers
1 parent db60fd0 commit c071586

File tree

6 files changed

+128
-32
lines changed

6 files changed

+128
-32
lines changed

packages/freezed/lib/src/models.dart

Lines changed: 82 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,8 +1041,15 @@ class Class {
10411041
return '$escapedElementName$generics';
10421042
}
10431043

1044-
bool get shouldBeFinal =>
1045-
options.annotation.makeGeneratedClassesFinal ?? false;
1044+
String get classModifiers {
1045+
final modifiers = options.annotation.classModifiers;
1046+
1047+
if (modifiers.isEmpty) {
1048+
return '';
1049+
}
1050+
1051+
return '${modifiers.map((modifier) => modifier.lexeme).join(' ')} ';
1052+
}
10461053
}
10471054

10481055
class PropertyList {
@@ -1080,6 +1087,7 @@ class ClassConfig {
10801087
required this.asUnmodifiableCollections,
10811088
required this.genericArgumentFactories,
10821089
required this.annotation,
1090+
required this.modifiers,
10831091
});
10841092

10851093
static ClassConfig from(
@@ -1104,6 +1112,9 @@ class ClassConfig {
11041112
resolvedAnnotation.makeCollectionsUnmodifiable!,
11051113
genericArgumentFactories: resolvedAnnotation.genericArgumentFactories,
11061114
annotation: resolvedAnnotation,
1115+
modifiers:
1116+
resolvedAnnotation.classModifiers.toNormalized()
1117+
..throwIfInvalid(declaration.declaredElement),
11071118
);
11081119
}
11091120

@@ -1167,11 +1178,34 @@ class ClassConfig {
11671178
},
11681179
orElse: () => globalConfigs.unionValueCase,
11691180
),
1170-
makeGeneratedClassesFinal: annotation.decodeField(
1171-
'makeGeneratedClassesFinal',
1172-
decode: (obj) => obj.toBoolValue(),
1173-
orElse: () => globalConfigs.makeGeneratedClassesFinal,
1174-
),
1181+
classModifiers:
1182+
annotation
1183+
.decodeField(
1184+
'classModifiers',
1185+
decode: (obj) {
1186+
final dartValues = obj.toListValue();
1187+
1188+
if (dartValues == null) {
1189+
return const <FreezedClassModifier>[];
1190+
}
1191+
1192+
final modifiers = <FreezedClassModifier>[];
1193+
1194+
for (final value in dartValues) {
1195+
final enumIndex = value.getField('index')?.toIntValue();
1196+
1197+
if (enumIndex == null) {
1198+
continue;
1199+
}
1200+
1201+
modifiers.add(FreezedClassModifier.values[enumIndex]);
1202+
}
1203+
1204+
return modifiers;
1205+
},
1206+
orElse: () => globalConfigs.classModifiers,
1207+
)
1208+
.toNormalized(),
11751209
);
11761210
}
11771211

@@ -1182,6 +1216,7 @@ class ClassConfig {
11821216
final bool asUnmodifiableCollections;
11831217
final bool genericArgumentFactories;
11841218
final Freezed annotation;
1219+
final List<FreezedClassModifier> modifiers;
11851220
}
11861221

11871222
extension on ConstructorDeclaration {
@@ -1255,3 +1290,43 @@ extension on DartObject {
12551290
return decode(field);
12561291
}
12571292
}
1293+
1294+
extension on FreezedClassModifier {
1295+
String get lexeme {
1296+
return switch (this) {
1297+
FreezedClassModifier.Final => 'final',
1298+
FreezedClassModifier.Base => 'base',
1299+
FreezedClassModifier.Mixin => 'mixin',
1300+
};
1301+
}
1302+
}
1303+
1304+
extension on List<FreezedClassModifier> {
1305+
List<FreezedClassModifier> toNormalized() {
1306+
return toSet().toList(growable: false)..sortByCompare(
1307+
(modifier) => switch (modifier) {
1308+
FreezedClassModifier.Final => 0,
1309+
FreezedClassModifier.Base => 0,
1310+
FreezedClassModifier.Mixin => 1,
1311+
},
1312+
(left, right) => left.compareTo(right),
1313+
);
1314+
}
1315+
1316+
void throwIfInvalid(Element? element) {
1317+
switch (this) {
1318+
case []:
1319+
case [FreezedClassModifier.Base]:
1320+
case [FreezedClassModifier.Base, FreezedClassModifier.Mixin]:
1321+
case [FreezedClassModifier.Mixin]:
1322+
case [FreezedClassModifier.Final]:
1323+
return;
1324+
1325+
default:
1326+
throw InvalidGenerationSourceError(
1327+
'invalid combination of class modifiers: ${map((modifier) => modifier.lexeme).join(' ')}',
1328+
element: element,
1329+
);
1330+
}
1331+
}
1332+
}

packages/freezed/lib/src/templates/concrete_template.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class Concrete {
4949
/// @nodoc
5050
$jsonSerializable
5151
${constructor.decorators.join('\n')}
52-
${data.shouldBeFinal ? 'final ' : ''}class ${constructor.redirectedName}${data.genericsDefinitionTemplate} $_concreteSuper {
52+
${data.classModifiers}class ${constructor.redirectedName}${data.genericsDefinitionTemplate} $_concreteSuper {
5353
$_concreteConstructor
5454
$_concreteFromJsonConstructor
5555

packages/freezed/test/integration/finalized.dart

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,34 @@ import 'package:freezed_annotation/freezed_annotation.dart';
22

33
part 'finalized.freezed.dart';
44

5-
@Freezed(makeGeneratedClassesFinal: true)
6-
sealed class SealedWithFinalFoo with _$SealedWithFinalFoo {
5+
@Freezed(classModifiers: [FreezedClassModifier.Final])
6+
abstract class SealedWithFinalFoo with _$SealedWithFinalFoo {
77
factory SealedWithFinalFoo() = _SealedWithFinalFoo;
88
}
99

10-
@Freezed(makeGeneratedClassesFinal: true)
10+
@Freezed(classModifiers: [FreezedClassModifier.Final])
1111
sealed class SealedWithFinalBar with _$SealedWithFinalBar {
1212
factory SealedWithFinalBar() = _SealedWithFinalBar;
1313
}
1414

15-
@Freezed(makeGeneratedClassesFinal: true)
15+
@Freezed(classModifiers: [FreezedClassModifier.Final])
1616
sealed class SealedWithFinalAbc with _$SealedWithFinalAbc {
1717
factory SealedWithFinalAbc.a() = SealedWithFinalAbcA;
1818
factory SealedWithFinalAbc.b() = SealedWithFinalAbcB;
1919
factory SealedWithFinalAbc.c() = SealedWithFinalAbcC;
2020
}
2121

22-
@Freezed(makeGeneratedClassesFinal: true)
22+
@Freezed(classModifiers: [FreezedClassModifier.Final])
2323
abstract class AbstractWithFinalFoo with _$AbstractWithFinalFoo {
2424
factory AbstractWithFinalFoo() = _AbstractWithFinalFoo;
2525
}
2626

27-
@Freezed(makeGeneratedClassesFinal: true)
27+
@Freezed(classModifiers: [FreezedClassModifier.Final])
2828
abstract class AbstractWithFinalBar with _$AbstractWithFinalBar {
2929
factory AbstractWithFinalBar() = _AbstractWithFinalBar;
3030
}
3131

32-
@Freezed(makeGeneratedClassesFinal: true)
32+
@Freezed(classModifiers: [FreezedClassModifier.Final])
3333
abstract class AbstractWithFinalAbc with _$AbstractWithFinalAbc {
3434
factory AbstractWithFinalAbc.a() = AbstractWithFinalAbcA;
3535
factory AbstractWithFinalAbc.b() = AbstractWithFinalAbcB;
@@ -42,15 +42,15 @@ sealed class SuperFoo {
4242

4343
final class CustomFoo extends SuperFoo {}
4444

45-
@Freezed(makeGeneratedClassesFinal: true)
45+
@Freezed(classModifiers: [FreezedClassModifier.Final])
4646
sealed class SealedWithFinalSuperFoo extends SuperFoo
4747
with _$SealedWithFinalSuperFoo {
4848
const SealedWithFinalSuperFoo._() : super();
4949

5050
factory SealedWithFinalSuperFoo() = _SealedWithFinalSuperFoo;
5151
}
5252

53-
@Freezed(makeGeneratedClassesFinal: true)
53+
@Freezed(classModifiers: [FreezedClassModifier.Final])
5454
abstract class AbstractWithFinalSuperFoo extends SuperFoo
5555
with _$AbstractWithFinalSuperFoo {
5656
const AbstractWithFinalSuperFoo._() : super();

packages/freezed_annotation/lib/freezed_annotation.dart

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ class Freezed {
8484
this.makeCollectionsUnmodifiable,
8585
this.addImplicitFinal = true,
8686
this.genericArgumentFactories = false,
87-
this.makeGeneratedClassesFinal,
87+
this.classModifiers = const [],
8888
});
8989

9090
/// Decode the options from a build.yaml
@@ -341,22 +341,24 @@ class Freezed {
341341
/// ```
342342
final bool genericArgumentFactories;
343343

344-
/// Whether to add `final` modifiers to the generated classes.
344+
/// Modifiers that are added the generated classes.
345345
///
346-
/// Defaults to false.
346+
/// Defaults to an empty list.
347+
///
348+
/// Can be used to make the generated classes `final`/'base'/'mixin'/'base mixin'/....
349+
/// See https://dart.dev/language/class-modifiers for more information on class modifiers.
347350
///
348-
/// This makes the generated classes `final` by default,
349-
/// so when using them in a switch statement, the analzyer will warn you
350-
/// if you try to match against a pattern that will never match the type.
351+
/// For example to make the compiler/analyzer warn about unnecessary clauses in switch statements,
352+
/// one could add the 'final' modifier to generated classes:
351353
///
352354
/// ```dart
353-
/// @Freezed(makeGeneratedClassesFinal: true)
355+
/// @Freezed(classModifiers: ['final'])
354356
/// sealed class Foo with _$Foo {
355357
/// const Foo._();
356358
/// const factory Foo() = _Foo;
357359
/// }
358360
///
359-
/// @Freezed(makeGeneratedClassesFinal: true)
361+
/// @Freezed(classModifiers: ['final'])
360362
/// sealed class Bar with _$Bar {
361363
/// const Bar._();
362364
/// const factory Bar() = _Bar;
@@ -376,7 +378,7 @@ class Freezed {
376378
/// break;
377379
/// }
378380
/// ```
379-
final bool? makeGeneratedClassesFinal;
381+
final List<FreezedClassModifier> classModifiers;
380382
}
381383

382384
/// Defines an immutable data-class.
@@ -569,3 +571,10 @@ enum FreezedUnionCase {
569571
/// Encodes a constructor named `screamingSnakeCase` with a JSON value `SCREAMING_SNAKE_CASE`.
570572
screamingSnake,
571573
}
574+
575+
@JsonEnum(fieldRename: FieldRename.snake)
576+
enum FreezedClassModifier {
577+
Final,
578+
Base,
579+
Mixin,
580+
}

packages/freezed_annotation/lib/freezed_annotation.g.dart

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/freezed_annotation/test/freezed_test.dart

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ void main() {
1818
expect(defaultValue.makeCollectionsUnmodifiable, isTrue);
1919
expect(defaultValue.addImplicitFinal, isTrue);
2020
expect(defaultValue.genericArgumentFactories, isFalse);
21-
expect(defaultValue.makeGeneratedClassesFinal, isNull);
21+
expect(defaultValue.classModifiers, []);
2222
});
2323

2424
test('explicit', () {
@@ -34,7 +34,7 @@ void main() {
3434
'make_collections_unmodifiable': false,
3535
'add_implicit_final': false,
3636
'generic_argument_factories': true,
37-
'make_generated_classes_final': true,
37+
'class_modifiers': ['base', 'mixin'],
3838
});
3939

4040
expect(overrides.copyWith, isFalse);
@@ -48,19 +48,22 @@ void main() {
4848
expect(overrides.makeCollectionsUnmodifiable, isFalse);
4949
expect(overrides.addImplicitFinal, isFalse);
5050
expect(overrides.genericArgumentFactories, isTrue);
51-
expect(overrides.makeGeneratedClassesFinal, isTrue);
51+
expect(overrides.classModifiers, [
52+
FreezedClassModifier.Base,
53+
FreezedClassModifier.Mixin,
54+
]);
5255
});
5356
});
5457
});
5558

5659
test('freezed', () {
57-
expect(freezed.makeGeneratedClassesFinal, isNull);
60+
expect(freezed.classModifiers, []);
5861
});
5962

6063
test('unfreezed', () {
6164
expect(unfreezed.makeCollectionsUnmodifiable, false);
6265
expect(unfreezed.equal, false);
6366
expect(unfreezed.addImplicitFinal, false);
64-
expect(unfreezed.makeGeneratedClassesFinal, isNull);
67+
expect(unfreezed.classModifiers, []);
6568
});
6669
}

0 commit comments

Comments
 (0)