diff --git a/.scalafmt.conf b/.scalafmt.conf
index 3dd7eeb..dec164f 100644
--- a/.scalafmt.conf
+++ b/.scalafmt.conf
@@ -1,5 +1,5 @@
version = "3.7.17"
-runner.dialect=scala213
+runner.dialect=scala3
style = defaultWithAlign
diff --git a/README.md b/README.md
index 3d7f383..030248f 100644
--- a/README.md
+++ b/README.md
@@ -48,7 +48,6 @@ Here's an example for Typescript:
~~~scala
import bridges.typescript._
-import bridges.typescript.syntax._
Typescript.render(List(
decl[Color],
@@ -109,15 +108,14 @@ for defining structural types directly:
~~~scala
import bridges.typescript._
import bridges.typescript.TsType._
-import bridges.typescript.syntax._
val logMessage: TsDecl =
decl("LogMessage")(struct(
"level" --> union(lit("error"), lit("warning")),
- text --> Str
+ text --> Str
)
-Typescript.render(logMessage)
+ Typescript.render(logMessage)
// res0: String =
// export interface LogMessage {
// level: "error" | "warning";
@@ -131,7 +129,6 @@ which is something the shapeless derivation currently can't handle:
~~~scala
import bridges.typescript._
import bridges.typescript.TsType._
-import bridges.typescript.syntax._
val pair: TsDecl =
decl("Pair", "A", "B")(struct(
@@ -139,7 +136,7 @@ val pair: TsDecl =
"tail" --> Ref("B"),
)
-Typescript.render(pair)
+ Typescript.render(pair)
// res0: String =
// export interface Pair {
// head: A;
diff --git a/build.sbt b/build.sbt
index eecf6b0..7e30e85 100644
--- a/build.sbt
+++ b/build.sbt
@@ -6,68 +6,20 @@ enablePlugins(GitBranchPrompt)
organization := "com.davegurnell"
name := "bridges"
-ThisBuild / scalaVersion := "3.5.0"
-
-ThisBuild / crossScalaVersions := Seq("2.13.13", "3.5.0")
-
-ThisBuild / scalacOptions ++= {
- CrossVersion.partialVersion(scalaVersion.value) match {
- case Some((2, 12)) =>
- Seq(
- "-feature",
- "-unchecked",
- "-deprecation",
- "-Xfatal-warnings",
- "-Ypartial-unification"
- )
-
- case Some((2, _)) =>
- Seq(
- "-feature",
- "-unchecked",
- "-deprecation",
- "-Xfatal-warnings",
- )
-
- case Some((3, _)) =>
- Seq(
- "-feature",
- "-unchecked",
- "-deprecation",
- "-Xfatal-warnings",
- "-old-syntax",
- )
-
- case _ =>
- Seq(
- "-feature",
- "-unchecked",
- "-deprecation",
- "-rewrite",
- "-new-syntax",
- )
- }
-}
+ThisBuild / scalaVersion := "3.5.0"
-ThisBuild / libraryDependencies ++= Seq(
- "com.davegurnell" %% "unindent" % "1.8.0",
- "org.apache.commons" % "commons-text" % "1.9",
- "org.scalatest" %% "scalatest" % "3.2.13" % Test,
- "eu.timepit" %% "refined" % "0.10.1" % Provided,
+ThisBuild / scalacOptions ++= Seq(
+ "-feature",
+ "-unchecked",
+ "-deprecation",
+ "-Xfatal-warnings",
)
-ThisBuild / libraryDependencies ++= {
- CrossVersion.partialVersion(scalaVersion.value) match {
- case Some((2, _)) =>
- Seq(
- "com.chuusai" %% "shapeless" % "2.3.10",
- "eu.timepit" %% "refined-shapeless" % "0.10.1" % Provided
- )
-
- case _ =>
- Seq.empty
- }
-}
+ThisBuild / libraryDependencies ++= Seq(
+ "com.davegurnell" %% "unindent" % "1.8.0",
+ "org.apache.commons" % "commons-text" % "1.9",
+ "org.scalameta" %% "munit" % "1.0.1" % Test,
+)
// Versioning -----------------------------------
diff --git a/src/main/scala-2/bridges/core/DerivedEncoderInstances.scala b/src/main/scala-2/bridges/core/DerivedEncoderInstances.scala
deleted file mode 100644
index ceed380..0000000
--- a/src/main/scala-2/bridges/core/DerivedEncoderInstances.scala
+++ /dev/null
@@ -1,62 +0,0 @@
-package bridges.core
-
-import shapeless._
-import shapeless.labelled.FieldType
-
-import scala.reflect.runtime.universe.WeakTypeTag
-
-trait DerivedEncoderInstances extends DerivedEncoderInstances1 {
- implicit def valueClassEncoder[A <: AnyVal, B](implicit unwrapped: Unwrapped.Aux[A, B], encoder: BasicEncoder[B]): BasicEncoder[A] =
- pure(encoder.encode)
-}
-
-trait DerivedEncoderInstances1 extends DerivedEncoderInstances0 {
- import Type._
-
- implicit val hnilProdEncoder: ProdEncoder[HNil] =
- pureProd(Prod(Nil))
-
- implicit def hconsProdEncoder[K <: Symbol, H, T <: HList](implicit
- witness: Witness.Aux[K],
- hEnc: Lazy[BasicEncoder[H]],
- tEnc: ProdEncoder[T]
- ): ProdEncoder[FieldType[K, H] :: T] = {
- val name = witness.value.name
- val head = hEnc.value.encode
- val tail = tEnc.encode
- pureProd(Prod((name -> head) +: tail.fields))
- }
-
- implicit def cnilSumEncoder: SumEncoder[CNil] =
- pureSum(Sum(Nil))
-
- implicit def cconsSumEncoder[K <: Symbol, H, T <: Coproduct](implicit
- witness: Witness.Aux[K],
- hEnc: Lazy[ProdEncoder[H]],
- tEnc: SumEncoder[T]
- ): SumEncoder[FieldType[K, H] :+: T] = {
- val name = witness.value.name
- val product = hEnc.value.encode
- val tail = tEnc.encode
- pureSum(Sum((name -> product) +: tail.products))
- }
-
- implicit def genericProdEncoder[A, R](implicit
- gen: LabelledGeneric.Aux[A, R],
- enc: Lazy[ProdEncoder[R]]
- ): ProdEncoder[A] =
- pureProd(enc.value.encode)
-
- implicit def genericSumEncoder[A, R](implicit
- gen: LabelledGeneric.Aux[A, R],
- enc: Lazy[SumEncoder[R]]
- ): SumEncoder[A] =
- pureSum(enc.value.encode)
-}
-
-trait DerivedEncoderInstances0 extends EncoderConstructors {
- import Type._
-
- implicit def genericBasicEncoder[A](implicit low: LowPriority, tpeTag: WeakTypeTag[A]): BasicEncoder[A] =
- pure(Ref(TypeName.getTypeName[A]))
-}
diff --git a/src/main/scala-2/bridges/core/TypeName.scala b/src/main/scala-2/bridges/core/TypeName.scala
deleted file mode 100644
index b1f120a..0000000
--- a/src/main/scala-2/bridges/core/TypeName.scala
+++ /dev/null
@@ -1,15 +0,0 @@
-package bridges.core
-
-import scala.reflect.runtime.universe.WeakTypeTag
-
-object TypeName {
- // NOTE: we can't use `shapeless.Typeable` in here as it breaks the code for recursive types like
- // final case class Recursive(head: Int, tail: Option[Recursive])
- //
- // The only solution I found is to use a `WeakTypeTag` from scala runtime,
- // which seems to manage the recursivity OK.
- def getTypeName[A](implicit tpeTag: WeakTypeTag[A]): String = {
- val fullName = tpeTag.tpe.typeSymbol.fullName
- fullName.split('.').last
- }
-}
diff --git a/src/main/scala-2/bridges/core/syntax.scala b/src/main/scala-2/bridges/core/syntax.scala
deleted file mode 100644
index 7675e16..0000000
--- a/src/main/scala-2/bridges/core/syntax.scala
+++ /dev/null
@@ -1,33 +0,0 @@
-package bridges.core
-
-import shapeless.Lazy
-
-import scala.reflect.runtime.universe.WeakTypeTag
-
-object syntax extends RenamableSyntax {
- import Type._
-
- def encode[A: Encoder]: Type =
- Encoder[A].encode
-
- def decl[A](implicit tpeTag: WeakTypeTag[A], encoder: Lazy[Encoder[A]]): Decl =
- DeclF(TypeName.getTypeName[A], encoder.value.encode)
-
- // To be used for classes with generic parameters as we can't use shapeless to derive them
- def decl(name: String, params: String*)(tpe: Type): Decl =
- DeclF(name, params.toList, tpe)
-
- def prod(fields: (String, Type)*): Prod =
- Prod(fields.toList)
-
- def sum(products: (String, Prod)*): Sum =
- Sum(products.toList)
-
- def dict(keyType: Type, valueType: Type): Type =
- Dict(keyType, valueType)
-
- implicit class StringDeclOps(str: String) {
- def :=[A](tpe: A): DeclF[A] =
- DeclF(str, tpe)
- }
-}
diff --git a/src/main/scala-2/bridges/flow/syntax.scala b/src/main/scala-2/bridges/flow/syntax.scala
deleted file mode 100644
index d65e38f..0000000
--- a/src/main/scala-2/bridges/flow/syntax.scala
+++ /dev/null
@@ -1,66 +0,0 @@
-package bridges.flow
-
-import bridges.core._
-import shapeless.Lazy
-
-import scala.language.implicitConversions
-import scala.reflect.runtime.universe.WeakTypeTag
-
-object syntax extends RenamableSyntax {
- import FlowType._
-
- def encode[A](implicit encoder: FlowEncoder[A]): FlowType =
- encoder.encode
-
- def decl[A](implicit tpeTag: WeakTypeTag[A], encoder: Lazy[FlowEncoder[A]]): FlowDecl =
- DeclF(TypeName.getTypeName[A], encoder.value.encode)
-
- def decl(name: String, params: String*)(tpe: FlowType): FlowDecl =
- DeclF(name, params.toList, tpe)
-
- def struct(fields: FlowField*): Struct =
- Struct(fields.toList)
-
- implicit class StringFieldOps(name: String) {
- def -->(tpe: FlowType): FlowField =
- FlowField(name, tpe, optional = false)
-
- def -?>(tpe: FlowType): FlowField =
- FlowField(name, tpe, optional = true)
- }
-
- @deprecated("Use --> instead of ->", "0.16.0")
- implicit def pairToField(pair: (String, FlowType)): FlowField = {
- val (name, tpe) = pair
- FlowField(name, tpe, optional = false)
- }
-
- def field(name: String, optional: Boolean = false)(tpe: FlowType): FlowField =
- FlowField(name, tpe, optional)
-
- def restField(name: String, keyType: FlowType)(valueType: FlowType): FlowRestField =
- FlowRestField(name, keyType, valueType)
-
- def tuple(types: FlowType*): FlowType =
- Tuple(types.toList)
-
- def union(types: FlowType*): FlowType =
- Union(types.toList)
-
- def inter(types: FlowType*): FlowType =
- Inter(types.toList)
-
- def arr(tpe: FlowType): FlowType =
- Arr(tpe)
-
- def dict(keyType: FlowType, valueType: FlowType): FlowType =
- Struct(Nil, Some(FlowRestField("key", keyType, valueType)))
-
- def ref(name: String, params: FlowType*): Ref =
- Ref(name, params.toList)
-
- implicit class StringDeclOps(str: String) {
- def :=[A](tpe: A): DeclF[A] =
- DeclF(str, tpe)
- }
-}
diff --git a/src/main/scala-2/bridges/typescript/syntax.scala b/src/main/scala-2/bridges/typescript/syntax.scala
deleted file mode 100644
index 3e0ece9..0000000
--- a/src/main/scala-2/bridges/typescript/syntax.scala
+++ /dev/null
@@ -1,66 +0,0 @@
-package bridges.typescript
-
-import bridges.core.{ DeclF, RenamableSyntax, TypeName }
-import shapeless.Lazy
-
-import scala.language.implicitConversions
-import scala.reflect.runtime.universe.WeakTypeTag
-
-object syntax extends RenamableSyntax {
- import TsType._
-
- def encode[A](implicit encoder: TsEncoder[A]): TsType =
- encoder.encode
-
- def decl[A](implicit tpeTag: WeakTypeTag[A], encoder: Lazy[TsEncoder[A]]): TsDecl =
- DeclF(TypeName.getTypeName[A], encoder.value.encode)
-
- def decl(name: String, params: String*)(tpe: TsType): TsDecl =
- DeclF(name, params.toList, tpe)
-
- def struct(fields: TsField*): Struct =
- Struct(fields.toList)
-
- def dict(keyType: TsType, valueType: TsType): Struct =
- Struct(Nil, Some(TsRestField("key", keyType, valueType)))
-
- implicit class StringFieldOps(name: String) {
- def -->(tpe: TsType): TsField =
- TsField(name, tpe, optional = false)
-
- def -?>(tpe: TsType): TsField =
- TsField(name, tpe, optional = true)
- }
-
- @deprecated("Use --> instead of ->", "0.16.0")
- implicit def pairToField(pair: (String, TsType)): TsField = {
- val (name, tpe) = pair
- TsField(name, tpe, optional = false)
- }
-
- def field(name: String, optional: Boolean = false)(tpe: TsType): TsField =
- TsField(name, tpe, optional)
-
- def restField(name: String, keyType: TsType)(valueType: TsType): TsRestField =
- TsRestField(name, keyType, valueType)
-
- def tuple(types: TsType*): Tuple =
- Tuple(types.toList)
-
- def union(types: TsType*): Union =
- Union(types.toList)
-
- def inter(types: TsType*): Inter =
- Inter(types.toList)
-
- def ref(name: String, params: TsType*): Ref =
- Ref(name, params.toList)
-
- implicit class StringDeclOps(str: String) {
- def :=[A](tpe: A): DeclF[A] =
- DeclF(str, tpe)
- }
-
- def func(args: (String, TsType)*)(ret: TsType): Func =
- Func(args.toList, ret)
-}
diff --git a/src/main/scala-3/bridges/core/DerivedEncoderInstances.scala b/src/main/scala-3/bridges/core/DerivedEncoderInstances.scala
deleted file mode 100644
index 1c0590a..0000000
--- a/src/main/scala-3/bridges/core/DerivedEncoderInstances.scala
+++ /dev/null
@@ -1,64 +0,0 @@
-package bridges.core
-
-import scala.compiletime._
-import scala.deriving._
-
-trait DerivedEncoderInstances extends DerivedEncoderInstances1 {
- // given valueClassEncoder[A <: AnyVal, B](implicit encoder: BasicEncoder[B]): BasicEncoder[A] =
- // ???
-}
-
-trait DerivedEncoderInstances1 extends DerivedEncoderInstances0 {
- inline given allEncoders[A <: Tuple]: List[Encoder[Any]] = {
- inline erasedValue[A] match {
- case _: EmptyTuple => Nil
- case _: (h *: t) => summonInline[Encoder[h]].asInstanceOf[Encoder[Any]] :: allEncoders[t]
- }
- }
-
- inline given allProdEncoders[A <: Tuple]: List[ProdEncoder[Any]] = {
- inline erasedValue[A] match {
- case _: EmptyTuple => Nil
- case _: (h *: t) => summonInline[ProdEncoder[h]].asInstanceOf[ProdEncoder[Any]] :: allProdEncoders[t]
- }
- }
-
- private inline def allLabels[A <: Tuple]: List[String] = {
- inline erasedValue[A] match {
- case _: EmptyTuple => Nil
- case _: (h *: t) => constValue[h].asInstanceOf[String] :: allLabels[t]
- }
- }
-
- inline given deriveEncoder[A](using mirror: Mirror.Of[A]): Encoder[A] = {
- lazy val name = TypeName.getTypeName[A]
-
- println(s"derivedEncoder[${name}]")
-
- lazy val labels: List[String] =
- allLabels[mirror.MirroredElemLabels]
-
- inline mirror match {
- case mirror: Mirror.ProductOf[A] => prodEncoder(mirror, labels)
- case mirror: Mirror.SumOf[A] => sumEncoder(mirror, labels)
- }
- }
-
- private inline def prodEncoder[A](mirror: Mirror.ProductOf[A], labels: List[String]): Encoder[A] = {
- lazy val types: List[Type] = allEncoders[mirror.MirroredElemTypes].map(_.encode)
- pureProd(Type.Prod(labels.zip(types)))
- }
-
- private inline def sumEncoder[A](mirror: Mirror.SumOf[A], labels: List[String]): Encoder[A] = {
- lazy val types: List[Type.Prod] = allProdEncoders[mirror.MirroredElemTypes].map(_.encode)
- pure(Type.Sum(labels.zip(types)))
- }
-}
-
-trait DerivedEncoderInstances0 extends EncoderConstructors {
- inline given genericBasicEncoder[A]: BasicEncoder[A] = {
- lazy val name = TypeName.getTypeName[A]
- println(s"genericBasicEncoder[${name}]")
- pure(Type.Ref(name))
- }
-}
diff --git a/src/main/scala-3/bridges/core/TypeName.scala b/src/main/scala-3/bridges/core/TypeName.scala
deleted file mode 100644
index 0ef73d2..0000000
--- a/src/main/scala-3/bridges/core/TypeName.scala
+++ /dev/null
@@ -1,14 +0,0 @@
-package bridges.core
-
-import scala.quoted.*
-
-object TypeName {
- inline def getTypeName[T]: String =
- ${ getTypeNameImpl[T] }
-
- private def getTypeNameImpl[T: Type](using Quotes): Expr[String] = {
- import quotes.reflect.*
- val typeRepr = TypeRepr.of[T]
- Expr(typeRepr.show.split("\\.").last)
- }
-}
diff --git a/src/main/scala-3/bridges/core/syntax.scala b/src/main/scala-3/bridges/core/syntax.scala
deleted file mode 100644
index 5f9fe65..0000000
--- a/src/main/scala-3/bridges/core/syntax.scala
+++ /dev/null
@@ -1,28 +0,0 @@
-package bridges.core
-
-object syntax extends RenamableSyntax {
- import Type._
-
- def encode[A: Encoder]: Type =
- Encoder[A].encode
-
- inline def decl[A](implicit encoder: => Encoder[A]): Decl =
- DeclF(TypeName.getTypeName[A], encoder.encode)
-
- def decl(name: String, params: String*)(tpe: Type): Decl =
- DeclF(name, params.toList, tpe)
-
- def prod(fields: (String, Type)*): Prod =
- Prod(fields.toList)
-
- def sum(products: (String, Prod)*): Sum =
- Sum(products.toList)
-
- def dict(keyType: Type, valueType: Type): Type =
- Dict(keyType, valueType)
-
- implicit class StringDeclOps(str: String) {
- def :=[A](tpe: A): DeclF[A] =
- DeclF(str, tpe)
- }
-}
diff --git a/src/main/scala-3/bridges/flow/syntax.scala b/src/main/scala-3/bridges/flow/syntax.scala
deleted file mode 100644
index 6db31f0..0000000
--- a/src/main/scala-3/bridges/flow/syntax.scala
+++ /dev/null
@@ -1,56 +0,0 @@
-package bridges.flow
-
-import bridges.core._
-
-object syntax extends RenamableSyntax {
- import FlowType._
-
- def encode[A](implicit encoder: FlowEncoder[A]): FlowType =
- encoder.encode
-
- inline def decl[A](implicit encoder: => FlowEncoder[A]): FlowDecl =
- DeclF(TypeName.getTypeName[A], encoder.encode)
-
- def decl(name: String, params: String*)(tpe: FlowType): FlowDecl =
- DeclF(name, params.toList, tpe)
-
- def struct(fields: FlowField*): Struct =
- Struct(fields.toList)
-
- implicit class StringFieldOps(name: String) {
- def -->(tpe: FlowType): FlowField =
- FlowField(name, tpe, optional = false)
-
- def -?>(tpe: FlowType): FlowField =
- FlowField(name, tpe, optional = true)
- }
-
- def field(name: String, optional: Boolean = false)(tpe: FlowType): FlowField =
- FlowField(name, tpe, optional)
-
- def restField(name: String, keyType: FlowType)(valueType: FlowType): FlowRestField =
- FlowRestField(name, keyType, valueType)
-
- def tuple(types: FlowType*): FlowType =
- Tuple(types.toList)
-
- def union(types: FlowType*): FlowType =
- Union(types.toList)
-
- def inter(types: FlowType*): FlowType =
- Inter(types.toList)
-
- def arr(tpe: FlowType): FlowType =
- Arr(tpe)
-
- def dict(keyType: FlowType, valueType: FlowType): FlowType =
- Struct(Nil, Some(FlowRestField("key", keyType, valueType)))
-
- def ref(name: String, params: FlowType*): Ref =
- Ref(name, params.toList)
-
- implicit class StringDeclOps(str: String) {
- def :=[A](tpe: A): DeclF[A] =
- DeclF(str, tpe)
- }
-}
diff --git a/src/main/scala-3/bridges/typescript/syntax.scala b/src/main/scala-3/bridges/typescript/syntax.scala
deleted file mode 100644
index eb9fa8d..0000000
--- a/src/main/scala-3/bridges/typescript/syntax.scala
+++ /dev/null
@@ -1,56 +0,0 @@
-package bridges.typescript
-
-import bridges.core.{ DeclF, RenamableSyntax, TypeName }
-
-object syntax extends RenamableSyntax {
- import TsType._
-
- def encode[A](implicit encoder: TsEncoder[A]): TsType =
- encoder.encode
-
- inline def decl[A](implicit encoder: => TsEncoder[A]): TsDecl =
- DeclF(TypeName.getTypeName[A], encoder.encode)
-
- def decl(name: String, params: String*)(tpe: TsType): TsDecl =
- DeclF(name, params.toList, tpe)
-
- def struct(fields: TsField*): Struct =
- Struct(fields.toList)
-
- def dict(keyType: TsType, valueType: TsType): Struct =
- Struct(Nil, Some(TsRestField("key", keyType, valueType)))
-
- implicit class StringFieldOps(name: String) {
- def -->(tpe: TsType): TsField =
- TsField(name, tpe, optional = false)
-
- def -?>(tpe: TsType): TsField =
- TsField(name, tpe, optional = true)
- }
-
- def field(name: String, optional: Boolean = false)(tpe: TsType): TsField =
- TsField(name, tpe, optional)
-
- def restField(name: String, keyType: TsType)(valueType: TsType): TsRestField =
- TsRestField(name, keyType, valueType)
-
- def tuple(types: TsType*): Tuple =
- Tuple(types.toList)
-
- def union(types: TsType*): Union =
- Union(types.toList)
-
- def inter(types: TsType*): Inter =
- Inter(types.toList)
-
- def ref(name: String, params: TsType*): Ref =
- Ref(name, params.toList)
-
- implicit class StringDeclOps(str: String) {
- def :=[A](tpe: A): DeclF[A] =
- DeclF(str, tpe)
- }
-
- def func(args: (String, TsType)*)(ret: TsType): Func =
- Func(args.toList, ret)
-}
diff --git a/src/main/scala/bridges/core/DeclF.scala b/src/main/scala/bridges/core/DeclF.scala
deleted file mode 100644
index 3e60ba6..0000000
--- a/src/main/scala/bridges/core/DeclF.scala
+++ /dev/null
@@ -1,25 +0,0 @@
-package bridges.core
-
-/** A named declaration. Either top-level or a field in a sum, product, or struct.
- *
- * We define type aliases for DeclF in
- * the package objects for bridges.core, bridges.flow, and bridges.typescript.
- */
-final case class DeclF[+A](name: String, params: List[String], tpe: A) {
- def map[B](func: A => B): DeclF[B] =
- copy(tpe = func(tpe))
-}
-
-object DeclF {
- def apply[A](name: String, tpe: A): DeclF[A] =
- DeclF(name, Nil, tpe)
-
- implicit def rename[A](implicit rename: Rename[A]): Rename[DeclF[A]] =
- Rename.pure[DeclF[A]] { (decl, from, to) =>
- DeclF(
- if (decl.name == from) to else decl.name,
- decl.params,
- if (decl.params.contains(from)) decl.tpe else rename(decl.tpe, from, to)
- )
- }
-}
diff --git a/src/main/scala/bridges/core/Encoder.scala b/src/main/scala/bridges/core/Encoder.scala
deleted file mode 100644
index f18a5ed..0000000
--- a/src/main/scala/bridges/core/Encoder.scala
+++ /dev/null
@@ -1,74 +0,0 @@
-package bridges.core
-
-import eu.timepit.refined.api._
-
-trait Encoder[A] {
- def encode: Type
-}
-
-trait ProdEncoder[A] extends Encoder[A] {
- override def encode: Type.Prod
-}
-
-trait SumEncoder[A] extends Encoder[A] {
- override def encode: Type.Sum
-}
-
-trait BasicEncoder[A] extends Encoder[A]
-
-object Encoder extends DerivedEncoderInstances1 {
- import Type._
-
- implicit val stringEncoder: BasicEncoder[String] =
- pure(Str)
-
- implicit val charEncoder: BasicEncoder[Char] =
- pure(Chr)
-
- implicit val intEncoder: BasicEncoder[Int] =
- pure(Intr)
-
- implicit val longEncoder: BasicEncoder[Long] =
- pure(Intr)
-
- implicit val bigDecimalEncoder: BasicEncoder[BigDecimal] =
- pure(Real)
-
- implicit val doubleEncoder: BasicEncoder[Double] =
- pure(Real)
-
- implicit val floatEncoder: BasicEncoder[Float] =
- pure(Real)
-
- implicit val booleanEncoder: BasicEncoder[Boolean] =
- pure(Bool)
-
- implicit def optionEncoder[A](implicit enc: BasicEncoder[A]): BasicEncoder[Option[A]] =
- pure(Opt(enc.encode))
-
- implicit def mapEncoder[A, B](implicit aEnc: BasicEncoder[A], bEnc: Encoder[B]): BasicEncoder[Map[A, B]] =
- pure(Dict(aEnc.encode, bEnc.encode))
-
- implicit def traversableEncoder[F[_] <: Iterable[?], A](implicit enc: BasicEncoder[A]): BasicEncoder[F[A]] =
- pure(Arr(enc.encode))
-
- implicit def refinedEncoder[A, B](implicit enc: BasicEncoder[A]): BasicEncoder[Refined[A, B]] =
- pure(enc.encode)
-
-}
-
-trait EncoderConstructors {
- import Type._
-
- def apply[A](implicit enc: Encoder[A]): Encoder[A] =
- enc
-
- def pure[A](tpe: Type): BasicEncoder[A] =
- new BasicEncoder[A] { def encode: Type = tpe }
-
- def pureProd[A](tpe: Prod): ProdEncoder[A] =
- new ProdEncoder[A] { def encode: Prod = tpe }
-
- def pureSum[A](tpe: Sum): SumEncoder[A] =
- new SumEncoder[A] { def encode: Sum = tpe }
-}
diff --git a/src/main/scala/bridges/core/Rename.scala b/src/main/scala/bridges/core/Rename.scala
deleted file mode 100644
index a2234d8..0000000
--- a/src/main/scala/bridges/core/Rename.scala
+++ /dev/null
@@ -1,30 +0,0 @@
-package bridges.core
-
-trait Rename[A] {
- def apply(value: A, from: String, to: String): A
-}
-
-object Rename {
- def apply[A](implicit rename: Rename[A]): Rename[A] =
- rename
-
- def pure[A](func: (A, String, String) => A): Rename[A] =
- new Rename[A] {
- override def apply(value: A, from: String, to: String): A =
- func(value, from, to)
- }
-
- implicit def pairRename[A](implicit aRename: Rename[A]): Rename[(String, A)] =
- Rename.pure { (pair, from, to) =>
- pair match {
- case (name, a) => (if (name == from) to else name, aRename(a, from, to))
- }
- }
-}
-
-trait RenamableSyntax {
- implicit class RenamableOps[A](value: A) {
- def rename(from: String, to: String)(implicit rename: Rename[A]): A =
- rename(value, from, to)
- }
-}
diff --git a/src/main/scala/bridges/core/Renderer.scala b/src/main/scala/bridges/core/Renderer.scala
deleted file mode 100644
index 06234bc..0000000
--- a/src/main/scala/bridges/core/Renderer.scala
+++ /dev/null
@@ -1,8 +0,0 @@
-package bridges.core
-
-trait Renderer[A] {
- def render(decl: DeclF[A]): String
-
- def render(decls: List[DeclF[A]]): String =
- decls.map(render).mkString("\n\n")
-}
diff --git a/src/main/scala/bridges/core/Type.scala b/src/main/scala/bridges/core/Type.scala
deleted file mode 100644
index e5bb8cb..0000000
--- a/src/main/scala/bridges/core/Type.scala
+++ /dev/null
@@ -1,59 +0,0 @@
-package bridges.core
-
-import bridges.core.syntax._
-
-/** Representation of a nominal, sum-of-products style type.
- *
- * We can encode Scala ADTs to this representation:
- *
- * - sealed traits become instances of Sum;
- * - case classes become instances of Prod;
- * - references to other types in the body of a Sum or Prod become Refs;
- * - we have special encodings for Options and sequences
- * (which are normally handled specially in the target language).
- *
- * We can generate Elm bindings directly from this representation. For Flow and Typescript bindings we translate to other intermediate
- * representations.
- */
-sealed abstract class Type extends Product with Serializable
-
-object Type {
- final case class Ref(id: String, params: List[Type] = Nil) extends Type
- case object Str extends Type
- case object Chr extends Type
- case object Intr extends Type
- case object Real extends Type
- case object Bool extends Type
- final case class Opt(tpe: Type) extends Type
- final case class Arr(tpe: Type) extends Type
- final case class Dict(keys: Type, values: Type) extends Type
- final case class Prod(fields: List[(String, Type)]) extends Type
- final case class Sum(products: List[(String, Prod)]) extends Type
-
- implicit private val prodRename: Rename[Prod] =
- Rename.pure { (tpe, from, to) =>
- tpe match {
- case Prod(fields) => Prod(fields.map(_.rename(from, to)))
- }
- }
-
- implicit val rename: Rename[Type] =
- Rename.pure { (tpe, from, to) =>
- def renameId(id: String): String =
- if (id == from) to else id
-
- tpe match {
- case Ref(id, params) => Ref(renameId(id), params.map(_.rename(from, to)))
- case tpe @ Str => tpe
- case tpe @ Chr => tpe
- case tpe @ Intr => tpe
- case tpe @ Real => tpe
- case tpe @ Bool => tpe
- case Opt(tpe) => Opt(tpe.rename(from, to))
- case Arr(tpe) => Arr(tpe.rename(from, to))
- case Dict(kTpe, vTpe) => Dict(kTpe.rename(from, to), vTpe.rename(from, to))
- case Prod(fields) => Prod(fields.map(_.rename(from, to)))
- case Sum(products) => Sum(products.map(_.rename(from, to)))
- }
- }
-}
diff --git a/src/main/scala/bridges/core/package.scala b/src/main/scala/bridges/core/package.scala
deleted file mode 100644
index 4d1aa2c..0000000
--- a/src/main/scala/bridges/core/package.scala
+++ /dev/null
@@ -1,5 +0,0 @@
-package bridges
-
-package object core {
- type Decl = DeclF[Type]
-}
diff --git a/src/main/scala/bridges/elm/Elm.scala b/src/main/scala/bridges/elm/Elm.scala
deleted file mode 100644
index ee11251..0000000
--- a/src/main/scala/bridges/elm/Elm.scala
+++ /dev/null
@@ -1,3 +0,0 @@
-package bridges.elm
-
-object Elm extends ElmRenderer with ElmJsonDecoder with ElmJsonEncoder with ElmFileBuilder
diff --git a/src/main/scala/bridges/elm/ElmFileBuilder.scala b/src/main/scala/bridges/elm/ElmFileBuilder.scala
deleted file mode 100644
index ed6884a..0000000
--- a/src/main/scala/bridges/elm/ElmFileBuilder.scala
+++ /dev/null
@@ -1,86 +0,0 @@
-package bridges.elm
-
-import bridges.core.Type._
-import bridges.core._
-import unindent._
-
-trait ElmFileBuilder {
- // Given a declaration, returns a tuple with file name and file contents:
- def buildFile(module: String, decl: Decl, customTypeReplacements: Map[Ref, TypeReplacement] = Map.empty): (String, String) =
- buildFile(module, List(decl), customTypeReplacements)
-
- def buildFile(module: String, decls: List[Decl], customTypeReplacements: Map[Ref, TypeReplacement]): (String, String) = {
- val fileName = decls.headOption.map(_.name).getOrElse("")
- val foldZero = ("", "", "")
-
- val typesInFile = decls.map(_.name)
- val replacementTypes = customTypeReplacements.keySet
- val typeImports = decls
- .flatMap(d => getDeclarationTypes(d.tpe, d.name))
- .distinct
- .filterNot(r => typesInFile.contains(r.id) || replacementTypes.contains(r))
- .map(r => s"import $module.${r.id} exposing (..)")
- .mkString("\n")
-
- val (declarations, decoders, encoders) =
- decls.map(getFileComponents(module, customTypeReplacements, _)).foldLeft(foldZero) { case (acc, (t, d, e)) =>
- (
- s"${acc._1}\n$t",
- s"${acc._2}\n\n$d",
- s"${acc._3}\n\n$e"
- )
- }
-
- val pipelineImport =
- "import Json.Decode.Pipeline exposing (..)"
-
- val customImports = customTypeReplacements.values.filter(td => decoders.contains(td.newType)).flatMap(_.imports).mkString("\n")
-
- val imports = typeImports + customImports
-
- val content =
- i"""
- module $module.$fileName exposing (..)
-
- import Json.Decode as Decode
- $pipelineImport
- import Json.Encode as Encode
- $imports
- $declarations
- $decoders
- $encoders
- """
-
- (s"$fileName.elm", content)
- }
-
- private def getFileComponents(
- module: String,
- customTypeReplacements: Map[Ref, TypeReplacement],
- decl: Decl
- ): (String, String, String) = {
- val declaration = Elm.render(decl, customTypeReplacements)
- val decoder = Elm.decoder(decl, customTypeReplacements)
- val encoder = Elm.encoder(decl, customTypeReplacements)
-
- (declaration, decoder, encoder)
- }
-
- private def getDeclarationTypes(tpe: Type, parentType: String): List[Ref] = {
- def getIncludeTypes(tpe: Type): List[Ref] =
- tpe match {
- case r: Ref => r :: Nil
- case Opt(optTpe) => getIncludeTypes(optTpe)
- case Arr(arrTpe) => getIncludeTypes(arrTpe)
- case Dict(kTpe, vTpe) => Ref("Dict") +: (getIncludeTypes(kTpe) ++ getIncludeTypes(vTpe))
- case Prod(fields) => fields.map { case (_, tpe) => tpe }.flatMap(getIncludeTypes)
- case Sum(products) => products.map { case (_, tpe) => tpe }.flatMap(getIncludeTypes)
- case _ => Nil
- }
-
- val exclude = Ref(parentType)
- val include = getIncludeTypes(tpe)
-
- include.distinct.filterNot(_ == exclude)
- }
-}
diff --git a/src/main/scala/bridges/elm/ElmJsonDecoder.scala b/src/main/scala/bridges/elm/ElmJsonDecoder.scala
deleted file mode 100644
index 9317b59..0000000
--- a/src/main/scala/bridges/elm/ElmJsonDecoder.scala
+++ /dev/null
@@ -1,84 +0,0 @@
-package bridges.elm
-
-import bridges.core._
-import bridges.core.Type._
-import unindent._
-
-trait ElmJsonDecoder extends ElmUtils {
- def decoder(decls: List[Decl], customTypeReplacements: Map[Ref, TypeReplacement]): String =
- decls.map(decoder(_, customTypeReplacements)).mkString("\n\n")
-
- def decoder(decl: Decl, customTypeReplacements: Map[Ref, TypeReplacement] = Map.empty): String = {
- val (newTypeReplacements, genericsDefinition) = mergeGenericsAndTypes(decl, customTypeReplacements)
- val genericsInType = genericsDefinition.foldLeft("")((acc, b) => s"$acc $b")
- val nameWithGenerics = if (genericsInType.isEmpty) decl.name else s"(${decl.name}$genericsInType)"
- val definitionsForGenerics = genericsDefinition.map(s => s"(Decode.Decoder $s) -> ").foldLeft("")((acc, b) => s"$acc$b")
- val methodsForGenerics = genericsDefinition.map(s => s"decoder${s.toUpperCase}").foldLeft("")((acc, b) => s"$acc $b")
-
- decl.tpe match {
- case Sum(products) =>
- // DO NOT REMOVE SPACE AT END - needed for Elm compiler and to pass tests. Yup, dirty, I know!
- val body = products.map { case (name, prod) => decodeSumType(name, prod, newTypeReplacements) }.mkString("\n ")
- i"""
- decoder${decl.name} : ${definitionsForGenerics}Decode.Decoder $nameWithGenerics
- decoder${decl.name}$methodsForGenerics = Decode.field "type" Decode.string |> Decode.andThen decoder${decl.name}Tpe$methodsForGenerics
-
- decoder${decl.name}Tpe : ${definitionsForGenerics}String -> Decode.Decoder $nameWithGenerics
- decoder${decl.name}Tpe$methodsForGenerics tpe =
- case tpe of
- $body
- _ -> Decode.fail ("Unexpected type for ${decl.name}: " ++ tpe)
- """
- case other =>
- val body = decodeType(other, newTypeReplacements)
-
- i"""
- decoder${decl.name} : ${definitionsForGenerics}Decode.Decoder $nameWithGenerics
- decoder${decl.name}$methodsForGenerics = Decode.succeed ${decl.name} $body
- """
- }
- }
-
- private def decodeSumType(name: String, prod: Prod, customTypeReplacements: Map[Ref, TypeReplacement]): String = {
- val refName = Ref(name)
- val mainType = customTypeReplacements.get(refName).map(_.newType).getOrElse(name)
-
- val paramsDecoder =
- prod.fields.map { case (name, tpe) => decodeField(name, tpe, customTypeReplacements) }.mkString(" |> ")
-
- // consider case objects vs case classes
- val bodyDecoder =
- if (paramsDecoder.isEmpty) s"Decode.succeed $mainType"
- else s"Decode.succeed $mainType |> $paramsDecoder"
-
- s""""$mainType" -> $bodyDecoder"""
- }
-
- private def decodeType(tpe: Type, customTypeReplacements: Map[Ref, TypeReplacement]): String =
- tpe match {
- case r @ Ref(id, _) => customTypeReplacements.get(r).flatMap(_.decoder).getOrElse(s"""(Decode.lazy (\\_ -> decoder$id))""")
- case Str => "Decode.string"
- case Chr => "Decode.string"
- case Intr => "Decode.int"
- case Real => "Decode.float"
- case Bool => "Decode.bool"
- case Opt(optTpe) => "(Decode.maybe " + decodeType(optTpe, customTypeReplacements) + ")"
- case Arr(arrTpe) => "(Decode.list " + decodeType(arrTpe, customTypeReplacements) + ")"
- case Dict(Str, vTpe) => "(Decode.dict " + decodeType(vTpe, customTypeReplacements) + ")"
- // The Elm standard library only provides JSON decoders for dictionaries with string keys:
- case _: Dict => throw new IllegalArgumentException("Cannot create a JsonDecoder for a Dict with anything other than String keys")
- case Prod(fields) => fields.map { case (name, tpe) => decodeField(name, tpe, customTypeReplacements) }.mkString("|> ", " |> ", "")
- case _: Sum => throw new IllegalArgumentException("SumOfProducts jsonEncoder: we should never be here")
- }
-
- private def decodeField(name: String, tpe: Type, customTypeReplacements: Map[Ref, TypeReplacement]): String = {
- def decode(tpe: Type) =
- s"""required "${name}" ${decodeType(tpe, customTypeReplacements)}"""
-
- tpe match {
- case Opt(optTpe) =>
- s"""optional "${name}" (Decode.maybe ${decodeType(optTpe, customTypeReplacements)}) Nothing"""
- case other => decode(other)
- }
- }
-}
diff --git a/src/main/scala/bridges/elm/ElmJsonEncoder.scala b/src/main/scala/bridges/elm/ElmJsonEncoder.scala
deleted file mode 100644
index a0fc613..0000000
--- a/src/main/scala/bridges/elm/ElmJsonEncoder.scala
+++ /dev/null
@@ -1,87 +0,0 @@
-package bridges.elm
-
-import bridges.core._
-import bridges.core.Type._
-import unindent._
-
-trait ElmJsonEncoder extends ElmUtils {
- def encoder(decls: List[Decl], customTypeReplacements: Map[Ref, TypeReplacement]): String =
- decls.map(encoder(_, customTypeReplacements)).mkString("\n\n")
-
- def encoder(decl: Decl, customTypeReplacements: Map[Ref, TypeReplacement] = Map.empty): String = {
- val (newTypeReplacements, genericsDefinition) = mergeGenericsAndTypes(decl, customTypeReplacements)
- val genericsInType = genericsDefinition.foldLeft("")((acc, b) => s"$acc $b")
- val definitionsForGenerics = genericsDefinition.map(s => s"($s -> Encode.Value) -> ").foldLeft("")((acc, b) => s"$acc$b")
- val methodsForGenerics = genericsDefinition.map(s => s"encoder${s.toUpperCase}").foldLeft("")((acc, b) => s"$acc $b")
- decl.tpe match {
- case Sum(products) =>
- // DO NOT REMOVE SPACE AT END - needed for Elm compiler and to pass tests. Yup, dirty, I know!
- val body =
- products.map { case (name, prod) => encodeSumType(name, prod, newTypeReplacements) }.mkString("\n ")
-
- i"""
- encoder${decl.name} : $definitionsForGenerics${decl.name}$genericsInType -> Encode.Value
- encoder${decl.name}$methodsForGenerics tpe =
- case tpe of
- $body
- """
- case other =>
- val body = encodeType(other, "obj", decl.name, newTypeReplacements)
-
- i"""
- encoder${decl.name} : $definitionsForGenerics${decl.name}$genericsInType -> Encode.Value
- encoder${decl.name}$methodsForGenerics obj = $body
- """
- }
- }
-
- private def encodeSumType(name: String, tpe: Prod, customTypeReplacements: Map[Ref, TypeReplacement]): String = {
- val refName = Ref(name)
- val mainType = customTypeReplacements.get(refName).map(_.newType).getOrElse(name)
-
- val params = tpe.fields.map { case (name, tpe) => name }.mkString(" ")
- val paramsEncoder = tpe.fields.map { case (name, tpe) => encodeField(name, tpe, "", customTypeReplacements) }
-
- val caseEncoder = if (params.isEmpty) mainType else s"$mainType $params"
- val bodyEncoder =
- (paramsEncoder :+ s"""("type", Encode.string "$mainType")""")
- .mkString("Encode.object [ ", ", ", " ]")
-
- s"""$caseEncoder -> $bodyEncoder"""
- }
-
- private def encodeType(tpe: Type, objectName: String, fieldName: String, customTypeReplacements: Map[Ref, TypeReplacement]): String =
- tpe match {
- case r @ Ref(id, _) => customTypeReplacements.get(r).flatMap(_.encoder).getOrElse(s"encoder$id") + s" $fieldName"
- case Str => s"Encode.string $fieldName"
- case Chr => s"Encode.string $fieldName"
- case Intr => s"Encode.int $fieldName"
- case Real => s"Encode.float $fieldName"
- case Bool => s"Encode.bool $fieldName"
- case Opt(optTpe) =>
- "Maybe.withDefault Encode.null (Maybe.map " +
- encodeType(optTpe, objectName, fieldName, customTypeReplacements) + ")"
- case Arr(arrTpe) =>
- "Encode.list " +
- encodeType(arrTpe, objectName, fieldName, customTypeReplacements)
- case Dict(kTpe, vTpe) =>
- "(Encode.dict " +
- encodeType(kTpe, objectName, fieldName, customTypeReplacements) + " " +
- encodeType(vTpe, objectName, fieldName, customTypeReplacements) + ")"
- case Prod(fields) =>
- fields
- .map { case (name, tpe) => encodeField(name, tpe, objectName, customTypeReplacements) }
- .mkString("Encode.object [ ", ", ", " ]")
- case _: Sum => throw new IllegalArgumentException("SumOfProducts jsonEncoder: we should never be here")
- }
-
- private def encodeField(name: String, tpe: Type, objectName: String, customTypeReplacements: Map[Ref, TypeReplacement]): String = {
- val typeFieldName =
- if (objectName.isEmpty) name else s"$objectName.$name"
-
- val encoding = encodeType(tpe, objectName, typeFieldName, customTypeReplacements)
-
- s"""("$name", $encoding)"""
- }
-
-}
diff --git a/src/main/scala/bridges/elm/ElmRenderer.scala b/src/main/scala/bridges/elm/ElmRenderer.scala
deleted file mode 100644
index df4debd..0000000
--- a/src/main/scala/bridges/elm/ElmRenderer.scala
+++ /dev/null
@@ -1,45 +0,0 @@
-package bridges.elm
-
-import bridges.core._
-import bridges.core.Type._
-
-trait ElmRenderer extends Renderer[Type] with ElmUtils {
- def render(decl: Decl): String =
- render(decl, Map.empty)
-
- def render(decl: Decl, customTypeReplacements: Map[Ref, TypeReplacement]): String = {
- val (newTypeReplacements, genericsDefinition) = mergeGenericsAndTypes(decl, customTypeReplacements)
- val genericsInType = genericsDefinition.foldLeft("")((acc, b) => s"$acc $b")
- decl.tpe match {
- case Sum(products) =>
- s"type ${decl.name}$genericsInType = ${products.map { case (name, prod) => renderSumType(name, prod, newTypeReplacements) }.mkString(" | ")}"
- case other => s"type alias ${decl.name}$genericsInType = ${renderType(other, newTypeReplacements)}"
- }
- }
-
- private def renderSumType(name: String, prod: Prod, customTypeReplacements: Map[Ref, TypeReplacement]) = {
- val refName = Ref(name)
- val mainType = customTypeReplacements.get(refName).map(_.newType).getOrElse(name)
- val params = prod.fields.map { case (name, tpe) => renderType(tpe, customTypeReplacements) }.mkString(" ")
- // We trim in case we have no params (case object) as tests don't like extra spaces:
- s"$mainType $params".trim
- }
-
- private def renderType(tpe: Type, customTypeReplacements: Map[Ref, TypeReplacement]): String =
- tpe match {
- case r @ Ref(id, _) => customTypeReplacements.get(r).map(_.newType).getOrElse(id)
- case Str => "String"
- case Chr => "Char"
- case Intr => "Int"
- case Real => "Float"
- case Bool => "Bool"
- case Opt(optTpe) => "(Maybe " + renderType(optTpe, customTypeReplacements) + ")"
- case Arr(arrTpe) => "(List " + renderType(arrTpe, customTypeReplacements) + ")"
- case Dict(kTpe, vTpe) => "(Dict " + renderType(kTpe, customTypeReplacements) + " " + renderType(vTpe, customTypeReplacements) + ")"
- case Prod(fields) => fields.map { case (name, tpe) => renderField(name, tpe, customTypeReplacements) }.mkString("{ ", ", ", " }")
- case _: Sum => throw new IllegalArgumentException("SumOfProducts Renderer: we should never be here")
- }
-
- private def renderField(name: String, tpe: Type, customTypeReplacements: Map[Ref, TypeReplacement]): String =
- s"""$name: ${renderType(tpe, customTypeReplacements)}"""
-}
diff --git a/src/main/scala/bridges/elm/ElmUtils.scala b/src/main/scala/bridges/elm/ElmUtils.scala
deleted file mode 100644
index 4056560..0000000
--- a/src/main/scala/bridges/elm/ElmUtils.scala
+++ /dev/null
@@ -1,17 +0,0 @@
-package bridges.elm
-
-import bridges.core.Decl
-import bridges.core.Type.Ref
-
-trait ElmUtils {
-
- // helper method intended to be used to unify how we work with hand made generics and custom type replacements in Elm
- def mergeGenericsAndTypes(decl: Decl, customTypeReplacements: Map[Ref, TypeReplacement]): (Map[Ref, TypeReplacement], List[String]) = {
- val genericsAsElmReplacement = decl.params.map(k => Ref(k) -> TypeReplacement(k.toLowerCase)).toMap
- val newTypeReplacements = customTypeReplacements ++ genericsAsElmReplacement
- val genericsDefinition = genericsAsElmReplacement.valuesIterator.map(_.newType).toList
-
- (newTypeReplacements, genericsDefinition)
- }
-
-}
diff --git a/src/main/scala/bridges/elm/TypeReplacement.scala b/src/main/scala/bridges/elm/TypeReplacement.scala
deleted file mode 100644
index 5f1da3f..0000000
--- a/src/main/scala/bridges/elm/TypeReplacement.scala
+++ /dev/null
@@ -1,10 +0,0 @@
-package bridges.elm
-
-final case class TypeReplacement(newType: String, imports: Option[String], decoder: Option[String], encoder: Option[String])
-
-object TypeReplacement {
- def apply(newType: String): TypeReplacement = TypeReplacement(newType, None, None, None)
-
- def apply(newType: String, imports: String, decoder: String, encoder: String): TypeReplacement =
- new TypeReplacement(newType, Some(imports), Some(decoder), Some(encoder))
-}
diff --git a/src/main/scala/bridges/flow/Flow.scala b/src/main/scala/bridges/flow/Flow.scala
deleted file mode 100644
index b4f902d..0000000
--- a/src/main/scala/bridges/flow/Flow.scala
+++ /dev/null
@@ -1,3 +0,0 @@
-package bridges.flow
-
-object Flow extends FlowRenderer
diff --git a/src/main/scala/bridges/flow/FlowEncoder.scala b/src/main/scala/bridges/flow/FlowEncoder.scala
deleted file mode 100644
index a58864d..0000000
--- a/src/main/scala/bridges/flow/FlowEncoder.scala
+++ /dev/null
@@ -1,20 +0,0 @@
-package bridges.flow
-
-import bridges.core.Encoder
-
-trait FlowEncoder[A] {
- def encode: FlowType
-}
-
-object FlowEncoder {
- def apply[A](implicit encoder: FlowEncoder[A]): FlowEncoder[A] =
- encoder
-
- def pure[A](tpe: FlowType): FlowEncoder[A] =
- new FlowEncoder[A] {
- override val encode = tpe
- }
-
- implicit def from[A](implicit encoder: Encoder[A]): FlowEncoder[A] =
- pure(FlowType.from(encoder.encode))
-}
diff --git a/src/main/scala/bridges/flow/FlowEncoderConfig.scala b/src/main/scala/bridges/flow/FlowEncoderConfig.scala
deleted file mode 100644
index 9add6de..0000000
--- a/src/main/scala/bridges/flow/FlowEncoderConfig.scala
+++ /dev/null
@@ -1,8 +0,0 @@
-package bridges.flow
-
-case class FlowEncoderConfig(optionalFields: Boolean)
-
-object FlowEncoderConfig {
- implicit val default: FlowEncoderConfig =
- FlowEncoderConfig(optionalFields = false)
-}
diff --git a/src/main/scala/bridges/flow/FlowField.scala b/src/main/scala/bridges/flow/FlowField.scala
deleted file mode 100644
index 703db61..0000000
--- a/src/main/scala/bridges/flow/FlowField.scala
+++ /dev/null
@@ -1,34 +0,0 @@
-package bridges.flow
-
-import bridges.core._
-import bridges.flow.syntax._
-
-final case class FlowField(name: String, valueType: FlowType, optional: Boolean = false)
-
-object FlowField {
- implicit val rename: Rename[FlowField] =
- Rename.pure { (field, from, to) =>
- val FlowField(name, valueType, optional) = field
-
- FlowField(
- name = if (field.name == from) to else field.name,
- valueType = valueType.rename(from, to),
- optional = optional
- )
- }
-}
-
-final case class FlowRestField(name: String, keyType: FlowType, valueType: FlowType)
-
-object FlowRestField {
- implicit val rename: Rename[FlowRestField] =
- Rename.pure { (field, from, to) =>
- val FlowRestField(name, keyType, valueType) = field
-
- FlowRestField(
- name = if (field.name == from) to else field.name,
- keyType = keyType.rename(from, to),
- valueType = valueType.rename(from, to)
- )
- }
-}
diff --git a/src/main/scala/bridges/flow/FlowRenderer.scala b/src/main/scala/bridges/flow/FlowRenderer.scala
deleted file mode 100644
index f00a639..0000000
--- a/src/main/scala/bridges/flow/FlowRenderer.scala
+++ /dev/null
@@ -1,87 +0,0 @@
-package bridges.flow
-
-import bridges.core.Renderer
-import org.apache.commons.text.StringEscapeUtils.{ escapeJava => escape }
-
-trait FlowRenderer extends Renderer[FlowType] {
- import FlowType._
-
- def render(decl: FlowDecl): String =
- s"""export type ${renderParams(decl.name, decl.params)} = ${renderType(decl.tpe)};"""
-
- private def renderParams(name: String, params: List[String]): String =
- if (params.isEmpty) name else params.mkString(s"$name<", ", ", ">")
-
- private def renderType(tpe: FlowType): String =
- tpe match {
- case Ref(id, params) => renderRef(id, params)
- case Str => "string"
- case Chr => "string"
- case Intr => "number"
- case Real => "number"
- case Bool => "boolean"
- case Null => "null"
- case Undefined => "undefined"
- case StrLit(value) => s""""${escape(value)}""""
- case ChrLit(value) => s""""${escape(value.toString)}""""
- case IntrLit(value) => value.toString
- case RealLit(value) => value.toString
- case BoolLit(value) => value.toString
- case tpe @ Opt(arg) => s"""?${renderParens(tpe)(arg)}"""
- case tpe @ Arr(arg) => s"""${renderParens(tpe)(arg)}[]"""
- case Tuple(types) => types.map(renderType).mkString("[", ", ", "]")
- case Struct(fields, rest) => renderStruct(fields, rest)
- case tpe @ Inter(types) => types.map(renderParens(tpe)).mkString(" & ")
- case tpe @ Union(types) => types.map(renderParens(tpe)).mkString(" | ")
- }
-
- private def renderRef(name: String, params: List[FlowType]): String =
- if (params.isEmpty) name else params.map(renderType).mkString(s"$name<", ", ", ">")
-
- private def renderStruct(fields: List[FlowField], rest: Option[FlowRestField]): String =
- (fields.map(renderField) ++ rest.toList.map(renderRestField)).mkString("{ ", ", ", " }")
-
- private def renderField(field: FlowField): String =
- field match {
- case FlowField(name, tpe, false) =>
- s"""${name}: ${renderType(tpe)}"""
-
- case FlowField(name, tpe, true) =>
- s"""${name}?: ${renderType(tpe)}"""
- }
-
- private def renderRestField(field: FlowRestField): String = {
- val FlowRestField(name, keyType, valueType) = field
- s"""[${name}: ${renderType(keyType)}]: ${renderType(valueType)}"""
- }
-
- private def renderParens(outer: FlowType)(inner: FlowType): String =
- if (precedence(outer) > precedence(inner)) {
- s"(${renderType(inner)})"
- } else {
- renderType(inner)
- }
-
- private def precedence(tpe: FlowType): Int =
- tpe match {
- case _: Ref => 1000
- case _ @Str => 1000
- case _ @Chr => 1000
- case _ @Intr => 1000
- case _ @Real => 1000
- case _ @Bool => 1000
- case _ @Null => 1000
- case _ @Undefined => 1000
- case _: StrLit => 1000
- case _: ChrLit => 1000
- case _: IntrLit => 1000
- case _: RealLit => 1000
- case _: BoolLit => 1000
- case _: Arr => 900
- case _: Tuple => 900
- case _: Opt => 800
- case _: Struct => 600
- case _: Union => 400
- case _: Inter => 200
- }
-}
diff --git a/src/main/scala/bridges/flow/FlowType.scala b/src/main/scala/bridges/flow/FlowType.scala
deleted file mode 100644
index ea1fab4..0000000
--- a/src/main/scala/bridges/flow/FlowType.scala
+++ /dev/null
@@ -1,102 +0,0 @@
-package bridges.flow
-
-import bridges.core._
-import bridges.flow.syntax._
-
-sealed abstract class FlowType extends Product with Serializable {
- import FlowType._
-
- def |(that: FlowType): FlowType =
- Union(List(this, that))
-
- def &(that: FlowType): FlowType =
- Inter(List(this, that))
-
- def ? : FlowType =
- Opt(this)
-}
-
-object FlowType {
- final case class Ref(id: String, params: List[FlowType] = Nil) extends FlowType
-
- case object Str extends FlowType
- case object Chr extends FlowType
- case object Intr extends FlowType
- case object Real extends FlowType
- case object Bool extends FlowType
- case object Null extends FlowType
- case object Undefined extends FlowType
-
- final case class StrLit(value: String) extends FlowType
- final case class ChrLit(value: Char) extends FlowType
- final case class IntrLit(value: Int) extends FlowType
- final case class RealLit(value: Double) extends FlowType
- final case class BoolLit(value: Boolean) extends FlowType
- final case class Opt(tpe: FlowType) extends FlowType
- final case class Arr(tpe: FlowType) extends FlowType
- final case class Tuple(types: List[FlowType]) extends FlowType
-
- final case class Struct(fields: List[FlowField], rest: Option[FlowRestField] = None) extends FlowType {
- def withRest(keyType: FlowType, valueType: FlowType, keyName: String = "key"): Struct =
- copy(rest = Some(FlowRestField(keyName, keyType, valueType)))
- }
- final case class Inter(types: List[FlowType]) extends FlowType
- final case class Union(types: List[FlowType]) extends FlowType
-
- def from(tpe: Type)(implicit config: FlowEncoderConfig): FlowType =
- tpe match {
- case Type.Ref(id, params) => Ref(id, params.map(from))
- case Type.Str => Str
- case Type.Chr => Chr
- case Type.Intr => Intr
- case Type.Real => Real
- case Type.Bool => Bool
- case Type.Opt(tpe) => Opt(from(tpe))
- case Type.Arr(tpe) => Arr(from(tpe))
- case Type.Dict(kTpe, vTpe) => Struct(Nil, Some(FlowRestField("key", from(kTpe), from(vTpe))))
- case Type.Prod(fields) => translateProd(fields)
- case Type.Sum(products) => translateSum(products)
- }
-
- private def translateProd(fields: List[(String, Type)])(implicit config: FlowEncoderConfig): Struct =
- Struct(fields.map { case (name, tpe) => FlowField(name, from(tpe), keyIsOptional(tpe)) })
-
- private def translateSum(products: List[(String, Type.Prod)])(implicit config: FlowEncoderConfig): Union =
- Union(products.map { case (name, prod) =>
- Struct(FlowField("type", StrLit(name)) +: translateProd(prod.fields).fields)
- })
-
- private def keyIsOptional(tpe: Type)(implicit config: FlowEncoderConfig): Boolean =
- tpe match {
- case _: Type.Opt if config.optionalFields => true
- case _ => false
- }
-
- implicit val rename: Rename[FlowType] =
- Rename.pure { (value, from, to) =>
- def renameId(id: String): String =
- if (id == from) to else id
-
- value match {
- case Ref(id, params) => Ref(renameId(id), params.map(_.rename(from, to)))
- case tpe @ Str => tpe
- case tpe @ Chr => tpe
- case tpe @ Intr => tpe
- case tpe @ Real => tpe
- case tpe @ Bool => tpe
- case tpe @ Null => tpe
- case tpe @ Undefined => tpe
- case tpe: StrLit => tpe
- case tpe: ChrLit => tpe
- case tpe: IntrLit => tpe
- case tpe: RealLit => tpe
- case tpe: BoolLit => tpe
- case Opt(tpe) => Opt(tpe.rename(from, to))
- case Arr(tpe) => Arr(tpe.rename(from, to))
- case Tuple(types) => Tuple(types.map(_.rename(from, to)))
- case Struct(fields, rest) => Struct(fields.map(_.rename(from, to)), rest.map(_.rename(from, to)))
- case Inter(types) => Inter(types.map(_.rename(from, to)))
- case Union(types) => Union(types.map(_.rename(from, to)))
- }
- }
-}
diff --git a/src/main/scala/bridges/flow/package.scala b/src/main/scala/bridges/flow/package.scala
deleted file mode 100644
index f2e9368..0000000
--- a/src/main/scala/bridges/flow/package.scala
+++ /dev/null
@@ -1,7 +0,0 @@
-package bridges
-
-import bridges.core.DeclF
-
-package object flow {
- type FlowDecl = DeclF[FlowType]
-}
diff --git a/src/main/scala/bridges/typescript/Decl.scala b/src/main/scala/bridges/typescript/Decl.scala
new file mode 100644
index 0000000..155b121
--- /dev/null
+++ b/src/main/scala/bridges/typescript/Decl.scala
@@ -0,0 +1,7 @@
+package bridges.typescript
+
+final case class Decl(name: String, params: List[String], tpe: TsType)
+
+object Decl:
+ def apply(name: String, tpe: TsType): Decl =
+ Decl(name, Nil, tpe)
diff --git a/src/main/scala/bridges/typescript/TsEncoder.scala b/src/main/scala/bridges/typescript/TsEncoder.scala
index ba7e49c..9579bf6 100644
--- a/src/main/scala/bridges/typescript/TsEncoder.scala
+++ b/src/main/scala/bridges/typescript/TsEncoder.scala
@@ -1,20 +1,140 @@
package bridges.typescript
-import bridges.core.Encoder
+import bridges.typescript.syntax.*
+import scala.compiletime.*
+import scala.deriving.*
+import scala.quoted.*
+import scala.util.NotGiven
-trait TsEncoder[A] {
+trait TsEncoder[A]:
def encode: TsType
-}
-object TsEncoder {
+trait BasicEncoder[A] extends TsEncoder[A]
+
+trait AdtEncoder[A] extends TsEncoder[A]
+
+trait StructEncoder[A] extends AdtEncoder[A]:
+ override def encode: TsType.Struct
+
+trait UnionEncoder[A] extends AdtEncoder[A]:
+ override def encode: TsType.Union
+
+object TsEncoder extends TsEncoderInstances, TsEncoderConstructors
+
+trait TsEncoderInstances extends DerivedTsEncoderInstances:
+ self: TsEncoderConstructors =>
+
+ given stringEncoder: BasicEncoder[String] =
+ basicEnc(TsType.Str)
+
+ given charEncoder: BasicEncoder[Char] =
+ basicEnc(TsType.Chr)
+
+ given intEncoder: BasicEncoder[Int] =
+ basicEnc(TsType.Intr)
+
+ given longEncoder: BasicEncoder[Long] =
+ basicEnc(TsType.Intr)
+
+ given bigDecimalEncoder: BasicEncoder[BigDecimal] =
+ basicEnc(TsType.Real)
+
+ given doubleEncoder: BasicEncoder[Double] =
+ basicEnc(TsType.Real)
+
+ given floatEncoder: BasicEncoder[Float] =
+ basicEnc(TsType.Real)
+
+ given booleanEncoder: BasicEncoder[Boolean] =
+ basicEnc(TsType.Bool)
+
+ given optionEncoder[A](using enc: BasicEncoder[A]): BasicEncoder[Option[A]] =
+ basicEnc(TsType.nullable(enc.encode))
+
+ given mapEncoder[A, B](using aEnc: BasicEncoder[A], bEnc: TsEncoder[B]): BasicEncoder[Map[A, B]] =
+ basicEnc(TsType.Struct(Nil, Some(TsRestField("rest", aEnc.encode, bEnc.encode))))
+
+ given traversableEncoder[F[_] <: Iterable[?], A](using enc: BasicEncoder[A]): BasicEncoder[F[A]] =
+ basicEnc(TsType.Arr(enc.encode))
+
+trait DerivedTsEncoderInstances extends LowPriorityEncoderInstances:
+ self: TsEncoderConstructors =>
+
+ inline given allBasicEncoders[A <: Tuple]: List[BasicEncoder[Any]] =
+ inline erasedValue[A] match
+ case _: EmptyTuple => Nil
+ case _: (h *: t) => summonInline[BasicEncoder[h]].asInstanceOf[BasicEncoder[Any]] :: allBasicEncoders[t]
+
+ inline given allStructEncoders[A <: Tuple]: List[StructEncoder[Any]] =
+ inline erasedValue[A] match
+ case _: EmptyTuple => Nil
+ case _: (h *: t) => summonInline[StructEncoder[h]].asInstanceOf[StructEncoder[Any]] :: allStructEncoders[t]
+
+ private inline def allLabels[A <: Tuple]: List[String] =
+ inline erasedValue[A] match
+ case _: EmptyTuple => Nil
+ case _: (h *: t) => constValue[h].asInstanceOf[String] :: allLabels[t]
+
+ inline given deriveStructEncoder[A](using mirror: Mirror.ProductOf[A], config: TsEncoderConfig): StructEncoder[A] =
+ lazy val labels: List[String] = allLabels[mirror.MirroredElemLabels]
+ lazy val types: List[TsType] = allBasicEncoders[mirror.MirroredElemTypes].map(_.encode)
+ structEnc {
+ TsType.Struct(
+ labels.zip(types).map { (name, tpe) =>
+ TsField(name, tpe, fieldIsOptional(config, tpe))
+ }
+ )
+ }
+
+ inline given deriveUnionEncoder[A](using mirror: Mirror.SumOf[A], config: TsEncoderConfig): UnionEncoder[A] =
+ import TsType.*
+
+ lazy val labels: List[String] = allLabels[mirror.MirroredElemLabels]
+ lazy val types: List[TsType.Struct] = allStructEncoders[mirror.MirroredElemTypes].map(_.encode)
+
+ unionEnc {
+ val products: List[TsType] =
+ labels.zip(types).map { (label, tpe) =>
+ if config.refsInUnions then
+ intersect(
+ TsType.struct(TsField("type", StrLit(label))),
+ Ref(label)
+ )
+ else
+ TsType.Struct(
+ TsField("type", StrLit(label)) ::
+ tpe.fields.map(field => field.copy(optional = fieldIsOptional(config, field.valueType)))
+ )
+ }
+
+ TsType.Union(products)
+ }
+
+ private def fieldIsOptional(config: TsEncoderConfig, tpe: TsType) =
+ config.optionalFields && tpe.isNullable
+
+trait LowPriorityEncoderInstances:
+ self: TsEncoderConstructors =>
+
+ inline given basicRefEncoder[A]: BasicEncoder[A] =
+ lazy val name = TypeName.of[A]
+ basicEnc(TsType.Ref(name))
+
+trait TsEncoderConstructors:
def apply[A](implicit encoder: TsEncoder[A]): TsEncoder[A] =
encoder
- def pure[A](tpe: TsType): TsEncoder[A] =
- new TsEncoder[A] {
- override val encode = tpe
- }
+ def basicEnc[A](tpe: TsType): BasicEncoder[A] =
+ new BasicEncoder[A]:
+ override def encode: TsType =
+ tpe
+
+ def structEnc[A](tpe: TsType.Struct): StructEncoder[A] =
+ new StructEncoder[A]:
+ override def encode: TsType.Struct =
+ tpe
- implicit def from[A](implicit encoder: Encoder[A], config: TsEncoderConfig): TsEncoder[A] =
- pure(TsType.from(encoder.encode))
-}
+ def unionEnc[A](tpe: TsType.Union): UnionEncoder[A] =
+ new UnionEncoder[A]:
+ override def encode: TsType.Union =
+ tpe
diff --git a/src/main/scala/bridges/typescript/TsEncoderConfig.scala b/src/main/scala/bridges/typescript/TsEncoderConfig.scala
index 2b424eb..e99e729 100644
--- a/src/main/scala/bridges/typescript/TsEncoderConfig.scala
+++ b/src/main/scala/bridges/typescript/TsEncoderConfig.scala
@@ -6,6 +6,6 @@ case class TsEncoderConfig(
)
object TsEncoderConfig {
- implicit val default: TsEncoderConfig =
+ given default: TsEncoderConfig =
TsEncoderConfig()
}
diff --git a/src/main/scala/bridges/typescript/TsField.scala b/src/main/scala/bridges/typescript/TsField.scala
index f69469f..25bd648 100644
--- a/src/main/scala/bridges/typescript/TsField.scala
+++ b/src/main/scala/bridges/typescript/TsField.scala
@@ -1,34 +1,17 @@
package bridges.typescript
-import bridges.core._
-import bridges.typescript.syntax._
+final case class TsField(name: String, valueType: TsType, optional: Boolean = false):
+ def rename(from: String, to: String): TsField =
+ TsField(
+ name = if name == from then to else name,
+ valueType = valueType.rename(from, to),
+ optional = optional
+ )
-final case class TsField(name: String, valueType: TsType, optional: Boolean = false)
-
-object TsField {
- implicit val rename: Rename[TsField] =
- Rename.pure { (field, from, to) =>
- val TsField(name, valueType, optional) = field
-
- TsField(
- name = if (field.name == from) to else field.name,
- valueType = valueType.rename(from, to),
- optional = optional
- )
- }
-}
-
-final case class TsRestField(name: String, keyType: TsType, valueType: TsType)
-
-object TsRestField {
- implicit val rename: Rename[TsRestField] =
- Rename.pure { (field, from, to) =>
- val TsRestField(name, keyType, valueType) = field
-
- TsRestField(
- name = if (field.name == from) to else field.name,
- keyType = keyType.rename(from, to),
- valueType = valueType.rename(from, to)
- )
- }
-}
+final case class TsRestField(name: String, keyType: TsType, valueType: TsType):
+ def rename(from: String, to: String): TsRestField =
+ TsRestField(
+ name = if name == from then to else name,
+ keyType = keyType.rename(from, to),
+ valueType = valueType.rename(from, to)
+ )
diff --git a/src/main/scala/bridges/typescript/TsGuardExpr.scala b/src/main/scala/bridges/typescript/TsGuardExpr.scala
index 89d1eac..89112ab 100644
--- a/src/main/scala/bridges/typescript/TsGuardExpr.scala
+++ b/src/main/scala/bridges/typescript/TsGuardExpr.scala
@@ -2,26 +2,25 @@ package bridges.typescript
import org.apache.commons.text.StringEscapeUtils.{ escapeJava => escape }
-sealed abstract class TsGuardExpr
-
-object TsGuardExpr {
- final case class Ref(name: String) extends TsGuardExpr
- final case class Dot(obj: TsGuardExpr, name: String) extends TsGuardExpr
- final case class Arr(exprs: List[TsGuardExpr]) extends TsGuardExpr
- final case class Index(arr: TsGuardExpr, index: TsGuardExpr) extends TsGuardExpr
- final case class Lit(name: String) extends TsGuardExpr
- final case class Typeof(expr: TsGuardExpr) extends TsGuardExpr
- final case class Call(func: TsGuardExpr, args: List[TsGuardExpr]) extends TsGuardExpr
- final case class Func(args: List[String], body: TsGuardExpr) extends TsGuardExpr
- final case class Guard(arg: String, retType: TsType, body: TsGuardExpr) extends TsGuardExpr
- final case class Cond(test: TsGuardExpr, trueArm: TsGuardExpr, falseArm: TsGuardExpr) extends TsGuardExpr
- final case class IsNull(expr: TsGuardExpr) extends TsGuardExpr
- final case class Not(expr: TsGuardExpr) extends TsGuardExpr
- final case class And(lhs: TsGuardExpr, rhs: TsGuardExpr) extends TsGuardExpr
- final case class Or(lhs: TsGuardExpr, rhs: TsGuardExpr) extends TsGuardExpr
- final case class Eql(lhs: TsGuardExpr, rhs: TsGuardExpr) extends TsGuardExpr
- final case class In(key: String, expr: TsGuardExpr) extends TsGuardExpr
-
+enum TsGuardExpr:
+ case Ref(name: String)
+ case Dot(obj: TsGuardExpr, name: String)
+ case Arr(exprs: List[TsGuardExpr])
+ case Index(arr: TsGuardExpr, index: TsGuardExpr)
+ case Lit(name: String)
+ case Typeof(expr: TsGuardExpr)
+ case Call(func: TsGuardExpr, args: List[TsGuardExpr])
+ case Func(args: List[String], body: TsGuardExpr)
+ case Guard(arg: String, retType: TsType, body: TsGuardExpr)
+ case Cond(test: TsGuardExpr, trueArm: TsGuardExpr, falseArm: TsGuardExpr)
+ case IsNull(expr: TsGuardExpr)
+ case Not(expr: TsGuardExpr)
+ case And(lhs: TsGuardExpr, rhs: TsGuardExpr)
+ case Or(lhs: TsGuardExpr, rhs: TsGuardExpr)
+ case Eql(lhs: TsGuardExpr, rhs: TsGuardExpr)
+ case In(key: String, expr: TsGuardExpr)
+
+object TsGuardExpr:
def ref(name: String): TsGuardExpr =
Ref(name)
@@ -88,10 +87,9 @@ object TsGuardExpr {
def in(key: String, expr: TsGuardExpr): TsGuardExpr =
In(key, expr)
- def render(expr: TsGuardExpr): String = {
- val r = renderParens(expr)(_)
-
- expr match {
+ def render(expr: TsGuardExpr): String =
+ val r = renderParens(expr)
+ expr match
case Ref(name) => name
case Dot(obj, name) => s"""${r(obj)}.${name}"""
case Arr(exprs) => exprs.map(r).mkString("[", ", ", "]")
@@ -109,18 +107,14 @@ object TsGuardExpr {
case Or(lhs, rhs) => s"""${r(lhs)} || ${r(rhs)}"""
case Eql(lhs, rhs) => s"""${r(lhs)} === ${r(rhs)}"""
case In(key, expr) => s"""${r(lit(key))} in ${r(expr)}"""
- }
- }
private def renderParens(outer: TsGuardExpr)(inner: TsGuardExpr): String =
- if (precedence(outer) > precedence(inner)) {
- s"(${render(inner)})"
- } else {
- render(inner)
- }
+ if precedence(outer) > precedence(inner)
+ then s"(${render(inner)})"
+ else render(inner)
private def precedence(tpe: TsGuardExpr): Int =
- tpe match {
+ tpe match
case _: Ref => 1000
case _: Dot => 1000
case _: Arr => 1000
@@ -138,5 +132,3 @@ object TsGuardExpr {
case _: Cond => 400
case _: Func => 300
case _: Guard => 300
- }
-}
diff --git a/src/main/scala/bridges/typescript/TsGuardRenderer.scala b/src/main/scala/bridges/typescript/TsGuardRenderer.scala
index fede5e7..f1bde49 100644
--- a/src/main/scala/bridges/typescript/TsGuardRenderer.scala
+++ b/src/main/scala/bridges/typescript/TsGuardRenderer.scala
@@ -1,25 +1,24 @@
package bridges.typescript
-import bridges.core.{ DeclF, Renderer }
-import unindent._
+import unindent.*
-abstract class TsGuardRenderer(
- predName: String => String = id => s"""is${id}""",
- guardName: String => String = id => s"""as${id}"""
-) extends Renderer[TsType] {
- import TsType._
- import TsGuardExpr._
+abstract class TsGuardRenderer(predName: String => String = id => s"""is${id}"""):
+ import TsGuardExpr.*
+ import TsType.{ func => _, ref => _, * }
- def render(decl: TsDecl): String =
+ def render(decls: List[Decl]): String =
+ decls.map(render).mkString("\n\n")
+
+ def render(decl: Decl): String =
decl match {
- case DeclF(name, Nil, tpe) =>
+ case Decl(name, Nil, tpe) =>
i"""
export const ${predName(decl.name)} = (v: any): v is ${name} => {
return ${TsGuardExpr.render(isType(ref("v"), decl.tpe))};
}
"""
- case DeclF(name, params, tpe) =>
+ case Decl(name, params, tpe) =>
val tparams = renderParamTypes(params)
val vparams = renderParamPreds(params)
i"""
@@ -30,31 +29,24 @@ abstract class TsGuardRenderer(
}
def renderParamTypes(params: List[String]): String =
- if (params.isEmpty) {
- ""
- } else {
- params.mkString("<", ", ", ">")
- }
+ if params.isEmpty
+ then ""
+ else params.mkString("<", ", ", ">")
def renderParamPreds(params: List[String]): String =
params.map(param => s"${predName(param)}: (${param.toLowerCase}: any) => ${param.toLowerCase} is ${param}").mkString(", ")
- import TsGuardExpr._
+ import TsGuardExpr.*
- def guardFunc(pair: (TsType, Int)): TsGuardExpr = {
+ def guardFunc(pair: (TsType, Int)): TsGuardExpr =
val (tpe, index) = pair
val arg = "a" + index
guard(arg, tpe)(isType(ref(arg), tpe))
- }
def isType(arg: TsGuardExpr, tpe: TsType): TsGuardExpr =
- tpe match {
- case TsType.Ref(id, Nil) =>
- call(ref(predName(id)), arg)
-
- case TsType.Ref(id, params) =>
- call(Call(ref(predName(id)), params.zipWithIndex.map(guardFunc)), arg)
-
+ tpe match
+ case TsType.Ref(id, Nil) => call(ref(predName(id)), arg)
+ case TsType.Ref(id, params) => call(Call(ref(predName(id)), params.zipWithIndex.map(guardFunc)), arg)
case TsType.Any => lit(true)
case TsType.Unknown => lit(true)
case TsType.Str => eql(typeof(arg), lit("string"))
@@ -74,7 +66,6 @@ abstract class TsGuardRenderer(
case TsType.Struct(fields, rest) => isStruct(arg, fields, rest)
case TsType.Inter(types) => isAll(arg, types)
case TsType.Union(types) => isUnion(arg, types)
- }
private def isArray(expr: TsGuardExpr, tpe: TsType): TsGuardExpr =
and(
@@ -82,16 +73,16 @@ abstract class TsGuardRenderer(
call(dot(expr, "every"), func("i")(isType(ref("i"), tpe)))
)
- private def isTuple(expr: TsGuardExpr, types: List[TsType]): TsGuardExpr = {
+ private def isTuple(expr: TsGuardExpr, types: List[TsType]): TsGuardExpr =
val baseChecks = List(
call(dot(ref("Array"), "isArray"), expr),
eql(dot(expr, "length"), lit(types.length))
)
- val itemChecks = types.zipWithIndex.map { case (tpe, idx) => isType(index(expr, idx), tpe) }
+ val itemChecks: List[TsGuardExpr] =
+ types.zipWithIndex.map((tpe, idx) => isType(index(expr, idx), tpe))
(baseChecks ++ itemChecks).reduceLeft(and(_, _))
- }
private def isStruct(expr: TsGuardExpr, fields: List[TsField], rest: Option[TsRestField]): TsGuardExpr = {
val seed = and(eql(typeof(expr), lit("object")), not(isnull(expr)))
@@ -101,11 +92,9 @@ abstract class TsGuardRenderer(
.map { field =>
val TsField(name, tpe, optional) = field
- if (optional) {
- or(not(in(name, expr)), isType(dot(expr, name), tpe))
- } else {
- and(in(name, expr), isType(dot(expr, name), tpe))
- }
+ if optional
+ then or(not(in(name, expr)), isType(dot(expr, name), tpe))
+ else and(in(name, expr), isType(dot(expr, name), tpe))
}
.foldLeft(seed)(and(_, _))
@@ -150,21 +139,29 @@ abstract class TsGuardRenderer(
}
private def isUnion(expr: TsGuardExpr, types: List[TsType]): TsGuardExpr =
- types.collectAll { case tpe @ DiscriminatedBy(name, rest) => name -> rest } match {
+ types.collectAll { case tpe @ DiscriminatedBy(name, rest) => name -> rest } match
case Some(pairs) =>
- and(eql(typeof(expr), lit("object")), not(isnull(expr)), in("type", expr), isDiscriminated(expr, pairs))
+ and(
+ eql(typeof(expr), lit("object")),
+ not(isnull(expr)),
+ in("type", expr),
+ isDiscriminated(expr, pairs)
+ )
+
case None =>
isAny(expr, types)
- }
private def isDiscriminated(expr: TsGuardExpr, types: List[(String, TsType.Struct)]): TsGuardExpr =
- types match {
+ types match
case Nil =>
lit(false)
case (name, head) :: tail =>
- cond(eql(dot(expr, "type"), lit(name)), isType(expr, head), isDiscriminated(expr, tail))
- }
+ cond(
+ eql(dot(expr, "type"), lit(name)),
+ isType(expr, head),
+ isDiscriminated(expr, tail)
+ )
private def isAny(expr: TsGuardExpr, types: List[TsType]): TsGuardExpr =
types
@@ -178,16 +175,14 @@ abstract class TsGuardRenderer(
.reduceLeftOption(and(_, _))
.getOrElse(lit(true))
- private implicit class ListOps[A](list: List[A]) {
- def collectAll[B](func: PartialFunction[A, B]): Option[List[B]] = {
+ extension [A](list: List[A])
+ def collectAll[B](func: PartialFunction[A, B]): Option[List[B]] =
val temp = list.collect(func)
if (temp.length == list.length) Some(temp) else None
- }
- }
- private object DiscriminatedBy {
+ private object DiscriminatedBy:
def unapply(tpe: TsType): Option[(String, TsType.Struct)] =
- tpe match {
+ tpe match
case TsType.Struct(fields, _) =>
fields.collectFirst { case decl @ TsField("type", TsType.StrLit(name), _) =>
(name, TsType.Struct(fields.filterNot(_ == decl)))
@@ -195,6 +190,5 @@ abstract class TsGuardRenderer(
case _ =>
None
- }
- }
-}
+ end DiscriminatedBy
+end TsGuardRenderer
diff --git a/src/main/scala/bridges/typescript/TsType.scala b/src/main/scala/bridges/typescript/TsType.scala
index dbf79e4..832535c 100644
--- a/src/main/scala/bridges/typescript/TsType.scala
+++ b/src/main/scala/bridges/typescript/TsType.scala
@@ -1,106 +1,98 @@
package bridges.typescript
-import bridges.core._
-import bridges.typescript.syntax._
-
-sealed abstract class TsType extends Product with Serializable {
+enum TsType:
import TsType._
- def |(that: TsType): TsType =
- Union(List(this, that))
-
- def &(that: TsType): TsType =
- Inter(List(this, that))
-}
-
-object TsType {
- final case class Ref(id: String, params: List[TsType] = Nil) extends TsType
-
- case object Any extends TsType
- case object Unknown extends TsType
- case object Str extends TsType
- case object Chr extends TsType
- case object Intr extends TsType
- case object Real extends TsType
- case object Bool extends TsType
- case object Null extends TsType
-
- final case class StrLit(value: String) extends TsType
- final case class ChrLit(value: Char) extends TsType
- final case class IntrLit(value: Int) extends TsType
- final case class RealLit(value: Double) extends TsType
- final case class BoolLit(value: Boolean) extends TsType
- final case class Arr(tpe: TsType) extends TsType
- final case class Tuple(types: List[TsType]) extends TsType
- final case class Func(args: List[(String, TsType)], ret: TsType) extends TsType
-
- final case class Struct(fields: List[TsField], rest: Option[TsRestField] = None) extends TsType {
- def withRest(keyType: TsType, valueType: TsType, keyName: String = "key"): Struct =
- copy(rest = Some(TsRestField(keyName, keyType, valueType)))
- }
-
- final case class Inter(types: List[TsType]) extends TsType
- final case class Union(types: List[TsType]) extends TsType
-
- def from(tpe: Type)(implicit config: TsEncoderConfig): TsType =
- tpe match {
- case Type.Ref(id, params) => Ref(id, params.map(from))
- case Type.Str => Str
- case Type.Chr => Chr
- case Type.Intr => Intr
- case Type.Real => Real
- case Type.Bool => Bool
- case Type.Opt(tpe) => from(tpe) | Null
- case Type.Arr(tpe) => Arr(from(tpe))
- case Type.Dict(kTpe, vTpe) => Struct(Nil, Some(TsRestField("key", from(kTpe), from(vTpe))))
- case Type.Prod(fields) => translateProd(fields)
- case Type.Sum(products) => translateSum(products)
- }
-
- private def translateProd(fields: List[(String, Type)])(implicit config: TsEncoderConfig): Struct =
- Struct(fields.map { case (name, tpe) => TsField(name, from(tpe), keyIsOptional(tpe)) })
-
- private def translateSum(products: List[(String, Type.Prod)])(implicit config: TsEncoderConfig): Union =
- Union(products.map { case (name, tpe) =>
- if (config.refsInUnions) {
- Inter(List(Struct(List(TsField("type", StrLit(name)))), Ref(name)))
- } else {
- Struct(TsField("type", StrLit(name)) +: translateProd(tpe.fields).fields)
- }
- })
-
- private def keyIsOptional(tpe: Type)(implicit config: TsEncoderConfig): Boolean =
- tpe match {
- case _: Type.Opt if config.optionalFields => true
- case _ => false
- }
-
- implicit val rename: Rename[TsType] =
- Rename.pure { (value, from, to) =>
- def renameId(id: String): String =
- if (id == from) to else id
-
- value match {
- case Ref(id, params) => Ref(renameId(id), params.map(_.rename(from, to)))
- case Any => Any
- case tpe @ Unknown => tpe
- case tpe @ Str => tpe
- case tpe @ Chr => tpe
- case tpe @ Intr => tpe
- case tpe @ Real => tpe
- case tpe @ Bool => tpe
- case tpe @ Null => tpe
- case tpe: StrLit => tpe
- case tpe: ChrLit => tpe
- case tpe: IntrLit => tpe
- case tpe: RealLit => tpe
- case tpe: BoolLit => tpe
- case Arr(tpe) => Arr(tpe.rename(from, to))
- case Tuple(types) => Tuple(types.map(_.rename(from, to)))
- case Func(args, ret) => Func(args.map(_.rename(from, to)), ret.rename(from, to))
- case Struct(fields, rest) => Struct(fields.map(_.rename(from, to)), rest.map(_.rename(from, to)))
- case Inter(types) => Inter(types.map(_.rename(from, to)))
- case Union(types) => Union(types.map(_.rename(from, to)))
- }
- }
-}
+ case Any
+ case Unknown
+ case Str
+ case Chr
+ case Intr
+ case Real
+ case Bool
+ case Null
+
+ case Ref(id: String, params: List[TsType] = Nil)
+ case StrLit(value: String)
+ case ChrLit(value: Char)
+ case IntrLit(value: Int)
+ case RealLit(value: Double)
+ case BoolLit(value: Boolean)
+ case Arr(tpe: TsType)
+ case Tuple(types: List[TsType])
+ case Func(args: List[(String, TsType)], ret: TsType)
+
+ case Struct(fields: List[TsField], rest: Option[TsRestField] = None)
+
+ case Inter(types: List[TsType])
+ case Union(types: List[TsType])
+
+ def isNullable: Boolean =
+ this match
+ case Null => true
+ case Union(types) => types.exists(_.isNullable)
+ case _ => false
+
+ def rename(from: String, to: String): TsType =
+ def renameId(id: String): String =
+ if (id == from) to else id
+
+ this match
+ case Ref(id, params) => Ref(renameId(id), params.map(_.rename(from, to)))
+ case Any => Any
+ case tpe @ Unknown => tpe
+ case tpe @ Str => tpe
+ case tpe @ Chr => tpe
+ case tpe @ Intr => tpe
+ case tpe @ Real => tpe
+ case tpe @ Bool => tpe
+ case tpe @ Null => tpe
+ case tpe: StrLit => tpe
+ case tpe: ChrLit => tpe
+ case tpe: IntrLit => tpe
+ case tpe: RealLit => tpe
+ case tpe: BoolLit => tpe
+ case Arr(tpe) => Arr(tpe.rename(from, to))
+ case Tuple(types) => Tuple(types.map(_.rename(from, to)))
+ case Func(args, ret) => Func(args.map((name, tpe) => (name, tpe.rename(from, to))), ret.rename(from, to))
+ case Struct(fields, rest) => Struct(fields.map(_.rename(from, to)), rest.map(_.rename(from, to)))
+ case Inter(types) => Inter(types.map(_.rename(from, to)))
+ case Union(types) => Union(types.map(_.rename(from, to)))
+
+object TsType:
+ def ref(name: String, params: TsType*): Ref =
+ Ref(name, params.toList)
+
+ def tuple(types: TsType*): Tuple =
+ Tuple(types.toList)
+
+ def union(types: TsType*): Union =
+ Union(types.toList)
+
+ def intersect(types: TsType*): TsType =
+ Inter(types.toList)
+
+ def nullable(tpe: TsType): TsType =
+ union(tpe, Null)
+
+ def dict(keyType: TsType, valueType: TsType): Struct =
+ Struct(Nil, Some(TsRestField("key", keyType, valueType)))
+
+ def struct(fields: TsField*): Struct =
+ Struct(fields.toList)
+
+ def labelled(name: String, tpe: TsType): TsType =
+ val discriminator = TsField("type", StrLit(name))
+ tpe match
+ case Struct(fields, rest) => Struct(discriminator :: fields, rest)
+ case tpe => intersect(struct(discriminator), tpe)
+
+ def discriminated(cases: (String, TsType)*): Union =
+ union(cases.map(labelled)*)
+
+ def func(args: (String, TsType)*)(ret: TsType): Func =
+ Func(args.toList, ret)
+
+extension (struct: TsType.Struct)
+ def withRest(keyType: TsType, valueType: TsType, keyName: String = "key"): TsType.Struct =
+ struct.copy(rest = Some(TsRestField(keyName, keyType, valueType)))
diff --git a/src/main/scala/bridges/typescript/TsTypeRenderer.scala b/src/main/scala/bridges/typescript/TsTypeRenderer.scala
index 8ee90d5..249833a 100644
--- a/src/main/scala/bridges/typescript/TsTypeRenderer.scala
+++ b/src/main/scala/bridges/typescript/TsTypeRenderer.scala
@@ -1,22 +1,23 @@
package bridges.typescript
-import bridges.core.{ DeclF, Renderer }
import org.apache.commons.text.StringEscapeUtils.{ escapeJava => escape }
-abstract class TsTypeRenderer(exportAll: Boolean) extends Renderer[TsType] {
+abstract class TsTypeRenderer(exportAll: Boolean):
import TsType._
- def render(decl: TsDecl): String =
- decl match {
- case DeclF(name, params, TsType.Struct(fields, rest)) =>
+ def render(decls: List[Decl]): String =
+ decls.map(render).mkString("\n\n")
+
+ def render(decl: Decl): String =
+ decl match
+ case Decl(name, params, TsType.Struct(fields, rest)) =>
s"${if (exportAll) "export interface" else "interface"} ${renderParams(name, params)} ${renderStructAsInterface(fields, rest)}"
- case DeclF(name, params, tpe) =>
+ case Decl(name, params, tpe) =>
s"${if (exportAll) "export type" else "type"} ${renderParams(name, params)} = ${renderType(tpe)};"
- }
def renderType(tpe: TsType): String =
- tpe match {
+ tpe match
case Ref(id, params) => renderRef(id, params)
case Any => "any"
case Str => "string"
@@ -37,7 +38,6 @@ abstract class TsTypeRenderer(exportAll: Boolean) extends Renderer[TsType] {
case Struct(fields, rest) => renderStruct(fields, rest)
case tpe @ Inter(types) => types.map(renderParens(tpe)).mkString(" & ")
case tpe @ Union(types) => types.map(renderParens(tpe)).mkString(" | ")
- }
private def renderParams(name: String, params: List[String]): String =
if (params.isEmpty) name else params.mkString(s"$name<", ", ", ">")
@@ -55,33 +55,29 @@ abstract class TsTypeRenderer(exportAll: Boolean) extends Renderer[TsType] {
.mkString("{\n", "", "}")
private def renderField(field: TsField): String =
- field match {
+ field match
case TsField(name, valueType, false) =>
- s"""${name}: ${renderType(valueType)}"""
+ s"""$name: ${renderType(valueType)}"""
case TsField(name, valueType, true) =>
- s"""${name}?: ${renderType(valueType)}"""
- }
+ s"""$name?: ${renderType(valueType)}"""
private def renderArgs(args: List[(String, TsType)]): String =
args
- .map { case (name, tpe) => s"""${name}: ${renderType(tpe)}""" }
+ .map { case (name, tpe) => s"""$name: ${renderType(tpe)}""" }
.mkString("(", ", ", ")")
- private def renderRestField(field: TsRestField): String = {
+ private def renderRestField(field: TsRestField): String =
val TsRestField(name, keyType, valueType) = field
- s"""[${name}: ${renderType(keyType)}]: ${renderType(valueType)}"""
- }
+ s"""[$name: ${renderType(keyType)}]: ${renderType(valueType)}"""
private def renderParens(outer: TsType)(inner: TsType): String =
- if (precedence(outer) > precedence(inner)) {
- s"(${renderType(inner)})"
- } else {
- renderType(inner)
- }
+ if precedence(outer) > precedence(inner)
+ then s"(${renderType(inner)})"
+ else renderType(inner)
private def precedence(tpe: TsType): Int =
- tpe match {
+ tpe match
case _: Ref => 1000
case _ @Any => 1000
case _ @Unknown => 1000
@@ -102,5 +98,3 @@ abstract class TsTypeRenderer(exportAll: Boolean) extends Renderer[TsType] {
case _: Union => 400
case _: Inter => 200
case _: Func => 100
- }
-}
diff --git a/src/main/scala/bridges/typescript/TypeName.scala b/src/main/scala/bridges/typescript/TypeName.scala
new file mode 100644
index 0000000..ba7b91f
--- /dev/null
+++ b/src/main/scala/bridges/typescript/TypeName.scala
@@ -0,0 +1,13 @@
+package bridges.typescript
+
+import scala.compiletime.*
+import scala.quoted.*
+
+object TypeName:
+ inline given of[A]: String =
+ ${ nameImpl[A] }
+
+ private def nameImpl[A: Type](using Quotes): Expr[String] =
+ import quotes.reflect.*
+ val typeRepr = TypeRepr.of[A]
+ Expr(typeRepr.show.split("\\.").last)
diff --git a/src/main/scala/bridges/typescript/package.scala b/src/main/scala/bridges/typescript/package.scala
deleted file mode 100644
index 2a8764a..0000000
--- a/src/main/scala/bridges/typescript/package.scala
+++ /dev/null
@@ -1,7 +0,0 @@
-package bridges
-
-import bridges.core.DeclF
-
-package object typescript {
- type TsDecl = DeclF[TsType]
-}
diff --git a/src/main/scala/bridges/typescript/syntax.scala b/src/main/scala/bridges/typescript/syntax.scala
new file mode 100644
index 0000000..dd3f084
--- /dev/null
+++ b/src/main/scala/bridges/typescript/syntax.scala
@@ -0,0 +1,16 @@
+package bridges.typescript
+
+object syntax:
+ inline def decl[A](using enc: AdtEncoder[A]): Decl =
+ lazy val name = TypeName.of[A]
+ Decl(name, enc.encode)
+
+ def decl(name: String, params: String*)(tpe: TsType): Decl =
+ Decl(name, params.toList, tpe)
+
+ extension (name: String)
+ def -->(tpe: TsType): TsField =
+ TsField(name, tpe)
+
+ def -?>(tpe: TsType): TsField =
+ TsField(name, tpe, true)
diff --git a/src/test/scala/bridges/core/EncoderSpec.scala b/src/test/scala/bridges/core/EncoderSpec.scala
deleted file mode 100644
index 3871f7f..0000000
--- a/src/test/scala/bridges/core/EncoderSpec.scala
+++ /dev/null
@@ -1,347 +0,0 @@
-package bridges.core
-
-import bridges.SampleTypes._
-import bridges.core.Type._
-import bridges.core.syntax._
-import org.scalatest.freespec.AnyFreeSpec
-import org.scalatest.matchers.should.Matchers
-
-class EncoderSpec extends AnyFreeSpec with Matchers {
- "encode[A]" - {
- "primitive types" in {
- encode[String] should be(Str)
- encode[Char] should be(Chr)
- encode[Int] should be(Intr)
- encode[Float] should be(Real)
- encode[Double] should be(Real)
- encode[Boolean] should be(Bool)
- }
-
- "options" in {
- encode[Option[String]] should be(Opt(Str))
- encode[Option[Int]] should be(Opt(Intr))
- }
-
- "sequences" in {
- encode[Seq[String]] should be(Arr(Str))
- encode[Set[Set[Int]]] should be(Arr(Arr(Intr)))
- }
-
- "value classes" in {
- encode[Value] should be(Str)
- }
-
- "a class with UUID member" in {
- encode[ClassUUID] should be(
- prod(
- "a" -> Ref("UUID")
- )
- )
- }
-
- "a class with Date member" in {
- encode[ClassDate] should be(
- prod(
- "a" -> Ref("Date")
- )
- )
- }
-
- "case classes" in {
- encode[Pair] should be(
- prod(
- "a" -> Str,
- "b" -> Intr
- )
- )
- }
-
- "sealed types" in {
- encode[OneOrOther] should be(
- sum(
- "One" -> prod("value" -> Str),
- "Other" -> prod("value" -> Intr)
- )
- )
- }
-
- "sealed types with objects" in {
- encode[ClassOrObject] should be(
- sum(
- "MyClass" -> prod("value" -> Intr),
- "MyObject" -> prod()
- )
- )
- }
-
- "sealed types with objects in nested objects" in {
- encode[NestedClassOrObject] should be(
- sum(
- "MyClass" -> prod("value" -> Intr),
- "MyObject" -> prod()
- )
- )
- }
-
- "overridden defaults" in {
- implicit val oneEncoder: BasicEncoder[One] =
- Encoder.pure(Str)
-
- encode[One] should be(Str)
-
- encode[OneOrOther] should be(
- sum(
- "One" -> prod("value" -> Str),
- "Other" -> prod("value" -> Intr)
- )
- )
- }
-
- "sealed types with intermediate types and indirect recursion" in {
- encode[Shape] should be(
- sum(
- "Circle" -> prod(
- "radius" -> Real,
- "color" -> Ref("Color")
- ),
- "Rectangle" -> prod(
- "width" -> Real,
- "height" -> Real,
- "color" -> Ref("Color")
- ),
- "ShapeGroup" -> prod(
- "leftShape" -> Ref("Shape"),
- "rightShape" -> Ref("Shape")
- )
- )
- )
-
- encode[Circle] should be(
- prod(
- "radius" -> Real,
- "color" -> Ref("Color")
- )
- )
-
- encode[Rectangle] should be(
- prod(
- "width" -> Real,
- "height" -> Real,
- "color" -> Ref("Color")
- )
- )
-
- encode[ShapeGroup] should be(
- prod(
- "leftShape" -> Ref("Shape"),
- "rightShape" -> Ref("Shape")
- )
- )
- }
-
- "recursive types with direct recursion on same type" in {
- encode[Navigation] should be(
- sum(
- "Node" -> prod(
- "name" -> Str,
- "children" -> Arr(Ref("Navigation"))
- ),
- "NodeList" -> prod(
- "all" -> Arr(Ref("Navigation"))
- )
- )
- )
-
- encode[NodeList] should be(
- prod(
- "all" -> Arr(Ref("Navigation"))
- )
- )
-
- encode[Node] should be(
- prod(
- "name" -> Str,
- "children" -> Arr(Ref("Navigation"))
- )
- )
- }
-
- "types with specific parameters" in {
- encode[Alpha] should be(
- prod(
- "name" -> Str,
- "char" -> Chr,
- "bool" -> Bool
- )
- )
-
- encode[ArrayClass] should be(
- prod(
- "aList" -> Arr(Str),
- "optField" -> Opt(Real)
- )
- )
- encode[Numeric] should be(
- prod(
- "double" -> Real,
- "float" -> Real,
- "int" -> Intr
- )
- )
- }
-
- "class that references other case classes" in {
- encode[ExternalReferences] should be(
- prod(
- "color" -> Ref("Color"),
- "nav" -> Ref("Navigation")
- )
- )
- }
-
- "mutually recursive types" in {
- encode[TypeOne] should be(
- prod(
- "name" -> Str,
- "values" -> Arr(Ref("TypeTwo"))
- )
- )
-
- encode[TypeTwo] should be(
- sum(
- "OptionOne" -> prod("value" -> Intr),
- "OptionTwo" -> prod("value" -> Ref("TypeOne"))
- )
- )
- }
-
- "self-recursive type" in {
- encode[Recursive] should be(
- prod(
- "head" -> Intr,
- "tail" -> Opt(Ref("Recursive"))
- )
- )
-
- encode[Recursive2] should be(
- prod(
- "head" -> Intr,
- "tail" -> Arr(Ref("Recursive2"))
- )
- )
- }
-
- "pure objects ADT" in {
- encode[ObjectsOnly] should be(
- sum(
- "ObjectOne" -> prod(),
- "ObjectTwo" -> prod()
- )
- )
- }
-
- "refined types and class containing them" in {
- encode[RefinedString] should be(Str)
- encode[RefinedInt] should be(Intr)
- encode[RefinedChar] should be(Chr)
-
- // Note that the import is required or it fails!
- // import eu.timepit.refined.shapeless.typeable._
-
- encode[ClassWithRefinedType] should be(
- prod("name" -> Str)
- )
- }
-
- "we can override uuid as string" in {
- implicit val uuidEncoder: BasicEncoder[java.util.UUID] =
- Encoder.pure(Str)
-
- encode[ClassUUID] should be(
- prod("a" -> Str)
- )
- }
- }
-
- "decl[A]" - {
- "value classes" in {
- decl[Value] should be(
- "Value" := Str
- )
- }
-
- "case classes" in {
- decl[Pair] should be(
- "Pair" := prod(
- "a" -> Str,
- "b" -> Intr
- )
- )
- }
-
- "sealed types" in {
- decl[OneOrOther] should be(
- "OneOrOther" := sum(
- "One" -> prod("value" -> Str),
- "Other" -> prod("value" -> Intr)
- )
- )
- }
-
- "overridden defaults" in {
- implicit val oneEncoder: BasicEncoder[One] =
- Encoder.pure(Str)
-
- encode[One] should be(Str)
-
- decl[OneOrOther] should be(
- "OneOrOther" := sum(
- "One" -> prod(
- "value" -> Str
- ),
- "Other" -> prod(
- "value" -> Intr
- )
- )
- )
- }
-
- "class with refined type" in {
- // Note that the import is required or it fails!
- // import eu.timepit.refined.shapeless.typeable._
-
- decl[ClassWithRefinedType] should be(
- "ClassWithRefinedType" := prod(
- "name" -> Str
- )
- )
- }
- }
-
- "Numeric types" in {
- decl[NumericTypes] shouldBe {
- decl("NumericTypes")(
- prod(
- "int" -> Intr,
- "long" -> Intr,
- "float" -> Real,
- "double" -> Real,
- "bigDecimal" -> Real
- )
- )
- }
- }
-
- "Map" in {
- decl[Map[String, Int]] shouldBe decl("Map")(dict(Str, Intr))
- decl[Map[String, Pair]] shouldBe decl("Map")(
- dict(
- Str,
- prod(
- "a" -> Str,
- "b" -> Intr
- )
- )
- )
- }
-}
diff --git a/src/test/scala/bridges/core/TypeSpec.scala b/src/test/scala/bridges/core/TypeSpec.scala
deleted file mode 100644
index 520ceac..0000000
--- a/src/test/scala/bridges/core/TypeSpec.scala
+++ /dev/null
@@ -1,121 +0,0 @@
-package bridges.core
-
-import bridges.core.syntax._
-import org.scalatest._
-import org.scalatest.freespec.AnyFreeSpec
-import org.scalatest.matchers.should.Matchers
-
-class TypeSpec extends AnyFreeSpec with Matchers {
- import Type._
-
- def t[A <: Type](a: A): Type = a
-
- "type.rename" - {
- "matching Ref" in {
- val actual = t(Ref("foo")).rename("foo", "bar")
- val expected = t(Ref("bar"))
- actual should equal(expected)
- }
-
- "non-matching Ref" in {
- val actual = t(Ref("baz")).rename("foo", "bar")
- val expected = t(Ref("baz"))
- actual should equal(expected)
- }
-
- "Optional" in {
- val actual = t(Opt(Ref("foo"))).rename("foo", "bar")
- val expected = t(Opt(Ref("bar")))
- actual should equal(expected)
- }
-
- "Array" in {
- val actual = t(Arr(Ref("foo"))).rename("foo", "bar")
- val expected = t(Arr(Ref("bar")))
- actual should equal(expected)
- }
-
- "Prod" in {
- val actual = t(
- prod(
- "a" -> Ref("foo"),
- "b" -> Ref("baz")
- )
- ).rename("foo", "bar")
-
- val expected = t(
- prod(
- "a" -> Ref("bar"),
- "b" -> Ref("baz")
- )
- )
-
- actual should equal(expected)
- }
-
- "Sum" - {
- "renames members " in {
- val actual = t(
- sum(
- "typeA" -> prod(
- "a" -> Ref("foo"),
- "b" -> Ref("baz")
- ),
- "typeB" -> prod(
- "a" -> Ref("foo"),
- "b" -> Ref("baz")
- )
- )
- ).rename("foo", "bar")
-
- val expected = t(
- sum(
- "typeA" -> Prod(
- List(
- "a" -> Ref("bar"),
- "b" -> Ref("baz")
- )
- ),
- "typeB" -> Prod(
- List(
- "a" -> Ref("bar"),
- "b" -> Ref("baz")
- )
- )
- )
- )
-
- actual should equal(expected)
- }
- "renames type name of members" in {
- val actual = t(
- sum(
- "typeA" -> prod(
- "a" -> Ref("foo"),
- "b" -> Ref("baz")
- ),
- "typeA" -> prod(
- "a" -> Ref("foo"),
- "b" -> Ref("baz")
- )
- )
- ).rename("typeA", "typeC")
-
- val expected = t(
- sum(
- "typeC" -> prod(
- "a" -> Ref("foo"),
- "b" -> Ref("baz")
- ),
- "typeC" -> prod(
- "a" -> Ref("foo"),
- "b" -> Ref("baz")
- )
- )
- )
-
- actual should equal(expected)
- }
- }
- }
-}
diff --git a/src/test/scala/bridges/elm/ElmFileBuilderSpec.scala b/src/test/scala/bridges/elm/ElmFileBuilderSpec.scala
deleted file mode 100644
index 12e96c5..0000000
--- a/src/test/scala/bridges/elm/ElmFileBuilderSpec.scala
+++ /dev/null
@@ -1,353 +0,0 @@
-package bridges.elm
-
-import bridges.SampleTypes._
-import bridges.core.Type._
-import bridges.core.syntax._
-import org.scalatest._
-import unindent._
-import org.scalatest.freespec.AnyFreeSpec
-import org.scalatest.matchers.should.Matchers
-
-class ElmFileBuilderSpec extends AnyFreeSpec with Matchers {
- "for a single case class" in {
- val fileContent =
- i"""
- module CustomModule.Color exposing (..)
-
- import Json.Decode as Decode
- import Json.Decode.Pipeline exposing (..)
- import Json.Encode as Encode
-
-
- type alias Color = { red: Int, green: Int, blue: Int }
-
-
- decoderColor : Decode.Decoder Color
- decoderColor = Decode.succeed Color |> required "red" Decode.int |> required "green" Decode.int |> required "blue" Decode.int
-
-
- encoderColor : Color -> Encode.Value
- encoderColor obj = Encode.object [ ("red", Encode.int obj.red), ("green", Encode.int obj.green), ("blue", Encode.int obj.blue) ]
- """
- val expected = ("Color.elm", fileContent)
-
- Elm.buildFile("CustomModule", decl[Color]) shouldBe expected
- }
-
- "for a single case class with complex types" in {
- val fileContent =
- i"""
- module CustomModule.ExternalReferences exposing (..)
-
- import Json.Decode as Decode
- import Json.Decode.Pipeline exposing (..)
- import Json.Encode as Encode
- import CustomModule.Color exposing (..)
- import CustomModule.Navigation exposing (..)
-
- type alias ExternalReferences = { color: Color, nav: Navigation }
-
-
- decoderExternalReferences : Decode.Decoder ExternalReferences
- decoderExternalReferences = Decode.succeed ExternalReferences |> required "color" (Decode.lazy (\\_ -> decoderColor)) |> required "nav" (Decode.lazy (\\_ -> decoderNavigation))
-
-
- encoderExternalReferences : ExternalReferences -> Encode.Value
- encoderExternalReferences obj = Encode.object [ ("color", encoderColor obj.color), ("nav", encoderNavigation obj.nav) ]
- """
- val expected = ("ExternalReferences.elm", fileContent)
-
- Elm.buildFile("CustomModule", decl[ExternalReferences]) shouldBe expected
- }
-
- "for a trait" in {
- val fileContent =
- i"""
- module CustomModule.Shape exposing (..)
-
- import Json.Decode as Decode
- import Json.Decode.Pipeline exposing (..)
- import Json.Encode as Encode
- import CustomModule.Color exposing (..)
-
- type Shape = Circle Float Color | Rectangle Float Float Color | ShapeGroup Shape Shape
-
-
- decoderShape : Decode.Decoder Shape
- decoderShape = Decode.field "type" Decode.string |> Decode.andThen decoderShapeTpe
-
- decoderShapeTpe : String -> Decode.Decoder Shape
- decoderShapeTpe tpe =
- case tpe of
- "Circle" -> Decode.succeed Circle |> required "radius" Decode.float |> required "color" (Decode.lazy (\\_ -> decoderColor))
- "Rectangle" -> Decode.succeed Rectangle |> required "width" Decode.float |> required "height" Decode.float |> required "color" (Decode.lazy (\\_ -> decoderColor))
- "ShapeGroup" -> Decode.succeed ShapeGroup |> required "leftShape" (Decode.lazy (\\_ -> decoderShape)) |> required "rightShape" (Decode.lazy (\\_ -> decoderShape))
- _ -> Decode.fail ("Unexpected type for Shape: " ++ tpe)
-
-
- encoderShape : Shape -> Encode.Value
- encoderShape tpe =
- case tpe of
- Circle radius color -> Encode.object [ ("radius", Encode.float radius), ("color", encoderColor color), ("type", Encode.string "Circle") ]
- Rectangle width height color -> Encode.object [ ("width", Encode.float width), ("height", Encode.float height), ("color", encoderColor color), ("type", Encode.string "Rectangle") ]
- ShapeGroup leftShape rightShape -> Encode.object [ ("leftShape", encoderShape leftShape), ("rightShape", encoderShape rightShape), ("type", Encode.string "ShapeGroup") ]
- """
- val expected = ("Shape.elm", fileContent)
-
- Elm.buildFile("CustomModule", decl[Shape]) shouldBe expected
- }
-
- "for a recursive trait" in {
- val fileContent =
- i"""
- module CustomModule.Navigation exposing (..)
-
- import Json.Decode as Decode
- import Json.Decode.Pipeline exposing (..)
- import Json.Encode as Encode
-
-
- type Navigation = Node String (List Navigation) | NodeList (List Navigation)
-
-
- decoderNavigation : Decode.Decoder Navigation
- decoderNavigation = Decode.field "type" Decode.string |> Decode.andThen decoderNavigationTpe
-
- decoderNavigationTpe : String -> Decode.Decoder Navigation
- decoderNavigationTpe tpe =
- case tpe of
- "Node" -> Decode.succeed Node |> required "name" Decode.string |> required "children" (Decode.list (Decode.lazy (\\_ -> decoderNavigation)))
- "NodeList" -> Decode.succeed NodeList |> required "all" (Decode.list (Decode.lazy (\\_ -> decoderNavigation)))
- _ -> Decode.fail ("Unexpected type for Navigation: " ++ tpe)
-
-
- encoderNavigation : Navigation -> Encode.Value
- encoderNavigation tpe =
- case tpe of
- Node name children -> Encode.object [ ("name", Encode.string name), ("children", Encode.list encoderNavigation children), ("type", Encode.string "Node") ]
- NodeList all -> Encode.object [ ("all", Encode.list encoderNavigation all), ("type", Encode.string "NodeList") ]
- """
- val expected = ("Navigation.elm", fileContent)
-
- Elm.buildFile("CustomModule", decl[Navigation]) shouldBe expected
- }
-
- "UUID" - {
- "without overrides" in {
- val fileContent =
- i"""
- module CustomModule2.ClassUUID exposing (..)
-
- import Json.Decode as Decode
- import Json.Decode.Pipeline exposing (..)
- import Json.Encode as Encode
- import CustomModule2.UUID exposing (..)
-
- type alias ClassUUID = { a: UUID }
-
-
- decoderClassUUID : Decode.Decoder ClassUUID
- decoderClassUUID = Decode.succeed ClassUUID |> required "a" (Decode.lazy (\\_ -> decoderUUID))
-
-
- encoderClassUUID : ClassUUID -> Encode.Value
- encoderClassUUID obj = Encode.object [ ("a", encoderUUID obj.a) ]
- """
- val expected = ("ClassUUID.elm", fileContent)
-
- Elm.buildFile("CustomModule2", decl[ClassUUID]) shouldBe expected
- }
-
- "with overrides" in {
- val customTypeReplacements: Map[Ref, TypeReplacement] = Map(
- Ref("UUID") -> TypeReplacement("Uuid", "import Uuid exposing (Uuid)", "Uuid.decoder", "Uuid.encode")
- )
-
- val fileContent =
- i"""
- module CustomModule2.ClassUUID exposing (..)
-
- import Json.Decode as Decode
- import Json.Decode.Pipeline exposing (..)
- import Json.Encode as Encode
- import Uuid exposing (Uuid)
-
- type alias ClassUUID = { a: Uuid }
-
-
- decoderClassUUID : Decode.Decoder ClassUUID
- decoderClassUUID = Decode.succeed ClassUUID |> required "a" Uuid.decoder
-
-
- encoderClassUUID : ClassUUID -> Encode.Value
- encoderClassUUID obj = Encode.object [ ("a", Uuid.encode obj.a) ]
- """
- val expected = ("ClassUUID.elm", fileContent)
-
- Elm.buildFile("CustomModule2", decl[ClassUUID], customTypeReplacements) shouldBe expected
- }
- }
-
- "objects only" in {
- val fileContent =
- i"""
- module CustomModule2.ObjectsOnly exposing (..)
-
- import Json.Decode as Decode
- import Json.Decode.Pipeline exposing (..)
- import Json.Encode as Encode
-
-
- type ObjectsOnly = ObjectOne | ObjectTwo
-
-
- decoderObjectsOnly : Decode.Decoder ObjectsOnly
- decoderObjectsOnly = Decode.field "type" Decode.string |> Decode.andThen decoderObjectsOnlyTpe
-
- decoderObjectsOnlyTpe : String -> Decode.Decoder ObjectsOnly
- decoderObjectsOnlyTpe tpe =
- case tpe of
- "ObjectOne" -> Decode.succeed ObjectOne
- "ObjectTwo" -> Decode.succeed ObjectTwo
- _ -> Decode.fail ("Unexpected type for ObjectsOnly: " ++ tpe)
-
-
- encoderObjectsOnly : ObjectsOnly -> Encode.Value
- encoderObjectsOnly tpe =
- case tpe of
- ObjectOne -> Encode.object [ ("type", Encode.string "ObjectOne") ]
- ObjectTwo -> Encode.object [ ("type", Encode.string "ObjectTwo") ]
- """
- val expected = ("ObjectsOnly.elm", fileContent)
-
- Elm.buildFile("CustomModule2", decl[ObjectsOnly]) shouldBe expected
- }
-
- "for several classes at once" - {
- "without overrides" in {
- val fileContent =
- i"""
- module CustomModule2.Color exposing (..)
-
- import Json.Decode as Decode
- import Json.Decode.Pipeline exposing (..)
- import Json.Encode as Encode
- import CustomModule2.UUID exposing (..)
-
- type alias Color = { red: Int, green: Int, blue: Int }
- type alias ClassUUID = { a: UUID }
-
-
- decoderColor : Decode.Decoder Color
- decoderColor = Decode.succeed Color |> required "red" Decode.int |> required "green" Decode.int |> required "blue" Decode.int
-
- decoderClassUUID : Decode.Decoder ClassUUID
- decoderClassUUID = Decode.succeed ClassUUID |> required "a" (Decode.lazy (\\_ -> decoderUUID))
-
-
- encoderColor : Color -> Encode.Value
- encoderColor obj = Encode.object [ ("red", Encode.int obj.red), ("green", Encode.int obj.green), ("blue", Encode.int obj.blue) ]
-
- encoderClassUUID : ClassUUID -> Encode.Value
- encoderClassUUID obj = Encode.object [ ("a", encoderUUID obj.a) ]
- """
-
- val expected = ("Color.elm", fileContent)
-
- Elm.buildFile(
- "CustomModule2",
- List(decl[Color], decl[ClassUUID]),
- Map.empty[Ref, TypeReplacement]
- ) shouldBe expected
- }
-
- "with custom mappings" in {
-
- val customTypeReplacements: Map[Ref, TypeReplacement] = Map(
- Ref("UUID") -> TypeReplacement("Uuid", "import Uuid exposing (Uuid)", "Uuid.decoder", "Uuid.encode")
- )
-
- val fileContent =
- i"""
- module CustomModule2.Color exposing (..)
-
- import Json.Decode as Decode
- import Json.Decode.Pipeline exposing (..)
- import Json.Encode as Encode
- import Uuid exposing (Uuid)
-
- type alias Color = { red: Int, green: Int, blue: Int }
- type alias ClassUUID = { a: Uuid }
-
-
- decoderColor : Decode.Decoder Color
- decoderColor = Decode.succeed Color |> required "red" Decode.int |> required "green" Decode.int |> required "blue" Decode.int
-
- decoderClassUUID : Decode.Decoder ClassUUID
- decoderClassUUID = Decode.succeed ClassUUID |> required "a" Uuid.decoder
-
-
- encoderColor : Color -> Encode.Value
- encoderColor obj = Encode.object [ ("red", Encode.int obj.red), ("green", Encode.int obj.green), ("blue", Encode.int obj.blue) ]
-
- encoderClassUUID : ClassUUID -> Encode.Value
- encoderClassUUID obj = Encode.object [ ("a", Uuid.encode obj.a) ]
- """
-
- val expected = ("Color.elm", fileContent)
-
- Elm.buildFile(
- "CustomModule2",
- List(decl[Color], decl[ClassUUID]),
- customTypeReplacements
- ) shouldBe expected
- }
- }
-
- "for mutually recursive classes" in {
- val fileContent =
- i"""
- module CustomModule2.TypeOne exposing (..)
-
- import Json.Decode as Decode
- import Json.Decode.Pipeline exposing (..)
- import Json.Encode as Encode
-
-
- type alias TypeOne = { name: String, values: (List TypeTwo) }
- type TypeTwo = OptionOne Int | OptionTwo TypeOne
-
-
- decoderTypeOne : Decode.Decoder TypeOne
- decoderTypeOne = Decode.succeed TypeOne |> required "name" Decode.string |> required "values" (Decode.list (Decode.lazy (\\_ -> decoderTypeTwo)))
-
- decoderTypeTwo : Decode.Decoder TypeTwo
- decoderTypeTwo = Decode.field "type" Decode.string |> Decode.andThen decoderTypeTwoTpe
-
- decoderTypeTwoTpe : String -> Decode.Decoder TypeTwo
- decoderTypeTwoTpe tpe =
- case tpe of
- "OptionOne" -> Decode.succeed OptionOne |> required "value" Decode.int
- "OptionTwo" -> Decode.succeed OptionTwo |> required "value" (Decode.lazy (\\_ -> decoderTypeOne))
- _ -> Decode.fail ("Unexpected type for TypeTwo: " ++ tpe)
-
-
- encoderTypeOne : TypeOne -> Encode.Value
- encoderTypeOne obj = Encode.object [ ("name", Encode.string obj.name), ("values", Encode.list encoderTypeTwo obj.values) ]
-
- encoderTypeTwo : TypeTwo -> Encode.Value
- encoderTypeTwo tpe =
- case tpe of
- OptionOne value -> Encode.object [ ("value", Encode.int value), ("type", Encode.string "OptionOne") ]
- OptionTwo value -> Encode.object [ ("value", encoderTypeOne value), ("type", Encode.string "OptionTwo") ]
- """
-
- val expected = ("TypeOne.elm", fileContent)
-
- Elm.buildFile(
- "CustomModule2",
- List(decl[TypeOne], decl[TypeTwo]),
- Map.empty[Ref, TypeReplacement]
- ) shouldBe expected
- }
-}
diff --git a/src/test/scala/bridges/elm/ElmJsonDecoderSpec.scala b/src/test/scala/bridges/elm/ElmJsonDecoderSpec.scala
deleted file mode 100644
index c063174..0000000
--- a/src/test/scala/bridges/elm/ElmJsonDecoderSpec.scala
+++ /dev/null
@@ -1,324 +0,0 @@
-package bridges.elm
-
-import bridges.SampleTypes._
-import bridges.core.Type._
-import bridges.core._
-import bridges.core.syntax._
-import org.scalatest._
-import unindent._
-import org.scalatest.freespec.AnyFreeSpec
-import org.scalatest.matchers.should.Matchers
-
-class ElmJsonDecoderSpec extends AnyFreeSpec with Matchers {
- "Color" in {
- Elm.decoder(decl[Color]) shouldBe {
- i"""
- decoderColor : Decode.Decoder Color
- decoderColor = Decode.succeed Color |> required "red" Decode.int |> required "green" Decode.int |> required "blue" Decode.int
- """
- }
- }
-
- "Circle" in {
- Elm.decoder(decl[Circle]) shouldBe {
- i"""
- decoderCircle : Decode.Decoder Circle
- decoderCircle = Decode.succeed Circle |> required "radius" Decode.float |> required "color" (Decode.lazy (\\_ -> decoderColor))
- """
- }
- }
-
- "Rectangle" in {
- Elm.decoder(decl[Rectangle]) shouldBe {
- i"""
- decoderRectangle : Decode.Decoder Rectangle
- decoderRectangle = Decode.succeed Rectangle |> required "width" Decode.float |> required "height" Decode.float |> required "color" (Decode.lazy (\\_ -> decoderColor))
- """
- }
- }
-
- "Shape" in {
- Elm.decoder(decl[Shape]) shouldBe {
- i"""
- decoderShape : Decode.Decoder Shape
- decoderShape = Decode.field "type" Decode.string |> Decode.andThen decoderShapeTpe
-
- decoderShapeTpe : String -> Decode.Decoder Shape
- decoderShapeTpe tpe =
- case tpe of
- "Circle" -> Decode.succeed Circle |> required "radius" Decode.float |> required "color" (Decode.lazy (\\_ -> decoderColor))
- "Rectangle" -> Decode.succeed Rectangle |> required "width" Decode.float |> required "height" Decode.float |> required "color" (Decode.lazy (\\_ -> decoderColor))
- "ShapeGroup" -> Decode.succeed ShapeGroup |> required "leftShape" (Decode.lazy (\\_ -> decoderShape)) |> required "rightShape" (Decode.lazy (\\_ -> decoderShape))
- _ -> Decode.fail ("Unexpected type for Shape: " ++ tpe)
- """
- }
- }
-
- "Alpha" in {
- Elm.decoder(decl[Alpha]) shouldBe {
- i"""
- decoderAlpha : Decode.Decoder Alpha
- decoderAlpha = Decode.succeed Alpha |> required "name" Decode.string |> required "char" Decode.string |> required "bool" Decode.bool
- """
- }
- }
-
- "ArrayClass" in {
- Elm.decoder(decl[ArrayClass]) shouldBe {
- i"""
- decoderArrayClass : Decode.Decoder ArrayClass
- decoderArrayClass = Decode.succeed ArrayClass |> required "aList" (Decode.list Decode.string) |> optional "optField" (Decode.maybe Decode.float) Nothing
- """
- }
- }
-
- "Numeric" in {
- Elm.decoder(decl[Numeric]) shouldBe {
- i"""
- decoderNumeric : Decode.Decoder Numeric
- decoderNumeric = Decode.succeed Numeric |> required "double" Decode.float |> required "float" Decode.float |> required "int" Decode.int
- """
- }
- }
-
- "ClassOrObject" in {
- Elm.decoder(decl[ClassOrObject]) shouldBe {
- i"""
- decoderClassOrObject : Decode.Decoder ClassOrObject
- decoderClassOrObject = Decode.field "type" Decode.string |> Decode.andThen decoderClassOrObjectTpe
-
- decoderClassOrObjectTpe : String -> Decode.Decoder ClassOrObject
- decoderClassOrObjectTpe tpe =
- case tpe of
- "MyClass" -> Decode.succeed MyClass |> required "value" Decode.int
- "MyObject" -> Decode.succeed MyObject
- _ -> Decode.fail ("Unexpected type for ClassOrObject: " ++ tpe)
- """
- }
- }
-
- "Navigation" in {
- Elm.decoder(decl[Navigation]) shouldBe {
- i"""
- decoderNavigation : Decode.Decoder Navigation
- decoderNavigation = Decode.field "type" Decode.string |> Decode.andThen decoderNavigationTpe
-
- decoderNavigationTpe : String -> Decode.Decoder Navigation
- decoderNavigationTpe tpe =
- case tpe of
- "Node" -> Decode.succeed Node |> required "name" Decode.string |> required "children" (Decode.list (Decode.lazy (\\_ -> decoderNavigation)))
- "NodeList" -> Decode.succeed NodeList |> required "all" (Decode.list (Decode.lazy (\\_ -> decoderNavigation)))
- _ -> Decode.fail ("Unexpected type for Navigation: " ++ tpe)
- """
- }
- }
-
- "ExternalReferences" in {
- Elm.decoder(decl[ExternalReferences]) shouldBe {
- i"""
- decoderExternalReferences : Decode.Decoder ExternalReferences
- decoderExternalReferences = Decode.succeed ExternalReferences |> required "color" (Decode.lazy (\\_ -> decoderColor)) |> required "nav" (Decode.lazy (\\_ -> decoderNavigation))
- """
- }
- }
-
- "TypeOne and TypeTwo" in {
- Elm.decoder(List(decl[TypeOne], decl[TypeTwo]), Map.empty[Ref, TypeReplacement]) shouldBe {
- i"""
- decoderTypeOne : Decode.Decoder TypeOne
- decoderTypeOne = Decode.succeed TypeOne |> required "name" Decode.string |> required "values" (Decode.list (Decode.lazy (\\_ -> decoderTypeTwo)))
-
- decoderTypeTwo : Decode.Decoder TypeTwo
- decoderTypeTwo = Decode.field "type" Decode.string |> Decode.andThen decoderTypeTwoTpe
-
- decoderTypeTwoTpe : String -> Decode.Decoder TypeTwo
- decoderTypeTwoTpe tpe =
- case tpe of
- "OptionOne" -> Decode.succeed OptionOne |> required "value" Decode.int
- "OptionTwo" -> Decode.succeed OptionTwo |> required "value" (Decode.lazy (\\_ -> decoderTypeOne))
- _ -> Decode.fail ("Unexpected type for TypeTwo: " ++ tpe)
- """
- }
- }
-
- "ClassUUID" - {
- "by default we treat UUID as a normal type we created" in {
- // this is the case when we don't import any Elm specific UUID library and we will create our own UUID type there
-
- Elm.decoder(decl[ClassUUID]) shouldBe {
- i"""
- decoderClassUUID : Decode.Decoder ClassUUID
- decoderClassUUID = Decode.succeed ClassUUID |> required "a" (Decode.lazy (\\_ -> decoderUUID))
- """
- }
- }
-
- "we can provide a map to substitute UUID decoding with a custom decoding logic" in {
- // this is the case when we import Elm specific UUID types we want to use in our decoder, but Scala can't know about them without extra hints
- val customTypeReplacements: Map[Ref, TypeReplacement] = Map(
- Ref("UUID") -> TypeReplacement("Uuid", "import Uuid exposing (Uuid)", "Uuid.decoder", "Uuid.encode")
- )
-
- Elm.decoder(decl[ClassUUID], customTypeReplacements) shouldBe {
- i"""
- decoderClassUUID : Decode.Decoder ClassUUID
- decoderClassUUID = Decode.succeed ClassUUID |> required "a" Uuid.decoder
- """
- }
- }
-
- "we can override the Encoder so we treat UUID as another basic type, like String, and Decode.succeed it accordingly" in {
- // probably not recommended, better to use a mapping as in other tests, but it is supported
- implicit val uuidEncoder: BasicEncoder[java.util.UUID] =
- Encoder.pure(Str)
-
- Elm.decoder(decl[ClassUUID]) shouldBe {
- i"""
- decoderClassUUID : Decode.Decoder ClassUUID
- decoderClassUUID = Decode.succeed ClassUUID |> required "a" Decode.string
- """
- }
- }
- }
-
- "ClassDate" - {
- "by default we treat Date as a normal type we created" in {
- // this is the case when we don't import any Elm specific Date library and we will create our own Date type there
-
- Elm.decoder(decl[ClassDate]) shouldBe {
- i"""
- decoderClassDate : Decode.Decoder ClassDate
- decoderClassDate = Decode.succeed ClassDate |> required "a" (Decode.lazy (\\_ -> decoderDate))
- """
- }
- }
-
- "we can provide a map to substitute Date decoding with a custom decoding logic" in {
- // this is the case when we import Elm specific UUID types we want to use in our decoder, but Scala can't know about them without extra hints
- val customTypeReplacements: Map[Ref, TypeReplacement] = Map(
- Ref("Date") -> TypeReplacement("Date", "import Date exposing (Date)", "Date.decoder", "Date.encode")
- )
-
- Elm.decoder(decl[ClassDate], customTypeReplacements) shouldBe {
- i"""
- decoderClassDate : Decode.Decoder ClassDate
- decoderClassDate = Decode.succeed ClassDate |> required "a" Date.decoder
- """
- }
- }
-
- "we can override the Encoder so we treat Date as another basic type, like String, and Decode.succeed it accordingly" in {
- // probably not recommended, better to use a mapping as in other tests, but it is supported
- implicit val dateEncoder: BasicEncoder[java.util.Date] =
- Encoder.pure(Str)
-
- Elm.decoder(decl[ClassDate]) shouldBe {
- i"""
- decoderClassDate : Decode.Decoder ClassDate
- decoderClassDate = Decode.succeed ClassDate |> required "a" Decode.string
- """
- }
- }
- }
-
- "Recursive" in {
- Elm.decoder(decl[Recursive]) shouldBe
- i"""
- decoderRecursive : Decode.Decoder Recursive
- decoderRecursive = Decode.succeed Recursive |> required "head" Decode.int |> optional "tail" (Decode.maybe (Decode.lazy (\\_ -> decoderRecursive))) Nothing
- """
- }
-
- "Recursive2" in {
- Elm.decoder(decl[Recursive2]) shouldBe {
- i"""
- decoderRecursive2 : Decode.Decoder Recursive2
- decoderRecursive2 = Decode.succeed Recursive2 |> required "head" Decode.int |> required "tail" (Decode.list (Decode.lazy (\\_ -> decoderRecursive2)))
- """
- }
- }
-
- "ObjectsOnly" in {
- Elm.decoder(decl[ObjectsOnly]) shouldBe {
- i"""
- decoderObjectsOnly : Decode.Decoder ObjectsOnly
- decoderObjectsOnly = Decode.field "type" Decode.string |> Decode.andThen decoderObjectsOnlyTpe
-
- decoderObjectsOnlyTpe : String -> Decode.Decoder ObjectsOnly
- decoderObjectsOnlyTpe tpe =
- case tpe of
- "ObjectOne" -> Decode.succeed ObjectOne
- "ObjectTwo" -> Decode.succeed ObjectTwo
- _ -> Decode.fail ("Unexpected type for ObjectsOnly: " ++ tpe)
- """
- }
- }
-
- "ClassWithGeneric" in {
- val productDef = prod("first" -> Ref("A"), "second" -> Ref("B"), "third" -> Ref("C"))
- val declaration = decl("ClassWithGeneric", "A", "B", "C")(productDef)
- Elm.decoder(declaration) shouldBe {
- i"""
- decoderClassWithGeneric : (Decode.Decoder a) -> (Decode.Decoder b) -> (Decode.Decoder c) -> Decode.Decoder (ClassWithGeneric a b c)
- decoderClassWithGeneric decoderA decoderB decoderC = Decode.succeed ClassWithGeneric |> required "first" (Decode.lazy (\\_ -> decoderA)) |> required "second" (Decode.lazy (\\_ -> decoderB)) |> required "third" (Decode.lazy (\\_ -> decoderC))
- """
- }
- }
-
- "ClassWithGeneric2" in {
- val productDef = prod("first" -> Ref("A"))
- val declaration = decl("ClassWithGeneric2", "A")(productDef)
- Elm.decoder(declaration) shouldBe {
- i"""
- decoderClassWithGeneric2 : (Decode.Decoder a) -> Decode.Decoder (ClassWithGeneric2 a)
- decoderClassWithGeneric2 decoderA = Decode.succeed ClassWithGeneric2 |> required "first" (Decode.lazy (\\_ -> decoderA))
- """
- }
- }
-
- "SumWithGeneric" in {
- val sumDef = sum(
- "First" -> prod("f" -> Ref("A")),
- "Second" -> prod("s" -> Ref("B")),
- "Third" -> prod("t" -> Ref("C"))
- )
- val declaration = decl("SumWithGeneric", "A", "B", "C")(sumDef)
- Elm.decoder(declaration) shouldBe {
- i"""
- decoderSumWithGeneric : (Decode.Decoder a) -> (Decode.Decoder b) -> (Decode.Decoder c) -> Decode.Decoder (SumWithGeneric a b c)
- decoderSumWithGeneric decoderA decoderB decoderC = Decode.field "type" Decode.string |> Decode.andThen decoderSumWithGenericTpe decoderA decoderB decoderC
-
- decoderSumWithGenericTpe : (Decode.Decoder a) -> (Decode.Decoder b) -> (Decode.Decoder c) -> String -> Decode.Decoder (SumWithGeneric a b c)
- decoderSumWithGenericTpe decoderA decoderB decoderC tpe =
- case tpe of
- "First" -> Decode.succeed First |> required "f" (Decode.lazy (\\_ -> decoderA))
- "Second" -> Decode.succeed Second |> required "s" (Decode.lazy (\\_ -> decoderB))
- "Third" -> Decode.succeed Third |> required "t" (Decode.lazy (\\_ -> decoderC))
- _ -> Decode.fail ("Unexpected type for SumWithGeneric: " ++ tpe)
- """
- }
- }
-
- "Numeric types" in {
- Elm.decoder(decl[NumericTypes]) shouldBe {
- i"""
- decoderNumericTypes : Decode.Decoder NumericTypes
- decoderNumericTypes = Decode.succeed NumericTypes |> required "int" Decode.int |> required "long" Decode.int |> required "float" Decode.float |> required "double" Decode.float |> required "bigDecimal" Decode.float
- """
- }
- }
-
- "Dictionary types" in {
- Elm.decoder(decl[Map[String, Int]]) shouldBe {
- i"""
- decoderMap : Decode.Decoder Map
- decoderMap = Decode.succeed Map (Decode.dict Decode.int)
- """
- }
-
- // The Elm standard library only provides JSON decoders for dictionaries with string keys:
- intercept[IllegalArgumentException] {
- Elm.decoder(decl[Map[Int, Int]])
- }
- }
-}
diff --git a/src/test/scala/bridges/elm/ElmJsonEncoderSpec.scala b/src/test/scala/bridges/elm/ElmJsonEncoderSpec.scala
deleted file mode 100644
index 5ab499c..0000000
--- a/src/test/scala/bridges/elm/ElmJsonEncoderSpec.scala
+++ /dev/null
@@ -1,295 +0,0 @@
-package bridges.elm
-
-import bridges.SampleTypes._
-import bridges.core._
-import bridges.core.Type._
-import bridges.core.syntax._
-import org.scalatest._
-import unindent._
-import org.scalatest.freespec.AnyFreeSpec
-import org.scalatest.matchers.should.Matchers
-
-class ElmJsonEncoderSpec extends AnyFreeSpec with Matchers {
- "Color" in {
- Elm.encoder(decl[Color]) shouldBe {
- i"""
- encoderColor : Color -> Encode.Value
- encoderColor obj = Encode.object [ ("red", Encode.int obj.red), ("green", Encode.int obj.green), ("blue", Encode.int obj.blue) ]
- """
- }
- }
-
- "Circle" in {
- Elm.encoder(decl[Circle]) shouldBe {
- i"""
- encoderCircle : Circle -> Encode.Value
- encoderCircle obj = Encode.object [ ("radius", Encode.float obj.radius), ("color", encoderColor obj.color) ]
- """
- }
- }
-
- "Rectangle" in {
- Elm.encoder(decl[Rectangle]) shouldBe {
- i"""
- encoderRectangle : Rectangle -> Encode.Value
- encoderRectangle obj = Encode.object [ ("width", Encode.float obj.width), ("height", Encode.float obj.height), ("color", encoderColor obj.color) ]
- """
- }
- }
-
- "Shape" in {
- Elm.encoder(decl[Shape]) shouldBe {
- i"""
- encoderShape : Shape -> Encode.Value
- encoderShape tpe =
- case tpe of
- Circle radius color -> Encode.object [ ("radius", Encode.float radius), ("color", encoderColor color), ("type", Encode.string "Circle") ]
- Rectangle width height color -> Encode.object [ ("width", Encode.float width), ("height", Encode.float height), ("color", encoderColor color), ("type", Encode.string "Rectangle") ]
- ShapeGroup leftShape rightShape -> Encode.object [ ("leftShape", encoderShape leftShape), ("rightShape", encoderShape rightShape), ("type", Encode.string "ShapeGroup") ]
- """
- }
- }
-
- "Alpha" in {
- Elm.encoder(decl[Alpha]) shouldBe {
- i"""
- encoderAlpha : Alpha -> Encode.Value
- encoderAlpha obj = Encode.object [ ("name", Encode.string obj.name), ("char", Encode.string obj.char), ("bool", Encode.bool obj.bool) ]
- """
- }
- }
-
- "ArrayClass" in {
- Elm.encoder(decl[ArrayClass]) shouldBe {
- i"""
- encoderArrayClass : ArrayClass -> Encode.Value
- encoderArrayClass obj = Encode.object [ ("aList", Encode.list Encode.string obj.aList), ("optField", Maybe.withDefault Encode.null (Maybe.map Encode.float obj.optField)) ]
- """
- }
- }
-
- "Numeric" in {
- Elm.encoder(decl[Numeric]) shouldBe {
- i"""
- encoderNumeric : Numeric -> Encode.Value
- encoderNumeric obj = Encode.object [ ("double", Encode.float obj.double), ("float", Encode.float obj.float), ("int", Encode.int obj.int) ]
- """
- }
- }
-
- "ClassOrObject" in {
- Elm.encoder(decl[ClassOrObject]) shouldBe
- i"""
- encoderClassOrObject : ClassOrObject -> Encode.Value
- encoderClassOrObject tpe =
- case tpe of
- MyClass value -> Encode.object [ ("value", Encode.int value), ("type", Encode.string "MyClass") ]
- MyObject -> Encode.object [ ("type", Encode.string "MyObject") ]
- """
- }
-
- "Navigation" in {
- Elm.encoder(decl[Navigation]) shouldBe {
- i"""
- encoderNavigation : Navigation -> Encode.Value
- encoderNavigation tpe =
- case tpe of
- Node name children -> Encode.object [ ("name", Encode.string name), ("children", Encode.list encoderNavigation children), ("type", Encode.string "Node") ]
- NodeList all -> Encode.object [ ("all", Encode.list encoderNavigation all), ("type", Encode.string "NodeList") ]
- """
- }
- }
-
- "ExternalReferences" in {
- Elm.encoder(decl[ExternalReferences]) shouldBe {
- i"""
- encoderExternalReferences : ExternalReferences -> Encode.Value
- encoderExternalReferences obj = Encode.object [ ("color", encoderColor obj.color), ("nav", encoderNavigation obj.nav) ]
- """
- }
- }
-
- "TypeOne and TypeTwo" in {
- Elm.encoder(List(decl[TypeOne], decl[TypeTwo]), Map.empty[Ref, TypeReplacement]) shouldBe {
- i"""
- encoderTypeOne : TypeOne -> Encode.Value
- encoderTypeOne obj = Encode.object [ ("name", Encode.string obj.name), ("values", Encode.list encoderTypeTwo obj.values) ]
-
- encoderTypeTwo : TypeTwo -> Encode.Value
- encoderTypeTwo tpe =
- case tpe of
- OptionOne value -> Encode.object [ ("value", Encode.int value), ("type", Encode.string "OptionOne") ]
- OptionTwo value -> Encode.object [ ("value", encoderTypeOne value), ("type", Encode.string "OptionTwo") ]
- """
- }
- }
-
- "ClassUUID" - {
- "by default we treat UUID as a normal type we created" in {
- // this is the case when we don't import any Elm specific UUID library and we will create our own UUID type there
-
- Elm.encoder(decl[ClassUUID]) shouldBe {
- i"""
- encoderClassUUID : ClassUUID -> Encode.Value
- encoderClassUUID obj = Encode.object [ ("a", encoderUUID obj.a) ]
- """
- }
- }
-
- "we can provide a map to substitute UUID decoding with a custom encoding logic" in {
- // this is the case when we import Elm specific UUID types we want to use in our decoder, but Scala can't know about them without extra hints
- val customTypeReplacements: Map[Ref, TypeReplacement] = Map(
- Ref("UUID") -> TypeReplacement("Uuid", "import Uuid exposing (Uuid)", "Uuid.decoder", "Uuid.encode")
- )
-
- Elm.encoder(decl[ClassUUID], customTypeReplacements) shouldBe {
- i"""
- encoderClassUUID : ClassUUID -> Encode.Value
- encoderClassUUID obj = Encode.object [ ("a", Uuid.encode obj.a) ]
- """
- }
- }
-
- "we can override the Encoder so we treat UUID as another basic type, like String, and encode it accordingly" in {
- // probably not recommended, better to use a mapping as in other tests, but it is supported
- implicit val uuidEncoder: BasicEncoder[java.util.UUID] =
- Encoder.pure(Str)
-
- Elm.encoder(decl[ClassUUID]) shouldBe {
- i"""
- encoderClassUUID : ClassUUID -> Encode.Value
- encoderClassUUID obj = Encode.object [ ("a", Encode.string obj.a) ]
- """
- }
- }
- }
-
- "ClassDate" - {
- "by default we treat Date as a normal type we created" in {
- // this is the case when we don't import any Elm specific Date library and we will create our own Date type there
-
- Elm.encoder(decl[ClassDate]) shouldBe {
- i"""
- encoderClassDate : ClassDate -> Encode.Value
- encoderClassDate obj = Encode.object [ ("a", encoderDate obj.a) ]
- """
- }
- }
-
- "we can provide a map to substitute Date decoding with a custom encoding logic" in {
- // this is the case when we import Elm specific Date types we want to use in our decoder, but Scala can't know about them without extra hints
- val customTypeReplacements: Map[Ref, TypeReplacement] = Map(
- Ref("Date") -> TypeReplacement("Date", "import Date exposing (Date)", "Date.decoder", "Date.encode")
- )
-
- Elm.encoder(decl[ClassDate], customTypeReplacements) shouldBe {
- i"""
- encoderClassDate : ClassDate -> Encode.Value
- encoderClassDate obj = Encode.object [ ("a", Date.encode obj.a) ]
- """
- }
- }
-
- "we can override the Encoder so we treat Date as another basic type, like String, and encode it accordingly" in {
- // probably not recommended, better to use a mapping as in other tests, but it is supported
- implicit val dateEncoder: BasicEncoder[java.util.Date] =
- Encoder.pure(Str)
-
- Elm.encoder(decl[ClassDate]) shouldBe {
- i"""
- encoderClassDate : ClassDate -> Encode.Value
- encoderClassDate obj = Encode.object [ ("a", Encode.string obj.a) ]
- """
- }
- }
- }
-
- "Recursive" in {
- Elm.encoder(decl[Recursive]) shouldBe {
- i"""
- encoderRecursive : Recursive -> Encode.Value
- encoderRecursive obj = Encode.object [ ("head", Encode.int obj.head), ("tail", Maybe.withDefault Encode.null (Maybe.map encoderRecursive obj.tail)) ]
- """
- }
- }
-
- "Recursive2" in {
- Elm.encoder(decl[Recursive2]) shouldBe {
- i"""
- encoderRecursive2 : Recursive2 -> Encode.Value
- encoderRecursive2 obj = Encode.object [ ("head", Encode.int obj.head), ("tail", Encode.list encoderRecursive2 obj.tail) ]
- """
- }
- }
-
- "ObjectsOnly" in {
- Elm.encoder(decl[ObjectsOnly]) shouldBe {
- i"""
- encoderObjectsOnly : ObjectsOnly -> Encode.Value
- encoderObjectsOnly tpe =
- case tpe of
- ObjectOne -> Encode.object [ ("type", Encode.string "ObjectOne") ]
- ObjectTwo -> Encode.object [ ("type", Encode.string "ObjectTwo") ]
- """
- }
- }
-
- "ClassWithGeneric" in {
- val productDef = prod("first" -> Ref("A"), "second" -> Ref("B"), "third" -> Ref("C"))
- val declaration = decl("ClassWithGeneric", "A", "B", "C")(productDef)
- Elm.encoder(declaration) shouldBe {
- i"""
- encoderClassWithGeneric : (a -> Encode.Value) -> (b -> Encode.Value) -> (c -> Encode.Value) -> ClassWithGeneric a b c -> Encode.Value
- encoderClassWithGeneric encoderA encoderB encoderC obj = Encode.object [ ("first", encoderA obj.first), ("second", encoderB obj.second), ("third", encoderC obj.third) ]
- """
- }
- }
-
- "ClassWithGeneric2" in {
- val productDef = prod("first" -> Ref("A"))
- val declaration = decl("ClassWithGeneric2", "A")(productDef)
- Elm.encoder(declaration) shouldBe {
- i"""
- encoderClassWithGeneric2 : (a -> Encode.Value) -> ClassWithGeneric2 a -> Encode.Value
- encoderClassWithGeneric2 encoderA obj = Encode.object [ ("first", encoderA obj.first) ]
- """
- }
- }
-
- "SumWithGeneric" in {
- val sumDef = sum(
- "First" -> prod("f" -> Ref("A")),
- "Second" -> prod("s" -> Ref("B")),
- "Third" -> prod("t" -> Ref("C"))
- )
- val declaration = decl("SumWithGeneric", "A", "B", "C")(sumDef)
- Elm.encoder(declaration) shouldBe {
- i"""
- encoderSumWithGeneric : (a -> Encode.Value) -> (b -> Encode.Value) -> (c -> Encode.Value) -> SumWithGeneric a b c -> Encode.Value
- encoderSumWithGeneric encoderA encoderB encoderC tpe =
- case tpe of
- First f -> Encode.object [ ("f", encoderA f), ("type", Encode.string "First") ]
- Second s -> Encode.object [ ("s", encoderB s), ("type", Encode.string "Second") ]
- Third t -> Encode.object [ ("t", encoderC t), ("type", Encode.string "Third") ]
- """
- }
- }
-
- "Numeric types" in {
- Elm.encoder(decl[NumericTypes]) shouldBe {
- i"""
- encoderNumericTypes : NumericTypes -> Encode.Value
- encoderNumericTypes obj = Encode.object [ ("int", Encode.int obj.int), ("long", Encode.int obj.long), ("float", Encode.float obj.float), ("double", Encode.float obj.double), ("bigDecimal", Encode.float obj.bigDecimal) ]
- """
- }
- }
-
- "Dictionary types" in {
- Elm.encoder(decl[Map[String, Int]]) shouldBe {
- i"""
- encoderMap : Map -> Encode.Value
- encoderMap obj = (Encode.dict Encode.string Map Encode.int Map)
- """
- }
- }
-}
diff --git a/src/test/scala/bridges/elm/ElmRendererSpec.scala b/src/test/scala/bridges/elm/ElmRendererSpec.scala
deleted file mode 100644
index b9dd841..0000000
--- a/src/test/scala/bridges/elm/ElmRendererSpec.scala
+++ /dev/null
@@ -1,153 +0,0 @@
-package bridges.elm
-
-import bridges.SampleTypes._
-import bridges.core.Type._
-import bridges.core._
-import bridges.core.syntax._
-import org.scalatest.freespec.AnyFreeSpec
-import org.scalatest.matchers.should.Matchers
-
-class ElmRendererSpec extends AnyFreeSpec with Matchers {
- "Color" in {
- Elm.render(decl[Color]) shouldBe "type alias Color = { red: Int, green: Int, blue: Int }"
- }
-
- "Circle" in {
- Elm.render(decl[Circle]) shouldBe "type alias Circle = { radius: Float, color: Color }"
- }
-
- "Rectangle" in {
- Elm.render(decl[Rectangle]) shouldBe "type alias Rectangle = { width: Float, height: Float, color: Color }"
- }
-
- "Shape" in {
- Elm.render(decl[Shape]) shouldBe """type Shape = Circle Float Color | Rectangle Float Float Color | ShapeGroup Shape Shape"""
- }
-
- "Alpha" in {
- Elm.render(decl[Alpha]) shouldBe "type alias Alpha = { name: String, char: Char, bool: Bool }"
- }
-
- "ArrayClass" in {
- Elm.render(decl[ArrayClass]) shouldBe """type alias ArrayClass = { aList: (List String), optField: (Maybe Float) }"""
- }
-
- "Numeric" in {
- Elm.render(decl[Numeric]) shouldBe """type alias Numeric = { double: Float, float: Float, int: Int }"""
- }
-
- "ClassOrObject" in {
- Elm.render(decl[ClassOrObject]) shouldBe """type ClassOrObject = MyClass Int | MyObject"""
- }
-
- "NestedClassOrObject" in {
- Elm.render(decl[NestedClassOrObject]) shouldBe """type NestedClassOrObject = MyClass Int | MyObject"""
- }
-
- "Navigation" in {
- Elm.render(decl[Navigation]) shouldBe """type Navigation = Node String (List Navigation) | NodeList (List Navigation)"""
- }
-
- "TypeOne and TypeTwo" in {
- Elm.render(decl[TypeOne]) shouldBe """type alias TypeOne = { name: String, values: (List TypeTwo) }"""
- Elm.render(decl[TypeTwo]) shouldBe """type TypeTwo = OptionOne Int | OptionTwo TypeOne"""
- }
-
- "Recursive" in {
- Elm.render(decl[Recursive]) shouldBe """type alias Recursive = { head: Int, tail: (Maybe Recursive) }"""
- }
-
- "Recursive2" in {
- Elm.render(decl[Recursive2]) shouldBe """type alias Recursive2 = { head: Int, tail: (List Recursive2) }"""
- }
-
- "ExternalReferences" in {
- Elm.render(decl[ExternalReferences]) shouldBe """type alias ExternalReferences = { color: Color, nav: Navigation }"""
- }
-
- "ClassUUID" - {
- "Without any specific override" in {
- Elm.render(decl[ClassUUID]) shouldBe """type alias ClassUUID = { a: UUID }"""
- }
-
- "providing a type override map" in {
- val customTypeReplacements: Map[Ref, TypeReplacement] = Map(
- Ref("UUID") -> TypeReplacement("Uuid", "import Uuid exposing (Uuid)", "Uuid.decoder", "Uuid.encode")
- )
-
- Elm.render(decl[ClassUUID], customTypeReplacements) shouldBe """type alias ClassUUID = { a: Uuid }"""
- }
-
- "using override to treat UUID as a String" in {
- implicit val uuidEncoder: BasicEncoder[java.util.UUID] =
- Encoder.pure(Str)
-
- Elm.render(decl[ClassUUID]) shouldBe """type alias ClassUUID = { a: String }"""
- }
- }
-
- "ClassDate" - {
- "Without any specific override" in {
- Elm.render(decl[ClassDate]) shouldBe """type alias ClassDate = { a: Date }"""
- }
-
- "providing a type override map" in {
- val customTypeReplacements: Map[Ref, TypeReplacement] = Map(
- Ref("Date") -> TypeReplacement("Date", "import Date exposing (Date)", "Date.decoder", "Date.encode")
- )
-
- Elm.render(decl[ClassDate], customTypeReplacements) shouldBe """type alias ClassDate = { a: Date }"""
- }
-
- "using override to treat Date as a String" in {
- implicit val dateEncoder: BasicEncoder[java.util.Date] =
- Encoder.pure(Str)
-
- Elm.render(decl[ClassDate]) shouldBe """type alias ClassDate = { a: String }"""
- }
- }
-
- "ObjectsOnly" in {
- Elm.render(decl[ObjectsOnly]) shouldBe """type ObjectsOnly = ObjectOne | ObjectTwo"""
- }
-
- "ClassWithRefinedType" in {
- // import eu.timepit.refined.shapeless.typeable._
-
- Elm.render(decl[ClassWithRefinedType]) shouldBe """type alias ClassWithRefinedType = { name: String }"""
- }
-
- "ClassWithGeneric" in {
- val productDef = prod("first" -> Ref("A"), "second" -> Ref("B"), "third" -> Ref("C"))
- val declaration = decl("ClassWithGeneric", "A", "B", "C")(productDef)
- Elm.render(declaration) shouldBe """type alias ClassWithGeneric a b c = { first: a, second: b, third: c }"""
- }
-
- "ClassWithGeneric2" in {
- val productDef = prod("first" -> Ref("A"))
- val declaration = decl("ClassWithGeneric2", "A")(productDef)
- Elm.render(declaration) shouldBe """type alias ClassWithGeneric2 a = { first: a }"""
- }
-
- "SumWithGeneric" in {
- val sumDef = sum(
- "First" -> prod("f" -> Ref("A")),
- "Second" -> prod("s" -> Ref("B")),
- "Third" -> prod("t" -> Ref("C"))
- )
- val declaration = decl("SumWithGeneric", "A", "B", "C")(sumDef)
- Elm.render(declaration) shouldBe """type SumWithGeneric a b c = First a | Second b | Third c"""
- }
-
- "Numeric types" in {
- Elm.render(decl[NumericTypes]) shouldBe {
- """type alias NumericTypes = { int: Int, long: Int, float: Float, double: Float, bigDecimal: Float }"""
- }
- }
-
- "Dictionary types" in {
- Elm.render(decl[Map[String, Int]]) shouldBe {
- """type alias Map = (Dict String Int)"""
- }
- }
-}
diff --git a/src/test/scala/bridges/flow/FlowRenameSpec.scala b/src/test/scala/bridges/flow/FlowRenameSpec.scala
deleted file mode 100644
index f667411..0000000
--- a/src/test/scala/bridges/flow/FlowRenameSpec.scala
+++ /dev/null
@@ -1,21 +0,0 @@
-package bridges.flow
-
-import bridges.SampleTypes._
-import bridges.flow.FlowType._
-import bridges.flow.syntax._
-import org.scalatest._
-import org.scalatest.freespec.AnyFreeSpec
-import org.scalatest.matchers.should.Matchers
-
-class FlowRenameSpec extends AnyFreeSpec with Matchers {
- "decl" in {
- val actual = decl[Color].rename("red", "r")
- val expected = "Color" := struct(
- "r" --> Intr,
- "green" --> Intr,
- "blue" --> Intr
- )
-
- actual shouldBe expected
- }
-}
diff --git a/src/test/scala/bridges/flow/FlowRendererSpec.scala b/src/test/scala/bridges/flow/FlowRendererSpec.scala
deleted file mode 100644
index 8312094..0000000
--- a/src/test/scala/bridges/flow/FlowRendererSpec.scala
+++ /dev/null
@@ -1,233 +0,0 @@
-package bridges.flow
-
-import bridges.SampleTypes._
-import bridges.core.DeclF
-import bridges.flow.FlowType._
-import bridges.flow.syntax._
-import org.scalatest._
-import unindent._
-import org.scalatest.freespec.AnyFreeSpec
-import org.scalatest.matchers.should.Matchers
-
-class FlowRendererSpec extends AnyFreeSpec with Matchers {
- "Color" in {
- Flow.render(decl[Color]) shouldBe {
- i"""
- export type Color = { red: number, green: number, blue: number };
- """
- }
- }
-
- "Circle" in {
- Flow.render(decl[Circle]) shouldBe {
- i"""
- export type Circle = { radius: number, color: Color };
- """
- }
- }
-
- "Rectangle" in {
- Flow.render(decl[Rectangle]) shouldBe {
- i"""
- export type Rectangle = { width: number, height: number, color: Color };
- """
- }
- }
-
- "Shape" in {
- Flow.render(decl[Shape]) shouldBe {
- i"""
- export type Shape = { type: "Circle", radius: number, color: Color } | { type: "Rectangle", width: number, height: number, color: Color } | { type: "ShapeGroup", leftShape: Shape, rightShape: Shape };
- """
- }
- }
-
- "Alpha" in {
- Flow.render(decl[Alpha]) shouldBe {
- i"""
- export type Alpha = { name: string, char: string, bool: boolean };
- """
- }
- }
-
- "ArrayClass" in {
- Flow.render(decl[ArrayClass]) shouldBe {
- i"""
- export type ArrayClass = { aList: string[], optField: ?number };
- """
- }
- }
-
- "Numeric" in {
- Flow.render(decl[Numeric]) shouldBe {
- i"""
- export type Numeric = { double: number, float: number, int: number };
- """
- }
- }
-
- "ClassOrObject" in {
- Flow.render(decl[ClassOrObject]) shouldBe {
- i"""
- export type ClassOrObject = { type: "MyClass", value: number } | { type: "MyObject" };
- """
- }
- }
-
- "NestedClassOrObject" in {
- Flow.render(decl[NestedClassOrObject]) shouldBe {
- i"""
- export type NestedClassOrObject = { type: "MyClass", value: number } | { type: "MyObject" };
- """
- }
- }
-
- "Navigation" in {
- Flow.render(decl[Navigation]) shouldBe {
- i"""
- export type Navigation = { type: "Node", name: string, children: Navigation[] } | { type: "NodeList", all: Navigation[] };
- """
- }
- }
-
- "ClassUUID" in {
- Flow.render(decl[ClassUUID]) shouldBe {
- i"""
- export type ClassUUID = { a: UUID };
- """
- }
- }
-
- "ClassDate" in {
- Flow.render(decl[ClassDate]) shouldBe {
- i"""
- export type ClassDate = { a: Date };
- """
- }
- }
-
- "ExternalReferences" in {
- Flow.render(decl[ExternalReferences]) shouldBe {
- i"""
- export type ExternalReferences = { color: Color, nav: Navigation };
- """
- }
- }
-
- "Recursive" in {
- Flow.render(decl[Recursive]) shouldBe {
- i"""
- export type Recursive = { head: number, tail: ?Recursive };
- """
- }
- }
-
- "Recursive2" in {
- Flow.render(decl[Recursive2]) shouldBe {
- i"""
- export type Recursive2 = { head: number, tail: Recursive2[] };
- """
- }
- }
-
- "ObjectsOnly" in {
- Flow.render(decl[ObjectsOnly]) shouldBe {
- i"""
- export type ObjectsOnly = { type: "ObjectOne" } | { type: "ObjectTwo" };
- """
- }
- }
-
- "Optional of Array" in {
- Flow.render("Foo" := Arr(Str).?) shouldBe {
- i"""
- export type Foo = ?string[];
- """
- }
- }
-
- "Array of Optional" in {
- Flow.render("Foo" := Arr(Str.?)) shouldBe {
- i"""
- export type Foo = (?string)[];
- """
- }
- }
-
- "Union of Union" in {
- Flow.render("A" := Ref("B") | Ref("C") | Ref("D")) shouldBe {
- i"""
- export type A = B | C | D;
- """
- }
- }
-
- "Inter of Inter" in {
- Flow.render("A" := Ref("B") & Ref("C") & Ref("D")) shouldBe {
- i"""
- export type A = B & C & D;
- """
- }
- }
-
- "Generic Decl" in {
- Flow.render(decl("Pair", "A", "B")(struct("a" --> Ref("A"), "b" --> Ref("B")))) shouldBe {
- i"""
- export type Pair = { a: A, b: B };
- """
- }
- }
-
- "Generic Ref" in {
- Flow.render(decl("Numbers")(ref("Pair", Real, Real))) shouldBe {
- i"""
- export type Numbers = Pair;
- """
- }
- }
-
- "Numeric types" in {
- Flow.render(decl[NumericTypes]) shouldBe {
- i"""
- export type NumericTypes = { int: number, long: number, float: number, double: number, bigDecimal: number };
- """
- }
- }
-
- "Tuple" in {
- Flow.render(decl("Cell")(tuple(Str, Intr))) shouldBe {
- i"""
- export type Cell = [string, number];
- """
- }
- }
-
- "Empty tuple" in {
- Flow.render(decl("Empty")(tuple())) shouldBe {
- i"""
- export type Empty = [];
- """
- }
- }
-
- "Structs with rest fields" in {
- Flow.render(decl("Dict")(dict(Str, Intr))) shouldBe {
- i"""
- export type Dict = { [key: string]: number };
- """
- }
-
- Flow.render(
- decl("Dict")(
- struct(
- "a" --> Str,
- "b" -?> Intr
- ).withRest(Str, Bool, "c")
- )
- ) shouldBe {
- i"""
- export type Dict = { a: string, b?: number, [c: string]: boolean };
- """
- }
- }
-}
diff --git a/src/test/scala/bridges/SampleTypes.scala b/src/test/scala/bridges/typescript/SampleTypes.scala
similarity index 80%
rename from src/test/scala/bridges/SampleTypes.scala
rename to src/test/scala/bridges/typescript/SampleTypes.scala
index b3e03dc..b8c4491 100644
--- a/src/test/scala/bridges/SampleTypes.scala
+++ b/src/test/scala/bridges/typescript/SampleTypes.scala
@@ -1,20 +1,9 @@
-package bridges
+package bridges.typescript
import java.util.{ Date, UUID }
-import bridges.core._
-import bridges.core.Type._
-import bridges.core.syntax._
-import eu.timepit.refined.api.Refined
-import eu.timepit.refined.collection.Size
-import eu.timepit.refined.generic.Equal
-import eu.timepit.refined.numeric.Greater
-import eu.timepit.refined.numeric.Interval.ClosedOpen
-
object SampleTypes {
- type RefinedString = String Refined Size[ClosedOpen[1, 100]]
- type RefinedInt = Int Refined Greater[6]
- type RefinedChar = Char Refined Equal['3']
+ import syntax.*
// Sample product
case class Pair(a: String, b: Int)
@@ -54,8 +43,8 @@ object SampleTypes {
// Recursive structure
sealed trait Navigation
- final case class NodeList(all: List[Navigation]) extends Navigation
final case class Node(name: String, children: List[Navigation]) extends Navigation
+ final case class NodeList(all: List[Navigation]) extends Navigation
// case classes with specific values (list, Float, Option, Char, etc)
final case class Alpha(name: String, char: Char, bool: Boolean)
@@ -82,13 +71,14 @@ object SampleTypes {
// Custom declaration of a intermediate structure
val customDeclaration: Decl =
- "Message" := sum(
- "ErrorMessage" -> prod("error" -> Ref("ErrorMessage")),
- "WarningMessage" -> prod("warning" -> Ref("WarningMessage"))
+ Decl(
+ "Message",
+ TsType.discriminated(
+ "ErrorMessage" -> TsType.struct("error" --> TsType.Ref("ErrorMessage")),
+ "WarningMessage" -> TsType.struct("warning" --> TsType.Ref("WarningMessage"))
+ )
)
- final case class ClassWithRefinedType(name: RefinedString)
-
final case class NumericTypes(
int: Int,
long: Long,
diff --git a/src/test/scala/bridges/typescript/TsEncoderSpec.scala b/src/test/scala/bridges/typescript/TsEncoderSpec.scala
index 58d747e..6fed232 100644
--- a/src/test/scala/bridges/typescript/TsEncoderSpec.scala
+++ b/src/test/scala/bridges/typescript/TsEncoderSpec.scala
@@ -1,82 +1,80 @@
-package bridges.typescript
-
-import bridges.SampleTypes._
-import bridges.core.DeclF
-import bridges.typescript.TsType._
-import bridges.typescript.syntax._
-import org.scalatest._
-import unindent._
-import org.scalatest.freespec.AnyFreeSpec
-import org.scalatest.matchers.should.Matchers
-
-class TsEncoderSpec extends AnyFreeSpec with Matchers {
- "TsEncoderConfig.optionalFields" - {
- "render optional fields by default" in {
- decl[Recursive] shouldBe {
- decl("Recursive")(
- struct(
- "head" --> Intr,
- "tail" -?> union(ref("Recursive"), Null)
- )
- )
- }
- }
-
- "override setting" in {
- implicit val config: TsEncoderConfig =
- TsEncoderConfig(optionalFields = false)
-
- decl[Recursive] shouldBe {
- decl("Recursive")(
- struct(
- "head" --> Intr,
- "tail" --> union(ref("Recursive"), Null)
- )
- )
- }
- }
- }
-
- "TsEncoderConfig.refsUnions" - {
- "render type names in unions by default" in {
- decl[OneOrOther] shouldBe {
- decl("OneOrOther")(
- struct(
- "type" --> StrLit("One"),
- "value" --> Str
- ) |
- struct(
- "type" --> StrLit("Other"),
- "value" --> Intr
- )
- )
- }
- }
-
- "override setting" in {
- implicit val config: TsEncoderConfig =
- TsEncoderConfig(refsInUnions = true)
-
- decl[OneOrOther] shouldBe {
- decl("OneOrOther")(
- (struct("type" --> StrLit("One")) & ref("One")) |
- (struct("type" --> StrLit("Other")) & ref("Other"))
- )
- }
- }
- }
-
- "Numeric types" in {
- decl[NumericTypes] shouldBe {
- decl("NumericTypes")(
- struct(
- "int" --> Intr,
- "long" --> Intr,
- "float" --> Real,
- "double" --> Real,
- "bigDecimal" --> Real
- )
- )
- }
- }
-}
+// package bridges.typescript
+//
+// import bridges.SampleTypes._
+// import bridges.typescript.TsType._
+// import org.scalatest._
+// import unindent._
+// import org.scalatest.freespec.AnyFreeSpec
+// import org.scalatest.matchers.should.Matchers
+//
+// class TsEncoderSpec extends AnyFreeSpec with Matchers {
+// "TsEncoderConfig.optionalFields" - {
+// "render optional fields by default" in {
+// decl[Recursive] shouldBe {
+// decl("Recursive")(
+// struct(
+// "head" --> Intr,
+// "tail" -?> union(ref("Recursive"), Null)
+// )
+// )
+// }
+// }
+//
+// "override setting" in {
+// implicit val config: TsEncoderConfig =
+// TsEncoderConfig(optionalFields = false)
+//
+// decl[Recursive] shouldBe {
+// decl("Recursive")(
+// struct(
+// "head" --> Intr,
+// "tail" --> union(ref("Recursive"), Null)
+// )
+// )
+// }
+// }
+// }
+//
+// "TsEncoderConfig.refsUnions" - {
+// "render type names in unions by default" in {
+// decl[OneOrOther] shouldBe {
+// decl("OneOrOther")(
+// struct(
+// "type" --> StrLit("One"),
+// "value" --> Str
+// ) |
+// struct(
+// "type" --> StrLit("Other"),
+// "value" --> Intr
+// )
+// )
+// }
+// }
+//
+// "override setting" in {
+// implicit val config: TsEncoderConfig =
+// TsEncoderConfig(refsInUnions = true)
+//
+// decl[OneOrOther] shouldBe {
+// decl("OneOrOther")(
+// (struct("type" --> StrLit("One")) & ref("One")) |
+// (struct("type" --> StrLit("Other")) & ref("Other"))
+// )
+// }
+// }
+// }
+//
+// "Numeric types" in {
+// decl[NumericTypes] shouldBe {
+// decl("NumericTypes")(
+// struct(
+// "int" --> Intr,
+// "long" --> Intr,
+// "float" --> Real,
+// "double" --> Real,
+// "bigDecimal" --> Real
+// )
+// )
+// }
+// }
+// }
diff --git a/src/test/scala/bridges/typescript/TsGuardRendererSpec.scala b/src/test/scala/bridges/typescript/TsGuardRendererSpec.scala
index 95b782f..4228806 100644
--- a/src/test/scala/bridges/typescript/TsGuardRendererSpec.scala
+++ b/src/test/scala/bridges/typescript/TsGuardRendererSpec.scala
@@ -1,322 +1,321 @@
-package bridges.typescript
-
-import bridges.SampleTypes._
-import bridges.typescript.TsType._
-import bridges.typescript.syntax._
-import org.scalatest._
-import unindent._
-import org.scalatest.freespec.AnyFreeSpec
-import org.scalatest.matchers.should.Matchers
-
-class TsGuardRendererSpec extends AnyFreeSpec with Matchers {
- "Color" in {
- TypescriptGuard.render(decl[Color]) shouldBe {
- i"""
- export const isColor = (v: any): v is Color => {
- return typeof v === "object" && v != null && "red" in v && typeof v.red === "number" && "green" in v && typeof v.green === "number" && "blue" in v && typeof v.blue === "number";
- }
- """
- }
- }
-
- "Circle" in {
- TypescriptGuard.render(decl[Circle]) shouldBe {
- i"""
- export const isCircle = (v: any): v is Circle => {
- return typeof v === "object" && v != null && "radius" in v && typeof v.radius === "number" && "color" in v && isColor(v.color);
- }
- """
- }
- }
-
- "Rectangle" in {
- TypescriptGuard.render(decl[Rectangle]) shouldBe {
- i"""
- export const isRectangle = (v: any): v is Rectangle => {
- return typeof v === "object" && v != null && "width" in v && typeof v.width === "number" && "height" in v && typeof v.height === "number" && "color" in v && isColor(v.color);
- }
- """
- }
- }
-
- "Shape" in {
- TypescriptGuard.render(decl[Shape]) shouldBe {
- i"""
- export const isShape = (v: any): v is Shape => {
- return typeof v === "object" && v != null && "type" in v && (v.type === "Circle" ? typeof v === "object" && v != null && "radius" in v && typeof v.radius === "number" && "color" in v && isColor(v.color) : v.type === "Rectangle" ? typeof v === "object" && v != null && "width" in v && typeof v.width === "number" && "height" in v && typeof v.height === "number" && "color" in v && isColor(v.color) : v.type === "ShapeGroup" ? typeof v === "object" && v != null && "leftShape" in v && isShape(v.leftShape) && "rightShape" in v && isShape(v.rightShape) : false);
- }
- """
- }
- }
-
- "Alpha" in {
- TypescriptGuard.render(decl[Alpha]) shouldBe {
- i"""
- export const isAlpha = (v: any): v is Alpha => {
- return typeof v === "object" && v != null && "name" in v && typeof v.name === "string" && "char" in v && typeof v.char === "string" && "bool" in v && typeof v.bool === "boolean";
- }
- """
- }
- }
-
- "ArrayClass" in {
- TypescriptGuard.render(decl[ArrayClass]) shouldBe {
- i"""
- export const isArrayClass = (v: any): v is ArrayClass => {
- return typeof v === "object" && v != null && "aList" in v && Array.isArray(v.aList) && v.aList.every((i: any) => typeof i === "string") && (!("optField" in v) || typeof v.optField === "number" || v.optField === null);
- }
- """
- }
- }
-
- "Numeric" in {
- TypescriptGuard.render(decl[Numeric]) shouldBe {
- i"""
- export const isNumeric = (v: any): v is Numeric => {
- return typeof v === "object" && v != null && "double" in v && typeof v.double === "number" && "float" in v && typeof v.float === "number" && "int" in v && typeof v.int === "number";
- }
- """
- }
- }
-
- "ClassOrObject" in {
- TypescriptGuard.render(decl[ClassOrObject]) shouldBe {
- i"""
- export const isClassOrObject = (v: any): v is ClassOrObject => {
- return typeof v === "object" && v != null && "type" in v && (v.type === "MyClass" ? typeof v === "object" && v != null && "value" in v && typeof v.value === "number" : v.type === "MyObject" ? typeof v === "object" && v != null : false);
- }
- """
- }
- }
-
- "NestedClassOrObject" in {
- TypescriptGuard.render(decl[NestedClassOrObject]) shouldBe {
- i"""
- export const isNestedClassOrObject = (v: any): v is NestedClassOrObject => {
- return typeof v === "object" && v != null && "type" in v && (v.type === "MyClass" ? typeof v === "object" && v != null && "value" in v && typeof v.value === "number" : v.type === "MyObject" ? typeof v === "object" && v != null : false);
- }
- """
- }
- }
-
- "Navigation" in {
- TypescriptGuard.render(decl[Navigation]) shouldBe {
- i"""
- export const isNavigation = (v: any): v is Navigation => {
- return typeof v === "object" && v != null && "type" in v && (v.type === "Node" ? typeof v === "object" && v != null && "name" in v && typeof v.name === "string" && "children" in v && Array.isArray(v.children) && v.children.every((i: any) => isNavigation(i)) : v.type === "NodeList" ? typeof v === "object" && v != null && "all" in v && Array.isArray(v.all) && v.all.every((i: any) => isNavigation(i)) : false);
- }
- """
- }
- }
-
- "ClassUUID" in {
- TypescriptGuard.render(decl[ClassUUID]) shouldBe {
- i"""
- export const isClassUUID = (v: any): v is ClassUUID => {
- return typeof v === "object" && v != null && "a" in v && isUUID(v.a);
- }
- """
- }
- }
-
- "ClassDate" in {
- TypescriptGuard.render(decl[ClassDate]) shouldBe {
- i"""
- export const isClassDate = (v: any): v is ClassDate => {
- return typeof v === "object" && v != null && "a" in v && isDate(v.a);
- }
- """
- }
- }
-
- "Recursive" in {
- TypescriptGuard.render(decl[Recursive]) shouldBe {
- i"""
- export const isRecursive = (v: any): v is Recursive => {
- return typeof v === "object" && v != null && "head" in v && typeof v.head === "number" && (!("tail" in v) || isRecursive(v.tail) || v.tail === null);
- }
- """
- }
- }
-
- "Recursive2" in {
- TypescriptGuard.render(decl[Recursive2]) shouldBe {
- i"""
- export const isRecursive2 = (v: any): v is Recursive2 => {
- return typeof v === "object" && v != null && "head" in v && typeof v.head === "number" && "tail" in v && Array.isArray(v.tail) && v.tail.every((i: any) => isRecursive2(i));
- }
- """
- }
- }
-
- "ExternalReferences" in {
- TypescriptGuard.render(decl[ExternalReferences]) shouldBe {
- i"""
- export const isExternalReferences = (v: any): v is ExternalReferences => {
- return typeof v === "object" && v != null && "color" in v && isColor(v.color) && "nav" in v && isNavigation(v.nav);
- }
- """
- }
- }
-
- "ObjectsOnly" in {
- TypescriptGuard.render(decl[ObjectsOnly]) shouldBe {
- i"""
- export const isObjectsOnly = (v: any): v is ObjectsOnly => {
- return typeof v === "object" && v != null && "type" in v && (v.type === "ObjectOne" ? typeof v === "object" && v != null : v.type === "ObjectTwo" ? typeof v === "object" && v != null : false);
- }
- """
- }
- }
-
- "Union of Union" in {
- TypescriptGuard.render("A" := Ref("B") | Ref("C") | Ref("D")) shouldBe {
- i"""
- export const isA = (v: any): v is A => {
- return isB(v) || isC(v) || isD(v);
- }
- """
- }
- }
-
- "Inter of Inter" in {
- TypescriptGuard.render("A" := Ref("B") & Ref("C") & Ref("D")) shouldBe {
- i"""
- export const isA = (v: any): v is A => {
- return isB(v) && isC(v) && isD(v);
- }
- """
- }
- }
-
- "Generic Decl" in {
- TypescriptGuard.render(
- decl("Pair", "A", "B")(
- struct(
- "a" --> Ref("A"),
- "b" -?> Ref("B")
- )
- )
- ) shouldBe {
- i"""
- export const isPair = (isA: (a: any) => a is A, isB: (b: any) => b is B) => (v: any): v is Pair => {
- return typeof v === "object" && v != null && "a" in v && isA(v.a) && (!("b" in v) || isB(v.b));
- }
- """
- }
- }
-
- "Applications of Generics" in {
- TypescriptGuard.render(decl("Cell")(ref("Pair", Str, Intr))) shouldBe {
- i"""
- export const isCell = (v: any): v is Cell => {
- return isPair((a0: any): a0 is string => typeof a0 === "string", (a1: any): a1 is number => typeof a1 === "number")(v);
- }
- """
- }
-
- TypescriptGuard.render(decl("Same", "A")(ref("Pair", ref("A"), ref("A")))) shouldBe {
- i"""
- export const isSame = (isA: (a: any) => a is A) => (v: any): v is Same => {
- return isPair((a0: any): a0 is A => isA(a0), (a1: any): a1 is A => isA(a1))(v);
- }
- """
- }
-
- TypescriptGuard.render(decl("AnyPair")(ref("Pair", Any, Any))) shouldBe {
- i"""
- export const isAnyPair = (v: any): v is AnyPair => {
- return isPair((a0: any): a0 is any => true, (a1: any): a1 is any => true)(v);
- }
- """
- }
- }
-
- "Numeric types" in {
- TypescriptGuard.render(decl[NumericTypes]) shouldBe {
- i"""
- export const isNumericTypes = (v: any): v is NumericTypes => {
- return typeof v === "object" && v != null && "int" in v && typeof v.int === "number" && "long" in v && typeof v.long === "number" && "float" in v && typeof v.float === "number" && "double" in v && typeof v.double === "number" && "bigDecimal" in v && typeof v.bigDecimal === "number";
- }
- """
- }
- }
-
- "Tuple" in {
- TypescriptGuard.render(decl("Cell")(tuple(Str, Intr))) shouldBe {
- i"""
- export const isCell = (v: any): v is Cell => {
- return Array.isArray(v) && v.length === 2 && typeof v[0] === "string" && typeof v[1] === "number";
- }
- """
- }
- }
-
- "Empty tuple" in {
- TypescriptGuard.render(decl("Empty")(tuple())) shouldBe {
- i"""
- export const isEmpty = (v: any): v is Empty => {
- return Array.isArray(v) && v.length === 0;
- }
- """
- }
- }
-
- "Structs with rest fields" in {
- TypescriptGuard.render(decl("Dict")(dict(Str, Intr))) shouldBe {
- i"""
- export const isDict = (v: any): v is Dict => {
- return typeof v === "object" && v != null && Object.keys(v).every((k: any) => typeof k === "string" && typeof v[k] === "number");
- }
- """
- }
-
- TypescriptGuard.render(
- decl("Dict")(
- struct(
- "a" --> Str,
- "b" -?> Intr
- ).withRest(Str, Bool, "c")
- )
- ) shouldBe {
- i"""
- export const isDict = (v: any): v is Dict => {
- return typeof v === "object" && v != null && "a" in v && typeof v.a === "string" && (!("b" in v) || typeof v.b === "number") && Object.keys(v).every((k: any) => ["a", "b"].includes(k) || typeof k === "string" && typeof v[k] === "boolean");
- }
- """
- }
- }
-
- "Function types" in {
- TypescriptGuard.render(
- decl("Rule")(
- struct(
- "message" --> Str,
- "apply" --> func("value" -> Unknown)(Bool)
- )
- )
- ) shouldBe {
- i"""
- export const isRule = (v: any): v is Rule => {
- return typeof v === "object" && v != null && "message" in v && typeof v.message === "string" && "apply" in v && typeof v.apply === "function";
- }
- """
- }
-
- TypescriptGuard.render(
- decl("Funcy")(
- tuple(
- func("arg" -> tuple(Str))(tuple(Str)),
- func("arg" -> tuple(Intr))(tuple(Intr))
- )
- )
- ) shouldBe {
- i"""
- export const isFuncy = (v: any): v is Funcy => {
- return Array.isArray(v) && v.length === 2 && typeof v[0] === "function" && typeof v[1] === "function";
- }
- """
- }
- }
-
-}
+// package bridges.typescript
+//
+// import bridges.SampleTypes._
+// import bridges.typescript.TsType._
+// import org.scalatest._
+// import unindent._
+// import org.scalatest.freespec.AnyFreeSpec
+// import org.scalatest.matchers.should.Matchers
+//
+// class TsGuardRendererSpec extends AnyFreeSpec with Matchers {
+// "Color" in {
+// TypescriptGuard.render(decl[Color]) shouldBe {
+// i"""
+// export const isColor = (v: any): v is Color => {
+// return typeof v === "object" && v != null && "red" in v && typeof v.red === "number" && "green" in v && typeof v.green === "number" && "blue" in v && typeof v.blue === "number";
+// }
+// """
+// }
+// }
+//
+// "Circle" in {
+// TypescriptGuard.render(decl[Circle]) shouldBe {
+// i"""
+// export const isCircle = (v: any): v is Circle => {
+// return typeof v === "object" && v != null && "radius" in v && typeof v.radius === "number" && "color" in v && isColor(v.color);
+// }
+// """
+// }
+// }
+//
+// "Rectangle" in {
+// TypescriptGuard.render(decl[Rectangle]) shouldBe {
+// i"""
+// export const isRectangle = (v: any): v is Rectangle => {
+// return typeof v === "object" && v != null && "width" in v && typeof v.width === "number" && "height" in v && typeof v.height === "number" && "color" in v && isColor(v.color);
+// }
+// """
+// }
+// }
+//
+// "Shape" in {
+// TypescriptGuard.render(decl[Shape]) shouldBe {
+// i"""
+// export const isShape = (v: any): v is Shape => {
+// return typeof v === "object" && v != null && "type" in v && (v.type === "Circle" ? typeof v === "object" && v != null && "radius" in v && typeof v.radius === "number" && "color" in v && isColor(v.color) : v.type === "Rectangle" ? typeof v === "object" && v != null && "width" in v && typeof v.width === "number" && "height" in v && typeof v.height === "number" && "color" in v && isColor(v.color) : v.type === "ShapeGroup" ? typeof v === "object" && v != null && "leftShape" in v && isShape(v.leftShape) && "rightShape" in v && isShape(v.rightShape) : false);
+// }
+// """
+// }
+// }
+//
+// "Alpha" in {
+// TypescriptGuard.render(decl[Alpha]) shouldBe {
+// i"""
+// export const isAlpha = (v: any): v is Alpha => {
+// return typeof v === "object" && v != null && "name" in v && typeof v.name === "string" && "char" in v && typeof v.char === "string" && "bool" in v && typeof v.bool === "boolean";
+// }
+// """
+// }
+// }
+//
+// "ArrayClass" in {
+// TypescriptGuard.render(decl[ArrayClass]) shouldBe {
+// i"""
+// export const isArrayClass = (v: any): v is ArrayClass => {
+// return typeof v === "object" && v != null && "aList" in v && Array.isArray(v.aList) && v.aList.every((i: any) => typeof i === "string") && (!("optField" in v) || typeof v.optField === "number" || v.optField === null);
+// }
+// """
+// }
+// }
+//
+// "Numeric" in {
+// TypescriptGuard.render(decl[Numeric]) shouldBe {
+// i"""
+// export const isNumeric = (v: any): v is Numeric => {
+// return typeof v === "object" && v != null && "double" in v && typeof v.double === "number" && "float" in v && typeof v.float === "number" && "int" in v && typeof v.int === "number";
+// }
+// """
+// }
+// }
+//
+// "ClassOrObject" in {
+// TypescriptGuard.render(decl[ClassOrObject]) shouldBe {
+// i"""
+// export const isClassOrObject = (v: any): v is ClassOrObject => {
+// return typeof v === "object" && v != null && "type" in v && (v.type === "MyClass" ? typeof v === "object" && v != null && "value" in v && typeof v.value === "number" : v.type === "MyObject" ? typeof v === "object" && v != null : false);
+// }
+// """
+// }
+// }
+//
+// "NestedClassOrObject" in {
+// TypescriptGuard.render(decl[NestedClassOrObject]) shouldBe {
+// i"""
+// export const isNestedClassOrObject = (v: any): v is NestedClassOrObject => {
+// return typeof v === "object" && v != null && "type" in v && (v.type === "MyClass" ? typeof v === "object" && v != null && "value" in v && typeof v.value === "number" : v.type === "MyObject" ? typeof v === "object" && v != null : false);
+// }
+// """
+// }
+// }
+//
+// "Navigation" in {
+// TypescriptGuard.render(decl[Navigation]) shouldBe {
+// i"""
+// export const isNavigation = (v: any): v is Navigation => {
+// return typeof v === "object" && v != null && "type" in v && (v.type === "Node" ? typeof v === "object" && v != null && "name" in v && typeof v.name === "string" && "children" in v && Array.isArray(v.children) && v.children.every((i: any) => isNavigation(i)) : v.type === "NodeList" ? typeof v === "object" && v != null && "all" in v && Array.isArray(v.all) && v.all.every((i: any) => isNavigation(i)) : false);
+// }
+// """
+// }
+// }
+//
+// "ClassUUID" in {
+// TypescriptGuard.render(decl[ClassUUID]) shouldBe {
+// i"""
+// export const isClassUUID = (v: any): v is ClassUUID => {
+// return typeof v === "object" && v != null && "a" in v && isUUID(v.a);
+// }
+// """
+// }
+// }
+//
+// "ClassDate" in {
+// TypescriptGuard.render(decl[ClassDate]) shouldBe {
+// i"""
+// export const isClassDate = (v: any): v is ClassDate => {
+// return typeof v === "object" && v != null && "a" in v && isDate(v.a);
+// }
+// """
+// }
+// }
+//
+// "Recursive" in {
+// TypescriptGuard.render(decl[Recursive]) shouldBe {
+// i"""
+// export const isRecursive = (v: any): v is Recursive => {
+// return typeof v === "object" && v != null && "head" in v && typeof v.head === "number" && (!("tail" in v) || isRecursive(v.tail) || v.tail === null);
+// }
+// """
+// }
+// }
+//
+// "Recursive2" in {
+// TypescriptGuard.render(decl[Recursive2]) shouldBe {
+// i"""
+// export const isRecursive2 = (v: any): v is Recursive2 => {
+// return typeof v === "object" && v != null && "head" in v && typeof v.head === "number" && "tail" in v && Array.isArray(v.tail) && v.tail.every((i: any) => isRecursive2(i));
+// }
+// """
+// }
+// }
+//
+// "ExternalReferences" in {
+// TypescriptGuard.render(decl[ExternalReferences]) shouldBe {
+// i"""
+// export const isExternalReferences = (v: any): v is ExternalReferences => {
+// return typeof v === "object" && v != null && "color" in v && isColor(v.color) && "nav" in v && isNavigation(v.nav);
+// }
+// """
+// }
+// }
+//
+// "ObjectsOnly" in {
+// TypescriptGuard.render(decl[ObjectsOnly]) shouldBe {
+// i"""
+// export const isObjectsOnly = (v: any): v is ObjectsOnly => {
+// return typeof v === "object" && v != null && "type" in v && (v.type === "ObjectOne" ? typeof v === "object" && v != null : v.type === "ObjectTwo" ? typeof v === "object" && v != null : false);
+// }
+// """
+// }
+// }
+//
+// "Union of Union" in {
+// TypescriptGuard.render("A" := Ref("B") union Ref("C") union Ref("D")) shouldBe {
+// i"""
+// export const isA = (v: any): v is A => {
+// return isB(v) || isC(v) || isD(v);
+// }
+// """
+// }
+// }
+//
+// "Inter of Inter" in {
+// TypescriptGuard.render("A" := Ref("B") intersect Ref("C") intersect Ref("D")) shouldBe {
+// i"""
+// export const isA = (v: any): v is A => {
+// return isB(v) && isC(v) && isD(v);
+// }
+// """
+// }
+// }
+//
+// "Generic Decl" in {
+// TypescriptGuard.render(
+// decl("Pair", "A", "B")(
+// struct(
+// "a" --> Ref("A"),
+// "b" -?> Ref("B")
+// )
+// )
+// ) shouldBe {
+// i"""
+// export const isPair = (isA: (a: any) => a is A, isB: (b: any) => b is B) => (v: any): v is Pair => {
+// return typeof v === "object" && v != null && "a" in v && isA(v.a) && (!("b" in v) || isB(v.b));
+// }
+// """
+// }
+// }
+//
+// "Applications of Generics" in {
+// TypescriptGuard.render(decl("Cell")(ref("Pair", Str, Intr))) shouldBe {
+// i"""
+// export const isCell = (v: any): v is Cell => {
+// return isPair((a0: any): a0 is string => typeof a0 === "string", (a1: any): a1 is number => typeof a1 === "number")(v);
+// }
+// """
+// }
+//
+// TypescriptGuard.render(decl("Same", "A")(ref("Pair", ref("A"), ref("A")))) shouldBe {
+// i"""
+// export const isSame = (isA: (a: any) => a is A) => (v: any): v is Same => {
+// return isPair((a0: any): a0 is A => isA(a0), (a1: any): a1 is A => isA(a1))(v);
+// }
+// """
+// }
+//
+// TypescriptGuard.render(decl("AnyPair")(ref("Pair", Any, Any))) shouldBe {
+// i"""
+// export const isAnyPair = (v: any): v is AnyPair => {
+// return isPair((a0: any): a0 is any => true, (a1: any): a1 is any => true)(v);
+// }
+// """
+// }
+// }
+//
+// "Numeric types" in {
+// TypescriptGuard.render(decl[NumericTypes]) shouldBe {
+// i"""
+// export const isNumericTypes = (v: any): v is NumericTypes => {
+// return typeof v === "object" && v != null && "int" in v && typeof v.int === "number" && "long" in v && typeof v.long === "number" && "float" in v && typeof v.float === "number" && "double" in v && typeof v.double === "number" && "bigDecimal" in v && typeof v.bigDecimal === "number";
+// }
+// """
+// }
+// }
+//
+// "Tuple" in {
+// TypescriptGuard.render(decl("Cell")(tuple(Str, Intr))) shouldBe {
+// i"""
+// export const isCell = (v: any): v is Cell => {
+// return Array.isArray(v) && v.length === 2 && typeof v[0] === "string" && typeof v[1] === "number";
+// }
+// """
+// }
+// }
+//
+// "Empty tuple" in {
+// TypescriptGuard.render(decl("Empty")(tuple())) shouldBe {
+// i"""
+// export const isEmpty = (v: any): v is Empty => {
+// return Array.isArray(v) && v.length === 0;
+// }
+// """
+// }
+// }
+//
+// "Structs with rest fields" in {
+// TypescriptGuard.render(decl("Dict")(dict(Str, Intr))) shouldBe {
+// i"""
+// export const isDict = (v: any): v is Dict => {
+// return typeof v === "object" && v != null && Object.keys(v).every((k: any) => typeof k === "string" && typeof v[k] === "number");
+// }
+// """
+// }
+//
+// TypescriptGuard.render(
+// decl("Dict")(
+// struct(
+// "a" --> Str,
+// "b" -?> Intr
+// ).withRest(Str, Bool, "c")
+// )
+// ) shouldBe {
+// i"""
+// export const isDict = (v: any): v is Dict => {
+// return typeof v === "object" && v != null && "a" in v && typeof v.a === "string" && (!("b" in v) || typeof v.b === "number") && Object.keys(v).every((k: any) => ["a", "b"].includes(k) || typeof k === "string" && typeof v[k] === "boolean");
+// }
+// """
+// }
+// }
+//
+// "Function types" in {
+// TypescriptGuard.render(
+// decl("Rule")(
+// struct(
+// "message" --> Str,
+// "apply" --> func("value" -> Unknown)(Bool)
+// )
+// )
+// ) shouldBe {
+// i"""
+// export const isRule = (v: any): v is Rule => {
+// return typeof v === "object" && v != null && "message" in v && typeof v.message === "string" && "apply" in v && typeof v.apply === "function";
+// }
+// """
+// }
+//
+// TypescriptGuard.render(
+// decl("Funcy")(
+// tuple(
+// func("arg" -> tuple(Str))(tuple(Str)),
+// func("arg" -> tuple(Intr))(tuple(Intr))
+// )
+// )
+// ) shouldBe {
+// i"""
+// export const isFuncy = (v: any): v is Funcy => {
+// return Array.isArray(v) && v.length === 2 && typeof v[0] === "function" && typeof v[1] === "function";
+// }
+// """
+// }
+// }
+//
+// }
diff --git a/src/test/scala/bridges/typescript/TsRenameSpec.scala b/src/test/scala/bridges/typescript/TsRenameSpec.scala
index 9043455..38a09f9 100644
--- a/src/test/scala/bridges/typescript/TsRenameSpec.scala
+++ b/src/test/scala/bridges/typescript/TsRenameSpec.scala
@@ -1,22 +1,21 @@
-package bridges.typescript
-
-import bridges.SampleTypes._
-import bridges.typescript.TsType._
-import bridges.typescript.syntax._
-import org.scalatest._
-import org.scalatest.freespec.AnyFreeSpec
-import org.scalatest.matchers.should.Matchers
-
-class TsRenameSpec extends AnyFreeSpec with Matchers {
- "decl" in {
- val actual = decl[Color].rename("red", "r")
-
- val expected = "Color" := struct(
- "r" --> Intr,
- "green" --> Intr,
- "blue" --> Intr
- )
-
- actual shouldBe expected
- }
-}
+// package bridges.typescript
+//
+// import bridges.SampleTypes._
+// import bridges.typescript.TsType._
+// import org.scalatest._
+// import org.scalatest.freespec.AnyFreeSpec
+// import org.scalatest.matchers.should.Matchers
+//
+// class TsRenameSpec extends AnyFreeSpec with Matchers {
+// "decl" in {
+// val actual = decl[Color].rename("red", "r")
+//
+// val expected = "Color" := struct(
+// "r" --> Intr,
+// "green" --> Intr,
+// "blue" --> Intr
+// )
+//
+// actual shouldBe expected
+// }
+// }
diff --git a/src/test/scala/bridges/typescript/TsTypeRendererSpec.scala b/src/test/scala/bridges/typescript/TsTypeRendererSpec.scala
index 3b90329..ff034e6 100644
--- a/src/test/scala/bridges/typescript/TsTypeRendererSpec.scala
+++ b/src/test/scala/bridges/typescript/TsTypeRendererSpec.scala
@@ -1,17 +1,16 @@
package bridges.typescript
-import bridges.SampleTypes._
-import bridges.core.DeclF
-import bridges.typescript.TsType._
-import bridges.typescript.syntax._
-import org.scalatest._
-import unindent._
-import org.scalatest.freespec.AnyFreeSpec
-import org.scalatest.matchers.should.Matchers
-
-class TsTypeRendererSpec extends AnyFreeSpec with Matchers {
- "Color" in {
- Typescript.render(decl[Color]) shouldBe {
+import munit.FunSuite
+import unindent.*
+
+class TsTypeRendererSpec extends FunSuite:
+ import SampleTypes.*
+ import TsType.*
+ import syntax.*
+
+ test("Color") {
+ assertEquals(
+ Typescript.render(decl[Color]),
i"""
export interface Color {
red: number;
@@ -19,22 +18,24 @@ class TsTypeRendererSpec extends AnyFreeSpec with Matchers {
blue: number;
}
"""
- }
+ )
}
- "Circle" in {
- Typescript.render(decl[Circle]) shouldBe {
+ test("Circle") {
+ assertEquals(
+ Typescript.render(decl[Circle]),
i"""
export interface Circle {
radius: number;
color: Color;
}
"""
- }
+ )
}
- "Rectangle" in {
- Typescript.render(decl[Rectangle]) shouldBe {
+ test("Rectangle") {
+ assertEquals(
+ Typescript.render(decl[Rectangle]),
i"""
export interface Rectangle {
width: number;
@@ -42,19 +43,21 @@ class TsTypeRendererSpec extends AnyFreeSpec with Matchers {
color: Color;
}
"""
- }
+ )
}
- "Shape" in {
- Typescript.render(decl[Shape]) shouldBe {
+ test("Shape") {
+ assertEquals(
+ Typescript.render(decl[Shape]),
i"""
export type Shape = { type: "Circle", radius: number, color: Color } | { type: "Rectangle", width: number, height: number, color: Color } | { type: "ShapeGroup", leftShape: Shape, rightShape: Shape };
"""
- }
+ )
}
- "Alpha" in {
- Typescript.render(decl[Alpha]) shouldBe {
+ test("Alpha") {
+ assertEquals(
+ Typescript.render(decl[Alpha]),
i"""
export interface Alpha {
name: string;
@@ -62,22 +65,24 @@ class TsTypeRendererSpec extends AnyFreeSpec with Matchers {
bool: boolean;
}
"""
- }
+ )
}
- "ArrayClass" in {
- Typescript.render(decl[ArrayClass]) shouldBe {
+ test("ArrayClass") {
+ assertEquals(
+ Typescript.render(decl[ArrayClass]),
i"""
export interface ArrayClass {
aList: string[];
optField?: number | null;
}
"""
- }
+ )
}
- "Numeric" in {
- Typescript.render(decl[Numeric]) shouldBe {
+ test("Numeric") {
+ assertEquals(
+ Typescript.render(decl[Numeric]),
i"""
export interface Numeric {
double: number;
@@ -85,159 +90,176 @@ class TsTypeRendererSpec extends AnyFreeSpec with Matchers {
int: number;
}
"""
- }
+ )
}
- "Singleton object" in {
- Typescript.render(decl[MyObject.type]) shouldBe {
+ test("Singleton object") {
+ assertEquals(
+ Typescript.render(decl[MyObject.type]),
i"""
export interface MyObject {
}
"""
- }
+ )
}
- "ClassOrObject" in {
- Typescript.render(decl[ClassOrObject]) shouldBe {
+ test("ClassOrObject") {
+ assertEquals(
+ Typescript.render(decl[ClassOrObject]),
i"""
export type ClassOrObject = { type: "MyClass", value: number } | { type: "MyObject" };
"""
- }
+ )
}
- "NestedClassOrObject" in {
- Typescript.render(decl[NestedClassOrObject]) shouldBe {
+ test("NestedClassOrObject") {
+ assertEquals(
+ Typescript.render(decl[NestedClassOrObject]),
i"""
export type NestedClassOrObject = { type: "MyClass", value: number } | { type: "MyObject" };
"""
- }
+ )
}
- "Navigation" in {
- Typescript.render(decl[Navigation]) shouldBe {
+ test("Navigation") {
+ assertEquals(
+ Typescript.render(decl[Navigation]),
i"""
export type Navigation = { type: "Node", name: string, children: Navigation[] } | { type: "NodeList", all: Navigation[] };
"""
- }
+ )
}
- "ClassUUID" in {
- Typescript.render(decl[ClassUUID]) shouldBe {
+ test("ClassUUID") {
+ assertEquals(
+ Typescript.render(decl[ClassUUID]),
i"""
export interface ClassUUID {
a: UUID;
}
"""
- }
+ )
}
- "ClassDate" in {
- Typescript.render(decl[ClassDate]) shouldBe {
+ test("ClassDate") {
+ assertEquals(
+ Typescript.render(decl[ClassDate]),
i"""
export interface ClassDate {
a: Date;
}
"""
- }
+ )
}
- "Recursive" in {
- Typescript.render(decl[Recursive]) shouldBe {
+ test("Recursive") {
+ assertEquals(
+ Typescript.render(decl[Recursive]),
i"""
export interface Recursive {
head: number;
tail?: Recursive | null;
}
"""
- }
+ )
}
- "Recursive2" in {
- Typescript.render(decl[Recursive2]) shouldBe {
+ test("Recursive2") {
+ assertEquals(
+ Typescript.render(decl[Recursive2]),
i"""
export interface Recursive2 {
head: number;
tail: Recursive2[];
}
"""
- }
+ )
}
- "ExternalReferences" in {
- Typescript.render(decl[ExternalReferences]) shouldBe {
+ test("ExternalReferences") {
+ assertEquals(
+ Typescript.render(decl[ExternalReferences]),
i"""
export interface ExternalReferences {
color: Color;
nav: Navigation;
}
"""
- }
+ )
}
- "ObjectsOnly" in {
- Typescript.render(decl[ObjectsOnly]) shouldBe {
+ test("ObjectsOnly") {
+ assertEquals(
+ Typescript.render(decl[ObjectsOnly]),
i"""
export type ObjectsOnly = { type: "ObjectOne" } | { type: "ObjectTwo" };
"""
- }
+ )
}
- "Union of Union" in {
- Typescript.render("A" := Ref("B") | Ref("C") | Ref("D")) shouldBe {
+ test("Union of Union") {
+ assertEquals(
+ Typescript.render(decl("A")(union(Ref("B"), Ref("C"), Ref("D")))),
i"""
export type A = B | C | D;
"""
- }
+ )
}
- "Inter of Inter" in {
- Typescript.render("A" := Ref("B") & Ref("C") & Ref("D")) shouldBe {
+ test("Inter of Inter") {
+ assertEquals(
+ Typescript.render(decl("A")(intersect(Ref("B"), Ref("C"), Ref("D")))),
i"""
export type A = B & C & D;
"""
- }
+ )
}
- "Generic Decl" in {
- Typescript.render(
- decl("Pair", "A", "B")(
- struct(
- "a" --> Ref("A"),
- "b" -?> Ref("B")
+ test("Generic Decl") {
+ assertEquals(
+ Typescript.render(
+ decl("Pair", "A", "B")(
+ struct(
+ "a" --> Ref("A"),
+ "b" -?> Ref("B")
+ )
)
- )
- ) shouldBe {
+ ),
i"""
export interface Pair {
a: A;
b?: B;
}
"""
- }
+ )
}
- "Applications of Generics" in {
- Typescript.render(decl("Cell")(ref("Pair", Str, Intr))) shouldBe {
+ test("Applications of Generics") {
+ assertEquals(
+ Typescript.render(decl("Cell")(ref("Pair", Str, Intr))),
i"""
export type Cell = Pair;
"""
- }
+ )
- Typescript.render(decl("Same", "A")(ref("Pair", ref("A"), ref("A")))) shouldBe {
+ assertEquals(
+ Typescript.render(decl("Same", "A")(ref("Pair", ref("A"), ref("A")))),
i"""
export type Same = Pair;
"""
- }
+ )
- Typescript.render(decl("AnyPair")(ref("Pair", Any, Any))) shouldBe {
+ assertEquals(
+ Typescript.render(decl("AnyPair")(ref("Pair", Any, Any))),
i"""
export type AnyPair = Pair;
"""
- }
+ )
}
- "Numeric types" in {
- Typescript.render(decl[NumericTypes]) shouldBe {
+ test("Numeric types") {
+ assertEquals(
+ Typescript.render(decl[NumericTypes]),
i"""
export interface NumericTypes {
int: number;
@@ -247,42 +269,46 @@ class TsTypeRendererSpec extends AnyFreeSpec with Matchers {
bigDecimal: number;
}
"""
- }
+ )
}
- "Tuple" in {
- Typescript.render(decl("Cell")(tuple(Str, Intr))) shouldBe {
+ test("Tuple") {
+ assertEquals(
+ Typescript.render(decl("Cell")(tuple(Str, Intr))),
i"""
export type Cell = [string, number];
"""
- }
+ )
}
- "Empty tuple" in {
- Typescript.render(decl("Empty")(tuple())) shouldBe {
+ test("Empty tuple") {
+ assertEquals(
+ Typescript.render(decl("Empty")(tuple())),
i"""
export type Empty = [];
"""
- }
+ )
}
- "Structs with rest fields" in {
- Typescript.render(decl("Dict")(dict(Str, Intr))) shouldBe {
+ test("Structs with rest fields") {
+ assertEquals(
+ Typescript.render(decl("Dict")(dict(Str, Intr))),
i"""
export interface Dict {
[key: string]: number;
}
"""
- }
-
- Typescript.render(
- decl("Dict")(
- struct(
- "a" --> Str,
- "b" -?> Intr
- ).withRest(Str, Bool, "c")
- )
- ) shouldBe {
+ )
+
+ assertEquals(
+ Typescript.render(
+ decl("Dict")(
+ struct(
+ "a" --> Str,
+ "b" -?> Intr
+ ).withRest(Str, Bool, "c")
+ )
+ ),
i"""
export interface Dict {
a: string;
@@ -290,41 +316,43 @@ class TsTypeRendererSpec extends AnyFreeSpec with Matchers {
[c: string]: boolean;
}
"""
- }
+ )
}
- "Unknown and any" in {
- Typescript.render(decl("UnknownAndAny")(struct("foo" --> Any, "bar" --> Unknown))) shouldBe {
+ test("Unknown and any") {
+ assertEquals(
+ Typescript.render(decl("UnknownAndAny")(struct("foo" --> Any, "bar" --> Unknown))),
i"""
export interface UnknownAndAny {
foo: any;
bar: unknown;
}
"""
- }
+ )
}
- "Function types" in {
- Typescript.render(
- decl("Rule")(
- struct(
- "message" --> Str,
- "apply" --> func("value" -> Unknown)(Bool)
+ test("Function types") {
+ assertEquals(
+ Typescript.render(
+ decl("Rule")(
+ struct(
+ "message" --> Str,
+ "apply" --> func("value" -> Unknown)(Bool)
+ )
)
- )
- ) shouldBe {
+ ),
i"""
export interface Rule {
message: string;
apply: (value: unknown) => boolean;
}
"""
- }
+ )
- Typescript.render(decl("Funcy")(tuple(func("arg" -> tuple(Str))(tuple(Str)), func("arg" -> tuple(Intr))(tuple(Intr))))) shouldBe {
+ assertEquals(
+ Typescript.render(decl("Funcy")(tuple(func("arg" -> tuple(Str))(tuple(Str)), func("arg" -> tuple(Intr))(tuple(Intr))))),
i"""
export type Funcy = [(arg: [string]) => [string], (arg: [number]) => [number]];
"""
- }
+ )
}
-}