Skip to content

Commit

Permalink
flutter_supabase_macro_0.0.3 (#3)
Browse files Browse the repository at this point in the history
• Refractor code
• Provide more documentation
  • Loading branch information
ThomasDevApps authored Oct 14, 2024
1 parent 885a35c commit b796127
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 180 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## 0.0.1

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

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

- [What is a macro](#-what-is-a-macro)
- [Getting started](#-getting-started)
Expand All @@ -21,13 +22,15 @@ Package greatly inspired by `JsonCodable` (from Dart), makes it easy to create a

## ❔ What is a macro

According with the official documentation, a Dart macro is a user-definable piece of code that takes in other code as parameters and operates on it in real-time to create, modify, or add declarations.
A Dart macro is a user-definable piece of code that takes in other code as
parameters and operates on it in real-time to create, modify, or add declarations.

Find out more at https://dart.dev/language/macros

## 🚀 Getting started

Because the macros are still under development, you need to follow these instructions to be able to test this package : https://dart.dev/language/macros#set-up-the-experiment
Because the macros are still under development, you need to follow these
instructions to be able to test this package : https://dart.dev/language/macros#set-up-the-experiment

Then add in your `pubspec.yaml` :

Expand Down Expand Up @@ -71,6 +74,7 @@ print(json); // {'name': 'Toto', 'age': 22}

## 📖 Additional information

This package is still undergoing experimentation, and is in no way intended for use in production apps.
This package is still undergoing experimentation, and is in no way intended for
use in production apps.

Not officially affiliated with Supabase.
173 changes: 5 additions & 168 deletions lib/flutter_supabase_macro.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
// ignore_for_file: deprecated_member_use, unintended_html_in_doc_comment
library;

import 'dart:async';

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/type_declaration_extension.dart';
part 'src/mixins/shared.dart';
part 'src/mixins/to_json_supabase.dart';
part 'src/models/shared_introspection_data.dart';

final _dartCore = Uri.parse('dart:core');

Expand Down Expand Up @@ -50,170 +54,3 @@ macro class FlutterSupabaseMacro
}
}

////////////////////////////////////////////////////////////////////////////////
/// This data is collected asynchronously, so we only want to do it once and
/// share that work across multiple locations.
final class _SharedIntrospectionData {
/// The declaration of the class we are generating for.
final ClassDeclaration clazz;

/// All the fields on the [clazz].
final List<FieldDeclaration> fields;

/// A [Code] representation of the type [List<Object?>].
final NamedTypeAnnotationCode jsonListCode;

/// A [Code] representation of the type [Map<String, dynamic>].
final NamedTypeAnnotationCode jsonMapCode;

/// The resolved [StaticType] representing the [Map<String, dynamic>] type.
final StaticType jsonMapType;

/// The resolved identifier for the [MapEntry] class.
final Identifier mapEntry;

/// A [Code] representation of the type [Object].
final NamedTypeAnnotationCode dynamicCode;

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

/// The declaration of the superclass of [clazz], if it is not [Object].
final ClassDeclaration? superclass;

_SharedIntrospectionData({
required this.clazz,
required this.fields,
required this.jsonListCode,
required this.jsonMapCode,
required this.jsonMapType,
required this.mapEntry,
required this.dynamicCode,
required this.stringCode,
required this.superclass,
});

static Future<_SharedIntrospectionData> build(
DeclarationPhaseIntrospector builder, ClassDeclaration clazz) async {
final (list, map, mapEntry, dynamic, string) = await (
builder.resolveIdentifier(_dartCore, 'List'),
builder.resolveIdentifier(_dartCore, 'Map'),
builder.resolveIdentifier(_dartCore, 'MapEntry'),
builder.resolveIdentifier(_dartCore, 'dynamic'),
builder.resolveIdentifier(_dartCore, 'String'),
).wait;
final dynamicCode = NamedTypeAnnotationCode(name: dynamic);
final jsonListCode = NamedTypeAnnotationCode(name: list, typeArguments: [
dynamicCode,
]);
final jsonMapCode = NamedTypeAnnotationCode(name: map, typeArguments: [
NamedTypeAnnotationCode(name: string),
dynamicCode,
]);
final stringCode = NamedTypeAnnotationCode(name: string);
final superclass = clazz.superclass;
final (fields, jsonMapType, superclassDecl) = await (
builder.fieldsOf(clazz),
builder.resolve(jsonMapCode),
superclass == null
? Future.value(null)
: builder.typeDeclarationOf(superclass.identifier),
).wait;

return _SharedIntrospectionData(
clazz: clazz,
fields: fields,
jsonListCode: jsonListCode,
jsonMapCode: jsonMapCode,
jsonMapType: jsonMapType,
mapEntry: mapEntry,
dynamicCode: dynamicCode,
stringCode: stringCode,
superclass: superclassDecl as ClassDeclaration?,
);
}
}

////////////////////////////////////////////////////////////////////////////////
extension _FirstWhereOrNull<T> on Iterable<T> {
T? firstWhereOrNull(bool Function(T) compare) {
for (final item in this) {
if (compare(item)) return item;
}
return null;
}
}

////////////////////////////////////////////////////////////////////////////////
extension _IsExactly on TypeDeclaration {
/// Cheaper than checking types using a [StaticType].
bool isExactly(String name, Uri library) =>
identifier.name == name && this.library.uri == library;
}

////////////////////////////////////////////////////////////////////////////////
extension on Code {
/// Used for error messages.
String get debugString {
final buffer = StringBuffer();
_writeDebugString(buffer);
return buffer.toString();
}

void _writeDebugString(StringBuffer buffer) {
for (final part in parts) {
switch (part) {
case Code():
part._writeDebugString(buffer);
case Identifier():
buffer.write(part.name);
case OmittedTypeAnnotation():
buffer.write('<omitted>');
default:
buffer.write(part);
}
}
}
}

////////////////////////////////////////////////////////////////////////////////
extension on NamedTypeAnnotation {
/// Follows the declaration of this type through any type aliases, until it
/// reaches a [ClassDeclaration], or returns null if it does not bottom out on
/// a class.
Future<ClassDeclaration?> classDeclaration(DefinitionBuilder builder) async {
var typeDecl = await builder.typeDeclarationOf(identifier);
while (typeDecl is TypeAliasDeclaration) {
final aliasedType = typeDecl.aliasedType;
if (aliasedType is! NamedTypeAnnotation) {
builder.report(
Diagnostic(
DiagnosticMessage(
'Only fields with named types are allowed on serializable '
'classes',
target: asDiagnosticTarget,
),
Severity.error,
),
);
return null;
}
typeDecl = await builder.typeDeclarationOf(aliasedType.identifier);
}
if (typeDecl is! ClassDeclaration) {
builder.report(Diagnostic(
DiagnosticMessage(
'Only classes are supported as field types for serializable '
'classes',
target: asDiagnosticTarget),
Severity.error));
return null;
}
return typeDecl;
}
}
25 changes: 25 additions & 0 deletions lib/src/extensions/code_extension.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
part of '../../flutter_supabase_macro.dart';

extension on Code {
/// Used for error messages.
String get debugString {
final buffer = StringBuffer();
_writeDebugString(buffer);
return buffer.toString();
}

void _writeDebugString(StringBuffer buffer) {
for (final part in parts) {
switch (part) {
case Code():
part._writeDebugString(buffer);
case Identifier():
buffer.write(part.name);
case OmittedTypeAnnotation():
buffer.write('<omitted>');
default:
buffer.write(part);
}
}
}
}
10 changes: 10 additions & 0 deletions lib/src/extensions/iterable_extension.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
part of '../../flutter_supabase_macro.dart';

extension _IterableExtension<T> on Iterable<T> {
T? firstWhereOrNull(bool Function(T) compare) {
for (final item in this) {
if (compare(item)) return item;
}
return null;
}
}
41 changes: 41 additions & 0 deletions lib/src/extensions/named_type_annotation_extension.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
part of '../../flutter_supabase_macro.dart';

extension on NamedTypeAnnotation {
/// Follows the declaration of this type through any type aliases, until it
/// reaches a [ClassDeclaration], or returns null if it does not bottom out on
/// a class.
Future<ClassDeclaration?> classDeclaration(DefinitionBuilder builder) async {
var typeDeclaration = await builder.typeDeclarationOf(identifier);
while (typeDeclaration is TypeAliasDeclaration) {
final aliasedType = typeDeclaration.aliasedType;
if (aliasedType is! NamedTypeAnnotation) {
builder.report(
Diagnostic(
DiagnosticMessage(
'Only fields with named types are allowed on serializable '
'classes',
target: asDiagnosticTarget,
),
Severity.error,
),
);
return null;
}
typeDeclaration = await builder.typeDeclarationOf(aliasedType.identifier);
}
if (typeDeclaration is! ClassDeclaration) {
builder.report(
Diagnostic(
DiagnosticMessage(
'Only classes are supported as field types for serializable '
'classes',
target: asDiagnosticTarget,
),
Severity.error,
),
);
return null;
}
return typeDeclaration;
}
}
7 changes: 7 additions & 0 deletions lib/src/extensions/type_declaration_extension.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
part of '../../flutter_supabase_macro.dart';

extension _IsExactly on TypeDeclaration {
/// Cheaper than checking types using a [StaticType].
bool isExactly(String name, Uri library) =>
identifier.name == name && this.library.uri == library;
}
19 changes: 13 additions & 6 deletions lib/src/mixins/to_json_supabase.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ mixin _ToJsonSupabase on _Shared {
if (!checkNoToJson) return;
builder.declareInType(
DeclarationCode.fromParts(
[' external ', mapStringObject, ' $_toJsonMethodName();'],
[' external ', mapStringObject, ' $_toJsonMethodName();\n'],
),
);
}
Expand Down Expand Up @@ -70,7 +70,7 @@ mixin _ToJsonSupabase on _Shared {
await _checkSuperclassHasToJson(introspectionData, typeBuilder);
if (superclassHasToJson == null) return;

// Create different parts
// Initialize the map to return
final parts = _initializeMap(introspectionData,
superclassHasToJson: superclassHasToJson);

Expand All @@ -92,7 +92,14 @@ mixin _ToJsonSupabase on _Shared {
);

parts.add('return json;\n }');
builder.augment(FunctionBodyCode.fromParts(parts));
builder.augment(
FunctionBodyCode.fromParts(parts),
docComments: CommentCode.fromParts([
' /// Map representing the model in json format for Supabase.\n',
' ///\n',
' /// The `primaryKey` is exclude from the map.'
]),
);
}

/// Returns void if [toJsonSupabase] not exist.
Expand All @@ -113,7 +120,7 @@ mixin _ToJsonSupabase on _Shared {
if (!methodIsValid) return;
}

/// Check that [method] is a valid `toJson` method, throws a
/// Check that [method] is a valid `toJsonSupabase` method, throws a
/// [DiagnosticException] if not.
Future<bool> _checkValidToJson(
MethodDeclaration method,
Expand Down Expand Up @@ -183,7 +190,7 @@ mixin _ToJsonSupabase on _Shared {

/// Initialize the map to return.
///
/// Two case are handled according with [superclassHasToJson].
/// Two cases are handled according with [superclassHasToJson].
///
/// If [superclassHasToJson] is true :
/// ```dart
Expand All @@ -199,7 +206,7 @@ mixin _ToJsonSupabase on _Shared {
///
/// ```
///
/// (The last `}` is voluntarily ommited)
/// (The last `}` is voluntarily omitted)
List<Object> _initializeMap(
_SharedIntrospectionData introspectionData, {
required bool superclassHasToJson,
Expand Down
Loading

0 comments on commit b796127

Please sign in to comment.