lue-bird/elm-review-missing-record-field-lens/1.0.2

NOTE: the latest version is 2.1.0

NoMissingRecordFieldLens

rule : Config -> Rule

Reports record field lenses that are called in the code but don't exist yet and automatically generates them.

Examples of such lenses are

config =
    [ NoMissingRecordFieldLens.rule
        { generate = NoMissingRecordFieldLens.accessors
        , generateIn = ( "Accessors", [ "Library", "Fields" ] )
        }
    ]

Example

module SomeModule exposing (scoreAPoint)


import Accessors.Library.Fields as Field


scoreAPoint =
    Accessors.over Field.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

Generate lenses for bChiquet's elm-accessors in the form

import Accessors exposing (Relation, makeOneToOne)


score : Relation score sub wrap -> Relation { record | score : score } sub wrap
score =
    makeOneToOne .score (\f r -> { r | score = f r.score })
monocle : FieldLensGenerator

Generate lenses for arturopala's elm-monocle in the form

import Monocle.Lens exposing (Lens)


score : Lens { record | score : score } score
score =
    { get = .score, set = \score_ r -> { r | score = score_ } }
fields : FieldLensGenerator

Generate lenses for sjorn3's elm-fields in the form

score :
    { get : { a | score : score } -> score
    , set : score -> { b | score : score } -> { b | score : score }
    }
score =
    { get = .score, set = \score_ r -> { r | score = score_ } }
zipper : FieldLensGenerator

Generate lenses for z5h's zipper in the form

import Zipper exposing (Zipper, into)


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

custom

type alias FieldLensGenerator =
{ imports : List 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 ]
            }
    }
{ documentation : Maybe (Comment DocComment)
, name : String
, annotation : Maybe TypeAnnotation
, implementation : 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 ]
    }
String
-> { access : Expression
, set : Expression
, update : Expression
}

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

functionsForField "score"
--> { access = accessFun ".score"
--> , set =
-->     lambda
-->         [ varPattern "score_", varPattern "r" ]
-->         (update "r" [ ( "score", val "score_" ) ])
--> , update =
-->     lambda
-->         [ varPattern "f", varPattern "r" ]
-->         (update "r"
-->             [ ( "score"
-->               , construct "f" [ access (val "r") "score" ]
-->               )
-->             ]
-->         )
--> }
getSetRecordForField : String -> Expression

Generate a field lens implementation in the form

{ get = .score, set = \score_ r -> { r | score = score_ } }

This is equivalent to

let
    { access, set } =
        functionsForField fieldName
in
record [ ( "get", access ), ( "set", set ) ]
withDocumentation : Comment 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")