Remove rules, to ease upgrade

This commit is contained in:
Jeroen Engels 2018-11-05 15:08:55 +01:00
parent 4229a8262d
commit 67f1e8223f
15 changed files with 1 additions and 1782 deletions

View File

@ -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": {

View File

@ -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 )

View File

@ -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 )

View File

@ -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 )

View File

@ -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 )

View File

@ -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 )

View File

@ -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 )

View File

@ -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 )

View File

@ -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 )

View File

@ -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 )

View File

@ -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 )

View File

@ -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 )

View File

@ -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 )

View File

@ -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 )

View File

@ -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 )