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: Write comments into generated Java and Kotlin #3104

Merged
merged 3 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import xyz.block.ftl.v1.schema.Module;
import xyz.block.ftl.v1.schema.Topic;
import xyz.block.ftl.v1.schema.Type;
import xyz.block.ftl.v1.schema.TypeAlias;
import xyz.block.ftl.v1.schema.Verb;

public abstract class JVMCodeGenerator implements CodeGenProvider {
Expand Down Expand Up @@ -77,7 +78,7 @@ public boolean trigger(CodeGenContext context) throws CodeGenException {
if (existing != null) {
nativeTypeAliasMap.put(new DeclRef(module.getName(), data.getName()),
nativeName);
generateTypeAliasMapper(module.getName(), data.getName(), packageName,
generateTypeAliasMapper(module.getName(), data, packageName,
Optional.of(nativeName),
context.outDir());
handled = true;
Expand All @@ -87,7 +88,7 @@ public boolean trigger(CodeGenContext context) throws CodeGenException {
}
}
if (!handled) {
generateTypeAliasMapper(module.getName(), data.getName(), packageName, Optional.empty(),
generateTypeAliasMapper(module.getName(), data, packageName, Optional.empty(),
context.outDir());
typeAliasMap.put(new DeclRef(module.getName(), data.getName()), data.getType());
}
Expand Down Expand Up @@ -141,7 +142,7 @@ public boolean trigger(CodeGenContext context) throws CodeGenException {
return true;
}

protected abstract void generateTypeAliasMapper(String module, String name, String packageName,
protected abstract void generateTypeAliasMapper(String module, TypeAlias typeAlias, String packageName,
Optional<String> nativeTypeAlias, Path outputDir) throws IOException;

protected abstract void generateTopicSubscription(Module module, Topic data, String packageName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,15 @@ public class JavaCodeGenerator extends JVMCodeGenerator {
public static final String PACKAGE_PREFIX = "ftl.";

@Override
protected void generateTypeAliasMapper(String module, String name, String packageName, Optional<String> nativeTypeAlias,
Path outputDir) throws IOException {
TypeSpec.Builder typeBuilder = TypeSpec.interfaceBuilder(className(name) + TYPE_MAPPER)
protected void generateTypeAliasMapper(String module, xyz.block.ftl.v1.schema.TypeAlias typeAlias,
String packageName, Optional<String> nativeTypeAlias, Path outputDir) throws IOException {
TypeSpec.Builder typeBuilder = TypeSpec.interfaceBuilder(className(typeAlias.getName()) + TYPE_MAPPER)
.addAnnotation(AnnotationSpec.builder(TypeAlias.class)
.addMember("name", "\"" + name + "\"")
.addMember("name", "\"" + typeAlias.getName() + "\"")
.addMember("module", "\"" + module + "\"")
.build())
.addModifiers(Modifier.PUBLIC);
.addModifiers(Modifier.PUBLIC)
.addJavadoc(String.join("\n", typeAlias.getCommentsList()));
if (nativeTypeAlias.isEmpty()) {
TypeVariableName finalType = TypeVariableName.get("T");
typeBuilder.addTypeVariable(finalType);
Expand All @@ -71,12 +72,9 @@ protected void generateTypeAliasMapper(String module, String name, String packag
typeBuilder.addSuperinterface(ParameterizedTypeName.get(ClassName.get(TypeAliasMapper.class),
ClassName.bestGuess(nativeTypeAlias.get()), ClassName.get(String.class)));
}
TypeSpec theType = typeBuilder
.build();

TypeSpec theType = typeBuilder.build();
JavaFile javaFile = JavaFile.builder(packageName, theType)
.build();

javaFile.writeTo(outputDir);
}

Expand Down Expand Up @@ -104,18 +102,19 @@ protected void generateTopicSubscription(Module module, Topic data, String packa
javaFile.writeTo(outputDir);
}

protected void generateEnum(Module module, Enum data, String packageName, Map<DeclRef, Type> typeAliasMap,
protected void generateEnum(Module module, Enum ennum, String packageName, Map<DeclRef, Type> typeAliasMap,
Map<DeclRef, String> nativeTypeAliasMap, Map<DeclRef, List<EnumInfo>> enumVariantInfoMap, Path outputDir)
throws IOException {
String interfaceType = className(data.getName());
if (data.hasType()) {
String interfaceType = className(ennum.getName());
if (ennum.hasType()) {
//Enums with a type are "value enums" - Java natively supports these
TypeSpec.Builder dataBuilder = TypeSpec.enumBuilder(interfaceType)
.addAnnotation(getGeneratedRefAnnotation(module.getName(), data.getName()))
.addAnnotation(getGeneratedRefAnnotation(module.getName(), ennum.getName()))
.addAnnotation(AnnotationSpec.builder(xyz.block.ftl.Enum.class).build())
.addModifiers(Modifier.PUBLIC);
.addModifiers(Modifier.PUBLIC)
.addJavadoc(String.join("\n", ennum.getCommentsList()));

TypeName enumType = toAnnotatedJavaTypeName(data.getType(), typeAliasMap, nativeTypeAliasMap);
TypeName enumType = toAnnotatedJavaTypeName(ennum.getType(), typeAliasMap, nativeTypeAliasMap);
dataBuilder.addField(enumType, "value", Modifier.PRIVATE, Modifier.FINAL);
dataBuilder.addMethod(MethodSpec.constructorBuilder()
.addParameter(enumType, "value")
Expand All @@ -128,8 +127,8 @@ protected void generateEnum(Module module, Enum data, String packageName, Map<De
.addStatement("return value")
.build());

var format = data.getType().hasString() ? "$S" : "$L";
for (var i : data.getVariantsList()) {
var format = ennum.getType().hasString() ? "$S" : "$L";
for (var i : ennum.getVariantsList()) {
Object value = toJavaValue(i.getValue());
dataBuilder.addEnumConstant(i.getName(), TypeSpec.anonymousClassBuilder(format, value).build());
}
Expand All @@ -143,14 +142,15 @@ protected void generateEnum(Module module, Enum data, String packageName, Map<De
// TODO JavaPoet doesn't support 'sealed' or 'permits' syntax yet, so we can't seal the interface
// https://github.com/square/javapoet/issues/823
TypeSpec.Builder interfaceBuilder = TypeSpec.interfaceBuilder(interfaceType)
.addAnnotation(getGeneratedRefAnnotation(module.getName(), data.getName()))
.addAnnotation(getGeneratedRefAnnotation(module.getName(), ennum.getName()))
.addAnnotation(AnnotationSpec.builder(xyz.block.ftl.Enum.class).build())
.addModifiers(Modifier.PUBLIC);
.addModifiers(Modifier.PUBLIC)
.addJavadoc(String.join("\n", ennum.getCommentsList()));

Map<String, TypeName> variantValuesTypes = data.getVariantsList().stream().collect(
Map<String, TypeName> variantValuesTypes = ennum.getVariantsList().stream().collect(
Collectors.toMap(EnumVariant::getName, v -> toAnnotatedJavaTypeName(v.getValue().getTypeValue().getValue(),
typeAliasMap, nativeTypeAliasMap)));
for (var variant : data.getVariantsList()) {
for (var variant : ennum.getVariantsList()) {
// Interface has isX and getX methods for each variant
String name = variant.getName();
TypeName valueTypeName = variantValuesTypes.get(name);
Expand All @@ -170,7 +170,7 @@ protected void generateEnum(Module module, Enum data, String packageName, Map<De
// Store this variant in enumVariantInfoMap so we can fetch it later
DeclRef key = new DeclRef(module.getName(), name);
List<EnumInfo> variantInfos = enumVariantInfoMap.computeIfAbsent(key, k -> new ArrayList<>());
variantInfos.add(new EnumInfo(interfaceType, variant, data.getVariantsList()));
variantInfos.add(new EnumInfo(interfaceType, variant, ennum.getVariantsList()));
} else {
// Value type isn't a Ref, so we make a wrapper class that implements our interface
TypeSpec.Builder dataBuilder = TypeSpec.classBuilder(className(name))
Expand Down Expand Up @@ -206,7 +206,8 @@ protected void generateDataObject(Module module, Data data, String packageName,
String thisType = className(data.getName());
TypeSpec.Builder dataBuilder = TypeSpec.classBuilder(thisType)
.addAnnotation(getGeneratedRefAnnotation(module.getName(), data.getName()))
.addModifiers(Modifier.PUBLIC);
.addModifiers(Modifier.PUBLIC)
.addJavadoc(String.join("\n", data.getCommentsList()));

// if data is part of a type enum, generate the interface methods for each variant
DeclRef key = new DeclRef(module.getName(), data.getName());
Expand Down Expand Up @@ -284,36 +285,40 @@ protected void generateVerb(Module module, Verb verb, String packageName, Map<De
.addMember("module", "\"" + module.getName() + "\"")
.build())
.addModifiers(Modifier.PUBLIC);
var comments = String.join("\n", verb.getCommentsList());
if (verb.getRequest().hasUnit() && verb.getResponse().hasUnit()) {
typeBuilder.addSuperinterface(ClassName.get(VerbClientEmpty.class));
typeBuilder.addSuperinterface(ClassName.get(VerbClientEmpty.class))
.addJavadoc(comments);
} else if (verb.getRequest().hasUnit()) {
typeBuilder.addSuperinterface(ParameterizedTypeName.get(ClassName.get(VerbClientSource.class),
toJavaTypeName(verb.getResponse(), typeAliasMap, nativeTypeAliasMap, true)));
typeBuilder.addMethod(MethodSpec.methodBuilder("call")
.returns(toAnnotatedJavaTypeName(verb.getResponse(), typeAliasMap, nativeTypeAliasMap))
.addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC).build());
.addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
.addJavadoc(comments)
.build());
} else if (verb.getResponse().hasUnit()) {
typeBuilder.addSuperinterface(ParameterizedTypeName.get(ClassName.get(VerbClientSink.class),
toJavaTypeName(verb.getRequest(), typeAliasMap, nativeTypeAliasMap, true)));
typeBuilder.addMethod(MethodSpec.methodBuilder("call").returns(TypeName.VOID)
typeBuilder.addMethod(MethodSpec.methodBuilder("call")
.returns(TypeName.VOID)
.addParameter(toAnnotatedJavaTypeName(verb.getRequest(), typeAliasMap, nativeTypeAliasMap), "value")
.addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC).build());
.addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
.addJavadoc(comments)
.build());
} else {
typeBuilder.addSuperinterface(ParameterizedTypeName.get(ClassName.get(VerbClient.class),
toJavaTypeName(verb.getRequest(), typeAliasMap, nativeTypeAliasMap, true),
toJavaTypeName(verb.getResponse(), typeAliasMap, nativeTypeAliasMap, true)));
typeBuilder.addMethod(MethodSpec.methodBuilder("call")
.returns(toAnnotatedJavaTypeName(verb.getResponse(), typeAliasMap, nativeTypeAliasMap))
.addParameter(toAnnotatedJavaTypeName(verb.getRequest(), typeAliasMap, nativeTypeAliasMap), "value")
.addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC).build());
.addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
.addJavadoc(comments)
.build());
}

TypeSpec helloWorld = typeBuilder
.build();

JavaFile javaFile = JavaFile.builder(packageName, helloWorld)
.build();

JavaFile javaFile = JavaFile.builder(packageName, typeBuilder.build()).build();
javaFile.writeTo(outputDir);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,17 @@ public class KotlinCodeGenerator extends JVMCodeGenerator {
public static final String PACKAGE_PREFIX = "ftl.";

@Override
protected void generateTypeAliasMapper(String module, String name, String packageName, Optional<String> nativeTypeAlias,
protected void generateTypeAliasMapper(String module, xyz.block.ftl.v1.schema.TypeAlias typeAlias, String packageName,
Optional<String> nativeTypeAlias,
Path outputDir) throws IOException {
String thisType = className(name) + TYPE_MAPPER;
String thisType = className(typeAlias.getName()) + TYPE_MAPPER;
TypeSpec.Builder typeBuilder = TypeSpec.interfaceBuilder(thisType)
.addAnnotation(AnnotationSpec.builder(TypeAlias.class)
.addMember("name=\"" + name + "\"")
.addMember("name=\"" + typeAlias.getName() + "\"")
.addMember("module=\"" + module + "\"")
.build())
.addModifiers(KModifier.PUBLIC);
.addModifiers(KModifier.PUBLIC)
.addKdoc(String.join("\n", typeAlias.getCommentsList()));
if (nativeTypeAlias.isEmpty()) {
TypeVariableName finalType = TypeVariableName.get("T");
typeBuilder.addTypeVariable(finalType);
Expand Down Expand Up @@ -101,7 +103,8 @@ protected void generateEnum(Module module, Enum data, String packageName, Map<De
AnnotationSpec.builder(GeneratedRef.class)
.addMember("name=\"" + data.getName() + "\"")
.addMember("module=\"" + module.getName() + "\"").build())
.addModifiers(KModifier.PUBLIC);
.addModifiers(KModifier.PUBLIC)
.addKdoc(String.join("\n", data.getCommentsList()));

for (var i : data.getVariantsList()) {
dataBuilder.addEnumConstant(i.getName());
Expand All @@ -122,7 +125,8 @@ protected void generateDataObject(Module module, Data data, String packageName,
AnnotationSpec.builder(GeneratedRef.class)
.addMember("name=\"" + data.getName() + "\"")
.addMember("module=\"" + module.getName() + "\"").build())
.addModifiers(KModifier.PUBLIC);
.addModifiers(KModifier.PUBLIC)
.addKdoc(String.join("\n", data.getCommentsList()));
if (!data.getFieldsList().isEmpty()) {
dataBuilder.addModifiers(KModifier.DATA);
}
Expand Down Expand Up @@ -159,28 +163,36 @@ protected void generateVerb(Module module, Verb verb, String packageName, Map<De
.addMember("module=\"" + module.getName() + "\"")
.build())
.addModifiers(KModifier.PUBLIC);
String comments = String.join("\n", verb.getCommentsList());
if (verb.getRequest().hasUnit() && verb.getResponse().hasUnit()) {
typeBuilder.addSuperinterface(className(VerbClientEmpty.class), CodeBlock.of(""));
typeBuilder.addSuperinterface(className(VerbClientEmpty.class), CodeBlock.of(""))
.addKdoc(comments);
} else if (verb.getRequest().hasUnit()) {
typeBuilder.addSuperinterface(ParameterizedTypeName.get(className(VerbClientSource.class),
toKotlinTypeName(verb.getResponse(), typeAliasMap, nativeTypeAliasMap)), CodeBlock.of(""));
typeBuilder.addFunction(FunSpec.builder("call")
.returns(toKotlinTypeName(verb.getResponse(), typeAliasMap, nativeTypeAliasMap))
.addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE, KModifier.ABSTRACT).build());
.addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE, KModifier.ABSTRACT)
.addKdoc(comments)
.build());
} else if (verb.getResponse().hasUnit()) {
typeBuilder.addSuperinterface(ParameterizedTypeName.get(className(VerbClientSink.class),
toKotlinTypeName(verb.getRequest(), typeAliasMap, nativeTypeAliasMap)), CodeBlock.of(""));
typeBuilder.addFunction(FunSpec.builder("call")
.addModifiers(KModifier.OVERRIDE, KModifier.ABSTRACT)
.addParameter("value", toKotlinTypeName(verb.getRequest(), typeAliasMap, nativeTypeAliasMap)).build());
.addParameter("value", toKotlinTypeName(verb.getRequest(), typeAliasMap, nativeTypeAliasMap))
.addKdoc(comments)
.build());
} else {
typeBuilder.addSuperinterface(ParameterizedTypeName.get(className(VerbClient.class),
toKotlinTypeName(verb.getRequest(), typeAliasMap, nativeTypeAliasMap),
toKotlinTypeName(verb.getResponse(), typeAliasMap, nativeTypeAliasMap)), CodeBlock.of(""));
typeBuilder.addFunction(FunSpec.builder("call")
.returns(toKotlinTypeName(verb.getResponse(), typeAliasMap, nativeTypeAliasMap))
.addParameter("value", toKotlinTypeName(verb.getRequest(), typeAliasMap, nativeTypeAliasMap))
.addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE, KModifier.ABSTRACT).build());
.addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE, KModifier.ABSTRACT)
.addKdoc(comments)
.build());
}

FileSpec javaFile = FileSpec.builder(packageName, thisType)
Expand Down
29 changes: 29 additions & 0 deletions jvm-runtime/testdata/go/gomodule/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ import (
"github.com/TBD54566975/ftl/go-runtime/ftl"
)

// Comment on topic
//
//ftl:export
var TestTopic = ftl.Topic[TestObject]("testTopic")

// Comment on subscription
//
//ftl:export
var testSubscription = ftl.Subscription(TestTopic, "testSubscription")

// Comment on data
type TestObject struct {
IntField int
FloatField float64
Expand All @@ -21,6 +32,8 @@ type TestObject struct {
MapField map[string]string
}

// Multiline comment
// Is longer than others
type TestObjectOptionalFields struct {
IntField ftl.Option[int]
FloatField ftl.Option[float64]
Expand All @@ -39,6 +52,8 @@ type ParameterizedType[T any] struct {
Map map[string]T
}

// Comment on value enum
//
//ftl:enum export
type ColorInt int

Expand Down Expand Up @@ -77,13 +92,17 @@ type TypeEnumWrapper struct {
Type TypeWrapperEnum
}

// Comment on type enum
//
//ftl:enum
type Animal interface{ animal() }
type Cat struct {
Name string
Breed string
FurLength int
}

// Comment on type enum variant
type Dog struct{}

func (Cat) animal() {}
Expand All @@ -101,12 +120,16 @@ type AnimalWrapper struct {
//func (Word) mixed() {}
//func (Dog) mixed() {}

// Comment on type alias
//
//ftl:typealias
//ftl:typemap kotlin "web5.sdk.dids.didcore.Did"
type DID = did.DID

// Test different signatures

// Comment on source verb
//
//ftl:verb export
func SourceVerb(ctx context.Context) (string, error) {
return "Source Verb", nil
Expand All @@ -117,11 +140,15 @@ type ExportedType[T any, S any] interface {
FTLDecode(in S) (T, error)
}

// Comment on sink verb
//
//ftl:verb export
func SinkVerb(ctx context.Context, req string) error {
return nil
}

// Comment on empty verb
//
//ftl:verb export
func EmptyVerb(ctx context.Context) error {
return nil
Expand All @@ -134,6 +161,8 @@ func ErrorEmptyVerb(ctx context.Context) error {

// Test different param and return types

// Comment on verb
//
//ftl:verb export
func IntVerb(ctx context.Context, val int) (int, error) {
return val, nil
Expand Down