Skip to content

Commit

Permalink
feat: add anonymous classes
Browse files Browse the repository at this point in the history
  • Loading branch information
simonseyock committed Jan 9, 2025
1 parent 788497c commit 91f0f47
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 100 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ The program runs in multiple steps:
* At the moment it works like this: for a given name the program checks if the name is a parameter of a method or a property of the class, if yes it prepends this.
* this is not sufficient for names from parent class. if we would gather all local names (names in the class context, parameters and any local variables) we could determine this correctly.
* Find a clever way to transform constructs that work in Java into equivalent TypeScript structures
* super is called
* super is called to late sometimes
* What to do with properties and methods with the same name??
* create drop in replacements for java builtins
* List, ArrayList, Collection -> array
* Automatically filter files that contain un-translatable structures
Expand Down
12 changes: 11 additions & 1 deletion src/main/scala/java2typescript/ast/expression.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ case class CallExpression(
}

case class NewExpression (
expression: Identifier,
expression: Expression,
arguments: List[Expression] = List(),
typeArguments: List[Type] = List()
) extends Expression {
Expand Down Expand Up @@ -102,3 +102,13 @@ case class VariableDeclarationList(
) extends Expression {
val kind: SyntaxKind = SyntaxKind.VariableDeclarationList
}

case class ClassExpression(
name: Option[Identifier],
typeParameters: List[Type] = List(),
heritageClauses: List[HeritageClause] = List(),
members: List[Member] = List(),
modifiers: List[Modifier] = List()
) extends Expression {
val kind: SyntaxKind = SyntaxKind.ClassExpression
}
11 changes: 11 additions & 0 deletions src/main/scala/java2typescript/main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,14 @@ class Progress (val total: Int, val barLength: Int = 50) {
println("")
}
}

//class ProgressLogger(val numberOfSteps: Int) {
// var currentStepTitle: String
// var currentStep: Int = 0
//
// def step(title: String): Unit =
// currentStepTitle = title
// currentStep += 1
//
//
//}
91 changes: 0 additions & 91 deletions src/main/scala/java2typescript/transformer/classes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -119,57 +119,6 @@ def transformClassOrInterfaceDeclaration(
extractedStatements
}

def transformEnumDeclaration(
context: FileContext|ClassContext,
decl: EnumDeclaration,
additionalModifiers: List[ast.Modifier] = List()
): List[ast.Statement] =
val enumMembers = decl.getEntries.asScala.map(e => ast.EnumMember(transformName(e.getName))).toList
val classContext = context match
case c: FileContext => ClassContext(c, Some(decl))
case c: ClassContext => ClassContext(c, Some(decl), Option(c))

val otherMembers = decl.getMembers.asScala
.flatMap(transformOtherEnumMember.curried(classContext))

List(ast.EnumDeclaration(
transformName(decl.getName),
members = enumMembers,
additionalModifiers
)) ::: otherMembers.toList

def transformOtherEnumMember(context: ClassContext, member: BodyDeclaration[?]): List[ast.Statement] =
member match
case member: FieldDeclaration =>
member.getVariables.asScala.toList.map(declarator =>
ast.VariableStatement(ast.VariableDeclarationList(List(transformDeclaratorToVariable(context, declarator))))
)
case member: MethodDeclaration =>
List(transformEnumMethodDeclaration(context, member))

def transformEnumMethodDeclaration(context: ClassContext, decl: MethodDeclaration): ast.Statement =
val methodParameters = decl.getParameters.asScala.map(transformParameter.curried(context)).toList
val methodContext = ParameterContext(context, methodParameters.toBuffer)
val methodBody = decl.getBody.toScala.map(body =>
ast.Block(body.getStatements.asScala.map(transformStatement.curried(methodContext)).toList)
)
val methodModifiers = if (decl.getModifiers.asScala.exists(m => m.getKeyword == Keyword.PUBLIC))
List(ast.ExportKeyword())
else
List()

ast.FunctionDeclaration(
transformName(decl.getName),
`type` = transformType(context, decl.getType),
typeParameters = decl.getTypeParameters.asScala.map(transformType.curried(context)).map {
t => t.get
}.toList,
parameters = methodParameters,
body = methodBody,
modifiers = methodModifiers
)


