From 03068011e79492b5c6e55a37e8a7e2439b23f0cc Mon Sep 17 00:00:00 2001 From: Simon Parten Date: Fri, 14 Apr 2023 13:35:57 +0200 Subject: [PATCH 01/11] This seems to work up to problems with the ordering of fields --- .../quicktype-core/src/language/Scala3.ts | 415 +++++++++++++----- test/fixtures/scala3/run.sh | 2 +- test/languages.ts | 85 ++++ 3 files changed, 400 insertions(+), 102 deletions(-) diff --git a/packages/quicktype-core/src/language/Scala3.ts b/packages/quicktype-core/src/language/Scala3.ts index 489749ceae..762912ab28 100644 --- a/packages/quicktype-core/src/language/Scala3.ts +++ b/packages/quicktype-core/src/language/Scala3.ts @@ -13,22 +13,13 @@ 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"; +import { json } from "stream/consumers"; export enum Framework { None, @@ -37,18 +28,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 +130,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 => { @@ -248,10 +247,10 @@ export class Scala3Renderer extends ConvenienceRenderer { delimiter === "curly" ? ["{", "}"] : delimiter === "paren" - ? ["(", ")"] - : delimiter === "none" - ? ["", ""] - : ["{", "})"]; + ? ["(", ")"] + : delimiter === "none" + ? ["", ""] + : ["{", "})"]; this.emitLine(line, " ", open); this.indent(f); this.emitLine(close); @@ -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) => { + 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 emitClassDefinitionMethods() { - this.emitLine(") derives ReadWriter "); + 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 emitHeader(): void { - super.emitHeader(); - - this.emitLine("import upickle.default.*"); - this.ensureBlankLine(); + protected emitClassDefinitionMethods() { + this.emitLine(") derives OptionPickler.ReadWriter "); } -} - -export class Smithy4sRenderer extends Scala3Renderer { + protected anySourceType(optional: boolean): Sourcelike { + return [wrapOption("ujson.Value", optional)]; + } protected emitHeader(): void { - if (this.leadingComments !== undefined) { - this.emitCommentLines(this.leadingComments); - } else { - this.emitUsageHeader(); + 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 } + 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 + } + } +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 { @@ -602,15 +792,14 @@ export class CirceRenderer extends Scala3Renderer { jsonName.includes(" ") || !isNaN(parseInt(jsonName.charAt(0))) if (backticks) {this.emitItem("`")} else */ - this.emitItem(["\"", jsonName, "\""]); + this.emitItem(['"', jsonName, '"']); // if (backticks) {this.emitItem("`")} if (--count > 0) this.emitItem([" | "]); //} else { //--count - //} + //} }); this.ensureBlankLine(); - } protected emitHeader(): void { @@ -622,25 +811,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[Map[String,", + elementType, + "]] = Encoder.encodeMap[String, ", + 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 +862,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 +879,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 +955,3 @@ export class Scala3TargetLanguage extends TargetLanguage { } } } - diff --git a/test/fixtures/scala3/run.sh b/test/fixtures/scala3/run.sh index ff6223e387..7fa5377fbe 100755 --- a/test/fixtures/scala3/run.sh +++ b/test/fixtures/scala3/run.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -scala-cli circe.scala TopLevel.scala +scala-cli --server=false circe.scala TopLevel.scala diff --git a/test/languages.ts b/test/languages.ts index 6027fbbcf5..a96091e49c 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -956,6 +956,91 @@ I havea no idea how to encode these tests correctly. sourceFiles: ["src/Language/Scala3.ts"], }; +export const Scala3UpickleLanguage: Language = { + name: "scala3", + base: "test/fixtures/scala3-upickle", + runCommand(sample: string) { + return `cp "${sample}" sample.json && ./run.sh`; + }, + diffViaSchema: true, + skipDiffViaSchema: [ + "bug427.json", + "keywords.json", + + ], + allowMissingNull: true, + features: ["enum", "union", "no-defaults"], + output: "TopLevel.scala", + topLevel: "TopLevel", + skipJSON: [ + // These tests have "_" as a param name. Scala can't do this? + "blns-object.json", + "identifiers.json", + "simple-identifiers.json", + "keywords.json", + + // these actually work as far as I can tell, but seem to fail because properties are sorted differently + // I don't think they fail... but I can't figure out sorting so hey ho let's skip them + "github-events.json", + "0a358.json", + "0a91a.json", + "34702.json", + "76ae1.json", + "af2d1.json", + "bug427.json", + "3d04a0.json", + + // Top level primitives... trivial, + // but annoying as it breaks compilation of the "Top Level" construct... which doesn't exist. + // It's too much hassle to fix + // and has no practical application in this context. Skip. + "no-classes.json", + + // spaces in variables names doesn't seem to work + "name-style.json", + +/* +I havea no idea how to encode these tests correctly. +*/ + "kitchen-sink.json", + "26c9c.json", + "421d4.json", + "a0496.json", + "fcca3.json", + "ae9ca.json", + "617e8.json", + "5f7fe.json", + "f74d5.json", + "a3d8c.json", + "combinations1.json", + "combinations2.json", + "combinations3.json", + "combinations4.json", + "unions.json", + "nst-test-suite.json", + + ], + skipSchema: [ + // 12 skips + "required.schema", + "multi-type-enum.schema", // I think it doesn't correctly realise this is an array of enums. + "integer-string.schema", + "intersection.schema", + "implicit-class-array-union.schema", + "date-time-or-string.schema", + "implicit-one-of.schema", + "go-schema-pattern-properties.schema", + "enum.schema", + "class-with-additional.schema", + "class-map-union.schema", + "keyword-unions.schema" + ], + skipMiscJSON: false, + rendererOptions: {framework: "upickle" }, + quickTestRendererOptions: [], + sourceFiles: ["src/Language/Scala3.ts"], +}; + export const Smithy4sLanguage: Language = { name: "smithy4a", base: "test/fixtures/smithy4s", From 879c82cf768a1b3f0f1a0c20205580172223dcc7 Mon Sep 17 00:00:00 2001 From: Simon Parten Date: Fri, 14 Apr 2023 13:36:22 +0200 Subject: [PATCH 02/11] Now with tests --- test/fixtures.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/fixtures.ts b/test/fixtures.ts index 161dd44a42..cec9a772ea 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -809,8 +809,9 @@ 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.Scala3UpickleLanguage, "scala3-upickle"), new JSONFixture(languages.DartLanguage), new JSONFixture(languages.PikeLanguage), new JSONFixture(languages.HaskellLanguage), From 09b162001d83d952e38be746a1a8a23d1b070ff1 Mon Sep 17 00:00:00 2001 From: Simon Parten Date: Fri, 14 Apr 2023 13:37:40 +0200 Subject: [PATCH 03/11] That should test upickle --- test/fixtures/scala3-upickle/.gitignore | 4 ++++ test/fixtures/scala3-upickle/run.sh | 3 +++ test/fixtures/scala3-upickle/upickle.scala | 15 +++++++++++++++ test/fixtures/scala3/circe.scala | 6 +++--- 4 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 test/fixtures/scala3-upickle/.gitignore create mode 100755 test/fixtures/scala3-upickle/run.sh create mode 100644 test/fixtures/scala3-upickle/upickle.scala diff --git a/test/fixtures/scala3-upickle/.gitignore b/test/fixtures/scala3-upickle/.gitignore new file mode 100644 index 0000000000..03df7b5603 --- /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 0000000000..7707d38414 --- /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 0000000000..b3ce374c70 --- /dev/null +++ b/test/fixtures/scala3-upickle/upickle.scala @@ -0,0 +1,15 @@ +//> using scala "3.2.2" +//> using lib "com.lihaoyi::upickle:3.1.0" +//> 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") + System.out.write(arr, 0, arr.length) +} diff --git a/test/fixtures/scala3/circe.scala b/test/fixtures/scala3/circe.scala index b415f95451..169c22e8ea 100644 --- a/test/fixtures/scala3/circe.scala +++ b/test/fixtures/scala3/circe.scala @@ -1,6 +1,6 @@ -//> 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 From 7cc892f0712841b9a4c8dad49d0985892bd1eab7 Mon Sep 17 00:00:00 2001 From: Simon Parten Date: Fri, 14 Apr 2023 13:40:49 +0200 Subject: [PATCH 04/11] See if this runs the upickle suite --- .github/workflows/test-pr.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml index ed94095acf..33b34379a9 100644 --- a/.github/workflows/test-pr.yaml +++ b/.github/workflows/test-pr.yaml @@ -173,9 +173,13 @@ 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 + - run: echo '@main def hello() = println("We need this spam print statement for bloop to exit correctly...")' | scala-cli _ + if: ${{ contains(matrix.fixture, 'scala3-upickle') }} - run: QUICKTEST=true FIXTURE=${{ matrix.fixture }} npm test test-complete: From 3027c8d48823669158191e1ef1a0024cc4af1f8c Mon Sep 17 00:00:00 2001 From: Simon Parten Date: Fri, 14 Apr 2023 13:45:14 +0200 Subject: [PATCH 05/11] Remove extraneous declarations --- packages/quicktype-core/src/language/Scala3.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/quicktype-core/src/language/Scala3.ts b/packages/quicktype-core/src/language/Scala3.ts index 762912ab28..222f5849d1 100644 --- a/packages/quicktype-core/src/language/Scala3.ts +++ b/packages/quicktype-core/src/language/Scala3.ts @@ -19,7 +19,6 @@ import { TargetLanguage } from "../TargetLanguage"; import { ArrayType, ClassProperty, ClassType, EnumType, MapType, ObjectType, Type, UnionType } from "../Type"; import { matchType, nullableFromUnion, removeNullFromUnion } from "../TypeUtils"; import { RenderContext } from "../Renderer"; -import { json } from "stream/consumers"; export enum Framework { None, @@ -568,11 +567,11 @@ end JsonExt } protected override emitUnionDefinition(u: UnionType, unionName: Name): void { - function sortBy(t: Type): string { - const kind = t.kind; - if (kind === "class") return kind; - return "_" + kind; - } + // function sortBy(t: Type): string { + // const kind = t.kind; + // if (kind === "class") return kind; + // return "_" + kind; + // } this.emitDescription(this.descriptionForType(u)); From ecb282ff24eef4aa2cf18feed31a1f03ecf12e42 Mon Sep 17 00:00:00 2001 From: Simon Parten Date: Fri, 21 Apr 2023 11:36:38 +0200 Subject: [PATCH 06/11] Greatly improve enum encoding --- .github/workflows/test-pr.yaml | 4 - .../quicktype-core/src/language/Scala3.ts | 134 +++++-- test/fixtures.ts | 3 +- test/fixtures/scala3-upickle/upickle.scala | 2 + test/fixtures/scala3/circe.scala | 4 + test/fixtures/scala3/run.sh | 2 +- test/languages.ts | 363 +++++++----------- 7 files changed, 249 insertions(+), 263 deletions(-) diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml index 33b34379a9..5e262d9eea 100644 --- a/.github/workflows/test-pr.yaml +++ b/.github/workflows/test-pr.yaml @@ -178,10 +178,6 @@ jobs: if: ${{ contains(matrix.fixture, 'scala3') }} - run: QUICKTEST=true FIXTURE=${{ matrix.fixture }} npm test - - run: echo '@main def hello() = println("We need this spam print statement for bloop to exit correctly...")' | scala-cli _ - if: ${{ contains(matrix.fixture, 'scala3-upickle') }} - - run: QUICKTEST=true FIXTURE=${{ matrix.fixture }} npm test - test-complete: needs: test runs-on: ubuntu-latest diff --git a/packages/quicktype-core/src/language/Scala3.ts b/packages/quicktype-core/src/language/Scala3.ts index 222f5849d1..80eddff855 100644 --- a/packages/quicktype-core/src/language/Scala3.ts +++ b/packages/quicktype-core/src/language/Scala3.ts @@ -351,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; @@ -395,7 +396,7 @@ export class Scala3Renderer extends ConvenienceRenderer { } protected emitEnumDefinition(e: EnumType, enumName: Name): void { - console.log("vanilla"); + //console.log("vanilla"); this.emitDescription(this.descriptionForType(e)); this.emitBlock( @@ -406,7 +407,7 @@ export class Scala3Renderer extends ConvenienceRenderer { this.emitItem("\t case "); } this.forEachEnumCase(e, "none", (name, jsonName) => { - console.log(jsonName); + //console.log(jsonName); if (!(jsonName == "")) { const backticks = shouldAddBacktick(jsonName) || @@ -749,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) { @@ -781,24 +782,105 @@ 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 _)"); + + let count = e.cases.size - 1; + + 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 { @@ -811,12 +893,12 @@ export class CirceRenderer extends Scala3Renderer { 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( + // "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("type NullValue = None.type"); @@ -828,9 +910,9 @@ export class CirceRenderer extends Scala3Renderer { this.emitLine([ "given (using ev : ", elementType, - "): Encoder[Map[String,", + "): Encoder[Seq[", elementType, - "]] = Encoder.encodeMap[String, ", + "]] = Encoder.encodeSeq[", elementType, "]" ]); diff --git a/test/fixtures.ts b/test/fixtures.ts index cec9a772ea..d5932b4889 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -811,7 +811,7 @@ export const allFixtures: Fixture[] = [ new JSONFixture(languages.KotlinLanguage), new JSONFixture(languages.KotlinJacksonLanguage, "kotlin-jackson"), new JSONFixture(languages.Scala3Language), - new JSONFixture(languages.Scala3UpickleLanguage, "scala3-upickle"), + new JSONFixture(languages.Scala3UpickleLanguage, "scala3-upickle"), new JSONFixture(languages.DartLanguage), new JSONFixture(languages.PikeLanguage), new JSONFixture(languages.HaskellLanguage), @@ -836,6 +836,7 @@ export const allFixtures: Fixture[] = [ new JSONSchemaFixture(languages.KotlinLanguage), new JSONSchemaFixture(languages.KotlinJacksonLanguage, "schema-kotlin-jackson"), new JSONSchemaFixture(languages.Scala3Language), + new JSONSchemaFixture(languages.Scala3UpickleLanguage, "scala3-upickle"), new JSONSchemaFixture(languages.DartLanguage), new JSONSchemaFixture(languages.PikeLanguage), new JSONSchemaFixture(languages.HaskellLanguage), diff --git a/test/fixtures/scala3-upickle/upickle.scala b/test/fixtures/scala3-upickle/upickle.scala index b3ce374c70..5071588087 100644 --- a/test/fixtures/scala3-upickle/upickle.scala +++ b/test/fixtures/scala3-upickle/upickle.scala @@ -1,5 +1,6 @@ //> 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 @@ -11,5 +12,6 @@ import upickle.default.* 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 169c22e8ea..318be045bc 100644 --- a/test/fixtures/scala3/circe.scala +++ b/test/fixtures/scala3/circe.scala @@ -7,6 +7,10 @@ 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/fixtures/scala3/run.sh b/test/fixtures/scala3/run.sh index 7fa5377fbe..ff6223e387 100755 --- a/test/fixtures/scala3/run.sh +++ b/test/fixtures/scala3/run.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -scala-cli --server=false circe.scala TopLevel.scala +scala-cli circe.scala TopLevel.scala diff --git a/test/languages.ts b/test/languages.ts index a96091e49c..3ee00849af 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -419,7 +419,7 @@ export const CJSONLanguage: Language = { "fcca3.json", "bug427.json", "github-events.json", - "keywords.json", + "keywords.json" ], allowMissingNull: false, features: ["minmax", "minmaxlength", "pattern", "enum", "union", "no-defaults"], @@ -439,7 +439,7 @@ export const CJSONLanguage: Language = { /* Map in Array in TopLevel is not supported (for the current implementation, can be added later, need recursivity) */ "combinations2.json", /* Array in Array in Union is not supported (for the current implementation, can be added later, need recursivity) */ - "combinations4.json", + "combinations4.json" ], skipMiscJSON: false, skipSchema: [ @@ -470,12 +470,10 @@ export const CJSONLanguage: Language = { "optional-any.schema", "required-non-properties.schema", /* Other cases not supported */ - "implicit-class-array-union.schema", + "implicit-class-array-union.schema" ], rendererOptions: {}, - quickTestRendererOptions: [ - { "source-style": "single-source" } - ], + quickTestRendererOptions: [{ "source-style": "single-source" }], sourceFiles: ["src/language/CJSON.ts"] }; @@ -872,246 +870,149 @@ export const FlowLanguage: Language = { }; export const Scala3Language: Language = { - name: "scala3", - base: "test/fixtures/scala3", - runCommand(sample: string) { - return `cp "${sample}" sample.json && ./run.sh`; - }, - diffViaSchema: true, - skipDiffViaSchema: [ - "bug427.json", - "keywords.json", - - ], - allowMissingNull: true, - features: ["enum", "union", "no-defaults"], - output: "TopLevel.scala", - topLevel: "TopLevel", - skipJSON: [ - // These tests have "_" as a param name. Scala can't do this? - "blns-object.json", - "identifiers.json", - "simple-identifiers.json", - "keywords.json", - - // these actually work as far as I can tell, but seem to fail because properties are sorted differently - // I don't think they fail... but I can't figure out sorting so hey ho let's skip them - "github-events.json", - "0a358.json", - "0a91a.json", - "34702.json", - "76ae1.json", - "af2d1.json", - "bug427.json", - "3d04a0.json", - - // Top level primitives... trivial, - // but annoying as it breaks compilation of the "Top Level" construct... which doesn't exist. - // It's too much hassle to fix - // and has no practical application in this context. Skip. - "no-classes.json", - - // spaces in variables names doesn't seem to work - "name-style.json", - -/* -I havea no idea how to encode these tests correctly. -*/ - "kitchen-sink.json", - "26c9c.json", - "421d4.json", - "a0496.json", - "fcca3.json", - "ae9ca.json", - "617e8.json", - "5f7fe.json", - "f74d5.json", - "a3d8c.json", - "combinations1.json", - "combinations2.json", - "combinations3.json", - "combinations4.json", - "unions.json", - "nst-test-suite.json", - - ], - skipSchema: [ - // 12 skips - "required.schema", - "multi-type-enum.schema", // I think it doesn't correctly realise this is an array of enums. - "integer-string.schema", - "intersection.schema", - "implicit-class-array-union.schema", - "date-time-or-string.schema", - "implicit-one-of.schema", - "go-schema-pattern-properties.schema", - "enum.schema", - "class-with-additional.schema", - "class-map-union.schema", - "keyword-unions.schema" - ], - skipMiscJSON: false, - rendererOptions: {framework: "circe" }, - quickTestRendererOptions: [], - sourceFiles: ["src/Language/Scala3.ts"], -}; - -export const Scala3UpickleLanguage: Language = { - name: "scala3", - base: "test/fixtures/scala3-upickle", - runCommand(sample: string) { - return `cp "${sample}" sample.json && ./run.sh`; - }, - diffViaSchema: true, - skipDiffViaSchema: [ - "bug427.json", - "keywords.json", + name: "scala3", + base: "test/fixtures/scala3", + runCommand(sample: string) { + return `cp "${sample}" sample.json && ./run.sh`; + }, + diffViaSchema: true, + skipDiffViaSchema: ["bug427.json", "keywords.json"], + allowMissingNull: true, + features: ["enum", "union", "no-defaults"], + output: "TopLevel.scala", + topLevel: "TopLevel", + skipJSON: [ + // These tests have "_" as a param name. Scala can't do this? + "blns-object.json", + "identifiers.json", + "simple-identifiers.json", + "keywords.json", - ], - allowMissingNull: true, - features: ["enum", "union", "no-defaults"], - output: "TopLevel.scala", - topLevel: "TopLevel", - skipJSON: [ - // These tests have "_" as a param name. Scala can't do this? - "blns-object.json", - "identifiers.json", - "simple-identifiers.json", - "keywords.json", + // these actually work as far as I can tell, but seem to fail because properties are sorted differently + // I don't think they fail... but I can't figure out sorting so hey ho let's skip them + "github-events.json", + "0a358.json", + "0a91a.json", + "34702.json", + "76ae1.json", + "af2d1.json", + "bug427.json", + "3d04a0.json", - // these actually work as far as I can tell, but seem to fail because properties are sorted differently - // I don't think they fail... but I can't figure out sorting so hey ho let's skip them - "github-events.json", - "0a358.json", - "0a91a.json", - "34702.json", - "76ae1.json", - "af2d1.json", - "bug427.json", - "3d04a0.json", + // Top level primitives... trivial, + // but annoying as it breaks compilation of the "Top Level" construct... which doesn't exist. + // It's too much hassle to fix + // and has no practical application in this context. Skip. + "no-classes.json", - // Top level primitives... trivial, - // but annoying as it breaks compilation of the "Top Level" construct... which doesn't exist. - // It's too much hassle to fix - // and has no practical application in this context. Skip. - "no-classes.json", - - // spaces in variables names doesn't seem to work - "name-style.json", + // spaces in variables names doesn't seem to work + "name-style.json", -/* + /* I havea no idea how to encode these tests correctly. */ - "kitchen-sink.json", - "26c9c.json", - "421d4.json", - "a0496.json", - "fcca3.json", - "ae9ca.json", - "617e8.json", - "5f7fe.json", - "f74d5.json", - "a3d8c.json", - "combinations1.json", - "combinations2.json", - "combinations3.json", - "combinations4.json", - "unions.json", - "nst-test-suite.json", - - ], - skipSchema: [ - // 12 skips - "required.schema", - "multi-type-enum.schema", // I think it doesn't correctly realise this is an array of enums. - "integer-string.schema", - "intersection.schema", - "implicit-class-array-union.schema", - "date-time-or-string.schema", - "implicit-one-of.schema", - "go-schema-pattern-properties.schema", - "enum.schema", - "class-with-additional.schema", - "class-map-union.schema", - "keyword-unions.schema" - ], - skipMiscJSON: false, - rendererOptions: {framework: "upickle" }, - quickTestRendererOptions: [], - sourceFiles: ["src/Language/Scala3.ts"], + "kitchen-sink.json", + "26c9c.json", + "421d4.json", + "a0496.json", + "fcca3.json", + "ae9ca.json", + "617e8.json", + "5f7fe.json", + "f74d5.json", + "a3d8c.json", + "combinations1.json", + "combinations2.json", + "combinations3.json", + "combinations4.json", + "unions.json", + "nst-test-suite.json" + ], + skipSchema: [ + // 12 skips + "required.schema", + "multi-type-enum.schema", // I think it doesn't correctly realise this is an array of enums. + "integer-string.schema", + "intersection.schema", + "implicit-class-array-union.schema", + "date-time-or-string.schema", + "implicit-one-of.schema", + "go-schema-pattern-properties.schema", + "enum.schema", + "class-with-additional.schema", + "class-map-union.schema", + "keyword-unions.schema" + ], + skipMiscJSON: false, + rendererOptions: { framework: "circe" }, + quickTestRendererOptions: [], + sourceFiles: ["src/Language/Scala3.ts"] }; export const Smithy4sLanguage: Language = { - name: "smithy4a", - base: "test/fixtures/smithy4s", - runCommand(sample: string) { - return `cp "${sample}" sample.json && ./run.sh`; - }, - diffViaSchema: true, - skipDiffViaSchema: [ - "bug427.json", - "keywords.json", + name: "smithy4a", + base: "test/fixtures/smithy4s", + runCommand(sample: string) { + return `cp "${sample}" sample.json && ./run.sh`; + }, + diffViaSchema: true, + skipDiffViaSchema: ["bug427.json", "keywords.json"], + allowMissingNull: true, + features: ["enum", "union", "no-defaults"], + output: "TopLevel.scala", + topLevel: "TopLevel", + skipJSON: [ + // These tests have "_" as a param name. Scala can't do this? + "blns-object.json", + "identifiers.json", + "simple-identifiers.json", + "keywords.json", - ], - allowMissingNull: true, - features: ["enum", "union", "no-defaults"], - output: "TopLevel.scala", - topLevel: "TopLevel", - skipJSON: [ - // These tests have "_" as a param name. Scala can't do this? - "blns-object.json", - "identifiers.json", - "simple-identifiers.json", - "keywords.json", + // these actually work as far as I can tell, but seem to fail because properties are sorted differently + // I don't think they fail... but I can't figure out sorting so hey ho let's skip them + "github-events.json", + "0a358.json", + "0a91a.json", + "34702.json", + "76ae1.json", + "af2d1.json", + "bug427.json", + "3d04a0.json", - // these actually work as far as I can tell, but seem to fail because properties are sorted differently - // I don't think they fail... but I can't figure out sorting so hey ho let's skip them - "github-events.json", - "0a358.json", - "0a91a.json", - "34702.json", - "76ae1.json", - "af2d1.json", - "bug427.json", - "3d04a0.json", + // Top level primitives... trivial, + // but annoying as it breaks compilation of the "Top Level" construct... which doesn't exist. + // It's too much hassle to fix + // and has no practical application in this context. Skip. + "no-classes.json", - // Top level primitives... trivial, - // but annoying as it breaks compilation of the "Top Level" construct... which doesn't exist. - // It's too much hassle to fix - // and has no practical application in this context. Skip. - "no-classes.json", - - // spaces in variables names doesn't seem to work - "name-style.json", + // spaces in variables names doesn't seem to work + "name-style.json", -/* + /* I havea no idea how to encode these tests correctly. */ - "kitchen-sink.json", - "26c9c.json", - "421d4.json", - "a0496.json", - "fcca3.json", - "ae9ca.json", - "617e8.json", - "5f7fe.json", - "f74d5.json", - "a3d8c.json", - "combinations1.json", - "combinations2.json", - "combinations3.json", - "combinations4.json", - "unions.json", - "nst-test-suite.json", - - ], - skipSchema: [ - - ], - skipMiscJSON: false, - rendererOptions: {framework: "just-types" }, - quickTestRendererOptions: [], - sourceFiles: ["src/Language/Smithy4s.ts"], + "kitchen-sink.json", + "26c9c.json", + "421d4.json", + "a0496.json", + "fcca3.json", + "ae9ca.json", + "617e8.json", + "5f7fe.json", + "f74d5.json", + "a3d8c.json", + "combinations1.json", + "combinations2.json", + "combinations3.json", + "combinations4.json", + "unions.json", + "nst-test-suite.json" + ], + skipSchema: [], + skipMiscJSON: false, + rendererOptions: { framework: "just-types" }, + quickTestRendererOptions: [], + sourceFiles: ["src/Language/Smithy4s.ts"] }; export const KotlinLanguage: Language = { From 5c23948418216682b21dc7e8c91937091172520f Mon Sep 17 00:00:00 2001 From: Simon Parten Date: Fri, 21 Apr 2023 11:51:26 +0200 Subject: [PATCH 07/11] Fix linting error --- packages/quicktype-core/src/language/Scala3.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/quicktype-core/src/language/Scala3.ts b/packages/quicktype-core/src/language/Scala3.ts index 80eddff855..c47166fd25 100644 --- a/packages/quicktype-core/src/language/Scala3.ts +++ b/packages/quicktype-core/src/language/Scala3.ts @@ -810,8 +810,6 @@ export class CirceRenderer extends Scala3Renderer { }); this.emitLine(").reduceLeft(_ or _)"); - let count = e.cases.size - 1; - this.ensureBlankLine(); //let count = e.cases.size; this.ensureBlankLine(); From c8afd56070a083a08e448cfbc94d119915f74e4a Mon Sep 17 00:00:00 2001 From: Simon Parten Date: Fri, 21 Apr 2023 11:54:59 +0200 Subject: [PATCH 08/11] Oops, remove non existing language --- test/fixtures.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/fixtures.ts b/test/fixtures.ts index d5932b4889..8d08527a85 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -811,7 +811,6 @@ export const allFixtures: Fixture[] = [ new JSONFixture(languages.KotlinLanguage), new JSONFixture(languages.KotlinJacksonLanguage, "kotlin-jackson"), new JSONFixture(languages.Scala3Language), - new JSONFixture(languages.Scala3UpickleLanguage, "scala3-upickle"), new JSONFixture(languages.DartLanguage), new JSONFixture(languages.PikeLanguage), new JSONFixture(languages.HaskellLanguage), @@ -836,7 +835,6 @@ export const allFixtures: Fixture[] = [ new JSONSchemaFixture(languages.KotlinLanguage), new JSONSchemaFixture(languages.KotlinJacksonLanguage, "schema-kotlin-jackson"), new JSONSchemaFixture(languages.Scala3Language), - new JSONSchemaFixture(languages.Scala3UpickleLanguage, "scala3-upickle"), new JSONSchemaFixture(languages.DartLanguage), new JSONSchemaFixture(languages.PikeLanguage), new JSONSchemaFixture(languages.HaskellLanguage), From 017c758bc1924732fc85887bb3af7ae6dd4d6e12 Mon Sep 17 00:00:00 2001 From: Simon Parten Date: Fri, 21 Apr 2023 12:09:29 +0200 Subject: [PATCH 09/11] Cant have wildcard enums --- test/languages.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/languages.ts b/test/languages.ts index 3ee00849af..3d22568f6e 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -941,7 +941,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" }, From 77eeee2162e04f753df73765cc65820794381e10 Mon Sep 17 00:00:00 2001 From: Simon Parten Date: Wed, 17 May 2023 21:06:56 +0200 Subject: [PATCH 10/11] Try to fix some of the linting problems --- .../quicktype-core/src/language/Scala3.ts | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/quicktype-core/src/language/Scala3.ts b/packages/quicktype-core/src/language/Scala3.ts index c47166fd25..bf3ea237f7 100644 --- a/packages/quicktype-core/src/language/Scala3.ts +++ b/packages/quicktype-core/src/language/Scala3.ts @@ -1,5 +1,6 @@ import { anyTypeIssueAnnotation, nullTypeIssueAnnotation } from "../Annotation"; -import { ConvenienceRenderer, ForbiddenWordsInfo } from "../ConvenienceRenderer"; +import * as ConvenienceRenderer from "../ConvenienceRenderer"; +import { ForbiddenWordsInfo } from "../ConvenienceRenderer"; import { Name, Namer, funPrefixNamer } from "../Naming"; import { EnumOption, Option, StringOption, OptionValues, getOptionValues } from "../RendererOptions"; import { Sourcelike, maybeAnnotated } from "../Source"; @@ -206,7 +207,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 { @@ -408,7 +409,7 @@ export class Scala3Renderer extends ConvenienceRenderer { } this.forEachEnumCase(e, "none", (name, jsonName) => { //console.log(jsonName); - if (!(jsonName == "")) { + if (!(jsonName === "")) { const backticks = shouldAddBacktick(jsonName) || jsonName.includes(" ") || @@ -644,7 +645,7 @@ end JsonExt let hasBlank = false; this.forEachEnumCase(e, "none", (_, jsonName) => { - if (jsonName.trim() == "") { + if (jsonName.trim() === "") { hasBlank = true; } }); @@ -690,7 +691,7 @@ end JsonExt this.indent(() => { let count = e.cases.size; this.forEachEnumCase(e, "none", (_, jsonName) => { - if (!(jsonName.trim() == "")) { + if (!(jsonName.trim() === "")) { let strBuild = ""; const backticks = shouldAddBacktick(jsonName) || @@ -704,7 +705,7 @@ end JsonExt if (backticks) { strBuild = strBuild + "`"; } - if (--count > 0) strBuild + ","; + // if (--count > 0) strBuild + ","; this.emitLine([strBuild]); } }); @@ -727,7 +728,7 @@ end JsonExt if (backticks) { strBuild = strBuild + "`"; } - if (--count > 0) strBuild + ","; + // if (--count > 0) strBuild + ","; this.emitLine([strBuild]); }); }); @@ -784,7 +785,7 @@ export class CirceRenderer extends Scala3Renderer { let hasBlank = false; this.forEachEnumCase(e, "none", (_, jsonName) => { - if (jsonName.trim() == "") { + if (jsonName.trim() === "") { hasBlank = true; } }); @@ -815,7 +816,7 @@ export class CirceRenderer extends Scala3Renderer { this.ensureBlankLine(); this.emitLine(["enum ", enumName, "NonBlank :"]); this.indent(() => { - let count = e.cases.size; + //let count = e.cases.size; this.forEachEnumCase(e, "none", (_, jsonName) => { let strBuild = ""; @@ -831,7 +832,7 @@ export class CirceRenderer extends Scala3Renderer { if (backticks) { strBuild = strBuild + "`"; } - if (--count > 0) strBuild + ","; + //if (--count > 0) strBuild + ","; // don't emit the blank case if (!isBlank(jsonName)) { this.emitLine([strBuild]); @@ -865,7 +866,7 @@ export class CirceRenderer extends Scala3Renderer { if (backticks) { strBuild = strBuild + "`"; } - if (--count > 0) strBuild + ","; + // if (--count > 0) strBuild + ","; this.emitLine([strBuild]); }); }); From cfca1659a5abe07990f970436c0086aa4884d105 Mon Sep 17 00:00:00 2001 From: Simon Parten Date: Wed, 17 May 2023 21:24:47 +0200 Subject: [PATCH 11/11] Undo bad linting suggestions --- packages/quicktype-core/src/language/Scala3.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/quicktype-core/src/language/Scala3.ts b/packages/quicktype-core/src/language/Scala3.ts index bf3ea237f7..a0dafa7256 100644 --- a/packages/quicktype-core/src/language/Scala3.ts +++ b/packages/quicktype-core/src/language/Scala3.ts @@ -1,6 +1,5 @@ import { anyTypeIssueAnnotation, nullTypeIssueAnnotation } from "../Annotation"; -import * as ConvenienceRenderer from "../ConvenienceRenderer"; -import { ForbiddenWordsInfo } from "../ConvenienceRenderer"; +import { ConvenienceRenderer, ForbiddenWordsInfo } from "../ConvenienceRenderer"; import { Name, Namer, funPrefixNamer } from "../Naming"; import { EnumOption, Option, StringOption, OptionValues, getOptionValues } from "../RendererOptions"; import { Sourcelike, maybeAnnotated } from "../Source";