blob: adebb52f9641fc828065d6c3e409e16c43ce5af1 [file] [log] [blame]
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/member.dart';
import 'package:build/build.dart';
import 'package:dartdoc/src/mustachio/annotations.dart';
import 'codegen_aot_compiler.dart';
import 'codegen_runtime_renderer.dart';
const rendererClassesArePublicOption = 'rendererClassesArePublic';
/// A [Builder] which builds runtime Mustachio renderers.
class MustachioBuilder implements Builder {
final bool _rendererClassesArePublic;
MustachioBuilder({bool rendererClassesArePublic = false})
: _rendererClassesArePublic = rendererClassesArePublic;
@override
final buildExtensions = const {
'.dart': [
'.aot_renderers_for_html.dart',
'.aot_renderers_for_md.dart',
'.runtime_renderers.dart',
]
};
@override
Future<void> build(BuildStep buildStep) async {
// Each `buildStep` has a single input.
var inputId = buildStep.inputId;
final entryLib = await buildStep.inputLibrary;
final rendererGatherer = _RendererGatherer(entryLib);
// Create a new target `AssetId` based on the old one.
var runtimeRenderersLibrary =
inputId.changeExtension('.runtime_renderers.dart');
var aotLibraries = {
TemplateFormat.html:
inputId.changeExtension('.aot_renderers_for_html.dart'),
TemplateFormat.md: inputId.changeExtension('.aot_renderers_for_md.dart'),
};
if (rendererGatherer._rendererSpecs.isEmpty) {
await buildStep.writeAsString(runtimeRenderersLibrary, '');
await buildStep.writeAsString(aotLibraries[TemplateFormat.html]!, '');
await buildStep.writeAsString(aotLibraries[TemplateFormat.md]!, '');
return;
}
var runtimeRenderersContents = buildRuntimeRenderers(
rendererGatherer._rendererSpecs,
entryLib.source.uri,
entryLib.typeProvider,
entryLib.typeSystem,
rendererClassesArePublic: _rendererClassesArePublic,
);
await buildStep.writeAsString(
runtimeRenderersLibrary, runtimeRenderersContents);
for (var format in TemplateFormat.values) {
String aotRenderersContents;
var someSpec = rendererGatherer._rendererSpecs.first;
if (someSpec.standardTemplateUris[format] != null) {
aotRenderersContents = await compileTemplatesToRenderers(
rendererGatherer._rendererSpecs,
entryLib.source.uri,
buildStep,
entryLib.typeProvider,
entryLib.typeSystem,
format,
);
} else {
aotRenderersContents = '';
}
await buildStep.writeAsString(
aotLibraries[format]!, aotRenderersContents);
}
}
}
class _RendererGatherer {
final Set<RendererSpec> _rendererSpecs;
_RendererGatherer._(this._rendererSpecs);
factory _RendererGatherer(LibraryElement entryLib) {
final rendererSpecs = <RendererSpec>{};
void addRendererSpecs(List<ElementAnnotation> annotations) {
for (var annotation in annotations) {
if (annotation.element is ConstructorElement) {
if (annotation.element!.enclosingElement!.name == 'Renderer') {
rendererSpecs.add(_buildRendererSpec(annotation));
}
} else if (annotation.element is ConstructorMember) {
if (annotation.element!.enclosingElement!.name == 'Renderer') {
rendererSpecs.add(_buildRendererSpec(annotation));
}
}
}
}
addRendererSpecs(entryLib.metadata);
for (final element in entryLib.topLevelElements) {
addRendererSpecs(element.metadata);
}
return _RendererGatherer._(rendererSpecs);
}
static RendererSpec _buildRendererSpec(ElementAnnotation annotation) {
var constantValue = annotation.computeConstantValue()!;
var nameField = constantValue.getField('name')!;
if (nameField.isNull) {
throw StateError('@Renderer name must not be null');
}
var contextField = constantValue.getField('context')!;
if (contextField.isNull) {
throw StateError('@Renderer context must not be null');
}
var contextFieldType = contextField.type as InterfaceType;
assert(contextFieldType.typeArguments.length == 1);
var contextType = contextFieldType.typeArguments.single;
var visibleTypesField = constantValue.getField('visibleTypes')!;
if (visibleTypesField.isNull) {
throw StateError('@Renderer visibleTypes must not be null');
}
var visibleTypes = {
...visibleTypesField.toSetValue()!.map((object) => object.toTypeValue()!)
};
var standardHtmlTemplateField =
constantValue.getField('standardHtmlTemplate');
var standardMdTemplateField = constantValue.getField('standardMdTemplate');
return RendererSpec(
nameField.toSymbolValue()!,
contextType as InterfaceType,
visibleTypes,
standardHtmlTemplateField!.toStringValue()!,
standardMdTemplateField!.toStringValue()!,
);
}
}
Builder mustachioBuilder(BuilderOptions options) => MustachioBuilder(
rendererClassesArePublic:
options.config[rendererClassesArePublicOption] as bool? ?? false);