mirror of
https://github.com/jfmengels/elm-review.git
synced 2024-10-26 16:10:55 +03:00
Backport rules from elm-review-cognitive-complexity
This commit is contained in:
parent
e1766bad01
commit
0d55d0b858
@ -11,6 +11,7 @@ import Elm.Syntax.Declaration as Declaration exposing (Declaration)
|
|||||||
import Elm.Syntax.Expression as Expression exposing (Expression)
|
import Elm.Syntax.Expression as Expression exposing (Expression)
|
||||||
import Elm.Syntax.Node as Node exposing (Node)
|
import Elm.Syntax.Node as Node exposing (Node)
|
||||||
import Elm.Syntax.Range exposing (Location, Range)
|
import Elm.Syntax.Range exposing (Location, Range)
|
||||||
|
import Json.Encode as Encode
|
||||||
import Review.Rule as Rule exposing (Rule)
|
import Review.Rule as Rule exposing (Rule)
|
||||||
import Set exposing (Set)
|
import Set exposing (Set)
|
||||||
|
|
||||||
@ -56,7 +57,26 @@ I would for now recommend to use it with a very high threshold to find places in
|
|||||||
and eventually to enable it in your configuration to make sure no new extremely complex functions appear. As you refactor more
|
and eventually to enable it in your configuration to make sure no new extremely complex functions appear. As you refactor more
|
||||||
and more of your codebase, you can gradually lower the threshold until you reach a level that you feel happy with.
|
and more of your codebase, you can gradually lower the threshold until you reach a level that you feel happy with.
|
||||||
|
|
||||||
Please let me know how enabling this rule works out for you!
|
Please let me know how enabling this rule works out for you! If enforcing doesn't work for you, then you can use this as
|
||||||
|
an insight rule instead.
|
||||||
|
|
||||||
|
|
||||||
|
## Use as an insight rule
|
||||||
|
|
||||||
|
If instead of enforcing a threshold, you wish to have an overview of the complexity for each function, you can run the
|
||||||
|
rule using as an insight rule (using `elm-review --report=json --extract`), which would yield an output like the following:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Some.Module": {
|
||||||
|
"someFunction": 16,
|
||||||
|
"someOtherFunction": 0
|
||||||
|
},
|
||||||
|
"Some.Other.Module": {
|
||||||
|
"awesomeFunction": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Complexity breakdown
|
## Complexity breakdown
|
||||||
@ -171,6 +191,13 @@ elm-review --template jfmengels/elm-review-cognitive-complexity/example --rules
|
|||||||
|
|
||||||
The cognitive complexity is set to 15 in the configuration used by the example.
|
The cognitive complexity is set to 15 in the configuration used by the example.
|
||||||
|
|
||||||
|
If instead of enforcing a threshold, you wish to have an overview of the complexity for each function, you can run the
|
||||||
|
rule like this (requires [`jq`](https://stedolan.github.io/jq/)):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
elm-review --template jfmengels/elm-review-cognitive-complexity/example --extract --report=json --rules CognitiveComplexity | jq -r '.extracts.CognitiveComplexity'
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
|
||||||
@ -180,15 +207,35 @@ Thanks to G. Ann Campbell for the different talks she made on the subject.
|
|||||||
-}
|
-}
|
||||||
rule : Int -> Rule
|
rule : Int -> Rule
|
||||||
rule threshold =
|
rule threshold =
|
||||||
Rule.newModuleRuleSchema "CognitiveComplexity" initialContext
|
Rule.newProjectRuleSchema "CognitiveComplexity" initialContext
|
||||||
|
|> Rule.withModuleVisitor (moduleVisitor threshold)
|
||||||
|
|> Rule.withModuleContextUsingContextCreator
|
||||||
|
{ fromProjectToModule = fromProjectToModule
|
||||||
|
, fromModuleToProject = fromModuleToProject
|
||||||
|
, foldProjectContexts = foldProjectContexts
|
||||||
|
}
|
||||||
|
|> Rule.withDataExtractor dataExtractor
|
||||||
|
|> Rule.fromProjectRuleSchema
|
||||||
|
|
||||||
|
|
||||||
|
moduleVisitor : Int -> Rule.ModuleRuleSchema schemaState ModuleContext -> Rule.ModuleRuleSchema { schemaState | hasAtLeastOneVisitor : () } ModuleContext
|
||||||
|
moduleVisitor threshold schema =
|
||||||
|
schema
|
||||||
|> Rule.withDeclarationExitVisitor declarationExitVisitor
|
|> Rule.withDeclarationExitVisitor declarationExitVisitor
|
||||||
|> Rule.withExpressionEnterVisitor expressionEnterVisitor
|
|> Rule.withExpressionEnterVisitor expressionEnterVisitor
|
||||||
|> Rule.withExpressionExitVisitor expressionExitVisitor
|
|> Rule.withExpressionExitVisitor expressionExitVisitor
|
||||||
|> Rule.withFinalModuleEvaluation (finalEvaluation threshold)
|
|> Rule.withFinalModuleEvaluation (finalModuleEvaluation threshold)
|
||||||
|> Rule.fromModuleRuleSchema
|
|
||||||
|
|
||||||
|
|
||||||
type alias Context =
|
type alias ProjectContext =
|
||||||
|
Dict String ComplexityDict
|
||||||
|
|
||||||
|
|
||||||
|
type alias ComplexityDict =
|
||||||
|
Dict String Int
|
||||||
|
|
||||||
|
|
||||||
|
type alias ModuleContext =
|
||||||
{ nesting : Int
|
{ nesting : Int
|
||||||
, operandsToIgnore : List Range
|
, operandsToIgnore : List Range
|
||||||
, elseIfToIgnore : List Range
|
, elseIfToIgnore : List Range
|
||||||
@ -223,8 +270,15 @@ type IncreaseKind
|
|||||||
| IndirectRecursiveCall String
|
| IndirectRecursiveCall String
|
||||||
|
|
||||||
|
|
||||||
initialContext : Context
|
initialContext : ProjectContext
|
||||||
initialContext =
|
initialContext =
|
||||||
|
Dict.empty
|
||||||
|
|
||||||
|
|
||||||
|
fromProjectToModule : Rule.ContextCreator ProjectContext ModuleContext
|
||||||
|
fromProjectToModule =
|
||||||
|
Rule.initContextCreator
|
||||||
|
(always
|
||||||
{ nesting = 0
|
{ nesting = 0
|
||||||
, operandsToIgnore = []
|
, operandsToIgnore = []
|
||||||
, elseIfToIgnore = []
|
, elseIfToIgnore = []
|
||||||
@ -233,9 +287,44 @@ initialContext =
|
|||||||
, increases = []
|
, increases = []
|
||||||
, functionsToReport = []
|
, functionsToReport = []
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
expressionEnterVisitor : Node Expression -> Context -> ( List nothing, Context )
|
fromModuleToProject : Rule.ContextCreator ModuleContext ProjectContext
|
||||||
|
fromModuleToProject =
|
||||||
|
Rule.initContextCreator
|
||||||
|
(\moduleName moduleContext ->
|
||||||
|
let
|
||||||
|
recursiveCalls : RecursiveCalls
|
||||||
|
recursiveCalls =
|
||||||
|
computeRecursiveCalls moduleContext
|
||||||
|
in
|
||||||
|
List.foldl
|
||||||
|
(\functionToReport acc ->
|
||||||
|
let
|
||||||
|
allIncreases : List Increase
|
||||||
|
allIncreases =
|
||||||
|
computeIncreases recursiveCalls functionToReport
|
||||||
|
|
||||||
|
finalComplexity : Int
|
||||||
|
finalComplexity =
|
||||||
|
List.foldl (\{ increase } complexity -> increase + complexity) 0 allIncreases
|
||||||
|
in
|
||||||
|
Dict.insert (Node.value functionToReport.functionName) finalComplexity acc
|
||||||
|
)
|
||||||
|
Dict.empty
|
||||||
|
moduleContext.functionsToReport
|
||||||
|
|> Dict.singleton (String.join "." moduleName)
|
||||||
|
)
|
||||||
|
|> Rule.withModuleName
|
||||||
|
|
||||||
|
|
||||||
|
foldProjectContexts : ProjectContext -> ProjectContext -> ProjectContext
|
||||||
|
foldProjectContexts =
|
||||||
|
Dict.union
|
||||||
|
|
||||||
|
|
||||||
|
expressionEnterVisitor : Node Expression -> ModuleContext -> ( List nothing, ModuleContext )
|
||||||
expressionEnterVisitor node context =
|
expressionEnterVisitor node context =
|
||||||
if List.member (Node.range node) context.rangesWhereNestingIncreases then
|
if List.member (Node.range node) context.rangesWhereNestingIncreases then
|
||||||
( [], expressionEnterVisitorHelp node { context | nesting = context.nesting + 1 } )
|
( [], expressionEnterVisitorHelp node { context | nesting = context.nesting + 1 } )
|
||||||
@ -244,7 +333,7 @@ expressionEnterVisitor node context =
|
|||||||
( [], expressionEnterVisitorHelp node context )
|
( [], expressionEnterVisitorHelp node context )
|
||||||
|
|
||||||
|
|
||||||
expressionEnterVisitorHelp : Node Expression -> Context -> Context
|
expressionEnterVisitorHelp : Node Expression -> ModuleContext -> ModuleContext
|
||||||
expressionEnterVisitorHelp node context =
|
expressionEnterVisitorHelp node context =
|
||||||
case Node.value node of
|
case Node.value node of
|
||||||
Expression.IfBlock _ _ else_ ->
|
Expression.IfBlock _ _ else_ ->
|
||||||
@ -398,7 +487,7 @@ incrementAndIgnore parentOperator node =
|
|||||||
( [], [] )
|
( [], [] )
|
||||||
|
|
||||||
|
|
||||||
expressionExitVisitor : Node Expression -> Context -> ( List nothing, Context )
|
expressionExitVisitor : Node Expression -> ModuleContext -> ( List nothing, ModuleContext )
|
||||||
expressionExitVisitor node context =
|
expressionExitVisitor node context =
|
||||||
if List.member (Node.range node) context.rangesWhereNestingIncreases then
|
if List.member (Node.range node) context.rangesWhereNestingIncreases then
|
||||||
( [], expressionExitVisitorHelp node { context | nesting = context.nesting - 1 } )
|
( [], expressionExitVisitorHelp node { context | nesting = context.nesting - 1 } )
|
||||||
@ -407,7 +496,7 @@ expressionExitVisitor node context =
|
|||||||
( [], expressionExitVisitorHelp node context )
|
( [], expressionExitVisitorHelp node context )
|
||||||
|
|
||||||
|
|
||||||
expressionExitVisitorHelp : Node Expression -> Context -> Context
|
expressionExitVisitorHelp : Node Expression -> ModuleContext -> ModuleContext
|
||||||
expressionExitVisitorHelp node context =
|
expressionExitVisitorHelp node context =
|
||||||
case Node.value node of
|
case Node.value node of
|
||||||
Expression.IfBlock _ _ _ ->
|
Expression.IfBlock _ _ _ ->
|
||||||
@ -427,7 +516,7 @@ expressionExitVisitorHelp node context =
|
|||||||
context
|
context
|
||||||
|
|
||||||
|
|
||||||
declarationExitVisitor : Node Declaration -> Context -> ( List (Rule.Error {}), Context )
|
declarationExitVisitor : Node Declaration -> ModuleContext -> ( List (Rule.Error {}), ModuleContext )
|
||||||
declarationExitVisitor node context =
|
declarationExitVisitor node context =
|
||||||
let
|
let
|
||||||
functionsToReport : List FunctionToReport
|
functionsToReport : List FunctionToReport
|
||||||
@ -455,66 +544,28 @@ declarationExitVisitor node context =
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
finalEvaluation : Int -> Context -> List (Rule.Error {})
|
finalModuleEvaluation : Int -> ModuleContext -> List (Rule.Error {})
|
||||||
finalEvaluation threshold context =
|
finalModuleEvaluation threshold context =
|
||||||
let
|
let
|
||||||
potentialRecursiveFunctions : Set String
|
|
||||||
potentialRecursiveFunctions =
|
|
||||||
List.map (.functionName >> Node.value) context.functionsToReport
|
|
||||||
|> Set.fromList
|
|
||||||
|
|
||||||
recursiveCalls : RecursiveCalls
|
recursiveCalls : RecursiveCalls
|
||||||
recursiveCalls =
|
recursiveCalls =
|
||||||
context.functionsToReport
|
computeRecursiveCalls context
|
||||||
|> List.map
|
|
||||||
(\{ functionName, references } ->
|
|
||||||
( Node.value functionName, Dict.filter (\name _ -> Set.member name potentialRecursiveFunctions) references )
|
|
||||||
)
|
|
||||||
|> Dict.fromList
|
|
||||||
|> findRecursiveCalls
|
|
||||||
in
|
in
|
||||||
List.filterMap
|
List.filterMap
|
||||||
(\{ functionName, increases, references } ->
|
(\functionToReport ->
|
||||||
let
|
let
|
||||||
recursiveCallsForFunctionName : List String
|
|
||||||
recursiveCallsForFunctionName =
|
|
||||||
Dict.get (Node.value functionName) recursiveCalls
|
|
||||||
|> Maybe.withDefault Set.empty
|
|
||||||
|> Set.toList
|
|
||||||
|
|
||||||
allIncreases : List Increase
|
allIncreases : List Increase
|
||||||
allIncreases =
|
allIncreases =
|
||||||
List.concat
|
computeIncreases recursiveCalls functionToReport
|
||||||
[ increases
|
|
||||||
, recursiveCallsForFunctionName
|
|
||||||
|> List.filterMap
|
|
||||||
(\referenceToRecursiveFunction ->
|
|
||||||
Dict.get referenceToRecursiveFunction references
|
|
||||||
|> Maybe.map (Tuple.pair referenceToRecursiveFunction)
|
|
||||||
)
|
|
||||||
|> List.map
|
|
||||||
(\( reference, location ) ->
|
|
||||||
{ line = location
|
|
||||||
, increase = 1
|
|
||||||
, nesting = 0
|
|
||||||
, kind =
|
|
||||||
if Node.value functionName == reference then
|
|
||||||
RecursiveCall
|
|
||||||
|
|
||||||
else
|
|
||||||
IndirectRecursiveCall reference
|
|
||||||
}
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
finalComplexity : Int
|
finalComplexity : Int
|
||||||
finalComplexity =
|
finalComplexity =
|
||||||
List.sum (List.map .increase allIncreases)
|
List.foldl (\{ increase } acc -> increase + acc) 0 allIncreases
|
||||||
in
|
in
|
||||||
if finalComplexity > threshold then
|
if finalComplexity > threshold then
|
||||||
Just
|
Just
|
||||||
(Rule.error
|
(Rule.error
|
||||||
{ message = Node.value functionName ++ " has a cognitive complexity of " ++ String.fromInt finalComplexity ++ ", higher than the allowed " ++ String.fromInt threshold
|
{ message = Node.value functionToReport.functionName ++ " has a cognitive complexity of " ++ String.fromInt finalComplexity ++ ", higher than the allowed " ++ String.fromInt threshold
|
||||||
, details =
|
, details =
|
||||||
if List.isEmpty allIncreases then
|
if List.isEmpty allIncreases then
|
||||||
explanation
|
explanation
|
||||||
@ -527,7 +578,7 @@ finalEvaluation threshold context =
|
|||||||
|> String.join "\n"
|
|> String.join "\n"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
(Node.range functionName)
|
(Node.range functionToReport.functionName)
|
||||||
)
|
)
|
||||||
|
|
||||||
else
|
else
|
||||||
@ -536,6 +587,58 @@ finalEvaluation threshold context =
|
|||||||
context.functionsToReport
|
context.functionsToReport
|
||||||
|
|
||||||
|
|
||||||
|
computeIncreases : RecursiveCalls -> FunctionToReport -> List Increase
|
||||||
|
computeIncreases allRecursiveCalls { functionName, increases, references } =
|
||||||
|
case Dict.get (Node.value functionName) allRecursiveCalls of
|
||||||
|
Just recursiveCalls ->
|
||||||
|
Set.foldl
|
||||||
|
(\reference acc ->
|
||||||
|
case Dict.get reference references of
|
||||||
|
Just location ->
|
||||||
|
{ line = location
|
||||||
|
, increase = 1
|
||||||
|
, nesting = 0
|
||||||
|
, kind =
|
||||||
|
if Node.value functionName == reference then
|
||||||
|
RecursiveCall
|
||||||
|
|
||||||
|
else
|
||||||
|
IndirectRecursiveCall reference
|
||||||
|
}
|
||||||
|
:: acc
|
||||||
|
|
||||||
|
Nothing ->
|
||||||
|
acc
|
||||||
|
)
|
||||||
|
increases
|
||||||
|
recursiveCalls
|
||||||
|
|
||||||
|
Nothing ->
|
||||||
|
increases
|
||||||
|
|
||||||
|
|
||||||
|
computeRecursiveCalls : ModuleContext -> RecursiveCalls
|
||||||
|
computeRecursiveCalls context =
|
||||||
|
let
|
||||||
|
potentialRecursiveFunctions : Set String
|
||||||
|
potentialRecursiveFunctions =
|
||||||
|
List.foldl
|
||||||
|
(\fn acc -> Set.insert (Node.value fn.functionName) acc)
|
||||||
|
Set.empty
|
||||||
|
context.functionsToReport
|
||||||
|
in
|
||||||
|
context.functionsToReport
|
||||||
|
|> List.foldl
|
||||||
|
(\{ functionName, references } acc ->
|
||||||
|
Dict.insert
|
||||||
|
(Node.value functionName)
|
||||||
|
(Dict.filter (\name _ -> Set.member name potentialRecursiveFunctions) references)
|
||||||
|
acc
|
||||||
|
)
|
||||||
|
Dict.empty
|
||||||
|
|> findRecursiveCalls
|
||||||
|
|
||||||
|
|
||||||
explanation : List String
|
explanation : List String
|
||||||
explanation =
|
explanation =
|
||||||
[ "This metric is a heuristic to measure how easy to understand a piece of code is, primarily through increments for breaks in the linear flow and for nesting those breaks."
|
[ "This metric is a heuristic to measure how easy to understand a piece of code is, primarily through increments for breaks in the linear flow and for nesting those breaks."
|
||||||
@ -600,9 +703,8 @@ type VisitState
|
|||||||
findRecursiveCalls : Dict String (Dict String a) -> RecursiveCalls
|
findRecursiveCalls : Dict String (Dict String a) -> RecursiveCalls
|
||||||
findRecursiveCalls graph =
|
findRecursiveCalls graph =
|
||||||
graph
|
graph
|
||||||
|> Dict.keys
|
|> Dict.foldl
|
||||||
|> List.foldl
|
(\vertice _ ( recursiveCalls, visited ) ->
|
||||||
(\vertice ( recursiveCalls, visited ) ->
|
|
||||||
let
|
let
|
||||||
res : { recursiveCalls : RecursiveCalls, visited : Visited, stack : List String }
|
res : { recursiveCalls : RecursiveCalls, visited : Visited, stack : List String }
|
||||||
res =
|
res =
|
||||||
@ -671,6 +773,20 @@ processDFSTree graph stack visited =
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
dataExtractor : ProjectContext -> Encode.Value
|
||||||
|
dataExtractor projectContext =
|
||||||
|
Encode.dict identity encodeComplexityDict projectContext
|
||||||
|
|
||||||
|
|
||||||
|
encodeComplexityDict : ComplexityDict -> Encode.Value
|
||||||
|
encodeComplexityDict dict =
|
||||||
|
dict
|
||||||
|
|> Dict.toList
|
||||||
|
|> List.sortBy (Tuple.second >> negate)
|
||||||
|
|> List.map (\( name, complexity ) -> ( name, Encode.int complexity ))
|
||||||
|
|> Encode.object
|
||||||
|
|
||||||
|
|
||||||
insertCycle : List String -> String -> RecursiveCalls -> RecursiveCalls
|
insertCycle : List String -> String -> RecursiveCalls -> RecursiveCalls
|
||||||
insertCycle stack vertice recursiveCalls =
|
insertCycle stack vertice recursiveCalls =
|
||||||
case stack of
|
case stack of
|
||||||
|
@ -16,7 +16,13 @@ all =
|
|||||||
a = 1
|
a = 1
|
||||||
"""
|
"""
|
||||||
|> Review.Test.run (rule 1)
|
|> Review.Test.run (rule 1)
|
||||||
|> Review.Test.expectNoErrors
|
|> Review.Test.expectDataExtract
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"A": {
|
||||||
|
"a": 0
|
||||||
|
}
|
||||||
|
}"""
|
||||||
, test "should report an error when the complexity is higher than the threshold" <|
|
, test "should report an error when the complexity is higher than the threshold" <|
|
||||||
\() ->
|
\() ->
|
||||||
"""module A exposing (..)
|
"""module A exposing (..)
|
||||||
@ -46,6 +52,12 @@ Line 6: +4 for the if expression (including 3 for nesting)
|
|||||||
""" ]
|
""" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"A": {
|
||||||
|
"fun": 10
|
||||||
|
}
|
||||||
|
}"""
|
||||||
, test "should count a simple value or operation as 0" <|
|
, test "should count a simple value or operation as 0" <|
|
||||||
\() ->
|
\() ->
|
||||||
"""module A exposing (..)
|
"""module A exposing (..)
|
||||||
@ -53,6 +65,12 @@ fun n =
|
|||||||
n + 1
|
n + 1
|
||||||
"""
|
"""
|
||||||
|> expect [ { name = "fun", complexity = 0, details = [] } ]
|
|> expect [ { name = "fun", complexity = 0, details = [] } ]
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"A": {
|
||||||
|
"fun": 0
|
||||||
|
}
|
||||||
|
}"""
|
||||||
, test "should count if expression as 1" <|
|
, test "should count if expression as 1" <|
|
||||||
\() ->
|
\() ->
|
||||||
"""module A exposing (..)
|
"""module A exposing (..)
|
||||||
@ -68,6 +86,12 @@ fun n =
|
|||||||
, details = [ "Line 3: +1 for the if expression" ]
|
, details = [ "Line 3: +1 for the if expression" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"A": {
|
||||||
|
"fun": 1
|
||||||
|
}
|
||||||
|
}"""
|
||||||
, test "should count if else expressions" <|
|
, test "should count if else expressions" <|
|
||||||
\() ->
|
\() ->
|
||||||
"""module A exposing (..)
|
"""module A exposing (..)
|
||||||
@ -88,6 +112,12 @@ Line 5: +1 for the else if expression
|
|||||||
""" ]
|
""" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"A": {
|
||||||
|
"fun": 2
|
||||||
|
}
|
||||||
|
}"""
|
||||||
, test "should properly decrement when exiting else expression" <|
|
, test "should properly decrement when exiting else expression" <|
|
||||||
\() ->
|
\() ->
|
||||||
"""module A exposing (..)
|
"""module A exposing (..)
|
||||||
@ -116,6 +146,12 @@ Line 12: +1 for the if expression
|
|||||||
""" ]
|
""" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"A": {
|
||||||
|
"fun": 3
|
||||||
|
}
|
||||||
|
}"""
|
||||||
, test "should increment nesting when inside a let function" <|
|
, test "should increment nesting when inside a let function" <|
|
||||||
\() ->
|
\() ->
|
||||||
"""module A exposing (..)
|
"""module A exposing (..)
|
||||||
@ -137,6 +173,12 @@ Line 5: +2 for the if expression (including 1 for nesting)
|
|||||||
""" ]
|
""" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"A": {
|
||||||
|
"fun": 2
|
||||||
|
}
|
||||||
|
}"""
|
||||||
, test "should properly decrement nesting when exiting a let function" <|
|
, test "should properly decrement nesting when exiting a let function" <|
|
||||||
\() ->
|
\() ->
|
||||||
"""module A exposing (..)
|
"""module A exposing (..)
|
||||||
@ -161,6 +203,12 @@ Line 8: +1 for the if expression
|
|||||||
""" ]
|
""" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"A": {
|
||||||
|
"fun": 1
|
||||||
|
}
|
||||||
|
}"""
|
||||||
, test "should count case expression as 1" <|
|
, test "should count case expression as 1" <|
|
||||||
\() ->
|
\() ->
|
||||||
"""module A exposing (..)
|
"""module A exposing (..)
|
||||||
@ -178,6 +226,12 @@ fun n =
|
|||||||
, details = [ "Line 3: +1 for the case expression" ]
|
, details = [ "Line 3: +1 for the case expression" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"A": {
|
||||||
|
"fun": 1
|
||||||
|
}
|
||||||
|
}"""
|
||||||
, test "should count nesting of case expressions" <|
|
, test "should count nesting of case expressions" <|
|
||||||
\() ->
|
\() ->
|
||||||
"""module A exposing (..)
|
"""module A exposing (..)
|
||||||
@ -199,6 +253,12 @@ Line 6: +2 for the case expression (including 1 for nesting)
|
|||||||
""" ]
|
""" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"A": {
|
||||||
|
"fun": 3
|
||||||
|
}
|
||||||
|
}"""
|
||||||
, test "should decrement the nesting when leaving a nested structure" <|
|
, test "should decrement the nesting when leaving a nested structure" <|
|
||||||
\() ->
|
\() ->
|
||||||
"""module A exposing (..)
|
"""module A exposing (..)
|
||||||
@ -226,6 +286,12 @@ Line 12: +2 for the case expression (including 1 for nesting)
|
|||||||
""" ]
|
""" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"A": {
|
||||||
|
"fun": 8
|
||||||
|
}
|
||||||
|
}"""
|
||||||
, test "should increment once when using the && boolean operator" <|
|
, test "should increment once when using the && boolean operator" <|
|
||||||
\() ->
|
\() ->
|
||||||
"""module A exposing (..)
|
"""module A exposing (..)
|
||||||
@ -246,6 +312,12 @@ Line 4: +1 for the use of `&&`
|
|||||||
""" ]
|
""" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"A": {
|
||||||
|
"fun": 2
|
||||||
|
}
|
||||||
|
}"""
|
||||||
, test "should increment once when using the || boolean operator" <|
|
, test "should increment once when using the || boolean operator" <|
|
||||||
\() ->
|
\() ->
|
||||||
"""module A exposing (..)
|
"""module A exposing (..)
|
||||||
@ -266,6 +338,12 @@ Line 4: +1 for the use of `||`
|
|||||||
""" ]
|
""" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"A": {
|
||||||
|
"fun": 2
|
||||||
|
}
|
||||||
|
}"""
|
||||||
, test "should increment when mixing boolean operators" <|
|
, test "should increment when mixing boolean operators" <|
|
||||||
\() ->
|
\() ->
|
||||||
"""module A exposing (..)
|
"""module A exposing (..)
|
||||||
@ -290,6 +368,12 @@ Line 5: +1 for the use of `&&`
|
|||||||
""" ]
|
""" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"A": {
|
||||||
|
"fun": 4
|
||||||
|
}
|
||||||
|
}"""
|
||||||
, test "should not increment for anonymous functions" <|
|
, test "should not increment for anonymous functions" <|
|
||||||
\() ->
|
\() ->
|
||||||
"""module A exposing (..)
|
"""module A exposing (..)
|
||||||
@ -302,6 +386,12 @@ fun n =
|
|||||||
, details = []
|
, details = []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"A": {
|
||||||
|
"fun": 0
|
||||||
|
}
|
||||||
|
}"""
|
||||||
, test "should increment the nesting inside anonymous functions" <|
|
, test "should increment the nesting inside anonymous functions" <|
|
||||||
\() ->
|
\() ->
|
||||||
"""module A exposing (..)
|
"""module A exposing (..)
|
||||||
@ -322,6 +412,12 @@ fun n =
|
|||||||
, details = [ "Line 5: +2 for the if expression (including 1 for nesting)" ]
|
, details = [ "Line 5: +2 for the if expression (including 1 for nesting)" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"A": {
|
||||||
|
"fun": 2
|
||||||
|
}
|
||||||
|
}"""
|
||||||
, test "should properly decrement the nesting when exiting an anonymous function" <|
|
, test "should properly decrement the nesting when exiting an anonymous function" <|
|
||||||
\() ->
|
\() ->
|
||||||
"""module A exposing (..)
|
"""module A exposing (..)
|
||||||
@ -340,6 +436,12 @@ fun n =
|
|||||||
, details = [ "Line 6: +1 for the if expression" ]
|
, details = [ "Line 6: +1 for the if expression" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"A": {
|
||||||
|
"fun": 1
|
||||||
|
}
|
||||||
|
}"""
|
||||||
, test "should increment when finding a recursive call" <|
|
, test "should increment when finding a recursive call" <|
|
||||||
\() ->
|
\() ->
|
||||||
"""module A exposing (..)
|
"""module A exposing (..)
|
||||||
@ -359,6 +461,12 @@ Line 4: +1 for the recursive call
|
|||||||
""" ]
|
""" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"A": {
|
||||||
|
"fun": 2
|
||||||
|
}
|
||||||
|
}"""
|
||||||
, test "should only increment once, even if there are multiple recursive calls" <|
|
, test "should only increment once, even if there are multiple recursive calls" <|
|
||||||
\() ->
|
\() ->
|
||||||
"""module A exposing (..)
|
"""module A exposing (..)
|
||||||
@ -379,6 +487,12 @@ Line 4: +1 for the recursive call
|
|||||||
""" ]
|
""" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"A": {
|
||||||
|
"fib": 2
|
||||||
|
}
|
||||||
|
}"""
|
||||||
, test "should increment the complexity for every recursive call in a chain" <|
|
, test "should increment the complexity for every recursive call in a chain" <|
|
||||||
\() ->
|
\() ->
|
||||||
"""module A exposing (..)
|
"""module A exposing (..)
|
||||||
@ -404,6 +518,12 @@ Line 6: +1 for the indirect recursive call to fun1
|
|||||||
""" ]
|
""" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
"""{
|
||||||
|
"A": {
|
||||||
|
"fun1": 1,
|
||||||
|
"fun2": 1
|
||||||
|
}
|
||||||
|
}"""
|
||||||
, test "should increment the complexity for every recursive call in a chain, for each different function call" <|
|
, test "should increment the complexity for every recursive call in a chain, for each different function call" <|
|
||||||
\() ->
|
\() ->
|
||||||
"""module A exposing (..)
|
"""module A exposing (..)
|
||||||
@ -434,6 +554,12 @@ Line 8: +1 for the indirect recursive call to fun1
|
|||||||
""" ]
|
""" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
""" {
|
||||||
|
"A": {
|
||||||
|
"fun1": 2,
|
||||||
|
"fun2": 1
|
||||||
|
}
|
||||||
|
}"""
|
||||||
, test "should increment the complexity for every recursive call in a chain, for long chains" <|
|
, test "should increment the complexity for every recursive call in a chain, for long chains" <|
|
||||||
\() ->
|
\() ->
|
||||||
"""module A exposing (..)
|
"""module A exposing (..)
|
||||||
@ -475,6 +601,16 @@ fun5 n =
|
|||||||
, details = [ "Line 11: +1 for the indirect recursive call to fun1" ]
|
, details = [ "Line 11: +1 for the indirect recursive call to fun1" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"A": {
|
||||||
|
"fun1": 1,
|
||||||
|
"fun2": 1,
|
||||||
|
"fun3": 1,
|
||||||
|
"fun4": 1,
|
||||||
|
"fun5": 1
|
||||||
|
}
|
||||||
|
}"""
|
||||||
, test "the complexity of a function should not affect another function's computed complexity" <|
|
, test "the complexity of a function should not affect another function's computed complexity" <|
|
||||||
\() ->
|
\() ->
|
||||||
"""module A exposing (..)
|
"""module A exposing (..)
|
||||||
@ -512,14 +648,22 @@ Line 6: +2 for the if expression (including 1 for nesting)
|
|||||||
, details = [ "Line 14: +1 for the if expression" ]
|
, details = [ "Line 14: +1 for the if expression" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
"""{
|
||||||
|
"A": {
|
||||||
|
"fun": 3,
|
||||||
|
"alsoSimple": 1,
|
||||||
|
"simple": 0
|
||||||
|
}
|
||||||
|
}"""
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
expect : List { name : String, complexity : Int, details : List String } -> String -> Expectation
|
expect : List { name : String, complexity : Int, details : List String } -> String -> String -> Expectation
|
||||||
expect functionComplexities source =
|
expect functionComplexities dataExtract source =
|
||||||
source
|
source
|
||||||
|> Review.Test.run (rule -1)
|
|> Review.Test.run (rule -1)
|
||||||
|> Review.Test.expectErrors
|
|> Review.Test.expect
|
||||||
|
[ Review.Test.moduleErrors "A"
|
||||||
(List.map
|
(List.map
|
||||||
(\{ name, complexity, details } ->
|
(\{ name, complexity, details } ->
|
||||||
Review.Test.error
|
Review.Test.error
|
||||||
@ -530,13 +674,16 @@ expect functionComplexities source =
|
|||||||
)
|
)
|
||||||
functionComplexities
|
functionComplexities
|
||||||
)
|
)
|
||||||
|
, Review.Test.dataExtract dataExtract
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
expectAtExactly : List { name : String, complexity : Int, details : List String, atExactly : Range } -> String -> Expectation
|
expectAtExactly : List { name : String, complexity : Int, details : List String, atExactly : Range } -> String -> String -> Expectation
|
||||||
expectAtExactly functionComplexities source =
|
expectAtExactly functionComplexities dataExtract source =
|
||||||
source
|
source
|
||||||
|> Review.Test.run (rule -1)
|
|> Review.Test.run (rule -1)
|
||||||
|> Review.Test.expectErrors
|
|> Review.Test.expect
|
||||||
|
[ Review.Test.moduleErrors "A"
|
||||||
(List.map
|
(List.map
|
||||||
(\{ name, complexity, details, atExactly } ->
|
(\{ name, complexity, details, atExactly } ->
|
||||||
Review.Test.error
|
Review.Test.error
|
||||||
@ -548,6 +695,8 @@ expectAtExactly functionComplexities source =
|
|||||||
)
|
)
|
||||||
functionComplexities
|
functionComplexities
|
||||||
)
|
)
|
||||||
|
, Review.Test.dataExtract dataExtract
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
explanation : List String
|
explanation : List String
|
||||||
|
Loading…
Reference in New Issue
Block a user