Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/test-pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,12 @@ jobs:
- scala3,schema-scala3
- scala3-upickle,schema-scala3-upickle
- elixir,schema-elixir,graphql-elixir
- elm,schema-elm
- comment-injection-treesitter,comment-injection-typescript,comment-injection-typescript-zod,comment-injection-typescript-effect-schema

# Partially working
# - schema-typescript # TODO Unify with typescript once fixed

# Implementation is too outdated to test in GitHub Actions
# - elm,schema-elm

# Language is too niche / obscure to test easily on ubuntu-22.04
# - pike,schema-pike

Expand Down
54 changes: 16 additions & 38 deletions packages/quicktype-core/src/language/Elm/ElmRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
parenIfNeeded,
singleWord,
} from "../../Source.js";
import { decapitalize, stringEscape } from "../../support/Strings.js";
import { decapitalize } from "../../support/Strings.js";
import { defined } from "../../support/Support.js";
import type { TargetLanguage } from "../../TargetLanguage.js";
import type {
Expand All @@ -34,6 +34,7 @@ import { matchType, nullableFromUnion } from "../../Type/TypeUtils.js";
import { forbiddenNames } from "./constants.js";
import type { elmOptions } from "./language.js";
import {
elmStringEscape,
lowerNamingFunction,
requiredOrOptional,
upperNamingFunction,
Expand Down Expand Up @@ -301,14 +302,15 @@ export class ElmRenderer extends ConvenienceRenderer {
(arrayType) =>
multiWord(
" ",
["make", this.arrayType, "Encoder"],
["Jenc.", decapitalize(this.arrayType)],
parenIfNeeded(this.encoderNameForType(arrayType.items)),
),
(classType) => singleWord(this.encoderNameForNamedType(classType)),
(mapType) =>
multiWord(
" ",
"makeDictEncoder",
"Jenc.dict",
"identity",
parenIfNeeded(this.encoderNameForType(mapType.values)),
),
(enumType) => singleWord(this.encoderNameForNamedType(enumType)),
Expand Down Expand Up @@ -456,7 +458,7 @@ export class ElmRenderer extends ConvenienceRenderer {
this.emitLine(decoderName, " : Jdec.Decoder ", className);
this.emitLine(decoderName, " =");
this.indent(() => {
this.emitLine("Jpipe.decode ", className);
this.emitLine("Jdec.succeed ", className);
this.indent(() => {
this.forEachClassProperty(c, "none", (_, jsonName, p) => {
const propDecoder = parenIfNeeded(
Expand All @@ -467,7 +469,7 @@ export class ElmRenderer extends ConvenienceRenderer {
"|> ",
reqOrOpt,
' "',
stringEscape(jsonName),
elmStringEscape(jsonName),
'" ',
propDecoder,
fallback,
Expand All @@ -490,7 +492,7 @@ export class ElmRenderer extends ConvenienceRenderer {
this.emitLine(
bracketOrComma,
' ("',
stringEscape(jsonName),
elmStringEscape(jsonName),
'", ',
propEncoder,
" x.",
Expand Down Expand Up @@ -522,7 +524,7 @@ export class ElmRenderer extends ConvenienceRenderer {
this.forEachEnumCase(e, "none", (name, jsonName) => {
this.emitLine(
'"',
stringEscape(jsonName),
elmStringEscape(jsonName),
'" -> Jdec.succeed ',
name,
);
Expand All @@ -547,7 +549,7 @@ export class ElmRenderer extends ConvenienceRenderer {
this.emitLine(
name,
' -> Jenc.string "',
stringEscape(jsonName),
elmStringEscape(jsonName),
'"',
);
});
Expand Down Expand Up @@ -652,11 +654,11 @@ export class ElmRenderer extends ConvenienceRenderer {
this.emitCommentLines([
"To decode the JSON data, add this file to your project, run",
"",
" elm-package install NoRedInk/elm-decode-pipeline",
" elm install NoRedInk/elm-json-decode-pipeline",
"",
"add these imports",
"",
" import Json.Decode exposing (decodeString)`);",
" import Json.Decode exposing (decodeString)",
]);
this.emitLine(
"-- import ",
Expand Down Expand Up @@ -695,11 +697,9 @@ export class ElmRenderer extends ConvenienceRenderer {
this.emitMultiline(`import Json.Decode as Jdec
import Json.Decode.Pipeline as Jpipe
import Json.Encode as Jenc
import Dict exposing (Dict, map, toList)`);
if (this._options.useList) {
this.emitLine("import List exposing (map)");
} else {
this.emitLine("import Array exposing (Array, map)");
import Dict exposing (Dict)`);
if (!this._options.useList) {
this.emitLine("import Array exposing (Array)");
}
}

Expand Down Expand Up @@ -741,29 +741,7 @@ import Dict exposing (Dict, map, toList)`);

this.emitLine("--- encoder helpers");
this.ensureBlankLine();
this.emitLine(
"make",
this.arrayType,
"Encoder : (a -> Jenc.Value) -> ",
this.arrayType,
" a -> Jenc.Value",
);
this.emitLine("make", this.arrayType, "Encoder f arr =");
this.indent(() => {
this.emitLine(
"Jenc.",
decapitalize(this.arrayType),
" (",
this.arrayType,
".map f arr)",
);
});
this.ensureBlankLine();
this.emitMultiline(`makeDictEncoder : (a -> Jenc.Value) -> Dict String a -> Jenc.Value
makeDictEncoder f dict =
Jenc.object (toList (Dict.map (\\k -> f) dict))

makeNullableEncoder : (a -> Jenc.Value) -> Maybe a -> Jenc.Value
this.emitMultiline(`makeNullableEncoder : (a -> Jenc.Value) -> Maybe a -> Jenc.Value
makeNullableEncoder f m =
case m of
Just x -> f x
Expand Down
13 changes: 9 additions & 4 deletions packages/quicktype-core/src/language/Elm/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,16 @@ export const forbiddenNames = [
"List",
"Dict",
"Maybe",
"map",
"toList",
"makeArrayEncoder",
"makeDictEncoder",
"makeNullableEncoder",
// Parameter names used in generated functions. Elm 0.19 does not
// allow a parameter to shadow a top-level definition.
"x",
"y",
"r",
"f",
"m",
"str",
"somethingElse",
"Int",
"True",
"False",
Expand Down
2 changes: 1 addition & 1 deletion packages/quicktype-core/src/language/Elm/language.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const elmOptions = {
array: false,
list: true,
} as const,
"array",
"list",
),
// FIXME: Do this via a configurable named eventually.
moduleName: new StringOption(
Expand Down
17 changes: 15 additions & 2 deletions packages/quicktype-core/src/language/Elm/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ import {
allLowerWordStyle,
allUpperWordStyle,
combineWords,
escapeNonPrintableMapper,
firstUpperWordStyle,
intToHex,
isAscii,
isLetterOrUnderscore,
isLetter,
isLetterOrUnderscoreOrDigit,
isPrintable,
legalizeCharacters,
splitIntoWords,
utf32ConcatMap,
} from "../../support/Strings.js";
import { type ClassProperty, UnionType } from "../../Type/index.js";
import { nullableFromUnion } from "../../Type/TypeUtils.js";
Expand All @@ -27,10 +31,19 @@ function elmNameStyle(original: string, upper: boolean): string {
upper ? allUpperWordStyle : allLowerWordStyle,
allUpperWordStyle,
"",
isLetterOrUnderscore,
// Elm identifiers must not start with an underscore.
isLetter,
);
}

function unicodeEscape(codePoint: number): string {
return `\\u{${intToHex(codePoint, 4).toUpperCase()}}`;
}

export const elmStringEscape = utf32ConcatMap(
escapeNonPrintableMapper(isPrintable, unicodeEscape),
);

export const upperNamingFunction = funPrefixNamer("upper", (n) =>
elmNameStyle(n, true),
);
Expand Down
27 changes: 18 additions & 9 deletions test/fixtures/elm/Main.elm
Original file line number Diff line number Diff line change
@@ -1,31 +1,40 @@
port module Main exposing (..)
port module Main exposing (main)

-- this is required for the ports
import Json.Decode exposing (decodeString)
import Json.Decode exposing (decodeString, errorToString)
import QuickType


port fromJS : (String -> msg) -> Sub msg


port toJS : String -> Cmd msg


type Msg
= FromJS String


update : Msg -> () -> ( (), Cmd Msg )
update msg _ =
case msg of
FromJS str ->
case decodeString QuickType.quickType str of
Ok r -> ((), toJS (QuickType.quickTypeToString r))
Err err -> ((), toJS ("Error: " ++ err))
Ok r ->
( (), toJS (QuickType.quickTypeToString r) )

Err err ->
( (), toJS ("Error: " ++ errorToString err) )


subscriptions : () -> Sub Msg
subscriptions _ =
fromJS (FromJS)
fromJS FromJS


main : Program Never () Msg
main : Program () () Msg
main =
Platform.program
{ init = ( (), Cmd.none )
Platform.worker
{ init = \_ -> ( (), Cmd.none )
, update = update
, subscriptions = subscriptions
}
18 changes: 18 additions & 0 deletions test/fixtures/elm/Warmup.elm
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module Warmup exposing (warmup)

{-| Compiled once by the fixture's setup command so that all package
dependencies are downloaded and built into the shared ELM_HOME cache
before the per-sample compiles run in parallel. Concurrent cold-cache
downloads can corrupt elm 0.19.1's package registry.
-}

import Dict exposing (Dict)
import Json.Decode as Jdec
import Json.Decode.Pipeline as Jpipe
import Json.Encode as Jenc


warmup : Jdec.Decoder ( Dict String Int, Jenc.Value )
warmup =
Jdec.succeed (\x -> ( Dict.empty, x ))
|> Jpipe.required "x" Jdec.value
14 changes: 0 additions & 14 deletions test/fixtures/elm/elm-package.json

This file was deleted.

17 changes: 17 additions & 0 deletions test/fixtures/elm/elm.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"type": "application",
"source-directories": ["."],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"NoRedInk/elm-json-decode-pipeline": "1.0.1",
"elm/core": "1.0.5",
"elm/json": "1.1.3"
},
"indirect": {}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}
8 changes: 4 additions & 4 deletions test/fixtures/elm/runner.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const Elm = require("./elm.js");
const { Elm } = require("./elm.js");
const fs = require("fs");

const ports = Elm.Main.worker().ports;
const app = Elm.Main.init();

ports.toJS.subscribe((result) => {
app.ports.toJS.subscribe((result) => {
if (result.startsWith("Error: ")) {
process.stderr.write(result + "\n", () => {
process.exit(1);
Expand All @@ -15,4 +15,4 @@ ports.toJS.subscribe((result) => {
}
});

ports.fromJS.send(fs.readFileSync(process.argv[2], "utf8"));
app.ports.fromJS.send(fs.readFileSync(process.argv[2], "utf8"));
Loading
Loading