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

feat(#48): add generate command to create application resources #51

Merged
merged 7 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
feat(#48): wip resources generation
  • Loading branch information
francescovallone committed Jul 3, 2024
commit b8ef285a32a48062fc870d2cc3f5e13cefa0b3c9
6 changes: 1 addition & 5 deletions examples/ping_pong/lib/app_module.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'package:ping_pong/test_module.dart';
import 'package:serinus/serinus.dart';

import 'app_controller.dart';
Expand All @@ -7,11 +6,8 @@ import 'app_provider.dart';
class AppModule extends Module {
AppModule()
: super(
imports: [TestModule()],
imports: [],
controllers: [AppController()],
providers: [AppProvider()],
);

@override
List<Module> get imports => super.imports;
}
23 changes: 0 additions & 23 deletions examples/ping_pong/lib/test_module.dart

This file was deleted.

222 changes: 169 additions & 53 deletions packages/serinus_cli/lib/src/commands/generate/builder.dart
Original file line number Diff line number Diff line change
@@ -1,96 +1,212 @@
// ignore_for_file: depend_on_referenced_packages

import 'dart:io';

import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
//ignore: implementation_imports
import 'package:analyzer/src/dart/ast/ast.dart';

Future<List<FileUpdates>> analyze(String path, List<GeneratedElement> elements, String? entrypointFile) async {
final directory = Directory(path);
final List<FileUpdates> updates = [];
final collection = AnalysisContextCollection(includedPaths: [directory.path], resourceProvider: PhysicalResourceProvider.INSTANCE);
for (final context in collection.contexts) {
for (final file in context.contextRoot.analyzedFiles()) {
if(entrypointFile != null && !file.contains(entrypointFile)) {
continue;
}
if (!file.endsWith('.dart')) {
continue;

class SerinusAnalyzer {

Future<List<FileUpdates>> analyze(
String path,
List<GeneratedElement> elements,
String? entrypointFile,
) async {
final directory = Directory(path);
final updates = <FileUpdates>[];
final collection = AnalysisContextCollection(
includedPaths: [directory.path],
resourceProvider: PhysicalResourceProvider.INSTANCE,
);
for (final context in collection.contexts) {
for (final file in context.contextRoot.analyzedFiles()) {
if(entrypointFile != null && !file.contains(entrypointFile)) {
continue;
}
if (!file.endsWith('.dart')) {
continue;
}
final unitElement = await context.currentSession.getUnitElement(file);
final unit = unitElement as UnitElementResult;
final resolvedUnit = await context.currentSession.getResolvedUnit(file);
final resolved = resolvedUnit as ResolvedUnitResult;
final directivesImports = resolved.unit.directives
.whereType<ImportDirective>().toSet();
final parts = resolved.unit.directives
.whereType<PartDirective>().toSet();
final partOf = resolved.unit.directives
.whereType<PartOfDirective>().firstOrNull;


// final visitor = _ClassVisitor();
final element = unit.element;
final classes = element.classes;
for (final clazz in classes) {
final superclass = clazz.supertype?.element.name;
final callForImports = elements.where(
(e) => e.type == ElementType.module,);
if (callForImports.isNotEmpty && superclass == 'Module') {
updates.add(getUpdates(
directivesImports,
parts,
partOf,
callForImports,
'Module',
'imports',
clazz.source.contents.data,
),);
}
final callForControllers = elements.where(
(e) => e.type == ElementType.controller,);
if(callForControllers.isNotEmpty && superclass == 'Module') {
updates.add(getUpdates(
directivesImports,
parts,
partOf,
callForControllers,
'Controller',
'controllers',
clazz.source.contents.data,
),);
}
final callForProviders = elements.where(
(e) => e.type == ElementType.provider,);
if(callForProviders.isNotEmpty && superclass == 'Module') {
updates.add(getUpdates(
directivesImports,
parts,
partOf,
callForProviders,
'Provider',
'providers',
clazz.source.contents.data,
),);
}
}
}
final resolvedUnit = await context.currentSession.getUnitElement(file);
final unit = resolvedUnit as UnitElementResult;
// final visitor = _ClassVisitor();
final element = unit.element;
final classes = element.classes;
for (final clazz in classes) {
print(clazz.name);
final superclass = clazz.supertype?.element.name;
// clazz.accept(visitor);
final callForImports = elements.where((e) => e.type == ElementType.module);
if (callForImports.isNotEmpty && superclass == 'Module') {
String imports = callForImports.map((e) => e.source).join(',\n');
final startOldValue = clazz.source.contents.data.indexOf('imports => ');
String? oldValue = startOldValue > -1
? clazz.source.contents.data.substring(
startOldValue,
).trim()
: null;
if(oldValue != null && !oldValue.startsWith('[')) {
oldValue = oldValue.substring(startOldValue, oldValue.indexOf(';') + 1);
imports = '''[
...$oldValue,
$imports,
}
return updates;
}

FileUpdates getUpdates(
Iterable<ImportDirective> imports,
Iterable<PartDirective> parts,
PartOfDirective? partOf,
Iterable<GeneratedElement> elements,
String type,
String getter,
String content,
) {
final elementsInEntrypoint = getListInEntrypoint(type, getter);
var elementsStringified = elements.map((e) => e.name).join(',\n');
final startOldValue = calculateStartIndex(content, elementsInEntrypoint,);
var oldValue = startOldValue > -1
? content.substring(
startOldValue,
).trim()
: null;
if(oldValue != null) {
oldValue = oldValue
.substring(0, oldValue.indexOf(';') + 1);
if(!oldValue.contains('[')) {
// ignore: leading_newlines_in_multiline_strings
elementsStringified = '''

@override
$elementsInEntrypoint [
...super.$getter,
$elementsStringified,
];\n''';
}else{
// ignore: leading_newlines_in_multiline_strings
if(!oldValue.contains(elementsStringified)){
final replacedOldValue = oldValue.replaceFirst('];', '');
elementsStringified = '''

$replacedOldValue
$elementsStringified,
];\n''';
}else{
elementsStringified = oldValue;
}
updates.add(FileUpdates(
newValue: imports,
oldValue: oldValue,
),);
}
}else{
// ignore: leading_newlines_in_multiline_strings
elementsStringified = '''

@override
$elementsInEntrypoint [
...super.$getter,
$elementsStringified,
];\n''';
}
}
return FileUpdates(
newValue: elementsStringified,
oldValue: oldValue,
imports: imports.map(
(e) => "import '${e.uri.stringValue ?? ''}';",).toSet(),
parts: parts.map(
(e) => "parts '${e.uri.stringValue ?? ''}';",).toSet(),
partOf: partOf != null
? "part of '${partOf.uri?.stringValue}'" : null,
);
}
return updates;

}

enum ElementType implements Comparable<ElementType>{
controller('controllers'),
module('imports'),
provider('providers');

final String keyword;

const ElementType(this.keyword);

final String keyword;

@override
int compareTo(ElementType other) => index;
}

class GeneratedElement {

final ElementType type;
final String name;
final String source;

const GeneratedElement({
required this.type,
required this.name,
required this.source,
});

final ElementType type;
final String name;

}

class FileUpdates {

final String newValue;
final String? oldValue;

const FileUpdates({
required this.newValue,
required this.imports,
required this.parts,
this.partOf,
this.oldValue,
});

final String newValue;
final String? oldValue;
final Iterable<String> imports;
final Iterable<String> parts;
final String? partOf;

}

String getListInEntrypoint(String type, String getter){
return 'List<$type> get $getter =>';
}

int calculateStartIndex(String data, String keyword){
return data.indexOf(
keyword,
) - '@override'.length - [13, 10].length * 2;
}
Loading
Loading