{"id":17770408,"url":"https://github.com/gampleman/elm-review-derive","last_synced_at":"2025-03-15T14:30:58.661Z","repository":{"id":42485019,"uuid":"332570319","full_name":"gampleman/elm-review-derive","owner":"gampleman","description":"Generate code for json encoders/decoders, codecs, fuzzers, generators, and more","archived":false,"fork":false,"pushed_at":"2025-03-06T16:03:47.000Z","size":347,"stargazers_count":30,"open_issues_count":8,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-06T17:22:40.113Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Elm","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gampleman.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["gampleman"]}},"created_at":"2021-01-24T22:29:34.000Z","updated_at":"2025-03-06T16:13:49.000Z","dependencies_parsed_at":"2024-04-04T15:47:27.055Z","dependency_job_id":"f50e4a75-f8da-4092-b855-66b31afd5cdb","html_url":"https://github.com/gampleman/elm-review-derive","commit_stats":null,"previous_names":["gampleman/elm-review-derive"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gampleman%2Felm-review-derive","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gampleman%2Felm-review-derive/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gampleman%2Felm-review-derive/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gampleman%2Felm-review-derive/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gampleman","download_url":"https://codeload.github.com/gampleman/elm-review-derive/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243742754,"owners_count":20340698,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-26T21:22:51.346Z","updated_at":"2025-03-15T14:30:58.655Z","avatar_url":"https://github.com/gampleman.png","language":"Elm","funding_links":["https://github.com/sponsors/gampleman"],"categories":["Elm"],"sub_categories":[],"readme":"# elm-review-derive\n\nThis elm-review rule is special in that it's not really a rule that checks for mistakes.  \nInstead it looks for code like this\n\n```elm\ntype MyType = A | B | C\n\nmyTypeToString : MyType -\u003e String\nmyTypeToString myType = Debug.todo \"\"\n```\n\nand offers a fix that transforms the code into this\n\n```elm\ntype MyType = A | B | C\n\nmyTypeToString : MyType -\u003e String\nmyTypeToString myType =\n    case myType of\n        A -\u003e \"A\"\n        B -\u003e \"B\"\n        C -\u003e \"C\"\n```\n\nIt knows to do this because it sees a top level function containing `Debug.todo \"\"` and `MyType -\u003e String` as the type signature.\n\n## What else can it do?\n\nThis rule isn't limited to generating toString functions for custom types though. It can handle the following signatures:\n\n### `Serialize.Codec e MyType`\n\nRequired package: [MartinSStewart/elm-serialize]  \nType features supported: **All\\***\n\n### `Codec.Codec e MyType`\n\nRequired package: [miniBill/elm-codec]  \nType features supported: **All\\***\n\n### `Csv.Decode.Decoder MyType`\n\nRequired package: [BrianHicks/elm-csv]  \nType features supported: Supports (non-recursive) **custom types and type aliases** as well as most primitives. Doesn't support collection types.\n\n### `String -\u003e Maybe MyType`\n\nRequired package: [elm/core]  \nType features supported: **Enums\\*\\***\n\n### `Fuzz.Fuzzer MyType`\n\nRequired package: [elm-explorations/test]  \nType features supported: **All\\***\n\n### `Json.Decode.Decoder MyType`\n\nRequired package: [elm/json]  \nType features supported: **All\\***\n\n### `MyType -\u003e Json.Encode.Value`\n\nRequired package: [elm/json]  \nType features supported: **All\\***\n\n### `List MyType`\n\nRequired package: [elm/core]  \nType features supported: **Enums\\*\\***\n\n### `Random.Generator MyType`\n\nRequired package: [elm/random]  \nType features supported: **All\\***  \nOptional packages: [elm-community/random-extra] supported - it will use `Random.Extra.anyInt`, `Random.Extra.choices` and `Random.Extra.andThen` if you have the package installed.\n\n### `MyType -\u003e String`\n\nRequired package: [elm/core]  \nType features supported: **Enums\\*\\***\n\n\\* All types actually supported by the relevant packages. Generally this means that it won't support function types, nor opaque\ntypes that don't provide a compatible signature (i.e. a `Decoder { foo : Color }` will fail of `Color` is an opaque type,\nunless there is a `Decoder Color` available in the project or dependencies).\n\n\\*\\* By Enum we mean a custom type where none of the constructors take any arguments. (i.e. `type Semaphore = Red | Yellow | Green`).\n\n[MartinSStewart/elm-serialize]: https://package.elm-lang.org/packages/MartinSStewart/elm-serialize/latest/\n[miniBill/elm-codec]: https://package.elm-lang.org/packages/miniBill/elm-codec/latest/\n[BrianHicks/elm-csv]: https://package.elm-lang.org/packages/BrianHicks/elm-csv/latest/\n[elm/core]: https://package.elm-lang.org/packages/elm/core/latest/\n[elm-explorations/test]: https://package.elm-lang.org/packages/elm-explorations/test/latest/\n[elm/json]: https://package.elm-lang.org/packages/elm/json/latest/\n[elm/random]: https://package.elm-lang.org/packages/elm/random/latest/\n[elm-community/random-extra]: https://package.elm-lang.org/packages/elm-community/random-extra/latest/\n\n## When will this rule trigger?\n\nIt will always flag any `Debug.todo`, but it will only attempt to generate code for top-level defintions with type annotations matching\none of the patterns above.\n\nHowever, the generator has good support for generics, so you can do things like this:\n\n```elm\nimport Json.Decode as Decode exposing (Decoder)\n\ntype MyTree a\n    = Branch (MyTree a) a (MyTree a)\n    | Leaf\n\ndecoder : Decoder a -\u003e Decoder (MyTree a)\ndecoder childDecoder =\n    Debug.todo \"\"\n```\n\nand you'll get:\n\n```elm\ndecoder : Decoder a -\u003e Decoder (MyTree a)\ndecoder childDecoder =\n    Decode.field \"tag\" Decode.string\n        |\u003e Decode.andThen\n            (\\ctor -\u003e\n                case ctor of\n                    \"Branch\" -\u003e\n                        Decode.map3\n                            Branch\n                            (Decode.field \"0\" (decoder childDecoder))\n                            (Decode.field \"1\" childDecoder)\n                            (Decode.field \"2\" (decoder childDecoder))\n\n                    \"Leaf\" -\u003e\n                        Decode.succeed Leaf\n\n                    _ -\u003e\n                        Decode.fail \"Unrecognized constructor\"\n            )\n```\n\nMore formally, for every type variable in the target type, you can provide an argument that provides the same matching pattern for that type variable.\n(Multiple arguments can be passed in any order, it is the matching type variable names that matter).\n\n## Great, how do I use this?\n\nYou can add it to your review config like any elm-review rule. However, I like to invoke it from the command line when I want it. You can do that with:\n\n```bash\nnpx elm-review --fix --template gampleman/elm-review-derive/preview\n```\n\nIf you like it, you can save that command in a convenient shell alias.\n\n```bash\nalias elm-derive='npx elm-review --fix --template gampleman/elm-review-derive/preview'\n```\n\n## How stable is it? Are there bugs?\n\nIt seems to work reasonably well. That said, it is a rather complex piece of code and testing it in anger on large codebases is likely to surface things\nthe authors haven't quite thought of. As such, we consider it Beta level software.\n\n## But what about some other thing I'd like to generate?\n\nThe infrastructure we provide is extensible. It is somewhat involved, so we don't expect every user to need to understand how this works.\n\nStill here? OK, let's see how this works.\n\nConceptually each generator is composed of two important pieces.\n\n1. The **Type Pattern** describes a pattern that can match type annotations in a user's code base.\n   Each Type Pattern has a `Target` in it, which is the user's type you care about and usually some other bits like some concrete wrapper type.\n2. The actually code generation function, which can be conceptually understood as a function `ResolvedType -\u003e Expression`.\n   `Expression` in this sense comes from [still4m/elm-syntax](https://package.elm-lang.org/packages/stil4m/elm-syntax/latest/).\n   `ResolvedType` is what this codebase spends much of its time and effort doing. It represents an actual type (as opposed to a type signature);\n   that is:\n\n   - it resolves type aliases to their actual definitions\n   - it resolves custom types to include all their constructors and their arguments\n   - it normalizes all names to a full module path (so you don't have to worry about how exactly the user imported them)\n   - it resolves visibility of all types (so if a type is opaque and in another module, you won't see it's defintion)\n   - it applies generic arguments\n\n   The upshot is that you can use a `ResolvedType` as a reliable guide to generate sensible code from.\n\nHowever, since manually building the entire `ResolvedType -\u003e Expression` function is a fair amount of work with many corner cases, it practice we define\nit piece meal. Here is the entire definition of the code generator for `Fuzzer`s:\n\n```elm\nmodule Internal.Builtin.Fuzzer exposing (codeGen)\n\nimport CodeGenerator exposing (CodeGenerator)\nimport Elm.CodeGen as CG\nimport TypePattern exposing (TypePattern(..))\n\n\nfuzz : String -\u003e CG.Expression\nfuzz =\n    CG.fqFun [ \"Fuzz\" ]\n\n\ncodeGen : CodeGenerator\ncodeGen =\n    CodeGenerator.define\n        { id = \"elm-explorations/test/Fuzz.Fuzzer\"\n        , dependency = \"elm-explorations/test\"\n        , typePattern = Typed [ \"Fuzz\" ] \"Fuzzer\" [ Target ]\n        , makeName = \\name -\u003e \"fuzz\" ++ name\n        }\n        [ CodeGenerator.customType (\\_ exps -\u003e CG.apply [ fuzz \"oneOf\", CG.list (List.map Tuple.second exps) ])\n        , CodeGenerator.pipeline (\\c -\u003e CG.apply [ fuzz \"constant\", c ]) (\\m -\u003e CG.apply [ fuzz \"andMap\", m ])\n        , CodeGenerator.mapN 8 (\\name a bs -\u003e CG.apply (fuzz name :: a :: bs))\n        , CodeGenerator.map (\\a b -\u003e CG.apply [ fuzz \"map\", a, b ])\n        , CodeGenerator.string (fuzz \"string\")\n        , CodeGenerator.dict (\\key val -\u003e CG.apply [ fuzz \"map\", CG.fqFun [ \"Dict\" ] \"fromList\", CG.apply [ fuzz \"list\", CG.apply [ fuzz \"pair\", key, val ] ] ])\n        , CodeGenerator.float (fuzz \"niceFloat\")\n        , CodeGenerator.tuple (\\a b -\u003e CG.apply [ fuzz \"pair\", a, b ])\n        , CodeGenerator.triple (\\a b c -\u003e CG.apply [ fuzz \"triple\", a, b, c ])\n        , CodeGenerator.char (fuzz \"char\")\n        , CodeGenerator.lambdaBreaker (\\inner -\u003e CG.apply [ fuzz \"lazy\", CG.lambda [ CG.unitPattern ] inner ])\n        ]\n```\n\nThe meat of it is about 20 lines of code.\n\nThe **Type Pattern** is `Typed [ \"Fuzz\" ] \"Fuzzer\" [ Target ]`, which can be read as `Fuzz.Fuzzer \u003cTARGET\u003e`.\nThis will have the effect of searching the whole codebase for things with that type annotations.\n\nWhenever it finds something matching this pattern, it checks the implementation. If the implementation is just a `Debug.todo`,\nthen it will try to run the generator to autofix is. If it's anything else, then it will resolve the `\u003cTARGET\u003e` and save it.\nThis means that the generator will pick up existing definitions and will try to use them in appropriate places.\n\nThe code generator has some smarts about creating auxiliary defintions. In order to do that, it uses the `makeName` function to name\nthese auxiliary definitions and the type pattern to create its type definition.\n\n## Contributing\n\nContributions welcome. To run the tests, please run `e2e/test.sh`, then you can also run `elm-test` as normal.\n\nMIT License\n\n## History\n\nThis package was initially conceived and implemented by @MartinSStewart with a slightly different focus (there was some tooling to generate Lamdera migrations for instance) and a more custom architecture.\nIt was called MartinSStewart/elm-review-todo-it-for-me.\n\n@gampleman then took the project over with some refactoring work on a more general architecture focused on type-oriented code generation. He renamed the project to be slightly less unwieldy to pronounce.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgampleman%2Felm-review-derive","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgampleman%2Felm-review-derive","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgampleman%2Felm-review-derive/lists"}