blob: 9dd07553abbf1b27b332eec7fc3a4346a0f9c64f [file] [log] [blame]
// Copyright (c) 2024, 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:dart_model/dart_model.dart';
import 'package:macro/macro.dart';
import 'package:macro_service/macro_service.dart';
import '../macro_client.dart';
abstract base class BuilderBase<T extends Object> implements Builder<T> {
@override
final T target;
final Host host;
final AugmentResponse response;
@override
Model get model => MacroScope.current.model;
BuilderBase(this.target, this.host)
: response = AugmentResponse(libraryAugmentations: [], newTypeNames: []);
BuilderBase.nested(this.target, this.host, this.response);
@override
Future<Model> query(Query query) => host.query(query);
}
abstract base class TypesBuilderBase<T extends Object> extends BuilderBase<T>
implements TypesBuilder<T> {
TypesBuilderBase(super.target, super.host);
TypesBuilderBase.nested(super.target, super.host, super.response)
: super.nested();
@override
void declareType(String name, Augmentation typeDeclaration) {
response.newTypeNames!.add(name);
response.libraryAugmentations!.add(typeDeclaration);
}
}
base mixin ExtendsClauseBuilderImpl<T extends Interface> on TypesBuilderBase<T>
implements ExtendsClauseBuilder<T> {
/// Sets the `extends` clause to [superclass].
///
/// The type must not already have an `extends` clause.
@override
void extendsType(Augmentation superclass) {
response.extendsTypeAugmentations!.update(
model.qualifiedNameOf(target.node)!.name,
// TODO: This should only ever have one thing, but we don't have setters
// so a list is a hack for lazily adding this field. We should probably
// throw though if we see more than one.
(value) => value..add(superclass),
ifAbsent: () => [superclass],
);
}
}
base mixin ImplementsClauseBuilderImpl<T extends Interface>
on TypesBuilderBase<T>
implements ImplementsClauseBuilder<T> {
/// Appends [interfaces] to the list of interfaces for this type.
@override
void appendInterfaces(Iterable<Augmentation> interfaces) {
response.interfaceAugmentations!.update(
model.qualifiedNameOf(target.node)!.name,
(value) => value..addAll(interfaces),
ifAbsent: () => [...interfaces],
);
}
}
base mixin WithClauseBuilderImpl<T extends Interface> on TypesBuilderBase<T>
implements WithClauseBuilder<T> {
/// Appends [mixins] to the list of mixins for this type.
@override
void appendMixins(Iterable<Augmentation> mixins) {
response.mixinAugmentations!.update(
model.qualifiedNameOf(target.node)!.name,
(value) => value..addAll(mixins),
ifAbsent: () => [...mixins],
);
}
}
final class ClassTypesBuilderImpl<T extends Interface>
extends TypesBuilderBase<T>
with
WithClauseBuilderImpl<T>,
ImplementsClauseBuilderImpl<T>,
ExtendsClauseBuilderImpl<T>
implements ClassTypesBuilder<T> {
ClassTypesBuilderImpl(super.target, super.host);
ClassTypesBuilderImpl.nested(super.target, super.host, super.response)
: super.nested();
}
abstract base class DeclarationsBuilderBase<T extends Object>
extends BuilderBase<T>
implements DeclarationsBuilder<T> {
DeclarationsBuilderBase(super.target, super.host);
DeclarationsBuilderBase.nested(super.target, super.host, super.response)
: super.nested();
@override
void declareInLibrary(Augmentation declaration) {
response.libraryAugmentations!.add(declaration);
}
}
abstract base class MemberDeclarationsBuilderBase<T extends Interface>
extends DeclarationsBuilderBase<T>
implements MemberDeclarationsBuilder<T> {
MemberDeclarationsBuilderBase(super.target, super.host);
MemberDeclarationsBuilderBase.nested(super.target, super.host, super.response)
: super.nested();
@override
void declareInType(Augmentation declaration) {
response.typeAugmentations!.update(
model.qualifiedNameOf(target.node)!.name,
(value) => value..add(declaration),
ifAbsent: () => [declaration],
);
}
}
final class ClassDeclarationsBuilderImpl<T extends Interface>
extends MemberDeclarationsBuilderBase<T>
implements ClassDeclarationsBuilder<T> {
ClassDeclarationsBuilderImpl(super.target, super.host);
ClassDeclarationsBuilderImpl.nested(super.target, super.host, super.response)
: super.nested();
}
final class MethodDefinitionsBuilderImpl<T extends Member>
extends BuilderBase<T>
implements MethodDefinitionsBuilder<T> {
MethodDefinitionsBuilderImpl(super.target, super.host);
MethodDefinitionsBuilderImpl.nested(super.target, super.host, super.response)
: super.nested();
@override
void augment({Augmentation? body, Augmentation? docCommentsAndMetadata}) {
final augmentation = _buildFunctionAugmentation(
body,
target,
model,
docComments: docCommentsAndMetadata,
);
response.typeAugmentations!.update(
target.parentInterface.name,
(value) => value..add(augmentation),
ifAbsent: () => [augmentation],
);
}
}
final class ConstructorDefinitionsBuilderImpl<T extends Member>
extends BuilderBase<T>
implements ConstructorDefinitionsBuilder<T> {
ConstructorDefinitionsBuilderImpl(super.target, super.host);
ConstructorDefinitionsBuilderImpl.nested(
super.target,
super.host,
super.response,
) : super.nested();
@override
void augment({
Augmentation? body,
List<Augmentation>? initializers,
Augmentation? docCommentsAndMetadata,
}) {
final augmentation = _buildFunctionAugmentation(
body,
target,
model,
initializers: initializers,
docComments: docCommentsAndMetadata,
);
response.typeAugmentations!.update(
target.parentInterface.name,
(value) => value..add(augmentation),
ifAbsent: () => [augmentation],
);
}
}
abstract base class InterfaceDefinitionsBuilderBase<T extends Interface>
extends BuilderBase<T>
implements InterfaceDefinitionsBuilder<T> {
InterfaceDefinitionsBuilderBase(super.target, super.host);
InterfaceDefinitionsBuilderBase.nested(
super.target,
super.host,
super.response,
) : super.nested();
/// Retrieve a [FieldDefinitionsBuilder] for a field with [name] in
/// [target].
///
/// Throws if [name] does not refer to a field in the [target] interface
/// declaration.
@override
FieldDefinitionsBuilder buildField(QualifiedName name) =>
throw UnimplementedError();
/// Retrieve a [MethodDefinitionsBuilder] for a method with [name] in
/// [target].
///
/// Throws if [name] does not refer to a method in the [target] interface
/// declaration.
@override
MethodDefinitionsBuilder buildMethod(QualifiedName name) =>
MethodDefinitionsBuilderImpl.nested(
Member.fromJson(model.lookup(name)!),
host,
response,
);
/// Retrieve a [ConstructorDefinitionsBuilder] for a constructor with [name]
/// in [target].
///
/// Throws if [name] does not refer to a constructor in the [target]
/// interface declaration.
@override
ConstructorDefinitionsBuilder buildConstructor(QualifiedName name) =>
ConstructorDefinitionsBuilderImpl.nested(
Member.fromJson(model.lookup(name)!),
host,
response,
);
}
final class ClassDefinitionsBuilderImpl<T extends Interface>
extends InterfaceDefinitionsBuilderBase<T>
implements ClassDefinitionsBuilder<T> {
ClassDefinitionsBuilderImpl(super.target, super.host);
ClassDefinitionsBuilderImpl.nested(super.target, super.host, super.response)
: super.nested();
@override
void augment({Augmentation? docCommentsAndMetadata}) {
if (docCommentsAndMetadata == null) return;
response.typeAugmentations!.update(
model.qualifiedNameOf(target.node)!.name,
(value) => value..add(docCommentsAndMetadata),
ifAbsent: () => [docCommentsAndMetadata],
);
}
}
/// Builds the code to augment a function, method, or constructor with a new
/// body.
///
/// The [initializers] parameter can only be used if [declaration] is a
/// constructor.
Augmentation _buildFunctionAugmentation(
Augmentation? body,
Member declaration,
Model model, {
List<Augmentation>? initializers,
Augmentation? docComments,
}) {
assert(initializers == null || declaration.properties.isConstructor);
final properties = declaration.properties;
final qualifiedName = model.qualifiedNameOf(declaration.node)!;
final parts = <Object?>[
if (docComments != null) ...[docComments, '\n'],
if (declaration.properties.isMethod) ' ',
'augment ',
if (properties.isConstructor) ...[
// TODO: Uncomment once we have `isConst`.
// if (properties.isConst) 'const ',
// TODO: Uncomment once we have `isFactory`.
// if (properties.isFactory) 'factory ',
declaration.parentInterface.name,
if (qualifiedName.name.isNotEmpty) '.',
] else ...[
if (properties.isMethod && properties.isStatic) 'static ',
// TODO: Uncomment once we have support for converting types into code.
// declaration.returnType.code,
' ',
// TODO: Uncomment once we have `isOperator`.
// if (properties.isOperator) 'operator ',
],
if (properties.isGetter) 'get ',
// TODO: Uncomment once we have `isSetter`.
if (properties.isGetter) 'set ',
qualifiedName.name,
if (!properties.isGetter) ...[
// TODO: Uncomment once we have `typeParameters`.
// if (declaration.typeParameters.isNotEmpty) ...[
// '<',
// for (TypeParameterDeclaration typeParam
// in declaration.typeParameters) ...[
// typeParam.identifier.name,
// if (typeParam.bound != null) ...[
// ' extends ',
// typeParam.bound!.code,
// ],
// if (typeParam != declaration.typeParameters.last) ', ',
// ],
// '>',
// ],
'(',
// TODO: Extreme hack alert!!! Parameters only expose their types today
// which isn't enough, this assumes we are a fromJson method :D.
if (declaration.requiredPositionalParameters.length == 1)
'json'
else if (declaration.requiredPositionalParameters.isNotEmpty)
throw UnimplementedError(),
// for (var positionalRequired
// in declaration.requiredPositionalParameters) ...[
// positionalRequired.code,
// ', ',
// ],
if (declaration.optionalPositionalParameters.isNotEmpty) ...[
// TODO: Implement this.
throw UnimplementedError(),
// '[',
// for (var positionalOptional
// in declaration.optionalPositionalParameters) ...[
// positionalOptional.code,
// ', ',
// ],
// ']',
],
if (declaration.namedParameters.isNotEmpty) ...[
// TODO: Implement this.
throw UnimplementedError(),
// '{',
// for (var named in declaration.namedParameters) ...[
// named.code,
// ', ',
// ],
// '}',
],
')',
],
if (initializers != null && initializers.isNotEmpty) ...[
'\n : ',
...initializers.first.code,
for (var initializer in initializers.skip(1)) ...[
',\n ',
...initializer.code,
],
],
if (body == null) ';' else ...[' ', ...body.code],
];
return Augmentation(
code: [
for (var part in parts)
switch (part) {
String() => Code.string(part),
// TODO: All maps will pass this check...
Code() => part,
_ => throw StateError('Unexpected code kind $part'),
},
],
);
}