lue-bird / elm-review-missing-record-field-lens / RecordFieldHelper.GenerateUsed

rule : Config -> Review.Rule.Rule

Automatically generates used record field helpers that don't exist yet.

Examples are

config =
    [ RecordFieldHelper.GenerateUsed.rule
        { generator = RecordFieldHelper.GenerateUsed.accessors
        , generateIn = ( "Accessors", [ "Library", "Fields" ] )
        }
    ]

Example

module SomeModule exposing (scoreAPoint)

import Accessors.Library.Fields as Field

scoreAPoint =
    Accessors.over Field.score (\score -> score + 1)

Fail

module Accessors.Library.Fields exposing (name)

...

Success

module Accessors.Library.Fields exposing (score)

...


type alias Config =
{ generator : FieldLensGenerator
, generateIn : ( String
, List String ) 
}

The rule's configuration.

lens generators

working out of the box

accessors : FieldLensGenerator

FieldLensGenerator for named erlandsona/elm-accessors in the form

import Accessors exposing (Lens, makeOneToOne_)

score : Lens { record | score : score } transformed score wrap
score =
    makeOneToOne_ ".score" .score (\alter record -> { record | score = record.score |> alter })

monocle : FieldLensGenerator

FieldLensGenerator for arturopala's elm-monocle in the form

import Monocle.Lens exposing (Lens)

score : Lens { record | score : score } score
score =
    { get = .score, set = \replacement record -> { record | score = replacement } }

fields : FieldLensGenerator

FieldLensGenerator for sjorn3's elm-fields in the form

score :
    { get : { record0 | score : score } -> score
    , set : score -> { record1 | score : score } -> { record1 | score : score }
    }
score =
    { get = .score, set = \replacement record -> { record | score = replacement } }

zipper : FieldLensGenerator

FieldLensGenerator for z5h's zipper in the form

import Zipper exposing (Zipper, into)

intoScore : Zipper { record | score : score } root -> Zipper score root
intoScore =
    into .score (\replacement record -> { record | score = replacement })

accessorsBChiquet : FieldLensGenerator

FieldLensGenerator for bChiquet/elm-accessors in the form

import Accessors exposing (Relation, makeOneToOne)

score : Relation score transformed wrap -> Relation { record | score : score } transformed wrap
score =
    makeOneToOne .score (\alter record -> { record | score = record.score |> alter })

accessors generates for erlandsona/elm-accessors which adds names.

custom


type alias FieldLensGenerator =
{ imports : List Elm.CodeGen.Import
, declaration : { fieldName : String } -> FieldLensDeclaration 
}

How to generate a FieldLensDeclaration plus the necessary imports.

Out of the box there are lenses for

You can also create a custom one with the help of the-sett's elm-syntax-dsl:

customLens : FieldLensGenerator
customLens =
    { imports =
        [ importStmt [ "CustomLens" ]
            Nothing
            (exposeExplicit
                [ typeOrAliasExpose "CustomLens" ]
                |> Just
            )
        ]
    , declaration =
        \{ fieldName } ->
            { documentation =
                emptyDocComment
                    |> markdown
                        ("`CustomLens` for the field `." ++ fieldName ++ "`.")
                    |> Just
            , name = fieldName
            , annotation =
                typed "CustomLens"
                    [ extRecordAnn "record"
                        [ ( fieldName, typeVar fieldName ) ]
                    , typeVar fieldName
                    ]
                    |> Just
            , implementation =
                let
                    { access, set } =
                        functionsForField fieldName
                in
                fqConstruct [ "CustomLens" ] "create" [ access, at ]
            }
    }


type alias FieldLensDeclaration =
{ documentation : Maybe (Elm.CodeGen.Comment Elm.CodeGen.DocComment)
, name : String
, annotation : Maybe Elm.CodeGen.TypeAnnotation
, implementation : Elm.CodeGen.Expression 
}

All the components to build a field lens declaration:

{-| [documentation]
-}
[name] : [annotation]
[name] =
    [implementation]

You can customize existing FieldLensDeclarations with withDocumentation and withName or create custom lens (functionsForField and getSetRecordForField can be helpful).

customLensDeclaration { fieldName } =
    { documentation =
        emptyDocComment
            |> markdown
                ("`CustomLens` for the field `." ++ fieldName ++ "`.")
            |> Just
    , name = fieldName
    , annotation =
        typed "CustomLens"
            [ extRecordAnn "record"
                [ ( fieldName, typeVar fieldName ) ]
            , typeVar fieldName
            ]
            |> Just
    , implementation =
        let
            { access, set } =
                functionsForField fieldName
        in
        fqConstruct [ "CustomLens" ] "create" [ access, at ]
    }

functionsForField : String -> { access : Elm.CodeGen.Expression, set : Elm.CodeGen.Expression, update : Elm.CodeGen.Expression }

access, set and update functions for a given record field.

functionsForField "score"

-->
{ access = accessFun ("." ++ fieldName)
, set =
    lambda
        [ varPattern "replacement"
        , varPattern "record"
        ]
        (update "record"
            [ ( fieldName
              , val "replacement"
              )
            ]
        )
, update =
    lambda
        [ varPattern "alter"
        , varPattern "record"
        ]
        (update "record"
            [ ( fieldName
              , applyBinOp
                    (access (val "record") fieldName)
                    piper
                    (fun "alter")
              )
            ]
        )
}

getSetRecordForField : String -> Elm.CodeGen.Expression

Generate a field lens implementation in the form

{ get = .score, set = \replacement record -> { record | score = replacement } }

This is equivalent to

let
    { access, set } =
        functionsForField fieldName
in
record [ ( "get", access ), ( "set", set ) ]

withDocumentation : Elm.CodeGen.Comment Elm.CodeGen.DocComment -> FieldLensDeclaration -> FieldLensDeclaration

The provided FieldLensGenerators in this package have no documentation comment.

You can generate your own documentation, though:

accessorsWithDocumentation { fieldName } =
    accessors { fieldName = fieldName }
        |> withDocumentation
            (emptyDocComment
                |> markdown
                    ("Accessor for the field `." ++ fieldName ++ "`.")
            )

withName : String -> FieldLensDeclaration -> FieldLensDeclaration

Use a different name for the generated lens.

accessorsWithFieldSuffix { fieldName } =
    accessors { fieldName = fieldName }
        |> withName (fieldName ++ "Field")