Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

flutter-supabase-macro-0.0.5 #5

Merged
merged 22 commits into from
Oct 16, 2024
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
19 changes: 14 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
## 0.0.1
## 0.0.5 (https://github.com/ThomasDevApps/flutter_supabase_macro/pull/5)

Initial release :
- Creation of a `toJsonSupabase` which exclude the `primaryKey` from the `Map`
Add a named parameter for each field of the class.
For example, if class contain a field named `id` then `bool? removeId`
will be add as a named parameter for `toJsonSupabase`.

If `removeId` is not null and true then, `id` will not be add in the json.

## 0.0.4
## 0.0.4 (https://github.com/ThomasDevApps/flutter_supabase_macro/pull/4)

Only exclude `primaryKey` from the Map if :
- Can't be nullable then check that `!= null`
- The type is `String`, then check that the value `isNotEmpty`
- The type is `String`, then check that the value `isNotEmpty`


## 0.0.1 (https://github.com/ThomasDevApps/flutter_supabase_macro/pull/1)

Initial release :
- Creation of a `toJsonSupabase` which exclude the `primaryKey` from the `Map`
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ and the Flutter guide for
-->
# Flutter Supabase Macro

![testing workflow](https://github.com/ThomasDevApps/flutter_supabase_macro/actions/workflows/main.yml/badge.svg)

Package greatly inspired by `JsonCodable` (from Dart), makes it easy to create
a JSON format of a template for Supabase.

| Before | After |
|----------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|
| Before | After |
|----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------|
| ![before](https://raw.githubusercontent.com/ThomasDevApps/flutter_supabase_macro/main/assets/before.png) | ![after](https://raw.githubusercontent.com/ThomasDevApps/flutter_supabase_macro/main/assets/after.png) |

- [What is a macro](#-what-is-a-macro)
Expand Down
Binary file modified assets/before.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 5 additions & 6 deletions lib/flutter_supabase_macro.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:macros/macros.dart';
part 'src/extensions/code_extension.dart';
part 'src/extensions/iterable_extension.dart';
part 'src/extensions/named_type_annotation_extension.dart';
part 'src/extensions/string_extension.dart';
part 'src/extensions/type_declaration_extension.dart';
part 'src/mixins/shared.dart';
part 'src/mixins/to_json_supabase.dart';
Expand All @@ -21,6 +22,7 @@ macro class FlutterSupabaseMacro
implements ClassDeclarationsMacro, ClassDefinitionMacro {

/// Primary key to exclude from the `toJsonSupabase`.
@override
final String primaryKey;

const FlutterSupabaseMacro({this.primaryKey = 'id'});
Expand All @@ -43,14 +45,11 @@ macro class FlutterSupabaseMacro
ClassDeclaration clazz,
TypeDefinitionBuilder builder,
) async {
final introspectionData =
await _SharedIntrospectionData.build(builder, clazz);
await _buildToJsonSupabase(
clazz,
final introspectionData = await _SharedIntrospectionData.build(
builder,
introspectionData,
primaryKey,
clazz,
);
await _buildToJsonSupabase(clazz, builder, introspectionData);
}
}

10 changes: 10 additions & 0 deletions lib/src/extensions/string_extension.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
part of '../../flutter_supabase_macro.dart';

extension _StringExtension on String {
/// Set the first character to upper case.
///
/// ```dart
/// 'test of the function'.firstLetterUpperCase(); // 'Test of the function'
/// ```
String _firstLetterToUpperCase() => "${this[0].toUpperCase()}${substring(1)}";
}
95 changes: 76 additions & 19 deletions lib/src/mixins/to_json_supabase.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// ignore_for_file: deprecated_member_use

part of '../../flutter_supabase_macro.dart';

mixin _ToJsonSupabase on _Shared {
String get primaryKey;

/// Declare the [_toJsonMethodName] method.
Future<void> _declareToJsonSupabase(
ClassDeclaration clazz,
Expand All @@ -10,13 +14,47 @@ mixin _ToJsonSupabase on _Shared {
// Check that no toJsonSupabase method exist
final checkNoToJson = await _checkNoToJson(builder, clazz);
if (!checkNoToJson) return;
final boolId = await builder.resolveIdentifier(_dartCore, 'bool');
final boolCode = NamedTypeAnnotationCode(name: boolId);
final fields = await builder.fieldsOf(clazz);
builder.declareInType(
DeclarationCode.fromParts(
[' external ', mapStringObject, ' $_toJsonMethodName();\n'],
),
DeclarationCode.fromParts([
' external ',
mapStringObject,
' $_toJsonMethodName(',
if (fields.isNotEmpty) '{\n',
if (fields.isNotEmpty) ..._createNamedParams(boolCode, fields),
if (fields.isNotEmpty) '\n }',
');\n'
]),
);
}

/// Create `List` of parts.
///
/// Example : [fields] contain one element named `firstField`, it will add :
/// ```dart
/// ' bool? removeFirstField,'
/// ```
List _createNamedParams(
NamedTypeAnnotationCode boolCode,
List<FieldDeclaration> fields,
) {
final list = [];
for (final field in fields) {
list.addAll([
' ',
boolCode,
'? ',
'remove',
field.identifier.name._firstLetterToUpperCase(),
',',
if (field != fields.last) '\n',
]);
}
return list;
}

/// Emits an error [Diagnostic] if there is an existing [_toJsonMethodName]
/// method on [clazz].
///
Expand Down Expand Up @@ -51,7 +89,6 @@ mixin _ToJsonSupabase on _Shared {
ClassDeclaration clazz,
TypeDefinitionBuilder typeBuilder,
_SharedIntrospectionData introspectionData,
String primaryKey,
) async {
// Get all methods of the class
final methods = await typeBuilder.methodsOf(clazz);
Expand Down Expand Up @@ -84,7 +121,6 @@ mixin _ToJsonSupabase on _Shared {
field,
builder,
introspectionData,
isPrimaryKey: field.identifier.name == primaryKey,
),
),
),
Expand All @@ -93,15 +129,33 @@ mixin _ToJsonSupabase on _Shared {
parts.add('return json;\n }');
builder.augment(
FunctionBodyCode.fromParts(parts),
docComments: CommentCode.fromParts([
' /// Map representing the model in json format for Supabase.\n',
' ///\n',
' /// The primary key [${fields.first.identifier.name}]',
' is exclude from the map if empty.'
]),
docComments: _createDocumentationForMethod(fields),
);
}

/// Create the documentation for [_toJsonMethodName] method
/// according with [fields].
CommentCode _createDocumentationForMethod(List<FieldDeclaration> fields) {
return CommentCode.fromParts([
' /// Map representing the model in json format for Supabase.\n',
' ///\n',
' /// The primary key [${fields.first.identifier.name}]',
' is exclude from the map if empty.\n',
' ///\n',
' /// ',
...fields.map((f) {
return [
'[remove',
f.identifier.name._firstLetterToUpperCase(),
']',
if (f != fields.last) ', '
].join();
}),
' can be set for remove field\n'
' /// from the json.'
]);
}

/// Returns void if [toJsonSupabase] not exist.
///
/// Otherwise it will check that [toJsonSupabase] is valid with [_checkValidToJson].
Expand Down Expand Up @@ -131,9 +185,7 @@ mixin _ToJsonSupabase on _Shared {
final methodIsMap = await methodReturnType.isExactly(
introspectionData.jsonMapType,
);
if (method.namedParameters.isNotEmpty ||
method.positionalParameters.isNotEmpty ||
!methodIsMap) {
if (!methodIsMap) {
builder.report(
Diagnostic(
DiagnosticMessage(
Expand Down Expand Up @@ -241,13 +293,17 @@ mixin _ToJsonSupabase on _Shared {
Future<Code> addEntryForField(
FieldDeclaration field,
DefinitionBuilder builder,
_SharedIntrospectionData introspectionData, {
bool isPrimaryKey = false,
}) async {
_SharedIntrospectionData introspectionData,
) async {
final parts = <Object>[];
final isPrimaryKey = field.identifier.name == primaryKey;
final doNullCheck = field.type.isNullable;
final needCondition = doNullCheck || isPrimaryKey;
final fieldName = field.identifier.name._firstLetterToUpperCase();
// Begin the definition of the condition
parts.addAll([
'if (remove$fieldName==null || !remove$fieldName) {\n ',
]);
if (needCondition) {
parts.addAll(['if (']);
}
Expand All @@ -265,7 +321,7 @@ mixin _ToJsonSupabase on _Shared {
}
}
// Close definition of the condition and open it
if (needCondition) parts.add(') {\n ');
if (needCondition) parts.add(') {\n ');
// Add the field in the json
parts.addAll([
"json[r'",
Expand All @@ -284,8 +340,9 @@ mixin _ToJsonSupabase on _Shared {
]);
// Close the condition
if (needCondition) {
parts.add('}\n ');
parts.add(' }\n ');
}
parts.add('}\n ');
return RawCode.fromParts(parts);
}

Expand Down
9 changes: 8 additions & 1 deletion lib/src/models/shared_introspection_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ final class _SharedIntrospectionData {
/// A [Code] representation of the type `dynamic`.
final NamedTypeAnnotationCode dynamicCode;

/// A [Code] representation of the type [bool].
final NamedTypeAnnotationCode boolCode;

/// A [Code] representation of the type [String].
final NamedTypeAnnotationCode stringCode;

Expand All @@ -41,18 +44,20 @@ final class _SharedIntrospectionData {
required this.mapEntry,
required this.dynamicCode,
required this.stringCode,
required this.boolCode,
required this.superclass,
});

static Future<_SharedIntrospectionData> build(
DeclarationPhaseIntrospector builder, ClassDeclaration clazz) async {
// Resolve identifiers
final (list, map, mapEntry, dynamic, string) = await (
final (list, map, mapEntry, dynamic, string, bool) = await (
builder.resolveIdentifier(_dartCore, 'List'),
builder.resolveIdentifier(_dartCore, 'Map'),
builder.resolveIdentifier(_dartCore, 'MapEntry'),
builder.resolveIdentifier(_dartCore, 'dynamic'),
builder.resolveIdentifier(_dartCore, 'String'),
builder.resolveIdentifier(_dartCore, 'bool'),
).wait;

// Get all NamedTypeAnnotationCode
Expand All @@ -65,6 +70,7 @@ final class _SharedIntrospectionData {
dynamicCode,
]);
final stringCode = NamedTypeAnnotationCode(name: string);
final boolCode = NamedTypeAnnotationCode(name: bool);

// Get the class's superclass (if exist)
final superclass = clazz.superclass;
Expand All @@ -88,6 +94,7 @@ final class _SharedIntrospectionData {
mapEntry: mapEntry,
dynamicCode: dynamicCode,
stringCode: stringCode,
boolCode: boolCode,
superclass: superclassDecl as ClassDeclaration?,
);
}
Expand Down
36 changes: 1 addition & 35 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: flutter_supabase_macro
description: "A new Flutter project."
version: 0.0.4
version: 0.0.5
homepage:

environment:
Expand All @@ -17,39 +17,5 @@ dev_dependencies:
sdk: flutter
flutter_lints: ^5.0.0

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

# The following section is specific to Flutter packages.
flutter:

# To add assets to your package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/to/asset-from-package
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images

# To add custom fonts to your package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/to/font-from-package
Loading