diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml index b9af1b9f2..07d238c2f 100644 --- a/.github/workflows/test-pr.yaml +++ b/.github/workflows/test-pr.yaml @@ -173,9 +173,9 @@ jobs: - name: Install scala if: ${{ contains(matrix.fixture, 'scala3') }} uses: VirtusLab/scala-cli-setup@main + - run: echo '@main def hello() = println("We need this spam print statement for bloop to exit correctly...")' | scala-cli _ if: ${{ contains(matrix.fixture, 'scala3') }} - - run: QUICKTEST=true FIXTURE=${{ matrix.fixture }} npm test test-complete: diff --git a/packages/quicktype-core/src/language/Scala3.ts b/packages/quicktype-core/src/language/Scala3.ts index 489749cea..a0dafa725 100644 --- a/packages/quicktype-core/src/language/Scala3.ts +++ b/packages/quicktype-core/src/language/Scala3.ts @@ -13,20 +13,10 @@ import { isNumeric, legalizeCharacters, splitIntoWords - } from "../support/Strings"; import { assertNever } from "../support/Support"; import { TargetLanguage } from "../TargetLanguage"; -import { - ArrayType, - ClassProperty, - ClassType, - EnumType, - MapType, - ObjectType, - Type, - UnionType -} from "../Type"; +import { ArrayType, ClassProperty, ClassType, EnumType, MapType, ObjectType, Type, UnionType } from "../Type"; import { matchType, nullableFromUnion, removeNullFromUnion } from "../TypeUtils"; import { RenderContext } from "../Renderer"; @@ -37,18 +27,22 @@ export enum Framework { } export const scala3Options = { - framework: new EnumOption("framework", "Serialization framework", + framework: new EnumOption( + "framework", + "Serialization framework", [ ["just-types", Framework.None], ["circe", Framework.Circe], - ["upickle", Framework.Upickle], - ] - , undefined), + ["upickle", Framework.Upickle] + ], + undefined + ), packageName: new StringOption("package", "Package", "PACKAGE", "quicktype") }; // Use backticks for param names with symbols const invalidSymbols = [ + "?", ":", "-", "+", @@ -135,13 +129,17 @@ const keywords = [ "Enum" ]; - /** * Check if given parameter name should be wrapped in a backtick * @param paramName */ const shouldAddBacktick = (paramName: string): boolean => { - return keywords.some(s => paramName === s) || invalidSymbols.some(s => paramName.includes(s)) || !isNaN(+parseFloat(paramName)) || !isNaN(parseInt(paramName.charAt(0))); + return ( + keywords.some(s => paramName === s) || + invalidSymbols.some(s => paramName.includes(s)) || + !isNaN(+parseFloat(paramName)) || + !isNaN(parseInt(paramName.charAt(0))) + ); }; const wrapOption = (s: string, optional: boolean): string => { @@ -208,7 +206,7 @@ export class Scala3Renderer extends ConvenienceRenderer { } protected forbiddenForEnumCases(_: EnumType, _enumName: Name): ForbiddenWordsInfo { - return { names: [], includeGlobalForbidden: true }; + return { names: ["_"], includeGlobalForbidden: true }; } protected forbiddenForUnionMembers(_u: UnionType, _unionName: Name): ForbiddenWordsInfo { @@ -248,10 +246,10 @@ export class Scala3Renderer extends ConvenienceRenderer { delimiter === "curly" ? ["{", "}"] : delimiter === "paren" - ? ["(", ")"] - : delimiter === "none" - ? ["", ""] - : ["{", "})"]; + ? ["(", ")"] + : delimiter === "none" + ? ["", ""] + : ["{", "})"]; this.emitLine(line, " ", open); this.indent(f); this.emitLine(close); @@ -353,6 +351,7 @@ export class Scala3Renderer extends ConvenienceRenderer { let count = c.getProperties().size; let first = true; this.forEachClassProperty(c, "none", (_, jsonName, p) => { + //console.log(jsonName); // Why is this in different order!!!!!! const nullable = p.type.kind === "union" && nullableFromUnion(p.type as UnionType) !== null; const nullableOrOptional = p.isOptional || p.type.kind === "null" || nullable; const last = --count === 0; @@ -393,29 +392,37 @@ export class Scala3Renderer extends ConvenienceRenderer { } protected emitClassDefinitionMethods() { - this.emitLine(")"); + this.emitLine(")"); } protected emitEnumDefinition(e: EnumType, enumName: Name): void { + //console.log("vanilla"); this.emitDescription(this.descriptionForType(e)); this.emitBlock( ["enum ", enumName, " : "], () => { let count = e.cases.size; - if (count > 0) { this.emitItem("\t case ") }; + if (count > 0) { + this.emitItem("\t case "); + } this.forEachEnumCase(e, "none", (name, jsonName) => { - if (!(jsonName == "")) { + //console.log(jsonName); + if (!(jsonName === "")) { const backticks = shouldAddBacktick(jsonName) || jsonName.includes(" ") || - !isNaN(parseInt(jsonName.charAt(0))) - if (backticks) { this.emitItem("`") } + !isNaN(parseInt(jsonName.charAt(0))); + if (backticks) { + this.emitItem("`"); + } this.emitItemOnce([name]); - if (backticks) { this.emitItem("`") } + if (backticks) { + this.emitItem("`"); + } if (--count > 0) this.emitItem([","]); } else { - --count + --count; } }); }, @@ -433,7 +440,7 @@ export class Scala3Renderer extends ConvenienceRenderer { this.emitDescription(this.descriptionForType(u)); const [maybeNull, nonNulls] = removeNullFromUnion(u, sortBy); - const theTypes: Array = [] + const theTypes: Array = []; this.forEachUnionMember(u, nonNulls, "none", null, (_, t) => { theTypes.push(this.scalaType(t)); }); @@ -470,82 +477,265 @@ export class Scala3Renderer extends ConvenienceRenderer { } export class UpickleRenderer extends Scala3Renderer { + seenUnionTypes: Array = []; + + protected upickleEncoderForType(t: Type, _ = false, noOptional = false, paramName: string = ""): Sourcelike { + return matchType( + t, + _anyType => ["OptionPickler.writeJs(", paramName, ")"], + _nullType => ["OptionPickler.writeJs(", paramName, ")"], + _boolType => ["OptionPickler.writeJs(", paramName, ")"], + _integerType => ["OptionPickler.writeJs(", paramName, ")"], + _doubleType => ["OptionPickler.writeJs(", paramName, ")"], + _stringType => ["OptionPickler.writeJs(", paramName, ")"], + arrayType => ["OptionPickler.writeJs[", this.scalaType(arrayType.items), "].apply(", paramName, ")"], + classType => ["OptionPickler.writeJs[", this.scalaType(classType), "].apply(", paramName, ")"], + mapType => ["OptionPickler.writeJs[String,", this.scalaType(mapType.values), "].apply(", paramName, ")"], + _ => ["OptionPickler.writeJs(", paramName, ")"], + unionType => { + const nullable = nullableFromUnion(unionType); + if (nullable !== null) { + if (noOptional) { + return ["OptionPickler.writeJs[", this.nameForNamedType(nullable), "]"]; + } else { + return ["OptionPickler.writeJs[Option[", this.nameForNamedType(nullable), "]]"]; + } + } + return ["OptionPickler.writeJs[", this.nameForNamedType(unionType), "]"]; + } + ); + } protected emitClassDefinitionMethods() { - this.emitLine(") derives ReadWriter "); + this.emitLine(") derives OptionPickler.ReadWriter "); + } + + protected anySourceType(optional: boolean): Sourcelike { + return [wrapOption("ujson.Value", optional)]; } protected emitHeader(): void { super.emitHeader(); + const optionPickler = `object OptionPickler extends upickle.AttributeTagged : + import upickle.default.Writer + import upickle.default.Reader + override implicit def OptionWriter[T: Writer]: Writer[Option[T]] = + implicitly[Writer[T]].comap[Option[T]] { + case None => null.asInstanceOf[T] + case Some(x) => x + } - this.emitLine("import upickle.default.*"); - this.ensureBlankLine(); + override implicit def OptionReader[T: Reader]: Reader[Option[T]] = { + new Reader.Delegate[Any, Option[T]](implicitly[Reader[T]].map(Some(_))){ + override def visitNull(index: Int) = None + } } - -} - -export class Smithy4sRenderer extends Scala3Renderer { - - protected emitHeader(): void { - if (this.leadingComments !== undefined) { - this.emitCommentLines(this.leadingComments); - } else { - this.emitUsageHeader(); +end OptionPickler + +object JsonExt: + val valueReader = OptionPickler.readwriter[ujson.Value] + def badMerge[T](r1: => OptionPickler.Reader[?], rest: OptionPickler.Reader[?]*): OptionPickler.Reader[T] = valueReader.map { json => + var t: T | Null = null + val stack = Vector.newBuilder[Throwable] + (r1 +: rest).foreach { reader => + if t == null then + try + t = OptionPickler.read[T](json, trace = true)(using reader.asInstanceOf[OptionPickler.Reader[T]]) + catch + case exc => stack += exc } + if t != null then t.nn else throw new Exception(json.toString(), stack.result().headOption.getOrElse(null)) + } + extension [T](r: OptionPickler.Reader[T]) def widen[K >: T] = r.map(_.asInstanceOf[K]) +end JsonExt +`; + // const singletonPickler = `given singletonStringPickler[A <: Singleton](using A <:< String): OptionPickler.ReadWriter[A] = OptionPickler.readwriter[ujson.Value].bimap[A]( + // _.toString(), + // str => { + // str.toString().asInstanceOf[A]() + // } + // )`; + this.emitMultiline(optionPickler); this.ensureBlankLine(); - this.emitLine("namespace ", this._scalaOptions.packageName); - this.ensureBlankLine(); + // this.emitMultiline(singletonPickler); } - protected emitTopLevelArray(t: ArrayType, name: Name): void { - const elementType = this.scalaType(t.items); - this.emitLine(["list ", name, " { member : ", elementType, "}"]); + protected override emitEmptyClassDefinition(c: ClassType, className: Name): void { + super.emitEmptyClassDefinition(c, className); + this.emitItem(" derives OptionPickler.ReadWriter"); + this.ensureBlankLine(); } - protected emitTopLevelMap(t: MapType, name: Name): void { - const elementType = this.scalaType(t.values); - this.emitLine(["map ", name, " { map[ key : String , value : ", elementType, "}"]); - } + protected override emitUnionDefinition(u: UnionType, unionName: Name): void { + // function sortBy(t: Type): string { + // const kind = t.kind; + // if (kind === "class") return kind; + // return "_" + kind; + // } - protected emitEmptyClassDefinition(c: ClassType, className: Name): void { - this.emitDescription(this.descriptionForType(c)); - this.emitLine("structure ", className, "{}"); - } + this.emitDescription(this.descriptionForType(u)); + + const [maybeNull, nonNulls] = removeNullFromUnion(u, false); + const theTypes: Array = []; + this.forEachUnionMember(u, nonNulls, "none", null, (_, t) => { + theTypes.push(this.scalaType(t)); + }); + if (maybeNull !== null) { + theTypes.push(this.nameForUnionMember(u, maybeNull)); + } + this.emitItem(["type ", unionName, " = "]); + theTypes.forEach((t, i) => { + this.emitItem(i === 0 ? t : [" | ", t]); + }); + const thisUnionType = theTypes.map(x => this.sourcelikeToString(x)).join(" | "); + this.ensureBlankLine(); + if (!this.seenUnionTypes.some(y => y === thisUnionType)) { + this.seenUnionTypes.push(thisUnionType); + const sourceLikeTypes: Array<[Sourcelike, Type]> = []; + this.forEachUnionMember(u, nonNulls, "none", null, (_, t) => { + sourceLikeTypes.push([this.scalaType(t), t]); + }); + if (maybeNull !== null) { + sourceLikeTypes.push([this.nameForUnionMember(u, maybeNull), maybeNull]); + } + this.ensureBlankLine(); + this.emitLine([ + "given unionWriter", + unionName, + ": OptionPickler.Reader[", + unionName, + "] = JsonExt.badMerge[", + unionName, + "](" + ]); + this.indent(() => { + sourceLikeTypes.forEach(t => { + this.emitLine(["summon[OptionPickler.Reader[", t[0], "]],"]); + }); + this.emitLine(")"); + }); + this.ensureBlankLine(); + this.emitLine([ + "given unionReader", + unionName, + ": OptionPickler.Writer[", + unionName, + "] = OptionPickler.writer[ujson.Value].comap[", + unionName, + "]{ _v =>" + ]); + this.indent(() => { + this.emitLine("(_v: @unchecked) match "); + this.indent(() => { + sourceLikeTypes.forEach(t => { + this.emitLine(["case v: ", t[0], " => OptionPickler.write[", t[0], "](v)"]); + }); + }); + }); + this.emitLine("}"); + } + } protected emitEnumDefinition(e: EnumType, enumName: Name): void { this.emitDescription(this.descriptionForType(e)); - this.ensureBlankLine(); - this.emitItem(["enum ", enumName, " { "]); - let count = e.cases.size; - this.forEachEnumCase(e, "none", (name, jsonName) => { - // if (!(jsonName == "")) { - /* const backticks = - shouldAddBacktick(jsonName) || - jsonName.includes(" ") || - !isNaN(parseInt(jsonName.charAt(0))) - if (backticks) {this.emitItem("`")} else */ - this.emitLine(); - this.emitItem([name, " = \"", jsonName, "\""]); - // if (backticks) {this.emitItem("`")} - if (--count > 0) this.emitItem([","]); - //} else { - //--count - //} + let hasBlank = false; + this.forEachEnumCase(e, "none", (_, jsonName) => { + if (jsonName.trim() === "") { + hasBlank = true; + } }); this.ensureBlankLine(); - this.emitItem(["}"]); + if (hasBlank) { + //console.log("enumName: " + enumName + " has blank"); + this.emitItem(["type ", enumName, ' = "" | ', enumName, "NonBlank"]); + this.ensureBlankLine(); + this.emitLine([ + "given singleton", + enumName, + 'Pickler[A <: "" | ', + enumName, + "NonBlank]: OptionPickler.ReadWriter[A] = " + ]); - } + this.indent(() => { + this.emitLine(["OptionPickler.readwriter[ujson.Value].bimap[A]("]); + this.indent(() => { + this.emitLine(["_.toString(),"]); + this.emitLine(["str => {"]); + this.indent(() => { + this.emitLine(["val str2 = str.str"]); + this.emitLine(["str2 match {"]); + this.indent(() => { + this.emitLine(['case _ if str2.length == 0 => "".asInstanceOf[A] ']); + this.emitLine([ + "case parseable =>", + enumName, + "NonBlank.valueOf(parseable).asInstanceOf[A] " + ]); + }); + this.emitLine(["}"]); + }); + this.emitLine(["}"]); + }); + this.emitLine([")"]); + }); + this.ensureBlankLine(); + //let count = e.cases.size; + this.ensureBlankLine(); + this.emitLine(["enum ", enumName, "NonBlank derives OptionPickler.ReadWriter: "]); + this.indent(() => { + let count = e.cases.size; + this.forEachEnumCase(e, "none", (_, jsonName) => { + if (!(jsonName.trim() === "")) { + let strBuild = ""; + const backticks = + shouldAddBacktick(jsonName) || + jsonName.includes(" ") || + !isNaN(parseInt(jsonName.charAt(0))); + this.emitItem(["case "]); + if (backticks) { + strBuild = strBuild + "`"; + } + strBuild = strBuild + jsonName; + if (backticks) { + strBuild = strBuild + "`"; + } + // if (--count > 0) strBuild + ","; + this.emitLine([strBuild]); + } + }); + }); + } else { + //console.log("enumName: " + enumName + " has non blank"); + this.emitLine(["enum ", enumName, " derives OptionPickler.ReadWriter: "]); + this.indent(() => { + let count = e.cases.size; + this.forEachEnumCase(e, "none", (_, jsonName) => { + let strBuild = ""; + const backticks = + shouldAddBacktick(jsonName) || jsonName.includes(" ") || !isNaN(parseInt(jsonName.charAt(0))); + this.emitItem(["case "]); + if (backticks) { + strBuild = strBuild + "`"; + } + strBuild = strBuild + jsonName; + if (backticks) { + strBuild = strBuild + "`"; + } + // if (--count > 0) strBuild + ","; + this.emitLine([strBuild]); + }); + }); + } + } } - export class CirceRenderer extends Scala3Renderer { - seenUnionTypes: Array = []; protected circeEncoderForType(t: Type, _ = false, noOptional = false, paramName: string = ""): Sourcelike { @@ -560,7 +750,7 @@ export class CirceRenderer extends Scala3Renderer { arrayType => ["Encoder.encodeSeq[", this.scalaType(arrayType.items), "].apply(", paramName, ")"], classType => ["Encoder.AsObject[", this.scalaType(classType), "].apply(", paramName, ")"], mapType => ["Encoder.encodeMap[String,", this.scalaType(mapType.values), "].apply(", paramName, ")"], - _ => ["Encoder.encodeString(", paramName, ")"], + enumType => ["summon[Encoder[", this.scalaType(enumType), "]].apply(", paramName, ")"], unionType => { const nullable = nullableFromUnion(unionType); if (nullable !== null) { @@ -592,25 +782,103 @@ export class CirceRenderer extends Scala3Renderer { protected emitEnumDefinition(e: EnumType, enumName: Name): void { this.emitDescription(this.descriptionForType(e)); - this.ensureBlankLine(); - this.emitItem(["type ", enumName, " = "]); - let count = e.cases.size; + let hasBlank = false; this.forEachEnumCase(e, "none", (_, jsonName) => { - // if (!(jsonName == "")) { - /* const backticks = - shouldAddBacktick(jsonName) || - jsonName.includes(" ") || - !isNaN(parseInt(jsonName.charAt(0))) - if (backticks) {this.emitItem("`")} else */ - this.emitItem(["\"", jsonName, "\""]); - // if (backticks) {this.emitItem("`")} - if (--count > 0) this.emitItem([" | "]); - //} else { - //--count - //} + if (jsonName.trim() === "") { + hasBlank = true; + } }); this.ensureBlankLine(); + const isBlank = (str: string): boolean => !str.trim(); + + if (hasBlank) { + //console.log("enumName: " + enumName + " has blank"); + this.emitItem(["type ", enumName, ' = "" | ', enumName, "NonBlank"]); + this.ensureBlankLine(); + this.emitLine([ + "given ", + enumName, + "Enc: Encoder[", + enumName, + "] = Encoder.encodeString.contramap(_.toString())" + ]); + this.emitLine(["given ", enumName, "Dec: Decoder[", enumName, "] = List[Decoder[", enumName, "]]("]); + this.indent(() => { + this.emitLine('Decoder[""].widen,'); + this.emitLine(["Decoder[", enumName, "NonBlank].widen"]); + }); + this.emitLine(").reduceLeft(_ or _)"); + + this.ensureBlankLine(); + //let count = e.cases.size; + this.ensureBlankLine(); + this.emitLine(["enum ", enumName, "NonBlank :"]); + this.indent(() => { + //let count = e.cases.size; + + this.forEachEnumCase(e, "none", (_, jsonName) => { + let strBuild = ""; + const backticks = + shouldAddBacktick(jsonName) || jsonName.includes(" ") || !isNaN(parseInt(jsonName.charAt(0))); + if (!isBlank(jsonName)) { + this.emitItem(["case "]); + } + if (backticks) { + strBuild = strBuild + "`"; + } + strBuild = strBuild + jsonName; + if (backticks) { + strBuild = strBuild + "`"; + } + //if (--count > 0) strBuild + ","; + // don't emit the blank case + if (!isBlank(jsonName)) { + this.emitLine([strBuild]); + } + }); + }); + + this.emitLine([ + "given Decoder[", + enumName, + "NonBlank] = Decoder.decodeString.emapTry(x => Try(", + enumName, + "NonBlank.valueOf(x) ))" + ]); + this.emitLine("given Encoder[", enumName, "NonBlank] = Encoder.encodeString.contramap(_.toString())"); + } else { + //console.log("enumName: " + enumName + " has non blank"); + this.emitLine(["enum ", enumName, " : "]); + + this.indent(() => { + let count = e.cases.size; + this.forEachEnumCase(e, "none", (_, jsonName) => { + let strBuild = ""; + const backticks = + shouldAddBacktick(jsonName) || jsonName.includes(" ") || !isNaN(parseInt(jsonName.charAt(0))); + this.emitItem(["case "]); + if (backticks) { + strBuild = strBuild + "`"; + } + strBuild = strBuild + jsonName; + if (backticks) { + strBuild = strBuild + "`"; + } + // if (--count > 0) strBuild + ","; + this.emitLine([strBuild]); + }); + }); + this.emitLine([ + "given Decoder[", + enumName, + "] = Decoder.decodeString.emapTry(x => Try(", + enumName, + ".valueOf(x) )) " + ]); + this.emitLine(["given Encoder[", enumName, "] = Encoder.encodeString.contramap(_.toString())"]); + this.ensureBlankLine(); + } } protected emitHeader(): void { @@ -622,25 +890,45 @@ export class CirceRenderer extends Scala3Renderer { this.emitLine("import cats.syntax.functor._"); this.ensureBlankLine(); - this.emitLine("// For serialising string unions") - this.emitLine("given [A <: Singleton](using A <:< String): Decoder[A] = Decoder.decodeString.emapTry(x => Try(x.asInstanceOf[A])) "); - this.emitLine("given [A <: Singleton](using ev: A <:< String): Encoder[A] = Encoder.encodeString.contramap(ev) "); + this.emitLine("// For serialising string unions"); + // this.emitLine( + // "given [A <: Singleton](using A <:< String): Decoder[A] = Decoder.decodeString.emapTry(x => Try(x.asInstanceOf[A])) " + // ); + // this.emitLine( + // "given [A <: Singleton](using ev: A <:< String): Encoder[A] = Encoder.encodeString.contramap(ev) " + // ); this.ensureBlankLine(); - this.emitLine("// If a union has a null in, then we'll need this too... ") + this.emitLine("// If a union has a null in, then we'll need this too... "); this.emitLine("type NullValue = None.type"); } protected emitTopLevelArray(t: ArrayType, name: Name): void { super.emitTopLevelArray(t, name); const elementType = this.scalaType(t.items); - this.emitLine(["given (using ev : ", elementType, "): Encoder[Map[String,", elementType, "]] = Encoder.encodeMap[String, ", elementType, "]"]); + this.emitLine([ + "given (using ev : ", + elementType, + "): Encoder[Seq[", + elementType, + "]] = Encoder.encodeSeq[", + elementType, + "]" + ]); } protected emitTopLevelMap(t: MapType, name: Name): void { super.emitTopLevelMap(t, name); const elementType = this.scalaType(t.values); this.ensureBlankLine(); - this.emitLine(["given (using ev : ", elementType, "): Encoder[Map[String, ", elementType, "]] = Encoder.encodeMap[String, ", elementType, "]"]); + this.emitLine([ + "given (using ev : ", + elementType, + "): Encoder[Map[String, ", + elementType, + "]] = Encoder.encodeMap[String, ", + elementType, + "]" + ]); } protected emitUnionDefinition(u: UnionType, unionName: Name): void { @@ -653,7 +941,7 @@ export class CirceRenderer extends Scala3Renderer { this.emitDescription(this.descriptionForType(u)); const [maybeNull, nonNulls] = removeNullFromUnion(u, sortBy); - const theTypes: Array = [] + const theTypes: Array = []; this.forEachUnionMember(u, nonNulls, "none", null, (_, t) => { theTypes.push(this.scalaType(t)); }); @@ -670,38 +958,43 @@ export class CirceRenderer extends Scala3Renderer { this.ensureBlankLine(); if (!this.seenUnionTypes.some(y => y === thisUnionType)) { this.seenUnionTypes.push(thisUnionType); - const sourceLikeTypes: Array<[Sourcelike, Type]> = [] + const sourceLikeTypes: Array<[Sourcelike, Type]> = []; this.forEachUnionMember(u, nonNulls, "none", null, (_, t) => { sourceLikeTypes.push([this.scalaType(t), t]); - }); if (maybeNull !== null) { sourceLikeTypes.push([this.nameForUnionMember(u, maybeNull), maybeNull]); } - this.emitLine(["given Decoder[", unionName, "] = {"]) + this.emitLine(["given Decoder[", unionName, "] = {"]); this.indent(() => { - this.emitLine(["List[Decoder[", unionName, "]]("]) + this.emitLine(["List[Decoder[", unionName, "]]("]); this.indent(() => { - sourceLikeTypes.forEach((t) => { + sourceLikeTypes.forEach(t => { this.emitLine(["Decoder[", t[0], "].widen,"]); }); - }) - this.emitLine(").reduceLeft(_ or _)") - } - ) - this.emitLine(["}"]) + }); + this.emitLine(").reduceLeft(_ or _)"); + }); + this.emitLine(["}"]); this.ensureBlankLine(); - this.emitLine(["given Encoder[", unionName, "] = Encoder.instance {"]) + this.emitLine(["given Encoder[", unionName, "] = Encoder.instance {"]); this.indent(() => { sourceLikeTypes.forEach((t, i) => { const paramTemp = `enc${i.toString()}`; - this.emitLine(["case ", paramTemp, " : ", t[0], " => ", this.circeEncoderForType(t[1], false, false, paramTemp)]); + this.emitLine([ + "case ", + paramTemp, + " : ", + t[0], + " => ", + this.circeEncoderForType(t[1], false, false, paramTemp) + ]); }); - }) - this.emitLine("}") + }); + this.emitLine("}"); } } } @@ -741,4 +1034,3 @@ export class Scala3TargetLanguage extends TargetLanguage { } } } - diff --git a/test/fixtures.ts b/test/fixtures.ts index 0f3190f08..7c62c066a 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -816,8 +816,8 @@ export const allFixtures: Fixture[] = [ new JSONFixture(languages.FlowLanguage), new JSONFixture(languages.JavaScriptLanguage), new JSONFixture(languages.KotlinLanguage), - new JSONFixture(languages.Scala3Language), new JSONFixture(languages.KotlinJacksonLanguage, "kotlin-jackson"), + new JSONFixture(languages.Scala3Language), new JSONFixture(languages.DartLanguage), new JSONFixture(languages.PikeLanguage), new JSONFixture(languages.HaskellLanguage), diff --git a/test/fixtures/scala3-upickle/.gitignore b/test/fixtures/scala3-upickle/.gitignore new file mode 100644 index 000000000..03df7b560 --- /dev/null +++ b/test/fixtures/scala3-upickle/.gitignore @@ -0,0 +1,4 @@ +main.jar +.bsp +.scala-build +rename/ \ No newline at end of file diff --git a/test/fixtures/scala3-upickle/run.sh b/test/fixtures/scala3-upickle/run.sh new file mode 100755 index 000000000..7707d3841 --- /dev/null +++ b/test/fixtures/scala3-upickle/run.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +scala-cli upickle.scala TopLevel.scala diff --git a/test/fixtures/scala3-upickle/upickle.scala b/test/fixtures/scala3-upickle/upickle.scala new file mode 100644 index 000000000..507158808 --- /dev/null +++ b/test/fixtures/scala3-upickle/upickle.scala @@ -0,0 +1,17 @@ +//> using scala "3.2.2" +//> using lib "com.lihaoyi::upickle:3.1.0" +//> using lib "com.lihaoyi::os-lib:0.9.1" +//> using options "-Xmax-inlines", "500000" + +package quicktype +import upickle.default.* + + +@main def main = { + val json = scala.io.Source.fromFile("sample.json").getLines.mkString + val parsed = OptionPickler.read[TopLevel](json) + val jsonString = OptionPickler.writeJs(parsed) + val arr : Array[Byte] = jsonString.toString.getBytes("UTF-8") + // os.write(os.pwd / "my.json", OptionPickler.write(parsed, 2)) + System.out.write(arr, 0, arr.length) +} diff --git a/test/fixtures/scala3/circe.scala b/test/fixtures/scala3/circe.scala index b415f9545..318be045b 100644 --- a/test/fixtures/scala3/circe.scala +++ b/test/fixtures/scala3/circe.scala @@ -1,12 +1,16 @@ -//> using scala "3.2.1" -//> using lib "io.circe::circe-core:0.15.0-M1" -//> using lib "io.circe::circe-parser:0.15.0-M1" +//> using scala "3.2.2" +//> using lib "io.circe::circe-core:0.14.5" +//> using lib "io.circe::circe-parser:0.14.5" //> using options "-Xmax-inlines", "3000" package quicktype import io.circe._ import io.circe.parser._ import io.circe.syntax._ +import scala.util.Try +import scala.util.Right +import scala.util.Left + /* case class TopLevel ( diff --git a/test/languages.ts b/test/languages.ts index 222559d18..70b844e3c 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -940,7 +940,8 @@ I havea no idea how to encode these tests correctly. "enum.schema", "class-with-additional.schema", "class-map-union.schema", - "keyword-unions.schema" + "keyword-unions.schema", + "keyword-enum.schema" ], skipMiscJSON: false, rendererOptions: { framework: "circe" },