mirror of
https://github.com/jfmengels/elm-review.git
synced 2024-11-27 12:08:51 +03:00
Remove rules, to ease upgrade
This commit is contained in:
parent
4229a8262d
commit
67f1e8223f
16
elm.json
16
elm.json
@ -7,21 +7,7 @@
|
||||
"exposed-modules": [
|
||||
"Lint",
|
||||
"Lint.Types",
|
||||
"Lint.Rules.DefaultPatternPosition",
|
||||
"Lint.Rules.NoConstantCondition",
|
||||
"Lint.Rules.NoDebug",
|
||||
"Lint.Rules.NoDuplicateImports",
|
||||
"Lint.Rules.NoExposingEverything",
|
||||
"Lint.Rules.NoImportingEverything",
|
||||
"Lint.Rules.NoNestedLet",
|
||||
"Lint.Rules.NoUnannotatedFunction",
|
||||
"Lint.Rules.NoUnusedVariables",
|
||||
"Lint.Rules.NoUselessIf",
|
||||
"Lint.Rules.NoUselessPatternMatching",
|
||||
"Lint.Rules.NoWarningComments",
|
||||
"Lint.Rules.SimplifyPiping",
|
||||
"Lint.Rules.SimplifyPropertyAccess",
|
||||
"Lint.Rules.ElmTest.NoDuplicateTestBodies"
|
||||
"Lint.Rules.NoDebug"
|
||||
],
|
||||
"elm-version": "0.19.0 <= v < 0.20.0",
|
||||
"dependencies": {
|
||||
|
@ -1,158 +0,0 @@
|
||||
module Lint.Rules.DefaultPatternPosition exposing (rule, Configuration, PatternPosition(..))
|
||||
|
||||
{-|
|
||||
|
||||
@docs rule, Configuration, PatternPosition
|
||||
|
||||
|
||||
# Fail
|
||||
|
||||
rules =
|
||||
[ DefaultPatternPosition.rule { position = Lint.Rules.DefaultPatternPosition.Last }
|
||||
]
|
||||
|
||||
case value of
|
||||
-- LintError, this pattern should appear last
|
||||
_ -> result
|
||||
Foo -> bar
|
||||
|
||||
-- --------------------
|
||||
|
||||
rules =
|
||||
[ DefaultPatternPosition.rule { position = Lint.Rules.DefaultPatternPosition.First }
|
||||
]
|
||||
|
||||
case value of
|
||||
Foo -> bar
|
||||
-- LintError, this pattern should appear first
|
||||
_ -> result
|
||||
|
||||
|
||||
# Success
|
||||
|
||||
rules =
|
||||
[ DefaultPatternPosition.rule { position = Lint.Rules.DefaultPatternPosition.Last }
|
||||
]
|
||||
|
||||
case value of
|
||||
Foo -> bar
|
||||
_ -> result
|
||||
|
||||
case value of
|
||||
-- No default pattern
|
||||
Foo -> bar
|
||||
Bar -> foo
|
||||
|
||||
-- --------------------
|
||||
|
||||
rules =
|
||||
[ DefaultPatternPosition.rule { position = Lint.Rules.DefaultPatternPosition.First }
|
||||
]
|
||||
|
||||
case value of
|
||||
_ -> result
|
||||
Foo -> bar
|
||||
|
||||
-}
|
||||
|
||||
import Ast.Expression exposing (..)
|
||||
import Lint exposing (doNothing, lint)
|
||||
import Lint.Types exposing (Direction(..), LintError, LintRule, LintRuleImplementation)
|
||||
import List.Extra exposing (findIndex)
|
||||
import Regex
|
||||
|
||||
|
||||
type alias Context =
|
||||
{}
|
||||
|
||||
|
||||
{-| Configures whether the default pattern should appear first or last.
|
||||
-}
|
||||
type PatternPosition
|
||||
= First
|
||||
| Last
|
||||
|
||||
|
||||
{-| Configuration for the rule.
|
||||
-}
|
||||
type alias Configuration =
|
||||
{ position : PatternPosition
|
||||
}
|
||||
|
||||
|
||||
{-| Enforce the default pattern to always appear first or last.
|
||||
-}
|
||||
rule : Configuration -> LintRule
|
||||
rule config input =
|
||||
lint input (implementation config)
|
||||
|
||||
|
||||
implementation : Configuration -> LintRuleImplementation Context
|
||||
implementation configuration =
|
||||
{ statementFn = doNothing
|
||||
, typeFn = doNothing
|
||||
, expressionFn = expressionFn configuration
|
||||
, moduleEndFn = \ctx -> ( [], ctx )
|
||||
, initialContext = Context
|
||||
}
|
||||
|
||||
|
||||
error : String -> LintError
|
||||
error =
|
||||
LintError "DefaultPatternPosition"
|
||||
|
||||
|
||||
|
||||
{- TODO Share isVariable this in a util file, already defined in NoUselessPatternMatching -}
|
||||
|
||||
|
||||
isVariable : String -> Bool
|
||||
isVariable =
|
||||
Regex.contains (Regex.regex "^[_a-z][\\w\\d]*$")
|
||||
|
||||
|
||||
isDefaultPattern : ( Expression, Expression ) -> Bool
|
||||
isDefaultPattern pattern =
|
||||
case Tuple.first pattern of
|
||||
Variable names ->
|
||||
if isVariable (String.join "." names) then
|
||||
True
|
||||
|
||||
else
|
||||
False
|
||||
|
||||
_ ->
|
||||
False
|
||||
|
||||
|
||||
findDefaultPattern : List ( Expression, Expression ) -> Maybe Int
|
||||
findDefaultPattern =
|
||||
findIndex isDefaultPattern
|
||||
|
||||
|
||||
expressionFn : Configuration -> Context -> Direction Expression -> ( List LintError, Context )
|
||||
expressionFn config ctx node =
|
||||
case node of
|
||||
Enter (Case expr patterns) ->
|
||||
case findDefaultPattern patterns of
|
||||
Nothing ->
|
||||
( [], ctx )
|
||||
|
||||
Just index ->
|
||||
case config.position of
|
||||
First ->
|
||||
if index /= 0 then
|
||||
( [ error "Expected default pattern to appear first in the list of patterns" ], ctx )
|
||||
|
||||
else
|
||||
( [], ctx )
|
||||
|
||||
Last ->
|
||||
if index /= List.length patterns - 1 then
|
||||
( [ error "Expected default pattern to appear last in the list of patterns" ], ctx )
|
||||
|
||||
else
|
||||
( [], ctx )
|
||||
|
||||
_ ->
|
||||
( [], ctx )
|
@ -1,194 +0,0 @@
|
||||
module Lint.Rules.ElmTest.NoDuplicateTestBodies exposing (rule)
|
||||
|
||||
{-|
|
||||
|
||||
@docs rule
|
||||
|
||||
|
||||
# Fail
|
||||
|
||||
import Test exposing (test)
|
||||
|
||||
tests =
|
||||
[ test "foo" <|
|
||||
\() ->
|
||||
1
|
||||
+ 1
|
||||
|> Expect.equal 2
|
||||
, test "bar" <|
|
||||
\() ->
|
||||
1
|
||||
+ 1
|
||||
|> Expect.equal 2
|
||||
]
|
||||
|
||||
|
||||
# Success
|
||||
|
||||
import Test exposing (test)
|
||||
|
||||
tests =
|
||||
[ test "foo" <|
|
||||
\() ->
|
||||
1
|
||||
+ 1
|
||||
|> Expect.equal 2
|
||||
, test "bar" <|
|
||||
\() ->
|
||||
1
|
||||
+ 2
|
||||
|> Expect.equal 3
|
||||
]
|
||||
|
||||
-}
|
||||
|
||||
import Ast.Expression exposing (..)
|
||||
import Ast.Statement exposing (..)
|
||||
import Dict exposing (Dict)
|
||||
import Lint exposing (doNothing, lint)
|
||||
import Lint.Types exposing (Direction(..), LintError, LintRule, LintRuleImplementation)
|
||||
|
||||
|
||||
type alias Context =
|
||||
{ availableTestAliases : List String
|
||||
}
|
||||
|
||||
|
||||
{-| Forbid dupicate test bodies.
|
||||
When copy-pasting tests, it can happen that the title is changed but the developer forgets to update the test body.
|
||||
This may result in specifications that are thought to be implemented but are not enforced by tests.
|
||||
|
||||
rules =
|
||||
[ ElmTest.NoDuplicateTestBodies.rule
|
||||
]
|
||||
|
||||
-}
|
||||
rule : LintRule
|
||||
rule input =
|
||||
lint input implementation
|
||||
|
||||
|
||||
implementation : LintRuleImplementation Context
|
||||
implementation =
|
||||
{ statementFn = statementFn
|
||||
, typeFn = doNothing
|
||||
, expressionFn = expressionFn
|
||||
, moduleEndFn = \ctx -> ( [], ctx )
|
||||
, initialContext = Context []
|
||||
}
|
||||
|
||||
|
||||
error : ( String, String ) -> LintError
|
||||
error ( title1, title2 ) =
|
||||
LintError
|
||||
"ElmTest.NoDuplicateTestBodies"
|
||||
("Test `" ++ title1 ++ "` has the same body as test `" ++ title2 ++ "`")
|
||||
|
||||
|
||||
isTestFunctionCall : List String -> Expression -> Bool
|
||||
isTestFunctionCall availableTestAliases expr =
|
||||
case expr of
|
||||
Variable fnName ->
|
||||
List.member (String.join "." fnName) availableTestAliases
|
||||
|
||||
Access (Variable object) fields ->
|
||||
List.member (String.join "." <| object ++ fields) availableTestAliases
|
||||
|
||||
_ ->
|
||||
False
|
||||
|
||||
|
||||
filterTests : List String -> List Expression -> List ( String, Expression )
|
||||
filterTests availableTestAliases listItems =
|
||||
List.concatMap
|
||||
(\item ->
|
||||
case item of
|
||||
BinOp (Variable [ "<|" ]) (Application fn (String title)) testBody ->
|
||||
if isTestFunctionCall availableTestAliases fn then
|
||||
[ ( title, testBody ) ]
|
||||
|
||||
else
|
||||
[]
|
||||
|
||||
_ ->
|
||||
[]
|
||||
)
|
||||
listItems
|
||||
|
||||
|
||||
expressionFn : Context -> Direction Expression -> ( List LintError, Context )
|
||||
expressionFn ctx node =
|
||||
case node of
|
||||
Enter (List listItems) ->
|
||||
let
|
||||
tests =
|
||||
filterTests ctx.availableTestAliases listItems
|
||||
|
||||
redundantTests =
|
||||
List.foldl
|
||||
(\( title, testBody ) { dict, redundant } ->
|
||||
let
|
||||
testBodyAsString =
|
||||
toString testBody
|
||||
|
||||
existingTest =
|
||||
Dict.get testBodyAsString dict
|
||||
in
|
||||
case existingTest of
|
||||
Nothing ->
|
||||
{ dict = Dict.insert testBodyAsString title dict, redundant = redundant }
|
||||
|
||||
Just existingTestTitle ->
|
||||
{ dict = dict, redundant = redundant ++ [ ( title, existingTestTitle ) ] }
|
||||
)
|
||||
{ dict = Dict.empty, redundant = [] }
|
||||
tests
|
||||
in
|
||||
( List.map error redundantTests.redundant, ctx )
|
||||
|
||||
_ ->
|
||||
( [], ctx )
|
||||
|
||||
|
||||
extractImported : ExportSet -> List String
|
||||
extractImported exportSet =
|
||||
case exportSet of
|
||||
AllExport ->
|
||||
[ "test" ]
|
||||
|
||||
SubsetExport list ->
|
||||
List.concatMap extractImported list
|
||||
|
||||
FunctionExport name ->
|
||||
if name == "test" then
|
||||
[ name ]
|
||||
|
||||
else
|
||||
[]
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
|
||||
computeAlias : Maybe String -> String
|
||||
computeAlias =
|
||||
Maybe.withDefault "Test"
|
||||
|
||||
|
||||
statementFn : Context -> Direction Statement -> ( List LintError, Context )
|
||||
statementFn ctx node =
|
||||
case node of
|
||||
Enter (ImportStatement [ "Test" ] testAlias exportSet) ->
|
||||
let
|
||||
moduleFnAccess =
|
||||
computeAlias testAlias ++ ".test"
|
||||
in
|
||||
case exportSet of
|
||||
Nothing ->
|
||||
( [], { availableTestAliases = [ moduleFnAccess ] } )
|
||||
|
||||
Just subExportSet ->
|
||||
( [], { availableTestAliases = [ moduleFnAccess ] ++ extractImported subExportSet } )
|
||||
|
||||
_ ->
|
||||
( [], ctx )
|
@ -1,132 +0,0 @@
|
||||
module Lint.Rules.NoConstantCondition exposing (rule)
|
||||
|
||||
{-|
|
||||
|
||||
@docs rule
|
||||
|
||||
|
||||
# Fail
|
||||
|
||||
if True then
|
||||
a
|
||||
|
||||
else
|
||||
b
|
||||
|
||||
if False then
|
||||
a
|
||||
|
||||
else
|
||||
b
|
||||
|
||||
if foo == foo then
|
||||
a
|
||||
|
||||
else
|
||||
b
|
||||
|
||||
|
||||
# Success
|
||||
|
||||
if foo == bar then
|
||||
a
|
||||
|
||||
else
|
||||
b
|
||||
|
||||
-}
|
||||
|
||||
import Ast.Expression exposing (..)
|
||||
import Lint exposing (doNothing, lint)
|
||||
import Lint.Types exposing (Direction(..), LintError, LintRule, LintRuleImplementation)
|
||||
import Set exposing (Set)
|
||||
|
||||
|
||||
type alias Context =
|
||||
{}
|
||||
|
||||
|
||||
{-| Forbid the use of expressions in an If condition whose value are always the same.
|
||||
|
||||
rules =
|
||||
[ NoConstantCondition.rule
|
||||
]
|
||||
|
||||
-}
|
||||
rule : LintRule
|
||||
rule input =
|
||||
lint input implementation
|
||||
|
||||
|
||||
implementation : LintRuleImplementation Context
|
||||
implementation =
|
||||
{ statementFn = doNothing
|
||||
, typeFn = doNothing
|
||||
, expressionFn = expressionFn
|
||||
, moduleEndFn = \ctx -> ( [], ctx )
|
||||
, initialContext = Context
|
||||
}
|
||||
|
||||
|
||||
error : LintError
|
||||
error =
|
||||
LintError "NoConstantCondition" "Useless condition: It will always evaluate to the same value"
|
||||
|
||||
|
||||
isStaticVariable : List String -> Bool
|
||||
isStaticVariable names =
|
||||
case names of
|
||||
[ "True" ] ->
|
||||
True
|
||||
|
||||
[ "False" ] ->
|
||||
True
|
||||
|
||||
_ ->
|
||||
False
|
||||
|
||||
|
||||
comparisonOperators : Set (List String)
|
||||
comparisonOperators =
|
||||
Set.fromList [ [ "==" ], [ "/=" ], [ "<" ], [ "<=" ], [ ">" ], [ ">=" ] ]
|
||||
|
||||
|
||||
isStatic : Expression -> Bool
|
||||
isStatic expr =
|
||||
case expr of
|
||||
Variable value ->
|
||||
if isStaticVariable value then
|
||||
True
|
||||
|
||||
else
|
||||
False
|
||||
|
||||
Integer value ->
|
||||
True
|
||||
|
||||
Float value ->
|
||||
True
|
||||
|
||||
String value ->
|
||||
True
|
||||
|
||||
BinOp (Variable op) left right ->
|
||||
Set.member op comparisonOperators
|
||||
&& (left == right || (isStatic left && isStatic right))
|
||||
|
||||
_ ->
|
||||
False
|
||||
|
||||
|
||||
expressionFn : Context -> Direction Expression -> ( List LintError, Context )
|
||||
expressionFn ctx node =
|
||||
case node of
|
||||
Enter (If cond _ _) ->
|
||||
if isStatic cond then
|
||||
( [ error ], ctx )
|
||||
|
||||
else
|
||||
( [], ctx )
|
||||
|
||||
_ ->
|
||||
( [], ctx )
|
@ -1,84 +0,0 @@
|
||||
module Lint.Rules.NoDuplicateImports exposing (rule)
|
||||
|
||||
{-|
|
||||
|
||||
@docs rule
|
||||
|
||||
|
||||
# Fail
|
||||
|
||||
import Set
|
||||
import Set exposing (Set)
|
||||
|
||||
|
||||
# Success
|
||||
|
||||
import Set exposing (Set)
|
||||
|
||||
-}
|
||||
|
||||
import Ast.Statement exposing (..)
|
||||
import Lint exposing (doNothing, lint)
|
||||
import Lint.Types exposing (Direction(..), LintError, LintRule, LintRuleImplementation)
|
||||
import Set exposing (Set)
|
||||
|
||||
|
||||
type alias Context =
|
||||
{ imports : Set String
|
||||
, duplicates : Set String
|
||||
}
|
||||
|
||||
|
||||
{-| Forbid importing the same module several times in a file.
|
||||
|
||||
rules =
|
||||
[ NoDuplicateImports.rule
|
||||
]
|
||||
|
||||
-}
|
||||
rule : LintRule
|
||||
rule input =
|
||||
lint input implementation
|
||||
|
||||
|
||||
implementation : LintRuleImplementation Context
|
||||
implementation =
|
||||
{ statementFn = statementFn
|
||||
, typeFn = doNothing
|
||||
, expressionFn = doNothing
|
||||
, moduleEndFn = moduleEndFn
|
||||
, initialContext = Context Set.empty Set.empty
|
||||
}
|
||||
|
||||
|
||||
error : String -> LintError
|
||||
error name =
|
||||
LintError "NoDuplicateImports" (name ++ " was imported several times")
|
||||
|
||||
|
||||
statementFn : Context -> Direction Statement -> ( List LintError, Context )
|
||||
statementFn ctx node =
|
||||
case node of
|
||||
Enter (ImportStatement names alias exportSet) ->
|
||||
let
|
||||
name =
|
||||
String.join "." names
|
||||
in
|
||||
if Set.member name ctx.imports then
|
||||
( [], { ctx | duplicates = Set.insert name ctx.duplicates } )
|
||||
|
||||
else
|
||||
( [], { ctx | imports = Set.insert name ctx.imports } )
|
||||
|
||||
_ ->
|
||||
( [], ctx )
|
||||
|
||||
|
||||
moduleEndFn : Context -> ( List LintError, Context )
|
||||
moduleEndFn ctx =
|
||||
let
|
||||
errors =
|
||||
Set.toList ctx.duplicates
|
||||
|> List.map error
|
||||
in
|
||||
( errors, ctx )
|
@ -1,67 +0,0 @@
|
||||
module Lint.Rules.NoExposingEverything exposing (rule)
|
||||
|
||||
{-|
|
||||
@docs rule
|
||||
|
||||
# Fail
|
||||
|
||||
module Main exposing (..)
|
||||
|
||||
# Success
|
||||
|
||||
module Main exposing (a, b, C)
|
||||
-}
|
||||
|
||||
import Ast.Statement exposing (..)
|
||||
import Lint exposing (lint, doNothing)
|
||||
import Lint.Types exposing (LintRule, LintRuleImplementation, LintError, Direction(..))
|
||||
|
||||
|
||||
type alias Context =
|
||||
{}
|
||||
|
||||
|
||||
{-| Forbid exporting everything in your modules `module Main exposing (..)`, to make your module explicit in what it exposes.
|
||||
|
||||
rules =
|
||||
[ NoExposingEverything.rule
|
||||
]
|
||||
-}
|
||||
rule : LintRule
|
||||
rule input =
|
||||
lint input implementation
|
||||
|
||||
|
||||
implementation : LintRuleImplementation Context
|
||||
implementation =
|
||||
{ statementFn = statementFn
|
||||
, typeFn = doNothing
|
||||
, expressionFn = doNothing
|
||||
, moduleEndFn = (\ctx -> ( [], ctx ))
|
||||
, initialContext = Context
|
||||
}
|
||||
|
||||
|
||||
createError : String -> LintError
|
||||
createError name =
|
||||
LintError "NoExposingEverything" ("Do not expose everything from module " ++ name ++ " using (..)")
|
||||
|
||||
|
||||
reportModule : List String -> LintError
|
||||
reportModule name =
|
||||
name
|
||||
|> String.join "."
|
||||
|> createError
|
||||
|
||||
|
||||
statementFn : Context -> Direction Statement -> ( List LintError, Context )
|
||||
statementFn ctx node =
|
||||
case node of
|
||||
Enter (ModuleDeclaration name AllExport) ->
|
||||
( [ reportModule name ], ctx )
|
||||
|
||||
Enter (PortModuleDeclaration name AllExport) ->
|
||||
( [ reportModule name ], ctx )
|
||||
|
||||
_ ->
|
||||
( [], ctx )
|
@ -1,78 +0,0 @@
|
||||
module Lint.Rules.NoImportingEverything exposing (rule, Configuration)
|
||||
|
||||
{-|
|
||||
|
||||
@docs rule, Configuration
|
||||
|
||||
|
||||
# Fail
|
||||
|
||||
import Html exposing (..)
|
||||
|
||||
|
||||
# Success
|
||||
|
||||
import Html exposing (div, p, textarea)
|
||||
|
||||
-}
|
||||
|
||||
import Ast.Statement exposing (..)
|
||||
import Lint exposing (doNothing, lint)
|
||||
import Lint.Types exposing (Direction(..), LintError, LintRule, LintRuleImplementation)
|
||||
import Set exposing (Set)
|
||||
|
||||
|
||||
type alias Context =
|
||||
{ exceptions : Set String }
|
||||
|
||||
|
||||
{-| Configuration for the rule.
|
||||
-}
|
||||
type alias Configuration =
|
||||
{ exceptions : List String }
|
||||
|
||||
|
||||
{-| Forbid importing everything from your module. This can especially be confusing to newcomers when the exposed
|
||||
functions and types are unknown to them.
|
||||
|
||||
rules =
|
||||
[ NoImportingEverything.rule { exceptions = [ "Html" ] }
|
||||
]
|
||||
|
||||
-}
|
||||
rule : Configuration -> LintRule
|
||||
rule exceptions input =
|
||||
lint input (implementation exceptions)
|
||||
|
||||
|
||||
implementation : Configuration -> LintRuleImplementation Context
|
||||
implementation config =
|
||||
{ statementFn = statementFn
|
||||
, typeFn = doNothing
|
||||
, expressionFn = doNothing
|
||||
, moduleEndFn = \ctx -> ( [], ctx )
|
||||
, initialContext = Context (Set.fromList config.exceptions)
|
||||
}
|
||||
|
||||
|
||||
error : String -> LintError
|
||||
error name =
|
||||
LintError "NoImportingEverything" ("Do not expose everything from " ++ name)
|
||||
|
||||
|
||||
statementFn : Context -> Direction Statement -> ( List LintError, Context )
|
||||
statementFn ctx node =
|
||||
case node of
|
||||
Enter (ImportStatement names alias (Just AllExport)) ->
|
||||
let
|
||||
name =
|
||||
String.join "." names
|
||||
in
|
||||
if Set.member name ctx.exceptions then
|
||||
( [], ctx )
|
||||
|
||||
else
|
||||
( [ error name ], ctx )
|
||||
|
||||
_ ->
|
||||
( [], ctx )
|
@ -1,63 +0,0 @@
|
||||
module Lint.Rules.NoNestedLet exposing (rule)
|
||||
|
||||
{-|
|
||||
@docs rule
|
||||
|
||||
# Fail
|
||||
|
||||
a = let b = 1
|
||||
in let c = 2
|
||||
in b + c
|
||||
|
||||
# Success
|
||||
|
||||
a = let
|
||||
b = 1
|
||||
c = 2
|
||||
in
|
||||
b + c
|
||||
-}
|
||||
|
||||
import Ast.Expression exposing (..)
|
||||
import Lint exposing (lint, doNothing)
|
||||
import Lint.Types exposing (LintRule, LintRuleImplementation, LintError, Direction(..))
|
||||
|
||||
|
||||
type alias Context =
|
||||
{}
|
||||
|
||||
|
||||
{-| Forbid nesting let expressions directly.
|
||||
|
||||
rules =
|
||||
[ NoNestedLet.rule
|
||||
]
|
||||
-}
|
||||
rule : LintRule
|
||||
rule input =
|
||||
lint input implementation
|
||||
|
||||
|
||||
implementation : LintRuleImplementation Context
|
||||
implementation =
|
||||
{ statementFn = doNothing
|
||||
, typeFn = doNothing
|
||||
, expressionFn = expressionFn
|
||||
, moduleEndFn = (\ctx -> ( [], ctx ))
|
||||
, initialContext = Context
|
||||
}
|
||||
|
||||
|
||||
error : LintError
|
||||
error =
|
||||
LintError "NoNestedLet" "Do not nest Let expressions directly"
|
||||
|
||||
|
||||
expressionFn : Context -> Direction Expression -> ( List LintError, Context )
|
||||
expressionFn ctx node =
|
||||
case node of
|
||||
Enter (Let declarations (Let _ _)) ->
|
||||
( [ error ], ctx )
|
||||
|
||||
_ ->
|
||||
( [], ctx )
|
@ -1,74 +0,0 @@
|
||||
module Lint.Rules.NoUnannotatedFunction exposing (rule)
|
||||
|
||||
{-|
|
||||
|
||||
@docs rule
|
||||
|
||||
|
||||
# Fail
|
||||
|
||||
a n =
|
||||
n + 1
|
||||
|
||||
|
||||
# Success
|
||||
|
||||
a : Int -> Int
|
||||
a n =
|
||||
n + 1
|
||||
|
||||
-}
|
||||
|
||||
import Ast.Statement exposing (..)
|
||||
import Lint exposing (doNothing, lint)
|
||||
import Lint.Types exposing (Direction(..), LintError, LintRule, LintRuleImplementation)
|
||||
import Set exposing (Set)
|
||||
|
||||
|
||||
type alias Context =
|
||||
{ annotatedFunctions : Set String
|
||||
}
|
||||
|
||||
|
||||
{-| Ensure every top-level function declaration has a type annotation.
|
||||
|
||||
rules =
|
||||
[ NoUnannotatedFunction.rule
|
||||
]
|
||||
|
||||
-}
|
||||
rule : LintRule
|
||||
rule input =
|
||||
lint input implementation
|
||||
|
||||
|
||||
implementation : LintRuleImplementation Context
|
||||
implementation =
|
||||
{ statementFn = statementFn
|
||||
, typeFn = doNothing
|
||||
, expressionFn = doNothing
|
||||
, moduleEndFn = \ctx -> ( [], ctx )
|
||||
, initialContext = Context Set.empty
|
||||
}
|
||||
|
||||
|
||||
createError : String -> LintError
|
||||
createError name =
|
||||
LintError "NoUnannotatedFunction" ("`" ++ name ++ "` does not have a type declaration")
|
||||
|
||||
|
||||
statementFn : Context -> Direction Statement -> ( List LintError, Context )
|
||||
statementFn ctx node =
|
||||
case node of
|
||||
Enter (FunctionTypeDeclaration name application) ->
|
||||
( [], { ctx | annotatedFunctions = Set.insert name ctx.annotatedFunctions } )
|
||||
|
||||
Enter (FunctionDeclaration name params body) ->
|
||||
if Set.member name ctx.annotatedFunctions then
|
||||
( [], ctx )
|
||||
|
||||
else
|
||||
( [ createError name ], ctx )
|
||||
|
||||
_ ->
|
||||
( [], ctx )
|
@ -1,258 +0,0 @@
|
||||
module Lint.Rules.NoUnusedVariables exposing (rule)
|
||||
|
||||
{-|
|
||||
|
||||
@docs rule
|
||||
|
||||
|
||||
# Fail
|
||||
|
||||
a n =
|
||||
n + 1
|
||||
|
||||
b =
|
||||
a 2
|
||||
|
||||
|
||||
# Success
|
||||
|
||||
a n =
|
||||
n + 1
|
||||
|
||||
-}
|
||||
|
||||
import Ast.Expression exposing (..)
|
||||
import Ast.Statement exposing (..)
|
||||
import Lint exposing (doNothing, lint)
|
||||
import Lint.Types exposing (Direction(..), LintError, LintRule, LintRuleImplementation)
|
||||
import Set exposing (Set)
|
||||
|
||||
|
||||
type alias Scope =
|
||||
{ declared : Set String
|
||||
, used : Set String
|
||||
}
|
||||
|
||||
|
||||
type alias Context =
|
||||
{ scopes : List Scope
|
||||
, exportsEverything : Bool
|
||||
}
|
||||
|
||||
|
||||
emptyScope : Scope
|
||||
emptyScope =
|
||||
Scope Set.empty Set.empty
|
||||
|
||||
|
||||
{-| Reports variables that are declared but never used.
|
||||
|
||||
rules =
|
||||
[ NoUnusedVariables.rule
|
||||
]
|
||||
|
||||
-}
|
||||
rule : LintRule
|
||||
rule input =
|
||||
lint input implementation
|
||||
|
||||
|
||||
implementation : LintRuleImplementation Context
|
||||
implementation =
|
||||
{ statementFn = statementFn
|
||||
, typeFn = doNothing
|
||||
, expressionFn = expressionFn
|
||||
, moduleEndFn = moduleEndFn
|
||||
, initialContext = Context [ Scope Set.empty Set.empty ] False
|
||||
}
|
||||
|
||||
|
||||
createError : String -> LintError
|
||||
createError name =
|
||||
LintError "NoUnusedVariables" ("Variable `" ++ name ++ "` is not used")
|
||||
|
||||
|
||||
addUsedToStack : List Scope -> List String -> List Scope
|
||||
addUsedToStack scopes variables =
|
||||
let
|
||||
lastScope =
|
||||
case List.head scopes of
|
||||
Nothing ->
|
||||
Debug.crash "Unexpected Empty scope stack" emptyScope
|
||||
|
||||
Just scope ->
|
||||
{ scope | used = Set.union scope.used (Set.fromList variables) }
|
||||
in
|
||||
lastScope :: List.drop 1 scopes
|
||||
|
||||
|
||||
addFoundToStack : List Scope -> List String -> List Scope
|
||||
addFoundToStack scopes variables =
|
||||
let
|
||||
lastScope =
|
||||
case List.head scopes of
|
||||
Nothing ->
|
||||
Debug.crash "Unexpected Empty scope stack" emptyScope
|
||||
|
||||
Just scope ->
|
||||
{ scope | declared = Set.union scope.declared (Set.fromList variables) }
|
||||
in
|
||||
lastScope :: List.drop 1 scopes
|
||||
|
||||
|
||||
makeReport : Maybe Scope -> ( List LintError, Set String )
|
||||
makeReport scope =
|
||||
case scope of
|
||||
Nothing ->
|
||||
Debug.crash "Unexpected Empty scope stack" ( [], Set.empty )
|
||||
|
||||
Just scope ->
|
||||
let
|
||||
notUsed =
|
||||
Set.diff scope.declared scope.used
|
||||
|
||||
variablesUsedButNotFromThisScope =
|
||||
Set.diff scope.used scope.declared
|
||||
|
||||
errors =
|
||||
Set.diff scope.declared scope.used
|
||||
|> Set.toList
|
||||
|> List.sort
|
||||
|> List.map createError
|
||||
in
|
||||
( errors, variablesUsedButNotFromThisScope )
|
||||
|
||||
|
||||
variableName : Expression -> Maybe (List String)
|
||||
variableName expr =
|
||||
case expr of
|
||||
Variable names ->
|
||||
Just names
|
||||
|
||||
Application var _ ->
|
||||
variableName var
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
|
||||
|
||||
expressionFn : Context -> Direction Expression -> ( List LintError, Context )
|
||||
expressionFn ctx node =
|
||||
case node of
|
||||
Enter (Variable names) ->
|
||||
case names of
|
||||
[ name ] ->
|
||||
( [], { ctx | scopes = addUsedToStack ctx.scopes [ name ] } )
|
||||
|
||||
_ ->
|
||||
( [], ctx )
|
||||
|
||||
Enter (Let declarations body) ->
|
||||
let
|
||||
variables =
|
||||
List.map Tuple.first declarations
|
||||
|> List.filterMap variableName
|
||||
|> List.concat
|
||||
|> Set.fromList
|
||||
|
||||
newScope =
|
||||
Scope variables Set.empty
|
||||
in
|
||||
( [], { ctx | scopes = newScope :: ctx.scopes } )
|
||||
|
||||
Exit (Let _ _) ->
|
||||
let
|
||||
( errors, variablesUsedButNotFromThisScope ) =
|
||||
ctx.scopes
|
||||
|> List.head
|
||||
|> makeReport
|
||||
|
||||
newScopes =
|
||||
List.drop 1 ctx.scopes
|
||||
in
|
||||
( errors, { ctx | scopes = addUsedToStack newScopes (Set.toList variablesUsedButNotFromThisScope) } )
|
||||
|
||||
_ ->
|
||||
( [], ctx )
|
||||
|
||||
|
||||
getExported : ExportSet -> Set String
|
||||
getExported exportType =
|
||||
case exportType of
|
||||
-- Ignore as this case is handled by `exportsEverything`
|
||||
AllExport ->
|
||||
Set.empty
|
||||
|
||||
SubsetExport exports ->
|
||||
List.map getExported exports
|
||||
|> List.foldl Set.union Set.empty
|
||||
|
||||
FunctionExport name ->
|
||||
Set.singleton name
|
||||
|
||||
TypeExport name _ ->
|
||||
Set.singleton name
|
||||
|
||||
|
||||
addExposedVariables : Context -> Ast.Statement.ExportSet -> Context
|
||||
addExposedVariables ctx exportType =
|
||||
{ ctx
|
||||
| scopes =
|
||||
getExported exportType
|
||||
|> Set.toList
|
||||
|> addUsedToStack ctx.scopes
|
||||
}
|
||||
|
||||
|
||||
statementFn : Context -> Direction Statement -> ( List LintError, Context )
|
||||
statementFn ctx node =
|
||||
case node of
|
||||
Enter (FunctionDeclaration name args body) ->
|
||||
( [], { ctx | scopes = addFoundToStack ctx.scopes [ name ] } )
|
||||
|
||||
Enter (ModuleDeclaration names AllExport) ->
|
||||
( [], { ctx | exportsEverything = True } )
|
||||
|
||||
Enter (PortModuleDeclaration names AllExport) ->
|
||||
( [], { ctx | exportsEverything = True } )
|
||||
|
||||
Enter (ImportStatement module_ alias_ (Just (SubsetExport imported))) ->
|
||||
let
|
||||
variables =
|
||||
List.foldl
|
||||
(\var res ->
|
||||
case var of
|
||||
FunctionExport name ->
|
||||
name :: res
|
||||
|
||||
_ ->
|
||||
res
|
||||
)
|
||||
[]
|
||||
imported
|
||||
in
|
||||
( [], { ctx | scopes = addFoundToStack ctx.scopes variables } )
|
||||
|
||||
Enter (ModuleDeclaration names exportType) ->
|
||||
( [], addExposedVariables ctx exportType )
|
||||
|
||||
Enter (PortModuleDeclaration names exportType) ->
|
||||
( [], addExposedVariables ctx exportType )
|
||||
|
||||
_ ->
|
||||
( [], ctx )
|
||||
|
||||
|
||||
moduleEndFn : Context -> ( List LintError, Context )
|
||||
moduleEndFn ctx =
|
||||
let
|
||||
( errors, _ ) =
|
||||
if ctx.exportsEverything then
|
||||
( [], Set.empty )
|
||||
|
||||
else
|
||||
ctx.scopes
|
||||
|> List.head
|
||||
|> makeReport
|
||||
in
|
||||
( errors, ctx )
|
@ -1,74 +0,0 @@
|
||||
module Lint.Rules.NoUselessIf exposing (rule)
|
||||
|
||||
{-|
|
||||
|
||||
@docs rule
|
||||
|
||||
|
||||
# Fail
|
||||
|
||||
if condition then
|
||||
value
|
||||
|
||||
else
|
||||
value
|
||||
|
||||
|
||||
# Success
|
||||
|
||||
if condition then
|
||||
value1
|
||||
|
||||
else
|
||||
value2
|
||||
|
||||
-}
|
||||
|
||||
import Ast.Expression exposing (..)
|
||||
import Lint exposing (doNothing, lint)
|
||||
import Lint.Types exposing (Direction(..), LintError, LintRule, LintRuleImplementation)
|
||||
|
||||
|
||||
type alias Context =
|
||||
{}
|
||||
|
||||
|
||||
{-| Reports when both paths of an If expression result will lead to the same value.
|
||||
|
||||
rules =
|
||||
[ NoUselessIf.rule
|
||||
]
|
||||
|
||||
-}
|
||||
rule : LintRule
|
||||
rule input =
|
||||
lint input implementation
|
||||
|
||||
|
||||
implementation : LintRuleImplementation Context
|
||||
implementation =
|
||||
{ statementFn = doNothing
|
||||
, typeFn = doNothing
|
||||
, expressionFn = expressionFn
|
||||
, moduleEndFn = \ctx -> ( [], ctx )
|
||||
, initialContext = Context
|
||||
}
|
||||
|
||||
|
||||
error : LintError
|
||||
error =
|
||||
LintError "NoUselessIf" "Useless if expression: It will always evaluate to the same value"
|
||||
|
||||
|
||||
expressionFn : Context -> Direction Expression -> ( List LintError, Context )
|
||||
expressionFn ctx node =
|
||||
case node of
|
||||
Enter (If cond then_ else_) ->
|
||||
if then_ == else_ then
|
||||
( [ error ], ctx )
|
||||
|
||||
else
|
||||
( [], ctx )
|
||||
|
||||
_ ->
|
||||
( [], ctx )
|
@ -1,255 +0,0 @@
|
||||
module Lint.Rules.NoUselessPatternMatching exposing (rule)
|
||||
|
||||
{-|
|
||||
|
||||
@docs rule
|
||||
|
||||
|
||||
# Fail
|
||||
|
||||
-- Useless pattern matching
|
||||
case value of
|
||||
Foo ->
|
||||
1
|
||||
|
||||
Bar ->
|
||||
1
|
||||
|
||||
_ ->
|
||||
1
|
||||
|
||||
-- Useless pattern `Bar`, it's the same as the default pattern
|
||||
case value of
|
||||
Foo ->
|
||||
2
|
||||
|
||||
Bar ->
|
||||
1
|
||||
|
||||
_ ->
|
||||
1
|
||||
|
||||
|
||||
# Success
|
||||
|
||||
case value of
|
||||
Foo ->
|
||||
1
|
||||
|
||||
Bar ->
|
||||
2
|
||||
|
||||
_ ->
|
||||
3
|
||||
|
||||
case value of
|
||||
Foo n ->
|
||||
n
|
||||
|
||||
Bar n ->
|
||||
n
|
||||
|
||||
-}
|
||||
|
||||
import Ast.Expression exposing (..)
|
||||
import Lint exposing (doNothing, lint, visitExpression)
|
||||
import Lint.Types exposing (Direction(..), LintError, LintRule, LintRuleImplementation)
|
||||
import Regex
|
||||
import Set exposing (Set)
|
||||
|
||||
|
||||
type alias Context =
|
||||
{}
|
||||
|
||||
|
||||
{-| Reports case expressions that can be simplified. Either when all patterns will lead to the same value, or when a
|
||||
pattern will lead to the same value as the default pattern.
|
||||
|
||||
rules =
|
||||
[ NoUselessPatternMatching.rule
|
||||
]
|
||||
|
||||
-}
|
||||
rule : LintRule
|
||||
rule input =
|
||||
lint input implementation
|
||||
|
||||
|
||||
implementation : LintRuleImplementation Context
|
||||
implementation =
|
||||
{ statementFn = doNothing
|
||||
, typeFn = doNothing
|
||||
, expressionFn = expressionFn
|
||||
, moduleEndFn = \ctx -> ( [], ctx )
|
||||
, initialContext = Context
|
||||
}
|
||||
|
||||
|
||||
variableFinder : LintRuleImplementation (Set String)
|
||||
variableFinder =
|
||||
{ statementFn = doNothing
|
||||
, typeFn = doNothing
|
||||
, expressionFn = findVariable
|
||||
, moduleEndFn = \ctx -> ( [], ctx )
|
||||
, initialContext = Set.empty
|
||||
}
|
||||
|
||||
|
||||
findVariable : Set String -> Direction Expression -> ( List LintError, Set String )
|
||||
findVariable foundVariables node =
|
||||
case node of
|
||||
Enter (Variable a) ->
|
||||
( [], Set.insert (String.join "." a) foundVariables )
|
||||
|
||||
_ ->
|
||||
( [], foundVariables )
|
||||
|
||||
|
||||
baseError : String -> LintError
|
||||
baseError =
|
||||
LintError "NoUselessPatternMatching"
|
||||
|
||||
|
||||
uselessPatternMatchingError : LintError
|
||||
uselessPatternMatchingError =
|
||||
baseError "Useless case expression: It will always evaluate to the same value"
|
||||
|
||||
|
||||
uselessPatternError : LintError
|
||||
uselessPatternError =
|
||||
baseError "Useless patterns: Some will always evaluate to the same value as the default pattern"
|
||||
|
||||
|
||||
subPatternMatchingVariables : Expression -> List String
|
||||
subPatternMatchingVariables pattern =
|
||||
case pattern of
|
||||
Variable a ->
|
||||
[ String.join "." a ]
|
||||
|
||||
Application object variable ->
|
||||
subPatternMatchingVariables object ++ subPatternMatchingVariables variable
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
|
||||
isVariable : String -> Bool
|
||||
isVariable =
|
||||
Regex.contains (Regex.regex "^[_a-z][\\w\\d]*$")
|
||||
|
||||
|
||||
patternMatchingVariables : Expression -> Set String
|
||||
patternMatchingVariables pattern =
|
||||
case pattern of
|
||||
Application _ _ ->
|
||||
subPatternMatchingVariables pattern
|
||||
|> Set.fromList
|
||||
|> Set.filter isVariable
|
||||
|
||||
_ ->
|
||||
Set.empty
|
||||
|
||||
|
||||
usesIntroducedVariable : ( Expression, Expression, Set String, Set String ) -> Bool
|
||||
usesIntroducedVariable ( _, _, used, declared ) =
|
||||
Set.intersect used declared
|
||||
|> (\set -> Set.size set > 0)
|
||||
|
||||
|
||||
patternsAreAllTheSame : List ( Expression, Expression, Set String, Set String ) -> Bool
|
||||
patternsAreAllTheSame patterns =
|
||||
let
|
||||
anyUseVariables =
|
||||
List.any usesIntroducedVariable patterns
|
||||
|
||||
bodiesAreIdentical =
|
||||
patterns
|
||||
|> List.map (\( _, body, _, _ ) -> toString body)
|
||||
|> Set.fromList
|
||||
|> (\set -> Set.size set == 1)
|
||||
in
|
||||
not anyUseVariables && bodiesAreIdentical
|
||||
|
||||
|
||||
defaultPattern : List ( Expression, Expression, Set String, Set String ) -> Maybe ( Expression, Expression, Set String, Set String )
|
||||
defaultPattern patterns =
|
||||
List.foldl
|
||||
(\( pattern, body, used, declared ) res ->
|
||||
case res of
|
||||
Just a ->
|
||||
res
|
||||
|
||||
Nothing ->
|
||||
case pattern of
|
||||
Variable names ->
|
||||
if isVariable (String.join "." names) then
|
||||
Just ( pattern, body, used, declared )
|
||||
|
||||
else
|
||||
Nothing
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
)
|
||||
Nothing
|
||||
patterns
|
||||
|
||||
|
||||
patternBody : ( Expression, Expression, Set String, Set String ) -> Expression
|
||||
patternBody ( _, body, _, _ ) =
|
||||
body
|
||||
|
||||
|
||||
thereAreUselessPatterns : List ( Expression, Expression, Set String, Set String ) -> Bool
|
||||
thereAreUselessPatterns patterns =
|
||||
let
|
||||
default =
|
||||
defaultPattern patterns
|
||||
|
||||
hasDefault =
|
||||
case default of
|
||||
Nothing ->
|
||||
False
|
||||
|
||||
Just a ->
|
||||
True
|
||||
|
||||
justDefault =
|
||||
Maybe.withDefault ( Integer 1, Integer 1, Set.empty, Set.empty ) default
|
||||
in
|
||||
hasDefault
|
||||
&& List.foldl
|
||||
(\pattern res ->
|
||||
res
|
||||
|| ((pattern /= justDefault)
|
||||
&& (patternBody pattern == patternBody justDefault)
|
||||
&& not (usesIntroducedVariable pattern)
|
||||
)
|
||||
)
|
||||
False
|
||||
patterns
|
||||
|
||||
|
||||
expressionFn : Context -> Direction Expression -> ( List LintError, Context )
|
||||
expressionFn ctx node =
|
||||
case node of
|
||||
Enter (Case expr patterns) ->
|
||||
let
|
||||
analyzedPatterns =
|
||||
patterns
|
||||
|> List.map
|
||||
(\( pattern, body ) ->
|
||||
( pattern, body, Tuple.second <| visitExpression variableFinder body, patternMatchingVariables pattern )
|
||||
)
|
||||
in
|
||||
if patternsAreAllTheSame analyzedPatterns then
|
||||
( [ uselessPatternMatchingError ], ctx )
|
||||
|
||||
else if thereAreUselessPatterns analyzedPatterns then
|
||||
( [ uselessPatternError ], ctx )
|
||||
|
||||
else
|
||||
( [], ctx )
|
||||
|
||||
_ ->
|
||||
( [], ctx )
|
@ -1,101 +0,0 @@
|
||||
module Lint.Rules.NoWarningComments exposing (rule)
|
||||
|
||||
{-|
|
||||
|
||||
@docs rule
|
||||
|
||||
|
||||
# Fail
|
||||
|
||||
-- TODO Refactor this part of the code
|
||||
-- FIXME Broken because of...
|
||||
-- XXX This should not be done like this
|
||||
|
||||
|
||||
|
||||
|
||||
# Success
|
||||
|
||||
-- Regular comment
|
||||
|
||||
|
||||
|
||||
-}
|
||||
|
||||
import Ast.Statement exposing (..)
|
||||
import Lint exposing (doNothing, lint)
|
||||
import Lint.Types exposing (Direction(..), LintError, LintRule, LintRuleImplementation)
|
||||
|
||||
|
||||
type alias Context =
|
||||
{}
|
||||
|
||||
|
||||
{-| Detect comments containing words like `TODO`, `FIXME` and `XXX`.
|
||||
|
||||
rules =
|
||||
[ NoWarningComments.rule
|
||||
]
|
||||
|
||||
-}
|
||||
rule : LintRule
|
||||
rule input =
|
||||
lint input implementation
|
||||
|
||||
|
||||
implementation : LintRuleImplementation Context
|
||||
implementation =
|
||||
{ statementFn = statementFn
|
||||
, typeFn = doNothing
|
||||
, expressionFn = doNothing
|
||||
, moduleEndFn = \ctx -> ( [], ctx )
|
||||
, initialContext = Context
|
||||
}
|
||||
|
||||
|
||||
error : String -> LintError
|
||||
error word =
|
||||
LintError "NoWarningComments" ("Unexpected " ++ word ++ " comment")
|
||||
|
||||
|
||||
findWarning : String -> Maybe LintError
|
||||
findWarning text =
|
||||
if String.contains "TODO" text then
|
||||
Just <| error "TODO"
|
||||
|
||||
else if String.contains "todo" text then
|
||||
Just <| error "todo"
|
||||
|
||||
else if String.contains "FIXME" text then
|
||||
Just <| error "FIXME"
|
||||
|
||||
else if String.contains "fixme" text then
|
||||
Just <| error "fixme"
|
||||
|
||||
else if String.contains "XXX" text then
|
||||
Just <| error "XXX"
|
||||
|
||||
else if String.contains "xxx" text then
|
||||
Just <| error "xxx"
|
||||
|
||||
else
|
||||
Nothing
|
||||
|
||||
|
||||
statementFn : Context -> Direction Statement -> ( List LintError, Context )
|
||||
statementFn ctx node =
|
||||
case node of
|
||||
Enter (Comment text) ->
|
||||
let
|
||||
warning =
|
||||
findWarning text
|
||||
in
|
||||
case warning of
|
||||
Just err ->
|
||||
( [ err ], ctx )
|
||||
|
||||
Nothing ->
|
||||
( [], ctx )
|
||||
|
||||
_ ->
|
||||
( [], ctx )
|
@ -1,146 +0,0 @@
|
||||
module Lint.Rules.SimplifyPiping exposing (rule)
|
||||
|
||||
{-|
|
||||
|
||||
@docs rule
|
||||
|
||||
|
||||
# Fail
|
||||
|
||||
a =
|
||||
values
|
||||
|> List.map foo
|
||||
|> List.map bar
|
||||
|
||||
|
||||
# Success
|
||||
|
||||
a =
|
||||
values
|
||||
|> List.map (foo >> bar)
|
||||
|
||||
-}
|
||||
|
||||
import Ast.Expression exposing (..)
|
||||
import Lint exposing (doNothing, lint)
|
||||
import Lint.Types exposing (Direction(..), LintError, LintRule, LintRuleImplementation)
|
||||
import Set exposing (Set)
|
||||
|
||||
|
||||
type alias Context =
|
||||
{}
|
||||
|
||||
|
||||
{-| Simplify piped functions like `List.map f >> List.map g` to `List.map (f >> g)`
|
||||
|
||||
rules =
|
||||
[ SimplifyPiping.rule
|
||||
]
|
||||
|
||||
-}
|
||||
rule : LintRule
|
||||
rule input =
|
||||
lint input implementation
|
||||
|
||||
|
||||
implementation : LintRuleImplementation Context
|
||||
implementation =
|
||||
{ statementFn = doNothing
|
||||
, typeFn = doNothing
|
||||
, expressionFn = expressionFn
|
||||
, moduleEndFn = \ctx -> ( [], ctx )
|
||||
, initialContext = Context
|
||||
}
|
||||
|
||||
|
||||
error : String -> String -> LintError
|
||||
error op fn =
|
||||
LintError
|
||||
"SimplifyPiping"
|
||||
("Instead of `" ++ fn ++ " f " ++ op ++ " List.map g`, try " ++ fn ++ " (f " ++ op ++ " g)")
|
||||
|
||||
|
||||
simplifiableFns : Set String
|
||||
simplifiableFns =
|
||||
Set.fromList
|
||||
[ "List.map"
|
||||
, "Set.map"
|
||||
, "Array.map"
|
||||
, "Array.indexedMap"
|
||||
]
|
||||
|
||||
|
||||
nameOfMethod : List (List String) -> String
|
||||
nameOfMethod members =
|
||||
members
|
||||
|> List.concatMap (\a -> a)
|
||||
|> String.join "."
|
||||
|
||||
|
||||
reportIfSimplifiableMethod : String -> Expression -> Expression -> List LintError
|
||||
reportIfSimplifiableMethod op left right =
|
||||
case [ left, right ] of
|
||||
[ Application (Access (Variable names1) fns1) _, Application (Access (Variable names2) fns2) _ ] ->
|
||||
if [ names1, fns1 ] == [ names2, fns2 ] && Set.member (nameOfMethod [ names1, fns1 ]) simplifiableFns then
|
||||
[ error op <| nameOfMethod [ names1, fns1 ] ]
|
||||
|
||||
else
|
||||
[]
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
|
||||
expressionFn : Context -> Direction Expression -> ( List LintError, Context )
|
||||
expressionFn ctx node =
|
||||
case node of
|
||||
Enter (BinOp (Variable [ "|>" ]) (Application left _) right) ->
|
||||
case right of
|
||||
-- X.y f data |> X.y g |> foo
|
||||
BinOp (Variable [ "|>" ]) subRight _ ->
|
||||
( reportIfSimplifiableMethod ">>" left subRight, ctx )
|
||||
|
||||
-- X.y f data |> X.y g
|
||||
_ ->
|
||||
( reportIfSimplifiableMethod ">>" left right, ctx )
|
||||
|
||||
-- X.y f <| X.y g data
|
||||
Enter (BinOp (Variable [ "<|" ]) left (Application right _)) ->
|
||||
case left of
|
||||
-- foo <| X.y f <| X.y g data
|
||||
BinOp (Variable [ "<|" ]) subLeft _ ->
|
||||
( reportIfSimplifiableMethod "<<" left subLeft, ctx )
|
||||
|
||||
_ ->
|
||||
( reportIfSimplifiableMethod "<<" left right, ctx )
|
||||
|
||||
-- a |> X.y f |> X.y g
|
||||
Enter (BinOp (Variable [ "|>" ]) _ (BinOp (Variable [ "|>" ]) left right)) ->
|
||||
( reportIfSimplifiableMethod ">>" left right, ctx )
|
||||
|
||||
-- X.y f <| X.y g <| a
|
||||
Enter (BinOp (Variable [ "<|" ]) left (BinOp (Variable [ "<|" ]) right _)) ->
|
||||
( reportIfSimplifiableMethod "<<" left right, ctx )
|
||||
|
||||
Enter (BinOp (Variable [ ">>" ]) left right) ->
|
||||
case left of
|
||||
-- foo >> X.y f >> X.y g
|
||||
BinOp (Variable [ ">>" ]) _ subLeft ->
|
||||
( reportIfSimplifiableMethod ">>" subLeft right, ctx )
|
||||
|
||||
-- X.y f >> X.y g
|
||||
_ ->
|
||||
( reportIfSimplifiableMethod ">>" left right, ctx )
|
||||
|
||||
Enter (BinOp (Variable [ "<<" ]) left right) ->
|
||||
case left of
|
||||
-- foo << X.y f << X.y g
|
||||
BinOp (Variable [ "<<" ]) _ subLeft ->
|
||||
( reportIfSimplifiableMethod "<<" subLeft right, ctx )
|
||||
|
||||
-- X.y f << X.y g
|
||||
_ ->
|
||||
( reportIfSimplifiableMethod "<<" left right, ctx )
|
||||
|
||||
_ ->
|
||||
( [], ctx )
|
@ -1,83 +0,0 @@
|
||||
module Lint.Rules.SimplifyPropertyAccess exposing (rule)
|
||||
|
||||
{-|
|
||||
|
||||
@docs rule
|
||||
|
||||
|
||||
# Fail
|
||||
|
||||
a =
|
||||
List.map (\x -> x.foo) values
|
||||
|
||||
|
||||
# Success
|
||||
|
||||
a =
|
||||
List.map .foo values
|
||||
|
||||
-}
|
||||
|
||||
import Ast.Expression exposing (..)
|
||||
import Ast.Statement exposing (..)
|
||||
import Lint exposing (doNothing, lint)
|
||||
import Lint.Types exposing (Direction(..), LintError, LintRule, LintRuleImplementation)
|
||||
|
||||
|
||||
type alias Context =
|
||||
{}
|
||||
|
||||
|
||||
{-| Simplify functions that return the property of a record by using an access function
|
||||
|
||||
rules =
|
||||
[ SimplifyPropertyAccess.rule
|
||||
]
|
||||
|
||||
-}
|
||||
rule : LintRule
|
||||
rule input =
|
||||
lint input implementation
|
||||
|
||||
|
||||
implementation : LintRuleImplementation Context
|
||||
implementation =
|
||||
{ statementFn = statementFn
|
||||
, typeFn = doNothing
|
||||
, expressionFn = expressionFn
|
||||
, moduleEndFn = \ctx -> ( [], ctx )
|
||||
, initialContext = Context
|
||||
}
|
||||
|
||||
|
||||
error : String -> LintError
|
||||
error property =
|
||||
LintError "SimplifyPropertyAccess" ("Access to property `" ++ property ++ "` could be simplified by using `." ++ property ++ "`")
|
||||
|
||||
|
||||
expressionFn : Context -> Direction Expression -> ( List LintError, Context )
|
||||
expressionFn ctx node =
|
||||
case node of
|
||||
Enter (Lambda [ Variable paramNames ] (Access (Variable varName) properties)) ->
|
||||
if List.length properties == 1 && varName == paramNames then
|
||||
( [ String.join "" properties |> error ], ctx )
|
||||
|
||||
else
|
||||
( [], ctx )
|
||||
|
||||
_ ->
|
||||
( [], ctx )
|
||||
|
||||
|
||||
statementFn : Context -> Direction Statement -> ( List LintError, Context )
|
||||
statementFn ctx node =
|
||||
case node of
|
||||
Enter (FunctionDeclaration _ [ Variable paramNames ] (Access (Variable varName) properties)) ->
|
||||
if List.length properties == 1 && varName == paramNames then
|
||||
( [ String.join "" properties |> error ], ctx )
|
||||
|
||||
else
|
||||
( [], ctx )
|
||||
|
||||
_ ->
|
||||
( [], ctx )
|
Loading…
Reference in New Issue
Block a user