def transformMember(context: ClassContext, member: BodyDeclaration[?]): List[ast.Member] =
member match
case member: FieldDeclaration =>
Expand All @@ -190,43 +139,3 @@ def transformMember(context: ClassContext, member: BodyDeclaration[?]): List[ast
val parameterContext = ParameterContext(context, ListBuffer())
context.addExtractedStatements(transformBlockStatement(parameterContext, member.getBody).statements)
List()

def transformConstructorDeclaration(context: ClassContext, declaration: ConstructorDeclaration) =
val methodParameters = declaration.getParameters.asScala.map(transformParameter.curried(context)).toList
val methodContext = ParameterContext(context, methodParameters.toBuffer)
val methodBody = ast.Block(
declaration.getBody.getStatements.asScala.map(transformStatement.curried(methodContext)).toList
)
val methodModifiers = declaration.getModifiers.asScala.flatMap(transformModifier).toList

ast.Constructor(
parameters = methodParameters,
body = Some(methodBody),
modifiers = methodModifiers
)

def transformMethodDeclaration(context: ClassContext, decl: MethodDeclaration) =
val methodParameters = decl.getParameters.asScala.map(transformParameter.curried(context)).toList
val methodContext = ParameterContext(context, methodParameters.toBuffer)
val methodBody = decl.getBody.toScala.map(body =>
ast.Block(body.getStatements.asScala.map(transformStatement.curried(methodContext)).toList)
)
val originalMethodModifiers = decl.getModifiers.asScala.flatMap(transformModifier).toList
val methodModifiers = if (originalMethodModifiers.isEmpty)
List(ast.PublicKeyword(), ast.AbstractKeyword())
else
originalMethodModifiers

ast.MethodDeclaration(
transformName(decl.getName),
`type` = transformType(context, decl.getType),
typeParameters = decl.getTypeParameters.asScala.map(transformType.curried(context)).map {
t => t.get
}.toList,
parameters = methodParameters,
body = methodBody,
modifiers = methodModifiers
)

def transformParameter(context: FileContext, param: Parameter) =
ast.Parameter(transformName(param.getName), transformType(context, param.getType))
59 changes: 59 additions & 0 deletions src/main/scala/java2typescript/transformer/enum.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package java2typescript.transformer

import com.github.javaparser.ast.Modifier.Keyword
import com.github.javaparser.ast.body.{BodyDeclaration, EnumDeclaration, FieldDeclaration, MethodDeclaration}
import java2typescript.ast

import scala.jdk.CollectionConverters.*
import scala.jdk.OptionConverters.*

def transformEnumDeclaration(
context: FileContext|ClassContext,
decl: EnumDeclaration,
additionalModifiers: List[ast.Modifier] = List()
): List[ast.Statement] =
val enumMembers = decl.getEntries.asScala.map(e => ast.EnumMember(transformName(e.getName))).toList
val classContext = context match
case c: FileContext => ClassContext(c, Some(decl))
case c: ClassContext => ClassContext(c, Some(decl), Option(c))

val otherMembers = decl.getMembers.asScala
.flatMap(transformOtherEnumMember.curried(classContext))

List(ast.EnumDeclaration(
transformName(decl.getName),
members = enumMembers,
additionalModifiers
)) ::: otherMembers.toList

def transformOtherEnumMember(context: ClassContext, member: BodyDeclaration[?]): List[ast.Statement] =
member match
case member: FieldDeclaration =>
member.getVariables.asScala.toList.map(declarator =>
ast.VariableStatement(ast.VariableDeclarationList(List(transformDeclaratorToVariable(context, declarator))))
)
case member: MethodDeclaration =>
List(transformEnumMethodDeclaration(context, member))

def transformEnumMethodDeclaration(context: ClassContext, decl: MethodDeclaration): ast.Statement =
val methodParameters = decl.getParameters.asScala.map(transformParameter.curried(context)).toList
val methodContext = ParameterContext(context, methodParameters.toBuffer)
val methodBody = decl.getBody.toScala.map(body =>
ast.Block(body.getStatements.asScala.map(transformStatement.curried(methodContext)).toList)
)
val methodModifiers = if (decl.getModifiers.asScala.exists(m => m.getKeyword == Keyword.PUBLIC))
List(ast.ExportKeyword())
else
List()

ast.FunctionDeclaration(
transformName(decl.getName),
`type` = transformType(context, decl.getType),
typeParameters = decl.getTypeParameters.asScala.map(transformType.curried(context)).map {
t => t.get
}.toList,
parameters = methodParameters,
body = methodBody,
modifiers = methodModifiers
)

47 changes: 47 additions & 0 deletions src/main/scala/java2typescript/transformer/methods.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package java2typescript.transformer

import com.github.javaparser.ast.body.{ConstructorDeclaration, MethodDeclaration, Parameter}
import java2typescript.ast

import scala.jdk.CollectionConverters.*
import scala.jdk.OptionConverters.*

def transformConstructorDeclaration(context: ClassContext, declaration: ConstructorDeclaration) =
val methodParameters = declaration.getParameters.asScala.map(transformParameter.curried(context)).toList
val methodContext = ParameterContext(context, methodParameters.toBuffer)
val methodBody = ast.Block(
declaration.getBody.getStatements.asScala.map(transformStatement.curried(methodContext)).toList
)
val methodModifiers = declaration.getModifiers.asScala.flatMap(transformModifier).toList

ast.Constructor(
parameters = methodParameters,
body = Some(methodBody),
modifiers = methodModifiers
)

def transformMethodDeclaration(context: ClassContext, decl: MethodDeclaration) =
val methodParameters = decl.getParameters.asScala.map(transformParameter.curried(context)).toList
val methodContext = ParameterContext(context, methodParameters.toBuffer)
val methodBody = decl.getBody.toScala.map(body =>
ast.Block(body.getStatements.asScala.map(transformStatement.curried(methodContext)).toList)
)
val originalMethodModifiers = decl.getModifiers.asScala.flatMap(transformModifier).toList
val methodModifiers = if (context.isInterface)
originalMethodModifiers ::: List(ast.PublicKeyword(), ast.AbstractKeyword())
else
originalMethodModifiers

ast.MethodDeclaration(
transformName(decl.getName),
`type` = transformType(context, decl.getType),
typeParameters = decl.getTypeParameters.asScala.map(transformType.curried(context)).map {
t => t.get
}.toList,
parameters = methodParameters,
body = methodBody,
modifiers = methodModifiers
)

def transformParameter(context: FileContext, param: Parameter) =
ast.Parameter(transformName(param.getName), transformType(context, param.getType))
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package java2typescript.transformer

import com.github.javaparser.ast.`type`.ClassOrInterfaceType
import com.github.javaparser.ast.body.{BodyDeclaration, MethodDeclaration}
import com.github.javaparser.ast.expr.{ObjectCreationExpr, SimpleName}
import java2typescript.ast

import scala.jdk.CollectionConverters.*
import scala.jdk.OptionConverters.*

def transformObjectCreationExpression(context: ParameterContext, expr: ObjectCreationExpr): ast.Expression =
if (isExceptionType(expr))
Expand All @@ -12,8 +15,13 @@ def transformObjectCreationExpression(context: ParameterContext, expr: ObjectCre
transformArrayExpression(context, expr)
else
context.addImportIfNeeded(getTypeScope(expr.getType), getTypeName(expr.getType))
val constructable = expr.getAnonymousClassBody.toScala match {
case Some(anonClass) => transformAnonymousClass(context, expr.getType, anonClass.asScala.toList)
case None => ast.Identifier(expr.getType.getName.getIdentifier)
}

ast.NewExpression(
ast.Identifier(expr.getType.getName.getIdentifier),
constructable,
transformArguments(context, expr.getArguments),
transformTypeArguments(context, expr.getTypeArguments)
)
Expand Down Expand Up @@ -49,7 +57,7 @@ def transformExceptionCreationExpression(context: ParameterContext, expr: Object

def isArrayType(expr: ObjectCreationExpr): Boolean =
val typeName = getTypeName(expr.getType)
typeName.endsWith("List") || typeName == "Collection"
typeName.endsWith("List") || typeName == "Collection"

def transformArrayExpression(context: ParameterContext, expr: ObjectCreationExpr) =
ast.ArrayLiteralExpression(List())
Expand All @@ -58,4 +66,20 @@ def isBuiltInType(name: SimpleName): Boolean =
List("Math", "System").contains(name.asString)

def isDroppableInterface(name: SimpleName): Boolean =
List("Cloneable", "Comparable", "Serializable").contains(name.asString)
List("Cloneable", "Comparable", "Serializable").contains(name.asString)

def transformAnonymousClass(context: ClassContext, classType: ClassOrInterfaceType, body: List[BodyDeclaration[_]]) =
if (body.isEmpty) {
throw new Error("Anonymous classes with no body declarations are not supported")
}

val anonClassContext = ClassContext(context.fileContext, None, Some(context))

val members = body.flatMap(m => transformMember(anonClassContext, m))

ast.ParenthesizedExpression(ast.ClassExpression(
None,
members = members,
modifiers = List(),
heritageClauses = transformHeritage(anonClassContext, List(classType), ast.SyntaxKind.ExtendsKeyword).toList
))
5 changes: 2 additions & 3 deletions src/test/resources/fixtures/anonymous-classes.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# Anonymous Classes
## simple anonymous class that implements interface
options: debug
```java
interface Filter {
boolean filter(String x);
Expand All @@ -20,15 +19,15 @@ class FilterFactory {
```
```typescript
export abstract class Filter {
public abstract filter(x: string): boolean;
public abstract filter(x: string): boolean;
}
```
```typescript
import { Filter } from "./Filter.ts";
export class FilterFactory {
public getFilter(): Filter {
return new (class extends Filter {
public filter(x: string) {
public filter(x: string): boolean {
return x === "value";
}
})();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@ export class LargestEmptyCircle {
return new Cell(p.getX(), p.getY(), 0, this.distanceToConstraints(p));
}
}
export class Cell implements Comparable<Cell> {
export class Cell {
private static SQRT2: number = 1.4142135623730951;
private x: number;
private y: number;
Expand Down

0 comments on commit 91f0f47

Please sign in to comment.