From 78504d537c37d53ea3ab680acb569806bcacef4b Mon Sep 17 00:00:00 2001 From: Jakub Hampl Date: Thu, 4 Jul 2024 15:08:25 +0100 Subject: [PATCH] Renames rule and adds better error messages --- docs.json | 2 +- elm.json | 2 +- preview/src/ReviewConfig.elm | 4 ++-- src/CodeGenerator/Test.elm | 16 +++++++------- src/{NoDebug/TodoItForMe.elm => Derive.elm} | 8 +++---- src/Internal/CodeGenTodo.elm | 13 ++++++----- tests/NoDebugTest.elm | 24 +++++++++++++++++---- 7 files changed, 44 insertions(+), 25 deletions(-) rename src/{NoDebug/TodoItForMe.elm => Derive.elm} (97%) diff --git a/docs.json b/docs.json index 2825443..7739a78 100644 --- a/docs.json +++ b/docs.json @@ -1 +1 @@ -[{"name":"CodeGenerator","comment":" This module let's you define type-oriented principled code generators.\n\nBy type oriented we mean generators that are driven by a type definition provided by the user.\n\nBy principled we mean that the generated code will for the foremost follow compositional patterns to be able to express (almost) any type.\n\n@docs CodeGenerator, define\n\n\n### Defining code generators\n\n@docs Definition, ifUserHasDependency\n\n\n### Using existing functions\n\nThe code generator will find matching functions in the users code and dependencies. It will divided them into multiple levels and prioritise like this:\n\n1. Values defined in the current file\n2. Values defined in other project modules that the current file imports.\n3. Values defined in dependency modules that the current file imports.\n4. Values defined in dependencies.\n5. Values defined in other project modules.\n6. Values hard-coded in the code generator definition.\n\nHowever, it will reject functions where multiple matching functions\nexists at the same level.\n\n@docs use\n\n\n### Primitives\n\n@docs bool, int, float, string, char, list, array, set, dict, maybe, customDict\n\n\n### Tuples\n\n@docs unit, tuple, triple\n\n\n### Combining values\n\n@docs succeed, map, mapN, pipeline, combiner\n\n\n### Dealing with custom types\n\n@docs customType, lambdaBreaker\n\n\n### Going crazy\n\n@docs custom\n\n\n## Advanced use\n\n@docs defineWithComputation, combinerWithInput, customTypeWithInput\n\n","unions":[{"name":"Definition","comment":" Definitions are a way to to generate and compose small snippets of code to handle specific situations that might occur in an Elm type.\nFundamentally you can think of all the definitions put together as forming a rather sophisticated function `ResolvedType -> Expression`, however this library will handle a large number of gotcha's for you, so it's more convenient to define the function piece-meal.\n","args":["a"],"cases":[]}],"aliases":[{"name":"CodeGenerator","comment":" Represents a code generator configuration.\n","args":[],"type":"Internal.CodeGenerator.CodeGenerator"}],"values":[{"name":"array","comment":" Handle a `List a` type. You will be given code that handles the `a` subtype.\n","type":"(Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"bool","comment":" Handle an `Bool` type.\n","type":"Elm.Syntax.Expression.Expression -> CodeGenerator.Definition a"},{"name":"char","comment":" Handle a `Char` type.\n","type":"Elm.Syntax.Expression.Expression -> CodeGenerator.Definition a"},{"name":"combiner","comment":" If map, mapN, succeed, pipeline don't work for you, this is a more custom way to combine these.\n\nThe arguments that the function you pass will recieve are:\n\n1. Information about the type being constructed (e.g. `Foo Int`).\n2. An expression representing a function that creates the type in question (e.g. `makeFoo : Int -> Foo`).\n3. A list of expressions that have already been generated (e.g. `[ Decode.int ]`)\n\n","type":"(ResolvedType.ResolvedType -> Elm.Syntax.Expression.Expression -> List.List Elm.Syntax.Expression.Expression -> Maybe.Maybe Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"combinerWithInput","comment":" Like combiner, but allows you to control how input flows to the children and also receives input from its parent.\n","type":"(a -> ResolvedType.ResolvedType -> List.List ResolvedType.ResolvedType -> List.List a) -> (a -> ResolvedType.ResolvedType -> Elm.Syntax.Expression.Expression -> List.List Elm.Syntax.Expression.Expression -> Maybe.Maybe Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"custom","comment":" This allows you complete freedom in generating expressions, however it also doesn't give you much help.\n\nThe recommendation here is to use the normal definitions and only use this for exceptional cases.\n\n","type":"(ResolvedType.ResolvedType -> Maybe.Maybe Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"customDict","comment":" Handle a `Dict`, but get information about the types. This is useful, since sometimes we need the type of the keys.\n","type":"(( ResolvedType.ResolvedType, Elm.Syntax.Expression.Expression ) -> ( ResolvedType.ResolvedType, Elm.Syntax.Expression.Expression ) -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"customType","comment":" Deal with custom types. You will get a list of `( constructorName, expressionThatGeneratesTheTypeWithThatConstructor )`.\n\nThe challenge is to work out which of the branches should be chosen. You can solve that with a `andThen`, or the library might have a different mechanism for disjunctions.\n\n","type":"(List.List ( ResolvedType.Reference, List.List ResolvedType.ResolvedType ) -> List.List ( String.String, Elm.Syntax.Expression.Expression ) -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"customTypeWithInput","comment":" Like customType, but allows you to control how input flows to the children and also receives input from its parent.\n","type":"(a -> List.List ( ResolvedType.Reference, List.List ResolvedType.ResolvedType ) -> List.List a) -> (a -> List.List ( ResolvedType.Reference, List.List ResolvedType.ResolvedType ) -> List.List ( String.String, Elm.Syntax.Expression.Expression ) -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"define","comment":" Create a code generator. This requires the following pieces:\n\n - a unique id. This id can be used to extend the generator later.\n - a dependency name which specifies what this generator deals with. This generator will only be active if the user has that dependency installed.\n - a [`TypePattern`](TypePattern) that is used to figure out which type the function should work on.\n - a function that generates names if the generator needs to make an auxiliary definition\n - a list of Definitions that determine how code is actually generated. Note that later definitions will override previous ones.\n\n","type":"{ id : String.String, dependency : String.String, typePattern : TypePattern.TypePattern, makeName : String.String -> String.String } -> List.List (CodeGenerator.Definition ()) -> CodeGenerator.CodeGenerator"},{"name":"defineWithComputation","comment":" Like `define`, but this allows you to compute as you progress down the tree of types. Also note that `makeName` now takes a `Maybe`.\nIf you pass `Nothing`, than the generator will not create any auxiliary definitions, but will rather generate everything inline.\n","type":"{ id : String.String, dependency : String.String, typePattern : TypePattern.TypePattern, makeName : Maybe.Maybe (String.String -> String.String), input : a } -> List.List (CodeGenerator.Definition a) -> CodeGenerator.CodeGenerator"},{"name":"dict","comment":" Handle a `Dict`.\n","type":"(Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"float","comment":" Handle a `Float` type.\n","type":"Elm.Syntax.Expression.Expression -> CodeGenerator.Definition a"},{"name":"ifUserHasDependency","comment":" Apply this definition conditionally if the user has this specific dependency installed (can be chained). Intended for things like json-pipeline or random-extra.\n","type":"String.String -> CodeGenerator.Definition a -> CodeGenerator.Definition a"},{"name":"int","comment":" Handle an `Int` type.\n","type":"Elm.Syntax.Expression.Expression -> CodeGenerator.Definition a"},{"name":"lambdaBreaker","comment":" Elm only allows recursive definitions if there is a lambda somewhere in the chain. For instance:\n\n naiveListDecoder : Decoder (List Int)\n naiveListDecoder =\n Decode.oneOf\n [ Decode.map2 (::) (Decode.index 0 Decode.int) (Decode.index 1 naiveListDecoder)\n , Decode.null []\n ]\n\nwould fail to compile, as this would crash immediately on calling the program with an infinite loop. Incidentally this would compile:\n\n naiveListDecoder2 : Decoder a -> Decoder (List a)\n naiveListDecoder2 childDecoder =\n Decode.oneOf\n [ Decode.map2 (::) (Decode.index 0 childDecoder) (Decode.index 1 (naiveListDecoder2 childDecoder))\n , Decode.null []\n ]\n\nBut the code would fail at runtime with a Maximum Call Stack Exceeded exception. However, this version would work fine:\n\n smartListDecoder : Decoder (List Int)\n smartListDecoder =\n Decode.field \"length\" Decode.int\n |> Decode.andThen\n (\\l ->\n case l of\n 0 ->\n Decode.succeed []\n\n 2 ->\n Decode.map2 (::) (Decode.index 0 Decode.int) (Decode.index 1 smartListDecoder)\n\n _ ->\n Decode.fail \"Unexpected list length\"\n )\n\nThe reason being the lambda, that is only evaluated when a preceding step succeeds, hence there will be no infinite evaluation.\n\nOf course, most libraries have a solution to this problem, typically called `lazy`, which we could use here:\n\n smartListDecoder2 : Decoder (List Int)\n smartListDecoder2 =\n Decode.oneOf\n [ Decode.map2 (::) (Decode.index 0 Decode.int) (Decode.index 1 (Decode.lazy (\\() -> smartListDecoder2))\n , Decode.null []\n ]\n\nHowever, if there is no `lazy` function, it can be implemented in terms of `andThen` and `succeed`:\n\n lazy fn =\n andThen fn (succeed ())\n\nWe call this `lazy` function a `lambdaBreaker`, since it's purpose it to break recursion with a lambda. Implementing it will enable the code generator to deal with recursive types.\n\n","type":"(Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"list","comment":" Handle a `List a` type. You will be given code that handles the `a` subtype.\n","type":"(Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"map","comment":" Transform a value inside a type. You will be handed the arguments.\n","type":"(Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"mapN","comment":" A convenient way to specify `map2`, `map3`, `map4`, etc.\n\nThe first argument specifies up to what number of arguments you want to specify the `mapN`.\n\nThe first argument in the callback is the standard name, so for 3 arguments you will get `\"map3\"`.\n\n","type":"Basics.Int -> (String.String -> Elm.Syntax.Expression.Expression -> List.List Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"maybe","comment":" Handle a `Maybe a` type. You will be given code that handles the `a` subtype.\n","type":"(Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"pipeline","comment":" Deal with any number of arguments using applicative style. The first argument is like for succeed, the second is a partially applied `andMap`.\n","type":"(Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> (Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"set","comment":" Handle a `List a` type. You will be given code that handles the `a` subtype.\n","type":"(Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"string","comment":" Handle a `String` type.\n","type":"Elm.Syntax.Expression.Expression -> CodeGenerator.Definition a"},{"name":"succeed","comment":" Wrap a value in the type. This is called different things in different libraries (i.e. `List.singleton`, `Random.constant`, etc.)\n","type":"(Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"triple","comment":" Deals with 3-tuples (i.e. triples). No need to implement if you have `map3`, as it will automatically be used.\n","type":"(Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"tuple","comment":" Deals with 2-tuples (i.e. pairs). No need to implement if you have `map2`, as it will automatically be used.\n","type":"(Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"unit","comment":" Handle the unit `()` type.\n","type":"Elm.Syntax.Expression.Expression -> CodeGenerator.Definition a"},{"name":"use","comment":" If there are multiple existing functions at the same level, this allows you to bless one of these to be used as the default implementation. Takes a fully qualified name.\n","type":"String.String -> CodeGenerator.Definition a"}],"binops":[]},{"name":"CodeGenerator.Test","comment":" Testing code generators can be tricky, but very rewarding as it makes developing CodeGenerators much easier.\n\n@docs codeGenTest, codeGenTestFailsWith, codeGenIncrementalTest, fakeDependency, FakeDependency\n\n","unions":[{"name":"FakeDependency","comment":" Represents a dependency that the user has in their project.\n","args":[],"cases":[]}],"aliases":[],"values":[{"name":"codeGenIncrementalTest","comment":" Like `codeGenTest`, but runs in incremental mode. Only looks for a fix for the first `Debug.todo` found.\n","type":"String.String -> List.List CodeGenerator.Test.FakeDependency -> List.List CodeGenerator.CodeGenerator -> List.List String.String -> String.String -> Test.Test"},{"name":"codeGenTest","comment":" Tests that a single `Debug.todo` gets replaced with a particular piece of code.\n\n codeGenTest \"Generates a generator for a int\"\n [ fakeDependency \"elm/random\" ]\n [ elmRandomCodeGeneratorUnderTest ]\n [ \"\"\"module A exposing (..)\n import Random\n\n generator : Random.Generator Int\n generator =\n Debug.todo \"\"\n \"\"\" ]\n \"\"\"module A exposing (..)\n import Random\n\n generator : Random.Generator Int\n generator =\n Random.int Random.minInt Random.maxInt\n \"\"\"\n\nThe arguments are:\n\n1. Description (i.e. what you would normally pass to `Test.test`)\n2. A list of dependencies. Note that this rule will only activate code generators based on dependencies in the user's project. This allows you to control which dependencies are present.\n3. A list of code generators. Typically this will be the code generator under test, but can also be used to test interactions between multiple.\n4. A list of code modules that form the project the rule is being run on. Exactly one of these must have a single top level `Debug.todo` that the test is trying to replace.\n5. The expected source code of the module containing the `Debug.todo` after running the code generator.\n\n","type":"String.String -> List.List CodeGenerator.Test.FakeDependency -> List.List CodeGenerator.CodeGenerator -> List.List String.String -> String.String -> Test.Test"},{"name":"codeGenTestFailsWith","comment":" Like `codeGenTest`, but expects the code generator to not be able to generate code. The final string is the expected error message.\n","type":"String.String -> List.List CodeGenerator.Test.FakeDependency -> List.List CodeGenerator.CodeGenerator -> List.List String.String -> String.String -> Test.Test"},{"name":"fakeDependency","comment":" Creates a fake elm review dependency type. This should only be used with the other helpers in this module, as the information inside the returned type is mostly rubbish.\n\nHow do you get this value without too much pain? I usually go to package.elm-lang.or, find the package I'm interested in, open the dev tools, in the Network tab find the `docs.json`,\nselect Preview, right-click and \"Store as global variable\", then run the following snippet:\n\n copy(\n `fakeDependency \\n { name = ${JSON.stringify(\n window.location.pathname.split(\"/\").slice(2, 4).join(\"/\")\n )}\\n , dependencies = []\\n , modules =\\n [ ${temp1\n .map(\n (mod) =>\n `{ name = ${JSON.stringify(mod.name)}\\n , values =\\n [ ${mod.values\n .map((v) => `( ${JSON.stringify(v.name)}, ${JSON.stringify(v.type)})`)\n .join(\"\\n , \")}\\n ]\\n }`\n )\n .join(\"\\n , \")}\\n ]\\n}`\n );\n\n","type":"{ name : String.String, dependencies : List.List String.String, modules : List.List { name : String.String, values : List.List ( String.String, String.String ) } } -> CodeGenerator.Test.FakeDependency"}],"binops":[]},{"name":"NoDebug.TodoItForMe","comment":"\n\n@docs rule\n\n\n### Pro-tip for VSCode users\n\nOpen your command palette and select `Preferences: Open Keyboard Shortcuts (JSON)` and add the following object to the array:\n\n```json\n {\n \"key\": \"ctrl+alt+d\",\n \"command\": \"runCommands\",\n \"when\": \"editorTextFocus\",\n \"args\": {\n \"commands\": [\n {\n \"command\": \"editor.action.insertSnippet\",\n \"args\": {\n \"snippet\": \"${TM_CURRENT_LINE/(.*):.+/\\n$1 =\\n Debug.todo \\\"\\\"/}\",\n \"langId\": \"elm\"\n }\n },\n { \"command\": \"workbench.action.files.save\" },\n { \"command\": \"workbench.action.terminal.focus\" },\n {\n \"command\": \"workbench.action.terminal.sendSequence\",\n \"args\": {\n \"text\": \"npx elm-review --fix --template gampleman/elm-review-derive/preview\\n\"\n }\n }\n ]\n }\n```\n\nThen whenever you type a type signature, you can hit `ctrl+alt+d` (you can customize the keybinding above).\n\nFor instance if you typed:\n\n decodeUserProfile : Decoder UserProfile\n\nThen running this will modify it to read:\n\n decodeUserProfile : Decoder UserProfile\n decodeUserProfile =\n Debug.todo \"\"\n\nIt will then save the file, open the integrated terminal and run `npx elm-review --fix --template gampleman/elm-review-derive/preview` in it.\n\n","unions":[],"aliases":[],"values":[{"name":"rule","comment":" Forbids the use of [`Debug.todo`] and uses type information at the top-level to fix.\n\n config =\n [ NoDebug.TodoItForMe.rule []\n ]\n\n🔧 Running with `--fix` will automatically generate code to replace some `Debug.todo` uses.\n\nIn particular it will generate code when `Debug.todo` is used in a top-level definition with an explicit\ntype annotation, like so:\n\n someFunction : Decoder SomeType\n someFunction =\n Debug.todo \"\"\n\nThere is an ever-expanding list of type signatures that this rule supports, however, it is relatively straightforward to\nadd your own. See [`CodeGenerator`](CodeGenerator) for details.\n\n\n## Try it out\n\nYou can try this rule out by running the following command:\n\n```bash\nelm-review --template gampleman/elm-review-derive/preview --fix\n```\n\n[`Debug.todo`]: https://package.elm-lang.org/packages/elm/core/latest/Debug#todo\n\n","type":"Basics.Bool -> List.List CodeGenerator.CodeGenerator -> Review.Rule.Rule"}],"binops":[]},{"name":"ResolvedType","comment":" This module is at the heart of the code generation process.\nThis library works by gathering all the information necessary to build this type,\nthen the code generator's job is to translate this type into an AST.\n\n@docs ResolvedType, Reference\n\n","unions":[{"name":"ResolvedType","comment":" Represents an Elm type that has been fully resolved:\n\n - all references are now in absolute module format\n - type variable applications have been applied\n - custom type and type alias information has been added\n\nThe constructors are:\n\n - `GenericType \"foo\" ref` represents an unfilled type variable `foo`\n - `Opaque ref vars` represents a custom type (or built in type)\n - `Function args return` represents a function type\n - `TypeAliasRecord ref args definition` represents a record where we have a type alias (and therefore a constructor) available\n - `AnonymouseRecord extensionVar definition` represents an anonymous record\n - `CustomType ref args [(constructorRef, arguments)]` represents a reference to an exposed (or accessible) custom type\n - `Tuple args` represents a tuple.\n\nLet's look at some simple examples and see how they would be represented:\n\n Int --> Opaque { modulePath = [\"Basics\"], name = \"Int\" } []\n\n List Int --> Opaque { modulePath = [\"Basics\"], name = \"List\" } [Opaque { modulePath = [\"Basics\"], name = \"Int\" } []]\n\n List a --> Opaque { modulePath = [\"Basics\"], name = \"List\" } [ GenericType \"a\" Nothing ]\n\n Int -> String --> Function [ Opaque { modulePath = [\"Basics\"], name = \"Int\" } [] ] Opaque { modulePath = [\"String\"], name = \"String\" } []\n\n { foo : Int } --> AnonymousRecord Nothing [ ( \"foo\", Opaque { modulePath = [\"Basics\"], name = \"Int\" } [] ) ]\n\n { x | foo : Int } --> AnonymousRecord (Just \"x\") [ ( \"foo\", Opaque { modulePath = [\"Basics\"], name = \"Int\" } [] ) ]\n\n () -> Tuple []\n\n ( Int, String ) --> Tuple [ Opaque { modulePath = [\"Basics\"], name = \"Int\" } [], Opaque { modulePath = [\"String\"], name = \"String\" } [] ]\n\nNow for some more complex examples:\n\n type Foo a =\n Bar a\n\n Foo a --> CustomType { modulePath = [], name = \"Foo\" } [ \"a\" ] [ ( { modulePath = [], name = \"Bar\" }, [ GenericType \"a\" Nothing ] ) ]\n\n Foo Int --> CustomType { modulePath = [], name = \"Foo\" } [ \"a\" ] [ ( { modulePath = [], name = \"Bar\" }, [ GenericType \"a\" (Just (Opaque { modulePath = [\"Basics\"], name = \"Int\" } [] ))] ) ]\n\nNote how in `Foo Int` the `Int` value is replaced in the definition.\n\n type alias Qux a =\n { foo : a }\n\n Qux a --> TypeAlias { modulePath = [], name = \"Qux\" } [ \"a\" ] [( \"foo\", GenericType \"a\" Nothing )]\n Qux Int --> TypeAliasRecord { modulePath = [], name = \"Qux\" } [ \"a\" ] [( \"foo\", GenericType \"a\" (Just (Opaque { modulePath = [\"Basics\"], name = \"Int\" } [] )))]\n\n","args":[],"cases":[["GenericType",["String.String","Maybe.Maybe ResolvedType.ResolvedType"]],["Opaque",["ResolvedType.Reference","List.List ResolvedType.ResolvedType"]],["Function",["List.List ResolvedType.ResolvedType","ResolvedType.ResolvedType"]],["TypeAlias",["ResolvedType.Reference","List.List String.String","ResolvedType.ResolvedType"]],["AnonymousRecord",["Maybe.Maybe String.String","List.List ( String.String, ResolvedType.ResolvedType )"]],["CustomType",["ResolvedType.Reference","List.List String.String","List.List ( ResolvedType.Reference, List.List ResolvedType.ResolvedType )"]],["Tuple",["List.List ResolvedType.ResolvedType"]]]}],"aliases":[{"name":"Reference","comment":" Represents a fully qualified name in Elm source code.\n","args":[],"type":"{ modulePath : List.List String.String, name : String.String }"}],"values":[],"binops":[]},{"name":"TypePattern","comment":"\n\n@docs TypePattern\n\n","unions":[{"name":"TypePattern","comment":" A type pattern represents a query over a type annotation. We use these to identify objects of interest in the users code, namely:\n\n1. Definitions with `Debug.todo` that we want to turn into code.\n2. Definitions that provide functionality that generated code might want to hook into.\n\nHowever, patterns can also be run in reverse, i.e. these can also be used to generate type annotations for auxiliary defintions.\n\nThe most important constructor here is `Target`, which denotes the type that will drive the code generation process.\n\n`Typed modulePath name arguments` represents a type application, like `Random.Generator target` could be `Typed [\"Random\"] \"Generator\" [ Target ]`.\n\n`Function` represents a `->` in a type. These are right-associative so `a -> b -> c` would be `Function (GenericType \"a\") (Function (GenericType \"b\") (GenericType \"c\"))`.\n\n`GenericType` takes a variable name. This name doesn't need to match the name of a type (so `Typed [] \"List\" (GenericType \"A\")` **will match** `List x`), but **do** need to match each other. So `Function (GenericType \"a\") (GenericType \"a\")` will match `x -> x` but will not match `x -> y`.\n\n","args":[],"cases":[["Target",[]],["Typed",["List.List String.String","String.String","List.List TypePattern.TypePattern"]],["Function",["TypePattern.TypePattern","TypePattern.TypePattern"]],["GenericType",["String.String"]],["Tuple",["List.List TypePattern.TypePattern"]],["Record",["List.List ( String.String, TypePattern.TypePattern )"]]]}],"aliases":[],"values":[],"binops":[]}] \ No newline at end of file +[{"name":"CodeGenerator","comment":" This module let's you define type-oriented principled code generators.\n\nBy type oriented we mean generators that are driven by a type definition provided by the user.\n\nBy principled we mean that the generated code will for the foremost follow compositional patterns to be able to express (almost) any type.\n\n@docs CodeGenerator, define\n\n\n### Defining code generators\n\n@docs Definition, ifUserHasDependency\n\n\n### Using existing functions\n\nThe code generator will find matching functions in the users code and dependencies. It will divided them into multiple levels and prioritise like this:\n\n1. Values defined in the current file\n2. Values defined in other project modules that the current file imports.\n3. Values defined in dependency modules that the current file imports.\n4. Values defined in dependencies.\n5. Values defined in other project modules.\n6. Values hard-coded in the code generator definition.\n\nHowever, it will reject functions where multiple matching functions\nexists at the same level.\n\n@docs use\n\n\n### Primitives\n\n@docs bool, int, float, string, char, list, array, set, dict, maybe, customDict\n\n\n### Tuples\n\n@docs unit, tuple, triple\n\n\n### Combining values\n\n@docs succeed, map, mapN, pipeline, combiner\n\n\n### Dealing with custom types\n\n@docs customType, lambdaBreaker\n\n\n### Going crazy\n\n@docs custom\n\n\n## Advanced use\n\n@docs defineWithComputation, combinerWithInput, customTypeWithInput\n\n","unions":[{"name":"Definition","comment":" Definitions are a way to to generate and compose small snippets of code to handle specific situations that might occur in an Elm type.\nFundamentally you can think of all the definitions put together as forming a rather sophisticated function `ResolvedType -> Expression`, however this library will handle a large number of gotcha's for you, so it's more convenient to define the function piece-meal.\n","args":["a"],"cases":[]}],"aliases":[{"name":"CodeGenerator","comment":" Represents a code generator configuration.\n","args":[],"type":"Internal.CodeGenerator.CodeGenerator"}],"values":[{"name":"array","comment":" Handle a `List a` type. You will be given code that handles the `a` subtype.\n","type":"(Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"bool","comment":" Handle an `Bool` type.\n","type":"Elm.Syntax.Expression.Expression -> CodeGenerator.Definition a"},{"name":"char","comment":" Handle a `Char` type.\n","type":"Elm.Syntax.Expression.Expression -> CodeGenerator.Definition a"},{"name":"combiner","comment":" If map, mapN, succeed, pipeline don't work for you, this is a more custom way to combine these.\n\nThe arguments that the function you pass will recieve are:\n\n1. Information about the type being constructed (e.g. `Foo Int`).\n2. An expression representing a function that creates the type in question (e.g. `makeFoo : Int -> Foo`).\n3. A list of expressions that have already been generated (e.g. `[ Decode.int ]`)\n\n","type":"(ResolvedType.ResolvedType -> Elm.Syntax.Expression.Expression -> List.List Elm.Syntax.Expression.Expression -> Maybe.Maybe Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"combinerWithInput","comment":" Like combiner, but allows you to control how input flows to the children and also receives input from its parent.\n","type":"(a -> ResolvedType.ResolvedType -> List.List ResolvedType.ResolvedType -> List.List a) -> (a -> ResolvedType.ResolvedType -> Elm.Syntax.Expression.Expression -> List.List Elm.Syntax.Expression.Expression -> Maybe.Maybe Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"custom","comment":" This allows you complete freedom in generating expressions, however it also doesn't give you much help.\n\nThe recommendation here is to use the normal definitions and only use this for exceptional cases.\n\n","type":"(ResolvedType.ResolvedType -> Maybe.Maybe Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"customDict","comment":" Handle a `Dict`, but get information about the types. This is useful, since sometimes we need the type of the keys.\n","type":"(( ResolvedType.ResolvedType, Elm.Syntax.Expression.Expression ) -> ( ResolvedType.ResolvedType, Elm.Syntax.Expression.Expression ) -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"customType","comment":" Deal with custom types. You will get a list of `( constructorName, expressionThatGeneratesTheTypeWithThatConstructor )`.\n\nThe challenge is to work out which of the branches should be chosen. You can solve that with a `andThen`, or the library might have a different mechanism for disjunctions.\n\n","type":"(List.List ( ResolvedType.Reference, List.List ResolvedType.ResolvedType ) -> List.List ( String.String, Elm.Syntax.Expression.Expression ) -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"customTypeWithInput","comment":" Like customType, but allows you to control how input flows to the children and also receives input from its parent.\n","type":"(a -> List.List ( ResolvedType.Reference, List.List ResolvedType.ResolvedType ) -> List.List a) -> (a -> List.List ( ResolvedType.Reference, List.List ResolvedType.ResolvedType ) -> List.List ( String.String, Elm.Syntax.Expression.Expression ) -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"define","comment":" Create a code generator. This requires the following pieces:\n\n - a unique id. This id can be used to extend the generator later.\n - a dependency name which specifies what this generator deals with. This generator will only be active if the user has that dependency installed.\n - a [`TypePattern`](TypePattern) that is used to figure out which type the function should work on.\n - a function that generates names if the generator needs to make an auxiliary definition\n - a list of Definitions that determine how code is actually generated. Note that later definitions will override previous ones.\n\n","type":"{ id : String.String, dependency : String.String, typePattern : TypePattern.TypePattern, makeName : String.String -> String.String } -> List.List (CodeGenerator.Definition ()) -> CodeGenerator.CodeGenerator"},{"name":"defineWithComputation","comment":" Like `define`, but this allows you to compute as you progress down the tree of types. Also note that `makeName` now takes a `Maybe`.\nIf you pass `Nothing`, than the generator will not create any auxiliary definitions, but will rather generate everything inline.\n","type":"{ id : String.String, dependency : String.String, typePattern : TypePattern.TypePattern, makeName : Maybe.Maybe (String.String -> String.String), input : a } -> List.List (CodeGenerator.Definition a) -> CodeGenerator.CodeGenerator"},{"name":"dict","comment":" Handle a `Dict`.\n","type":"(Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"float","comment":" Handle a `Float` type.\n","type":"Elm.Syntax.Expression.Expression -> CodeGenerator.Definition a"},{"name":"ifUserHasDependency","comment":" Apply this definition conditionally if the user has this specific dependency installed (can be chained). Intended for things like json-pipeline or random-extra.\n","type":"String.String -> CodeGenerator.Definition a -> CodeGenerator.Definition a"},{"name":"int","comment":" Handle an `Int` type.\n","type":"Elm.Syntax.Expression.Expression -> CodeGenerator.Definition a"},{"name":"lambdaBreaker","comment":" Elm only allows recursive definitions if there is a lambda somewhere in the chain. For instance:\n\n naiveListDecoder : Decoder (List Int)\n naiveListDecoder =\n Decode.oneOf\n [ Decode.map2 (::) (Decode.index 0 Decode.int) (Decode.index 1 naiveListDecoder)\n , Decode.null []\n ]\n\nwould fail to compile, as this would crash immediately on calling the program with an infinite loop. Incidentally this would compile:\n\n naiveListDecoder2 : Decoder a -> Decoder (List a)\n naiveListDecoder2 childDecoder =\n Decode.oneOf\n [ Decode.map2 (::) (Decode.index 0 childDecoder) (Decode.index 1 (naiveListDecoder2 childDecoder))\n , Decode.null []\n ]\n\nBut the code would fail at runtime with a Maximum Call Stack Exceeded exception. However, this version would work fine:\n\n smartListDecoder : Decoder (List Int)\n smartListDecoder =\n Decode.field \"length\" Decode.int\n |> Decode.andThen\n (\\l ->\n case l of\n 0 ->\n Decode.succeed []\n\n 2 ->\n Decode.map2 (::) (Decode.index 0 Decode.int) (Decode.index 1 smartListDecoder)\n\n _ ->\n Decode.fail \"Unexpected list length\"\n )\n\nThe reason being the lambda, that is only evaluated when a preceding step succeeds, hence there will be no infinite evaluation.\n\nOf course, most libraries have a solution to this problem, typically called `lazy`, which we could use here:\n\n smartListDecoder2 : Decoder (List Int)\n smartListDecoder2 =\n Decode.oneOf\n [ Decode.map2 (::) (Decode.index 0 Decode.int) (Decode.index 1 (Decode.lazy (\\() -> smartListDecoder2))\n , Decode.null []\n ]\n\nHowever, if there is no `lazy` function, it can be implemented in terms of `andThen` and `succeed`:\n\n lazy fn =\n andThen fn (succeed ())\n\nWe call this `lazy` function a `lambdaBreaker`, since it's purpose it to break recursion with a lambda. Implementing it will enable the code generator to deal with recursive types.\n\n","type":"(Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"list","comment":" Handle a `List a` type. You will be given code that handles the `a` subtype.\n","type":"(Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"map","comment":" Transform a value inside a type. You will be handed the arguments.\n","type":"(Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"mapN","comment":" A convenient way to specify `map2`, `map3`, `map4`, etc.\n\nThe first argument specifies up to what number of arguments you want to specify the `mapN`.\n\nThe first argument in the callback is the standard name, so for 3 arguments you will get `\"map3\"`.\n\n","type":"Basics.Int -> (String.String -> Elm.Syntax.Expression.Expression -> List.List Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"maybe","comment":" Handle a `Maybe a` type. You will be given code that handles the `a` subtype.\n","type":"(Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"pipeline","comment":" Deal with any number of arguments using applicative style. The first argument is like for succeed, the second is a partially applied `andMap`.\n","type":"(Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> (Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"set","comment":" Handle a `List a` type. You will be given code that handles the `a` subtype.\n","type":"(Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"string","comment":" Handle a `String` type.\n","type":"Elm.Syntax.Expression.Expression -> CodeGenerator.Definition a"},{"name":"succeed","comment":" Wrap a value in the type. This is called different things in different libraries (i.e. `List.singleton`, `Random.constant`, etc.)\n","type":"(Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"triple","comment":" Deals with 3-tuples (i.e. triples). No need to implement if you have `map3`, as it will automatically be used.\n","type":"(Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"tuple","comment":" Deals with 2-tuples (i.e. pairs). No need to implement if you have `map2`, as it will automatically be used.\n","type":"(Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression -> Elm.Syntax.Expression.Expression) -> CodeGenerator.Definition a"},{"name":"unit","comment":" Handle the unit `()` type.\n","type":"Elm.Syntax.Expression.Expression -> CodeGenerator.Definition a"},{"name":"use","comment":" If there are multiple existing functions at the same level, this allows you to bless one of these to be used as the default implementation. Takes a fully qualified name.\n","type":"String.String -> CodeGenerator.Definition a"}],"binops":[]},{"name":"CodeGenerator.Test","comment":" Testing code generators can be tricky, but very rewarding as it makes developing CodeGenerators much easier.\n\n@docs codeGenTest, codeGenTestFailsWith, codeGenIncrementalTest, fakeDependency, FakeDependency\n\n","unions":[{"name":"FakeDependency","comment":" Represents a dependency that the user has in their project.\n","args":[],"cases":[]}],"aliases":[],"values":[{"name":"codeGenIncrementalTest","comment":" Like `codeGenTest`, but runs in incremental mode. Only looks for a fix for the first `Debug.todo` found.\n","type":"String.String -> List.List CodeGenerator.Test.FakeDependency -> List.List CodeGenerator.CodeGenerator -> List.List String.String -> String.String -> Test.Test"},{"name":"codeGenTest","comment":" Tests that a single `Debug.todo` gets replaced with a particular piece of code.\n\n codeGenTest \"Generates a generator for a int\"\n [ fakeDependency \"elm/random\" ]\n [ elmRandomCodeGeneratorUnderTest ]\n [ \"\"\"module A exposing (..)\n import Random\n\n generator : Random.Generator Int\n generator =\n Debug.todo \"\"\n \"\"\" ]\n \"\"\"module A exposing (..)\n import Random\n\n generator : Random.Generator Int\n generator =\n Random.int Random.minInt Random.maxInt\n \"\"\"\n\nThe arguments are:\n\n1. Description (i.e. what you would normally pass to `Test.test`)\n2. A list of dependencies. Note that this rule will only activate code generators based on dependencies in the user's project. This allows you to control which dependencies are present.\n3. A list of code generators. Typically this will be the code generator under test, but can also be used to test interactions between multiple.\n4. A list of code modules that form the project the rule is being run on. Exactly one of these must have a single top level `Debug.todo` that the test is trying to replace.\n5. The expected source code of the module containing the `Debug.todo` after running the code generator.\n\n","type":"String.String -> List.List CodeGenerator.Test.FakeDependency -> List.List CodeGenerator.CodeGenerator -> List.List String.String -> String.String -> Test.Test"},{"name":"codeGenTestFailsWith","comment":" Like `codeGenTest`, but expects the code generator to not be able to generate code. The final string is the expected error message.\n","type":"String.String -> List.List CodeGenerator.Test.FakeDependency -> List.List CodeGenerator.CodeGenerator -> List.List String.String -> String.String -> Test.Test"},{"name":"fakeDependency","comment":" Creates a fake elm review dependency type. This should only be used with the other helpers in this module, as the information inside the returned type is mostly rubbish.\n\nHow do you get this value without too much pain? I usually go to package.elm-lang.or, find the package I'm interested in, open the dev tools, in the Network tab find the `docs.json`,\nselect Preview, right-click and \"Store as global variable\", then run the following snippet:\n\n copy(\n `fakeDependency \\n { name = ${JSON.stringify(\n window.location.pathname.split(\"/\").slice(2, 4).join(\"/\")\n )}\\n , dependencies = []\\n , modules =\\n [ ${temp1\n .map(\n (mod) =>\n `{ name = ${JSON.stringify(mod.name)}\\n , values =\\n [ ${mod.values\n .map((v) => `( ${JSON.stringify(v.name)}, ${JSON.stringify(v.type)})`)\n .join(\"\\n , \")}\\n ]\\n }`\n )\n .join(\"\\n , \")}\\n ]\\n}`\n );\n\n","type":"{ name : String.String, dependencies : List.List String.String, modules : List.List { name : String.String, values : List.List ( String.String, String.String ) } } -> CodeGenerator.Test.FakeDependency"}],"binops":[]},{"name":"Derive","comment":"\n\n@docs rule\n\n\n### Pro-tip for VSCode users\n\nOpen your command palette and select `Preferences: Open Keyboard Shortcuts (JSON)` and add the following object to the array:\n\n```json\n {\n \"key\": \"ctrl+alt+d\",\n \"command\": \"runCommands\",\n \"when\": \"editorTextFocus\",\n \"args\": {\n \"commands\": [\n {\n \"command\": \"editor.action.insertSnippet\",\n \"args\": {\n \"snippet\": \"${TM_CURRENT_LINE/(.*):.+/\\n$1 =\\n Debug.todo \\\"\\\"/}\",\n \"langId\": \"elm\"\n }\n },\n { \"command\": \"workbench.action.files.save\" },\n { \"command\": \"workbench.action.terminal.focus\" },\n {\n \"command\": \"workbench.action.terminal.sendSequence\",\n \"args\": {\n \"text\": \"npx elm-review --fix --template gampleman/elm-review-derive/preview\\n\"\n }\n }\n ]\n }\n```\n\nThen whenever you type a type signature, you can hit `ctrl+alt+d` (you can customize the keybinding above).\n\nFor instance if you typed:\n\n decodeUserProfile : Decoder UserProfile\n\nThen running this will modify it to read:\n\n decodeUserProfile : Decoder UserProfile\n decodeUserProfile =\n Debug.todo \"\"\n\nIt will then save the file, open the integrated terminal and run `npx elm-review --fix --template gampleman/elm-review-derive/preview` in it.\n\n","unions":[],"aliases":[],"values":[{"name":"rule","comment":" Forbids the use of [`Debug.todo`] and uses type information at the top-level to fix.\n\n config =\n [ NoDebug.TodoItForMe.rule []\n ]\n\n🔧 Running with `--fix` will automatically generate code to replace some `Debug.todo` uses.\n\nIn particular it will generate code when `Debug.todo` is used in a top-level definition with an explicit\ntype annotation, like so:\n\n someFunction : Decoder SomeType\n someFunction =\n Debug.todo \"\"\n\nThere is an ever-expanding list of type signatures that this rule supports, however, it is relatively straightforward to\nadd your own. See [`CodeGenerator`](CodeGenerator) for details.\n\n\n## Try it out\n\nYou can try this rule out by running the following command:\n\n```bash\nelm-review --template gampleman/elm-review-derive/preview --fix\n```\n\n[`Debug.todo`]: https://package.elm-lang.org/packages/elm/core/latest/Debug#todo\n\n","type":"Basics.Bool -> List.List CodeGenerator.CodeGenerator -> Review.Rule.Rule"}],"binops":[]},{"name":"ResolvedType","comment":" This module is at the heart of the code generation process.\nThis library works by gathering all the information necessary to build this type,\nthen the code generator's job is to translate this type into an AST.\n\n@docs ResolvedType, Reference\n\n","unions":[{"name":"ResolvedType","comment":" Represents an Elm type that has been fully resolved:\n\n - all references are now in absolute module format\n - type variable applications have been applied\n - custom type and type alias information has been added\n\nThe constructors are:\n\n - `GenericType \"foo\" ref` represents an unfilled type variable `foo`\n - `Opaque ref vars` represents a custom type (or built in type)\n - `Function args return` represents a function type\n - `TypeAliasRecord ref args definition` represents a record where we have a type alias (and therefore a constructor) available\n - `AnonymouseRecord extensionVar definition` represents an anonymous record\n - `CustomType ref args [(constructorRef, arguments)]` represents a reference to an exposed (or accessible) custom type\n - `Tuple args` represents a tuple.\n\nLet's look at some simple examples and see how they would be represented:\n\n Int --> Opaque { modulePath = [\"Basics\"], name = \"Int\" } []\n\n List Int --> Opaque { modulePath = [\"Basics\"], name = \"List\" } [Opaque { modulePath = [\"Basics\"], name = \"Int\" } []]\n\n List a --> Opaque { modulePath = [\"Basics\"], name = \"List\" } [ GenericType \"a\" Nothing ]\n\n Int -> String --> Function [ Opaque { modulePath = [\"Basics\"], name = \"Int\" } [] ] Opaque { modulePath = [\"String\"], name = \"String\" } []\n\n { foo : Int } --> AnonymousRecord Nothing [ ( \"foo\", Opaque { modulePath = [\"Basics\"], name = \"Int\" } [] ) ]\n\n { x | foo : Int } --> AnonymousRecord (Just \"x\") [ ( \"foo\", Opaque { modulePath = [\"Basics\"], name = \"Int\" } [] ) ]\n\n () -> Tuple []\n\n ( Int, String ) --> Tuple [ Opaque { modulePath = [\"Basics\"], name = \"Int\" } [], Opaque { modulePath = [\"String\"], name = \"String\" } [] ]\n\nNow for some more complex examples:\n\n type Foo a =\n Bar a\n\n Foo a --> CustomType { modulePath = [], name = \"Foo\" } [ \"a\" ] [ ( { modulePath = [], name = \"Bar\" }, [ GenericType \"a\" Nothing ] ) ]\n\n Foo Int --> CustomType { modulePath = [], name = \"Foo\" } [ \"a\" ] [ ( { modulePath = [], name = \"Bar\" }, [ GenericType \"a\" (Just (Opaque { modulePath = [\"Basics\"], name = \"Int\" } [] ))] ) ]\n\nNote how in `Foo Int` the `Int` value is replaced in the definition.\n\n type alias Qux a =\n { foo : a }\n\n Qux a --> TypeAlias { modulePath = [], name = \"Qux\" } [ \"a\" ] [( \"foo\", GenericType \"a\" Nothing )]\n Qux Int --> TypeAliasRecord { modulePath = [], name = \"Qux\" } [ \"a\" ] [( \"foo\", GenericType \"a\" (Just (Opaque { modulePath = [\"Basics\"], name = \"Int\" } [] )))]\n\n","args":[],"cases":[["GenericType",["String.String","Maybe.Maybe ResolvedType.ResolvedType"]],["Opaque",["ResolvedType.Reference","List.List ResolvedType.ResolvedType"]],["Function",["List.List ResolvedType.ResolvedType","ResolvedType.ResolvedType"]],["TypeAlias",["ResolvedType.Reference","List.List String.String","ResolvedType.ResolvedType"]],["AnonymousRecord",["Maybe.Maybe String.String","List.List ( String.String, ResolvedType.ResolvedType )"]],["CustomType",["ResolvedType.Reference","List.List String.String","List.List ( ResolvedType.Reference, List.List ResolvedType.ResolvedType )"]],["Tuple",["List.List ResolvedType.ResolvedType"]]]}],"aliases":[{"name":"Reference","comment":" Represents a fully qualified name in Elm source code.\n","args":[],"type":"{ modulePath : List.List String.String, name : String.String }"}],"values":[],"binops":[]},{"name":"TypePattern","comment":"\n\n@docs TypePattern\n\n","unions":[{"name":"TypePattern","comment":" A type pattern represents a query over a type annotation. We use these to identify objects of interest in the users code, namely:\n\n1. Definitions with `Debug.todo` that we want to turn into code.\n2. Definitions that provide functionality that generated code might want to hook into.\n\nHowever, patterns can also be run in reverse, i.e. these can also be used to generate type annotations for auxiliary defintions.\n\nThe most important constructor here is `Target`, which denotes the type that will drive the code generation process.\n\n`Typed modulePath name arguments` represents a type application, like `Random.Generator target` could be `Typed [\"Random\"] \"Generator\" [ Target ]`.\n\n`Function` represents a `->` in a type. These are right-associative so `a -> b -> c` would be `Function (GenericType \"a\") (Function (GenericType \"b\") (GenericType \"c\"))`.\n\n`GenericType` takes a variable name. This name doesn't need to match the name of a type (so `Typed [] \"List\" (GenericType \"A\")` **will match** `List x`), but **do** need to match each other. So `Function (GenericType \"a\") (GenericType \"a\")` will match `x -> x` but will not match `x -> y`.\n\n","args":[],"cases":[["Target",[]],["Typed",["List.List String.String","String.String","List.List TypePattern.TypePattern"]],["Function",["TypePattern.TypePattern","TypePattern.TypePattern"]],["GenericType",["String.String"]],["Tuple",["List.List TypePattern.TypePattern"]],["Record",["List.List ( String.String, TypePattern.TypePattern )"]]]}],"aliases":[],"values":[],"binops":[]}] \ No newline at end of file diff --git a/elm.json b/elm.json index 8eec49c..1a77d53 100644 --- a/elm.json +++ b/elm.json @@ -5,7 +5,7 @@ "license": "MIT", "version": "1.0.0", "exposed-modules": [ - "NoDebug.TodoItForMe", + "Derive", "CodeGenerator", "CodeGenerator.Test", "TypePattern", diff --git a/preview/src/ReviewConfig.elm b/preview/src/ReviewConfig.elm index 5897739..46cc706 100644 --- a/preview/src/ReviewConfig.elm +++ b/preview/src/ReviewConfig.elm @@ -11,11 +11,11 @@ when inside the directory containing this file. -} -import NoDebug.TodoItForMe +import Derive import Review.Rule exposing (Rule) config : List Rule config = - [ NoDebug.TodoItForMe.rule True [] + [ Derive.rule True [] ] diff --git a/src/CodeGenerator/Test.elm b/src/CodeGenerator/Test.elm index f5e84ca..4b75443 100644 --- a/src/CodeGenerator/Test.elm +++ b/src/CodeGenerator/Test.elm @@ -8,6 +8,7 @@ module CodeGenerator.Test exposing (codeGenTest, codeGenTestFailsWith, codeGenIn import Array import CodeGenerator exposing (CodeGenerator) +import Derive import Elm.Constraint import Elm.License import Elm.Module @@ -24,7 +25,6 @@ import Elm.Type import Elm.Version import Expect exposing (Expectation) import Json.Decode -import NoDebug.TodoItForMe import Review.Project import Review.Project.Dependency exposing (Dependency) import Review.Test @@ -78,7 +78,7 @@ codeGenTestHelper description dependencies codeGens modules incremental fn = project = List.foldl Review.Project.addDependency Review.Test.Dependencies.projectWithElmCore validDependencies in - fn result (Review.Test.runOnModulesWithProjectData project (NoDebug.TodoItForMe.rule incremental codeGens) inputModules) + fn result (Review.Test.runOnModulesWithProjectData project (Derive.rule incremental codeGens) inputModules) else Expect.fail ("Found issues in the following dependencies: \n\n" ++ String.join "\n" failedDeps) @@ -100,8 +100,8 @@ codeGenTestFailsWith description dependencies codeGens modules expectedFailureDe Review.Test.expectErrorsForModules [ ( result.module_ , [ Review.Test.error - { message = "Remove the use of `Debug.todo` before shipping to production" - , details = [ "`Debug.todo` can be useful when developing, but is not meant to be shipped to production or published in a package. I suggest removing its use before committing and attempting to push to production.", expectedFailureDetails ] + { message = "Cannot generate the implementation of this `Debug.todo`" + , details = [ expectedFailureDetails ] , under = result.under } ] @@ -150,8 +150,8 @@ codeGenTest description dependencies codeGens modules expected = Review.Test.expectErrorsForModules [ ( result.module_ , [ Review.Test.error - { message = "Remove the use of `Debug.todo` before shipping to production" - , details = [ "`Debug.todo` can be useful when developing, but is not meant to be shipped to production or published in a package. I suggest removing its use before committing and attempting to push to production." ] + { message = "Remove the use of `Debug.todo`" + , details = [ "We have successfully generated an implementation for this `Debug.todo`" ] , under = result.under } |> Review.Test.whenFixed (String.replace "\u{000D}" "" expected) @@ -174,8 +174,8 @@ codeGenIncrementalTest description dependencies codeGens modules expected = Review.Test.expectErrorsForModules [ ( result.module_ , [ Review.Test.error - { message = "Remove the use of `Debug.todo` before shipping to production" - , details = [ "`Debug.todo` can be useful when developing, but is not meant to be shipped to production or published in a package. I suggest removing its use before committing and attempting to push to production." ] + { message = "Remove the use of `Debug.todo`" + , details = [ "We have successfully generated a stub for this `Debug.todo`" ] , under = result.under } |> Review.Test.whenFixed (String.replace "\u{000D}" "" expected) diff --git a/src/NoDebug/TodoItForMe.elm b/src/Derive.elm similarity index 97% rename from src/NoDebug/TodoItForMe.elm rename to src/Derive.elm index fee076a..41cab95 100644 --- a/src/NoDebug/TodoItForMe.elm +++ b/src/Derive.elm @@ -1,4 +1,4 @@ -module NoDebug.TodoItForMe exposing (rule) +module Derive exposing (rule) {-| @@ -134,7 +134,7 @@ rule incrementalMode generators = , Internal.Builtin.CsvDecoder.codeGen ] in - Rule.newProjectRuleSchema "CodeGen" initialProjectContext + Rule.newProjectRuleSchema "Derive" initialProjectContext |> Rule.withContextFromImportedModules |> Rule.withModuleVisitor moduleVisitor |> Rule.withModuleContextUsingContextCreator @@ -430,9 +430,9 @@ finalProjectEvaluation incrementalMode projectContext = List.map (\( moduleKey, range ) -> Rule.errorForModule moduleKey - { message = "Remove the use of `Debug.todo` before shipping to production" + { message = "Cannot derive implementation for Debug.todo within functions" , details = - [ "`Debug.todo` can be useful when developing, but is not meant to be shipped to production or published in a package. I suggest removing its use before committing and attempting to push to production." + [ "This rule can only derive implementations for you when the `Debug.todo` call is at the top-level and has an explicit supported type declaration." ] } range diff --git a/src/Internal/CodeGenTodo.elm b/src/Internal/CodeGenTodo.elm index 62d1ff4..05cc3b6 100644 --- a/src/Internal/CodeGenTodo.elm +++ b/src/Internal/CodeGenTodo.elm @@ -239,9 +239,13 @@ todoErrors incrementalMode projectContext currentModule todo = case createFixes incrementalMode projectContext codeGen imports currentModule todo of Ok fixes -> Rule.errorForModuleWithFix moduleKey - { message = "Remove the use of `Debug.todo` before shipping to production" + { message = "Remove the use of `Debug.todo`" , details = - [ "`Debug.todo` can be useful when developing, but is not meant to be shipped to production or published in a package. I suggest removing its use before committing and attempting to push to production." + [ if incrementalMode then + "We have successfully generated a stub for this `Debug.todo`" + + else + "We have successfully generated an implementation for this `Debug.todo`" ] } todo.range @@ -250,10 +254,9 @@ todoErrors incrementalMode projectContext currentModule todo = Err reason -> Rule.errorForModule moduleKey - { message = "Remove the use of `Debug.todo` before shipping to production" + { message = "Cannot generate the implementation of this `Debug.todo`" , details = - [ "`Debug.todo` can be useful when developing, but is not meant to be shipped to production or published in a package. I suggest removing its use before committing and attempting to push to production." - , reason + [ reason ] } todo.range diff --git a/tests/NoDebugTest.elm b/tests/NoDebugTest.elm index 2f97b9c..bfa7622 100644 --- a/tests/NoDebugTest.elm +++ b/tests/NoDebugTest.elm @@ -1,6 +1,6 @@ module NoDebugTest exposing (all) -import NoDebug.TodoItForMe +import Derive import Review.Test exposing (ReviewResult) import Review.Test.Dependencies import Test exposing (Test, describe, test) @@ -10,17 +10,17 @@ testRule : String -> ReviewResult testRule string = "module A exposing (..)\n\n" ++ string - |> Review.Test.runWithProjectData Review.Test.Dependencies.projectWithElmCore (NoDebug.TodoItForMe.rule False []) + |> Review.Test.runWithProjectData Review.Test.Dependencies.projectWithElmCore (Derive.rule False []) todoMessage : String todoMessage = - "Remove the use of `Debug.todo` before shipping to production" + "Cannot derive implementation for Debug.todo within functions" todoDetails : List String todoDetails = - [ "`Debug.todo` can be useful when developing, but is not meant to be shipped to production or published in a package. I suggest removing its use before committing and attempting to push to production." + [ "This rule can only derive implementations for you when the `Debug.todo` call is at the top-level and has an explicit supported type declaration." ] @@ -102,4 +102,20 @@ import Debug exposing (log) a = todo "" 1 """ |> Review.Test.expectNoErrors + , test "should report the use of `todo` when it has a non-matching type signature" <| + \() -> + testRule """ +import Json.Decode exposing (Decoder) +type Foo var = Foo var + +a : Decoder Int +a = Debug.todo "" +""" + |> Review.Test.expectErrors + [ Review.Test.error + { message = todoMessage + , details = todoDetails + , under = "Debug.todo" + } + ] ]