summaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2026-09-19 22:21:42 +0100
committerMichael Williamson <mike@zwobble.org>2026-09-19 22:21:42 +0100
commit8b601565c9e14aa673dca94bd79c84ad6f2f04f3 (patch)
tree00addc1f0d541542c9f047636d285aebf2ffecf4 /src/main/java
parent3563b294bfaeb494511d6542301ea2a3b77091d5 (diff)
Add partial import support to typescript-types
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/output/generators/typescript/TypescriptGenerator.java59
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/output/generators/typescripttypes/TypescriptTypesGenerator.java65
2 files changed, 109 insertions, 15 deletions
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/typescript/TypescriptGenerator.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/typescript/TypescriptGenerator.java
index f19a641..25435c5 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/typescript/TypescriptGenerator.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/typescript/TypescriptGenerator.java
@@ -3,12 +3,15 @@ package org.zwobble.hobgoblin.compiler.output.generators.typescript;
import org.zwobble.hobgoblin.compiler.builtins.NativeTypes;
import org.zwobble.hobgoblin.compiler.output.lang.typescript.TypescriptWriter;
import org.zwobble.hobgoblin.compiler.output.lang.typescript.ast.*;
+import org.zwobble.hobgoblin.compiler.typechecker.TypesInfo;
import org.zwobble.hobgoblin.compiler.types.*;
import org.zwobble.hobgoblin.compiler.util.Casing;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
+import java.util.LinkedHashSet;
+import java.util.SequencedSet;
import java.util.stream.Collectors;
public class TypescriptGenerator {
@@ -30,6 +33,35 @@ public class TypescriptGenerator {
TypescriptWriter.writeTo(path, writer -> writer.writeModule(module));
}
+ public record RequiredType(SimpleType type, TypescriptIdentifier importName) {}
+
+ public static class Context {
+ private final SequencedSet<RequiredType> requiredTypes = new LinkedHashSet<>();
+ private final NamespaceName namespaceName;
+ private final TypesInfo typesInfo;
+
+ public Context(NamespaceName namespaceName, TypesInfo typesInfo) {
+ this.namespaceName = namespaceName;
+ this.typesInfo = typesInfo;
+ }
+
+ public NamespaceName namespaceName() {
+ return this.namespaceName;
+ }
+
+ public void requireType(SimpleType type, TypescriptIdentifier importName) {
+ this.requiredTypes.add(new RequiredType(type, importName));
+ }
+
+ public SequencedSet<RequiredType> requiredTypes() {
+ return requiredTypes;
+ }
+
+ public TypesInfo typesInfo() {
+ return typesInfo;
+ }
+ }
+
public TypescriptIdentifier generateModuleName(String namespaceNamePart) {
return new TypescriptIdentifier(namespaceNamePart);
}
@@ -46,13 +78,13 @@ public class TypescriptGenerator {
return new TypescriptIdentifier(Casing.lowerCamelCaseToUpperCamelCase(name));
}
- public TypescriptExpression generateTypeExpression(Type value) {
+ public TypescriptExpression generateTypeExpression(Type value, Context context) {
return switch (collapseType(value)) {
case ConstructedType constructedType -> {
yield new TypescriptConstructedType(
- generateTypeExpression(constructedType.constructor().genericType()),
+ generateTypeExpression(constructedType.constructor().genericType(), context),
constructedType.args().stream()
- .map(this::generateTypeExpression)
+ .map(argType -> generateTypeExpression(argType, context))
.toList()
);
}
@@ -65,7 +97,26 @@ public class TypescriptGenerator {
}
}
- yield new TypescriptRef(this.generateTypeName(simpleType.name()));
+ var typescriptName = this.generateTypeName(simpleType.name());
+
+ if (
+ !simpleType.namespaceName().isRoot() &&
+ !simpleType.namespaceName().equals(context.namespaceName())
+ ) {
+ var importName = new StringBuilder();
+ importName.append("$ns$");
+ for (var namespaceNamePart : simpleType.namespaceName().parts()) {
+ importName.append(this.generateModuleName(namespaceNamePart).value());
+ importName.append("$");
+ }
+ importName.append(this.generateTypeName(simpleType.name()).value());
+
+ typescriptName = TypescriptIdentifier.of(importName.toString());
+
+ context.requireType(simpleType, typescriptName);
+ }
+
+ yield new TypescriptRef(typescriptName);
}
case TypeLevelValueType _ -> {
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/typescripttypes/TypescriptTypesGenerator.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/typescripttypes/TypescriptTypesGenerator.java
index ac8e93a..dafb0d2 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/typescripttypes/TypescriptTypesGenerator.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/typescripttypes/TypescriptTypesGenerator.java
@@ -19,6 +19,7 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
+import java.util.stream.Collectors;
public class TypescriptTypesGenerator implements Generator {
private static final String NAME = "typescript-types";
@@ -85,16 +86,49 @@ public class TypescriptTypesGenerator implements Generator {
.map(part -> this.typescriptGenerator.generateModuleName(part))
.toList();
+ // We could translate hobgoblin imports into TypeScript imports
+ // directly: for instance, `import {DocComment} from ast;` could be
+ // translated into `import {DocComment} from "./ast"`. However, given
+ // there may be custom code, we want to avoid conflicting with that
+ // custom code, or having the custom code depending on the generated
+ // imports. Therefore, track the uses of imported names and rename them
+ // to be fully qualified, for instance:
+ // `import {DocComment as $ns$ast$DocComment} from "./ast"`
+
+ var context = new TypescriptGenerator.Context(namespace.namespaceName(), typesInfo);
+
var statements = namespace.body().stream()
- .flatMap(statement -> generateNamespaceStatement(statement, typesInfo).stream())
+ .flatMap(statement -> generateNamespaceStatement(statement, context).stream())
.toList();
- return new TypescriptModule(typescriptModuleName, List.of(), statements);
+ // TODO: combine imports from same module
+
+ var imports = context.requiredTypes().stream()
+ .map(requiredType -> {
+ var exportName = this.typescriptGenerator.generateTypeName(requiredType.type().name());
+
+ return new TypescriptImportDeclaration(
+ List.of(
+ new TypescriptNamedImport(
+ exportName,
+ requiredType.importName()
+ )
+ ),
+ // TODO: handle imports from parent
+ "./" + requiredType.type().namespaceName().parts().stream()
+ .map(part -> this.typescriptGenerator.generateModuleName(part).value())
+ // TODO: handle extensions
+ .collect(Collectors.joining("/")) + ".ts"
+ );
+ })
+ .toList();
+
+ return new TypescriptModule(typescriptModuleName, imports, statements);
}
private List<TypescriptStatement> generateNamespaceStatement(
TypedNamespaceStatementNode statement,
- TypesInfo typesInfo
+ TypescriptGenerator.Context context
) {
switch (statement) {
case TypedEnumDefinitionNode enumDefinition -> {
@@ -106,11 +140,11 @@ public class TypescriptTypesGenerator implements Generator {
}
case TypedStructDefinitionNode structDefinition -> {
- return generateStructDefinition(structDefinition, typesInfo);
+ return generateStructDefinition(structDefinition, context);
}
case TypedSumDefinitionNode sumDefinition -> {
- return generateSumDefinition(sumDefinition, typesInfo);
+ return generateSumDefinition(sumDefinition, context);
}
}
}
@@ -188,7 +222,7 @@ public class TypescriptTypesGenerator implements Generator {
private List<TypescriptStatement> generateStructDefinition(
TypedStructDefinitionNode structDefinition,
- TypesInfo typesInfo
+ TypescriptGenerator.Context context
) {
var typescriptTypeName = this.typescriptGenerator.generateTypeName(structDefinition.name());
@@ -206,7 +240,7 @@ public class TypescriptTypesGenerator implements Generator {
var body = new ArrayList<TypescriptClassElement>();
- if (!typesInfo.variantOf(structDefinition.typeOrConstructor().unboundType()).isEmpty()) {
+ if (!context.typesInfo().variantOf(structDefinition.typeOrConstructor().unboundType()).isEmpty()) {
body.add(new TypescriptClassFieldDeclaration(
TAG_PROPERTY_NAME,
TypescriptVisibility.PUBLIC,
@@ -238,7 +272,10 @@ public class TypescriptTypesGenerator implements Generator {
TypescriptVisibility.PUBLIC,
false,
true,
- Optional.of(this.typescriptGenerator.generateTypeExpression(field.type().value())),
+ Optional.of(this.typescriptGenerator.generateTypeExpression(
+ field.type().value(),
+ context
+ )),
Optional.empty()
));
}
@@ -266,7 +303,10 @@ public class TypescriptTypesGenerator implements Generator {
structDefinition.fields().orElse(List.of()).stream()
.map(field -> new TypescriptParam(
this.typescriptGenerator.generateFieldName(field.name()),
- Optional.of(this.typescriptGenerator.generateTypeExpression(field.type().value()))
+ Optional.of(this.typescriptGenerator.generateTypeExpression(
+ field.type().value(),
+ context
+ ))
))
.toList(),
structDefinition.fields().orElse(List.of()).stream()
@@ -294,7 +334,7 @@ public class TypescriptTypesGenerator implements Generator {
private List<TypescriptStatement> generateSumDefinition(
TypedSumDefinitionNode sumDefinition,
- TypesInfo typesInfo
+ TypescriptGenerator.Context context
) {
var typescriptTypeName = this.typescriptGenerator.generateTypeName(sumDefinition.name());
@@ -303,7 +343,10 @@ public class TypescriptTypesGenerator implements Generator {
typescriptTypeName,
new TypescriptTypeUnion(
sumDefinition.variants().stream()
- .map(variant -> this.typescriptGenerator.generateTypeExpression(variant.type().value()))
+ .map(variant -> this.typescriptGenerator.generateTypeExpression(
+ variant.type().value(),
+ context
+ ))
.toList()
)
),