From 9150faf948457aa414a9ffd678a5a0a5f766a56c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Wyrowi=C5=84ski?= Date: Tue, 2 Dec 2025 13:57:26 +0100 Subject: [PATCH 1/3] Add support for making external types draftable --- packages/draft/README.md | 16 ++++++++ packages/draft_builder/lib/src/generator.dart | 11 +++++- .../draft_builder/test/extension_test.dart | 37 +++++++++++++++++++ .../test/integration/extension.dart | 12 ++++++ 4 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 packages/draft_builder/test/extension_test.dart create mode 100644 packages/draft_builder/test/integration/extension.dart diff --git a/packages/draft/README.md b/packages/draft/README.md index 051d862..f34ca7b 100644 --- a/packages/draft/README.md +++ b/packages/draft/README.md @@ -143,6 +143,22 @@ final c = C([1, 2, 3]).produce((draft) { Draft is unopinionated and does not provide any sort of equality checking out of the box. If you want equality checking, consider using [equatable](https://pub.dev/packages/equatable) +### External classes + +You can make classes from external packages draftable by annotating an extension on them with `@draft`: + +```dart +@draft +extension on A {} + +// Class defined in an external package. +class A { + final B b; + A(this.b); +} +``` +Once annotated, the external class behaves like any other draftable type and supports all Draft features. + ## Contributing If you like the package and want to contribute, feel free to [open and issue or create a PR](https://github.com/josiahsrc/draft/tree/main). I'm always open to suggestions and improvements. diff --git a/packages/draft_builder/lib/src/generator.dart b/packages/draft_builder/lib/src/generator.dart index 3107fc1..e0182a9 100644 --- a/packages/draft_builder/lib/src/generator.dart +++ b/packages/draft_builder/lib/src/generator.dart @@ -476,10 +476,17 @@ class DraftGenerator extends GeneratorForAnnotation { ConstantReader annotation, BuildStep buildStep, ) { + final classElement = switch (element) { + // Direct class annotation. + ClassElement element => element, + // Extension on a class. + ExtensionElement(extendedType: DartType(:ClassElement element)) => element, + _ => null, + }; + // Process only classes. - if (element is! ClassElement) return ''; + if (classElement == null) return ''; - final classElement = element; final className = classElement.name; final draftClassName = _draftTypeName(className); diff --git a/packages/draft_builder/test/extension_test.dart b/packages/draft_builder/test/extension_test.dart new file mode 100644 index 0000000..cb48ae1 --- /dev/null +++ b/packages/draft_builder/test/extension_test.dart @@ -0,0 +1,37 @@ +import 'package:test/test.dart'; + +import 'common.dart'; +import 'integration/extension.dart'; + +void main() { + test('compiles', () async { + await expectLater( + compile(r''' +import 'extension.dart'; + +void main() { + BasicWithExtension(value: 1); + BasicWithExtension(value: 1).draft().save(); + BasicWithExtension(value: 1).produce((draft) { + draft.value = 2; + }); +} +'''), + completes, + ); + }); + + test('works correctly', () async { + expect(BasicWithExtension(value: 1).draft().value, 1); + expect(BasicWithExtension(value: 1).draft().save().value, 1); + expect( + BasicWithExtension(value: 1).produce((draft) { + draft.value = 2; + }).value, + 2, + ); + + final draft = BasicWithExtension(value: 1).draft()..value = 10; + expect(draft.save().value, 10); + }); +} diff --git a/packages/draft_builder/test/integration/extension.dart b/packages/draft_builder/test/integration/extension.dart new file mode 100644 index 0000000..ce724ad --- /dev/null +++ b/packages/draft_builder/test/integration/extension.dart @@ -0,0 +1,12 @@ +import 'package:draft/draft.dart'; + +part 'extension.draft.dart'; + +@draft +extension on BasicWithExtension {} + +class BasicWithExtension { + final int value; + + BasicWithExtension({required this.value}); +} From f584da0df86e5457050ee5bf290a0c657ebffc0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Wyrowi=C5=84ski?= Date: Tue, 2 Dec 2025 16:50:53 +0100 Subject: [PATCH 2/3] Update packages/draft/README.md Co-authored-by: Josiah Saunders --- packages/draft/README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/draft/README.md b/packages/draft/README.md index f34ca7b..84e6024 100644 --- a/packages/draft/README.md +++ b/packages/draft/README.md @@ -148,14 +148,10 @@ Draft is unopinionated and does not provide any sort of equality checking out of You can make classes from external packages draftable by annotating an extension on them with `@draft`: ```dart +import 'package:a_class/a_class.dart' show A; + @draft extension on A {} - -// Class defined in an external package. -class A { - final B b; - A(this.b); -} ``` Once annotated, the external class behaves like any other draftable type and supports all Draft features. From 9a1b9815788b9a87bd2962beae1004caf4191031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Wyrowi=C5=84ski?= Date: Tue, 2 Dec 2025 16:52:51 +0100 Subject: [PATCH 3/3] Update readme example --- packages/draft/README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/draft/README.md b/packages/draft/README.md index 84e6024..82bdbd5 100644 --- a/packages/draft/README.md +++ b/packages/draft/README.md @@ -148,12 +148,18 @@ Draft is unopinionated and does not provide any sort of equality checking out of You can make classes from external packages draftable by annotating an extension on them with `@draft`: ```dart -import 'package:a_class/a_class.dart' show A; +import 'package:foo_class/foo_class.dart' show Foo; @draft -extension on A {} +extension on Foo {} +``` +Once annotated, the external class behaves like any other draftable type and supports all Draft features: + +```dart +Foo(value: 1).produce((draft) { + draft.value += 1; +}); ``` -Once annotated, the external class behaves like any other draftable type and supports all Draft features. ## Contributing