Skip to content

Commit

Permalink
fix(katana_router_builder): Create a mechanism for automatic Routing …
Browse files Browse the repository at this point in the history
…generation
  • Loading branch information
mathrunet committed Oct 16, 2022
1 parent 3317df0 commit fb705f8
Show file tree
Hide file tree
Showing 15 changed files with 314 additions and 45 deletions.
22 changes: 19 additions & 3 deletions packages/katana_router_builder/build.yaml
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
builders:
katana_router_builder:
katana_router_page_builder:
import: 'package:katana_router_builder/katana_router_builder.dart'
builder_factories: ['katanaRouterBuilderFactory']
builder_factories: ['katanaRouterPageBuilderFactory']
build_extensions: {'.dart': ['.page.dart']}
auto_apply: dependents
build_to: source
applies_builders: ["source_gen|combining_builder"]
katana_router_router_builder:
import: 'package:katana_router_builder/katana_router_builder.dart'
builder_factories: ['katanaRouterRouterBuilderFactory']
build_extensions: {'.dart': ['.router.dart']}
auto_apply: dependents
build_to: source
applies_builders: ["source_gen|combining_builder"]
targets:
$default:
builders:
katana_router_builder:
katana_router_page_builder:
enabled: true
generate_for:
exclude:
- test
- example
include:
- test/integration/*
- test/integration/**/*
katana_router_router_builder:
enabled: true
generate_for:
exclude:
Expand Down
6 changes: 3 additions & 3 deletions packages/katana_router_builder/lib/common/extends_class.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
part of katana_router_builder;

