Jeroen Engels
@jfmengels
Elm Radio
module MyModule exposing (Model, Msg, update, view)
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
type alias Model =
{ count : Int }
type Msg
= Increment
| Decrement
update : Msg -> Model -> Model
update msg model =
case msg of
Increment ->
{ model | count = model.count + 1 }
Decrement ->
{ model | count = model.count - 1 }
view : Model -> Html Msg
view model =
div []
[ button [ onClick Increment ] [ text "+1" ]
, div [] [ text <| String.fromInt model.count ]
, button [ onClick Decrement ] [ text "-1" ]
]
-- Page/Checkout.elm
viewConfirmButton : Html msg
viewConfirmButton =
Html.button
[ Attr.style "height" "35px"
, Events.onClick UserClickedOnConfirmButton
]
[ Html.text "Confirm" ]
-- Page/Payment.elm
viewPayButton : Html msg
viewPayButton =
Html.button
[ Attr.style "height" "33px"
, Events.onClick UserClickedOnPayButton
]
[ Html.text "Pay" ]
module Ui.Button exposing (confirm, cancel, andSomeOtherButtons)
confirm : msg -> String -> Html msg
cancel : msg -> String -> Html msg
-- Page/Checkout.elm
import Ui.Button as Button
viewConfirmButton : Html msg
viewConfirmButton =
Button.confirm UserClickedOnConfirmButton "Confirm"
-- Page/Payment.elm
import Ui.Button as Button
viewPayButton : Html msg
viewPayButton =
Button.confirm UserClickedOnPayButton "Pay"
view : Model -> Html Msg
view model =
Html.button
[ Attr.style "height" "34px"
, Events.onClick UserClickedOnRemoveButton
]
[ Html.text "Remove" ]
view : Model -> Html Msg
view model =
Html.button
[ Attr.style "height" "34px"
, Events.onClick UserClickedOnRemoveButton
]
[ Html.text "Remove" ]
import NoUnused.Variables
import NoUsingHtmlButton
config : List Rule
config =
[ NoUnused.Variables.rule
, NoUsingHtmlButton.rule
]
(a + b) / 2
Integer 2
OperatorApplication
operator +
FunctionOrValue a
OperatorApplication
operator /
FunctionOrValue b
ParenthesizedExpression
rule : Rule
rule =
Rule.newSchema "NoUsingHtmlButton" ()
|> Rule.withExpressionVisitor expressionVisitor
|> Rule.fromSchema
expressionVisitor : Node Expression -> Context -> ( List Error, Context )
expressionVisitor node context =
case Node.value node of
Expression.FunctionOrValue [ "Html" ] "button" ->
( [ -- Report an error
]
, context
)
_ ->
( [], context )
expressionVisitor : Node Expression -> Context -> ( List Error, Context )
expressionVisitor node context =
case Node.value node of
Expression.FunctionOrValue [ "Html" ] "button" ->
( [ Rule.error
"Do not use Html.button"
(Node.range node)
]
, context
)
_ ->
( [], context )
Rule name
Source
code extract
Message
File path
expressionVisitor : Node Expression -> Context -> ( List Error, Context )
expressionVisitor node context =
case Node.value node of
Expression.FunctionOrValue [ "Html" ] "button" ->
( [ Rule.error
"Use Ui.Button instead of the native Html.button"
(Node.range node)
]
, context
)
_ ->
( [], context )
expressionVisitor : Node Expression -> Context -> ( List Error, Context )
expressionVisitor node context =
case Node.value node of
Expression.FunctionOrValue [ "Html" ] "button" ->
( [ Rule.error
{ message = "Use Ui.Button instead of the native Html.button"
, details =
[ "At fruits.com, we try to have a consistent UI across the application, and one of the ways we do that is by having a single nice module to create buttons, named Ui.Button."
, "Here, you defined a button using `Html.button` or `Html.Styled.Button`, which is likely not to have the consistent UI we aim for or some of the guarantees we created around our buttons."
, "Instead, you should use the Ui.Button module. I suggest reading the documentation in that module, but here is what it would kind of look like: ..."
]
}
(Node.range node)
]
, context
)
_ ->
( [], context )
Context
Problem
Suggestion
Example
Rule name
Source
code extract
Message
File path
view : Model -> Html Msg
view model =
-- elm-review-disable-next-line
Html.button
[ Attr.css
[ Css.height (Css.px 34)
, Css.fontSize (Css.px 16)
]
, Events.onClick UserClickedOnRemoveButton
]
[ Html.text "Remove" ]
type alias Context =
{ isUiButtonModule : Bool }
rule : Rule
rule =
Rule.newSchema "NoUnusedDeclarations" { isUiButtonModule = False }
-- visitors...
|> Rule.withModuleDefinitionVisitor moduleDefinitionVisitor
|> Rule.fromSchema
moduleDefinitionVisitor : Node Module -> Context -> ( List Error, Context )
moduleDefinitionVisitor (Node _ { moduleName }) context =
( [], { isUiButtonModule = (moduleName == "Ui.Button") } )
module Ui.Button exposing (confirm, cancel, andSomeOtherButtons)
expressionVisitor : Node Expression -> Context -> ( List Error, Context )
expressionVisitor node context =
case Node.value node of
Expression.FunctionOrValue [ "Html" ] "button" ->
if not context.isUiButtonModule then
( [ Rule.error {- ... -} ]
, context
)
else
( [], context )
_ ->
( [], context )
module A exposing (used, neverUsed)
used =
"some thing"
neverUsed =
"other thing"
module B exposing (thing)
import A
thing =
A.used ++ " is exposed"
No dynamic property access
const data = {
fn1: (a) => { /* do something */ },
fn2: (a) => { /* do something else */ }
};
function format(kind, value) {
return data['fn' + kind];
}
No dynamic property access
No variable return types
No side-effects
No mutations
No macros
...
No memory management
No race conditions
...
config : List Rule
config =
[ NoUnused.Variables.rule
, NoDebug.Log.rule
]
|> Rule.ignoreErrorsForDirectories [ "src-generated/", "src-vendor/" ]
{-| Does something.
@deprecated Use someBetterFunction which does it better.
-}
someFunction input =
-- do something with input
config : List Rule
config =
[ NoDeprecated.rule
|> Rule.ignoreErrorsForFiles
[ "src/Api.elm"
, -- ...and other files
]
]
{
"version": 1,
"automatically created by": "elm-review suppress",
"learn more": "elm-review suppress --help",
"suppressions": [
{ "count": 7, "filePath": "src/Api.elm" }
]
}
review/suppressed/NoDeprecated.json
Jeroen Engels
@jfmengels
Elm Radio
module Main exposing (main)
import Html
main =
Html.text used
used =
"used"
unused =
"unused"
type alias Context =
{ declared : Set String
, used : Set String
}
initialContext : Context
initialContext =
{ declared = Set.empty
, used = Set.empty
}
rule : Rule
rule =
Rule.newSchema "NoUnusedDeclarations" initialContext
-- visitors...
|> Rule.fromSchema
rule : Rule
rule =
Rule.newSchema "NoUnusedDeclarations" initialContext
-- visitors...
|> Rule.withDeclarationVisitor declarationVisitor
|> Rule.fromSchema
declarationVisitor : Node Declaration -> Context -> ( List Error, Context )
declarationVisitor node context =
case Node.value node of
Declaration.FunctionDeclaration { name } ->
( []
, { context | declared = Set.insert name context.declared }
)
_ ->
( [], context )
rule : Rule
rule =
Rule.newSchema "NoUnusedDeclarations" initialContext
-- visitors...
|> Rule.withExpressionVisitor expressionVisitor
|> Rule.fromSchema
expressionVisitor : Node Expression -> Context -> ( List Error, Context )
expressionVisitor node context =
case Node.value node of
Expression.FunctionOrValue [] name ->
( []
, { context | used = Set.insert name context.used }
)
_ ->
( [], context )
rule : Rule
rule =
Rule.newSchema "NoUnusedDeclarations" initialContext
-- visitors...
|> Rule.withFinalEvaluation finalEvaluation
|> Rule.fromSchema
finalEvaluation : Context -> List Error
finalEvaluation context =
Set.diff context.declared context.used
|> Set.toList
|> List.map createError
Language for making web apps
Pure functional language
Easy to learn
Statically typed
No runtime errors