mirror of
https://github.com/jfmengels/elm-review.git
synced 2024-10-05 20:18:06 +03:00
Backport rules from other packages
This commit is contained in:
parent
7a9246e4fa
commit
449b21addd
@ -33,6 +33,15 @@ and publishing the package. Otherwise the link for a given version could link to
|
||||
|
||||
**NOTE**: A similar rule would be useful for links inside the modules. I'll be working on that too!
|
||||
|
||||
|
||||
## Try it out
|
||||
|
||||
You can try this rule out by running the following command:
|
||||
|
||||
```bash
|
||||
elm-review --template jfmengels/review-documentation/example --rules NoUselessSubscriptions
|
||||
```
|
||||
|
||||
-}
|
||||
rule : Rule
|
||||
rule =
|
||||
|
@ -1,43 +0,0 @@
|
||||
module MiscRules.NoBooleanCase exposing (rule)
|
||||
|
||||
import Elm.Syntax.Expression as Expression exposing (Expression)
|
||||
import Elm.Syntax.Node as Node exposing (Node)
|
||||
import Elm.Syntax.Pattern as Pattern
|
||||
import Elm.Syntax.Range exposing (Range)
|
||||
import Review.Rule as Rule exposing (Error, Rule)
|
||||
|
||||
|
||||
rule : Rule
|
||||
rule =
|
||||
Rule.newModuleRuleSchema "NoBooleanCase" ()
|
||||
|> Rule.withSimpleExpressionVisitor expressionVisitor
|
||||
|> Rule.fromModuleRuleSchema
|
||||
|
||||
|
||||
expressionVisitor : Node Expression -> List (Error {})
|
||||
expressionVisitor (Node.Node range expression) =
|
||||
case expression of
|
||||
Expression.CaseExpression caseBlock ->
|
||||
errorsForCaseBlock range caseBlock
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
|
||||
errorsForCaseBlock : Range -> Expression.CaseBlock -> List (Error {})
|
||||
errorsForCaseBlock range { cases } =
|
||||
case cases of
|
||||
[ ( Node.Node _ (Pattern.NamedPattern { moduleName, name } []), _ ), _ ] ->
|
||||
if moduleName == [] && (name == "True" || name == "False") then
|
||||
[ Rule.error
|
||||
{ message = "Matching boolean values in a case .. of expression"
|
||||
, details = [ "It's quite silly" ]
|
||||
}
|
||||
range
|
||||
]
|
||||
|
||||
else
|
||||
[]
|
||||
|
||||
_ ->
|
||||
[]
|
@ -1,31 +0,0 @@
|
||||
module MiscRules.NoBooleanCaseTest exposing (all)
|
||||
|
||||
import MiscRules.NoBooleanCase as NoBooleanCase
|
||||
import Review.Test
|
||||
import Test exposing (Test, describe, test)
|
||||
|
||||
|
||||
all : Test
|
||||
all =
|
||||
describe "noBooleanCase"
|
||||
[ test "Simple one" <|
|
||||
\_ ->
|
||||
"""module A exposing (..)
|
||||
a =
|
||||
case expr of
|
||||
True -> True
|
||||
False -> False
|
||||
"""
|
||||
|> Review.Test.run NoBooleanCase.rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Matching boolean values in a case .. of expression"
|
||||
, details =
|
||||
[ "It's quite silly"
|
||||
]
|
||||
, under = """case expr of
|
||||
True -> True
|
||||
False -> False"""
|
||||
}
|
||||
]
|
||||
]
|
@ -1,103 +0,0 @@
|
||||
module NoBooleanCaseOf exposing (rule)
|
||||
|
||||
{-|
|
||||
|
||||
@docs rule
|
||||
|
||||
-}
|
||||
|
||||
import Elm.Syntax.Expression as Expression exposing (Expression)
|
||||
import Elm.Syntax.Node as Node exposing (Node)
|
||||
import Elm.Syntax.Pattern as Pattern exposing (Pattern)
|
||||
import Review.Rule as Rule exposing (Error, Rule)
|
||||
|
||||
|
||||
{-| Reports when pattern matching is used for a boolean value.
|
||||
|
||||
The idiomatic way to check for a condition is to use an `if` expression.
|
||||
Read more about it at: <https://guide.elm-lang.org/core_language.html#if-expressions>
|
||||
|
||||
config =
|
||||
[ NoBooleanCaseOf.rule
|
||||
]
|
||||
|
||||
This won't report pattern matching when a boolean is part of the evaluated value.
|
||||
|
||||
|
||||
## Fail
|
||||
|
||||
_ =
|
||||
case bool of
|
||||
True ->
|
||||
expression
|
||||
|
||||
False ->
|
||||
otherExpression
|
||||
|
||||
|
||||
## Success
|
||||
|
||||
_ =
|
||||
if bool then
|
||||
expression
|
||||
|
||||
else
|
||||
otherExpression
|
||||
|
||||
_ =
|
||||
case ( bool, somethingElse ) of
|
||||
( True, SomeThingElse ) ->
|
||||
expression
|
||||
|
||||
_ ->
|
||||
otherExpression
|
||||
|
||||
|
||||
# When (not) to use this rule
|
||||
|
||||
You should not use this rule if you do not care about how your boolean values are
|
||||
evaluated.
|
||||
|
||||
-}
|
||||
rule : Rule
|
||||
rule =
|
||||
Rule.newModuleRuleSchema "NoBooleanCaseOf" ()
|
||||
|> Rule.withSimpleExpressionVisitor expressionVisitor
|
||||
|> Rule.fromModuleRuleSchema
|
||||
|
||||
|
||||
expressionVisitor : Node Expression -> List (Error {})
|
||||
expressionVisitor node =
|
||||
case Node.value node of
|
||||
Expression.CaseExpression { expression, cases } ->
|
||||
if List.any (Tuple.first >> isBoolConstructor) cases then
|
||||
[ error expression ]
|
||||
|
||||
else
|
||||
[]
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
|
||||
error : Node a -> Error {}
|
||||
error node =
|
||||
Rule.error
|
||||
{ message = "Replace `case..of` by an `if` condition"
|
||||
, details =
|
||||
[ "The idiomatic way to check for a condition is to use an `if` expression."
|
||||
, "Read more about it at: https://guide.elm-lang.org/core_language.html#if-expressions"
|
||||
]
|
||||
}
|
||||
(Node.range node)
|
||||
|
||||
|
||||
isBoolConstructor : Node Pattern -> Bool
|
||||
isBoolConstructor node =
|
||||
case Node.value node of
|
||||
Pattern.NamedPattern { moduleName, name } _ ->
|
||||
(name == "True" || name == "False")
|
||||
&& (moduleName == [] || moduleName == [ "Basics" ])
|
||||
|
||||
_ ->
|
||||
False
|
@ -1,111 +0,0 @@
|
||||
module NoBooleanCaseOfTest exposing (all)
|
||||
|
||||
import NoBooleanCaseOf exposing (rule)
|
||||
import Review.Test exposing (ReviewResult)
|
||||
import Test exposing (Test, describe, test)
|
||||
|
||||
|
||||
testRule : String -> ReviewResult
|
||||
testRule string =
|
||||
"module A exposing (..)\n\n"
|
||||
++ string
|
||||
|> Review.Test.run rule
|
||||
|
||||
|
||||
message : String
|
||||
message =
|
||||
"Replace `case..of` by an `if` condition"
|
||||
|
||||
|
||||
details : List String
|
||||
details =
|
||||
[ "The idiomatic way to check for a condition is to use an `if` expression."
|
||||
, "Read more about it at: https://guide.elm-lang.org/core_language.html#if-expressions"
|
||||
]
|
||||
|
||||
|
||||
tests : List Test
|
||||
tests =
|
||||
[ test "should not report pattern matches for non-boolean values" <|
|
||||
\() ->
|
||||
testRule """
|
||||
a = case thing of
|
||||
Thing -> 1"""
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should not report pattern matches when the evaluated expression is a tuple of with a boolean" <|
|
||||
\() ->
|
||||
testRule """
|
||||
a = case ( bool1, bool2 ) of
|
||||
( True, True ) -> 1
|
||||
_ -> 2"""
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should report pattern matches when one of the patterns is a bool constructor" <|
|
||||
\() ->
|
||||
testRule """
|
||||
a = case boolTrue of
|
||||
True -> 1
|
||||
_ -> 2
|
||||
|
||||
b = case boolFalse of
|
||||
False -> 1
|
||||
_ -> 2
|
||||
|
||||
c = case boolAll of
|
||||
False -> 1
|
||||
True -> 2"""
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "boolTrue"
|
||||
}
|
||||
, Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "boolFalse"
|
||||
}
|
||||
, Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "boolAll"
|
||||
}
|
||||
]
|
||||
, test "should report pattern matches for booleans even when one of the patterns starts with `Basics.`" <|
|
||||
\() ->
|
||||
testRule """
|
||||
a = case boolTrue of
|
||||
Basics.True -> 1
|
||||
_ -> 2
|
||||
|
||||
b = case boolFalse of
|
||||
Basics.False -> 1
|
||||
_ -> 2"""
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "boolTrue"
|
||||
}
|
||||
, Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "boolFalse"
|
||||
}
|
||||
]
|
||||
, test "should report pattern matches for booleans even when the constructor seems to be for booleans but comes from an unknown module" <|
|
||||
\() ->
|
||||
testRule """
|
||||
a = case boolTrue of
|
||||
OtherModule.True -> 1
|
||||
_ -> 2
|
||||
|
||||
b = case boolFalse of
|
||||
OtherModule.False -> 1
|
||||
_ -> 2"""
|
||||
|> Review.Test.expectNoErrors
|
||||
]
|
||||
|
||||
|
||||
all : Test
|
||||
all =
|
||||
describe "NoBooleanCaseOf" tests
|
@ -51,10 +51,19 @@ You should not use this rule if you are developing an application that is not
|
||||
put into production, and you do not care about having stray debug logs, and you
|
||||
do not ship to production.
|
||||
|
||||
|
||||
## Try it out
|
||||
|
||||
You can try this rule out by running the following command:
|
||||
|
||||
```bash
|
||||
elm-review --template jfmengels/review-debug/example --rules NoDebug.Log
|
||||
```
|
||||
|
||||
-}
|
||||
rule : Rule
|
||||
rule =
|
||||
Rule.newModuleRuleSchema "NoDebugLog" { hasLogBeenImported = False }
|
||||
Rule.newModuleRuleSchema "NoDebug.Log" { hasLogBeenImported = False }
|
||||
|> Rule.withImportVisitor importVisitor
|
||||
|> Rule.withExpressionVisitor expressionVisitor
|
||||
|> Rule.fromModuleRuleSchema
|
||||
|
@ -23,265 +23,261 @@ details =
|
||||
]
|
||||
|
||||
|
||||
tests : List Test
|
||||
tests =
|
||||
[ test "should not report normal function calls" <|
|
||||
\() ->
|
||||
testRule """
|
||||
all : Test
|
||||
all =
|
||||
describe "NoDebug.Log"
|
||||
[ test "should not report normal function calls" <|
|
||||
\() ->
|
||||
testRule """
|
||||
a = foo n
|
||||
b = bar.foo n
|
||||
c = debug
|
||||
e = debug.log n
|
||||
d = debug.log n
|
||||
"""
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should not report Debug.todo or Debug.toString calls" <|
|
||||
\() ->
|
||||
testRule """
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should not report Debug.todo or Debug.toString calls" <|
|
||||
\() ->
|
||||
testRule """
|
||||
a = Debug.toString n
|
||||
b = Debug.todo ""
|
||||
"""
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should report Debug.log use" <|
|
||||
\() ->
|
||||
testRule "a = Debug.log"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log calls" <|
|
||||
\() ->
|
||||
testRule "a = Debug.log z"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report multiple Debug.log calls" <|
|
||||
\() ->
|
||||
testRule """
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should report Debug.log use" <|
|
||||
\() ->
|
||||
testRule "a = Debug.log"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log calls" <|
|
||||
\() ->
|
||||
testRule "a = Debug.log z"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report multiple Debug.log calls" <|
|
||||
\() ->
|
||||
testRule """
|
||||
a = Debug.log z
|
||||
b = Debug.log z
|
||||
"""
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 5 }, end = { row = 4, column = 14 } }
|
||||
, Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 5, column = 5 }, end = { row = 5, column = 14 } }
|
||||
]
|
||||
, test "should report Debug.log in a binary expression" <|
|
||||
\() ->
|
||||
testRule "a = ( Debug.log z ) + 2"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in a << binary expression" <|
|
||||
\() ->
|
||||
testRule "a = fn << Debug.log"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in a pipe expression" <|
|
||||
\() ->
|
||||
testRule "a = fn |> Debug.log z"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in an list expression" <|
|
||||
\() ->
|
||||
testRule "a = [ Debug.log z y ]"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in an record expression" <|
|
||||
\() ->
|
||||
testRule "a = { foo = Debug.log z y }"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in an record update expression" <|
|
||||
\() ->
|
||||
testRule "a = { model | foo = Debug.log z y }"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in an lambda expression" <|
|
||||
\() ->
|
||||
testRule "a = (\\foo -> Debug.log z foo)"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in an if expression condition" <|
|
||||
\() ->
|
||||
testRule "a = if Debug.log a b then True else False"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in an if expression then branch" <|
|
||||
\() ->
|
||||
testRule "a = if True then Debug.log a b else False"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in an if expression else branch" <|
|
||||
\() ->
|
||||
testRule "a = if True then True else Debug.log a b"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in a case value" <|
|
||||
\() ->
|
||||
testRule """
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 5 }, end = { row = 4, column = 14 } }
|
||||
, Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 5, column = 5 }, end = { row = 5, column = 14 } }
|
||||
]
|
||||
, test "should report Debug.log in a binary expression" <|
|
||||
\() ->
|
||||
testRule "a = ( Debug.log z ) + 2"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in a << binary expression" <|
|
||||
\() ->
|
||||
testRule "a = fn << Debug.log"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in a pipe expression" <|
|
||||
\() ->
|
||||
testRule "a = fn |> Debug.log z"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in an list expression" <|
|
||||
\() ->
|
||||
testRule "a = [ Debug.log z y ]"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in an record expression" <|
|
||||
\() ->
|
||||
testRule "a = { foo = Debug.log z y }"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in an record update expression" <|
|
||||
\() ->
|
||||
testRule "a = { model | foo = Debug.log z y }"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in an lambda expression" <|
|
||||
\() ->
|
||||
testRule "a = (\\foo -> Debug.log z foo)"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in an if expression condition" <|
|
||||
\() ->
|
||||
testRule "a = if Debug.log a b then True else False"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in an if expression then branch" <|
|
||||
\() ->
|
||||
testRule "a = if True then Debug.log a b else False"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in an if expression else branch" <|
|
||||
\() ->
|
||||
testRule "a = if True then True else Debug.log a b"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in a case value" <|
|
||||
\() ->
|
||||
testRule """
|
||||
a = case Debug.log a b of
|
||||
_ -> []
|
||||
"""
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in a case body" <|
|
||||
\() ->
|
||||
testRule """
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in a case body" <|
|
||||
\() ->
|
||||
testRule """
|
||||
a = case a of
|
||||
_ -> Debug.log a b
|
||||
"""
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in let declaration section" <|
|
||||
\() ->
|
||||
testRule """
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in let declaration section" <|
|
||||
\() ->
|
||||
testRule """
|
||||
a = let b = Debug.log a b
|
||||
in b
|
||||
"""
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in let body" <|
|
||||
\() ->
|
||||
testRule """
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.log in let body" <|
|
||||
\() ->
|
||||
testRule """
|
||||
a = let b = c
|
||||
in Debug.log a b
|
||||
"""
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should not report calls from a module containing Debug but that is not Debug" <|
|
||||
\() ->
|
||||
testRule """
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
, test "should not report calls from a module containing Debug but that is not Debug" <|
|
||||
\() ->
|
||||
testRule """
|
||||
a = Foo.Debug.log 1
|
||||
b = Debug.Foo.log 1
|
||||
"""
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should not report the import of the Debug module" <|
|
||||
\() ->
|
||||
testRule "import Debug"
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should report the use of `log` when it has been explicitly imported" <|
|
||||
\() ->
|
||||
testRule """
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should not report the import of the Debug module" <|
|
||||
\() ->
|
||||
testRule "import Debug"
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should report the use of `log` when it has been explicitly imported" <|
|
||||
\() ->
|
||||
testRule """
|
||||
import Debug exposing (log)
|
||||
a = log "" 1
|
||||
"""
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "log"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 5, column = 5 }, end = { row = 5, column = 8 } }
|
||||
]
|
||||
, test "should report the use of `log` when it has been implicitly imported" <|
|
||||
\() ->
|
||||
testRule """
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "log"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 5, column = 5 }, end = { row = 5, column = 8 } }
|
||||
]
|
||||
, test "should report the use of `log` when it has been implicitly imported" <|
|
||||
\() ->
|
||||
testRule """
|
||||
import Debug exposing (..)
|
||||
a = log "" 1
|
||||
"""
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "log"
|
||||
}
|
||||
]
|
||||
, test "should not report the use of `log` when it has not been imported" <|
|
||||
\() ->
|
||||
testRule """
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "log"
|
||||
}
|
||||
]
|
||||
, test "should not report the use of `log` when it has not been imported" <|
|
||||
\() ->
|
||||
testRule """
|
||||
import Debug exposing (todo)
|
||||
a = log "" 1
|
||||
"""
|
||||
|> Review.Test.expectNoErrors
|
||||
]
|
||||
|
||||
|
||||
all : Test
|
||||
all =
|
||||
describe "NoDebugLog" tests
|
||||
|> Review.Test.expectNoErrors
|
||||
]
|
||||
|
@ -68,6 +68,15 @@ can configure the rule like this.
|
||||
else
|
||||
b
|
||||
|
||||
|
||||
## Try it out
|
||||
|
||||
You can try this rule out by running the following command:
|
||||
|
||||
```bash
|
||||
elm-review --template jfmengels/review-debug/example --rules NoDebug.TodoOrToString
|
||||
```
|
||||
|
||||
[`Debug.log`]: https://package.elm-lang.org/packages/elm/core/latest/Debug#log
|
||||
[`Debug.todo`]: https://package.elm-lang.org/packages/elm/core/latest/Debug#todo
|
||||
[`Debug.toString`]: https://package.elm-lang.org/packages/elm/core/latest/Debug#toString
|
||||
|
@ -1,16 +1,15 @@
|
||||
module NoDebug.TodoOrToStringTest exposing (all)
|
||||
|
||||
import Elm.Project
|
||||
import Json.Decode as Decode
|
||||
import NoDebug.TodoOrToString exposing (rule)
|
||||
import Review.Project
|
||||
import Review.Test
|
||||
import Review.Test exposing (ReviewResult)
|
||||
import Test exposing (Test, describe, test)
|
||||
|
||||
|
||||
all : Test
|
||||
all =
|
||||
describe "NoDebug.TodoOrToString" tests
|
||||
testRule : String -> ReviewResult
|
||||
testRule string =
|
||||
"module A exposing (..)\n\n"
|
||||
++ string
|
||||
|> Review.Test.run rule
|
||||
|
||||
|
||||
todoMessage : String
|
||||
@ -35,11 +34,12 @@ toStringDetails =
|
||||
]
|
||||
|
||||
|
||||
tests : List Test
|
||||
tests =
|
||||
[ test "should not report normal function calls" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
all : Test
|
||||
all =
|
||||
describe "NoDebug.TodoOrToString"
|
||||
[ test "should not report normal function calls" <|
|
||||
\() ->
|
||||
testRule """
|
||||
a = foo n
|
||||
b = bar.foo n
|
||||
c = debug
|
||||
@ -48,204 +48,112 @@ c = List.toString
|
||||
d = debug.todo n
|
||||
e = debug.toString n
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should not report Debug.log calls" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should not report Debug.log calls" <|
|
||||
\() ->
|
||||
testRule """
|
||||
a = Debug.log n
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should report Debug.todo use" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
a = Debug.todo"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = todoMessage
|
||||
, details = todoDetails
|
||||
, under = "Debug.todo"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.toString use" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
a = Debug.toString"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = toStringMessage
|
||||
, details = toStringDetails
|
||||
, under = "Debug.toString"
|
||||
}
|
||||
]
|
||||
, test "should not report calls from a module containing Debug but that is not Debug" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should report Debug.todo use" <|
|
||||
\() ->
|
||||
testRule "a = Debug.todo"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = todoMessage
|
||||
, details = todoDetails
|
||||
, under = "Debug.todo"
|
||||
}
|
||||
]
|
||||
, test "should report Debug.toString use" <|
|
||||
\() ->
|
||||
testRule "a = Debug.toString"
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = toStringMessage
|
||||
, details = toStringDetails
|
||||
, under = "Debug.toString"
|
||||
}
|
||||
]
|
||||
, test "should not report calls from a module containing Debug but that is not Debug" <|
|
||||
\() ->
|
||||
testRule """
|
||||
a = Foo.Debug.todo 1
|
||||
b = Debug.Foo.todo 1
|
||||
a = Foo.Debug.toString 1
|
||||
b = Debug.Foo.toString 1
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should not report the import of the Debug module" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
import Debug"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should report the use of `todo` when `todo` has been explicitly imported" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
"""
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should not report the import of the Debug module" <|
|
||||
\() ->
|
||||
testRule "import Debug"
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should report the use of `todo` when `todo` has been explicitly imported" <|
|
||||
\() ->
|
||||
testRule """
|
||||
import Debug exposing (todo)
|
||||
a = todo ""
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = todoMessage
|
||||
, details = todoDetails
|
||||
, under = "todo"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 5 }, end = { row = 3, column = 9 } }
|
||||
]
|
||||
, test "should report the use of `todo` when `todo` has been implicitly imported" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = todoMessage
|
||||
, details = todoDetails
|
||||
, under = "todo"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 5, column = 5 }, end = { row = 5, column = 9 } }
|
||||
]
|
||||
, test "should report the use of `todo` when `todo` has been implicitly imported" <|
|
||||
\() ->
|
||||
testRule """
|
||||
import Debug exposing (..)
|
||||
a = todo "" 1
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = todoMessage
|
||||
, details = todoDetails
|
||||
, under = "todo"
|
||||
}
|
||||
]
|
||||
, test "should not report the use of `todo` when it has not been imported" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = todoMessage
|
||||
, details = todoDetails
|
||||
, under = "todo"
|
||||
}
|
||||
]
|
||||
, test "should not report the use of `todo` when it has not been imported" <|
|
||||
\() ->
|
||||
testRule """
|
||||
import Debug exposing (log)
|
||||
a = todo "" 1
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should report the use of `toString` when `toString` has been explicitly imported" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should report the use of `toString` when `toString` has been explicitly imported" <|
|
||||
\() ->
|
||||
testRule """
|
||||
import Debug exposing (toString)
|
||||
a = toString ""
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = toStringMessage
|
||||
, details = toStringDetails
|
||||
, under = "toString"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 5 }, end = { row = 3, column = 13 } }
|
||||
]
|
||||
, test "should report the use of `toString` when `toString` has been implicitly imported" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = toStringMessage
|
||||
, details = toStringDetails
|
||||
, under = "toString"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 5, column = 5 }, end = { row = 5, column = 13 } }
|
||||
]
|
||||
, test "should report the use of `toString` when `toString` has been implicitly imported" <|
|
||||
\() ->
|
||||
testRule """
|
||||
import Debug exposing (..)
|
||||
a = toString "" 1
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = toStringMessage
|
||||
, details = toStringDetails
|
||||
, under = "toString"
|
||||
}
|
||||
]
|
||||
, test "should not report the use of `toString` when it has not been imported" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = toStringMessage
|
||||
, details = toStringDetails
|
||||
, under = "toString"
|
||||
}
|
||||
]
|
||||
, test "should not report the use of `toString` when it has not been imported" <|
|
||||
\() ->
|
||||
testRule """
|
||||
import Debug exposing (log)
|
||||
a = toString "" 1
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should not report the use of `toString` or `todo` in files outside the source directories (TODO only with flag)" <|
|
||||
\() ->
|
||||
let
|
||||
project : Review.Project.Project
|
||||
project =
|
||||
Review.Project.addModule { path = "tests/FooTest.elm", source = """module A exposing (..)
|
||||
a = Debug.todo Debug.toString""" } applicationProject
|
||||
in
|
||||
Review.Test.runOnModulesWithProjectData project rule []
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should report the use of `toString` or `todo` in files inside the source directories (TODO only with flag)" <|
|
||||
\() ->
|
||||
let
|
||||
project : Review.Project.Project
|
||||
project =
|
||||
Review.Project.addModule { path = "src/Foo.elm", source = """module A exposing (..)
|
||||
a = Debug.todo Debug.toString""" } applicationProject
|
||||
in
|
||||
Review.Test.runOnModulesWithProjectData project rule []
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = todoMessage
|
||||
, details = todoDetails
|
||||
, under = "Debug.todo"
|
||||
}
|
||||
, Review.Test.error
|
||||
{ message = toStringMessage
|
||||
, details = toStringDetails
|
||||
, under = "Debug.toString"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
applicationProject : Review.Project.Project
|
||||
applicationProject =
|
||||
Review.Project.new
|
||||
|> withDebugTodoElmJson Debug.todo rawApplicationElmJson
|
||||
|
||||
|
||||
rawApplicationElmJson : String
|
||||
rawApplicationElmJson =
|
||||
"""
|
||||
{
|
||||
"type": "package",
|
||||
"name": "author/dependency",
|
||||
"summary": "Summary",
|
||||
"license": "MIT",
|
||||
"version": "1.0.0",
|
||||
"exposed-modules": [
|
||||
"Foo"
|
||||
],
|
||||
"elm-version": "0.19.0 <= v < 0.20.0",
|
||||
"dependencies": {
|
||||
"elm/core": "1.0.0 <= v < 2.0.0"
|
||||
},
|
||||
"test-dependencies": {}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
withDebugTodoElmJson : (String -> Never) -> String -> Review.Project.Project -> Review.Project.Project
|
||||
withDebugTodoElmJson debugTodo rawElmJson project =
|
||||
case Decode.decodeString Elm.Project.decoder rawElmJson of
|
||||
Ok elmJsonProject ->
|
||||
Review.Project.addElmJson
|
||||
{ path = "elm.json"
|
||||
, raw = rawElmJson
|
||||
, project = elmJsonProject
|
||||
}
|
||||
project
|
||||
|
||||
Err _ ->
|
||||
let
|
||||
_ =
|
||||
debugTodo "Invalid elm.json supplied to test"
|
||||
in
|
||||
withDebugTodoElmJson debugTodo rawElmJson project
|
||||
|> Review.Test.expectNoErrors
|
||||
]
|
||||
|
@ -40,6 +40,15 @@ in the following manner:
|
||||
|
||||
module A exposing (B(..), C, d)
|
||||
|
||||
|
||||
## Try it out
|
||||
|
||||
You can try this rule out by running the following command:
|
||||
|
||||
```bash
|
||||
elm-review --template jfmengels/review-common/example --rules NoExposingEverything
|
||||
```
|
||||
|
||||
-}
|
||||
rule : Rule
|
||||
rule =
|
||||
|
@ -9,7 +9,7 @@ all : Test
|
||||
all =
|
||||
describe "NoExposingEverything"
|
||||
[ test "should not report anything when a module exposes a limited set of things" <|
|
||||
\_ ->
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (B(..), C, d)
|
||||
type B = B
|
||||
@ -18,7 +18,7 @@ d = 1
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should report when a module exposes everything" <|
|
||||
\_ ->
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
import B exposing (..)
|
||||
|
@ -36,6 +36,15 @@ import Review.Rule as Rule exposing (Error, Rule)
|
||||
_ =
|
||||
(+)
|
||||
|
||||
|
||||
## Try it out
|
||||
|
||||
You can try this rule out by running the following command:
|
||||
|
||||
```bash
|
||||
elm-review --template jfmengels/elm-review-simplification/example --rules NoFullyAppliedPrefixOperator
|
||||
```
|
||||
|
||||
-}
|
||||
rule : Rule
|
||||
rule =
|
||||
|
@ -44,6 +44,15 @@ you can configure a list of exceptions.
|
||||
-- If configured with `[ "Html" ]`
|
||||
import Html exposing (..)
|
||||
|
||||
|
||||
## Try it out
|
||||
|
||||
You can try this rule out by running the following command:
|
||||
|
||||
```bash
|
||||
elm-review --template jfmengels/review-common/example --rules NoImportingEverything
|
||||
```
|
||||
|
||||
-}
|
||||
rule : List String -> Rule
|
||||
rule exceptions =
|
||||
@ -73,7 +82,7 @@ importVisitor exceptions node =
|
||||
Just (Exposing.All range) ->
|
||||
[ Rule.error
|
||||
{ message = "Prefer listing what you wish to import and/or using qualified imports"
|
||||
, details = [ "When you import everything from a module, it becomes harder to know where a function or a type comes from" ]
|
||||
, details = [ "When you import everything from a module it becomes harder to know where a function or a type comes from." ]
|
||||
}
|
||||
{ start = { row = range.start.row, column = range.start.column - 1 }
|
||||
, end = { row = range.end.row, column = range.end.column + 1 }
|
||||
|
@ -39,7 +39,7 @@ import Html exposing (..)
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Prefer listing what you wish to import and/or using qualified imports"
|
||||
, details = [ "When you import everything from a module, it becomes harder to know where a function or a type comes from" ]
|
||||
, details = [ "When you import everything from a module it becomes harder to know where a function or a type comes from." ]
|
||||
, under = "(..)"
|
||||
}
|
||||
]
|
||||
|
96
tests/NoInconsistentAliases.elm
Normal file
96
tests/NoInconsistentAliases.elm
Normal file
@ -0,0 +1,96 @@
|
||||
module NoInconsistentAliases exposing
|
||||
( rule, config, noMissingAliases
|
||||
, Config
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
@docs rule, config, noMissingAliases
|
||||
@docs Config
|
||||
|
||||
-}
|
||||
|
||||
import NoInconsistentAliases.Config as Config
|
||||
import NoInconsistentAliases.Visitor as Visitor
|
||||
import Review.Rule exposing (Rule)
|
||||
|
||||
|
||||
{-| Ensure consistent use of import aliases throughout your project.
|
||||
|
||||
config : List Rule
|
||||
config =
|
||||
[ NoInconsistentAliases.config
|
||||
[ ( "Html.Attributes", "Attr" )
|
||||
]
|
||||
|> NoInconsistentAliases.rule
|
||||
]
|
||||
|
||||
-}
|
||||
rule : Config -> Rule
|
||||
rule =
|
||||
Visitor.rule
|
||||
|
||||
|
||||
{-| Provide a list of preferred names to be enforced. If we find any of the given modules imported with a different alias we will report them.
|
||||
|
||||
NoInconsistentAliases.config
|
||||
[ ( "Html.Attributes", "Attr" )
|
||||
, ( "Json.Decode", "Decode" )
|
||||
, ( "Json.Encode", "Encode" )
|
||||
]
|
||||
|> NoInconsistentAliases.rule
|
||||
|
||||
-}
|
||||
config : List ( String, String ) -> Config
|
||||
config =
|
||||
Config.config
|
||||
|
||||
|
||||
{-| Ensure that imports are aliased if a module is used to qualify a function or type, and has a known alias.
|
||||
|
||||
NoInconsistentAliases.config
|
||||
[ ( "Html.Attributes", "Attr" )
|
||||
]
|
||||
|> NoInconsistentAliases.noMissingAliases
|
||||
|> NoInconsistentAliases.rule
|
||||
|
||||
|
||||
## Failure
|
||||
|
||||
Here `Html.Attributes` has been used to call `class` and the preferred alias has not been used.
|
||||
|
||||
import Html.Attributes
|
||||
|
||||
view children =
|
||||
div [ Html.Attributes.class "container" ] children
|
||||
|
||||
|
||||
## Success
|
||||
|
||||
Here `Html.Attributes` has been aliased to `Attr` as expected.
|
||||
|
||||
import Html.Attributes as Attr
|
||||
|
||||
view children =
|
||||
div [ Attr.class "container" ] children
|
||||
|
||||
|
||||
## Success
|
||||
|
||||
Here `class` has been exposed so the alias is not needed.
|
||||
|
||||
import Html.Attributes exposing (class)
|
||||
|
||||
view children =
|
||||
div [ class "container" ] children
|
||||
|
||||
-}
|
||||
noMissingAliases : Config -> Config
|
||||
noMissingAliases =
|
||||
Config.noMissingAliases
|
||||
|
||||
|
||||
{-| Configuration for the NoInconsistentAliases rule.
|
||||
-}
|
||||
type alias Config =
|
||||
Config.Config
|
56
tests/NoInconsistentAliases/BadAlias.elm
Normal file
56
tests/NoInconsistentAliases/BadAlias.elm
Normal file
@ -0,0 +1,56 @@
|
||||
module NoInconsistentAliases.BadAlias exposing (BadAlias, mapExpectedName, mapModuleName, mapName, mapUses, new, range, withModuleUse)
|
||||
|
||||
import Elm.Syntax.ModuleName exposing (ModuleName)
|
||||
import Elm.Syntax.Range exposing (Range)
|
||||
import NoInconsistentAliases.ModuleUse exposing (ModuleUse)
|
||||
|
||||
|
||||
type BadAlias
|
||||
= BadAlias
|
||||
{ name : String
|
||||
, moduleName : ModuleName
|
||||
, expectedName : String
|
||||
, at : Range
|
||||
, uses : List ModuleUse
|
||||
}
|
||||
|
||||
|
||||
new : { name : String, moduleName : ModuleName, expectedName : String, range : Range } -> BadAlias
|
||||
new options =
|
||||
BadAlias
|
||||
{ name = options.name
|
||||
, moduleName = options.moduleName
|
||||
, expectedName = options.expectedName
|
||||
, at = options.range
|
||||
, uses = []
|
||||
}
|
||||
|
||||
|
||||
withModuleUse : ModuleUse -> BadAlias -> BadAlias
|
||||
withModuleUse moduleUse (BadAlias alias) =
|
||||
BadAlias { alias | uses = moduleUse :: alias.uses }
|
||||
|
||||
|
||||
range : BadAlias -> Range
|
||||
range (BadAlias alias) =
|
||||
alias.at
|
||||
|
||||
|
||||
mapName : (String -> a) -> BadAlias -> a
|
||||
mapName mapper (BadAlias alias) =
|
||||
mapper alias.name
|
||||
|
||||
|
||||
mapModuleName : (ModuleName -> a) -> BadAlias -> a
|
||||
mapModuleName mapper (BadAlias alias) =
|
||||
mapper alias.moduleName
|
||||
|
||||
|
||||
mapExpectedName : (String -> a) -> BadAlias -> a
|
||||
mapExpectedName mapper (BadAlias alias) =
|
||||
mapper alias.expectedName
|
||||
|
||||
|
||||
mapUses : (ModuleUse -> a) -> BadAlias -> List a
|
||||
mapUses mapper (BadAlias { uses }) =
|
||||
List.map mapper uses
|
38
tests/NoInconsistentAliases/BadAliasSet.elm
Normal file
38
tests/NoInconsistentAliases/BadAliasSet.elm
Normal file
@ -0,0 +1,38 @@
|
||||
module NoInconsistentAliases.BadAliasSet exposing (BadAliasSet, empty, fold, insert, use)
|
||||
|
||||
import Dict exposing (Dict)
|
||||
import NoInconsistentAliases.BadAlias as BadAlias exposing (BadAlias)
|
||||
import NoInconsistentAliases.ModuleUse exposing (ModuleUse)
|
||||
|
||||
|
||||
type BadAliasSet
|
||||
= BadAliasSet (Dict String BadAlias)
|
||||
|
||||
|
||||
empty : BadAliasSet
|
||||
empty =
|
||||
BadAliasSet Dict.empty
|
||||
|
||||
|
||||
insert : BadAlias -> BadAliasSet -> BadAliasSet
|
||||
insert badAlias (BadAliasSet aliases) =
|
||||
BadAliasSet (BadAlias.mapName (\name -> Dict.insert name badAlias aliases) badAlias)
|
||||
|
||||
|
||||
use : String -> ModuleUse -> BadAliasSet -> BadAliasSet
|
||||
use name moduleUse (BadAliasSet aliases) =
|
||||
case Dict.get name aliases of
|
||||
Nothing ->
|
||||
BadAliasSet aliases
|
||||
|
||||
Just badAlias ->
|
||||
let
|
||||
badAliasWithUse =
|
||||
BadAlias.withModuleUse moduleUse badAlias
|
||||
in
|
||||
BadAliasSet (Dict.insert name badAliasWithUse aliases)
|
||||
|
||||
|
||||
fold : (BadAlias -> a -> a) -> a -> BadAliasSet -> a
|
||||
fold folder start (BadAliasSet aliases) =
|
||||
aliases |> Dict.values |> List.foldl folder start
|
56
tests/NoInconsistentAliases/Config.elm
Normal file
56
tests/NoInconsistentAliases/Config.elm
Normal file
@ -0,0 +1,56 @@
|
||||
module NoInconsistentAliases.Config exposing
|
||||
( Config, config, noMissingAliases
|
||||
, canMissAliases, lookupAlias
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
@docs Config, config, noMissingAliases
|
||||
@docs canMissAliases, lookupAlias
|
||||
|
||||
-}
|
||||
|
||||
import Dict exposing (Dict)
|
||||
import Elm.Syntax.ModuleName exposing (ModuleName)
|
||||
|
||||
|
||||
type Config
|
||||
= Config
|
||||
{ aliases : Dict ModuleName String
|
||||
, allowMissingAliases : Bool
|
||||
}
|
||||
|
||||
|
||||
config : List ( String, String ) -> Config
|
||||
config aliases =
|
||||
Config
|
||||
{ aliases =
|
||||
aliases
|
||||
|> List.map (Tuple.mapFirst toModuleName)
|
||||
|> Dict.fromList
|
||||
, allowMissingAliases = True
|
||||
}
|
||||
|
||||
|
||||
noMissingAliases : Config -> Config
|
||||
noMissingAliases (Config cfg) =
|
||||
Config { cfg | allowMissingAliases = False }
|
||||
|
||||
|
||||
canMissAliases : Config -> Bool
|
||||
canMissAliases (Config cfg) =
|
||||
cfg.allowMissingAliases
|
||||
|
||||
|
||||
lookupAlias : Config -> ModuleName -> Maybe String
|
||||
lookupAlias (Config { aliases }) moduleName =
|
||||
Dict.get moduleName aliases
|
||||
|
||||
|
||||
|
||||
--- HELPERS
|
||||
|
||||
|
||||
toModuleName : String -> ModuleName
|
||||
toModuleName moduleName =
|
||||
String.split "." moduleName
|
105
tests/NoInconsistentAliases/Context.elm
Normal file
105
tests/NoInconsistentAliases/Context.elm
Normal file
@ -0,0 +1,105 @@
|
||||
module NoInconsistentAliases.Context exposing
|
||||
( Module, initial
|
||||
, addModuleAlias, lookupModuleName
|
||||
, addMissingAlias, foldMissingAliases
|
||||
, addBadAlias, foldBadAliases
|
||||
, addModuleCall
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
@docs Module, initial
|
||||
@docs addModuleAlias, lookupModuleName
|
||||
@docs addMissingAlias, foldMissingAliases
|
||||
@docs addBadAlias, foldBadAliases
|
||||
@docs addModuleCall
|
||||
|
||||
-}
|
||||
|
||||
import Dict exposing (Dict)
|
||||
import Elm.Syntax.ModuleName exposing (ModuleName)
|
||||
import Elm.Syntax.Range exposing (Range)
|
||||
import NoInconsistentAliases.BadAlias exposing (BadAlias)
|
||||
import NoInconsistentAliases.BadAliasSet as BadAliasSet exposing (BadAliasSet)
|
||||
import NoInconsistentAliases.MissingAlias exposing (MissingAlias)
|
||||
import NoInconsistentAliases.MissingAliasSet as MissingAliasSet exposing (MissingAliasSet)
|
||||
import NoInconsistentAliases.ModuleUse as ModuleUse exposing (ModuleUse)
|
||||
|
||||
|
||||
type Module
|
||||
= Module
|
||||
{ aliases : Dict String ModuleName
|
||||
, badAliases : BadAliasSet
|
||||
, missingAliases : MissingAliasSet
|
||||
}
|
||||
|
||||
|
||||
initial : Module
|
||||
initial =
|
||||
Module
|
||||
{ aliases = Dict.empty
|
||||
, badAliases = BadAliasSet.empty
|
||||
, missingAliases = MissingAliasSet.empty
|
||||
}
|
||||
|
||||
|
||||
addModuleAlias : ModuleName -> String -> Module -> Module
|
||||
addModuleAlias moduleName moduleAlias (Module context) =
|
||||
Module { context | aliases = Dict.insert moduleAlias moduleName context.aliases }
|
||||
|
||||
|
||||
addBadAlias : BadAlias -> Module -> Module
|
||||
addBadAlias badAlias (Module context) =
|
||||
Module { context | badAliases = BadAliasSet.insert badAlias context.badAliases }
|
||||
|
||||
|
||||
addMissingAlias : MissingAlias -> Module -> Module
|
||||
addMissingAlias missingAlias (Module context) =
|
||||
Module { context | missingAliases = MissingAliasSet.insert missingAlias context.missingAliases }
|
||||
|
||||
|
||||
addModuleCall : ModuleName -> String -> Range -> Module -> Module
|
||||
addModuleCall moduleName function range context =
|
||||
let
|
||||
moduleUse =
|
||||
ModuleUse.new function range
|
||||
in
|
||||
context
|
||||
|> useBadAliasCall moduleName moduleUse
|
||||
|> useMissingAliasCall moduleName moduleUse
|
||||
|
||||
|
||||
useBadAliasCall : ModuleName -> ModuleUse -> Module -> Module
|
||||
useBadAliasCall moduleName moduleUse (Module context) =
|
||||
case moduleName of
|
||||
[ moduleAlias ] ->
|
||||
Module
|
||||
{ context
|
||||
| badAliases = BadAliasSet.use moduleAlias moduleUse context.badAliases
|
||||
}
|
||||
|
||||
_ ->
|
||||
Module context
|
||||
|
||||
|
||||
useMissingAliasCall : ModuleName -> ModuleUse -> Module -> Module
|
||||
useMissingAliasCall moduleName moduleUse (Module context) =
|
||||
Module
|
||||
{ context
|
||||
| missingAliases = MissingAliasSet.use moduleName moduleUse context.missingAliases
|
||||
}
|
||||
|
||||
|
||||
foldBadAliases : (BadAlias -> a -> a) -> a -> Module -> a
|
||||
foldBadAliases folder start (Module { badAliases }) =
|
||||
BadAliasSet.fold folder start badAliases
|
||||
|
||||
|
||||
lookupModuleName : Module -> String -> Maybe ModuleName
|
||||
lookupModuleName (Module { aliases }) moduleAlias =
|
||||
Dict.get moduleAlias aliases
|
||||
|
||||
|
||||
foldMissingAliases : (MissingAlias -> a -> a) -> a -> Module -> a
|
||||
foldMissingAliases folder start (Module { missingAliases }) =
|
||||
MissingAliasSet.fold folder start missingAliases
|
54
tests/NoInconsistentAliases/MissingAlias.elm
Normal file
54
tests/NoInconsistentAliases/MissingAlias.elm
Normal file
@ -0,0 +1,54 @@
|
||||
module NoInconsistentAliases.MissingAlias exposing (MissingAlias, hasUses, mapExpectedName, mapModuleName, mapUses, new, range, withModuleUse)
|
||||
|
||||
import Elm.Syntax.ModuleName exposing (ModuleName)
|
||||
import Elm.Syntax.Range exposing (Range)
|
||||
import NoInconsistentAliases.ModuleUse exposing (ModuleUse)
|
||||
|
||||
|
||||
type MissingAlias
|
||||
= MissingAlias
|
||||
{ moduleName : ModuleName
|
||||
, expectedName : String
|
||||
, at : Range
|
||||
, uses : List ModuleUse
|
||||
}
|
||||
|
||||
|
||||
new : ModuleName -> String -> Range -> MissingAlias
|
||||
new newModuleName newExpectedName newRange =
|
||||
MissingAlias
|
||||
{ moduleName = newModuleName
|
||||
, expectedName = newExpectedName
|
||||
, at = newRange
|
||||
, uses = []
|
||||
}
|
||||
|
||||
|
||||
withModuleUse : ModuleUse -> MissingAlias -> MissingAlias
|
||||
withModuleUse moduleUse (MissingAlias alias) =
|
||||
MissingAlias { alias | uses = moduleUse :: alias.uses }
|
||||
|
||||
|
||||
hasUses : MissingAlias -> Bool
|
||||
hasUses (MissingAlias { uses }) =
|
||||
uses /= []
|
||||
|
||||
|
||||
mapModuleName : (ModuleName -> a) -> MissingAlias -> a
|
||||
mapModuleName mapper (MissingAlias { moduleName }) =
|
||||
mapper moduleName
|
||||
|
||||
|
||||
mapExpectedName : (String -> a) -> MissingAlias -> a
|
||||
mapExpectedName mapper (MissingAlias { expectedName }) =
|
||||
mapper expectedName
|
||||
|
||||
|
||||
mapUses : (ModuleUse -> a) -> MissingAlias -> List a
|
||||
mapUses mapper (MissingAlias { uses }) =
|
||||
List.map mapper uses
|
||||
|
||||
|
||||
range : MissingAlias -> Range
|
||||
range (MissingAlias { at }) =
|
||||
at
|
39
tests/NoInconsistentAliases/MissingAliasSet.elm
Normal file
39
tests/NoInconsistentAliases/MissingAliasSet.elm
Normal file
@ -0,0 +1,39 @@
|
||||
module NoInconsistentAliases.MissingAliasSet exposing (MissingAliasSet, empty, fold, insert, use)
|
||||
|
||||
import Dict exposing (Dict)
|
||||
import Elm.Syntax.ModuleName exposing (ModuleName)
|
||||
import NoInconsistentAliases.MissingAlias as MissingAlias exposing (MissingAlias)
|
||||
import NoInconsistentAliases.ModuleUse exposing (ModuleUse)
|
||||
|
||||
|
||||
type MissingAliasSet
|
||||
= MissingAliasSet (Dict ModuleName MissingAlias)
|
||||
|
||||
|
||||
empty : MissingAliasSet
|
||||
empty =
|
||||
MissingAliasSet Dict.empty
|
||||
|
||||
|
||||
insert : MissingAlias -> MissingAliasSet -> MissingAliasSet
|
||||
insert missingAlias (MissingAliasSet aliases) =
|
||||
MissingAliasSet (MissingAlias.mapModuleName (\name -> Dict.insert name missingAlias aliases) missingAlias)
|
||||
|
||||
|
||||
use : ModuleName -> ModuleUse -> MissingAliasSet -> MissingAliasSet
|
||||
use moduleName moduleUse (MissingAliasSet aliases) =
|
||||
case Dict.get moduleName aliases of
|
||||
Nothing ->
|
||||
MissingAliasSet aliases
|
||||
|
||||
Just missingAlias ->
|
||||
let
|
||||
missingAliasWithUse =
|
||||
MissingAlias.withModuleUse moduleUse missingAlias
|
||||
in
|
||||
MissingAliasSet (Dict.insert moduleName missingAliasWithUse aliases)
|
||||
|
||||
|
||||
fold : (MissingAlias -> a -> a) -> a -> MissingAliasSet -> a
|
||||
fold folder start (MissingAliasSet aliases) =
|
||||
aliases |> Dict.values |> List.foldl folder start
|
22
tests/NoInconsistentAliases/ModuleUse.elm
Normal file
22
tests/NoInconsistentAliases/ModuleUse.elm
Normal file
@ -0,0 +1,22 @@
|
||||
module NoInconsistentAliases.ModuleUse exposing (ModuleUse, mapFunction, new, range)
|
||||
|
||||
import Elm.Syntax.Range exposing (Range)
|
||||
|
||||
|
||||
type ModuleUse
|
||||
= ModuleUse String Range
|
||||
|
||||
|
||||
new : String -> Range -> ModuleUse
|
||||
new newFunction newRange =
|
||||
ModuleUse newFunction newRange
|
||||
|
||||
|
||||
mapFunction : (String -> a) -> ModuleUse -> a
|
||||
mapFunction mapper (ModuleUse name _) =
|
||||
mapper name
|
||||
|
||||
|
||||
range : ModuleUse -> Range
|
||||
range (ModuleUse _ useRange) =
|
||||
useRange
|
233
tests/NoInconsistentAliases/Visitor.elm
Normal file
233
tests/NoInconsistentAliases/Visitor.elm
Normal file
@ -0,0 +1,233 @@
|
||||
module NoInconsistentAliases.Visitor exposing (rule)
|
||||
|
||||
import Elm.Syntax.Import exposing (Import)
|
||||
import Elm.Syntax.ModuleName exposing (ModuleName)
|
||||
import Elm.Syntax.Node as Node exposing (Node(..))
|
||||
import NoInconsistentAliases.BadAlias as BadAlias exposing (BadAlias)
|
||||
import NoInconsistentAliases.Config exposing (Config)
|
||||
import NoInconsistentAliases.Context as Context
|
||||
import NoInconsistentAliases.MissingAlias as MissingAlias exposing (MissingAlias)
|
||||
import NoInconsistentAliases.ModuleUse as ModuleUse exposing (ModuleUse)
|
||||
import NoInconsistentAliases.Visitor.Options as Options exposing (Options)
|
||||
import Review.Fix as Fix exposing (Fix)
|
||||
import Review.Rule as Rule exposing (Error, Rule)
|
||||
import Vendor.NameVisitor as NameVisitor
|
||||
|
||||
|
||||
rule : Config -> Rule
|
||||
rule config =
|
||||
let
|
||||
options : Options
|
||||
options =
|
||||
Options.fromConfig config
|
||||
in
|
||||
Rule.newModuleRuleSchema "NoInconsistentAliases" Context.initial
|
||||
|> Rule.withImportVisitor (importVisitor options)
|
||||
|> NameVisitor.withNameVisitor moduleCallVisitor
|
||||
|> Rule.withFinalModuleEvaluation (finalEvaluation options.lookupAlias)
|
||||
|> Rule.fromModuleRuleSchema
|
||||
|
||||
|
||||
importVisitor : Options -> Node Import -> Context.Module -> ( List (Error {}), Context.Module )
|
||||
importVisitor options (Node _ { moduleName, moduleAlias }) context =
|
||||
( []
|
||||
, context
|
||||
|> rememberModuleAlias moduleName moduleAlias
|
||||
|> rememberBadAlias options moduleName moduleAlias
|
||||
)
|
||||
|
||||
|
||||
rememberModuleAlias : Node ModuleName -> Maybe (Node ModuleName) -> Context.Module -> Context.Module
|
||||
rememberModuleAlias moduleName maybeModuleAlias context =
|
||||
let
|
||||
moduleAlias =
|
||||
maybeModuleAlias |> Maybe.withDefault moduleName |> Node.map formatModuleName
|
||||
in
|
||||
context |> Context.addModuleAlias (Node.value moduleName) (Node.value moduleAlias)
|
||||
|
||||
|
||||
rememberBadAlias : Options -> Node ModuleName -> Maybe (Node ModuleName) -> Context.Module -> Context.Module
|
||||
rememberBadAlias { lookupAlias, canMissAliases } (Node moduleNameRange moduleName) maybeModuleAlias context =
|
||||
case ( lookupAlias moduleName, maybeModuleAlias ) of
|
||||
( Just expectedAlias, Just (Node moduleAliasRange moduleAlias) ) ->
|
||||
if [ expectedAlias ] /= moduleAlias then
|
||||
let
|
||||
badAlias =
|
||||
BadAlias.new
|
||||
{ name = moduleAlias |> formatModuleName
|
||||
, moduleName = moduleName
|
||||
, expectedName = expectedAlias
|
||||
, range = moduleAliasRange
|
||||
}
|
||||
in
|
||||
context |> Context.addBadAlias badAlias
|
||||
|
||||
else
|
||||
context
|
||||
|
||||
( Just expectedAlias, Nothing ) ->
|
||||
if canMissAliases then
|
||||
context
|
||||
|
||||
else
|
||||
let
|
||||
missingAlias =
|
||||
MissingAlias.new moduleName expectedAlias moduleNameRange
|
||||
in
|
||||
context |> Context.addMissingAlias missingAlias
|
||||
|
||||
( Nothing, _ ) ->
|
||||
context
|
||||
|
||||
|
||||
moduleCallVisitor : Node ( ModuleName, String ) -> Context.Module -> ( List (Error {}), Context.Module )
|
||||
moduleCallVisitor node context =
|
||||
case Node.value node of
|
||||
( moduleName, function ) ->
|
||||
( [], Context.addModuleCall moduleName function (Node.range node) context )
|
||||
|
||||
|
||||
finalEvaluation : Options.AliasLookup -> Context.Module -> List (Error {})
|
||||
finalEvaluation lookupAlias context =
|
||||
let
|
||||
lookupModuleName =
|
||||
Context.lookupModuleName context
|
||||
in
|
||||
Context.foldBadAliases (foldBadAliasError lookupAlias lookupModuleName) [] context
|
||||
++ Context.foldMissingAliases foldMissingAliasError [] context
|
||||
|
||||
|
||||
foldBadAliasError : Options.AliasLookup -> ModuleNameLookup -> BadAlias -> List (Error {}) -> List (Error {})
|
||||
foldBadAliasError lookupAlias lookupModuleName badAlias errors =
|
||||
let
|
||||
moduleName =
|
||||
badAlias |> BadAlias.mapModuleName identity
|
||||
|
||||
expectedAlias =
|
||||
badAlias |> BadAlias.mapExpectedName identity
|
||||
|
||||
moduleClash =
|
||||
detectCollision (lookupModuleName expectedAlias) moduleName
|
||||
|
||||
aliasClash =
|
||||
moduleClash |> Maybe.andThen lookupAlias
|
||||
in
|
||||
case ( aliasClash, moduleClash ) of
|
||||
( Just _, _ ) ->
|
||||
errors
|
||||
|
||||
( Nothing, Just collisionName ) ->
|
||||
Rule.error (collisionAliasMessage collisionName expectedAlias badAlias) (BadAlias.range badAlias)
|
||||
:: errors
|
||||
|
||||
( Nothing, Nothing ) ->
|
||||
let
|
||||
badRange =
|
||||
BadAlias.range badAlias
|
||||
|
||||
fixes =
|
||||
Fix.replaceRangeBy badRange expectedAlias
|
||||
:: BadAlias.mapUses (fixModuleUse expectedAlias) badAlias
|
||||
in
|
||||
Rule.errorWithFix (incorrectAliasMessage expectedAlias badAlias) badRange fixes
|
||||
:: errors
|
||||
|
||||
|
||||
foldMissingAliasError : MissingAlias -> List (Error {}) -> List (Error {})
|
||||
foldMissingAliasError missingAlias errors =
|
||||
if MissingAlias.hasUses missingAlias then
|
||||
let
|
||||
expectedAlias =
|
||||
missingAlias |> MissingAlias.mapExpectedName identity
|
||||
|
||||
badRange =
|
||||
MissingAlias.range missingAlias
|
||||
|
||||
fixes =
|
||||
Fix.insertAt badRange.end (" as " ++ expectedAlias)
|
||||
:: MissingAlias.mapUses (fixModuleUse expectedAlias) missingAlias
|
||||
in
|
||||
Rule.errorWithFix (missingAliasMessage expectedAlias missingAlias) badRange fixes
|
||||
:: errors
|
||||
|
||||
else
|
||||
errors
|
||||
|
||||
|
||||
detectCollision : Maybe ModuleName -> ModuleName -> Maybe ModuleName
|
||||
detectCollision maybeCollisionName moduleName =
|
||||
maybeCollisionName
|
||||
|> Maybe.andThen
|
||||
(\collisionName ->
|
||||
if collisionName == moduleName then
|
||||
Nothing
|
||||
|
||||
else
|
||||
Just collisionName
|
||||
)
|
||||
|
||||
|
||||
incorrectAliasMessage : String -> BadAlias -> { message : String, details : List String }
|
||||
incorrectAliasMessage expectedAlias badAlias =
|
||||
let
|
||||
badAliasName =
|
||||
BadAlias.mapName identity badAlias
|
||||
|
||||
moduleName =
|
||||
BadAlias.mapModuleName formatModuleName badAlias
|
||||
in
|
||||
{ message =
|
||||
"Incorrect alias `" ++ badAliasName ++ "` for module `" ++ moduleName ++ "`."
|
||||
, details =
|
||||
[ "This import does not use your preferred alias `" ++ expectedAlias ++ "` for `" ++ moduleName ++ "`."
|
||||
, "You should update the alias to be consistent with the rest of the project. Remember to change all references to the alias in this module too."
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
collisionAliasMessage : ModuleName -> String -> BadAlias -> { message : String, details : List String }
|
||||
collisionAliasMessage collisionName expectedAlias badAlias =
|
||||
let
|
||||
badAliasName =
|
||||
BadAlias.mapName identity badAlias
|
||||
|
||||
moduleName =
|
||||
BadAlias.mapModuleName formatModuleName badAlias
|
||||
in
|
||||
{ message =
|
||||
"Incorrect alias `" ++ badAliasName ++ "` for module `" ++ moduleName ++ "`."
|
||||
, details =
|
||||
[ "This import does not use your preferred alias `" ++ expectedAlias ++ "` for `" ++ moduleName ++ "`."
|
||||
, "Your preferred alias has already been taken by `" ++ formatModuleName collisionName ++ "`."
|
||||
, "You should change the alias for both modules to be consistent with the rest of the project. Remember to change all references to the alias in this module too."
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
missingAliasMessage : String -> MissingAlias -> { message : String, details : List String }
|
||||
missingAliasMessage expectedAlias missingAlias =
|
||||
let
|
||||
moduleName =
|
||||
MissingAlias.mapModuleName formatModuleName missingAlias
|
||||
in
|
||||
{ message =
|
||||
"Expected alias `" ++ expectedAlias ++ "` missing for module `" ++ moduleName ++ "`."
|
||||
, details =
|
||||
[ "This import does not use your preferred alias `" ++ expectedAlias ++ "` for `" ++ moduleName ++ "`."
|
||||
, "You should update the alias to be consistent with the rest of the project. Remember to change all references to the alias in this module too."
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
fixModuleUse : String -> ModuleUse -> Fix
|
||||
fixModuleUse expectedAlias use =
|
||||
Fix.replaceRangeBy (ModuleUse.range use) (ModuleUse.mapFunction (\name -> expectedAlias ++ "." ++ name) use)
|
||||
|
||||
|
||||
formatModuleName : ModuleName -> String
|
||||
formatModuleName moduleName =
|
||||
String.join "." moduleName
|
||||
|
||||
|
||||
type alias ModuleNameLookup =
|
||||
String -> Maybe ModuleName
|
21
tests/NoInconsistentAliases/Visitor/Options.elm
Normal file
21
tests/NoInconsistentAliases/Visitor/Options.elm
Normal file
@ -0,0 +1,21 @@
|
||||
module NoInconsistentAliases.Visitor.Options exposing (AliasLookup, Options, fromConfig)
|
||||
|
||||
import Elm.Syntax.ModuleName exposing (ModuleName)
|
||||
import NoInconsistentAliases.Config as Config exposing (Config)
|
||||
|
||||
|
||||
type alias Options =
|
||||
{ lookupAlias : AliasLookup
|
||||
, canMissAliases : Bool
|
||||
}
|
||||
|
||||
|
||||
type alias AliasLookup =
|
||||
ModuleName -> Maybe String
|
||||
|
||||
|
||||
fromConfig : Config -> Options
|
||||
fromConfig config =
|
||||
{ lookupAlias = Config.lookupAlias config
|
||||
, canMissAliases = Config.canMissAliases config
|
||||
}
|
@ -1,174 +0,0 @@
|
||||
module NoLeftPizza exposing (rule, Strictness(..))
|
||||
|
||||
{-|
|
||||
|
||||
@docs rule, Strictness
|
||||
|
||||
-}
|
||||
|
||||
import Elm.Syntax.Expression as Expression exposing (Expression)
|
||||
import Elm.Syntax.Node as Node exposing (Node)
|
||||
import NoLeftPizzaUtil
|
||||
import Review.Fix as Fix
|
||||
import Review.Rule as Rule exposing (Error, Rule)
|
||||
|
||||
|
||||
{-| Specify how strict the rule should be.
|
||||
|
||||
Specifying `Any` means that _any_ use of `<|` will be flagged, whereas
|
||||
`Redundant` limits it to cases where `<|` can be removed - without adding any
|
||||
parenthesis - without changing the semantics.
|
||||
|
||||
-}
|
||||
type Strictness
|
||||
= Any
|
||||
| Redundant
|
||||
|
||||
|
||||
{-| Forbids using the left pizza operator (<|) in infix position.
|
||||
|
||||
Expressions like `foo <| "hello" ++ world` will be flagged, and a fix will be
|
||||
proposed to write the expression to `foo ("hello" ++ world)`.
|
||||
|
||||
To use this rule, add it to your `elm-review` config like so:
|
||||
|
||||
import NoLeftPizza
|
||||
import Review.Rule exposing (Rule)
|
||||
|
||||
config : List Rule
|
||||
config =
|
||||
[ NoLeftPizza.rule NoLeftPizza.Any
|
||||
]
|
||||
|
||||
The above configuration results in absolutely any use of `<|` being flagged. If
|
||||
you'd prefer only flagging redundant usage (such as `foo <| bar`), pass
|
||||
`NoLeftPizza.Redundant` as the configuration option.
|
||||
|
||||
If you would prefer to keep writing tests in the more "traditional" style which
|
||||
uses `<|`, you can disable the rule for `tests/` like so:
|
||||
|
||||
import NoLeftPizza
|
||||
import Review.Rule exposing (Rule)
|
||||
|
||||
config : List Rule
|
||||
config =
|
||||
[ NoLeftPizza.rule NoLeftPizza.Any
|
||||
|> Rule.ignoreErrorsForDirectories
|
||||
[ -- Test functions are traditionally built up using a left pizza.
|
||||
-- While we don't want them in our regular code, let's allow them
|
||||
-- just for tests.
|
||||
"tests/"
|
||||
]
|
||||
]
|
||||
|
||||
Or pass `NoLeftPizza.Redundant` which will only apply to redundant usage:
|
||||
|
||||
import NoLeftPizza
|
||||
import Review.Rule exposing (Rule)
|
||||
|
||||
config : List Rule
|
||||
config =
|
||||
[ NoLeftPizza.rule NoLeftPizza.Redundant
|
||||
]
|
||||
|
||||
-}
|
||||
rule : Strictness -> Rule
|
||||
rule strictness =
|
||||
Rule.newModuleRuleSchema "NoLeftPizza" strictness
|
||||
|> Rule.withSimpleExpressionVisitor (expressionVisitor strictness)
|
||||
|> Rule.fromModuleRuleSchema
|
||||
|
||||
|
||||
expressionVisitor : Strictness -> Node Expression -> List (Error {})
|
||||
expressionVisitor strictness node =
|
||||
case Node.value node of
|
||||
Expression.OperatorApplication "<|" _ left right ->
|
||||
case makeError strictness node left right of
|
||||
Just error ->
|
||||
[ error ]
|
||||
|
||||
Nothing ->
|
||||
[]
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
|
||||
makeError : Strictness -> Node Expression -> Node Expression -> Node Expression -> Maybe (Error {})
|
||||
makeError strictness node left right =
|
||||
case ( strictness, isSimpleExpression right ) of
|
||||
( Any, False ) ->
|
||||
Just (produceError strictness node left (parenthesized right))
|
||||
|
||||
( Redundant, False ) ->
|
||||
Nothing
|
||||
|
||||
_ ->
|
||||
Just (produceError strictness node left right)
|
||||
|
||||
|
||||
produceError : Strictness -> Node Expression -> Node Expression -> Node Expression -> Error {}
|
||||
produceError strictness node left right =
|
||||
Rule.errorWithFix (infoFor strictness)
|
||||
(Node.range node)
|
||||
[ Fix.replaceRangeBy (Node.range node)
|
||||
(NoLeftPizzaUtil.expressionToString (Node.range node)
|
||||
(Expression.Application [ left, right ])
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
infoFor : Strictness -> { message : String, details : List String }
|
||||
infoFor strictness =
|
||||
case strictness of
|
||||
Any ->
|
||||
{ message = "That's a left pizza (<|) operator application there!"
|
||||
, details =
|
||||
[ "We prefer using either parenthesized function application like `Html.text (context.translate Foo.Bar)` or right pizza's like `foo |> bar`."
|
||||
, "The proposed fix rewrites the expression to a simple parenthesized expression, however, this may not always be what you want. Use your best judgement!"
|
||||
]
|
||||
}
|
||||
|
||||
Redundant ->
|
||||
{ message = "Redundant left pizza (<|) operator application"
|
||||
, details =
|
||||
[ "This left pizza operator can be removed without any further changes, without changing the semantics of your code."
|
||||
, "Using `<|` like this adds visual noise to code that can make it harder to read."
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
parenthesized : Node Expression -> Node Expression
|
||||
parenthesized ((Node.Node range _) as node) =
|
||||
Node.Node range (Expression.ParenthesizedExpression node)
|
||||
|
||||
|
||||
isSimpleExpression : Node Expression -> Bool
|
||||
isSimpleExpression (Node.Node _ expr) =
|
||||
case expr of
|
||||
Expression.Application _ ->
|
||||
False
|
||||
|
||||
Expression.OperatorApplication _ _ _ _ ->
|
||||
False
|
||||
|
||||
Expression.IfBlock _ _ _ ->
|
||||
False
|
||||
|
||||
Expression.Operator _ ->
|
||||
False
|
||||
|
||||
Expression.LetExpression _ ->
|
||||
False
|
||||
|
||||
Expression.CaseExpression _ ->
|
||||
False
|
||||
|
||||
Expression.LambdaExpression _ ->
|
||||
False
|
||||
|
||||
Expression.GLSLExpression _ ->
|
||||
False
|
||||
|
||||
_ ->
|
||||
True
|
@ -1,636 +0,0 @@
|
||||
module NoLeftPizzaTest exposing (tests)
|
||||
|
||||
import NoLeftPizza
|
||||
import Review.Test
|
||||
import Test exposing (Test, describe, test)
|
||||
|
||||
|
||||
tests : Test
|
||||
tests =
|
||||
describe "NoLeftPizza"
|
||||
[ anyTests
|
||||
, redundantTests
|
||||
]
|
||||
|
||||
|
||||
anyTests : Test
|
||||
anyTests =
|
||||
describe "NoLeftPizza.Any"
|
||||
[ test "Simple pizza" <|
|
||||
\_ ->
|
||||
"""module A exposing (..)
|
||||
|
||||
a = foo <| bar"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Any)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeAnyError "foo <| bar"
|
||||
|> Review.Test.whenFixed
|
||||
"""module A exposing (..)
|
||||
|
||||
a = foo bar"""
|
||||
]
|
||||
, test "Nested pizza" <|
|
||||
\_ ->
|
||||
"""module A exposing (..)
|
||||
|
||||
a = foo <| bar <| baz"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Any)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeAnyError
|
||||
"foo <| bar <| baz"
|
||||
|> Review.Test.whenFixed
|
||||
"""module A exposing (..)
|
||||
|
||||
a = foo (bar <| baz)"""
|
||||
, makeAnyError "bar <| baz"
|
||||
|> Review.Test.whenFixed
|
||||
"""module A exposing (..)
|
||||
|
||||
a = foo <| bar baz"""
|
||||
]
|
||||
, test "Fixes operator precedence" <|
|
||||
\_ ->
|
||||
"""module A exposing (..)
|
||||
|
||||
a = foo <| 1 + 1"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Any)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeAnyError
|
||||
"foo <| 1 + 1"
|
||||
|> Review.Test.whenFixed
|
||||
"""module A exposing (..)
|
||||
|
||||
a = foo (1 + 1)"""
|
||||
]
|
||||
, test "Fixes more operator precedence" <|
|
||||
\_ ->
|
||||
"""module A exposing (..)
|
||||
|
||||
a = foo <| 1 + 1 / 2"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Any)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeAnyError
|
||||
"foo <| 1 + 1 / 2"
|
||||
|> Review.Test.whenFixed
|
||||
"""module A exposing (..)
|
||||
|
||||
a = foo (1 + 1 / 2)"""
|
||||
]
|
||||
, test "Why isn't this fixed?" <|
|
||||
\_ ->
|
||||
"""module A exposing (..)
|
||||
|
||||
f =
|
||||
List.map .x <| y
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Any)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeAnyError
|
||||
"List.map .x <| y"
|
||||
|> Review.Test.whenFixed
|
||||
"""module A exposing (..)
|
||||
|
||||
f =
|
||||
List.map .x y
|
||||
"""
|
||||
]
|
||||
, test "Why isn't _this_ fixed, pt2?" <|
|
||||
\_ ->
|
||||
"""module A exposing (..)
|
||||
|
||||
f =
|
||||
String.join " " <|
|
||||
List.map x y
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Any)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeAnyError
|
||||
"""String.join " " <|
|
||||
List.map x y"""
|
||||
|> Review.Test.whenFixed
|
||||
"""module A exposing (..)
|
||||
|
||||
f =
|
||||
String.join " " (List.map x y)
|
||||
"""
|
||||
]
|
||||
, test "Why isn't _this_ fixed, pt3?" <|
|
||||
\_ ->
|
||||
"""module A exposing (..)
|
||||
|
||||
f =
|
||||
String.join " " <|
|
||||
List.map
|
||||
(\\x ->
|
||||
x
|
||||
)
|
||||
y
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Any)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeAnyError
|
||||
"""String.join " " <|
|
||||
List.map
|
||||
(\\x ->
|
||||
x
|
||||
)
|
||||
y"""
|
||||
|> Review.Test.whenFixed
|
||||
"""module A exposing (..)
|
||||
|
||||
f =
|
||||
String.join " " (List.map (\\x -> x)
|
||||
y)
|
||||
"""
|
||||
]
|
||||
, test "handle parser operators with pizza" <|
|
||||
\() ->
|
||||
"""
|
||||
module MyParser exposing (..)
|
||||
numberToken =
|
||||
Parser.getChompedString <|
|
||||
Parser.succeed ()
|
||||
|. Parser.chompIf Char.isDigit
|
||||
|. Parser.chompWhile Char.isDigit
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Any)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeAnyError """Parser.getChompedString <|
|
||||
Parser.succeed ()
|
||||
|. Parser.chompIf Char.isDigit
|
||||
|. Parser.chompWhile Char.isDigit"""
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module MyParser exposing (..)
|
||||
numberToken =
|
||||
Parser.getChompedString (Parser.succeed () |. Parser.chompIf Char.isDigit |. Parser.chompWhile Char.isDigit)
|
||||
"""
|
||||
]
|
||||
, test "handle logic operators with pizza" <|
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
f =
|
||||
if isTrue <| True || False then
|
||||
True
|
||||
else
|
||||
False
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Any)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeAnyError "isTrue <| True || False"
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module A exposing (..)
|
||||
f =
|
||||
if isTrue (True || False) then
|
||||
True
|
||||
else
|
||||
False
|
||||
"""
|
||||
]
|
||||
, describe "mixed pizzas" mixedPizzaTests
|
||||
]
|
||||
|
||||
|
||||
mixedPizzaTests : List Test
|
||||
mixedPizzaTests =
|
||||
[ test "a <| (b |> c)" <|
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
f =
|
||||
a <| (b |> c)
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Any)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeAnyError "a <| (b |> c)"
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module A exposing (..)
|
||||
f =
|
||||
a (b |> c)
|
||||
"""
|
||||
]
|
||||
, test "(a <| b) |> c)" <|
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
f =
|
||||
(a <| b) |> c
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Any)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeAnyError "a <| b"
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module A exposing (..)
|
||||
f =
|
||||
(a b) |> c
|
||||
"""
|
||||
]
|
||||
, test "a |> (b <| c)" <|
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
f =
|
||||
a |> (b <| c)
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Any)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeAnyError "b <| c"
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module A exposing (..)
|
||||
f =
|
||||
a |> (b c)
|
||||
"""
|
||||
]
|
||||
, test "(a |> b) <| c" <|
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
f =
|
||||
(a |> b) <| c
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Any)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeAnyError "(a |> b) <| c"
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module A exposing (..)
|
||||
f =
|
||||
(a |> b) c
|
||||
"""
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
redundantTests : Test
|
||||
redundantTests =
|
||||
describe "NoLeftPizza.Redundant"
|
||||
[ test "Simple pizza" <|
|
||||
\_ ->
|
||||
"""module A exposing (..)
|
||||
|
||||
a = foo <| bar"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Redundant)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeRedundantError "foo <| bar"
|
||||
|> Review.Test.whenFixed
|
||||
"""module A exposing (..)
|
||||
|
||||
a = foo bar"""
|
||||
]
|
||||
, test "Nested pizza" <|
|
||||
\_ ->
|
||||
"""module A exposing (..)
|
||||
|
||||
a = foo <| bar <| baz"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Redundant)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeRedundantError
|
||||
"bar <| baz"
|
||||
|> Review.Test.whenFixed
|
||||
"""module A exposing (..)
|
||||
|
||||
a = foo <| bar baz"""
|
||||
]
|
||||
, test "Fixes operator precedence" <|
|
||||
\_ ->
|
||||
"""module A exposing (..)
|
||||
|
||||
a = foo <| 1 + 1"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Redundant)
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "Why isn't this fixed?" <|
|
||||
\_ ->
|
||||
"""module A exposing (..)
|
||||
|
||||
f =
|
||||
List.map .x <| y
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Redundant)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeRedundantError
|
||||
"List.map .x <| y"
|
||||
|> Review.Test.whenFixed
|
||||
"""module A exposing (..)
|
||||
|
||||
f =
|
||||
List.map .x y
|
||||
"""
|
||||
]
|
||||
, test "Why isn't _this_ fixed, pt2?" <|
|
||||
\_ ->
|
||||
"""module A exposing (..)
|
||||
|
||||
f =
|
||||
String.join " " <|
|
||||
List.map x y
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Redundant)
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "Why isn't _this_ fixed, pt3?" <|
|
||||
\_ ->
|
||||
"""module A exposing (..)
|
||||
|
||||
f =
|
||||
String.join " " <|
|
||||
List.map
|
||||
(\\x ->
|
||||
x
|
||||
)
|
||||
y
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Redundant)
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "handle parser operators with pizza" <|
|
||||
\() ->
|
||||
"""
|
||||
module MyParser exposing (..)
|
||||
numberToken =
|
||||
Parser.getChompedString <|
|
||||
Parser.succeed ()
|
||||
|. Parser.chompIf Char.isDigit
|
||||
|. Parser.chompWhile Char.isDigit
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Redundant)
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "handle logic operators with pizza" <|
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
f =
|
||||
if isTrue <| True || False then
|
||||
True
|
||||
else
|
||||
False
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Redundant)
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "Parenthesized expression" <|
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar <| (abc xyz)
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Redundant)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeRedundantError "bar <| (abc xyz)"
|
||||
|> Review.Test.whenFixed """
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar (abc xyz)
|
||||
"""
|
||||
]
|
||||
, test "Unit expression" <|
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar <| ()
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Redundant)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeRedundantError "bar <| ()"
|
||||
|> Review.Test.whenFixed """
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar ()
|
||||
"""
|
||||
]
|
||||
, test "Qualified function" <|
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar <| Foo.Bar.xyz
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Redundant)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeRedundantError "bar <| Foo.Bar.xyz"
|
||||
|> Review.Test.whenFixed """
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar Foo.Bar.xyz
|
||||
"""
|
||||
]
|
||||
, test "Prefix operator" <|
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar <| (++)
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Redundant)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeRedundantError "bar <| (++)"
|
||||
|> Review.Test.whenFixed """
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar (++)
|
||||
"""
|
||||
]
|
||||
, test "Integer value" <|
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar <| 123
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Redundant)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeRedundantError "bar <| 123"
|
||||
|> Review.Test.whenFixed """
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar 123
|
||||
"""
|
||||
]
|
||||
, test "Floatable value" <|
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar <| 123.123
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Redundant)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeRedundantError "bar <| 123.123"
|
||||
|> Review.Test.whenFixed """
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar 123.123
|
||||
"""
|
||||
]
|
||||
, test "Negated value" <|
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar <| -123.123
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Redundant)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeRedundantError "bar <| -123.123"
|
||||
|> Review.Test.whenFixed """
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar -123.123
|
||||
"""
|
||||
]
|
||||
, test "String literal" <|
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar <| "hello there"
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Redundant)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeRedundantError "bar <| \"hello there\""
|
||||
|> Review.Test.whenFixed """
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar "hello there"
|
||||
"""
|
||||
]
|
||||
, test "Character literal" <|
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar <| 'a'
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Redundant)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeRedundantError "bar <| 'a'"
|
||||
|> Review.Test.whenFixed """
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar 'a'
|
||||
"""
|
||||
]
|
||||
, test "Tupled expression" <|
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar <| ( a, b, abc xyz )
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Redundant)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeRedundantError "bar <| ( a, b, abc xyz )"
|
||||
|> Review.Test.whenFixed """
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar (a, b, abc xyz)
|
||||
"""
|
||||
]
|
||||
, test "Record expression" <|
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar <| { foo = 123 }
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Redundant)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeRedundantError "bar <| { foo = 123 }"
|
||||
|> Review.Test.whenFixed """
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar {foo = 123}
|
||||
"""
|
||||
]
|
||||
, test "Record update expression" <|
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar <| { r | foo = 123 }
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Redundant)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeRedundantError "bar <| { r | foo = 123 }"
|
||||
|> Review.Test.whenFixed """
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar { r | foo = 123 }
|
||||
"""
|
||||
]
|
||||
, test "Record access expression" <|
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar <| (foo bar).foo.bar
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Redundant)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeRedundantError "bar <| (foo bar).foo.bar"
|
||||
|> Review.Test.whenFixed """
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar (foo bar).foo.bar
|
||||
"""
|
||||
]
|
||||
, test "Record access function" <|
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar <| .bar
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Redundant)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeRedundantError "bar <| .bar"
|
||||
|> Review.Test.whenFixed """
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar .bar
|
||||
"""
|
||||
]
|
||||
, test "List literal" <|
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar <| [ 123, 456 ]
|
||||
"""
|
||||
|> Review.Test.run (NoLeftPizza.rule NoLeftPizza.Redundant)
|
||||
|> Review.Test.expectErrors
|
||||
[ makeRedundantError "bar <| [ 123, 456 ]"
|
||||
|> Review.Test.whenFixed """
|
||||
module A exposing (..)
|
||||
|
||||
foo = bar [123, 456]
|
||||
"""
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
makeAnyError : String -> Review.Test.ExpectedError
|
||||
makeAnyError under =
|
||||
Review.Test.error
|
||||
{ message = "That's a left pizza (<|) operator application there!"
|
||||
, details =
|
||||
[ "We prefer using either parenthesized function application like `Html.text (context.translate Foo.Bar)` or right pizza's like `foo |> bar`."
|
||||
, "The proposed fix rewrites the expression to a simple parenthesized expression, however, this may not always be what you want. Use your best judgement!"
|
||||
]
|
||||
, under = under
|
||||
}
|
||||
|
||||
|
||||
makeRedundantError : String -> Review.Test.ExpectedError
|
||||
makeRedundantError under =
|
||||
Review.Test.error
|
||||
{ message = "Redundant left pizza (<|) operator application"
|
||||
, details =
|
||||
[ "This left pizza operator can be removed without any further changes, without changing the semantics of your code."
|
||||
, "Using `<|` like this adds visual noise to code that can make it harder to read."
|
||||
]
|
||||
, under = under
|
||||
}
|
@ -60,6 +60,15 @@ import Review.Rule as Rule exposing (Error, Rule)
|
||||
, [ 4, mysteryNumber, 6 ]
|
||||
]
|
||||
|
||||
|
||||
## Try it out
|
||||
|
||||
You can try this rule out by running the following command:
|
||||
|
||||
```bash
|
||||
elm-review --template jfmengels/elm-review-simplification/example --rules NoListLiteralsConcat
|
||||
```
|
||||
|
||||
-}
|
||||
rule : Rule
|
||||
rule =
|
||||
|
@ -52,6 +52,15 @@ This won't fail if `SomeModule` does not define a `subscriptions` function.
|
||||
subscriptions model =
|
||||
SomeModule.subscriptions
|
||||
|
||||
|
||||
## Try it out
|
||||
|
||||
You can try this rule out by running the following command:
|
||||
|
||||
```bash
|
||||
elm-review --template jfmengels/review-tea/example --rules NoMissingSubscriptionsCall
|
||||
```
|
||||
|
||||
-}
|
||||
rule : Rule
|
||||
rule =
|
||||
|
@ -43,6 +43,15 @@ For that, enable [`NoMissingTypeAnnotationInLetIn`](./NoMissingTypeAnnotationInL
|
||||
in
|
||||
c
|
||||
|
||||
|
||||
## Try it out
|
||||
|
||||
You can try this rule out by running the following command:
|
||||
|
||||
```bash
|
||||
elm-review --template jfmengels/review-common/example --rules NoMissingTypeAnnotation
|
||||
```
|
||||
|
||||
-}
|
||||
rule : Rule
|
||||
rule =
|
||||
|
@ -47,6 +47,15 @@ For that, enable [`NoMissingTypeAnnotation`](./NoMissingTypeAnnotation).
|
||||
in
|
||||
b
|
||||
|
||||
|
||||
## Try it out
|
||||
|
||||
You can try this rule out by running the following command:
|
||||
|
||||
```bash
|
||||
elm-review --template jfmengels/review-common/example --rules NoMissingTypeAnnotationInLetIn
|
||||
```
|
||||
|
||||
-}
|
||||
rule : Rule
|
||||
rule =
|
||||
|
760
tests/NoMissingTypeExpose.elm
Normal file
760
tests/NoMissingTypeExpose.elm
Normal file
@ -0,0 +1,760 @@
|
||||
module NoMissingTypeExpose exposing (rule)
|
||||
|
||||
{-|
|
||||
|
||||
@docs rule
|
||||
|
||||
-}
|
||||
|
||||
import Dict exposing (Dict)
|
||||
import Elm.Docs exposing (Module)
|
||||
import Elm.Module
|
||||
import Elm.Project exposing (Project)
|
||||
import Elm.Syntax.Declaration as Declaration exposing (Declaration)
|
||||
import Elm.Syntax.Exposing as Exposing exposing (Exposing)
|
||||
import Elm.Syntax.Import exposing (Import)
|
||||
import Elm.Syntax.Module as Module exposing (Module)
|
||||
import Elm.Syntax.ModuleName exposing (ModuleName)
|
||||
import Elm.Syntax.Node as Node exposing (Node(..))
|
||||
import Elm.Syntax.Range as Range
|
||||
import Elm.Syntax.Signature exposing (Signature)
|
||||
import Elm.Syntax.Type as Type
|
||||
import Elm.Syntax.TypeAnnotation as TypeAnnotation exposing (TypeAnnotation)
|
||||
import Review.Fix as Fix exposing (Fix)
|
||||
import Review.Project.Dependency as Dependency exposing (Dependency)
|
||||
import Review.Rule as Rule exposing (Rule)
|
||||
import Set exposing (Set)
|
||||
|
||||
|
||||
{-| Reports types that should be exposed but are not.
|
||||
|
||||
If a type is not exposed then it can be impossible to annotate functions or values that use them outside of the module. Affected types may be used in exposed function signatures, type aliases or other custom types.
|
||||
|
||||
import NoMissingTypeExpose
|
||||
|
||||
config : List Rule
|
||||
config =
|
||||
[ NoMissingTypeExpose.rule
|
||||
]
|
||||
|
||||
|
||||
## Fail
|
||||
|
||||
module Happiness exposing (happy, toString)
|
||||
|
||||
-- Type `Happiness` is private because it's not been exposed
|
||||
|
||||
type Happiness
|
||||
= Happy
|
||||
|
||||
-- Private type `Happiness` used by exposed function `toString`
|
||||
toString : Happiness -> String
|
||||
toString happiness =
|
||||
"Happy"
|
||||
|
||||
-- Private type `Happiness` used by exposed value `happy`
|
||||
happy : Happiness
|
||||
happy =
|
||||
Happy
|
||||
|
||||
## Success
|
||||
|
||||
module Happiness exposing (Happiness, happy, toString)
|
||||
|
||||
type Happiness
|
||||
= Happy
|
||||
|
||||
toString : Happiness -> String
|
||||
toString happiness =
|
||||
"Happy"
|
||||
|
||||
happy : Happiness
|
||||
happy =
|
||||
Happy
|
||||
|
||||
|
||||
## Try it out
|
||||
|
||||
You can try this rule out by running the following command:
|
||||
|
||||
```bash
|
||||
elm-review --template jfmengels/review-common/example --rules NoMissingTypeExpose
|
||||
```
|
||||
|
||||
-}
|
||||
rule : Rule
|
||||
rule =
|
||||
Rule.newProjectRuleSchema "NoMissingTypeExpose" initialProjectContext
|
||||
|> Rule.withElmJsonProjectVisitor elmJsonVisitor
|
||||
|> Rule.withDependenciesProjectVisitor dependencyDictVisitor
|
||||
|> Rule.withModuleVisitor moduleVisitor
|
||||
|> Rule.withContextFromImportedModules
|
||||
|> Rule.withModuleContext
|
||||
{ fromProjectToModule = fromProjectToModuleContext
|
||||
, fromModuleToProject = fromModuleToProjectContext
|
||||
, foldProjectContexts = foldProjectContexts
|
||||
}
|
||||
|> Rule.fromProjectRuleSchema
|
||||
|
||||
|
||||
elmJsonVisitor : Maybe { a | project : Project } -> ProjectContext -> ( List nothing, ProjectContext )
|
||||
elmJsonVisitor maybeProject context =
|
||||
case maybeProject of
|
||||
Just { project } ->
|
||||
( []
|
||||
, { context
|
||||
| exposedModules = exposedModulesForElmJson project
|
||||
}
|
||||
)
|
||||
|
||||
Nothing ->
|
||||
( [], context )
|
||||
|
||||
|
||||
exposedModulesForElmJson : Project -> ExposedModules
|
||||
exposedModulesForElmJson project =
|
||||
case project of
|
||||
Elm.Project.Package { exposed } ->
|
||||
Package (elmProjectExposedList exposed)
|
||||
|
||||
Elm.Project.Application _ ->
|
||||
Application
|
||||
|
||||
|
||||
elmProjectExposedList : Elm.Project.Exposed -> Set String
|
||||
elmProjectExposedList exposed =
|
||||
case exposed of
|
||||
Elm.Project.ExposedList list ->
|
||||
List.foldl (Elm.Module.toString >> Set.insert) Set.empty list
|
||||
|
||||
Elm.Project.ExposedDict dict ->
|
||||
List.foldl
|
||||
(\( _, list ) acc ->
|
||||
List.foldl (Elm.Module.toString >> Set.insert) acc list
|
||||
)
|
||||
Set.empty
|
||||
dict
|
||||
|
||||
|
||||
dependencyDictVisitor : Dict String Dependency -> ProjectContext -> ( List nothing, ProjectContext )
|
||||
dependencyDictVisitor dependencies context =
|
||||
( []
|
||||
, { context
|
||||
| exposedModules =
|
||||
Dict.values dependencies
|
||||
|> List.foldl exposedModulesForDependency context.exposedModules
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
exposedModulesForDependency : Dependency -> ExposedModules -> ExposedModules
|
||||
exposedModulesForDependency dependency exposedModules =
|
||||
Dependency.modules dependency
|
||||
|> List.foldl (.name >> addExposedModule) exposedModules
|
||||
|
||||
|
||||
moduleVisitor :
|
||||
Rule.ModuleRuleSchema state ModuleContext
|
||||
-> Rule.ModuleRuleSchema { state | hasAtLeastOneVisitor : () } ModuleContext
|
||||
moduleVisitor schema =
|
||||
schema
|
||||
|> Rule.withModuleDefinitionVisitor moduleDefinitionVisitor
|
||||
|> Rule.withImportVisitor importVisitor
|
||||
|> Rule.withDeclarationListVisitor declarationListVisitor
|
||||
|> Rule.withFinalModuleEvaluation finalEvaluation
|
||||
|
||||
|
||||
moduleDefinitionVisitor : Node Module -> ModuleContext -> ( List nothing, ModuleContext )
|
||||
moduleDefinitionVisitor (Node _ mod) context =
|
||||
case context of
|
||||
InternalModule data ->
|
||||
( [], InternalModule { data | exposes = Module.exposingList mod } )
|
||||
|
||||
ExposedModule data ->
|
||||
( []
|
||||
, ExposedModule
|
||||
{ data
|
||||
| exposes = Module.exposingList mod
|
||||
, exposingListStart = exposingListStartLocation (Module.exposingList mod)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
exposingListStartLocation : Exposing -> Maybe Range.Location
|
||||
exposingListStartLocation exposes =
|
||||
case exposes of
|
||||
Exposing.Explicit ((Node range _) :: _) ->
|
||||
Just range.start
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
|
||||
|
||||
importVisitor : Node Import -> ModuleContext -> ( List nothing, ModuleContext )
|
||||
importVisitor (Node _ { moduleName, moduleAlias, exposingList }) context =
|
||||
case context of
|
||||
InternalModule _ ->
|
||||
( [], context )
|
||||
|
||||
ExposedModule data ->
|
||||
( []
|
||||
, ExposedModule
|
||||
{ data
|
||||
| exposedModules =
|
||||
exposedModulesForImportAlias (Node.value moduleName) moduleAlias data.exposedModules
|
||||
, importedTypes =
|
||||
importedTypesForImportExposing (Node.value moduleName) exposingList data.moduleTypes data.importedTypes
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
exposedModulesForImportAlias : ModuleName -> Maybe (Node ModuleName) -> ExposedModules -> ExposedModules
|
||||
exposedModulesForImportAlias moduleName maybeModuleAlias exposedModules =
|
||||
case maybeModuleAlias of
|
||||
Just (Node _ moduleAlias) ->
|
||||
addExposedModuleAlias moduleName
|
||||
(String.join "." moduleAlias)
|
||||
exposedModules
|
||||
|
||||
Nothing ->
|
||||
exposedModules
|
||||
|
||||
|
||||
importedTypesForImportExposing :
|
||||
ModuleName
|
||||
-> Maybe (Node Exposing)
|
||||
-> Dict ModuleName (Set String)
|
||||
-> Dict String ModuleName
|
||||
-> Dict String ModuleName
|
||||
importedTypesForImportExposing moduleName maybeExposing moduleTypes importedTypes =
|
||||
case maybeExposing of
|
||||
Just (Node _ (Exposing.Explicit list)) ->
|
||||
List.foldl (importedTypesForImportExpose moduleName) importedTypes list
|
||||
|
||||
Just (Node _ (Exposing.All _)) ->
|
||||
importedTypesForModule moduleName moduleTypes importedTypes
|
||||
|
||||
Nothing ->
|
||||
importedTypes
|
||||
|
||||
|
||||
importedTypesForImportExpose : ModuleName -> Node Exposing.TopLevelExpose -> Dict String ModuleName -> Dict String ModuleName
|
||||
importedTypesForImportExpose moduleName (Node _ expose) importedTypes =
|
||||
case expose of
|
||||
Exposing.TypeExpose { name } ->
|
||||
rememberImportedType moduleName name importedTypes
|
||||
|
||||
Exposing.TypeOrAliasExpose name ->
|
||||
rememberImportedType moduleName name importedTypes
|
||||
|
||||
Exposing.FunctionExpose _ ->
|
||||
importedTypes
|
||||
|
||||
Exposing.InfixExpose _ ->
|
||||
importedTypes
|
||||
|
||||
|
||||
importedTypesForModule :
|
||||
ModuleName
|
||||
-> Dict ModuleName (Set String)
|
||||
-> Dict String ModuleName
|
||||
-> Dict String ModuleName
|
||||
importedTypesForModule moduleName moduleTypes importedTypes =
|
||||
case Dict.get moduleName moduleTypes of
|
||||
Just types ->
|
||||
Set.foldl (rememberImportedType moduleName) importedTypes types
|
||||
|
||||
Nothing ->
|
||||
importedTypes
|
||||
|
||||
|
||||
rememberImportedType : ModuleName -> String -> Dict String ModuleName -> Dict String ModuleName
|
||||
rememberImportedType moduleName typeName importedTypes =
|
||||
Dict.insert typeName moduleName importedTypes
|
||||
|
||||
|
||||
declarationListVisitor : List (Node Declaration) -> ModuleContext -> ( List nothing, ModuleContext )
|
||||
declarationListVisitor nodes context =
|
||||
( []
|
||||
, case context of
|
||||
InternalModule data ->
|
||||
InternalModule
|
||||
{ data
|
||||
| exposedTypes =
|
||||
exposedTypesForDeclarationList data.exposes nodes data.exposedTypes
|
||||
}
|
||||
|
||||
ExposedModule data ->
|
||||
ExposedModule
|
||||
{ data
|
||||
| declaredTypes = declaredTypesForDeclarationList nodes data.declaredTypes
|
||||
, exposedSignatureTypes = exposedSignatureTypesForDeclarationList data.exposes nodes data.exposedSignatureTypes
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
exposedTypesForDeclarationList : Exposing -> List (Node Declaration) -> Set String -> Set String
|
||||
exposedTypesForDeclarationList exposes list exposedTypes =
|
||||
List.foldl (exposedTypesForDeclaration exposes) exposedTypes list
|
||||
|
||||
|
||||
exposedTypesForDeclaration : Exposing -> Node Declaration -> Set String -> Set String
|
||||
exposedTypesForDeclaration exposes (Node _ declaration) exposedTypes =
|
||||
case declaration of
|
||||
Declaration.CustomTypeDeclaration { name } ->
|
||||
rememberExposedType exposes name exposedTypes
|
||||
|
||||
Declaration.AliasDeclaration { name } ->
|
||||
rememberExposedType exposes name exposedTypes
|
||||
|
||||
_ ->
|
||||
exposedTypes
|
||||
|
||||
|
||||
rememberExposedType : Exposing -> Node String -> Set String -> Set String
|
||||
rememberExposedType exposes (Node _ name) exposedTypes =
|
||||
if isTypeExposed exposes name then
|
||||
Set.insert name exposedTypes
|
||||
|
||||
else
|
||||
exposedTypes
|
||||
|
||||
|
||||
declaredTypesForDeclarationList : List (Node Declaration) -> Set String -> Set String
|
||||
declaredTypesForDeclarationList list declaredTypes =
|
||||
List.foldl declaredTypesForDeclaration declaredTypes list
|
||||
|
||||
|
||||
declaredTypesForDeclaration : Node Declaration -> Set String -> Set String
|
||||
declaredTypesForDeclaration (Node _ declaration) declaredTypes =
|
||||
case declaration of
|
||||
Declaration.CustomTypeDeclaration { name } ->
|
||||
rememberDeclaredType name declaredTypes
|
||||
|
||||
Declaration.AliasDeclaration { name } ->
|
||||
rememberDeclaredType name declaredTypes
|
||||
|
||||
_ ->
|
||||
declaredTypes
|
||||
|
||||
|
||||
rememberDeclaredType : Node String -> Set String -> Set String
|
||||
rememberDeclaredType (Node _ name) declaredTypes =
|
||||
Set.insert name declaredTypes
|
||||
|
||||
|
||||
exposedSignatureTypesForDeclarationList :
|
||||
Exposing
|
||||
-> List (Node Declaration)
|
||||
-> List (Node ( ModuleName, String ))
|
||||
-> List (Node ( ModuleName, String ))
|
||||
exposedSignatureTypesForDeclarationList exposes list exposedSignatureTypes =
|
||||
List.foldl (exposedSignatureTypesForDeclaration exposes) exposedSignatureTypes list
|
||||
|
||||
|
||||
exposedSignatureTypesForDeclaration :
|
||||
Exposing
|
||||
-> Node Declaration
|
||||
-> List (Node ( ModuleName, String ))
|
||||
-> List (Node ( ModuleName, String ))
|
||||
exposedSignatureTypesForDeclaration exposes (Node _ declaration) exposedSignatureTypes =
|
||||
case declaration of
|
||||
Declaration.CustomTypeDeclaration { name, constructors } ->
|
||||
exposedSignatureTypesForConstructorList exposes name constructors exposedSignatureTypes
|
||||
|
||||
Declaration.AliasDeclaration { name, typeAnnotation } ->
|
||||
exposedSignatureTypesForAlias exposes name typeAnnotation exposedSignatureTypes
|
||||
|
||||
Declaration.FunctionDeclaration { signature } ->
|
||||
exposedSignatureTypesForSignature exposes signature exposedSignatureTypes
|
||||
|
||||
_ ->
|
||||
exposedSignatureTypes
|
||||
|
||||
|
||||
exposedSignatureTypesForConstructorList :
|
||||
Exposing
|
||||
-> Node String
|
||||
-> List (Node Type.ValueConstructor)
|
||||
-> List (Node ( ModuleName, String ))
|
||||
-> List (Node ( ModuleName, String ))
|
||||
exposedSignatureTypesForConstructorList exposes (Node _ name) list exposedSignatureTypes =
|
||||
if isTypeExposedOpen exposes name then
|
||||
List.foldl exposedSignatureTypesForConstructor exposedSignatureTypes list
|
||||
|
||||
else
|
||||
exposedSignatureTypes
|
||||
|
||||
|
||||
exposedSignatureTypesForConstructor :
|
||||
Node Type.ValueConstructor
|
||||
-> List (Node ( ModuleName, String ))
|
||||
-> List (Node ( ModuleName, String ))
|
||||
exposedSignatureTypesForConstructor (Node _ { arguments }) exposedSignatureTypes =
|
||||
exposedSignatureTypesForTypeAnnotationList arguments exposedSignatureTypes
|
||||
|
||||
|
||||
exposedSignatureTypesForAlias :
|
||||
Exposing
|
||||
-> Node String
|
||||
-> Node TypeAnnotation
|
||||
-> List (Node ( ModuleName, String ))
|
||||
-> List (Node ( ModuleName, String ))
|
||||
exposedSignatureTypesForAlias exposes (Node _ name) typeAnnotation exposedSignatureTypes =
|
||||
if isTypeExposed exposes name then
|
||||
case typeAnnotation of
|
||||
Node _ (TypeAnnotation.Typed _ list) ->
|
||||
exposedSignatureTypesForTypeAnnotationList list exposedSignatureTypes
|
||||
|
||||
_ ->
|
||||
exposedSignatureTypesForTypeAnnotation typeAnnotation exposedSignatureTypes
|
||||
|
||||
else
|
||||
exposedSignatureTypes
|
||||
|
||||
|
||||
exposedSignatureTypesForSignature :
|
||||
Exposing
|
||||
-> Maybe (Node Signature)
|
||||
-> List (Node ( ModuleName, String ))
|
||||
-> List (Node ( ModuleName, String ))
|
||||
exposedSignatureTypesForSignature exposes maybeSignature exposedSignatureTypes =
|
||||
case maybeSignature of
|
||||
Just (Node _ { name, typeAnnotation }) ->
|
||||
if Exposing.exposesFunction (Node.value name) exposes then
|
||||
exposedSignatureTypesForTypeAnnotation typeAnnotation exposedSignatureTypes
|
||||
|
||||
else
|
||||
exposedSignatureTypes
|
||||
|
||||
Nothing ->
|
||||
exposedSignatureTypes
|
||||
|
||||
|
||||
exposedSignatureTypesForRecordFieldList :
|
||||
List (Node TypeAnnotation.RecordField)
|
||||
-> List (Node ( ModuleName, String ))
|
||||
-> List (Node ( ModuleName, String ))
|
||||
exposedSignatureTypesForRecordFieldList fields exposedSignatureTypes =
|
||||
List.foldl exposedSignatureTypesForRecordField exposedSignatureTypes fields
|
||||
|
||||
|
||||
exposedSignatureTypesForRecordField :
|
||||
Node TypeAnnotation.RecordField
|
||||
-> List (Node ( ModuleName, String ))
|
||||
-> List (Node ( ModuleName, String ))
|
||||
exposedSignatureTypesForRecordField (Node _ ( _, typeAnnotation )) exposedSignatureTypes =
|
||||
exposedSignatureTypesForTypeAnnotation typeAnnotation exposedSignatureTypes
|
||||
|
||||
|
||||
exposedSignatureTypesForTypeAnnotationList :
|
||||
List (Node TypeAnnotation)
|
||||
-> List (Node ( ModuleName, String ))
|
||||
-> List (Node ( ModuleName, String ))
|
||||
exposedSignatureTypesForTypeAnnotationList list exposedSignatureTypes =
|
||||
List.foldl exposedSignatureTypesForTypeAnnotation exposedSignatureTypes list
|
||||
|
||||
|
||||
exposedSignatureTypesForTypeAnnotation :
|
||||
Node TypeAnnotation
|
||||
-> List (Node ( ModuleName, String ))
|
||||
-> List (Node ( ModuleName, String ))
|
||||
exposedSignatureTypesForTypeAnnotation (Node _ typeAnnotation) exposedSignatureTypes =
|
||||
case typeAnnotation of
|
||||
TypeAnnotation.Typed name list ->
|
||||
(name :: exposedSignatureTypes)
|
||||
|> exposedSignatureTypesForTypeAnnotationList list
|
||||
|
||||
TypeAnnotation.FunctionTypeAnnotation left right ->
|
||||
exposedSignatureTypes
|
||||
|> exposedSignatureTypesForTypeAnnotation left
|
||||
|> exposedSignatureTypesForTypeAnnotation right
|
||||
|
||||
TypeAnnotation.Tupled list ->
|
||||
exposedSignatureTypes
|
||||
|> exposedSignatureTypesForTypeAnnotationList list
|
||||
|
||||
TypeAnnotation.Record fields ->
|
||||
exposedSignatureTypes
|
||||
|> exposedSignatureTypesForRecordFieldList fields
|
||||
|
||||
TypeAnnotation.GenericRecord _ (Node _ fields) ->
|
||||
exposedSignatureTypes
|
||||
|> exposedSignatureTypesForRecordFieldList fields
|
||||
|
||||
TypeAnnotation.Unit ->
|
||||
exposedSignatureTypes
|
||||
|
||||
TypeAnnotation.GenericType _ ->
|
||||
exposedSignatureTypes
|
||||
|
||||
|
||||
finalEvaluation : ModuleContext -> List (Rule.Error {})
|
||||
finalEvaluation context =
|
||||
case context of
|
||||
InternalModule _ ->
|
||||
[]
|
||||
|
||||
ExposedModule data ->
|
||||
data.exposedSignatureTypes
|
||||
|> List.map (Node.map (moduleNameForType data.importedTypes))
|
||||
|> List.filter (isTypePrivate data)
|
||||
|> List.map (makeError data.exposingListStart)
|
||||
|
||||
|
||||
isTypePrivate : ExposedModuleData -> Node ( ModuleName, String ) -> Bool
|
||||
isTypePrivate data (Node _ typeCall) =
|
||||
case typeCall of
|
||||
( [], name ) ->
|
||||
if Set.member name data.declaredTypes then
|
||||
not (isTypeExposed data.exposes name)
|
||||
|
||||
else
|
||||
False
|
||||
|
||||
( moduleName, _ ) ->
|
||||
not (isModuleExposed data.exposedModules moduleName)
|
||||
|
||||
|
||||
moduleNameForType : Dict String ModuleName -> ( ModuleName, String ) -> ( ModuleName, String )
|
||||
moduleNameForType importedTypes ( moduleName, typeName ) =
|
||||
case Dict.get typeName importedTypes of
|
||||
Just typeModuleName ->
|
||||
( typeModuleName, typeName )
|
||||
|
||||
_ ->
|
||||
( moduleName, typeName )
|
||||
|
||||
|
||||
isTypeExposed : Exposing -> String -> Bool
|
||||
isTypeExposed exposes name =
|
||||
case exposes of
|
||||
Exposing.All _ ->
|
||||
True
|
||||
|
||||
Exposing.Explicit list ->
|
||||
List.any (isExposingATypeNamed name) list
|
||||
|
||||
|
||||
isTypeExposedOpen : Exposing -> String -> Bool
|
||||
isTypeExposedOpen exposes name =
|
||||
case exposes of
|
||||
Exposing.All _ ->
|
||||
True
|
||||
|
||||
Exposing.Explicit list ->
|
||||
List.any (isExposingAnOpenTypeNamed name) list
|
||||
|
||||
|
||||
isExposingATypeNamed : String -> Node Exposing.TopLevelExpose -> Bool
|
||||
isExposingATypeNamed needle (Node _ topLevelExpose) =
|
||||
case topLevelExpose of
|
||||
Exposing.InfixExpose _ ->
|
||||
False
|
||||
|
||||
Exposing.FunctionExpose _ ->
|
||||
False
|
||||
|
||||
Exposing.TypeOrAliasExpose name ->
|
||||
name == needle
|
||||
|
||||
Exposing.TypeExpose { name } ->
|
||||
name == needle
|
||||
|
||||
|
||||
isExposingAnOpenTypeNamed : String -> Node Exposing.TopLevelExpose -> Bool
|
||||
isExposingAnOpenTypeNamed needle (Node _ expose) =
|
||||
case expose of
|
||||
Exposing.TypeExpose { name, open } ->
|
||||
name == needle && open /= Nothing
|
||||
|
||||
_ ->
|
||||
False
|
||||
|
||||
|
||||
addExposedModule : String -> ExposedModules -> ExposedModules
|
||||
addExposedModule moduleName exposedModules =
|
||||
case exposedModules of
|
||||
Application ->
|
||||
exposedModules
|
||||
|
||||
Package list ->
|
||||
Package (Set.insert moduleName list)
|
||||
|
||||
|
||||
addExposedModuleAlias : ModuleName -> String -> ExposedModules -> ExposedModules
|
||||
addExposedModuleAlias moduleName moduleAlias exposedModules =
|
||||
case exposedModules of
|
||||
Application ->
|
||||
exposedModules
|
||||
|
||||
Package list ->
|
||||
if Set.member (String.join "." moduleName) list then
|
||||
Package (Set.insert moduleAlias list)
|
||||
|
||||
else
|
||||
exposedModules
|
||||
|
||||
|
||||
isModuleExposed : ExposedModules -> ModuleName -> Bool
|
||||
isModuleExposed exposedModules moduleName =
|
||||
case exposedModules of
|
||||
Application ->
|
||||
True
|
||||
|
||||
Package list ->
|
||||
Set.member (String.join "." moduleName) list
|
||||
|
||||
|
||||
makeError : Maybe Range.Location -> Node ( ModuleName, String ) -> Rule.Error {}
|
||||
makeError exposingListStart (Node range typeName) =
|
||||
let
|
||||
formattedName : String
|
||||
formattedName =
|
||||
formatTypeName typeName
|
||||
in
|
||||
Rule.errorWithFix
|
||||
{ message = "Private type `" ++ formattedName ++ "` should be exposed"
|
||||
, details =
|
||||
[ "Users of this module will not be able to annotate a value of this type if they wanted to. You should expose this type or an alias of this type."
|
||||
]
|
||||
}
|
||||
range
|
||||
(exposeTypeFix exposingListStart typeName)
|
||||
|
||||
|
||||
exposeTypeFix : Maybe Range.Location -> ( ModuleName, String ) -> List Fix
|
||||
exposeTypeFix exposingListStart ( moduleName, name ) =
|
||||
case ( exposingListStart, moduleName ) of
|
||||
( Just start, [] ) ->
|
||||
[ Fix.insertAt start (name ++ ", ") ]
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
|
||||
formatTypeName : ( ModuleName, String ) -> String
|
||||
formatTypeName ( moduleName, name ) =
|
||||
String.join "." (moduleName ++ [ name ])
|
||||
|
||||
|
||||
fromProjectToModuleContext : Rule.ModuleKey -> Node ModuleName -> ProjectContext -> ModuleContext
|
||||
fromProjectToModuleContext _ (Node _ moduleName) { exposedModules, moduleTypes } =
|
||||
if isModuleExposed exposedModules moduleName then
|
||||
initialExposedModuleContext exposedModules moduleTypes
|
||||
|
||||
else
|
||||
initialInternalModuleContext
|
||||
|
||||
|
||||
fromModuleToProjectContext : Rule.ModuleKey -> Node ModuleName -> ModuleContext -> ProjectContext
|
||||
fromModuleToProjectContext _ (Node _ moduleName) context =
|
||||
case context of
|
||||
InternalModule { exposedTypes } ->
|
||||
{ initialProjectContext | moduleTypes = Dict.singleton moduleName exposedTypes }
|
||||
|
||||
ExposedModule _ ->
|
||||
initialProjectContext
|
||||
|
||||
|
||||
foldProjectContexts : ProjectContext -> ProjectContext -> ProjectContext
|
||||
foldProjectContexts new old =
|
||||
{ exposedModules = foldExposedModules new.exposedModules old.exposedModules
|
||||
, moduleTypes = foldModuleTypes new.moduleTypes old.moduleTypes
|
||||
}
|
||||
|
||||
|
||||
foldExposedModules : ExposedModules -> ExposedModules -> ExposedModules
|
||||
foldExposedModules newExposedModules oldExposedModules =
|
||||
case ( oldExposedModules, newExposedModules ) of
|
||||
( Application, Application ) ->
|
||||
Application
|
||||
|
||||
( Application, Package _ ) ->
|
||||
newExposedModules
|
||||
|
||||
( Package _, Application ) ->
|
||||
oldExposedModules
|
||||
|
||||
( Package oldList, Package newList ) ->
|
||||
Package (Set.union oldList newList)
|
||||
|
||||
|
||||
foldModuleTypes : Dict ModuleName (Set String) -> Dict ModuleName (Set String) -> Dict ModuleName (Set String)
|
||||
foldModuleTypes newModuleTypes oldModuleTypes =
|
||||
Dict.foldl foldModuleTypesHelp newModuleTypes oldModuleTypes
|
||||
|
||||
|
||||
foldModuleTypesHelp : ModuleName -> Set String -> Dict ModuleName (Set String) -> Dict ModuleName (Set String)
|
||||
foldModuleTypesHelp moduleName newTypes moduleTypes =
|
||||
case Dict.get moduleName moduleTypes of
|
||||
Just oldTypes ->
|
||||
Dict.insert moduleName (Set.union oldTypes newTypes) moduleTypes
|
||||
|
||||
Nothing ->
|
||||
Dict.insert moduleName newTypes moduleTypes
|
||||
|
||||
|
||||
initialProjectContext : ProjectContext
|
||||
initialProjectContext =
|
||||
{ exposedModules = Application
|
||||
, moduleTypes = Dict.empty
|
||||
}
|
||||
|
||||
|
||||
initialInternalModuleContext : ModuleContext
|
||||
initialInternalModuleContext =
|
||||
InternalModule initialAnyModuleData
|
||||
|
||||
|
||||
initialExposedModuleContext : ExposedModules -> Dict ModuleName (Set String) -> ModuleContext
|
||||
initialExposedModuleContext exposedModules moduleTypes =
|
||||
ExposedModule
|
||||
{ declaredTypes = Set.empty
|
||||
, exposedModules = exposedModules
|
||||
, exposedSignatureTypes = []
|
||||
, exposes = Exposing.Explicit []
|
||||
, exposingListStart = Nothing
|
||||
, importedTypes = Dict.empty
|
||||
, moduleTypes = moduleTypes
|
||||
}
|
||||
|
||||
|
||||
initialAnyModuleData : InternalModuleData
|
||||
initialAnyModuleData =
|
||||
{ exposedTypes = Set.empty
|
||||
, exposes = Exposing.Explicit []
|
||||
}
|
||||
|
||||
|
||||
type alias ProjectContext =
|
||||
{ exposedModules : ExposedModules
|
||||
, moduleTypes : Dict ModuleName (Set String)
|
||||
}
|
||||
|
||||
|
||||
type ModuleContext
|
||||
= InternalModule InternalModuleData
|
||||
| ExposedModule ExposedModuleData
|
||||
|
||||
|
||||
type alias InternalModuleData =
|
||||
{ exposedTypes : Set String
|
||||
, exposes : Exposing
|
||||
}
|
||||
|
||||
|
||||
type alias ExposedModuleData =
|
||||
{ declaredTypes : Set String
|
||||
, exposedModules : ExposedModules
|
||||
, exposedSignatureTypes : List (Node ( ModuleName, String ))
|
||||
, exposes : Exposing
|
||||
, exposingListStart : Maybe Range.Location
|
||||
, importedTypes : Dict String ModuleName
|
||||
, moduleTypes : Dict ModuleName (Set String)
|
||||
}
|
||||
|
||||
|
||||
type ExposedModules
|
||||
= Application
|
||||
| Package (Set String)
|
950
tests/NoMissingTypeExposeTest.elm
Normal file
950
tests/NoMissingTypeExposeTest.elm
Normal file
@ -0,0 +1,950 @@
|
||||
module NoMissingTypeExposeTest exposing (all)
|
||||
|
||||
import Elm.Docs
|
||||
import Elm.Project
|
||||
import Json.Decode as Decode
|
||||
import NoMissingTypeExpose exposing (rule)
|
||||
import Review.Project as Project exposing (Project)
|
||||
import Review.Project.Dependency as Dependency exposing (Dependency)
|
||||
import Review.Test
|
||||
import Test exposing (Test, describe, test)
|
||||
|
||||
|
||||
all : Test
|
||||
all =
|
||||
describe "NoMissingTypeExpose"
|
||||
[ describe "in exposed functions" functionTests
|
||||
, describe "in exposed types" typeTests
|
||||
, describe "in exposed type aliases" typeAliasTests
|
||||
, describe "in package projects" packageTests
|
||||
]
|
||||
|
||||
|
||||
details : List String
|
||||
details =
|
||||
[ "Users of this module will not be able to annotate a value of this type if they wanted to. You should expose this type or an alias of this type."
|
||||
]
|
||||
|
||||
|
||||
functionTests : List Test
|
||||
functionTests =
|
||||
[ test "passes when everything is exposed" <|
|
||||
\() ->
|
||||
"""
|
||||
module Happiness exposing (..)
|
||||
type Happiness = Happy
|
||||
toString : Happiness -> String
|
||||
toString howHappy = "Happy"
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "passes when an exposed type is used" <|
|
||||
\() ->
|
||||
"""
|
||||
module Happiness exposing (Happiness, toString)
|
||||
type Happiness = Ecstatic
|
||||
toString : Happiness -> String
|
||||
toString howHappy = "Very"
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "reports when a private type is used" <|
|
||||
\() ->
|
||||
"""
|
||||
module Happiness exposing (toString)
|
||||
type Happiness = Ecstatic
|
||||
toString : Happiness -> String
|
||||
toString howHappy = "Very"
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Private type `Happiness` should be exposed"
|
||||
, details = details
|
||||
, under = "Happiness"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 12 }, end = { row = 4, column = 21 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Happiness exposing (Happiness, toString)
|
||||
type Happiness = Ecstatic
|
||||
toString : Happiness -> String
|
||||
toString howHappy = "Very"
|
||||
"""
|
||||
]
|
||||
, test "reports when a private type is returned" <|
|
||||
\() ->
|
||||
"""
|
||||
module Happiness exposing (ecstatic)
|
||||
type Happiness = Ecstatic
|
||||
ecstatic : Happiness
|
||||
ecstatic = Ecstatic
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Private type `Happiness` should be exposed"
|
||||
, details = details
|
||||
, under = "Happiness"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 12 }, end = { row = 4, column = 21 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Happiness exposing (Happiness, ecstatic)
|
||||
type Happiness = Ecstatic
|
||||
ecstatic : Happiness
|
||||
ecstatic = Ecstatic
|
||||
"""
|
||||
]
|
||||
, test "reports when a private type is used in a Maybe" <|
|
||||
\() ->
|
||||
"""
|
||||
module Happiness exposing (toString)
|
||||
type Happiness = Ecstatic
|
||||
toString : Maybe Happiness -> String
|
||||
toString maybeHappy = "Very"
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Private type `Happiness` should be exposed"
|
||||
, details = details
|
||||
, under = "Happiness"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 18 }, end = { row = 4, column = 27 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Happiness exposing (Happiness, toString)
|
||||
type Happiness = Ecstatic
|
||||
toString : Maybe Happiness -> String
|
||||
toString maybeHappy = "Very"
|
||||
"""
|
||||
]
|
||||
, test "reports when an external private type is used in a Maybe" <|
|
||||
\() ->
|
||||
"""
|
||||
module Happiness exposing (toString)
|
||||
type Happiness = Ecstatic
|
||||
toString : Maybe.Maybe Happiness -> String
|
||||
toString maybeHappy = "Very"
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Private type `Happiness` should be exposed"
|
||||
, details = details
|
||||
, under = "Happiness"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 24 }, end = { row = 4, column = 33 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Happiness exposing (Happiness, toString)
|
||||
type Happiness = Ecstatic
|
||||
toString : Maybe.Maybe Happiness -> String
|
||||
toString maybeHappy = "Very"
|
||||
"""
|
||||
]
|
||||
, test "reports when a private type is used in a tuple" <|
|
||||
\() ->
|
||||
"""
|
||||
module Happiness exposing (equal)
|
||||
type Happiness = Ecstatic
|
||||
equal : ( Happiness, Happiness ) -> Bool
|
||||
equal ( a, b ) = a == b
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Private type `Happiness` should be exposed"
|
||||
, details = details
|
||||
, under = "Happiness"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 11 }, end = { row = 4, column = 20 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Happiness exposing (Happiness, equal)
|
||||
type Happiness = Ecstatic
|
||||
equal : ( Happiness, Happiness ) -> Bool
|
||||
equal ( a, b ) = a == b
|
||||
"""
|
||||
, Review.Test.error
|
||||
{ message = "Private type `Happiness` should be exposed"
|
||||
, details = details
|
||||
, under = "Happiness"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 22 }, end = { row = 4, column = 31 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Happiness exposing (Happiness, equal)
|
||||
type Happiness = Ecstatic
|
||||
equal : ( Happiness, Happiness ) -> Bool
|
||||
equal ( a, b ) = a == b
|
||||
"""
|
||||
]
|
||||
, test "reports when a private type is used in a record" <|
|
||||
\() ->
|
||||
"""
|
||||
module Happiness exposing (rank)
|
||||
type Happiness = Ecstatic
|
||||
rank : { happiness : Happiness } -> Int
|
||||
rank { happiness } = 0
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Private type `Happiness` should be exposed"
|
||||
, details = details
|
||||
, under = "Happiness"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 22 }, end = { row = 4, column = 31 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Happiness exposing (Happiness, rank)
|
||||
type Happiness = Ecstatic
|
||||
rank : { happiness : Happiness } -> Int
|
||||
rank { happiness } = 0
|
||||
"""
|
||||
]
|
||||
, test "reports when a private type is used in an extensible record" <|
|
||||
\() ->
|
||||
"""
|
||||
module Happiness exposing (rank)
|
||||
type Happiness = Ecstatic
|
||||
rank : { a | happiness : Happiness } -> Int
|
||||
rank { happiness } = 0
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Private type `Happiness` should be exposed"
|
||||
, details = details
|
||||
, under = "Happiness"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 26 }, end = { row = 4, column = 35 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Happiness exposing (Happiness, rank)
|
||||
type Happiness = Ecstatic
|
||||
rank : { a | happiness : Happiness } -> Int
|
||||
rank { happiness } = 0
|
||||
"""
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
typeTests : List Test
|
||||
typeTests =
|
||||
[ test "passes when an exposed type uses another exposed type" <|
|
||||
\() ->
|
||||
"""
|
||||
module Happiness exposing (Happiness, Mood(..))
|
||||
type Mood = Happy Happiness
|
||||
type Happiness = Ecstatic
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "passes when an exposed opaque type uses a private type" <|
|
||||
\() ->
|
||||
"""
|
||||
module Happiness exposing (Mood)
|
||||
type Mood = Happy Happiness
|
||||
type Happiness = Ecstatic
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "reports when an exposed open type uses a private type" <|
|
||||
\() ->
|
||||
"""
|
||||
module Happiness exposing (Mood(..))
|
||||
type Mood = Happy Happiness
|
||||
type Happiness = Ecstatic
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Private type `Happiness` should be exposed"
|
||||
, details = details
|
||||
, under = "Happiness"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 19 }, end = { row = 3, column = 28 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Happiness exposing (Happiness, Mood(..))
|
||||
type Mood = Happy Happiness
|
||||
type Happiness = Ecstatic
|
||||
"""
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
typeAliasTests : List Test
|
||||
typeAliasTests =
|
||||
[ test "passes when everything is exposed" <|
|
||||
\() ->
|
||||
"""
|
||||
module Happiness exposing (..)
|
||||
type alias Happiness = { happiness : Int }
|
||||
toString : Happiness -> String
|
||||
toString howHappy = "Very"
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "does not report a function using an exposed type alias of a private type" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.new
|
||||
|> Project.addElmJson (createElmJson packageElmJson)
|
||||
in
|
||||
[ """
|
||||
module Exposed exposing (Happiness, toString)
|
||||
import Mood
|
||||
type alias Happiness = Mood.Happiness
|
||||
toString : Happiness -> String
|
||||
toString happiness = "Happy"
|
||||
""", """
|
||||
module Mood exposing (Happiness)
|
||||
type Happiness = Ecstatic
|
||||
""" ]
|
||||
|> Review.Test.runOnModulesWithProjectData project rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "reports a function using a private type alias" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.new
|
||||
|> Project.addElmJson (createElmJson packageElmJson)
|
||||
in
|
||||
"""
|
||||
module Exposed exposing (toString)
|
||||
type alias Happiness = { happiness : Int }
|
||||
toString : Happiness -> String
|
||||
toString happiness = "Happy"
|
||||
"""
|
||||
|> Review.Test.runWithProjectData project rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Private type `Happiness` should be exposed"
|
||||
, details = details
|
||||
, under = "Happiness"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 12 }, end = { row = 4, column = 21 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Exposed exposing (Happiness, toString)
|
||||
type alias Happiness = { happiness : Int }
|
||||
toString : Happiness -> String
|
||||
toString happiness = "Happy"
|
||||
"""
|
||||
]
|
||||
, test "reports a function using an internal type alias" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.new
|
||||
|> Project.addElmJson (createElmJson packageElmJson)
|
||||
in
|
||||
[ """
|
||||
module Exposed exposing (toString)
|
||||
import Mood
|
||||
toString : Mood.Happiness -> String
|
||||
toString happiness = "Happy"
|
||||
""", """
|
||||
module Mood exposing (Happiness)
|
||||
type alias Happiness = { howHappy : Int }
|
||||
""" ]
|
||||
|> Review.Test.runOnModulesWithProjectData project rule
|
||||
|> Review.Test.expectErrorsForModules
|
||||
[ ( "Exposed"
|
||||
, [ Review.Test.error
|
||||
{ message = "Private type `Mood.Happiness` should be exposed"
|
||||
, details = details
|
||||
, under = "Mood.Happiness"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 12 }, end = { row = 4, column = 26 } }
|
||||
]
|
||||
)
|
||||
]
|
||||
, test "reports a function using an internal type alias exposed via (..)" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.new
|
||||
|> Project.addElmJson (createElmJson packageElmJson)
|
||||
in
|
||||
[ """
|
||||
module Exposed exposing (toString)
|
||||
import Mood exposing (..)
|
||||
toString : Happiness -> String
|
||||
toString happiness = "Happy"
|
||||
""", """
|
||||
module Mood exposing (..)
|
||||
type alias Happiness = { howHappy : Int }
|
||||
""" ]
|
||||
|> Review.Test.runOnModulesWithProjectData project rule
|
||||
|> Review.Test.expectErrorsForModules
|
||||
[ ( "Exposed"
|
||||
, [ Review.Test.error
|
||||
{ message = "Private type `Mood.Happiness` should be exposed"
|
||||
, details = details
|
||||
, under = "Happiness"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 12 }, end = { row = 4, column = 21 } }
|
||||
]
|
||||
)
|
||||
]
|
||||
, test "reports a function using an internal type alias exposed explicitly and imported via (..)" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.new
|
||||
|> Project.addElmJson (createElmJson packageElmJson)
|
||||
in
|
||||
[ """
|
||||
module Exposed exposing (toString)
|
||||
import Mood exposing (..)
|
||||
toString : Happiness -> String
|
||||
toString happiness = "Happy"
|
||||
""", """
|
||||
module Mood exposing (Happiness)
|
||||
type alias Happiness = { howHappy : Int }
|
||||
""" ]
|
||||
|> Review.Test.runOnModulesWithProjectData project rule
|
||||
|> Review.Test.expectErrorsForModules
|
||||
[ ( "Exposed"
|
||||
, [ Review.Test.error
|
||||
{ message = "Private type `Mood.Happiness` should be exposed"
|
||||
, details = details
|
||||
, under = "Happiness"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 12 }, end = { row = 4, column = 21 } }
|
||||
]
|
||||
)
|
||||
]
|
||||
, describe "when an exposed type alias uses a private type" <|
|
||||
[ test "does not report when the type alias is directly a private type" <|
|
||||
\() ->
|
||||
"""
|
||||
module Happiness exposing (Mood)
|
||||
type alias Mood = Happiness Int
|
||||
type Happiness a = Ecstatic
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "reports a private type in another type" <|
|
||||
\() ->
|
||||
"""
|
||||
module Happiness exposing (Mood)
|
||||
type alias Mood = Maybe Happiness
|
||||
type Happiness = Ecstatic
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Private type `Happiness` should be exposed"
|
||||
, details = details
|
||||
, under = "Happiness"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 25 }, end = { row = 3, column = 34 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Happiness exposing (Happiness, Mood)
|
||||
type alias Mood = Maybe Happiness
|
||||
type Happiness = Ecstatic
|
||||
"""
|
||||
]
|
||||
, test "reports a private type in a record" <|
|
||||
\() ->
|
||||
"""
|
||||
module Happiness exposing (Mood)
|
||||
type alias Mood = { happiness : Happiness }
|
||||
type Happiness = Ecstatic
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Private type `Happiness` should be exposed"
|
||||
, details = details
|
||||
, under = "Happiness"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 33 }, end = { row = 3, column = 42 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Happiness exposing (Happiness, Mood)
|
||||
type alias Mood = { happiness : Happiness }
|
||||
type Happiness = Ecstatic
|
||||
"""
|
||||
]
|
||||
, test "reports private type in an extensible record" <|
|
||||
\() ->
|
||||
"""
|
||||
module Happiness exposing (Mood)
|
||||
type alias Mood = { a | happiness : Happiness }
|
||||
type Happiness = Ecstatic
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Private type `Happiness` should be exposed"
|
||||
, details = details
|
||||
, under = "Happiness"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 37 }, end = { row = 3, column = 46 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Happiness exposing (Happiness, Mood)
|
||||
type alias Mood = { a | happiness : Happiness }
|
||||
type Happiness = Ecstatic
|
||||
"""
|
||||
]
|
||||
, test "reports a private type in a tuple" <|
|
||||
\() ->
|
||||
"""
|
||||
module Happiness exposing (Mood)
|
||||
type alias Mood = ( Happiness, Int )
|
||||
type Happiness = Ecstatic
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Private type `Happiness` should be exposed"
|
||||
, details = details
|
||||
, under = "Happiness"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 21 }, end = { row = 3, column = 30 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Happiness exposing (Happiness, Mood)
|
||||
type alias Mood = ( Happiness, Int )
|
||||
type Happiness = Ecstatic
|
||||
"""
|
||||
]
|
||||
, test "reports a private type in a function" <|
|
||||
\() ->
|
||||
"""
|
||||
module Happiness exposing (Mood)
|
||||
type alias Mood = Happiness -> Int
|
||||
type Happiness = Ecstatic
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Private type `Happiness` should be exposed"
|
||||
, details = details
|
||||
, under = "Happiness"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 19 }, end = { row = 3, column = 28 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Happiness exposing (Happiness, Mood)
|
||||
type alias Mood = Happiness -> Int
|
||||
type Happiness = Ecstatic
|
||||
"""
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
packageTests : List Test
|
||||
packageTests =
|
||||
[ test "reports a function exposed by a package module using a type from an internal module" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.new
|
||||
|> Project.addElmJson (createElmJson packageElmJson)
|
||||
in
|
||||
[ """
|
||||
module Exposed exposing (toString)
|
||||
import Mood
|
||||
toString : Mood.Happiness -> String
|
||||
toString happiness = "Happy"
|
||||
""", """
|
||||
module Mood exposing (Happiness)
|
||||
type Happiness = Ecstatic
|
||||
""" ]
|
||||
|> Review.Test.runOnModulesWithProjectData project rule
|
||||
|> Review.Test.expectErrorsForModules
|
||||
[ ( "Exposed"
|
||||
, [ Review.Test.error
|
||||
{ message = "Private type `Mood.Happiness` should be exposed"
|
||||
, details = details
|
||||
, under = "Mood.Happiness"
|
||||
}
|
||||
]
|
||||
)
|
||||
]
|
||||
, test "does not report an exposed function using a type from an exposed module" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.new
|
||||
|> Project.addElmJson (createElmJson packageElmJson)
|
||||
in
|
||||
[ """
|
||||
module Exposed exposing (toString)
|
||||
import ExposedMood
|
||||
toString : ExposedMood.Happiness -> String
|
||||
toString happiness = "Happy"
|
||||
""", """
|
||||
module ExposedMood exposing (Happiness)
|
||||
type Happiness = Ecstatic
|
||||
""" ]
|
||||
|> Review.Test.runOnModulesWithProjectData project rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "reports an exposed function using an internal type from an aliased module" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.new
|
||||
|> Project.addElmJson (createElmJson packageElmJson)
|
||||
in
|
||||
[ """
|
||||
module Exposed exposing (toString)
|
||||
import Mood as M
|
||||
toString : M.Happiness -> String
|
||||
toString happiness = "Happy"
|
||||
""", """
|
||||
module Mood exposing (Happiness)
|
||||
type Happiness = Ecstatic
|
||||
""" ]
|
||||
|> Review.Test.runOnModulesWithProjectData project rule
|
||||
|> Review.Test.expectErrorsForModules
|
||||
[ ( "Exposed"
|
||||
, [ Review.Test.error
|
||||
{ message = "Private type `M.Happiness` should be exposed"
|
||||
, details = details
|
||||
, under = "M.Happiness"
|
||||
}
|
||||
]
|
||||
)
|
||||
]
|
||||
, test "reports an exposed function using an exposed type from an aliased module" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.new
|
||||
|> Project.addElmJson (createElmJson packageElmJson)
|
||||
in
|
||||
[ """
|
||||
module Exposed exposing (toString)
|
||||
import ExposedMood as M
|
||||
toString : M.Happiness -> String
|
||||
toString happiness = "Happy"
|
||||
""", """
|
||||
module ExposedMood exposing (Happiness)
|
||||
type Happiness = Ecstatic
|
||||
""" ]
|
||||
|> Review.Test.runOnModulesWithProjectData project rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "reports an exposed function using an internal type imported from a module" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.new
|
||||
|> Project.addElmJson (createElmJson packageElmJson)
|
||||
in
|
||||
[ """
|
||||
module Exposed exposing (toString)
|
||||
import Mood exposing (Happiness)
|
||||
toString : Happiness -> String
|
||||
toString happiness = "Happy"
|
||||
""", """
|
||||
module Mood exposing (Happiness)
|
||||
type Happiness = Ecstatic
|
||||
""" ]
|
||||
|> Review.Test.runOnModulesWithProjectData project rule
|
||||
|> Review.Test.expectErrorsForModules
|
||||
[ ( "Exposed"
|
||||
, [ Review.Test.error
|
||||
{ message = "Private type `Mood.Happiness` should be exposed"
|
||||
, details = details
|
||||
, under = "Happiness"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 12 }, end = { row = 4, column = 21 } }
|
||||
]
|
||||
)
|
||||
]
|
||||
, test "reports an exposed function using an internal type imported exposing all" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.new
|
||||
|> Project.addElmJson (createElmJson packageElmJson)
|
||||
in
|
||||
[ """
|
||||
module Exposed exposing (toRating)
|
||||
import Mood exposing (..)
|
||||
import Rating exposing (..)
|
||||
toRating : Happiness -> Rating
|
||||
toRating happiness = Rating.five
|
||||
""", """
|
||||
module Mood exposing (Happiness)
|
||||
type Happiness = Ecstatic
|
||||
""", """
|
||||
module Rating exposing (Rating, five)
|
||||
type Rating = Rating Int
|
||||
five : Rating
|
||||
five = Rating 5
|
||||
""" ]
|
||||
|> Review.Test.runOnModulesWithProjectData project rule
|
||||
|> Review.Test.expectErrorsForModules
|
||||
[ ( "Exposed"
|
||||
, [ Review.Test.error
|
||||
{ message = "Private type `Mood.Happiness` should be exposed"
|
||||
, details = details
|
||||
, under = "Happiness"
|
||||
}
|
||||
, Review.Test.error
|
||||
{ message = "Private type `Rating.Rating` should be exposed"
|
||||
, details = details
|
||||
, under = "Rating"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 5, column = 25 }, end = { row = 5, column = 31 } }
|
||||
]
|
||||
)
|
||||
]
|
||||
, test "does not report an internal function using a type from another internal module" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.new
|
||||
|> Project.addElmJson (createElmJson packageElmJson)
|
||||
in
|
||||
[ """
|
||||
module Mood exposing (Happiness, toRating)
|
||||
import Rating exposing (Rating)
|
||||
type Happiness = Ecstatic
|
||||
toRating : Happiness -> Rating
|
||||
toRating happiness = Rating.five
|
||||
""", """
|
||||
module Rating exposing (Rating, five)
|
||||
type Rating = Rating Int
|
||||
five : Rating
|
||||
five = Rating 5
|
||||
""" ]
|
||||
|> Review.Test.runOnModulesWithProjectData project rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "does not report an exposed function using an exposed imported type" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.new
|
||||
|> Project.addElmJson (createElmJson packageElmJson)
|
||||
in
|
||||
[ """
|
||||
module Exposed exposing (toString)
|
||||
import ExposedMood exposing (Happiness)
|
||||
toString : Happiness -> String
|
||||
toString happiness = "Happy"
|
||||
""", """
|
||||
module ExposedMood exposing (Happiness)
|
||||
type Happiness = Ecstatic
|
||||
""" ]
|
||||
|> Review.Test.runOnModulesWithProjectData project rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "does not report an exposed function using an exposed type imported all" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.new
|
||||
|> Project.addElmJson (createElmJson packageElmJson)
|
||||
in
|
||||
[ """
|
||||
module Exposed exposing (toString)
|
||||
import ExposedMood exposing (..)
|
||||
toString : Happiness -> String
|
||||
toString happiness = "Happy"
|
||||
""", """
|
||||
module ExposedMood exposing (Happiness)
|
||||
type Happiness = Ecstatic
|
||||
""" ]
|
||||
|> Review.Test.runOnModulesWithProjectData project rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "does not report an exposed function using an type exposed all and imported all" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.new
|
||||
|> Project.addElmJson (createElmJson packageElmJson)
|
||||
in
|
||||
[ """
|
||||
module Exposed exposing (toString)
|
||||
import ExposedMood exposing (..)
|
||||
toString : Happiness -> String
|
||||
toString happiness = "Happy"
|
||||
""", """
|
||||
module ExposedMood exposing (..)
|
||||
type Happiness = Ecstatic
|
||||
""" ]
|
||||
|> Review.Test.runOnModulesWithProjectData project rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "does not report an exposed function using a dependency's type" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.new
|
||||
|> Project.addElmJson (createElmJson packageElmJson)
|
||||
|> Project.addDependency dependency
|
||||
in
|
||||
"""
|
||||
module Exposed exposing (toString)
|
||||
import ExternalMood exposing (Happiness)
|
||||
toString : Happiness -> String
|
||||
toString happiness = "Happy"
|
||||
"""
|
||||
|> Review.Test.runWithProjectData project rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "does not report an exposed function using a dependency's type exposing all" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.new
|
||||
|> Project.addElmJson (createElmJson packageElmJson)
|
||||
|> Project.addDependency dependency
|
||||
in
|
||||
"""
|
||||
module Exposed exposing (toString)
|
||||
import ExternalMood exposing (..)
|
||||
toString : Happiness -> String
|
||||
toString happiness = "Happy"
|
||||
"""
|
||||
|> Review.Test.runWithProjectData project rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "does not report an exposed function importing a dependency's type" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.new
|
||||
|> Project.addElmJson (createElmJson packageElmJson)
|
||||
|> Project.addDependency dependency
|
||||
in
|
||||
"""
|
||||
module Exposed exposing (toString)
|
||||
import ExternalMood
|
||||
toString : ExternalMood.Happiness -> String
|
||||
toString happiness = "Happy"
|
||||
"""
|
||||
|> Review.Test.runWithProjectData project rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "does not report an exposed function importing a dependency's type with alias" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.new
|
||||
|> Project.addElmJson (createElmJson packageElmJson)
|
||||
|> Project.addDependency dependency
|
||||
in
|
||||
"""
|
||||
module Exposed exposing (toString)
|
||||
import ExternalMood as M
|
||||
toString : M.Happiness -> String
|
||||
toString happiness = "Happy"
|
||||
"""
|
||||
|> Review.Test.runWithProjectData project rule
|
||||
|> Review.Test.expectNoErrors
|
||||
]
|
||||
|
||||
|
||||
createElmJson : String -> { path : String, raw : String, project : Elm.Project.Project }
|
||||
createElmJson rawElmJson =
|
||||
{ path = "elm.json"
|
||||
, raw = rawElmJson
|
||||
, project = createElmJsonProject rawElmJson
|
||||
}
|
||||
|
||||
|
||||
createElmJsonProject : String -> Elm.Project.Project
|
||||
createElmJsonProject rawElmJson =
|
||||
case Decode.decodeString Elm.Project.decoder rawElmJson of
|
||||
Ok project ->
|
||||
project
|
||||
|
||||
Err error ->
|
||||
Debug.todo ("[elm.json]: " ++ Debug.toString error)
|
||||
|
||||
|
||||
packageElmJson : String
|
||||
packageElmJson =
|
||||
"""{
|
||||
"type": "package",
|
||||
"name": "jfmengels/review-common-tests",
|
||||
"summary": "A test package",
|
||||
"license": "MIT",
|
||||
"version": "1.0.0",
|
||||
"exposed-modules": [
|
||||
"Exposed",
|
||||
"ExposedMood"
|
||||
],
|
||||
"elm-version": "0.19.0 <= v < 0.20.0",
|
||||
"dependencies": {
|
||||
"elm/core": "1.0.2 <= v < 2.0.0"
|
||||
},
|
||||
"test-dependencies": {}
|
||||
}"""
|
||||
|
||||
|
||||
dependency : Dependency
|
||||
dependency =
|
||||
Dependency.create
|
||||
"sparksp/external-mood"
|
||||
(createElmJsonProject dependencyElmJson)
|
||||
dependencyModules
|
||||
|
||||
|
||||
dependencyModules : List Elm.Docs.Module
|
||||
dependencyModules =
|
||||
[ { name = "ExternalMood"
|
||||
, comment = ""
|
||||
, unions =
|
||||
[ { name = "Happiness"
|
||||
, comment = ""
|
||||
, args = []
|
||||
, tags =
|
||||
[ ( "Ecstatic", [] )
|
||||
, ( "FineIGuess", [] )
|
||||
, ( "Unhappy", [] )
|
||||
]
|
||||
}
|
||||
]
|
||||
, aliases = []
|
||||
, values = []
|
||||
, binops = []
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
dependencyElmJson : String
|
||||
dependencyElmJson =
|
||||
"""{
|
||||
"type": "package",
|
||||
"name": "sparksp/external-mood",
|
||||
"summary": "Moods for tests",
|
||||
"license": "MIT",
|
||||
"version": "1.0.0",
|
||||
"exposed-modules": [
|
||||
"ExternalMood"
|
||||
],
|
||||
"elm-version": "0.19.0 <= v <= 0.20.0",
|
||||
"dependencies": {},
|
||||
"test-dependencies": {}
|
||||
}"""
|
173
tests/NoModuleOnExposedNames.elm
Normal file
173
tests/NoModuleOnExposedNames.elm
Normal file
@ -0,0 +1,173 @@
|
||||
module NoModuleOnExposedNames exposing (rule)
|
||||
|
||||
{-|
|
||||
|
||||
@docs rule
|
||||
|
||||
-}
|
||||
|
||||
import Elm.Syntax.Import exposing (Import)
|
||||
import Elm.Syntax.ModuleName exposing (ModuleName)
|
||||
import Elm.Syntax.Node as Node exposing (Node)
|
||||
import Elm.Syntax.Range exposing (Range)
|
||||
import NoModuleOnExposedNames.Context as Context
|
||||
import Review.Fix as Fix
|
||||
import Review.Rule as Rule exposing (Error, Rule)
|
||||
import Vendor.NameVisitor as NameVisitor
|
||||
|
||||
|
||||
{-| Forbid modules on names that have been exposed.
|
||||
|
||||
config : List Rule
|
||||
config =
|
||||
[ NoModuleOnExposedNames.rule
|
||||
]
|
||||
|
||||
If a function or type has been exposed on import do not allow it to be called via the module name/alias. You should either remove the call from the import exposes or remove the module name from the call.
|
||||
|
||||
|
||||
## Failure
|
||||
|
||||
Here `class` is exposed but is still called by `Attr.class`.
|
||||
|
||||
import Html.Attributes as Attr exposing (class)
|
||||
|
||||
view children =
|
||||
div [ Attr.class "container" ] children
|
||||
|
||||
|
||||
## Success
|
||||
|
||||
Remove the module name from the call:
|
||||
|
||||
import Html.Attributes as Attr exposing (class)
|
||||
|
||||
view children =
|
||||
div [ class "container" ] children
|
||||
|
||||
|
||||
## Success
|
||||
|
||||
Or remove `class` from `exposing`:
|
||||
|
||||
import Html.Attributes as Attr
|
||||
|
||||
view children =
|
||||
div [ Attr.class "container" ] children
|
||||
|
||||
|
||||
## Failure
|
||||
|
||||
Here `Attribute` has been exposed but is still called by `Html.Attribute`.
|
||||
|
||||
import Html exposing (Attribute, Html)
|
||||
|
||||
container : List (Html.Attribute msg) -> List (Html msg) -> Html msg
|
||||
container attributes children =
|
||||
div attributes children
|
||||
|
||||
|
||||
## Success
|
||||
|
||||
Remove the module name from the call:
|
||||
|
||||
import Html exposing (Attribute, Html)
|
||||
|
||||
container : List (Attribute msg) -> List (Html msg) -> Html msg
|
||||
container attributes children =
|
||||
div attributes children
|
||||
|
||||
|
||||
## Success
|
||||
|
||||
Or remove `Attribute` from `exposing`:
|
||||
|
||||
import Html exposing (Html)
|
||||
|
||||
container : List (Html.Attribute msg) -> List (Html msg) -> Html msg
|
||||
container attributes children =
|
||||
div attributes children
|
||||
|
||||
-}
|
||||
rule : Rule
|
||||
rule =
|
||||
Rule.newModuleRuleSchema "NoModuleOnExposedNames" Context.initial
|
||||
|> Rule.withImportVisitor importVisitor
|
||||
|> NameVisitor.withValueAndTypeVisitors
|
||||
{ valueVisitor = valueVisitor
|
||||
, typeVisitor = typeVisitor
|
||||
}
|
||||
|> Rule.fromModuleRuleSchema
|
||||
|
||||
|
||||
importVisitor : Node Import -> Context.Module -> ( List (Error {}), Context.Module )
|
||||
importVisitor node context =
|
||||
( [], context |> rememberExposedNames (Node.value node) )
|
||||
|
||||
|
||||
rememberExposedNames : Import -> Context.Module -> Context.Module
|
||||
rememberExposedNames { moduleName, moduleAlias, exposingList } context =
|
||||
case exposingList of
|
||||
Nothing ->
|
||||
context
|
||||
|
||||
Just exposes ->
|
||||
let
|
||||
moduleNameOrAlias =
|
||||
moduleAlias
|
||||
|> Maybe.map Node.value
|
||||
|> Maybe.withDefault (Node.value moduleName)
|
||||
in
|
||||
context |> Context.expose moduleNameOrAlias (Node.value exposes)
|
||||
|
||||
|
||||
valueVisitor : Node ( ModuleName, String ) -> Context.Module -> ( List (Error {}), Context.Module )
|
||||
valueVisitor node context =
|
||||
case Node.value node of
|
||||
( moduleName, name ) ->
|
||||
if Context.isFunctionExposed context moduleName name then
|
||||
( [ moduleOnExposedValueError name (Node.range node) ]
|
||||
, context
|
||||
)
|
||||
|
||||
else
|
||||
( [], context )
|
||||
|
||||
|
||||
typeVisitor : Node ( ModuleName, String ) -> Context.Module -> ( List (Error {}), Context.Module )
|
||||
typeVisitor node context =
|
||||
case Node.value node of
|
||||
( moduleName, name ) ->
|
||||
if Context.isTypeExposed context moduleName name then
|
||||
( [ moduleOnExposedTypeError name (Node.range node) ]
|
||||
, context
|
||||
)
|
||||
|
||||
else
|
||||
( [], context )
|
||||
|
||||
|
||||
moduleOnExposedValueError : String -> Range -> Error {}
|
||||
moduleOnExposedValueError name range =
|
||||
Rule.errorWithFix
|
||||
{ message = "Module used on exposed value `" ++ name ++ "`."
|
||||
, details =
|
||||
[ "It is not necessary to use the module here as `" ++ name ++ "` was exposed on import."
|
||||
, "You should remove the module from this call, or remove the name from the import .. exposing list."
|
||||
]
|
||||
}
|
||||
range
|
||||
[ Fix.replaceRangeBy range name ]
|
||||
|
||||
|
||||
moduleOnExposedTypeError : String -> Range -> Error {}
|
||||
moduleOnExposedTypeError name range =
|
||||
Rule.errorWithFix
|
||||
{ message = "Module used on exposed type `" ++ name ++ "`."
|
||||
, details =
|
||||
[ "It is not necessary to use the module here as `" ++ name ++ "` was exposed on import."
|
||||
, "You should remove the module from this call, or remove the name from the import .. exposing list."
|
||||
]
|
||||
}
|
||||
range
|
||||
[ Fix.replaceRangeBy range name ]
|
68
tests/NoModuleOnExposedNames/Context.elm
Normal file
68
tests/NoModuleOnExposedNames/Context.elm
Normal file
@ -0,0 +1,68 @@
|
||||
module NoModuleOnExposedNames.Context exposing (Module, expose, initial, isFunctionExposed, isTypeExposed)
|
||||
|
||||
import Dict exposing (Dict)
|
||||
import Elm.Syntax.Exposing as Exposing exposing (Exposing)
|
||||
import Elm.Syntax.ModuleName exposing (ModuleName)
|
||||
import Elm.Syntax.Node as Node exposing (Node)
|
||||
|
||||
|
||||
type Module
|
||||
= Expose (Dict ModuleName Exposing)
|
||||
|
||||
|
||||
initial : Module
|
||||
initial =
|
||||
Expose Dict.empty
|
||||
|
||||
|
||||
expose : ModuleName -> Exposing -> Module -> Module
|
||||
expose moduleName exposer (Expose exposes) =
|
||||
Expose (Dict.insert moduleName exposer exposes)
|
||||
|
||||
|
||||
isFunctionExposed : Module -> ModuleName -> String -> Bool
|
||||
isFunctionExposed (Expose exposes) moduleName name =
|
||||
case Dict.get moduleName exposes of
|
||||
Nothing ->
|
||||
False
|
||||
|
||||
Just exposer ->
|
||||
Exposing.exposesFunction name exposer
|
||||
|
||||
|
||||
isTypeExposed : Module -> ModuleName -> String -> Bool
|
||||
isTypeExposed (Expose exposes) moduleName name =
|
||||
case Dict.get moduleName exposes of
|
||||
Nothing ->
|
||||
False
|
||||
|
||||
Just (Exposing.All _) ->
|
||||
True
|
||||
|
||||
Just (Exposing.Explicit list) ->
|
||||
List.any (isTypeNamed name) list
|
||||
|
||||
|
||||
|
||||
--- HELPERS
|
||||
|
||||
|
||||
isTypeNamed : String -> Node Exposing.TopLevelExpose -> Bool
|
||||
isTypeNamed name topLevelExpose =
|
||||
(topLevelExpose |> Node.value |> exposingTypeName) == Just name
|
||||
|
||||
|
||||
exposingTypeName : Exposing.TopLevelExpose -> Maybe String
|
||||
exposingTypeName topLevelExpose =
|
||||
case topLevelExpose of
|
||||
Exposing.FunctionExpose _ ->
|
||||
Nothing
|
||||
|
||||
Exposing.TypeOrAliasExpose name ->
|
||||
Just name
|
||||
|
||||
Exposing.TypeExpose { name } ->
|
||||
Just name
|
||||
|
||||
Exposing.InfixExpose _ ->
|
||||
Nothing
|
86
tests/NoNoOpMsg.elm
Normal file
86
tests/NoNoOpMsg.elm
Normal file
@ -0,0 +1,86 @@
|
||||
module NoNoOpMsg exposing (rule)
|
||||
|
||||
{-|
|
||||
|
||||
@docs rule
|
||||
|
||||
-}
|
||||
|
||||
import Elm.Syntax.Declaration as Declaration exposing (Declaration)
|
||||
import Elm.Syntax.Node as Node exposing (Node)
|
||||
import Review.Rule as Rule exposing (Error, Rule)
|
||||
|
||||
|
||||
{-| Reports NoOp messages
|
||||
|
||||
config =
|
||||
[ NoNoOpMsg.rule
|
||||
]
|
||||
|
||||
|
||||
## Fail
|
||||
|
||||
type Msg
|
||||
= NoOp
|
||||
|
||||
|
||||
## Success
|
||||
|
||||
type Msg
|
||||
= DomNodeWasFocused
|
||||
|
||||
|
||||
## When (not) to enable this rule
|
||||
|
||||
This rule is not useful when you are working on a package, since you don't have an update function.
|
||||
|
||||
|
||||
## Try it out
|
||||
|
||||
You can try this rule out by running the following command:
|
||||
|
||||
```bash
|
||||
elm - review --template jfmengels/elm-review-noop/preview --rules NoNoOpMsg
|
||||
```
|
||||
|
||||
-}
|
||||
rule : Rule
|
||||
rule =
|
||||
Rule.newModuleRuleSchema "NoNoOpMsg" ()
|
||||
|> Rule.withSimpleDeclarationVisitor declarationVisitor
|
||||
|> Rule.fromModuleRuleSchema
|
||||
|
||||
|
||||
declarationVisitor : Node Declaration -> List (Error {})
|
||||
declarationVisitor node =
|
||||
case Node.value node of
|
||||
Declaration.CustomTypeDeclaration { constructors } ->
|
||||
constructors
|
||||
|> List.map
|
||||
(\constructor ->
|
||||
constructor
|
||||
|> Node.value
|
||||
|> .name
|
||||
)
|
||||
|> List.filter
|
||||
(\constructorName ->
|
||||
(Node.value constructorName == "NoOp")
|
||||
|| (Node.value constructorName == "Noop")
|
||||
)
|
||||
|> List.map error
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
|
||||
error : Node String -> Error {}
|
||||
error node =
|
||||
Rule.error
|
||||
{ message = "Don't use NoOp, give it a better name"
|
||||
, details =
|
||||
[ "A Msg name should explain what happened. NoOp means tat nothing happened."
|
||||
, "Even if you don't care about handling the event, give it a name that describes what happened."
|
||||
, "@noahzgordon's talk on it: https://www.youtube.com/watch?v=w6OVDBqergc"
|
||||
]
|
||||
}
|
||||
(Node.range node)
|
78
tests/NoNoOpMsgTest.elm
Normal file
78
tests/NoNoOpMsgTest.elm
Normal file
@ -0,0 +1,78 @@
|
||||
module NoNoOpMsgTest exposing (all)
|
||||
|
||||
import NoNoOpMsg exposing (rule)
|
||||
import Review.Test
|
||||
import Test exposing (Test, describe, test)
|
||||
|
||||
|
||||
message : String
|
||||
message =
|
||||
"Don't use NoOp, give it a better name"
|
||||
|
||||
|
||||
details : List String
|
||||
details =
|
||||
[ "A Msg name should explain what happened. NoOp means tat nothing happened."
|
||||
, "Even if you don't care about handling the event, give it a name that describes what happened."
|
||||
, "@noahzgordon's talk on it: https://www.youtube.com/watch?v=w6OVDBqergc"
|
||||
]
|
||||
|
||||
|
||||
all : Test
|
||||
all =
|
||||
describe "NoNoOpMsg"
|
||||
[ test "should report an error when there is a NoOp Msg constructor" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
type Msg
|
||||
= NoOp
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "NoOp"
|
||||
}
|
||||
]
|
||||
, test "should report an error when there is a Noop Msg constructor" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
type Msg
|
||||
= Noop
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = message
|
||||
, details = details
|
||||
, under = "Noop"
|
||||
}
|
||||
]
|
||||
, test "should not report an error when there is no custom type" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
a = 1
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should not report an error when the constructor name is not NoOp" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
type Msg
|
||||
= Foo
|
||||
| Bar
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should not report an error when we see NoOp in pattern matching" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
update : Msg -> Model -> Model
|
||||
update msg model =
|
||||
case msg of
|
||||
NoOp -> model
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectNoErrors
|
||||
]
|
137
tests/NoRedundantConcat.elm
Normal file
137
tests/NoRedundantConcat.elm
Normal file
@ -0,0 +1,137 @@
|
||||
module NoRedundantConcat exposing (rule)
|
||||
|
||||
{-|
|
||||
|
||||
@docs rule
|
||||
|
||||
-}
|
||||
|
||||
import Elm.Syntax.Expression as Expression exposing (Expression)
|
||||
import Elm.Syntax.Infix as Infix
|
||||
import Elm.Syntax.Node as Node exposing (Node)
|
||||
import Elm.Syntax.Range exposing (Range)
|
||||
import Review.Fix as Fix
|
||||
import Review.Rule as Rule exposing (Error, Rule)
|
||||
import Util
|
||||
|
||||
|
||||
{-| Forbids using concatenation (`++` and `List.concat`) when it's not needed.
|
||||
|
||||
Expressions like `[ a, b ] ++ c` will be flagged, with a fix proposing to
|
||||
rewrite that to somethine like `a :: b :: c`. This is more performant and makes
|
||||
it clear that we're talking about consing items to the head of a list. Easy!
|
||||
|
||||
Expressions like `[ a, b ] ++ [ c, d ]` could be rewritten to a single literal
|
||||
list: no need to perform the concatenation at runtime when we can just write it
|
||||
ourselves! So, the fix will propose writing that as `[ a, b, c, d ]`.
|
||||
|
||||
Expression like `List.concat [ [ a ], [ b ], [ c ] ]` could become `[ a, b c ]`.
|
||||
|
||||
Finally, expressions like `"foo" ++ "bar"` can also be `"foobar"`.
|
||||
|
||||
To use this rule, add it to your `elm-review` config like so:
|
||||
|
||||
module ReviewConfig exposing (config)
|
||||
|
||||
import NoRedundantConcat
|
||||
import Review.Rule exposing (Rule)
|
||||
|
||||
config : List Rule
|
||||
config =
|
||||
[ NoRedundantConcat.rule
|
||||
]
|
||||
|
||||
-}
|
||||
rule : Rule
|
||||
rule =
|
||||
Rule.newModuleRuleSchema "NoRedundantConcat" ()
|
||||
|> Rule.withSimpleExpressionVisitor expressionVisitor
|
||||
|> Rule.fromModuleRuleSchema
|
||||
|
||||
|
||||
expressionVisitor : Node Expression -> List (Error {})
|
||||
expressionVisitor (Node.Node range expression) =
|
||||
case expression of
|
||||
Expression.OperatorApplication "++" _ (Node.Node _ (Expression.ListExpr leftItems)) (Node.Node _ (Expression.ListExpr rightItems)) ->
|
||||
[ Rule.errorWithFix
|
||||
{ message = "Concatenating a literal list with another literal list can be written as a single list literal"
|
||||
, details =
|
||||
[ "Expressions like `[ foo ] ++ [ bar ]` can be written as `[ foo, bar ]`."
|
||||
, "Using 'complex' expressions when not necessary can make code look a lot more complex than it really is. When you need to put two literal lists together, you can just put them together! No need to have that happen at runtime."
|
||||
]
|
||||
}
|
||||
range
|
||||
[ combineLists range leftItems rightItems ]
|
||||
]
|
||||
|
||||
Expression.OperatorApplication "++" _ (Node.Node _ (Expression.Literal left)) (Node.Node _ (Expression.Literal right)) ->
|
||||
[ Rule.errorWithFix
|
||||
{ message = "Concatenating a literal string with another literal string is redundant"
|
||||
, details =
|
||||
[ "Expressions like `\"foo\" ++ \"bar\"` are harder to read than `\"foobar\"`. Consider simplifying this expression."
|
||||
]
|
||||
}
|
||||
range
|
||||
[ Fix.replaceRangeBy range (Util.expressionToString range (Expression.Literal (left ++ right))) ]
|
||||
]
|
||||
|
||||
Expression.OperatorApplication "++" _ (Node.Node _ (Expression.ListExpr items)) right ->
|
||||
[ Rule.errorWithFix
|
||||
{ message = "Concatenating a literal list with something else can be written using cons operators"
|
||||
, details =
|
||||
[ "Expressions like `[ foo ] ++ b` can be written as `foo :: b`."
|
||||
, "This preserves the mental model that `List` is a linked list, with the performance considerations associated with those."
|
||||
]
|
||||
}
|
||||
range
|
||||
[ concatItems range (List.reverse items) right ]
|
||||
]
|
||||
|
||||
Expression.Application [ Node.Node _ (Expression.FunctionOrValue [ "List" ] "concat"), Node.Node _ (Expression.ListExpr items) ] ->
|
||||
attemptToCombineItems range items []
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
|
||||
attemptToCombineItems : Range -> List (Node Expression) -> List (Node Expression) -> List (Error {})
|
||||
attemptToCombineItems range expressions acc =
|
||||
case expressions of
|
||||
[] ->
|
||||
[ Rule.errorWithFix
|
||||
{ message = "Using List.concat to concatenate list literals is redundant"
|
||||
, details = [ "Rather than using `List.concat`, consider putting the elements into a single list literal" ]
|
||||
}
|
||||
range
|
||||
[ Fix.replaceRangeBy range
|
||||
(Util.expressionToString range (Expression.ListExpr acc))
|
||||
]
|
||||
]
|
||||
|
||||
(Node.Node _ (Expression.ListExpr items)) :: rest ->
|
||||
attemptToCombineItems range rest (acc ++ items)
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
|
||||
combineLists : Range -> List (Node Expression) -> List (Node Expression) -> Fix.Fix
|
||||
combineLists range left right =
|
||||
(left ++ right)
|
||||
|> Expression.ListExpr
|
||||
|> Util.expressionToString range
|
||||
|> Fix.replaceRangeBy range
|
||||
|
||||
|
||||
concatItems : Range -> List (Node Expression) -> Node Expression -> Fix.Fix
|
||||
concatItems range items ((Node.Node ontoRange ontoExpr) as onto) =
|
||||
case items of
|
||||
[] ->
|
||||
Fix.replaceRangeBy range (Util.expressionToString ontoRange ontoExpr)
|
||||
|
||||
item :: rest ->
|
||||
concatItems range
|
||||
rest
|
||||
(Node.Node ontoRange
|
||||
(Expression.OperatorApplication "::" Infix.Non item onto)
|
||||
)
|
161
tests/NoRedundantConcatTest.elm
Normal file
161
tests/NoRedundantConcatTest.elm
Normal file
@ -0,0 +1,161 @@
|
||||
module NoRedundantConcatTest exposing (..)
|
||||
|
||||
import NoRedundantConcat
|
||||
import Review.Test
|
||||
import Test exposing (Test, describe, test)
|
||||
|
||||
|
||||
tests : Test
|
||||
tests =
|
||||
describe "NoRedundantConcat"
|
||||
[ test "Simple one" <|
|
||||
\_ ->
|
||||
"""module A exposing (..)
|
||||
|
||||
a = [ foo ] ++ b
|
||||
"""
|
||||
|> Review.Test.run NoRedundantConcat.rule
|
||||
|> Review.Test.expectErrors
|
||||
[ useConsError
|
||||
"""[ foo ] ++ b"""
|
||||
|> Review.Test.whenFixed
|
||||
"""module A exposing (..)
|
||||
|
||||
a = foo :: b
|
||||
"""
|
||||
]
|
||||
, test "Multiple" <|
|
||||
\_ ->
|
||||
"""module A exposing (..)
|
||||
|
||||
a = [ foo, bar ] ++ b
|
||||
"""
|
||||
|> Review.Test.run NoRedundantConcat.rule
|
||||
|> Review.Test.expectErrors
|
||||
[ useConsError
|
||||
"""[ foo, bar ] ++ b"""
|
||||
|> Review.Test.whenFixed
|
||||
"""module A exposing (..)
|
||||
|
||||
a = foo :: bar :: b
|
||||
"""
|
||||
]
|
||||
, test "Does not mention concat of non-list literals" <|
|
||||
\_ ->
|
||||
"""module A exposing (..)
|
||||
|
||||
a = foo ++ bar ++ b
|
||||
"""
|
||||
|> Review.Test.run NoRedundantConcat.rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "Concat of list literals can be single list" <|
|
||||
\_ ->
|
||||
"""module A exposing (..)
|
||||
|
||||
a = [ foo ] ++ [ bar ]
|
||||
"""
|
||||
|> Review.Test.run NoRedundantConcat.rule
|
||||
|> Review.Test.expectErrors
|
||||
[ useSingleListError
|
||||
"""[ foo ] ++ [ bar ]"""
|
||||
|> Review.Test.whenFixed
|
||||
"""module A exposing (..)
|
||||
|
||||
a = [foo, bar]
|
||||
"""
|
||||
]
|
||||
, test "List.concat of literal lists can be single list" <|
|
||||
\_ ->
|
||||
"""module A exposing (..)
|
||||
|
||||
a = List.concat [ [ a ], [ b ] ]
|
||||
"""
|
||||
|> Review.Test.run NoRedundantConcat.rule
|
||||
|> Review.Test.expectErrors
|
||||
[ redundantListConcatError
|
||||
"""List.concat [ [ a ], [ b ] ]"""
|
||||
|> Review.Test.whenFixed
|
||||
"""module A exposing (..)
|
||||
|
||||
a = [a, b]
|
||||
"""
|
||||
]
|
||||
, test "List.concat of multiple literal lists can be single list" <|
|
||||
\_ ->
|
||||
"""module A exposing (..)
|
||||
|
||||
a = List.concat [ [ a, b ], [ c, d ], [ e, f ] ]
|
||||
"""
|
||||
|> Review.Test.run NoRedundantConcat.rule
|
||||
|> Review.Test.expectErrors
|
||||
[ redundantListConcatError
|
||||
"""List.concat [ [ a, b ], [ c, d ], [ e, f ] ]"""
|
||||
|> Review.Test.whenFixed
|
||||
"""module A exposing (..)
|
||||
|
||||
a = [a, b, c, d, e, f]
|
||||
"""
|
||||
]
|
||||
, test "Concatenation of literal strings can be single string" <|
|
||||
\_ ->
|
||||
"""module A exposing (..)
|
||||
|
||||
a = "hello" ++ " world"
|
||||
"""
|
||||
|> Review.Test.run NoRedundantConcat.rule
|
||||
|> Review.Test.expectErrors
|
||||
[ redundantStringConcatError
|
||||
""""hello" ++ " world\""""
|
||||
|> Review.Test.whenFixed
|
||||
"""module A exposing (..)
|
||||
|
||||
a = "hello world"
|
||||
"""
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
redundantStringConcatError : String -> Review.Test.ExpectedError
|
||||
redundantStringConcatError under =
|
||||
Review.Test.error
|
||||
{ message = "Concatenating a literal string with another literal string is redundant"
|
||||
, details =
|
||||
[ "Expressions like `\"foo\" ++ \"bar\"` are harder to read than `\"foobar\"`. Consider simplifying this expression."
|
||||
]
|
||||
, under = under
|
||||
}
|
||||
|
||||
|
||||
redundantListConcatError : String -> Review.Test.ExpectedError
|
||||
redundantListConcatError under =
|
||||
Review.Test.error
|
||||
{ message = "Using List.concat to concatenate list literals is redundant"
|
||||
, details =
|
||||
[ "Rather than using `List.concat`, consider putting the elements into a single list literal"
|
||||
]
|
||||
, under = under
|
||||
}
|
||||
|
||||
|
||||
useConsError : String -> Review.Test.ExpectedError
|
||||
useConsError under =
|
||||
Review.Test.error
|
||||
{ message = "Concatenating a literal list with something else can be written using cons operators"
|
||||
, details =
|
||||
[ "Expressions like `[ foo ] ++ b` can be written as `foo :: b`."
|
||||
, "This preserves the mental model that `List` is a linked list, with the performance considerations associated with those."
|
||||
]
|
||||
, under = under
|
||||
}
|
||||
|
||||
|
||||
useSingleListError : String -> Review.Test.ExpectedError
|
||||
useSingleListError under =
|
||||
Review.Test.error
|
||||
{ message = "Concatenating a literal list with another literal list can be written as a single list literal"
|
||||
, details =
|
||||
[ "Expressions like `[ foo ] ++ [ bar ]` can be written as `[ foo, bar ]`."
|
||||
, "Using 'complex' expressions when not necessary can make code look a lot more complex than it really is. When you need to put two literal lists together, you can just put them together! No need to have that happen at runtime."
|
||||
]
|
||||
, under = under
|
||||
}
|
@ -18,7 +18,6 @@ import Elm.Syntax.Expression as Expression exposing (Expression)
|
||||
import Elm.Syntax.Module as Module exposing (Module)
|
||||
import Elm.Syntax.ModuleName exposing (ModuleName)
|
||||
import Elm.Syntax.Node as Node exposing (Node)
|
||||
import Elm.Syntax.Signature exposing (Signature)
|
||||
import Elm.Syntax.TypeAnnotation as TypeAnnotation exposing (TypeAnnotation)
|
||||
import Review.Rule as Rule exposing (Error, Rule)
|
||||
import Scope
|
||||
@ -104,6 +103,15 @@ I would love help with improving this :)
|
||||
| B
|
||||
| C
|
||||
|
||||
|
||||
## Try it out
|
||||
|
||||
You can try this rule out by running the following command:
|
||||
|
||||
```bash
|
||||
elm-review --template jfmengels/review-unused/example --rules NoUnused.CustomTypeConstructors
|
||||
```
|
||||
|
||||
-}
|
||||
rule : List { moduleName : String, typeName : String, index : Int } -> Rule
|
||||
rule phantomTypes =
|
||||
@ -418,7 +426,10 @@ declarationVisitor node context =
|
||||
)
|
||||
|
||||
Declaration.FunctionDeclaration function ->
|
||||
( [], markPhantomTypesFromTypeSignatureAsUsed function.signature context )
|
||||
( [], markPhantomTypesFromTypeAnnotationAsUsed (Maybe.map (Node.value >> .typeAnnotation) function.signature) context )
|
||||
|
||||
Declaration.AliasDeclaration { typeAnnotation } ->
|
||||
( [], markPhantomTypesFromTypeAnnotationAsUsed (Just typeAnnotation) context )
|
||||
|
||||
_ ->
|
||||
( [], context )
|
||||
@ -441,12 +452,12 @@ expressionVisitor node moduleContext =
|
||||
(\declaration ->
|
||||
case Node.value declaration of
|
||||
Expression.LetFunction function ->
|
||||
Just function.signature
|
||||
Just (Maybe.map (Node.value >> .typeAnnotation) function.signature)
|
||||
|
||||
Expression.LetDestructuring _ _ ->
|
||||
Nothing
|
||||
)
|
||||
|> List.foldl markPhantomTypesFromTypeSignatureAsUsed moduleContext
|
||||
|> List.foldl markPhantomTypesFromTypeAnnotationAsUsed moduleContext
|
||||
)
|
||||
|
||||
_ ->
|
||||
@ -533,17 +544,17 @@ errorForModule moduleKey node =
|
||||
-- TYPE ANNOTATION UTILITY FUNCTIONS
|
||||
|
||||
|
||||
markPhantomTypesFromTypeSignatureAsUsed : Maybe (Node Signature) -> ModuleContext -> ModuleContext
|
||||
markPhantomTypesFromTypeSignatureAsUsed maybeSignature moduleContext =
|
||||
markPhantomTypesFromTypeAnnotationAsUsed : Maybe (Node TypeAnnotation) -> ModuleContext -> ModuleContext
|
||||
markPhantomTypesFromTypeAnnotationAsUsed maybeTypeAnnotation moduleContext =
|
||||
let
|
||||
used : List ( ModuleName, CustomTypeName )
|
||||
used =
|
||||
case maybeSignature of
|
||||
Just signature ->
|
||||
signature
|
||||
|> Node.value
|
||||
|> .typeAnnotation
|
||||
|> collectTypesUsedAsPhantomVariables moduleContext.scope moduleContext.phantomVariables
|
||||
case maybeTypeAnnotation of
|
||||
Just typeAnnotation ->
|
||||
collectTypesUsedAsPhantomVariables
|
||||
moduleContext.scope
|
||||
moduleContext.phantomVariables
|
||||
typeAnnotation
|
||||
|
||||
Nothing ->
|
||||
[]
|
||||
|
@ -376,6 +376,25 @@ type User = User Something
|
||||
|
||||
id : Id User
|
||||
id = Id
|
||||
"""
|
||||
|> Review.Test.runWithProjectData project
|
||||
(rule
|
||||
[ { moduleName = "IdModule"
|
||||
, typeName = "Id"
|
||||
, index = 0
|
||||
}
|
||||
]
|
||||
)
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should not report a custom type, when it is used aliased by a type alias" <|
|
||||
\() ->
|
||||
"""
|
||||
module MyModule exposing (Thing)
|
||||
import IdModule exposing (Id)
|
||||
|
||||
type ThingType = ThingType
|
||||
type alias ThingId = Id ThingType
|
||||
type alias Thing = { id : Id ThingType }
|
||||
"""
|
||||
|> Review.Test.runWithProjectData project
|
||||
(rule
|
||||
|
@ -29,6 +29,15 @@ A dependency is considered unused if none of its modules are imported in the pro
|
||||
[ NoUnused.Dependencies.rule
|
||||
]
|
||||
|
||||
|
||||
## Try it out
|
||||
|
||||
You can try this rule out by running the following command:
|
||||
|
||||
```bash
|
||||
elm-review --template jfmengels/review-unused/example --rules NoUnused.Dependencies
|
||||
```
|
||||
|
||||
-}
|
||||
rule : Rule
|
||||
rule =
|
||||
|
@ -40,6 +40,15 @@ then nothing will be reported.
|
||||
[ NoUnused.Exports.rule
|
||||
]
|
||||
|
||||
|
||||
## Try it out
|
||||
|
||||
You can try this rule out by running the following command:
|
||||
|
||||
```bash
|
||||
elm-review --template jfmengels/review-unused/example --rules NoUnused.Exports
|
||||
```
|
||||
|
||||
-}
|
||||
rule : Rule
|
||||
rule =
|
||||
|
@ -37,6 +37,15 @@ config =
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
## Try it out
|
||||
|
||||
You can try this rule out by running the following command:
|
||||
|
||||
```bash
|
||||
elm-review --template jfmengels/review-unused/example --rules NoUnused.Modules
|
||||
```
|
||||
|
||||
-}
|
||||
rule : Rule
|
||||
rule =
|
||||
|
@ -17,7 +17,7 @@ import Elm.Syntax.Pattern as Pattern exposing (Pattern)
|
||||
import Elm.Syntax.Range as Range exposing (Range)
|
||||
import Elm.Writer as Writer
|
||||
import NoUnused.Patterns.NameVisitor as NameVisitor
|
||||
import Review.Fix as Fix
|
||||
import Review.Fix as Fix exposing (Fix)
|
||||
import Review.Rule as Rule exposing (Rule)
|
||||
import Set exposing (Set)
|
||||
|
||||
@ -49,6 +49,15 @@ Value `something` is not used:
|
||||
add1 number =
|
||||
number + 1
|
||||
|
||||
|
||||
## Try it out
|
||||
|
||||
You can try this rule out by running the following command:
|
||||
|
||||
```bash
|
||||
elm-review --template jfmengels/review-unused/example --rules NoUnused.Parameters
|
||||
```
|
||||
|
||||
-}
|
||||
rule : Rule
|
||||
rule =
|
||||
@ -310,6 +319,7 @@ errorsForPattern use (Node range pattern) context =
|
||||
errorsForUselessNamePattern : PatternUse -> Range -> Context -> ( List (Rule.Error {}), Context )
|
||||
errorsForUselessNamePattern use range context =
|
||||
let
|
||||
fix : List Fix
|
||||
fix =
|
||||
case use of
|
||||
Lambda ->
|
||||
@ -332,6 +342,7 @@ errorsForUselessNamePattern use range context =
|
||||
errorsForUselessTuple : PatternUse -> Range -> Context -> ( List (Rule.Error {}), Context )
|
||||
errorsForUselessTuple use range context =
|
||||
let
|
||||
fix : List Fix
|
||||
fix =
|
||||
case use of
|
||||
Lambda ->
|
||||
@ -363,15 +374,19 @@ errorsForRecordValueList use recordRange list context =
|
||||
|
||||
firstNode :: restNodes ->
|
||||
let
|
||||
first : String
|
||||
first =
|
||||
firstNode |> Node.value
|
||||
Node.value firstNode
|
||||
|
||||
rest : List String
|
||||
rest =
|
||||
List.map Node.value restNodes
|
||||
|
||||
errorRange : Range
|
||||
errorRange =
|
||||
Range.combine (List.map Node.range unused)
|
||||
|
||||
fix : List Fix
|
||||
fix =
|
||||
case ( use, used ) of
|
||||
( Lambda, [] ) ->
|
||||
@ -427,6 +442,7 @@ errorsForAsPattern : PatternUse -> Range -> Node Pattern -> Node String -> Conte
|
||||
errorsForAsPattern use patternRange inner (Node range name) context =
|
||||
if Set.member name context then
|
||||
let
|
||||
fix : List Fix
|
||||
fix =
|
||||
case use of
|
||||
Lambda ->
|
||||
@ -496,6 +512,7 @@ errorsForValue : PatternUse -> String -> Range -> Context -> ( List (Rule.Error
|
||||
errorsForValue use value range context =
|
||||
if Set.member value context then
|
||||
let
|
||||
fix : List Fix
|
||||
fix =
|
||||
case use of
|
||||
Lambda ->
|
||||
|
@ -16,7 +16,7 @@ import Elm.Syntax.Pattern as Pattern exposing (Pattern)
|
||||
import Elm.Syntax.Range as Range exposing (Range)
|
||||
import Elm.Writer as Writer
|
||||
import NoUnused.Patterns.NameVisitor as NameVisitor
|
||||
import Review.Fix as Fix
|
||||
import Review.Fix as Fix exposing (Fix)
|
||||
import Review.Rule as Rule exposing (Rule)
|
||||
import Set exposing (Set)
|
||||
|
||||
@ -51,6 +51,15 @@ Value `something` is not used:
|
||||
Nothing ->
|
||||
False
|
||||
|
||||
|
||||
## Try it out
|
||||
|
||||
You can try this rule out by running the following command:
|
||||
|
||||
```bash
|
||||
elm-review --template jfmengels/review-unused/example --rules NoUnused.Patterns
|
||||
```
|
||||
|
||||
-}
|
||||
rule : Rule
|
||||
rule =
|
||||
@ -325,9 +334,11 @@ errorsForRecordValueList recordRange list context =
|
||||
|
||||
firstNode :: restNodes ->
|
||||
let
|
||||
first : String
|
||||
first =
|
||||
firstNode |> Node.value
|
||||
Node.value firstNode
|
||||
|
||||
rest : List String
|
||||
rest =
|
||||
List.map Node.value restNodes
|
||||
|
||||
@ -384,6 +395,7 @@ errorsForAsPattern : Range -> Node Pattern -> Node String -> Context -> ( List (
|
||||
errorsForAsPattern patternRange inner (Node range name) context =
|
||||
if Set.member name context then
|
||||
let
|
||||
fix : List Fix
|
||||
fix =
|
||||
[ inner
|
||||
|> Writer.writePattern
|
||||
|
@ -50,6 +50,15 @@ import Set exposing (Set)
|
||||
a n =
|
||||
n + 1
|
||||
|
||||
|
||||
## Try it out
|
||||
|
||||
You can try this rule out by running the following command:
|
||||
|
||||
```bash
|
||||
elm-review --template jfmengels/review-unused/example --rules NoUnused.Variables
|
||||
```
|
||||
|
||||
-}
|
||||
rule : Rule
|
||||
rule =
|
||||
@ -234,6 +243,7 @@ moduleDefinitionVisitor (Node _ moduleNode) context =
|
||||
|
||||
Exposing.Explicit list ->
|
||||
let
|
||||
names : List String
|
||||
names =
|
||||
List.filterMap
|
||||
(\(Node _ node) ->
|
||||
@ -430,6 +440,7 @@ expressionExitVisitor node context =
|
||||
( errors, remainingUsed ) =
|
||||
makeReport (NonemptyList.head context.scopes)
|
||||
|
||||
contextWithPoppedScope : Context
|
||||
contextWithPoppedScope =
|
||||
{ context | scopes = NonemptyList.pop context.scopes }
|
||||
in
|
||||
|
@ -41,6 +41,15 @@ that turn out to be unnecessary later.
|
||||
, view = view
|
||||
}
|
||||
|
||||
|
||||
## Try it out
|
||||
|
||||
You can try this rule out by running the following command:
|
||||
|
||||
```bash
|
||||
elm-review --template jfmengels/review-tea/example --rules NoUselessSubscriptions
|
||||
```
|
||||
|
||||
-}
|
||||
rule : Rule
|
||||
rule =
|
||||
|
478
tests/Tests/NoInconsistentAliases.elm
Normal file
478
tests/Tests/NoInconsistentAliases.elm
Normal file
@ -0,0 +1,478 @@
|
||||
module Tests.NoInconsistentAliases exposing (all)
|
||||
|
||||
import NoInconsistentAliases as Rule exposing (rule)
|
||||
import Review.Test
|
||||
import Test exposing (Test, describe, test)
|
||||
|
||||
|
||||
all : Test
|
||||
all =
|
||||
describe "NoInconsistentAliases"
|
||||
[ describe "preferred aliases" preferredAliasTests
|
||||
, describe "noMissingAliases" noMissingAliasesTests
|
||||
]
|
||||
|
||||
|
||||
noMissingAliasesTests : List Test
|
||||
noMissingAliasesTests =
|
||||
[ test "reports missing alias when one should be used" <|
|
||||
\_ ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
import Html
|
||||
import Html.Attributes
|
||||
view = Html.div [ Html.Attributes.class "container" ] []
|
||||
"""
|
||||
|> Review.Test.run
|
||||
(Rule.config
|
||||
[ ( "Html.Attributes", "Attr" )
|
||||
]
|
||||
|> Rule.noMissingAliases
|
||||
|> rule
|
||||
)
|
||||
|> Review.Test.expectErrors
|
||||
[ missingAliasError "Attr" "Html.Attributes"
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 8 }, end = { row = 4, column = 23 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Page exposing (view)
|
||||
import Html
|
||||
import Html.Attributes as Attr
|
||||
view = Html.div [ Attr.class "container" ] []
|
||||
"""
|
||||
]
|
||||
, test "does not report missing aliases when not used" <|
|
||||
\_ ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
import Html
|
||||
import Html.Attributes exposing (class)
|
||||
view = Html.div [ class "container" ] []
|
||||
"""
|
||||
|> Review.Test.run
|
||||
(Rule.config
|
||||
[ ( "Html.Attributes", "Attr" )
|
||||
]
|
||||
|> Rule.noMissingAliases
|
||||
|> rule
|
||||
)
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "does not report missing aliases when the option is not set" <|
|
||||
\_ ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
import Html
|
||||
import Html.Attributes
|
||||
view = Html.div [ Html.Attributes.class "container" ] []
|
||||
"""
|
||||
|> Review.Test.run
|
||||
(Rule.config
|
||||
[ ( "Html.Attributes", "Attr" )
|
||||
]
|
||||
|> rule
|
||||
)
|
||||
|> Review.Test.expectNoErrors
|
||||
]
|
||||
|
||||
|
||||
preferredAliasTests : List Test
|
||||
preferredAliasTests =
|
||||
[ test "reports incorrect aliases" <|
|
||||
\_ ->
|
||||
"""
|
||||
module Main exposing (main)
|
||||
import Html
|
||||
import Html.Attributes as A
|
||||
main = Html.div [ A.class "container" ] []
|
||||
"""
|
||||
|> Review.Test.run
|
||||
(Rule.config
|
||||
[ ( "Html.Attributes", "Attr" )
|
||||
]
|
||||
|> rule
|
||||
)
|
||||
|> Review.Test.expectErrors
|
||||
[ incorrectAliasError "Attr" "Html.Attributes" "A"
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 27 }, end = { row = 4, column = 28 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Main exposing (main)
|
||||
import Html
|
||||
import Html.Attributes as Attr
|
||||
main = Html.div [ Attr.class "container" ] []
|
||||
"""
|
||||
]
|
||||
, test "fixes incorrect aliases in a function signature" <|
|
||||
\_ ->
|
||||
"""
|
||||
module Main exposing (main)
|
||||
import Json.Encode as E
|
||||
import Page
|
||||
main : Program E.Value Page.Model Page.Msg
|
||||
main =
|
||||
Page.program
|
||||
"""
|
||||
|> Review.Test.run
|
||||
(Rule.config
|
||||
[ ( "Json.Encode", "Encode" )
|
||||
]
|
||||
|> rule
|
||||
)
|
||||
|> Review.Test.expectErrors
|
||||
[ incorrectAliasError "Encode" "Json.Encode" "E"
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 23 }, end = { row = 3, column = 24 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Main exposing (main)
|
||||
import Json.Encode as Encode
|
||||
import Page
|
||||
main : Program Encode.Value Page.Model Page.Msg
|
||||
main =
|
||||
Page.program
|
||||
"""
|
||||
]
|
||||
, test "fixes incorrect aliases in a type alias" <|
|
||||
\_ ->
|
||||
"""
|
||||
module Main exposing (main)
|
||||
import Json.Encode as E
|
||||
import Page
|
||||
type alias JsonValue = E.Value
|
||||
main : Program JsonValue Page.Model Page.Msg
|
||||
main =
|
||||
Page.program
|
||||
"""
|
||||
|> Review.Test.run
|
||||
(Rule.config
|
||||
[ ( "Json.Encode", "Encode" )
|
||||
]
|
||||
|> rule
|
||||
)
|
||||
|> Review.Test.expectErrors
|
||||
[ incorrectAliasError "Encode" "Json.Encode" "E"
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 23 }, end = { row = 3, column = 24 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Main exposing (main)
|
||||
import Json.Encode as Encode
|
||||
import Page
|
||||
type alias JsonValue = Encode.Value
|
||||
main : Program JsonValue Page.Model Page.Msg
|
||||
main =
|
||||
Page.program
|
||||
"""
|
||||
]
|
||||
, test "fixes incorrect aliases in a custom type constructor" <|
|
||||
\_ ->
|
||||
"""
|
||||
module Main exposing (main)
|
||||
import Json.Encode as E
|
||||
import Page
|
||||
type JsonValue = JsonValue E.Value
|
||||
main = Page.main
|
||||
"""
|
||||
|> Review.Test.run
|
||||
(Rule.config
|
||||
[ ( "Json.Encode", "Encode" )
|
||||
]
|
||||
|> rule
|
||||
)
|
||||
|> Review.Test.expectErrors
|
||||
[ incorrectAliasError "Encode" "Json.Encode" "E"
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 23 }, end = { row = 3, column = 24 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Main exposing (main)
|
||||
import Json.Encode as Encode
|
||||
import Page
|
||||
type JsonValue = JsonValue Encode.Value
|
||||
main = Page.main
|
||||
"""
|
||||
]
|
||||
, test "fixes incorrect aliases in case expressions" <|
|
||||
\_ ->
|
||||
"""
|
||||
module Visitor exposing (expressionVisitor)
|
||||
import Elm.Syntax.Expression as ESE
|
||||
import Elm.Syntax.Node as ESN
|
||||
expressionVisitor : ESN.Node ESE.Expression -> List (Error {})
|
||||
expressionVisitor node =
|
||||
case ESN.value node of
|
||||
ESE.FunctionOrValue _ _ ->
|
||||
[]
|
||||
_ ->
|
||||
[]
|
||||
"""
|
||||
|> Review.Test.run
|
||||
(Rule.config
|
||||
[ ( "Elm.Syntax.Expression", "Expression" )
|
||||
, ( "Elm.Syntax.Node", "Node" )
|
||||
]
|
||||
|> rule
|
||||
)
|
||||
|> Review.Test.expectErrors
|
||||
[ incorrectAliasError "Expression" "Elm.Syntax.Expression" "ESE"
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 33 }, end = { row = 3, column = 36 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Visitor exposing (expressionVisitor)
|
||||
import Elm.Syntax.Expression as Expression
|
||||
import Elm.Syntax.Node as ESN
|
||||
expressionVisitor : ESN.Node Expression.Expression -> List (Error {})
|
||||
expressionVisitor node =
|
||||
case ESN.value node of
|
||||
Expression.FunctionOrValue _ _ ->
|
||||
[]
|
||||
_ ->
|
||||
[]
|
||||
"""
|
||||
, incorrectAliasError "Node" "Elm.Syntax.Node" "ESN"
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 27 }, end = { row = 4, column = 30 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Visitor exposing (expressionVisitor)
|
||||
import Elm.Syntax.Expression as ESE
|
||||
import Elm.Syntax.Node as Node
|
||||
expressionVisitor : Node.Node ESE.Expression -> List (Error {})
|
||||
expressionVisitor node =
|
||||
case Node.value node of
|
||||
ESE.FunctionOrValue _ _ ->
|
||||
[]
|
||||
_ ->
|
||||
[]
|
||||
"""
|
||||
]
|
||||
, test "fixes incorrect aliases in function arguments" <|
|
||||
\_ ->
|
||||
"""
|
||||
module Visitor exposing (getRange)
|
||||
import Elm.Syntax.Node as ESN
|
||||
getRange ((ESN.Node range _) as node) = range
|
||||
"""
|
||||
|> Review.Test.run
|
||||
(Rule.config
|
||||
[ ( "Elm.Syntax.Node", "Node" )
|
||||
]
|
||||
|> rule
|
||||
)
|
||||
|> Review.Test.expectErrors
|
||||
[ incorrectAliasError "Node" "Elm.Syntax.Node" "ESN"
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 27 }, end = { row = 3, column = 30 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Visitor exposing (getRange)
|
||||
import Elm.Syntax.Node as Node
|
||||
getRange ((Node.Node range _) as node) = range
|
||||
"""
|
||||
]
|
||||
, test "fixes incorrect aliases in let blocks" <|
|
||||
\_ ->
|
||||
"""
|
||||
module Visitor exposing (shiftRange)
|
||||
import Elm.Syntax.Node as ESN
|
||||
shiftRange : ( ESN.Node String, Int ) -> { node : ESN.Node String } -> { a | node : ESN.Node String } -> Range
|
||||
shiftRange input _ _ =
|
||||
let
|
||||
asList : ( ESN.Node, Int ) -> List ESN.Node
|
||||
asList ( node, _ ) = [ node ]
|
||||
( ESN.Node range1 value1, length1 ) = input
|
||||
[ ESN.Node range2 value2 ] = asList input
|
||||
ESN.Node range3 value3 :: [] = asList input
|
||||
in
|
||||
range
|
||||
"""
|
||||
|> Review.Test.run
|
||||
(Rule.config
|
||||
[ ( "Elm.Syntax.Node", "Node" )
|
||||
]
|
||||
|> rule
|
||||
)
|
||||
|> Review.Test.expectErrors
|
||||
[ incorrectAliasError "Node" "Elm.Syntax.Node" "ESN"
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 27 }, end = { row = 3, column = 30 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Visitor exposing (shiftRange)
|
||||
import Elm.Syntax.Node as Node
|
||||
shiftRange : ( Node.Node String, Int ) -> { node : Node.Node String } -> { a | node : Node.Node String } -> Range
|
||||
shiftRange input _ _ =
|
||||
let
|
||||
asList : ( Node.Node, Int ) -> List Node.Node
|
||||
asList ( node, _ ) = [ node ]
|
||||
( Node.Node range1 value1, length1 ) = input
|
||||
[ Node.Node range2 value2 ] = asList input
|
||||
Node.Node range3 value3 :: [] = asList input
|
||||
in
|
||||
range
|
||||
"""
|
||||
]
|
||||
, test "fixes incorrect aliases in a lambda function" <|
|
||||
\_ ->
|
||||
"""
|
||||
module NoCode exposing (visitor)
|
||||
import Elm.Syntax.Node as ESN
|
||||
import Review.Rule as Review
|
||||
visitor : List (ESN.Node String) -> List (Review.Error {})
|
||||
visitor list =
|
||||
List.map (\\ESN.Node range value -> Review.error value) list
|
||||
"""
|
||||
|> Review.Test.run
|
||||
(Rule.config
|
||||
[ ( "Elm.Syntax.Node", "Node" )
|
||||
, ( "Review.Rule", "Rule" )
|
||||
]
|
||||
|> rule
|
||||
)
|
||||
|> Review.Test.expectErrors
|
||||
[ incorrectAliasError "Node" "Elm.Syntax.Node" "ESN"
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 27 }, end = { row = 3, column = 30 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module NoCode exposing (visitor)
|
||||
import Elm.Syntax.Node as Node
|
||||
import Review.Rule as Review
|
||||
visitor : List (Node.Node String) -> List (Review.Error {})
|
||||
visitor list =
|
||||
List.map (\\Node.Node range value -> Review.error value) list
|
||||
"""
|
||||
, incorrectAliasError "Rule" "Review.Rule" "Review"
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 23 }, end = { row = 4, column = 29 } }
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module NoCode exposing (visitor)
|
||||
import Elm.Syntax.Node as ESN
|
||||
import Review.Rule as Rule
|
||||
visitor : List (ESN.Node String) -> List (Rule.Error {})
|
||||
visitor list =
|
||||
List.map (\\ESN.Node range value -> Rule.error value) list
|
||||
"""
|
||||
]
|
||||
, test "does not offer a fix when there's an alias collision" <|
|
||||
\_ ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
import Html.Attributes as A
|
||||
import Svg.Attributes as Attr
|
||||
view = div [ A.class "container" ] []
|
||||
"""
|
||||
|> Review.Test.run
|
||||
(Rule.config
|
||||
[ ( "Html.Attributes", "Attr" )
|
||||
]
|
||||
|> rule
|
||||
)
|
||||
|> Review.Test.expectErrors
|
||||
[ aliasCollisionError "Attr" "Html.Attributes" "A" "Svg.Attributes"
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 27 }, end = { row = 3, column = 28 } }
|
||||
]
|
||||
, test "does not offer a fix when there's a module collision" <|
|
||||
\_ ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
import Html.Attributes as A
|
||||
import Attr
|
||||
view = div [ A.class "container" ] []
|
||||
"""
|
||||
|> Review.Test.run
|
||||
(Rule.config
|
||||
[ ( "Html.Attributes", "Attr" )
|
||||
]
|
||||
|> rule
|
||||
)
|
||||
|> Review.Test.expectErrors
|
||||
[ aliasCollisionError "Attr" "Html.Attributes" "A" "Attr"
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 27 }, end = { row = 3, column = 28 } }
|
||||
]
|
||||
, test "does not report when there's a collision with another preferred alias" <|
|
||||
\_ ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
import Html.Attributes as A
|
||||
import Svg.Attributes as Attr
|
||||
view = div [ A.class "container" ] []
|
||||
"""
|
||||
|> Review.Test.run
|
||||
(Rule.config
|
||||
[ ( "Html.Attributes", "Attr" )
|
||||
, ( "Svg.Attributes", "Attr" )
|
||||
]
|
||||
|> rule
|
||||
)
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "does not report modules imported with no alias" <|
|
||||
\_ ->
|
||||
"""
|
||||
module Main exposing (main)
|
||||
import Html.Attributes
|
||||
main = 1"""
|
||||
|> Review.Test.run
|
||||
(Rule.config
|
||||
[ ( "Html.Attributes", "Attr" )
|
||||
]
|
||||
|> rule
|
||||
)
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "does not report modules with the correct alias" <|
|
||||
\_ ->
|
||||
"""
|
||||
module Main exposing (main)
|
||||
import Html.Attributes as Attr
|
||||
main = 1"""
|
||||
|> Review.Test.run
|
||||
(Rule.config
|
||||
[ ( "Html.Attributes", "Attr" )
|
||||
]
|
||||
|> rule
|
||||
)
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "does not report modules with no preferred alias" <|
|
||||
\_ ->
|
||||
"""
|
||||
module Main exposing (main)
|
||||
import Json.Encode as E
|
||||
main = 1"""
|
||||
|> Review.Test.run
|
||||
(Rule.config
|
||||
[ ( "Html.Attributes", "Attr" )
|
||||
]
|
||||
|> rule
|
||||
)
|
||||
|> Review.Test.expectNoErrors
|
||||
]
|
||||
|
||||
|
||||
incorrectAliasError : String -> String -> String -> Review.Test.ExpectedError
|
||||
incorrectAliasError expectedAlias moduleName wrongAlias =
|
||||
Review.Test.error
|
||||
{ message = "Incorrect alias `" ++ wrongAlias ++ "` for module `" ++ moduleName ++ "`."
|
||||
, details =
|
||||
[ "This import does not use your preferred alias `" ++ expectedAlias ++ "` for `" ++ moduleName ++ "`."
|
||||
, "You should update the alias to be consistent with the rest of the project. Remember to change all references to the alias in this module too."
|
||||
]
|
||||
, under = wrongAlias
|
||||
}
|
||||
|
||||
|
||||
aliasCollisionError : String -> String -> String -> String -> Review.Test.ExpectedError
|
||||
aliasCollisionError expectedAlias moduleName wrongAlias collisionName =
|
||||
Review.Test.error
|
||||
{ message = "Incorrect alias `" ++ wrongAlias ++ "` for module `" ++ moduleName ++ "`."
|
||||
, details =
|
||||
[ "This import does not use your preferred alias `" ++ expectedAlias ++ "` for `" ++ moduleName ++ "`."
|
||||
, "Your preferred alias has already been taken by `" ++ collisionName ++ "`."
|
||||
, "You should change the alias for both modules to be consistent with the rest of the project. Remember to change all references to the alias in this module too."
|
||||
]
|
||||
, under = wrongAlias
|
||||
}
|
||||
|
||||
|
||||
missingAliasError : String -> String -> Review.Test.ExpectedError
|
||||
missingAliasError expectedAlias moduleName =
|
||||
Review.Test.error
|
||||
{ message = "Expected alias `" ++ expectedAlias ++ "` missing for module `" ++ moduleName ++ "`."
|
||||
, details =
|
||||
[ "This import does not use your preferred alias `" ++ expectedAlias ++ "` for `" ++ moduleName ++ "`."
|
||||
, "You should update the alias to be consistent with the rest of the project. Remember to change all references to the alias in this module too."
|
||||
]
|
||||
, under = moduleName
|
||||
}
|
85
tests/Tests/NoModuleOnExposedNames.elm
Normal file
85
tests/Tests/NoModuleOnExposedNames.elm
Normal file
@ -0,0 +1,85 @@
|
||||
module Tests.NoModuleOnExposedNames exposing (all)
|
||||
|
||||
import NoModuleOnExposedNames exposing (rule)
|
||||
import Review.Test
|
||||
import Test exposing (Test, describe, test)
|
||||
|
||||
|
||||
all : Test
|
||||
all =
|
||||
describe "NoModuleOnExposedNames"
|
||||
[ test "reports modules used on exposed values" <|
|
||||
\_ ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
import Html.Attributes as Attr exposing (class)
|
||||
view children =
|
||||
div [ Attr.class "container" ] children
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ moduleOnExposedValueError "Attr.class" "class"
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Page exposing (view)
|
||||
import Html.Attributes as Attr exposing (class)
|
||||
view children =
|
||||
div [ class "container" ] children
|
||||
"""
|
||||
]
|
||||
, test "reports modules used on exposed types" <|
|
||||
\_ ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
import Html exposing (Html, Attribute)
|
||||
view : List (Html.Attribute msg) -> Html msg
|
||||
view children =
|
||||
Html.div [] children
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ moduleOnExposedTypeError "Html.Attribute" "Attribute"
|
||||
|> Review.Test.whenFixed
|
||||
"""
|
||||
module Page exposing (view)
|
||||
import Html exposing (Html, Attribute)
|
||||
view : List (Attribute msg) -> Html msg
|
||||
view children =
|
||||
Html.div [] children
|
||||
"""
|
||||
]
|
||||
, test "does not report names not exposed" <|
|
||||
\_ ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
import Html.Attributes as Attr
|
||||
view children =
|
||||
div [ Attr.class "container" ] children
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectNoErrors
|
||||
]
|
||||
|
||||
|
||||
moduleOnExposedValueError : String -> String -> Review.Test.ExpectedError
|
||||
moduleOnExposedValueError call name =
|
||||
Review.Test.error
|
||||
{ message = "Module used on exposed value `" ++ name ++ "`."
|
||||
, details =
|
||||
[ "It is not necessary to use the module here as `" ++ name ++ "` was exposed on import."
|
||||
, "You should remove the module from this call, or remove the name from the import .. exposing list."
|
||||
]
|
||||
, under = call
|
||||
}
|
||||
|
||||
|
||||
moduleOnExposedTypeError : String -> String -> Review.Test.ExpectedError
|
||||
moduleOnExposedTypeError call name =
|
||||
Review.Test.error
|
||||
{ message = "Module used on exposed type `" ++ name ++ "`."
|
||||
, details =
|
||||
[ "It is not necessary to use the module here as `" ++ name ++ "` was exposed on import."
|
||||
, "You should remove the module from this call, or remove the name from the import .. exposing list."
|
||||
]
|
||||
, under = call
|
||||
}
|
81
tests/Tests/NoModuleOnExposedNames/Context.elm
Normal file
81
tests/Tests/NoModuleOnExposedNames/Context.elm
Normal file
@ -0,0 +1,81 @@
|
||||
module Tests.NoModuleOnExposedNames.Context exposing (all)
|
||||
|
||||
import Elm.Syntax.Exposing as Exposing
|
||||
import Elm.Syntax.Node exposing (Node(..))
|
||||
import Elm.Syntax.Range as Range
|
||||
import Expect
|
||||
import NoModuleOnExposedNames.Context as Context
|
||||
import Test exposing (Test, describe, test)
|
||||
|
||||
|
||||
all : Test
|
||||
all =
|
||||
describe "NoModuleOnExposedNames.Context"
|
||||
[ describe "isTypeExposed"
|
||||
[ test "is False with no imports" <|
|
||||
\_ ->
|
||||
Context.isTypeExposed Context.initial [ "Html" ] "Attribute"
|
||||
|> Expect.equal False
|
||||
, test "is True with Exposing.All" <|
|
||||
\_ ->
|
||||
let
|
||||
context =
|
||||
Context.initial
|
||||
|> Context.expose [ "Html" ] (Exposing.All Range.emptyRange)
|
||||
in
|
||||
Context.isTypeExposed context [ "Html" ] "Attribute"
|
||||
|> Expect.equal True
|
||||
, test "is True with matching TypeExpose" <|
|
||||
\_ ->
|
||||
let
|
||||
context =
|
||||
Context.initial
|
||||
|> Context.expose [ "Html" ] (Exposing.Explicit [ typeExpose "Attribute" ])
|
||||
in
|
||||
Context.isTypeExposed context [ "Html" ] "Attribute"
|
||||
|> Expect.equal True
|
||||
, test "is True with matching TypeOrAliasExpose" <|
|
||||
\_ ->
|
||||
let
|
||||
context =
|
||||
Context.initial
|
||||
|> Context.expose [ "Html" ] (Exposing.Explicit [ typeOrAliasExpose "Attribute" ])
|
||||
in
|
||||
Context.isTypeExposed context [ "Html" ] "Attribute"
|
||||
|> Expect.equal True
|
||||
, test "is False with matching FunctionExpose" <|
|
||||
\_ ->
|
||||
let
|
||||
context =
|
||||
Context.initial
|
||||
|> Context.expose [ "Html" ] (Exposing.Explicit [ functionExpose "div" ])
|
||||
in
|
||||
-- Functions are not types
|
||||
Context.isTypeExposed context [ "Html" ] "div"
|
||||
|> Expect.equal False
|
||||
, test "is False with no matching TypeExpose" <|
|
||||
\_ ->
|
||||
let
|
||||
context =
|
||||
Context.initial
|
||||
|> Context.expose [ "Html" ] (Exposing.Explicit [ typeExpose "Html" ])
|
||||
in
|
||||
Context.isTypeExposed context [ "Html" ] "Attribute"
|
||||
|> Expect.equal False
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
functionExpose : String -> Node Exposing.TopLevelExpose
|
||||
functionExpose name =
|
||||
Node Range.emptyRange (Exposing.FunctionExpose name)
|
||||
|
||||
|
||||
typeExpose : String -> Node Exposing.TopLevelExpose
|
||||
typeExpose name =
|
||||
Node Range.emptyRange (Exposing.TypeExpose { name = name, open = Nothing })
|
||||
|
||||
|
||||
typeOrAliasExpose : String -> Node Exposing.TopLevelExpose
|
||||
typeOrAliasExpose name =
|
||||
Node Range.emptyRange (Exposing.TypeOrAliasExpose name)
|
618
tests/Tests/Vendor/NameVisitor.elm
vendored
Normal file
618
tests/Tests/Vendor/NameVisitor.elm
vendored
Normal file
@ -0,0 +1,618 @@
|
||||
module Tests.Vendor.NameVisitor exposing (all)
|
||||
|
||||
import Elm.Syntax.ModuleName exposing (ModuleName)
|
||||
import Elm.Syntax.Node as Node exposing (Node)
|
||||
import Fuzz
|
||||
import Review.Rule as Rule exposing (Error, Rule)
|
||||
import Review.Test
|
||||
import Test exposing (Test, describe, fuzz, test)
|
||||
import Vendor.NameVisitor as NameVisitor
|
||||
|
||||
|
||||
all : Test
|
||||
all =
|
||||
describe "NameVisitor"
|
||||
[ describe "Context" contextTests
|
||||
, describe "Declarations" declarationTests
|
||||
, describe "Expressions" expressionTests
|
||||
, describe "Patterns" patternTests
|
||||
, describe "Type Annotations" typeAnnotationTests
|
||||
, describe "Visitors" visitorTests
|
||||
, describe "Project Rules" projectRuleTests
|
||||
]
|
||||
|
||||
|
||||
declarationTests : List Test
|
||||
declarationTests =
|
||||
[ fuzz Fuzz.string "FunctionDeclaration" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
view : Node (Html.Attribute msg) -> Html msg
|
||||
view (Node attribute) = Html.div [ attribute ] []
|
||||
"""
|
||||
|> Review.Test.run (valueOrTypeVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedTypeError context "Node"
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 8 }, end = { row = 3, column = 12 } }
|
||||
, expectedTypeError context "Html.Attribute"
|
||||
, expectedTypeError context "Html"
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 37 }, end = { row = 3, column = 41 } }
|
||||
, expectedValueError context "Node"
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 7 }, end = { row = 4, column = 11 } }
|
||||
, expectedValueError context "Html.div"
|
||||
, expectedValueError context "attribute"
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 36 }, end = { row = 4, column = 45 } }
|
||||
]
|
||||
, fuzz Fuzz.string "AliasDeclaration" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (Page)
|
||||
type alias Page =
|
||||
{ title : String
|
||||
, body : List (Html msg)
|
||||
}
|
||||
"""
|
||||
|> Review.Test.run (valueOrTypeVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedTypeError context "String"
|
||||
, expectedTypeError context "List"
|
||||
, expectedTypeError context "Html"
|
||||
]
|
||||
, fuzz Fuzz.string "CustomTypeDeclaration" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (Page)
|
||||
type Page
|
||||
= Home Route.Home
|
||||
"""
|
||||
|> Review.Test.run (valueOrTypeVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedTypeError context "Route.Home"
|
||||
]
|
||||
, fuzz Fuzz.string "PortDeclaration" <|
|
||||
\context ->
|
||||
"""
|
||||
port module Ports exposing (alarm)
|
||||
port alarm : Json.Encode.Value -> Cmd msg
|
||||
"""
|
||||
|> Review.Test.run (valueOrTypeVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedTypeError context "Json.Encode.Value"
|
||||
, expectedTypeError context "Cmd"
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
expressionTests : List Test
|
||||
expressionTests =
|
||||
[ fuzz Fuzz.string "FunctionOrValue" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
view = Html.div [] []
|
||||
"""
|
||||
|> Review.Test.run (valueOrTypeVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedValueError context "Html.div"
|
||||
]
|
||||
, fuzz Fuzz.string "LetDestructuring" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
view =
|
||||
let
|
||||
html = Html.span [] []
|
||||
in
|
||||
html
|
||||
"""
|
||||
|> Review.Test.run (valueOrTypeVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedValueError context "Html.span"
|
||||
, expectedValueError context "html"
|
||||
|> Review.Test.atExactly { start = { row = 7, column = 5 }, end = { row = 7, column = 9 } }
|
||||
]
|
||||
, fuzz Fuzz.string "LetFunction" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
view =
|
||||
let
|
||||
html : Nested.Html msg
|
||||
html (Nested.Node name) = span [] []
|
||||
in
|
||||
html
|
||||
"""
|
||||
|> Review.Test.run (valueOrTypeVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedTypeError context "Nested.Html"
|
||||
, expectedValueError context "Nested.Node"
|
||||
, expectedValueError context "span"
|
||||
, expectedValueError context "html"
|
||||
|> Review.Test.atExactly { start = { row = 8, column = 5 }, end = { row = 8, column = 9 } }
|
||||
]
|
||||
, fuzz Fuzz.string "CaseExpression" <|
|
||||
\context ->
|
||||
"""
|
||||
module Rule exposing (visitor)
|
||||
visitor node =
|
||||
case Node.value node of
|
||||
Expression.FunctionOrValue _ _ ->
|
||||
[]
|
||||
"""
|
||||
|> Review.Test.run (valueOrTypeVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedValueError context "Node.value"
|
||||
, expectedValueError context "node"
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 21 }, end = { row = 4, column = 25 } }
|
||||
, expectedValueError context "Expression.FunctionOrValue"
|
||||
]
|
||||
, fuzz Fuzz.string "LambdaExpression" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
view nodes =
|
||||
Html.div [] (List.map (\\(Node name) -> Html.text name))
|
||||
"""
|
||||
|> Review.Test.run (valueOrTypeVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedValueError context "Html.div"
|
||||
, expectedValueError context "List.map"
|
||||
, expectedValueError context "Node"
|
||||
, expectedValueError context "Html.text"
|
||||
, expectedValueError context "name"
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 54 }, end = { row = 4, column = 58 } }
|
||||
]
|
||||
, fuzz Fuzz.string "RecordAccess" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
value model =
|
||||
model.value
|
||||
"""
|
||||
|> Review.Test.run (valueOrTypeVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedValueError context "model"
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 5 }, end = { row = 4, column = 10 } }
|
||||
]
|
||||
, fuzz Fuzz.string "RecordUpdateExpression" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
update model =
|
||||
{ model | key = value }
|
||||
"""
|
||||
|> Review.Test.run (valueOrTypeVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedValueError context "model"
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 7 }, end = { row = 4, column = 12 } }
|
||||
, expectedValueError context "value"
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
patternTests : List Test
|
||||
patternTests =
|
||||
[ fuzz Fuzz.string "TuplePattern" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
view ( Nested.Node name, Nested.Value value ) =
|
||||
Html.div [] [ Html.text (name ++ value) ]
|
||||
"""
|
||||
|> Review.Test.run (valueVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedValueError context "Nested.Node"
|
||||
, expectedValueError context "Nested.Value"
|
||||
, expectedValueError context "Html.div"
|
||||
, expectedValueError context "Html.text"
|
||||
, expectedValueError context "name"
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 30 }, end = { row = 4, column = 34 } }
|
||||
, expectedValueError context "value"
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 38 }, end = { row = 4, column = 43 } }
|
||||
]
|
||||
, fuzz Fuzz.string "UnConsPattern" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (list)
|
||||
list children =
|
||||
case children of
|
||||
(Page.First first) :: (Page.Second second) :: _ ->
|
||||
Html.text ""
|
||||
"""
|
||||
|> Review.Test.run (valueVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedValueError context "children"
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 10 }, end = { row = 4, column = 18 } }
|
||||
, expectedValueError context "Page.First"
|
||||
, expectedValueError context "Page.Second"
|
||||
, expectedValueError context "Html.text"
|
||||
]
|
||||
, fuzz Fuzz.string "ListPattern" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (list)
|
||||
list children =
|
||||
case children of
|
||||
[ Page.First first, Page.Second second ] ->
|
||||
Html.text ""
|
||||
"""
|
||||
|> Review.Test.run (valueVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedValueError context "children"
|
||||
|> Review.Test.atExactly { start = { row = 4, column = 10 }, end = { row = 4, column = 18 } }
|
||||
, expectedValueError context "Page.First"
|
||||
, expectedValueError context "Page.Second"
|
||||
, expectedValueError context "Html.text"
|
||||
]
|
||||
, fuzz Fuzz.string "NamedPattern" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
view (Node name) = Html.text name
|
||||
"""
|
||||
|> Review.Test.run (valueVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedValueError context "Node"
|
||||
, expectedValueError context "Html.text"
|
||||
, expectedValueError context "name"
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 30 }, end = { row = 3, column = 34 } }
|
||||
]
|
||||
, fuzz Fuzz.string "AsPattern" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
view ((Page.Document title _) as doc) = Html.text title
|
||||
"""
|
||||
|> Review.Test.run (valueVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedValueError context "Page.Document"
|
||||
, expectedValueError context "Html.text"
|
||||
, expectedValueError context "title"
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 51 }, end = { row = 3, column = 56 } }
|
||||
]
|
||||
, fuzz Fuzz.string "ParenthesizedPattern" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
view (Nested.Node name) = Html.text name
|
||||
"""
|
||||
|> Review.Test.run (valueVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedValueError context "Nested.Node"
|
||||
, expectedValueError context "Html.text"
|
||||
, expectedValueError context "name"
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 37 }, end = { row = 3, column = 41 } }
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
typeAnnotationTests : List Test
|
||||
typeAnnotationTests =
|
||||
[ fuzz Fuzz.string "Types" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
view : Nested.Node Page.Document msg -> Html msg
|
||||
view node = div [] []
|
||||
"""
|
||||
|> Review.Test.run (typeVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedTypeError context "Nested.Node"
|
||||
, expectedTypeError context "Page.Document"
|
||||
, expectedTypeError context "Html"
|
||||
]
|
||||
, fuzz Fuzz.string "Tupled" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (Page)
|
||||
type Page
|
||||
= Page ( Document.Title, Document.List Document.Item )
|
||||
"""
|
||||
|> Review.Test.run (typeVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedTypeError context "Document.Title"
|
||||
, expectedTypeError context "Document.List"
|
||||
, expectedTypeError context "Document.Item"
|
||||
]
|
||||
, fuzz Fuzz.string "Record" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (Page)
|
||||
type Page
|
||||
= Page { title : Document.Title, body : Document.List Document.Item }
|
||||
"""
|
||||
|> Review.Test.run (typeVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedTypeError context "Document.Title"
|
||||
, expectedTypeError context "Document.List"
|
||||
, expectedTypeError context "Document.Item"
|
||||
]
|
||||
, fuzz Fuzz.string "GenericRecord" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (Page)
|
||||
type Page
|
||||
= Page { page | title : Document.Title, body : Document.List Document.Item }
|
||||
"""
|
||||
|> Review.Test.run (typeVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedTypeError context "Document.Title"
|
||||
, expectedTypeError context "Document.List"
|
||||
, expectedTypeError context "Document.Item"
|
||||
]
|
||||
, fuzz Fuzz.string "FunctionTypeAnnotation" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (Viewer)
|
||||
type Viewer msg
|
||||
= Viewer (Document.Title -> Document.List Document.Item -> Html msg)
|
||||
"""
|
||||
|> Review.Test.run (typeVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedTypeError context "Document.Title"
|
||||
, expectedTypeError context "Document.List"
|
||||
, expectedTypeError context "Document.Item"
|
||||
, expectedTypeError context "Html"
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
visitorTests : List Test
|
||||
visitorTests =
|
||||
[ fuzz Fuzz.string "withNameVisitor" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
view : Page -> Html msg
|
||||
view page =
|
||||
Html.div [] (Page.body page)
|
||||
"""
|
||||
|> Review.Test.run (nameVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedNameError context "Page"
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 8 }, end = { row = 3, column = 12 } }
|
||||
, expectedNameError context "Html"
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 16 }, end = { row = 3, column = 20 } }
|
||||
, expectedNameError context "Html.div"
|
||||
, expectedNameError context "Page.body"
|
||||
, expectedNameError context "page"
|
||||
|> Review.Test.atExactly { start = { row = 5, column = 28 }, end = { row = 5, column = 32 } }
|
||||
]
|
||||
, fuzz Fuzz.string "withValueVisitor" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
view : Page -> Html msg
|
||||
view page =
|
||||
Html.div [] (Page.body page)
|
||||
"""
|
||||
|> Review.Test.run (valueVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedValueError context "Html.div"
|
||||
, expectedValueError context "Page.body"
|
||||
, expectedValueError context "page"
|
||||
|> Review.Test.atExactly { start = { row = 5, column = 28 }, end = { row = 5, column = 32 } }
|
||||
]
|
||||
, fuzz Fuzz.string "withTypeVisitor" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
view : Page -> Html msg
|
||||
view page =
|
||||
Html.div [] (Page.body page)
|
||||
"""
|
||||
|> Review.Test.run (typeVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedTypeError context "Page"
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 8 }, end = { row = 3, column = 12 } }
|
||||
, expectedTypeError context "Html"
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 16 }, end = { row = 3, column = 20 } }
|
||||
]
|
||||
, fuzz Fuzz.string "withValueOrTypeVisitor" <|
|
||||
\context ->
|
||||
"""
|
||||
module Page exposing (view)
|
||||
view : Page -> Html msg
|
||||
view page =
|
||||
Html.div [] (Page.body page)
|
||||
"""
|
||||
|> Review.Test.run (valueOrTypeVisitorRule context)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedTypeError context "Page"
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 8 }, end = { row = 3, column = 12 } }
|
||||
, expectedTypeError context "Html"
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 16 }, end = { row = 3, column = 20 } }
|
||||
, expectedValueError context "Html.div"
|
||||
, expectedValueError context "Page.body"
|
||||
, expectedValueError context "page"
|
||||
|> Review.Test.atExactly { start = { row = 5, column = 28 }, end = { row = 5, column = 32 } }
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
contextTests : List Test
|
||||
contextTests =
|
||||
[ test "mutates context" <|
|
||||
\_ ->
|
||||
"""
|
||||
module Page exposing (Page)
|
||||
view : Page -> Html msg
|
||||
view page =
|
||||
Html.div [] (Page.body page)
|
||||
"""
|
||||
|> Review.Test.run (countingVisitorRule 0)
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedNameError "0" "Page"
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 8 }, end = { row = 3, column = 12 } }
|
||||
, expectedNameError "1" "Html"
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 16 }, end = { row = 3, column = 20 } }
|
||||
, expectedNameError "2" "Html.div"
|
||||
, expectedNameError "3" "Page.body"
|
||||
, expectedNameError "4" "page"
|
||||
|> Review.Test.atExactly { start = { row = 5, column = 28 }, end = { row = 5, column = 32 } }
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
projectRuleTests : List Test
|
||||
projectRuleTests =
|
||||
[ test "works in a project rule schema" <|
|
||||
\() ->
|
||||
let
|
||||
rule : Rule
|
||||
rule =
|
||||
Rule.newProjectRuleSchema "TestProjectRule" ()
|
||||
|> Rule.withModuleVisitor
|
||||
(\schema ->
|
||||
schema
|
||||
|> NameVisitor.withNameVisitor nameVisitor
|
||||
)
|
||||
|> Rule.withModuleContext
|
||||
{ foldProjectContexts = \() () -> ()
|
||||
, fromProjectToModule = \_ _ () -> "context"
|
||||
, fromModuleToProject = \_ _ _ -> ()
|
||||
}
|
||||
|> Rule.fromProjectRuleSchema
|
||||
in
|
||||
"""
|
||||
module A exposing (..)
|
||||
foo = bar
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ expectedNameError "context" "bar"
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
|
||||
-- TEST HELPERS
|
||||
|
||||
|
||||
expectedNameError : String -> String -> Review.Test.ExpectedError
|
||||
expectedNameError context name =
|
||||
Review.Test.error
|
||||
{ message = "Error for name `" ++ name ++ "`."
|
||||
, details = [ "The context of this error was:", context ]
|
||||
, under = name
|
||||
}
|
||||
|
||||
|
||||
expectedValueError : String -> String -> Review.Test.ExpectedError
|
||||
expectedValueError context name =
|
||||
Review.Test.error
|
||||
{ message = "Error for value `" ++ name ++ "`."
|
||||
, details = [ "The context of this error was:", context ]
|
||||
, under = name
|
||||
}
|
||||
|
||||
|
||||
expectedTypeError : String -> String -> Review.Test.ExpectedError
|
||||
expectedTypeError context name =
|
||||
Review.Test.error
|
||||
{ message = "Error for type `" ++ name ++ "`."
|
||||
, details = [ "The context of this error was:", context ]
|
||||
, under = name
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- RULE HELPERS
|
||||
|
||||
|
||||
nameVisitorRule : String -> Rule
|
||||
nameVisitorRule context =
|
||||
Rule.newModuleRuleSchema "NameVisitor" context
|
||||
|> NameVisitor.withNameVisitor nameVisitor
|
||||
|> Rule.fromModuleRuleSchema
|
||||
|
||||
|
||||
valueOrTypeVisitorRule : String -> Rule
|
||||
valueOrTypeVisitorRule context =
|
||||
Rule.newModuleRuleSchema "ValueOrTypeVisitor" context
|
||||
|> NameVisitor.withValueAndTypeVisitors
|
||||
{ valueVisitor = valueVisitor
|
||||
, typeVisitor = typeVisitor
|
||||
}
|
||||
|> Rule.fromModuleRuleSchema
|
||||
|
||||
|
||||
valueVisitorRule : String -> Rule
|
||||
valueVisitorRule context =
|
||||
Rule.newModuleRuleSchema "ValueVisitor" context
|
||||
|> NameVisitor.withValueVisitor valueVisitor
|
||||
|> Rule.fromModuleRuleSchema
|
||||
|
||||
|
||||
typeVisitorRule : String -> Rule
|
||||
typeVisitorRule context =
|
||||
Rule.newModuleRuleSchema "TypeVisitor" context
|
||||
|> NameVisitor.withTypeVisitor typeVisitor
|
||||
|> Rule.fromModuleRuleSchema
|
||||
|
||||
|
||||
nameVisitor : Node ( ModuleName, String ) -> String -> ( List (Error {}), String )
|
||||
nameVisitor node context =
|
||||
( [ nameError context node ], context )
|
||||
|
||||
|
||||
valueVisitor : Node ( ModuleName, String ) -> String -> ( List (Error {}), String )
|
||||
valueVisitor node context =
|
||||
( [ valueError context node ], context )
|
||||
|
||||
|
||||
typeVisitor : Node ( ModuleName, String ) -> String -> ( List (Error {}), String )
|
||||
typeVisitor node context =
|
||||
( [ typeError context node ], context )
|
||||
|
||||
|
||||
countingVisitorRule : Int -> Rule
|
||||
countingVisitorRule counter =
|
||||
Rule.newModuleRuleSchema "NameVisitor" counter
|
||||
|> NameVisitor.withNameVisitor countingVisitor
|
||||
|> Rule.fromModuleRuleSchema
|
||||
|
||||
|
||||
countingVisitor : Node ( ModuleName, String ) -> Int -> ( List (Error {}), Int )
|
||||
countingVisitor node counter =
|
||||
( [ nameError (String.fromInt counter) node ], counter + 1 )
|
||||
|
||||
|
||||
nameError : String -> Node ( ModuleName, String ) -> Error {}
|
||||
nameError context node =
|
||||
Rule.error
|
||||
{ message = "Error for name `" ++ formatNode node ++ "`."
|
||||
, details = [ "The context of this error was:", context ]
|
||||
}
|
||||
(Node.range node)
|
||||
|
||||
|
||||
valueError : String -> Node ( ModuleName, String ) -> Error {}
|
||||
valueError context node =
|
||||
Rule.error
|
||||
{ message = "Error for value `" ++ formatNode node ++ "`."
|
||||
, details = [ "The context of this error was:", context ]
|
||||
}
|
||||
(Node.range node)
|
||||
|
||||
|
||||
typeError : String -> Node ( ModuleName, String ) -> Error {}
|
||||
typeError context node =
|
||||
Rule.error
|
||||
{ message = "Error for type `" ++ formatNode node ++ "`."
|
||||
, details = [ "The context of this error was:", context ]
|
||||
}
|
||||
(Node.range node)
|
||||
|
||||
|
||||
formatNode : Node ( ModuleName, String ) -> String
|
||||
formatNode node =
|
||||
node
|
||||
|> Node.value
|
||||
|> formatTuple
|
||||
|
||||
|
||||
formatTuple : ( ModuleName, String ) -> String
|
||||
formatTuple ( moduleName, name ) =
|
||||
String.join "." (moduleName ++ [ name ])
|
@ -1,4 +1,4 @@
|
||||
module NoLeftPizzaUtil exposing (expressionToString)
|
||||
module Util exposing (expressionToString)
|
||||
|
||||
import Elm.Syntax.Expression as Expression exposing (Expression)
|
||||
import Elm.Syntax.Node as Node exposing (Node)
|
475
tests/Vendor/NameVisitor.elm
vendored
Normal file
475
tests/Vendor/NameVisitor.elm
vendored
Normal file
@ -0,0 +1,475 @@
|
||||
module Vendor.NameVisitor exposing (withNameVisitor, withValueVisitor, withTypeVisitor, withValueAndTypeVisitors)
|
||||
|
||||
{-| Visit each name in the module.
|
||||
|
||||
A "name" is a `Node ( ModuleName, String )` and represents a value or type reference. Here are some examples:
|
||||
|
||||
- `Json.Encode.Value` -> `( [ "Json", "Encode" ], "Value" )`
|
||||
- `Html.Attributes.class` -> `( [ "Html", "Attributes" ], "class" )`
|
||||
- `Page` -> `( [], "Page" )`
|
||||
- `view` -> `( [], "view" )`
|
||||
|
||||
These can appear in many places throughout declarations and expressions, and picking them out each time is a lot of work. Instead of writing 1000 lines of code and tests each time, you can write one `nameVisitor` and plug it straight into your module schema, or separate `valueVisitor` and `typeVisitor`s.
|
||||
|
||||
@docs withNameVisitor, withValueVisitor, withTypeVisitor, withValueAndTypeVisitors
|
||||
|
||||
|
||||
## Scope
|
||||
|
||||
This makes no attempt to resolve module names from imports, it just returns what's written in the code. It would be trivial to connect [elm-review-scope] with the name visitor if you want to do this.
|
||||
|
||||
[elm-review-scope]: http://github.com/jfmengels/elm-review-scope/
|
||||
|
||||
|
||||
## Version
|
||||
|
||||
Version: 0.3.0
|
||||
|
||||
-}
|
||||
|
||||
import Elm.Syntax.Declaration as Declaration exposing (Declaration)
|
||||
import Elm.Syntax.Expression as Expression exposing (Expression)
|
||||
import Elm.Syntax.ModuleName exposing (ModuleName)
|
||||
import Elm.Syntax.Node as Node exposing (Node(..))
|
||||
import Elm.Syntax.Pattern as Pattern exposing (Pattern)
|
||||
import Elm.Syntax.Signature exposing (Signature)
|
||||
import Elm.Syntax.Type as Type
|
||||
import Elm.Syntax.TypeAnnotation as TypeAnnotation exposing (TypeAnnotation)
|
||||
import Review.Rule as Rule exposing (Error)
|
||||
|
||||
|
||||
type Visitor context
|
||||
= NameVisitor (VisitorFunction context)
|
||||
| ValueVisitor (VisitorFunction context)
|
||||
| TypeVisitor (VisitorFunction context)
|
||||
| ValueAndTypeVisitor (VisitorFunction context) (VisitorFunction context)
|
||||
|
||||
|
||||
type alias VisitorFunction context =
|
||||
Node ( ModuleName, String ) -> context -> ( List (Error {}), context )
|
||||
|
||||
|
||||
type Name
|
||||
= Value (Node ( ModuleName, String ))
|
||||
| Type (Node ( ModuleName, String ))
|
||||
|
||||
|
||||
{-| This will apply the `nameVisitor` to every value and type in the module, you will get no information about whether the name is a value or type.
|
||||
|
||||
rule : Rule
|
||||
rule =
|
||||
Rule.newModuleRuleSchema "NoInconsistentAliases" initialContext
|
||||
|> NameVisitor.withNameVisitor nameVisitor
|
||||
|> Rule.fromModuleRuleSchema
|
||||
|
||||
nameVisitor : Node ( ModuleName, String ) -> context -> ( List (Error {}), context )
|
||||
nameVisitor node context =
|
||||
-- Do what you want with the name
|
||||
( [], context )
|
||||
|
||||
-}
|
||||
withNameVisitor :
|
||||
(Node ( ModuleName, String ) -> context -> ( List (Error {}), context ))
|
||||
-> Rule.ModuleRuleSchema state context
|
||||
-> Rule.ModuleRuleSchema { state | hasAtLeastOneVisitor : () } context
|
||||
withNameVisitor nameVisitor rule =
|
||||
let
|
||||
visitor =
|
||||
NameVisitor nameVisitor
|
||||
in
|
||||
rule
|
||||
|> Rule.withDeclarationListVisitor (declarationListVisitor visitor)
|
||||
|> Rule.withExpressionEnterVisitor (expressionVisitor visitor)
|
||||
|
||||
|
||||
{-| This will apply the `valueVisitor` to every value in the module, and ignore any types.
|
||||
|
||||
rule : Rule
|
||||
rule =
|
||||
Rule.newModuleRuleSchema "NoInconsistentAliases" initialContext
|
||||
|> NameVisitor.withValueVisitor valueVisitor
|
||||
|> Rule.fromModuleRuleSchema
|
||||
|
||||
valueVisitor : Node ( ModuleName, String ) -> context -> ( List (Error {}), context )
|
||||
valueVisitor node context =
|
||||
-- Do what you want with the value
|
||||
( [], context )
|
||||
|
||||
-}
|
||||
withValueVisitor :
|
||||
(Node ( ModuleName, String ) -> context -> ( List (Error {}), context ))
|
||||
-> Rule.ModuleRuleSchema state context
|
||||
-> Rule.ModuleRuleSchema { state | hasAtLeastOneVisitor : () } context
|
||||
withValueVisitor valueVisitor rule =
|
||||
let
|
||||
visitor =
|
||||
ValueVisitor valueVisitor
|
||||
in
|
||||
rule
|
||||
|> Rule.withDeclarationListVisitor (declarationListVisitor visitor)
|
||||
|> Rule.withExpressionEnterVisitor (expressionVisitor visitor)
|
||||
|
||||
|
||||
{-| This will apply the `typeVisitor` to every type in the module, and ignore any values.
|
||||
|
||||
rule : Rule
|
||||
rule =
|
||||
Rule.newModuleRuleSchema "NoInconsistentAliases" initialContext
|
||||
|> NameVisitor.withTypeVisitor typeVisitor
|
||||
|> Rule.fromModuleRuleSchema
|
||||
|
||||
typeVisitor : Node ( ModuleName, String ) -> context -> ( List (Error {}), context )
|
||||
typeVisitor node context =
|
||||
-- Do what you want with the type
|
||||
( [], context )
|
||||
|
||||
-}
|
||||
withTypeVisitor :
|
||||
(Node ( ModuleName, String ) -> context -> ( List (Error {}), context ))
|
||||
-> Rule.ModuleRuleSchema state context
|
||||
-> Rule.ModuleRuleSchema { state | hasAtLeastOneVisitor : () } context
|
||||
withTypeVisitor typeVisitor rule =
|
||||
let
|
||||
visitor =
|
||||
TypeVisitor typeVisitor
|
||||
in
|
||||
rule
|
||||
|> Rule.withDeclarationListVisitor (declarationListVisitor visitor)
|
||||
|> Rule.withExpressionEnterVisitor (expressionVisitor visitor)
|
||||
|
||||
|
||||
{-| This will apply the `valueVisitor` to every value and the `typeVisitor` to every type in the module.
|
||||
|
||||
rule : Rule
|
||||
rule =
|
||||
Rule.newModuleRuleSchema "NoInconsistentAliases" initialContext
|
||||
|> NameVisitor.withValueAndTypeVisitors
|
||||
{ valueVisitor = valueVisitor
|
||||
, typeVisitor = typeVisitor
|
||||
}
|
||||
|> Rule.fromModuleRuleSchema
|
||||
|
||||
valueVisitor : Node ( ModuleName, String ) -> context -> ( List (Error {}), context )
|
||||
valueVisitor node context =
|
||||
-- Do what you want with the value
|
||||
( [], context )
|
||||
|
||||
typeVisitor : Node ( ModuleName, String ) -> context -> ( List (Error {}), context )
|
||||
typeVisitor node context =
|
||||
-- Do what you want with the type
|
||||
( [], context )
|
||||
|
||||
-}
|
||||
withValueAndTypeVisitors :
|
||||
{ valueVisitor : Node ( ModuleName, String ) -> context -> ( List (Error {}), context )
|
||||
, typeVisitor : Node ( ModuleName, String ) -> context -> ( List (Error {}), context )
|
||||
}
|
||||
-> Rule.ModuleRuleSchema state context
|
||||
-> Rule.ModuleRuleSchema { state | hasAtLeastOneVisitor : () } context
|
||||
withValueAndTypeVisitors { valueVisitor, typeVisitor } rule =
|
||||
let
|
||||
visitor =
|
||||
ValueAndTypeVisitor valueVisitor typeVisitor
|
||||
in
|
||||
rule
|
||||
|> Rule.withDeclarationListVisitor (declarationListVisitor visitor)
|
||||
|> Rule.withExpressionEnterVisitor (expressionVisitor visitor)
|
||||
|
||||
|
||||
|
||||
--- VISITORS
|
||||
|
||||
|
||||
declarationListVisitor :
|
||||
Visitor context
|
||||
-> (List (Node Declaration) -> context -> ( List (Error {}), context ))
|
||||
declarationListVisitor visitor list context =
|
||||
visitDeclarationList list
|
||||
|> folder visitor context
|
||||
|
||||
|
||||
expressionVisitor : Visitor context -> (Node Expression -> context -> ( List (Error {}), context ))
|
||||
expressionVisitor visitor node context =
|
||||
visitExpression node
|
||||
|> folder visitor context
|
||||
|
||||
|
||||
|
||||
--- FOLDER
|
||||
|
||||
|
||||
folder :
|
||||
Visitor context
|
||||
-> context
|
||||
-> List Name
|
||||
-> ( List (Error {}), context )
|
||||
folder visitor context list =
|
||||
List.foldl (folderHelper visitor) ( [], context ) list
|
||||
|
||||
|
||||
folderHelper :
|
||||
Visitor context
|
||||
-> Name
|
||||
-> ( List (Error {}), context )
|
||||
-> ( List (Error {}), context )
|
||||
folderHelper visitor name ( errors, context ) =
|
||||
let
|
||||
( newErrors, newContext ) =
|
||||
applyVisitor visitor name context
|
||||
in
|
||||
( newErrors ++ errors, newContext )
|
||||
|
||||
|
||||
applyVisitor : Visitor context -> Name -> context -> ( List (Error {}), context )
|
||||
applyVisitor visitor name context =
|
||||
case name of
|
||||
Value node ->
|
||||
applyValueVisitor visitor node context
|
||||
|
||||
Type node ->
|
||||
applyTypeVisitor visitor node context
|
||||
|
||||
|
||||
applyValueVisitor : Visitor context -> VisitorFunction context
|
||||
applyValueVisitor visitor =
|
||||
case visitor of
|
||||
NameVisitor function ->
|
||||
function
|
||||
|
||||
ValueVisitor function ->
|
||||
function
|
||||
|
||||
TypeVisitor _ ->
|
||||
noopVisitor
|
||||
|
||||
ValueAndTypeVisitor function _ ->
|
||||
function
|
||||
|
||||
|
||||
applyTypeVisitor : Visitor context -> VisitorFunction context
|
||||
applyTypeVisitor visitor =
|
||||
case visitor of
|
||||
NameVisitor function ->
|
||||
function
|
||||
|
||||
ValueVisitor _ ->
|
||||
noopVisitor
|
||||
|
||||
TypeVisitor function ->
|
||||
function
|
||||
|
||||
ValueAndTypeVisitor _ function ->
|
||||
function
|
||||
|
||||
|
||||
noopVisitor : VisitorFunction context
|
||||
noopVisitor _ context =
|
||||
( [], context )
|
||||
|
||||
|
||||
|
||||
--- PRIVATE
|
||||
|
||||
|
||||
visitDeclarationList : List (Node Declaration) -> List Name
|
||||
visitDeclarationList nodes =
|
||||
fastConcatMap visitDeclaration nodes
|
||||
|
||||
|
||||
visitDeclaration : Node Declaration -> List Name
|
||||
visitDeclaration node =
|
||||
case Node.value node of
|
||||
Declaration.FunctionDeclaration { signature, declaration } ->
|
||||
visitMaybeSignature signature
|
||||
++ visitFunctionImplementation declaration
|
||||
|
||||
Declaration.AliasDeclaration { typeAnnotation } ->
|
||||
visitTypeAnnotation typeAnnotation
|
||||
|
||||
Declaration.CustomTypeDeclaration { constructors } ->
|
||||
visitValueConstructorList constructors
|
||||
|
||||
Declaration.PortDeclaration { typeAnnotation } ->
|
||||
visitTypeAnnotation typeAnnotation
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
|
||||
visitMaybeSignature : Maybe (Node Signature) -> List Name
|
||||
visitMaybeSignature maybeNode =
|
||||
case maybeNode of
|
||||
Just node ->
|
||||
visitSignature node
|
||||
|
||||
Nothing ->
|
||||
[]
|
||||
|
||||
|
||||
visitSignature : Node Signature -> List Name
|
||||
visitSignature node =
|
||||
visitTypeAnnotation (node |> Node.value |> .typeAnnotation)
|
||||
|
||||
|
||||
visitFunctionImplementation : Node Expression.FunctionImplementation -> List Name
|
||||
visitFunctionImplementation node =
|
||||
visitPatternList (node |> Node.value |> .arguments)
|
||||
|
||||
|
||||
visitValueConstructorList : List (Node Type.ValueConstructor) -> List Name
|
||||
visitValueConstructorList list =
|
||||
fastConcatMap visitValueConstructor list
|
||||
|
||||
|
||||
visitValueConstructor : Node Type.ValueConstructor -> List Name
|
||||
visitValueConstructor node =
|
||||
visitTypeAnnotationList (node |> Node.value |> .arguments)
|
||||
|
||||
|
||||
visitTypeAnnotationList : List (Node TypeAnnotation) -> List Name
|
||||
visitTypeAnnotationList list =
|
||||
fastConcatMap visitTypeAnnotation list
|
||||
|
||||
|
||||
visitTypeAnnotation : Node TypeAnnotation -> List Name
|
||||
visitTypeAnnotation node =
|
||||
case Node.value node of
|
||||
TypeAnnotation.GenericType _ ->
|
||||
[]
|
||||
|
||||
TypeAnnotation.Typed call types ->
|
||||
visitType call
|
||||
++ visitTypeAnnotationList types
|
||||
|
||||
TypeAnnotation.Unit ->
|
||||
[]
|
||||
|
||||
TypeAnnotation.Tupled list ->
|
||||
visitTypeAnnotationList list
|
||||
|
||||
TypeAnnotation.Record list ->
|
||||
visitRecordFieldList list
|
||||
|
||||
TypeAnnotation.GenericRecord _ list ->
|
||||
visitRecordFieldList (Node.value list)
|
||||
|
||||
TypeAnnotation.FunctionTypeAnnotation argument return ->
|
||||
visitTypeAnnotation argument
|
||||
++ visitTypeAnnotation return
|
||||
|
||||
|
||||
visitRecordFieldList : List (Node TypeAnnotation.RecordField) -> List Name
|
||||
visitRecordFieldList list =
|
||||
fastConcatMap visitRecordField list
|
||||
|
||||
|
||||
visitRecordField : Node TypeAnnotation.RecordField -> List Name
|
||||
visitRecordField node =
|
||||
visitTypeAnnotation (node |> Node.value |> Tuple.second)
|
||||
|
||||
|
||||
visitExpression : Node Expression -> List Name
|
||||
visitExpression (Node range expression) =
|
||||
case expression of
|
||||
Expression.FunctionOrValue moduleName function ->
|
||||
visitValue (Node range ( moduleName, function ))
|
||||
|
||||
Expression.LetExpression { declarations } ->
|
||||
visitLetDeclarationList declarations
|
||||
|
||||
Expression.CaseExpression { cases } ->
|
||||
visitCaseList cases
|
||||
|
||||
Expression.LambdaExpression { args } ->
|
||||
visitPatternList args
|
||||
|
||||
Expression.RecordUpdateExpression name _ ->
|
||||
visitValue (Node.map (\function -> ( [], function )) name)
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
|
||||
visitLetDeclarationList : List (Node Expression.LetDeclaration) -> List Name
|
||||
visitLetDeclarationList list =
|
||||
fastConcatMap visitLetDeclaration list
|
||||
|
||||
|
||||
visitLetDeclaration : Node Expression.LetDeclaration -> List Name
|
||||
visitLetDeclaration node =
|
||||
case Node.value node of
|
||||
Expression.LetFunction { signature, declaration } ->
|
||||
visitMaybeSignature signature
|
||||
++ visitFunctionImplementation declaration
|
||||
|
||||
Expression.LetDestructuring pattern _ ->
|
||||
visitPattern pattern
|
||||
|
||||
|
||||
visitCaseList : List Expression.Case -> List Name
|
||||
visitCaseList list =
|
||||
fastConcatMap visitCase list
|
||||
|
||||
|
||||
visitCase : Expression.Case -> List Name
|
||||
visitCase ( pattern, _ ) =
|
||||
visitPattern pattern
|
||||
|
||||
|
||||
visitPatternList : List (Node Pattern) -> List Name
|
||||
visitPatternList list =
|
||||
fastConcatMap visitPattern list
|
||||
|
||||
|
||||
visitPattern : Node Pattern -> List Name
|
||||
visitPattern node =
|
||||
case Node.value node of
|
||||
Pattern.TuplePattern patterns ->
|
||||
visitPatternList patterns
|
||||
|
||||
Pattern.UnConsPattern head rest ->
|
||||
visitPattern head ++ visitPattern rest
|
||||
|
||||
Pattern.ListPattern list ->
|
||||
visitPatternList list
|
||||
|
||||
Pattern.NamedPattern { moduleName, name } _ ->
|
||||
let
|
||||
{ start } =
|
||||
Node.range node
|
||||
|
||||
newEnd =
|
||||
{ start | column = start.column + (name :: moduleName |> String.join "." |> String.length) }
|
||||
|
||||
range =
|
||||
{ start = start, end = newEnd }
|
||||
in
|
||||
visitValue (Node range ( moduleName, name ))
|
||||
|
||||
Pattern.AsPattern pattern _ ->
|
||||
visitPattern pattern
|
||||
|
||||
Pattern.ParenthesizedPattern pattern ->
|
||||
visitPattern pattern
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
|
||||
visitValue : Node ( ModuleName, String ) -> List Name
|
||||
visitValue node =
|
||||
[ Value node ]
|
||||
|
||||
|
||||
visitType : Node ( ModuleName, String ) -> List Name
|
||||
visitType node =
|
||||
[ Type node ]
|
||||
|
||||
|
||||
|
||||
--- High Performance List
|
||||
|
||||
|
||||
fastConcatMap : (a -> List b) -> List a -> List b
|
||||
fastConcatMap fn =
|
||||
List.foldr (fn >> (++)) []
|
Loading…
Reference in New Issue
Block a user