List<Class> extendsClass(
ClassModel model,
PathModel path,
AnnotationModel annotation,
ClassValue model,
PathValue path,
AnnotationValue annotation,
) {
return [
Class(
Expand Down
20 changes: 10 additions & 10 deletions packages/katana_router_builder/lib/common/query_class.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
part of katana_router_builder;

List<Class> queryClass(
ClassModel model,
PathModel path,
AnnotationModel annotation,
ClassValue model,
PathValue path,
AnnotationValue annotation,
) {
return [
Class(
(c) => c
..name = "_\$${model.name}"
..extend = const Reference("PageQueryBuilder")
..extend = const Reference("RouteQueryBuilder")
..annotations = ListBuilder([const Reference("immutable")])
..constructors = ListBuilder([
Constructor(
Expand Down Expand Up @@ -54,7 +54,7 @@ List<Class> queryClass(
(m) => m
..name = "resolve"
..annotations = ListBuilder([const Reference("override")])
..returns = const Reference("PageQuery?")
..returns = const Reference("RouteQuery?")
..requiredParameters = ListBuilder([
Parameter(
(p) => p
Expand All @@ -80,7 +80,7 @@ List<Class> queryClass(
Class(
(c) => c
..name = "_\$_${model.name}"
..extend = const Reference("PageQuery")
..extend = const Reference("RouteQuery")
..annotations = ListBuilder([const Reference("immutable")])
..constructors = ListBuilder([
Constructor(
Expand Down Expand Up @@ -134,24 +134,24 @@ List<Class> queryClass(
(m) => m
..name = "route<T>"
..annotations = ListBuilder([const Reference("override")])
..returns = const Reference("PageRouteQuery<T>")
..returns = const Reference("AppPageRoute<T>")
..optionalParameters = ListBuilder([
Parameter(
(p) => p
..type = const Reference("RouteQuery?")
..type = const Reference("TransitionQuery?")
..name = "query",
)
])
..body = Code(
"return PageRouteQuery<T>(path: path,routeQuery: query,builder: (context) => ${model.name}(${model.parameters.map((param) => "${param.name}:${param.name}").join(",")}),transition: query?.transition ?? RouteQueryType.initial,);",
"return AppPageRoute<T>(path: path,routeQuery: query,builder: (context) => ${model.name}(${model.parameters.map((param) => "${param.name}:${param.name}").join(",")}),transition: query?.transition ?? TransitionQueryType.initial,);",
),
),
]),
),
];
}

String _defaultParsedValue(ParamaterModel param) {
String _defaultParsedValue(ParamaterValue param) {
if (param.type.toString() == "String") {
return "${param.name}:match?.groupNames.contains(\"${param.pageParamName}\") ?? false ? match?.namedGroup(\"${param.pageParamName}\") ?? ${_defaultValue(param)} : ${_defaultValue(param)}";
} else {
Expand Down
102 changes: 102 additions & 0 deletions packages/katana_router_builder/lib/common/router_class.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
part of katana_router_builder;

List<Class> routerClass(
List<QueryValue> queries,
) {
return [
Class(
(c) => c
..name = r"AppRouter"
..extend = const Reference("AppRouterBase")
..constructors = ListBuilder([
Constructor(
(c) => c
..optionalParameters = ListBuilder([
Parameter(
(p) => p
..name = "unknown"
..named = true
..toSuper = true,
),
Parameter(
(p) => p
..name = "boot"
..named = true
..toSuper = true,
),
Parameter(
(p) => p
..name = "initialPath"
..toSuper = true
..named = true
..defaultTo = const Code("\"/\""),
),
Parameter(
(p) => p
..name = "redirect"
..toSuper = true
..named = true
..defaultTo = const Code("const []"),
),
Parameter(
(p) => p
..name = "observers"
..toSuper = true
..named = true
..defaultTo = const Code("const []"),
),
Parameter(
(p) => p
..name = "redirectLimit"
..toSuper = true
..named = true
..defaultTo = const Code("5"),
),
Parameter(
(p) => p
..name = "navigatorKey"
..named = true
..toSuper = true,
),
Parameter(
(p) => p
..name = "restorationScopeId"
..named = true
..toSuper = true,
),
Parameter(
(p) => p
..name = "defaultRouteQuery"
..named = true
..toSuper = true,
),
])
..initializers = ListBuilder([
Code(
"super(pages: [${queries.map((e) => e.query).join(",")}])",
),
]),
)
])
..fields = ListBuilder([
...queries.map((query) {
return Field(
(f) => f
..name = query.element.name.toCamelCase()
..static = true
..modifier = FieldModifier.constant
..assignment = Code(query.query),
);
}),
Field(
(f) => f
..name = "queryMap"
..type = const Reference("Map<RouteQueryBuilder, String>")
..assignment = Code(
"{${queries.map((q) => "${q.query}:\"/${q.path}\"").join(",")}}",
),
)
]),
),
];
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
part of katana_router_builder;

class PageGenerator extends GeneratorForAnnotation<PagePath> {
PageGenerator();

@override
FutureOr<String> generateForAnnotatedElement(
Element element,
Expand All @@ -22,10 +24,10 @@ class PageGenerator extends GeneratorForAnnotation<PagePath> {
);
}

final _annotation = AnnotationModel(element, PagePath);
final _annotation = AnnotationValue(element, PagePath);
final _path =
PathModel("/${annotation.read("path").stringValue.trimString("/")}");
final _class = ClassModel(element);
PathValue("/${annotation.read("path").stringValue.trimString("/")}");
final _class = ClassValue(element);

for (final param in _path.parameters) {
if (!_class.parameters.any((e) => e.name == param.camelCase)) {
Expand Down
119 changes: 119 additions & 0 deletions packages/katana_router_builder/lib/generator/router_generator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
part of katana_router_builder;

class RouterGenerator extends GeneratorForAnnotation<AppRoute> {
RouterGenerator();

static const _typeChecker = TypeChecker.fromRuntime(PagePath);
static const _pageRouteQueryChecker = TypeChecker.fromRuntime(PageRouteQuery);

static final _regExp = RegExp(r"^/(?<packageName>[^/]+)/lib/(?<path>.+)$");

@override
FutureOr<String> generateForAnnotatedElement(
Element element,
ConstantReader annotation,
BuildStep buildStep,
) async {
if (!element.library!.isNonNullableByDefault) {
throw InvalidGenerationSourceError(
"Generator cannot target libraries that have not been migrated to "
"null-safety.",
element: element,
);
}

if (element is! TopLevelVariableElement) {
throw InvalidGenerationSourceError(
"`@AppRoute()`はトップレベルのフィールドにのみ付与してください。\n"
"```\n"
"\n"
"```\n",
element: element,
);
}

final queries = <QueryValue>[];
final assets = buildStep.findAssets(Glob("**.dart"));
await for (final asset in assets) {
if (!await buildStep.resolver.isLibrary(asset)) {
continue;
}
final lib = LibraryReader(
await buildStep.resolver.libraryFor(
asset,
allowSyntaxErrors: false,
),
);
for (final annotatedElement in lib.annotatedWith(_typeChecker)) {
final element = annotatedElement.element;
if (element is! ClassElement) {
continue;
}
final annotation = annotatedElement.annotation;
final path = annotation.read("path").stringValue.trimString("/");
for (final field in element.fields) {
if (_pageRouteQueryChecker.hasAnnotationOfExact(field)) {
if (!field.isStatic || !field.isConst) {
throw InvalidGenerationSourceError(
"The `@pageRouteQuery()` can only be given to static and const fields.",
element: field,
);
}
final library = _getImportLibrary(lib.element);
if (library.isEmpty) {
continue;
}
queries.add(
QueryValue(
library: library!,
path: path,
query: "${element.name}.${field.name}",
element: element,
),
);
}
}
}
}

final sorted = queries.sortTo((a, b) => a.path.compareTo(b.path));

final generated = Library(
(l) => l
..directives = ListBuilder([
Directive.import(
"package:katana_router/katana_router.dart",
),
...sorted
.map((e) => e.library)
.distinct()
.map((e) => Directive.import(e)),
Directive.export(
"package:katana_router/katana_router.dart",
),
...sorted
.map((e) => e.library)
.distinct()
.map((e) => Directive.export(e))
])
..body.addAll(
[
...routerClass(sorted),
],
),
);
final emitter = DartEmitter();
return DartFormatter().format(
"${generated.accept(emitter)}",
);
}

String? _getImportLibrary(LibraryElement element) {
final path = element.librarySource.toString();
final match = _regExp.firstMatch(path);
if (match == null) {
return null;
}
return "package:${match.namedGroup("packageName")}/${match.namedGroup("path")}";
}
}
14 changes: 10 additions & 4 deletions packages/katana_router_builder/lib/katana_router_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,22 @@ import 'package:build/build.dart';
import 'package:built_collection/built_collection.dart';
import 'package:code_builder/code_builder.dart';
import 'package:dart_style/dart_style.dart';
import 'package:glob/glob.dart';
import 'package:katana_router_annotation/katana_router_annotation.dart';
import 'package:source_gen/source_gen.dart';

import 'package:source_gen/source_gen.dart' as source_gen;

part 'common/extends_class.dart';
part 'common/query_class.dart';
part 'common/router_class.dart';
part 'generator/page_generator.dart';
part 'model/class_model.dart';
part 'model/parameter_model.dart';
part 'model/path_model.dart';
part 'model/annotation_model.dart';
part 'generator/router_generator.dart';
part 'value/annotation_value.dart';
part 'value/class_value.dart';
part 'value/parameter_value.dart';
part 'value/path_value.dart';
part 'value/query_value.dart';
part 'src/builder.dart';
part 'src/config.dart';
part 'src/extensions.dart';
Expand Down

0 comments on commit fb705f8

Please sign in to comment.