platform/packages/jael/jael_web/lib/src/builder/builder.dart
2021-12-20 00:22:12 +08:00

145 lines
4.9 KiB
Dart

import 'dart:async';
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:code_builder/code_builder.dart';
import 'package:jael3/jael3.dart' as jael;
import 'package:jael3_preprocessor/jael3_preprocessor.dart' as jael;
import 'package:jael3_web/jael3_web.dart';
import 'package:path/path.dart' as p;
import 'package:source_gen/source_gen.dart';
import 'util.dart';
var _upper = RegExp(r'^[A-Z]');
Builder jaelComponentBuilder(_) {
return SharedPartBuilder([JaelComponentGenerator()], 'jael_web_cmp');
}
class JaelComponentGenerator extends GeneratorForAnnotation<Jael> {
@override
Future<String> generateForAnnotatedElement(
Element element, ConstantReader annotation, BuildStep buildStep) async {
if (element is ClassElement) {
// Load the template
String? templateString;
var inputId = buildStep.inputId;
var ann = Jael(
template: annotation.peek('template')?.stringValue,
templateUrl: annotation.peek('templateUrl')?.stringValue,
asDsx: annotation.peek('asDsx')?.boolValue ?? false,
);
if (ann.template == null && ann.templateUrl == null) {
throw 'Both `template` and `templateUrl` cannot be null.';
}
if (ann.template != null) {
templateString = ann.template;
} else {
var dir = p.dirname(inputId.path);
var assetId = AssetId(inputId.package, p.join(dir, ann.templateUrl));
if (!await buildStep.canRead(assetId)) {
throw 'Cannot find template "${assetId.uri}"';
} else {
templateString = await buildStep.readAsString(assetId);
}
}
var fs = BuildFileSystem(buildStep, inputId.package);
var errors = <jael.JaelError>[];
var doc = jael.parseDocument(templateString!,
sourceUrl: inputId.uri, asDSX: ann.asDsx!, onError: errors.add);
if (errors.isEmpty) {
doc = await jael.resolve(doc!, fs.file(inputId.uri).parent,
onError: errors.add);
}
if (errors.isNotEmpty) {
errors.forEach(log.severe);
throw 'Jael processing finished with ${errors.length} error(s).';
}
// Generate a _XJaelTemplate mixin class
var clazz = Class((b) {
b
..abstract = true
..name = '_${element.name}JaelTemplate'
..implements.add(convertTypeReference(element.supertype));
// Add fields corresponding to each of the class's fields.
for (var field in element.fields) {
b.methods.add(Method((b) {
b
..name = field.name
..type = MethodType.getter
..returns = convertTypeReference(field.type);
}));
}
// ... And methods too.
for (var method in element.methods) {
b.methods.add(Method((b) {
b
..name = method.name
..returns = convertTypeReference(method.returnType)
..requiredParameters.addAll(method.parameters
.where(isRequiredParameter)
.map(convertParameter))
..optionalParameters.addAll(method.parameters
.where(isOptionalParameter)
.map(convertParameter));
}));
}
// Add a render() stub
b.methods.add(Method((b) {
b
..name = 'render'
..returns = refer('DomNode')
..annotations.add(refer('override'))
..body = Block((b) {
var result = compileElementChild(doc!.root);
b.addExpression(result.returned);
});
}));
});
return clazz.accept(DartEmitter()).toString();
} else {
throw '@Jael() is only supported for classes.';
}
}
Expression compileElementChild(jael.ElementChild child) {
if (child is jael.TextNode || child is jael.Text) {
return refer('text').call([literalString(child.span.text)]);
} else if (child is jael.Interpolation) {
Expression expr = CodeExpression(Code(child.expression.span.text));
expr = expr.property('toString').call([]);
return refer('text').call([expr]);
} else if (child is jael.Element) {
// TODO: Handle strict resolution
var attrs = <String, Expression>{};
for (var attr in child.attributes) {
attrs[attr.name] = attr.value == null
? literalTrue
: CodeExpression(Code(attr.value!.span.text));
}
var tagName = child.tagName.name;
if (!_upper.hasMatch(tagName)) {
return refer('h').call([
literalString(tagName),
literalMap(attrs),
literalList(child.children.map(compileElementChild)),
]);
} else {
// TODO: How to pass children?
return refer(tagName).newInstance([], attrs);
}
// return refer(child.tagName.name).newInstance([]);
} else {
throw 'Unsupported: $child';
}
}
}