diff --git a/.gitignore b/.gitignore index de69dad..ae45d73 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ target /.settings /RUNNING_PID log +metals.sbt diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 0000000..768a031 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,21 @@ +version = 2.3.2 +maxColumn = 140 +project.git = true +project.excludeFilters = [ /sbt-test/, /input_sources/, /contraband-scala/ ] + +# http://docs.scala-lang.org/style/scaladoc.html recommends the JavaDoc style. +# scala/scala is written that way too https://github.com/scala/scala/blob/v2.12.2/src/library/scala/Predef.scala +docstrings = JavaDoc + +# This also seems more idiomatic to include whitespace in import x.{ yyy } +spaces.inImportCurlyBraces = true + +# This is more idiomatic Scala. +# http://docs.scala-lang.org/style/indentation.html#methods-with-numerous-arguments +align.openParenCallSite = false +align.openParenDefnSite = false + +# For better code clarity +danglingParentheses = true + +trailingCommas = preserve diff --git a/.travis.yml b/.travis.yml index 67447ef..e040760 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,6 @@ env: ADOPTOPENJDK=11 matrix: include: - - env: SBT_VERSION="0.13.18" ADOPTOPENJDK=8 - scala: 2.10.7 - env: SBT_VERSION="1.2.8" ADOPTOPENJDK=11 scala: 2.12.10 - scala: 2.13.1 @@ -18,10 +16,11 @@ before_install: - "[[ -d $HOME/.sdkman/bin/ ]] || rm -rf $HOME/.sdkman/" - curl -sL https://get.sdkman.io | bash - echo sdkman_auto_answer=true > $HOME/.sdkman/etc/config + - echo sdkman_auto_selfupdate=true >> $HOME/.sdkman/etc/config - source "$HOME/.sdkman/bin/sdkman-init.sh" install: - - sdk install java $(sdk list java | grep -o "$ADOPTOPENJDK\.[0-9\.]*hs-adpt" | head -1) + - sdk install java $(sdk list java | grep -o "$ADOPTOPENJDK\.[0-9\.]*hs-adpt" | head -1) || true - unset JAVA_HOME - java -Xmx32m -version - javac -J-Xmx32m -version diff --git a/build.sbt b/build.sbt index b577f46..03e67e6 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import Dependencies._ ThisBuild / version := "0.4.6-SNAPSHOT" ThisBuild / organization := "org.scala-sbt" -ThisBuild / crossScalaVersions := Seq(scala213, scala212, scala211, scala210) +ThisBuild / crossScalaVersions := Seq(scala213, scala212, scala211) ThisBuild / scalaVersion := "2.12.8" ThisBuild / organizationName := "sbt" ThisBuild / organizationHomepage := Some(url("http://scala-sbt.org/")) @@ -40,7 +40,8 @@ lazy val library = (project in file("library")) baseDirectory.value / "src/main/scala-2.13+" } }, - libraryDependencies ++= Seq(parboiled.value) ++ jsonDependencies.value ++ Seq(scalaTest % Test, diffutils % Test) + testFrameworks += new TestFramework("verify.runner.Framework"), + libraryDependencies ++= Seq(parboiled.value) ++ jsonDependencies.value ++ Seq(verify % Test, scalaTest % Test, diffutils % Test) ) lazy val plugin = (project in file("plugin")) @@ -50,8 +51,10 @@ lazy val plugin = (project in file("plugin")) name := "sbt-contraband", bintrayPackage := "sbt-contraband", description := "sbt plugin to generate growable datatypes.", - scriptedLaunchOpts := { scriptedLaunchOpts.value ++ - Seq("-Xmx1024M", "-Dplugin.version=" + version.value) + scriptedLaunchOpts := { + scriptedLaunchOpts.value ++ + Seq("-Xmx1024M", "-Dplugin.version=" + version.value) }, + crossScalaVersions := Seq(scala212), publishLocal := (publishLocal dependsOn (publishLocal in library)).value ) diff --git a/library/src/main/scala/sbt/contraband/JavaCodeGen.scala b/library/src/main/scala/sbt/contraband/JavaCodeGen.scala index 1bfd5c5..69a232e 100644 --- a/library/src/main/scala/sbt/contraband/JavaCodeGen.scala +++ b/library/src/main/scala/sbt/contraband/JavaCodeGen.scala @@ -9,9 +9,12 @@ import AstUtil._ /** * Code generator for Java. */ -class JavaCodeGen(lazyInterface: String, optionalInterface: String, - instantiateJavaOptional: (String, String) => String, - wrapOption: Boolean) extends CodeGenerator { +class JavaCodeGen( + lazyInterface: String, + optionalInterface: String, + instantiateJavaOptional: (String, String) => String, + wrapOption: Boolean +) extends CodeGenerator { /** Indentation configuration for Java sources. */ implicit object indentationConfiguration extends IndentationConfiguration { @@ -34,7 +37,7 @@ class JavaCodeGen(lazyInterface: String, optionalInterface: String, val parentsInSchema = lookupInterfaces(s, parents) val parent: Option[InterfaceTypeDefinition] = parentsInSchema.headOption val allFields = fields filter { _.arguments.isEmpty } - val extendsCode = parent map (p => s"extends ${fullyQualifiedName(p)}") getOrElse "implements java.io.Serializable" + val extendsCode = genExtendsCode(parent, extraParents) val doc = toDoc(comments) val extra = toExtra(i) val msgs = i.fields filter { _.arguments.nonEmpty } @@ -65,7 +68,7 @@ class JavaCodeGen(lazyInterface: String, optionalInterface: String, val parent: Option[InterfaceTypeDefinition] = parentsInSchema.headOption val doc = toDoc(r.comments) val extra = toExtra(r) - val extendsCode = parent map (p => s"extends ${fullyQualifiedName(p)}") getOrElse "implements java.io.Serializable" + val extendsCode = genExtendsCode(parent, extraParents) val toStringImpl: List[String] = toToStringImpl(r) val lfs = localFields(r, parentsInSchema) val mod = toModifier(r.directives).getOrElse("public final") @@ -93,10 +96,12 @@ class JavaCodeGen(lazyInterface: String, optionalInterface: String, val valuesCode = if (values.isEmpty) "" - else (values map { case EnumValueDefinition(name, dir, comments, _) => - s"""${genDoc(toDoc(comments))} + else + (values map { + case EnumValueDefinition(name, dir, comments, _) => + s"""${genDoc(toDoc(comments))} |$name""".stripMargin - }).mkString("", "," + EOL, ";") + }).mkString("", "," + EOL, ";") val extra = toExtra(e) val code = @@ -111,7 +116,7 @@ class JavaCodeGen(lazyInterface: String, optionalInterface: String, } private def genDoc(doc: List[String]) = doc match { - case Nil => "" + case Nil => "" case l :: Nil => s"/** $l */" case lines => val doc = lines map (l => s" * $l") mkString EOL @@ -120,6 +125,14 @@ class JavaCodeGen(lazyInterface: String, optionalInterface: String, | */""".stripMargin } + private def genExtendsCode(parent: Option[InterfaceTypeDefinition], extraParents: List[String]): String = { + val parentOpt = parent.map(p => fullyQualifiedName(p)) + (parentOpt match { + case Some(p) => s"extends $p " + case _ => "" + }) + "implements " + (extraParents ::: List("java.io.Serializable")).mkString(", ") + } + private def genFile(d: TypeDefinition) = { val fileName = d.name + ".java" d.namespace map (ns => new File(ns.replace(".", File.separator), fileName)) getOrElse new File(fileName) @@ -158,23 +171,24 @@ class JavaCodeGen(lazyInterface: String, optionalInterface: String, } private def genMessages(messages: List[FieldDefinition]) = messages map genMessage mkString EOL - private def genMessage(message: FieldDefinition): String = - { - val FieldDefinition(name, fieldType, arguments, defaultValue, dirs, comments, _) = message - val doc = toDoc(comments) - val argsDoc = arguments flatMap { a: InputValueDefinition => - toDoc(a.comments) match { - case Nil => Nil - case doc :: Nil => s"@param ${a.name} $doc" :: Nil - case docs => - val prefix = s"@param ${a.name} " - docs.mkString(prefix, EOL + " " * (prefix.length + 3), "") :: Nil - } + private def genMessage(message: FieldDefinition): String = { + val FieldDefinition(name, fieldType, arguments, defaultValue, dirs, comments, _) = message + val doc = toDoc(comments) + val argsDoc = arguments flatMap { a: InputValueDefinition => + toDoc(a.comments) match { + case Nil => Nil + case doc :: Nil => s"@param ${a.name} $doc" :: Nil + case docs => + val prefix = s"@param ${a.name} " + docs.mkString(prefix, EOL + " " * (prefix.length + 3), "") :: Nil } - val params: List[String] = arguments map { a => s"${genRealTpe(a.valueType)} ${a.name}" } - s"""${genDoc(doc ++ argsDoc)} - |public abstract ${genRealTpe(fieldType)} ${name}(${params mkString ","});""" } + val params: List[String] = arguments map { a => + s"${genRealTpe(a.valueType)} ${a.name}" + } + s"""${genDoc(doc ++ argsDoc)} + |public abstract ${genRealTpe(fieldType)} ${name}(${params mkString ","});""" + } private def renderJavaValue(v: Value, tpe: Type): String = v match { @@ -188,9 +202,11 @@ class JavaCodeGen(lazyInterface: String, optionalInterface: String, val str = v match { case x: ObjectValue => - val args = x.fields map { f => f.value.renderPretty } - s"""new ${tpe.name}(${ args.mkString(", ") })""" - case _ => v.renderPretty + val args = x.fields map { f => + f.value.renderPretty + } + s"""new ${tpe.name}(${args.mkString(", ")})""" + case _ => v.renderPretty } if (tpe.isListType) s"new Array { $str }" else if (tpe.isNotNullType) str @@ -202,7 +218,7 @@ class JavaCodeGen(lazyInterface: String, optionalInterface: String, case Some(v) => renderJavaValue(v, f.fieldType) case None if f.fieldType.isListType || !f.fieldType.isNotNullType => renderJavaValue(NullValue(), f.fieldType) - case _ => sys.error(s"Needs a default value for field ${f.name}.") + case _ => sys.error(s"Needs a default value for field ${f.name}.") } private def genFactoryMethods(cl: RecordLikeDefinition, parent: Option[InterfaceTypeDefinition]) = @@ -212,21 +228,24 @@ class JavaCodeGen(lazyInterface: String, optionalInterface: String, val args = provided map (f => s"_${f.name}") val factoryMethodNames = List("create", "of") - val methods = factoryMethodNames map { factory => genStaticFactoryMethod(cl, factory, ctorParameters, args) } - methods.mkString(EOL + EOL) + - { + val methods = factoryMethodNames map { factory => + genStaticFactoryMethod(cl, factory, ctorParameters, args) + } + methods.mkString(EOL + EOL) + { if (!containsStrictOptional(provided) || !wrapOption) "" else { val ctorParameters2 = provided map { f => if (f.fieldType.isOptionalType && !f.fieldType.isLazyType) s"${genRealTpe(f.fieldType.notNull)} _${f.name}" else s"${genRealTpe(f.fieldType)} _${f.name}" } - val methods2 = factoryMethodNames map { factory => genStaticFactoryMethod(cl, factory, ctorParameters2, args) } + val methods2 = factoryMethodNames map { factory => + genStaticFactoryMethod(cl, factory, ctorParameters2, args) + } EOL + EOL + methods2.mkString(EOL + EOL) } } } mkString (EOL + EOL) - + private def genStaticFactoryMethod(cl: RecordLikeDefinition, methodName: String, params: List[String], args: List[String]): String = s"""public static ${cl.name} ${methodName}(${params.mkString(", ")}) { | return new ${cl.name}(${args.mkString(", ")}); @@ -280,12 +299,12 @@ class JavaCodeGen(lazyInterface: String, optionalInterface: String, | $superCall2 | $assignments2 |}""".stripMargin - } + } } } mkString (EOL + EOL) private def genWith(r: ObjectTypeDefinition, parents: List[InterfaceTypeDefinition]) = { - def capitalize(s: String) = { val (fst, rst) = s.splitAt(1) ; fst.toUpperCase + rst } + def capitalize(s: String) = { val (fst, rst) = s.splitAt(1); fst.toUpperCase + rst } val allFields = (r.fields filter { _.arguments.isEmpty }).zipWithIndex val lfs = localFields(r, parents) def nonParam(f: (FieldDefinition, Int)): String = { @@ -299,37 +318,41 @@ class JavaCodeGen(lazyInterface: String, optionalInterface: String, } else s"${f._1.name}()" } - allFields map { case (f, idx) => - val (before, after) = allFields filterNot (_._2 == idx) splitAt idx - val tpe = f.fieldType - val params = (before map nonParam) ::: f.name :: (after map nonParam) mkString ", " - s"""public ${r.name} with${capitalize(f.name)}(${genRealTpe(tpe)} ${f.name}) { + allFields map { + case (f, idx) => + val (before, after) = allFields filterNot (_._2 == idx) splitAt idx + val tpe = f.fieldType + val params = (before map nonParam) ::: f.name :: (after map nonParam) mkString ", " + s"""public ${r.name} with${capitalize(f.name)}(${genRealTpe(tpe)} ${f.name}) { | return new ${r.name}($params); |}""".stripMargin + - ( if (tpe.isListType || tpe.isNotNullType) "" - else { - val wrappedParams = (before map nonParam) ::: instantiateJavaOptional(boxedType(tpe.name), f.name) :: (after map nonParam) mkString ", " - s""" + (if (tpe.isListType || tpe.isNotNullType) "" + else { + val wrappedParams = (before map nonParam) ::: instantiateJavaOptional(boxedType(tpe.name), f.name) :: (after map nonParam) mkString ", " + s""" |public ${r.name} with${capitalize(f.name)}(${genRealTpe(f.fieldType.notNull)} ${f.name}) { | return new ${r.name}($wrappedParams); |}""".stripMargin - } - ) + }) } mkString (EOL + EOL) } private def genEquals(cl: RecordLikeDefinition) = { val allFields = cl.fields filter { _.arguments.isEmpty } val body = - if (allFields exists { f => f.fieldType.isLazyType }) { + if (allFields exists { f => + f.fieldType.isLazyType + }) { "return this == obj; // We have lazy members, so use object identity to avoid circularity." } else { val comparisonCode = if (allFields.isEmpty) "return true;" else - allFields.map { f => - genJavaEquals("this", "o", f, s"${f.name}()", true) - }.mkString("return ", " && ", ";") + allFields + .map { f => + genJavaEquals("this", "o", f, s"${f.name}()", true) + } + .mkString("return ", " && ", ";") s"""if (this == obj) { | return true; @@ -351,7 +374,9 @@ class JavaCodeGen(lazyInterface: String, optionalInterface: String, val fqcn = cl.namespace.fold("")(_ + ".") + cl.name val seed = s"""37 * (17 + "$fqcn".hashCode())""" val body = - if (allFields exists { f => f.fieldType.isLazyType }) { + if (allFields exists { f => + f.fieldType.isLazyType + }) { "return super.hashCode(); // Avoid evaluating lazy members in hashCode to avoid circularity." } else { val computation = (seed /: allFields) { (acc, f) => @@ -369,12 +394,16 @@ class JavaCodeGen(lazyInterface: String, optionalInterface: String, private def genToString(cl: RecordLikeDefinition, toString: List[String]) = { val body = if (toString.isEmpty) { val allFields = cl.fields filter { _.arguments.isEmpty } - if (allFields exists { f => f.fieldType.isLazyType }) { + if (allFields exists { f => + f.fieldType.isLazyType + }) { "return super.toString(); // Avoid evaluating lazy members in toString to avoid circularity." } else { - allFields.map{ f => - s""" + "${f.name}: " + ${f.name}()""" - }.mkString(s"""return "${cl.name}(" """, " + \", \"", " + \")\";") + allFields + .map { f => + s""" + "${f.name}: " + ${f.name}()""" + } + .mkString(s"""return "${cl.name}(" """, " + \", \"", " + \")\";") } } else toString mkString s"$EOL " diff --git a/library/src/test/scala/EqualLines.scala b/library/src/test/scala/EqualLines.scala index 5263865..f3cb0a9 100644 --- a/library/src/test/scala/EqualLines.scala +++ b/library/src/test/scala/EqualLines.scala @@ -10,6 +10,7 @@ trait EqualLines { private val emptyLines = Lines(Vector.empty) implicit class CleanedString(s: String) { + def stripSpace: String = unindent.value.mkString("\n") def unindent: Lines = Lines(s.linesIterator.map(_.trim).filterNot(_.isEmpty).toVector) def withoutEmptyLines: Lines = Lines(s.linesIterator.filterNot(_.trim.isEmpty).toVector) } diff --git a/library/src/test/scala/GraphQLCodecCodeGenSpec.scala b/library/src/test/scala/GraphQLCodecCodeGenSpec.scala index 8ebe085..f8d79f6 100644 --- a/library/src/test/scala/GraphQLCodecCodeGenSpec.scala +++ b/library/src/test/scala/GraphQLCodecCodeGenSpec.scala @@ -1,19 +1,17 @@ package sbt.contraband -import org.scalatest._ +import verify._ import java.io.File import parser.SchemaParser import GraphQLExample._ import scala.util.Success -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers -class GraphQLCodecCodeGenSpec extends AnyFlatSpec with Matchers with Inside with EqualLines { - "generate(Interface)" should "generate a codec for an interface" in { +object GraphQLCodecCodeGenSpec extends BasicTestSuite with EqualLines { + test("generate(Interface) should generate a codec for an interface") { val Success(ast) = SchemaParser.parse(intfExample) val code = mkCodecCodeGen.generate(ast) - - code(new File("generated", "InterfaceExampleFormats.scala")).unindent should equalLines ( + assertEquals( + code(new File("generated", "InterfaceExampleFormats.scala")).stripSpace, """/** | * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. | */ @@ -25,8 +23,11 @@ class GraphQLCodecCodeGenSpec extends AnyFlatSpec with Matchers with Inside with | |trait InterfaceExampleFormats { self: sjsonnew.BasicJsonProtocol with generated.ChildTypeFormats => | implicit lazy val InterfaceExampleFormat: JsonFormat[com.example.InterfaceExample] = flatUnionFormat1[com.example.InterfaceExample, com.example.ChildType]("type") - |}""".stripMargin.unindent) - code(new File("generated", "ChildTypeFormats.scala")).unindent should equalLines ( + |}""".stripMargin.stripSpace + ) + + assertEquals( + code(new File("generated", "ChildTypeFormats.scala")).stripSpace, """/** | * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. | */ @@ -57,14 +58,16 @@ class GraphQLCodecCodeGenSpec extends AnyFlatSpec with Matchers with Inside with | builder.endObject() | } | } - |}""".stripMargin.unindent) + |}""".stripMargin.stripSpace + ) } - it should "generate a codec for a two-level hierarchy of interfaces" in { + test("generate a codec for a two-level hierarchy of interfaces") { val Success(ast) = SchemaParser.parse(twoLevelIntfExample) val code = mkCodecCodeGen.generate(ast) - code(new File("generated", "InterfaceExampleFormats.scala")).unindent should equalLines ( + assertEquals( + code(new File("generated", "InterfaceExampleFormats.scala")).stripSpace, """/** | * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. | */ @@ -76,9 +79,13 @@ class GraphQLCodecCodeGenSpec extends AnyFlatSpec with Matchers with Inside with | |trait InterfaceExampleFormats { self: sjsonnew.BasicJsonProtocol with generated.ChildTypeFormats => | implicit lazy val InterfaceExampleFormat: JsonFormat[com.example.InterfaceExample] = flatUnionFormat1[com.example.InterfaceExample, com.example.ChildType]("type") - |}""".stripMargin.unindent) - code.contains(new File("generated", "MiddleInterfaceFormats.scala")) shouldEqual false - code(new File("generated", "ChildTypeFormats.scala")).unindent should equalLines ( + |}""".stripMargin.stripSpace + ) + + assert(!code.contains(new File("generated", "MiddleInterfaceFormats.scala"))) + + assertEquals( + code(new File("generated", "ChildTypeFormats.scala")).stripSpace, """/** | * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. | */ @@ -109,7 +116,8 @@ class GraphQLCodecCodeGenSpec extends AnyFlatSpec with Matchers with Inside with | builder.endObject() | } | } - |}""".stripMargin.unindent) + |}""".stripMargin.stripSpace + ) } val codecParents = List("sjsonnew.BasicJsonProtocol") diff --git a/library/src/test/scala/GraphQLExample.scala b/library/src/test/scala/GraphQLExample.scala index f399d37..763ac15 100644 --- a/library/src/test/scala/GraphQLExample.scala +++ b/library/src/test/scala/GraphQLExample.scala @@ -26,6 +26,17 @@ type TypeExample { #x // Some extra code }""" + val recordExtraIntfExample = """ +package com.example @target(Scala) + +## Example of a type +type TypeExample { + ## something + field: java.net.URL + + #xinterface Intf1 +}""" + val stringStringMapExample = """ package com.example @target(Scala) diff --git a/library/src/test/scala/GraphQLJavaCodeGenSpec.scala b/library/src/test/scala/GraphQLJavaCodeGenSpec.scala index 8bbfbc6..2071871 100644 --- a/library/src/test/scala/GraphQLJavaCodeGenSpec.scala +++ b/library/src/test/scala/GraphQLJavaCodeGenSpec.scala @@ -1,19 +1,18 @@ package sbt.contraband -import org.scalatest._ +import verify._ import java.io.File import parser.SchemaParser import GraphQLExample._ import scala.util.Success -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers -class GraphQLJavaCodeGenSpec extends AnyFlatSpec with Matchers with Inside with EqualLines { - "generate(Enumeration)" should "generate a simple enumeration" in { +object GraphQLJavaCodeGenSpec extends BasicTestSuite with EqualLines { + test("generate(Enumeration) should generate a simple enumeration") { val Success(ast) = SchemaParser.parse(simpleEnumerationExample) // println(ast) val code = mkJavaCodeGen.generate(ast) - code.head._2.unindent should equalLines ( + assertEquals( + code.head._2.stripSpace, """package com.example; |/** Example of an enumeration */ |public enum EnumExample { @@ -21,14 +20,16 @@ class GraphQLJavaCodeGenSpec extends AnyFlatSpec with Matchers with Inside with | First, | Second; | // Some extra code - |}""".stripMargin.unindent) + |}""".stripMargin.stripSpace + ) } - "generate(Record)" should "generate a record" in { + test("generate(Record) should generate a record") { val Success(ast) = SchemaParser.parse(recordExample) // println(ast) val code = mkJavaCodeGen.generate(ast) - code.head._2.unindent should equalLines( + assertEquals( + code.head._2.stripSpace, """package com.example; |/** Example of a type */ |public final class TypeExample implements java.io.Serializable { @@ -81,15 +82,78 @@ class GraphQLJavaCodeGenSpec extends AnyFlatSpec with Matchers with Inside with | public String toString() { | return "TypeExample(" + "field: " + field() + ")"; | } - |}""".stripMargin.unindent) + |}""".stripMargin.stripSpace + ) } - it should "grow a record from 0 to 1 field" in { + test("generate with xinterface") { + val Success(ast) = SchemaParser.parse(recordExtraIntfExample) + // println(ast) + val code = mkJavaCodeGen.generate(ast) + assertEquals( + code.head._2.stripSpace, + """package com.example; + |/** Example of a type */ + |public final class TypeExample implements Intf1, java.io.Serializable { + | + | public static TypeExample create(java.util.Optional _field) { + | return new TypeExample(_field); + | } + | public static TypeExample of(java.util.Optional _field) { + | return new TypeExample(_field); + | } + | public static TypeExample create(java.net.URL _field) { + | return new TypeExample(_field); + | } + | public static TypeExample of(java.net.URL _field) { + | return new TypeExample(_field); + | } + | private java.util.Optional field; + | protected TypeExample(java.util.Optional _field) { + | super(); + | field = _field; + | } + | protected TypeExample(java.net.URL _field) { + | super(); + | field = java.util.Optional.ofNullable(_field); + | } + | /** something */ + | public java.util.Optional field() { + | return this.field; + | } + | public TypeExample withField(java.util.Optional field) { + | return new TypeExample(field); + | } + | public TypeExample withField(java.net.URL field) { + | return new TypeExample(java.util.Optional.ofNullable(field)); + | } + | public boolean equals(Object obj) { + | if (this == obj) { + | return true; + | } else if (!(obj instanceof TypeExample)) { + | return false; + | } else { + | TypeExample o = (TypeExample)obj; + | return this.field().equals(o.field()); + | } + | } + | public int hashCode() { + | return 37 * (37 * (17 + "com.example.TypeExample".hashCode()) + field().hashCode()); + | } + | public String toString() { + | return "TypeExample(" + "field: " + field() + ")"; + | } + |}""".stripMargin.stripSpace + ) + } + + test("grow a record from 0 to 1 field") { val Success(ast) = SchemaParser.parse(growableAddOneFieldExample) // println(ast) val code = mkJavaCodeGen.generate(ast) - code.head._2.unindent should equalLines ( + assertEquals( + code.head._2.stripSpace, """package com.example; |public final class Growable implements java.io.Serializable { | public static Growable create() { @@ -148,15 +212,17 @@ class GraphQLJavaCodeGenSpec extends AnyFlatSpec with Matchers with Inside with | public String toString() { | return "Growable(" + "field: " + field() + ")"; | } - |}""".stripMargin.unindent) + |}""".stripMargin.stripSpace + ) } - it should "grow a record from 0 to 2 field" in { + test("grow a record from 0 to 2 field") { val Success(ast) = SchemaParser.parse(growableZeroToOneToTwoFieldsExample) // println(ast) val code = mkJavaCodeGen.generate(ast) - code.head._2.unindent should equalLines ( + assertEquals( + code.head._2.stripSpace, """package com.example; |public final class Foo implements java.io.Serializable { | public static Foo create() { @@ -247,16 +313,18 @@ class GraphQLJavaCodeGenSpec extends AnyFlatSpec with Matchers with Inside with | public String toString() { | return "Foo(" + "x: " + x() + ", " + "y: " + y() + ")"; | } - |}""".stripMargin.unindent) + |}""".stripMargin.stripSpace + ) } - "generate(Interface)" should "generate an interface with one child" in { + test("generate(Interface) should generate an interface with one child") { val Success(ast) = SchemaParser.parse(intfExample) val code = mkJavaCodeGen.generate(ast) - val code1 = code.toList(0)._2.unindent - val code2 = code.toList(1)._2.unindent - code1 should equalLines ( + val code1 = code.toList(0)._2.stripSpace + val code2 = code.toList(1)._2.stripSpace + assertEquals( + code1, """package com.example; |/** Example of an interface */ |public abstract class InterfaceExample implements java.io.Serializable { @@ -290,10 +358,12 @@ class GraphQLJavaCodeGenSpec extends AnyFlatSpec with Matchers with Inside with | public String toString() { | return "InterfaceExample(" + "field: " + field() + ")"; | } - |}""".stripMargin.unindent) - code2 should equalLines ( + |}""".stripMargin.stripSpace + ) + assertEquals( + code2, """package com.example; - |public final class ChildType extends com.example.InterfaceExample { + |public final class ChildType extends com.example.InterfaceExample implements java.io.Serializable { | public static ChildType create(java.util.Optional _name, java.util.Optional _field) { | return new ChildType(_name, _field); | } @@ -346,13 +416,15 @@ class GraphQLJavaCodeGenSpec extends AnyFlatSpec with Matchers with Inside with | public String toString() { | return "ChildType(" + "name: " + name() + ", " + "field: " + field() + ")"; | } - |}""".stripMargin.unindent) + |}""".stripMargin.stripSpace + ) } - it should "generate messages" in { + test("generate messages") { val Success(ast) = SchemaParser.parse(messageExample) val code = mkJavaCodeGen.generate(ast) - code.head._2.unindent should equalLines( + assertEquals( + code.head._2.stripSpace, """package com.example; |public abstract class IntfExample implements java.io.Serializable { | private java.util.Optional field; @@ -392,10 +464,9 @@ class GraphQLJavaCodeGenSpec extends AnyFlatSpec with Matchers with Inside with | public String toString() { | return "IntfExample(" + "field: " + field() + ")"; | } - |}""".stripMargin.unindent + |}""".stripMargin.stripSpace ) } def mkJavaCodeGen: JavaCodeGen = - new JavaCodeGen("com.example.MyLazy", CodeGen.javaOptional, CodeGen.instantiateJavaOptional, - wrapOption = true) + new JavaCodeGen("com.example.MyLazy", CodeGen.javaOptional, CodeGen.instantiateJavaOptional, wrapOption = true) } diff --git a/library/src/test/scala/GraphQLScalaCodeGenSpec.scala b/library/src/test/scala/GraphQLScalaCodeGenSpec.scala index e602a89..1984e71 100644 --- a/library/src/test/scala/GraphQLScalaCodeGenSpec.scala +++ b/library/src/test/scala/GraphQLScalaCodeGenSpec.scala @@ -1,6 +1,6 @@ package sbt.contraband -import org.scalatest._ +import verify._ import java.io.File import parser.SchemaParser import GraphQLExample._ @@ -8,12 +8,13 @@ import scala.util.Success import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers -class GraphQLScalaCodeGenSpec extends AnyFlatSpec with Matchers with Inside with EqualLines { - "generate(Enumeration)" should "generate a simple enumeration" in { +object GraphQLScalaCodeGenSpec extends BasicTestSuite with EqualLines { + test("generate(Enumeration) should generate a simple enumeration") { val Success(ast) = SchemaParser.parse(simpleEnumerationExample) // println(ast) val code = mkScalaCodeGen.generate(ast) - code.head._2.unindent should equalLines( + assertEquals( + code.head._2.stripSpace, """package com.example |/** Example of an enumeration */ |sealed abstract class EnumExample extends Serializable @@ -23,14 +24,16 @@ class GraphQLScalaCodeGenSpec extends AnyFlatSpec with Matchers with Inside with | case object First extends EnumExample | | case object Second extends EnumExample - |}""".stripMargin.unindent) + |}""".stripMargin.stripSpace + ) } - "generate(Record)" should "generate a record" in { + test("generate(Record) should generate a record") { val Success(ast) = SchemaParser.parse(recordExample) // println(ast) val code = mkScalaCodeGen.generate(ast) - code.head._2.unindent should equalLines( + assertEquals( + code.head._2.stripSpace, """package com.example |/** |* Example of a type @@ -62,14 +65,56 @@ class GraphQLScalaCodeGenSpec extends AnyFlatSpec with Matchers with Inside with |object TypeExample { | def apply(field: Option[java.net.URL]): TypeExample = new TypeExample(field) | def apply(field: java.net.URL): TypeExample = new TypeExample(Option(field)) - |}""".stripMargin.unindent) + |}""".stripMargin.stripSpace + ) + } + + test("generate with xinterface") { + val Success(ast) = SchemaParser.parse(recordExtraIntfExample) + // println(ast) + val code = mkScalaCodeGen.generate(ast) + assertEquals( + code.head._2.stripSpace, + """package com.example + |/** + |* Example of a type + |* @param field something + |*/ + |final class TypeExample private ( + |val field: Option[java.net.URL]) extends Intf1 with Serializable { + | override def equals(o: Any): Boolean = o match { + | case x: TypeExample => (this.field == x.field) + | case _ => false + | } + | override def hashCode: Int = { + | 37 * (37 * (17 + "com.example.TypeExample".##) + field.##) + | } + | override def toString: String = { + | "TypeExample(" + field + ")" + | } + | private[this] def copy(field: Option[java.net.URL] = field): TypeExample = { + | new TypeExample(field) + | } + | def withField(field: Option[java.net.URL]): TypeExample = { + | copy(field = field) + | } + | def withField(field: java.net.URL): TypeExample = { + | copy(field = Option(field)) + | } + |} + |object TypeExample { + | def apply(field: Option[java.net.URL]): TypeExample = new TypeExample(field) + | def apply(field: java.net.URL): TypeExample = new TypeExample(Option(field)) + |}""".stripMargin.stripSpace + ) } - it should "generate Map[String, String] from StringStringMap" in { + test("generate Map[String, String] from StringStringMap") { val Success(ast) = SchemaParser.parse(stringStringMapExample) // println(ast) val code = mkScalaCodeGen.generate(ast) - code.head._2.unindent should equalLines( + assertEquals( + code.head._2.stripSpace, """package com.example |/** Example of a type */ |final class TypeExample private ( @@ -93,15 +138,17 @@ class GraphQLScalaCodeGenSpec extends AnyFlatSpec with Matchers with Inside with |} |object TypeExample { | def apply(field: scala.collection.immutable.Map[String, String]): TypeExample = new TypeExample(field) - |}""".stripMargin.unindent) + |}""".stripMargin.stripSpace + ) } - it should "grow a record from 0 to 1 field" in { + test("grow a record from 0 to 1 field") { val Success(ast) = SchemaParser.parse(growableAddOneFieldExample) // println(ast) val code = mkScalaCodeGen.generate(ast) - code.head._2.unindent should equalLines ( + assertEquals( + code.head._2.stripSpace, """package com.example |final class Growable private ( | val field: Option[Int]) extends Serializable { @@ -131,15 +178,17 @@ class GraphQLScalaCodeGenSpec extends AnyFlatSpec with Matchers with Inside with | def apply(field: Option[Int]): Growable = new Growable(field) | def apply(field: Int): Growable = new Growable(Option(field)) |} - |""".stripMargin.unindent) + |""".stripMargin.stripSpace + ) } - it should "grow a record from 0 to 2 field" in { + test("grow a record from 0 to 2 field") { val Success(ast) = SchemaParser.parse(growableZeroToOneToTwoFieldsExample) // println(ast) val code = mkScalaCodeGen.generate(ast) - code.head._2.unindent should equalLines ( + assertEquals( + code.head._2.stripSpace, """package com.example |final class Foo private ( | val x: Option[Int], @@ -176,15 +225,17 @@ class GraphQLScalaCodeGenSpec extends AnyFlatSpec with Matchers with Inside with | def apply(x: Option[Int], y: Vector[Int]): Foo = new Foo(x, y) | def apply(x: Int, y: Vector[Int]): Foo = new Foo(Option(x), y) |} - |""".stripMargin.unindent) + |""".stripMargin.stripSpace + ) } - it should "generate with modifier" in { + test("generate with modifier") { val Success(ast) = SchemaParser.parse(modifierExample) // println(ast) val code = mkScalaCodeGen.generate(ast) - code.head._2.unindent should equalLines ( + assertEquals( + code.head._2.stripSpace, """package com.example |sealed class ModifierExample private ( |val field: Int) extends Serializable { @@ -208,13 +259,15 @@ class GraphQLScalaCodeGenSpec extends AnyFlatSpec with Matchers with Inside with |object ModifierExample { | def apply(field: Int): ModifierExample = new ModifierExample(field) |} - |""".stripMargin.unindent) + |""".stripMargin.stripSpace + ) } - "generate(Interface)" should "generate an interface with one child" in { + test("generate(Interface) should generate an interface with one child") { val Success(ast) = SchemaParser.parse(intfExample) val code = mkScalaCodeGen.generate(ast) - code.head._2.unindent should equalLines( + assertEquals( + code.head._2.stripSpace, """package com.example |/** Example of an interface */ |sealed abstract class InterfaceExample( @@ -266,13 +319,15 @@ class GraphQLScalaCodeGenSpec extends AnyFlatSpec with Matchers with Inside with |object ChildType { | def apply(name: Option[String], field: Option[Int]): ChildType = new ChildType(name, field) | def apply(name: String, field: Int): ChildType = new ChildType(Option(name), Option(field)) - |}""".stripMargin.unindent) + |}""".stripMargin.stripSpace + ) } - it should "generate messages" in { + test("generate messages") { val Success(ast) = SchemaParser.parse(messageExample) val code = mkScalaCodeGen.generate(ast) - code.head._2.unindent should equalLines( + assertEquals( + code.head._2.stripSpace, """package com.example |sealed abstract class IntfExample( | val field: Option[Int]) extends Serializable { @@ -298,14 +353,15 @@ class GraphQLScalaCodeGenSpec extends AnyFlatSpec with Matchers with Inside with | |object IntfExample { |} - |""".stripMargin.unindent + |""".stripMargin.stripSpace ) } - it should "generate with customization" in { + test("generate with customization") { val Success(ast) = SchemaParser.parse(customizationExample) val code = mkScalaCodeGen.generate(ast) - code.head._2.unindent should equalLines( + assertEquals( + code.head._2.stripSpace, """package com.example |/** Example of an interface */ |sealed abstract class IntfExample( @@ -326,13 +382,21 @@ class GraphQLScalaCodeGenSpec extends AnyFlatSpec with Matchers with Inside with |object IntfExample extends CompanionInterface1 with CompanionInterface2 { | // Some extra companion code... |} - |""".stripMargin.unindent + |""".stripMargin.stripSpace ) } def mkScalaCodeGen: ScalaCodeGen = - new ScalaCodeGen(javaLazy, CodeGen.javaOptional, CodeGen.instantiateJavaOptional, scalaArray, genFileName, - scalaSealProtocols = true, scalaPrivateConstructor = true, wrapOption = true) + new ScalaCodeGen( + javaLazy, + CodeGen.javaOptional, + CodeGen.instantiateJavaOptional, + scalaArray, + genFileName, + scalaSealProtocols = true, + scalaPrivateConstructor = true, + wrapOption = true + ) val javaLazy = "com.example.Lazy" val outputFile = new File("output.scala") val scalaArray = "Vector" diff --git a/library/src/test/scala/GraphQLSchemaParserSpec.scala b/library/src/test/scala/GraphQLSchemaParserSpec.scala index f68a680..7da61ee 100644 --- a/library/src/test/scala/GraphQLSchemaParserSpec.scala +++ b/library/src/test/scala/GraphQLSchemaParserSpec.scala @@ -3,219 +3,300 @@ package sbt.contraband import org.parboiled2.Position import parser.SchemaParser import ast._ -import org.scalatest._ +import verify._ import scala.util.Success -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers -class GraphQLSchemaParserSpec extends AnyFlatSpec with Matchers with Inside { - "SchemaParser" should "parse an empty type" in { +object GraphQLSchemaParserSpec extends BasicTestSuite { + test("SchemaParser should parse an empty type") { val Success(ast) = SchemaParser.parse("""type Hello {}""") // println(ast) - inside(ast) { case Document(_, ObjectTypeDefinition(name, _, Nil, Nil, _, _, _, _) :: Nil, _, _, _) => - name shouldEqual "Hello" + ast match { + case Document(_, ObjectTypeDefinition(name, _, Nil, Nil, _, _, _, _) :: Nil, _, _, _) => + assert(name == "Hello") + case _ => fail() } } - it should "parse two types" in { - val Success(ast) = SchemaParser.parse( - """# comment + test("parse two types") { + val Success(ast) = SchemaParser.parse("""# comment |type Foo {} |type Bar {}""".stripMargin) // println(ast) - inside(ast) { case Document(_, foo :: bar :: Nil, _, _, _) => - inside(foo) { case ObjectTypeDefinition(name, _, Nil, Nil, _, comment :: Nil, _, _) => - name shouldEqual "Foo" - comment.text shouldEqual " comment" - } - inside(bar) { case ObjectTypeDefinition(name, _, Nil, Nil, _, _, _, _) => - name shouldEqual "Bar" - } + ast match { + case Document(_, foo :: bar :: Nil, _, _, _) => + foo match { + case ObjectTypeDefinition(name, _, Nil, Nil, _, comment :: Nil, _, _) => + assert(name == "Foo") + assert(comment.text == " comment") + case _ => fail() + } + bar match { + case ObjectTypeDefinition(name, _, Nil, Nil, _, _, _, _) => + assert(name == "Bar") + case _ => fail() + } + case _ => fail() } } - it should "parse a type with doc comments" in { - val Success(ast) = SchemaParser.parse( - """## doc comment + test("parse a type with doc comments") { + val Success(ast) = SchemaParser.parse("""## doc comment |type Foo {}""".stripMargin) // println(ast) - inside(ast) { case Document(_, ObjectTypeDefinition(name, _, Nil, Nil, _, comment :: Nil, _, _) :: Nil, _, _, _) => - name shouldEqual "Foo" - inside(comment) { case DocComment(x, _) => - x shouldEqual " doc comment" - } + ast match { + case Document(_, ObjectTypeDefinition(name, _, Nil, Nil, _, comment :: Nil, _, _) :: Nil, _, _, _) => + assert(name == "Foo") + comment match { + case DocComment(x, _) => + assert(x == " doc comment") + case _ => fail() + } + case _ => fail() + } + } + + test("parse a type with extra comments") { + val Success(ast) = SchemaParser.parse("""type Foo { + | #x // extra code + | #xinterface Interface1 + |}""".stripMargin) + // println(ast) + ast match { + case Document(_, ObjectTypeDefinition(name, _, Nil, Nil, _, _, List(comment1, comment2), _) :: Nil, _, _, _) => + assert(name == "Foo") + comment1 match { + case ExtraComment(x, _) => assert(x == " // extra code") + case _ => fail(comment1.toString) + } + comment2 match { + case ExtraIntfComment(x, _) => assert(x == "Interface1") + case _ => fail(comment2.toString) + } + case _ => fail(ast.toString) } } - it should "parse a type with target directive" in { - val Success(ast) = SchemaParser.parse( - """type Foo @target(Java) { + test("parse a type with target directive") { + val Success(ast) = SchemaParser.parse("""type Foo @target(Java) { |}""".stripMargin) // println(ast) - inside(ast) { case Document(_, ObjectTypeDefinition(name, _, Nil, Nil, dir :: Nil, _, _, _) :: Nil, _, _, _) => - name shouldEqual "Foo" - inside(dir) { case Directive(name, Argument(argName, v, _, _) :: Nil, _, _) => - name shouldEqual "target" - argName shouldEqual None - inside(v) { case EnumValue(value, _, _) => - value shouldEqual "Java" + ast match { + case Document(_, ObjectTypeDefinition(name, _, Nil, Nil, dir :: Nil, _, _, _) :: Nil, _, _, _) => + assert(name == "Foo") + + dir match { + case Directive(name, Argument(argName, v, _, _) :: Nil, _, _) => + assert(name == "target") + assert(argName == None) + v match { + case EnumValue(value, _, _) => + assert(value == "Java") + case _ => fail() + } + case _ => fail() } - } + case _ => fail() } } - it should "parse a type with fields" in { - val Success(ast) = SchemaParser.parse( - """type Person { + test("parse a type with fields") { + val Success(ast) = SchemaParser.parse("""type Person { | name: String | age: Int! | xs: [java.util.Date] | x: raw"Map[String, String]" |}""".stripMargin) // println(ast) - inside(ast) { case Document(_, foo :: Nil, _, _, _) => - inside(foo) { case ObjectTypeDefinition(name, _, Nil, f1 :: f2 :: f3 :: f4 :: Nil, _, _, _, _) => - name shouldEqual "Person" - inside(f1) { case FieldDefinition(name, NamedType(typeName :: Nil, _), _ , _, _, _, _) => - name shouldEqual "name" - typeName shouldEqual "String" - } - inside(f2) { case FieldDefinition(name, NotNullType(NamedType(typeName :: Nil, _), _), _, _, _, _, _) => - name shouldEqual "age" - typeName shouldEqual "Int" - } - inside(f3) { case FieldDefinition(name, ListType(NamedType(typeName, _), _), _, _, _, _, _) => - name shouldEqual "xs" - typeName shouldEqual List("java", "util", "Date") - } - inside(f4) { case FieldDefinition(name, NamedType(typeName, _), _, _, _, _, _) => - name shouldEqual "x" - typeName shouldEqual List("Map[String, String]") + ast match { + case Document(_, foo :: Nil, _, _, _) => + foo match { + case ObjectTypeDefinition(name, _, Nil, f1 :: f2 :: f3 :: f4 :: Nil, _, _, _, _) => + assert(name == "Person") + f1 match { + case FieldDefinition(name, NamedType(typeName :: Nil, _), _, _, _, _, _) => + assert(name == "name") + assert(typeName == "String") + case _ => fail() + } + f2 match { + case FieldDefinition(name, NotNullType(NamedType(typeName :: Nil, _), _), _, _, _, _, _) => + assert(name == "age") + assert(typeName == "Int") + case _ => fail() + } + f3 match { + case FieldDefinition(name, ListType(NamedType(typeName, _), _), _, _, _, _, _) => + assert(name == "xs") + assert(typeName == List("java", "util", "Date")) + case _ => fail() + } + f4 match { + case FieldDefinition(name, NamedType(typeName, _), _, _, _, _, _) => + assert(name == "x") + assert(typeName == List("Map[String, String]")) + case _ => fail() + } + case _ => fail() } - } + case _ => fail() } } - it should "parse a type with fields with defaults" in { - val Success(ast) = SchemaParser.parse( - """type Person { + test("parse a type with fields with defaults") { + val Success(ast) = SchemaParser.parse("""type Person { | name: String = "x" | age: Int! = 0 | xs: [java.util.Date] = [] |}""".stripMargin) // println(ast) - inside(ast) { case Document(_, foo :: Nil, _, _, _) => - inside(foo) { case ObjectTypeDefinition(name, _, Nil, f1 :: f2 :: f3 :: Nil, _, _, _, _) => - name shouldEqual "Person" - inside(f1) { case FieldDefinition(name, NamedType(typeName :: Nil, _), _ , Some(StringValue(v, _, _)), _, _, _) => - name shouldEqual "name" - typeName shouldEqual "String" - v shouldEqual "x" - } - inside(f2) { case FieldDefinition(name, NotNullType(NamedType(typeName :: Nil, _), _), _, Some(BigIntValue(v, _, _)), _, _, _) => - name shouldEqual "age" - typeName shouldEqual "Int" - v shouldEqual 0 - } - inside(f3) { case FieldDefinition(name, ListType(NamedType(typeName, _), _), _, Some(ListValue(xs, _, _)), _, _, _) => - name shouldEqual "xs" - typeName shouldEqual List("java", "util", "Date") - xs shouldEqual Nil + ast match { + case Document(_, foo :: Nil, _, _, _) => + foo match { + case ObjectTypeDefinition(name, _, Nil, f1 :: f2 :: f3 :: Nil, _, _, _, _) => + assert(name == "Person") + + f1 match { + case FieldDefinition(name, NamedType(typeName :: Nil, _), _, Some(StringValue(v, _, _)), _, _, _) => + assert(name == "name") + assert(typeName == "String") + assert(v == "x") + case _ => fail() + } + f2 match { + case FieldDefinition(name, NotNullType(NamedType(typeName :: Nil, _), _), _, Some(BigIntValue(v, _, _)), _, _, _) => + assert(name == "age") + assert(typeName == "Int") + assert(v == 0) + case _ => fail() + } + f3 match { + case FieldDefinition(name, ListType(NamedType(typeName, _), _), _, Some(ListValue(xs, _, _)), _, _, _) => + assert(name == "xs") + assert(typeName == List("java", "util", "Date")) + assert(xs == Nil) + case _ => fail() + } + case _ => fail() } - } + case _ => fail() } } - it should "parse a type with fields with since directive" in { - val Success(ast) = SchemaParser.parse( - """type Person { + test("parse a type with fields with since directive") { + val Success(ast) = SchemaParser.parse("""type Person { | name: String @since("0.1.0") |}""".stripMargin) // println(ast) - inside(ast) { case Document(_, foo :: Nil, _, _, _) => - inside(foo) { case ObjectTypeDefinition(name, _, Nil, f1 :: Nil, _, _, _, _) => - name shouldEqual "Person" - inside(f1) { case FieldDefinition(name, NamedType(typeName :: Nil, _), Nil, _, dir :: Nil, _, _) => - name shouldEqual "name" - typeName shouldEqual "String" - inside(dir) { case Directive(name, Argument(argName, v, _, _) :: Nil, _, _) => - name shouldEqual "since" - argName shouldEqual None - inside(v) { case StringValue(value, _, _) => - value shouldEqual "0.1.0" + ast match { + case Document(_, foo :: Nil, _, _, _) => + foo match { + case ObjectTypeDefinition(name, _, Nil, f1 :: Nil, _, _, _, _) => + assert(name == "Person") + f1 match { + case FieldDefinition(name, NamedType(typeName :: Nil, _), Nil, _, dir :: Nil, _, _) => + assert(name == "name") + assert(typeName == "String") + + dir match { + case Directive(name, Argument(argName, v, _, _) :: Nil, _, _) => + assert(name == "since") + assert(argName == None) + v match { + case StringValue(value, _, _) => assert(value == "0.1.0") + case _ => fail() + } + case _ => fail() + } + case _ => fail() } - } + case _ => fail() } - } + case _ => fail() } } - it should "parse an interface" in { - val Success(ast) = SchemaParser.parse( - """interface Entity {} + test("parse an interface") { + val Success(ast) = SchemaParser.parse("""interface Entity {} | |type Person implements Entity { | age: Int |}""".stripMargin) // println(ast) - inside(ast) { case Document(_, intf :: person :: Nil, _, _, _) => - inside(intf) { case InterfaceTypeDefinition(name, _, _, _, _, _, _, _) => - name shouldEqual "Entity" - } - inside(person) { case ObjectTypeDefinition(name, _, NamedType(parentTypeName :: Nil, _) :: Nil, f1 :: Nil, _, _, _, _) => - name shouldEqual "Person" - parentTypeName shouldEqual "Entity" - } + ast match { + case Document(_, intf :: person :: Nil, _, _, _) => + intf match { + case InterfaceTypeDefinition(name, _, _, _, _, _, _, _) => + assert(name == "Entity") + case _ => fail() + } + person match { + case ObjectTypeDefinition(name, _, NamedType(parentTypeName :: Nil, _) :: Nil, f1 :: Nil, _, _, _, _) => + assert(name == "Person") + assert(parentTypeName == "Entity") + case _ => fail() + } + case _ => fail() } } - it should "parse an interface with a parent" in { - val Success(ast) = SchemaParser.parse( - """interface Fruit {} + test("parse an interface with a parent") { + val Success(ast) = SchemaParser.parse("""interface Fruit {} | |interface Citrus implements Fruit { |}""".stripMargin) // println(ast) - inside(ast) { case Document(_, fruit :: citrus :: Nil, _, _, _) => - inside(fruit) { case InterfaceTypeDefinition(name, _, _, _, _, _, _, _) => - name shouldEqual "Fruit" - } - inside(citrus) { case InterfaceTypeDefinition(name, _, NamedType(parentTypeName :: Nil, _) :: Nil, _, _, _, _, _) => - name shouldEqual "Citrus" - parentTypeName shouldEqual "Fruit" - } + ast match { + case Document(_, fruit :: citrus :: Nil, _, _, _) => + fruit match { + case InterfaceTypeDefinition(name, _, _, _, _, _, _, _) => + assert(name == "Fruit") + case _ => fail() + } + citrus match { + case InterfaceTypeDefinition(name, _, NamedType(parentTypeName :: Nil, _) :: Nil, _, _, _, _, _) => + assert(name == "Citrus") + assert(parentTypeName == "Fruit") + case _ => fail() + } + case _ => fail() } } - it should "parse an enumueration" in { - val Success(ast) = SchemaParser.parse( - """enum Episode { + test("parse an enumueration") { + val Success(ast) = SchemaParser.parse("""enum Episode { | NEW_HOPE | EMPIRE | JEDI |}""".stripMargin) // println(ast) - inside(ast) { case Document(_, episode :: Nil, _, _, _) => - inside(episode) { case EnumTypeDefinition(name, ns, v1 :: v2 :: v3 :: Nil, _, _, _, _) => - name shouldEqual "Episode" - v1.name shouldEqual "NEW_HOPE" - v2.name shouldEqual "EMPIRE" - v3.name shouldEqual "JEDI" - } + ast match { + case Document(_, episode :: Nil, _, _, _) => + episode match { + case EnumTypeDefinition(name, ns, v1 :: v2 :: v3 :: Nil, _, _, _, _) => + assert(name == "Episode") + assert(v1.name == "NEW_HOPE") + assert(v2.name == "EMPIRE") + assert(v3.name == "JEDI") + case _ => fail() + } + case _ => fail() } } - it should "parse a type with package" in { - val Success(ast) = SchemaParser.parse( - """package com.example @target(Scala) + test("parse a type with package") { + val Success(ast) = SchemaParser.parse("""package com.example @target(Scala) | |type Foo {}""".stripMargin) // println(ast) - inside(ast) { case Document(Some(pkg), ObjectTypeDefinition(name, _, Nil, Nil, _, _, _, _) :: Nil, _, _, _) => - name shouldEqual "Foo" - inside(pkg) { case PackageDecl(names, dir :: Nil, _, _) => - names shouldEqual List("com", "example") - } + ast match { + case Document(Some(pkg), ObjectTypeDefinition(name, _, Nil, Nil, _, _, _, _) :: Nil, _, _, _) => + assert(name == "Foo") + pkg match { + case PackageDecl(names, dir :: Nil, _, _) => + assert(names == List("com", "example")) + case _ => fail() + } + case _ => fail() } } } diff --git a/library/src/test/scala/JsonJavaCodeGenSpec.scala b/library/src/test/scala/JsonJavaCodeGenSpec.scala index 4fd8599..41ab392 100644 --- a/library/src/test/scala/JsonJavaCodeGenSpec.scala +++ b/library/src/test/scala/JsonJavaCodeGenSpec.scala @@ -12,8 +12,7 @@ class JsonJavaCodeGenSpec extends GCodeGenSpec("Java") { val enumeration = JsonParser.EnumTypeDefinition.parse(simpleEnumerationExample) val code = mkJavaCodeGen generate enumeration - code.head._2.unindent should equalLines ( - """/** Example of simple enumeration */ + code.head._2.unindent should equalLines("""/** Example of simple enumeration */ |public enum simpleEnumerationExample { | /** First symbol */ | first, @@ -26,9 +25,9 @@ class JsonJavaCodeGenSpec extends GCodeGenSpec("Java") { val protocol = JsonParser.InterfaceTypeDefinition.parseInterface(simpleInterfaceExample) val code = mkJavaCodeGen generate protocol - code.head._2.unindent should equalLines ( + code.head._2.unindent should equalLines( """/** example of simple interface */ - |public abstract class simpleInterfaceExample implements java.io.Serializable { + |public abstract class simpleInterfaceExample implements Interface1, Interface2, java.io.Serializable { | // Some extra code... | private type field; | protected simpleInterfaceExample(type _field) { @@ -54,7 +53,8 @@ class JsonJavaCodeGenSpec extends GCodeGenSpec("Java") { | public String toString() { | return "custom"; | } - |}""".stripMargin.unindent) + |}""".stripMargin.unindent + ) } override def interfaceGenerateOneChild = { @@ -62,8 +62,7 @@ class JsonJavaCodeGenSpec extends GCodeGenSpec("Java") { val code = mkJavaCodeGen generate protocol val code1 = code.toList(0)._2.unindent val code2 = code.toList(1)._2.unindent - code1 should equalLines ( - """/** example of interface */ + code1 should equalLines("""/** example of interface */ |public abstract class oneChildInterfaceExample implements java.io.Serializable { | | private int field; @@ -91,8 +90,8 @@ class JsonJavaCodeGenSpec extends GCodeGenSpec("Java") { | return "oneChildInterfaceExample(" + "field: " + field() + ")"; | } |}""".stripMargin.unindent) - code2 should equalLines ( - """public final class childRecord extends oneChildInterfaceExample { + code2 should equalLines( + """public final class childRecord extends oneChildInterfaceExample implements java.io.Serializable { | public static childRecord create(int _field, int _x) { | return new childRecord(_field, _x); | } @@ -129,14 +128,15 @@ class JsonJavaCodeGenSpec extends GCodeGenSpec("Java") { | public String toString() { | return "childRecord(" + "field: " + field() + ", " + "x: " + x() + ")"; | } - |}""".stripMargin.unindent) + |}""".stripMargin.unindent + ) } override def interfaceGenerateNested = { val protocol = JsonParser.InterfaceTypeDefinition.parseInterface(nestedInterfaceExample) val code = mkJavaCodeGen generate protocol - code.mapValues(_.unindent).toMap should equalMapLines ( + code.mapValues(_.unindent).toMap should equalMapLines( ListMap( new File("nestedProtocolExample.java") -> """/** example of nested protocols */ @@ -161,9 +161,8 @@ class JsonJavaCodeGenSpec extends GCodeGenSpec("Java") { | return "nestedProtocolExample(" + ")"; | } |}""".stripMargin.unindent, - new File("nestedProtocol.java") -> - """public abstract class nestedProtocol extends nestedProtocolExample { + """public abstract class nestedProtocol extends nestedProtocolExample implements java.io.Serializable { | protected nestedProtocol() { | super(); | } @@ -184,9 +183,8 @@ class JsonJavaCodeGenSpec extends GCodeGenSpec("Java") { | return "nestedProtocol(" + ")"; | } |}""".stripMargin.unindent, - new File("ChildRecord.java") -> - """public final class ChildRecord extends nestedProtocol { + """public final class ChildRecord extends nestedProtocol implements java.io.Serializable { | public static ChildRecord create() { | return new ChildRecord(); | } @@ -213,14 +211,15 @@ class JsonJavaCodeGenSpec extends GCodeGenSpec("Java") { | return "ChildRecord(" + ")"; | } |}""".stripMargin.unindent - )) + ) + ) } override def interfaceGenerateMessages = { val schema = JsonParser.Document.parse(generateArgDocExample) val code = mkJavaCodeGen generate schema - code.mapValues(_.withoutEmptyLines).toMap should equalMapLines ( + code.mapValues(_.withoutEmptyLines).toMap should equalMapLines( ListMap( new File("generateArgDocExample.java") -> """public abstract class generateArgDocExample implements java.io.Serializable { @@ -258,14 +257,15 @@ class JsonJavaCodeGenSpec extends GCodeGenSpec("Java") { | return "generateArgDocExample(" + "field: " + field() + ")"; | } |}""".stripMargin.withoutEmptyLines - )) + ) + ) } override def recordGenerateSimple = { val record = JsonParser.ObjectTypeDefinition.parse(simpleRecordExample) val code = mkJavaCodeGen generate record - code.mapValues(_.unindent).toMap should equalMapLines ( + code.mapValues(_.unindent).toMap should equalMapLines( ListMap( new File("simpleRecordExample.java") -> """/** Example of simple record */ @@ -306,14 +306,15 @@ class JsonJavaCodeGenSpec extends GCodeGenSpec("Java") { | return "simpleRecordExample(" + "field: " + field() + ")"; | } |}""".stripMargin.unindent - )) + ) + ) } override def recordGrowZeroToOneField = { val record = JsonParser.ObjectTypeDefinition.parse(growableAddOneFieldExample) val code = mkJavaCodeGen generate record - code.mapValues(_.unindent).toMap should equalMapLines ( + code.mapValues(_.unindent).toMap should equalMapLines( ListMap( new File("growableAddOneField.java") -> """public final class growableAddOneField implements java.io.Serializable { @@ -361,14 +362,15 @@ class JsonJavaCodeGenSpec extends GCodeGenSpec("Java") { | return "growableAddOneField(" + "field: " + field() + ")"; | } |}""".stripMargin.unindent - )) + ) + ) } override def recordGrowZeroToOneToTwoFields = { val record = JsonParser.ObjectTypeDefinition.parse(growableZeroToOneToTwoFieldsJavaExample) val code = mkJavaCodeGen generate record - code.mapValues(_.unindent).toMap should equalMapLines ( + code.mapValues(_.unindent).toMap should equalMapLines( ListMap( new File("Foo.java") -> """public final class Foo implements java.io.Serializable { @@ -461,14 +463,15 @@ class JsonJavaCodeGenSpec extends GCodeGenSpec("Java") { | return "Foo(" + "x: " + x() + ", " + "y: " + y() + ")"; | } |}""".stripMargin.unindent - )) + ) + ) } override def recordPrimitives: Unit = { val record = JsonParser.ObjectTypeDefinition.parse(primitiveTypesExample2) val code = mkJavaCodeGen generate record - code.mapValues(_.unindent).toMap should equalMapLines ( + code.mapValues(_.unindent).toMap should equalMapLines( ListMap( new File("primitiveTypesExample2.java") -> """public final class primitiveTypesExample2 implements java.io.Serializable { @@ -516,14 +519,15 @@ class JsonJavaCodeGenSpec extends GCodeGenSpec("Java") { return "primitiveTypesExample2(" + "smallBoolean: " + smallBoolean() + ", " + "bigBoolean: " + bigBoolean() + ")"; } }""".stripMargin.unindent - )) + ) + ) } override def recordWithModifier: Unit = { val record = JsonParser.ObjectTypeDefinition.parse(modifierExample) val code = mkJavaCodeGen generate record - code.mapValues(_.unindent).toMap should equalMapLines ( + code.mapValues(_.unindent).toMap should equalMapLines( ListMap( new File("modifierExample.java") -> """sealed class modifierExample implements java.io.Serializable { @@ -562,14 +566,15 @@ class JsonJavaCodeGenSpec extends GCodeGenSpec("Java") { | return "modifierExample(" + "field: " + field() + ")"; | } |}""".stripMargin.unindent - )) + ) + ) } override def schemaGenerateTypeReferences = { val schema = JsonParser.Document.parse(primitiveTypesExample) val code = mkJavaCodeGen generate schema - code.head._2.unindent should equalLines ( + code.head._2.unindent should equalLines( """public final class primitiveTypesExample implements java.io.Serializable { | | public static primitiveTypesExample create(int _simpleInteger, com.example.MyLazy _lazyInteger, int[] _arrayInteger, java.util.Optional _optionInteger, com.example.MyLazy _lazyArrayInteger, com.example.MyLazy> _lazyOptionInteger) { @@ -659,14 +664,15 @@ class JsonJavaCodeGenSpec extends GCodeGenSpec("Java") { | public String toString() { | return super.toString(); // Avoid evaluating lazy members in toString to avoid circularity. | } - |}""".stripMargin.unindent) + |}""".stripMargin.unindent + ) } override def schemaGenerateTypeReferencesNoLazy = { val schema = JsonParser.Document.parse(primitiveTypesNoLazyExample) val code = mkJavaCodeGen generate schema - code.mapValues(_.unindent).toMap should equalMapLines ( + code.mapValues(_.unindent).toMap should equalMapLines( ListMap( new File("primitiveTypesNoLazyExample.java") -> """public final class primitiveTypesNoLazyExample implements java.io.Serializable { @@ -714,22 +720,22 @@ class JsonJavaCodeGenSpec extends GCodeGenSpec("Java") { | return "primitiveTypesNoLazyExample(" + "simpleInteger: " + simpleInteger() + ", " + "arrayInteger: " + arrayInteger() + ")"; | } |}""".stripMargin.unindent - )) + ) + ) } override def schemaGenerateComplete = { val schema = JsonParser.Document.parse(completeExample) val code = mkJavaCodeGen generate schema - code.mapValues(_.unindent).toMap should equalMapLines (completeExampleCodeJava.mapValues(_.unindent).toMap) + code.mapValues(_.unindent).toMap should equalMapLines(completeExampleCodeJava.mapValues(_.unindent).toMap) } override def schemaGenerateCompletePlusIndent = { val schema = JsonParser.Document.parse(completeExample) val code = mkJavaCodeGen generate schema - code.mapValues(_.withoutEmptyLines).toMap should equalMapLines (completeExampleCodeJava.mapValues(_.withoutEmptyLines).toMap) + code.mapValues(_.withoutEmptyLines).toMap should equalMapLines(completeExampleCodeJava.mapValues(_.withoutEmptyLines).toMap) } def mkJavaCodeGen: JavaCodeGen = - new JavaCodeGen("com.example.MyLazy", CodeGen.javaOptional, CodeGen.instantiateJavaOptional, - wrapOption = true) + new JavaCodeGen("com.example.MyLazy", CodeGen.javaOptional, CodeGen.instantiateJavaOptional, wrapOption = true) } diff --git a/library/src/test/scala/JsonSchemaExample.scala b/library/src/test/scala/JsonSchemaExample.scala index 2c8695b..2e769fb 100644 --- a/library/src/test/scala/JsonSchemaExample.scala +++ b/library/src/test/scala/JsonSchemaExample.scala @@ -722,7 +722,6 @@ object PriorityLevel { | return super.toString(); // Avoid evaluating lazy members in toString to avoid circularity. | } |}""".stripMargin, - new File("com/example/PriorityLevel.java") -> """package com.example; |/** Priority levels */ @@ -732,11 +731,10 @@ object PriorityLevel { | Medium, | High; |}""".stripMargin, - new File("com/example/GreetingWithAttachments.java") -> """package com.example; |/** A Greeting with attachments */ - |public final class GreetingWithAttachments extends com.example.Greetings { + |public final class GreetingWithAttachments extends com.example.Greetings implements java.io.Serializable { | public static GreetingWithAttachments create(com.example.MyLazy _message, java.io.File[] _attachments) { | return new GreetingWithAttachments(_message, _attachments); | } @@ -781,10 +779,9 @@ object PriorityLevel { | return super.toString(); // Avoid evaluating lazy members in toString to avoid circularity. | } |}""".stripMargin, - new File("com/example/GreetingExtra.java") -> """package com.example; -public abstract class GreetingExtra extends com.example.Greetings { +public abstract class GreetingExtra extends com.example.Greetings implements java.io.Serializable { private String[] extra; protected GreetingExtra(com.example.MyLazy _message, String[] _extra) { @@ -809,10 +806,9 @@ public abstract class GreetingExtra extends com.example.Greetings { return "Welcome, extra!"; } }""", - new File("com/example/GreetingExtraImpl.java") -> """package com.example; -public final class GreetingExtraImpl extends com.example.GreetingExtra { +public final class GreetingExtraImpl extends com.example.GreetingExtra implements java.io.Serializable { public static GreetingExtraImpl create(com.example.MyLazy _message, String[] _extra, String _x) { return new GreetingExtraImpl(_message, _extra, _x); @@ -860,11 +856,10 @@ public final class GreetingExtraImpl extends com.example.GreetingExtra { return "Welcome, extra implosion!"; } }""", - new File("com/example/Greetings.java") -> """package com.example; |/** A greeting interface */ - |public abstract class Greetings implements java.io.Serializable { + |public abstract class Greetings implements com.example.GreetingsLike, java.io.Serializable { | | private com.example.MyLazy message; | private com.example.GreetingHeader header; @@ -906,11 +901,10 @@ public final class GreetingExtraImpl extends com.example.GreetingExtra { | return super.toString(); // Avoid evaluating lazy members in toString to avoid circularity. | } |}""".stripMargin, - new File("com/example/SimpleGreeting.java") -> """package com.example; |/** A Greeting in its simplest form */ - |public final class SimpleGreeting extends com.example.Greetings { + |public final class SimpleGreeting extends com.example.Greetings implements java.io.Serializable { | public static SimpleGreeting create(com.example.MyLazy _message) { | return new SimpleGreeting(_message); | } @@ -950,7 +944,8 @@ public final class GreetingExtraImpl extends com.example.GreetingExtra { | public String toString() { | return super.toString(); // Avoid evaluating lazy members in toString to avoid circularity. | } - |}""".stripMargin) + |}""".stripMargin + ) val completeExampleCodeCodec = """/** diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 7ce7901..6c41d49 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -47,4 +47,5 @@ object Dependencies { "org.parboiled" %% "parboiled" % v } val diffutils = "com.googlecode.java-diff-utils" % "diffutils" % "1.3.0" + val verify = "com.eed3si9n.verify" %% "verify" % "0.2.0" }