mirror of
https://github.com/jfmengels/elm-review.git
synced 2024-08-16 19:40:22 +03:00
Compare commits
6 Commits
57fa48e5f2
...
5573f0a666
Author | SHA1 | Date | |
---|---|---|---|
|
5573f0a666 | ||
|
60a15216a9 | ||
|
cf2a955d40 | ||
|
2ee409c546 | ||
|
f01508a61c | ||
|
b7842e704b |
2
elm.json
2
elm.json
@ -20,7 +20,7 @@
|
||||
"elm/json": "1.1.3 <= v < 2.0.0",
|
||||
"elm/project-metadata-utils": "1.0.0 <= v < 2.0.0",
|
||||
"elm-explorations/test": "2.0.1 <= v < 3.0.0",
|
||||
"stil4m/elm-syntax": "7.2.7 <= v < 8.0.0"
|
||||
"stil4m/elm-syntax": "7.3.2 <= v < 8.0.0"
|
||||
},
|
||||
"test-dependencies": {
|
||||
"elm/parser": "1.1.0 <= v < 2.0.0",
|
||||
|
@ -594,19 +594,22 @@ registerDeclaration declaration innerContext =
|
||||
|> registerIfExposed (\name ctx -> registerExposedValue function name ctx) (Node.value nameNode)
|
||||
|
||||
Declaration.AliasDeclaration alias ->
|
||||
{ innerContext | localTypes = Set.insert (Node.value alias.name) innerContext.localTypes }
|
||||
|> (\ctx ->
|
||||
case Node.value alias.typeAnnotation of
|
||||
TypeAnnotation.Record _ ->
|
||||
addToScope
|
||||
{ variableType = TopLevelVariable
|
||||
, node = alias.name
|
||||
}
|
||||
ctx
|
||||
|
||||
_ ->
|
||||
let
|
||||
registerAlias : Context -> Context
|
||||
registerAlias ctx =
|
||||
case Node.value alias.typeAnnotation of
|
||||
TypeAnnotation.Record _ ->
|
||||
addToScope
|
||||
{ variableType = TopLevelVariable
|
||||
, node = alias.name
|
||||
}
|
||||
ctx
|
||||
)
|
||||
|
||||
_ ->
|
||||
ctx
|
||||
in
|
||||
{ innerContext | localTypes = Set.insert (Node.value alias.name) innerContext.localTypes }
|
||||
|> registerAlias
|
||||
|> registerIfExposed registerExposedTypeAlias (Node.value alias.name)
|
||||
|
||||
Declaration.CustomTypeDeclaration { name, constructors } ->
|
||||
|
@ -1368,7 +1368,7 @@ mergeModuleVisitorsHelp ruleName_ initialProjectContext moduleContextCreator vis
|
||||
)
|
||||
emptyModuleVisitor
|
||||
visitors
|
||||
|> (\(ModuleRuleSchema moduleVisitorSchema) -> ModuleRuleSchema moduleVisitorSchema)
|
||||
|> removeExtensibleRecordFromModuleRuleSchema
|
||||
, moduleContextCreator
|
||||
)
|
||||
|
||||
@ -1408,7 +1408,12 @@ removeExtensibleRecordTypeVariable :
|
||||
(ModuleRuleSchema {} moduleContext -> ModuleRuleSchema { a | hasAtLeastOneVisitor : () } moduleContext)
|
||||
-> (ModuleRuleSchema {} moduleContext -> ModuleRuleSchema { hasAtLeastOneVisitor : () } moduleContext)
|
||||
removeExtensibleRecordTypeVariable function =
|
||||
function >> (\(ModuleRuleSchema param) -> ModuleRuleSchema param)
|
||||
function >> removeExtensibleRecordFromModuleRuleSchema
|
||||
|
||||
|
||||
removeExtensibleRecordFromModuleRuleSchema : ModuleRuleSchema schemaState moduleContext -> ModuleRuleSchema a moduleContext
|
||||
removeExtensibleRecordFromModuleRuleSchema (ModuleRuleSchema param) =
|
||||
ModuleRuleSchema param
|
||||
|
||||
|
||||
{-| Creates a rule that will **only** report a configuration error, which stops `elm-review` from reviewing the project
|
||||
@ -1903,7 +1908,7 @@ and by reading the value at `<output>.extracts["YourRuleName"]` in the output.
|
||||
|> Rule.withDataExtractor dataExtractor
|
||||
|> Rule.fromProjectRuleSchema
|
||||
|
||||
dataExtractor : ProjectContext -> Encode.Value
|
||||
dataExtractor : ProjectContext -> Json.Encode.Value
|
||||
dataExtractor projectContext =
|
||||
Json.Encode.list
|
||||
(\thing ->
|
||||
|
@ -337,32 +337,7 @@ expressionEnterVisitorHelp : Node Expression -> ModuleContext -> ModuleContext
|
||||
expressionEnterVisitorHelp node context =
|
||||
case Node.value node of
|
||||
Expression.IfBlock _ _ else_ ->
|
||||
if not (List.member (Node.range node) context.elseIfToIgnore) then
|
||||
{ context
|
||||
| increases =
|
||||
{ line = (Node.range node).start
|
||||
, increase = context.nesting + 1
|
||||
, nesting = context.nesting
|
||||
, kind = If
|
||||
}
|
||||
:: context.increases
|
||||
, nesting = context.nesting + 1
|
||||
, elseIfToIgnore = Node.range else_ :: context.elseIfToIgnore
|
||||
}
|
||||
|
||||
else
|
||||
-- This if expression is an else if
|
||||
-- We want to increase the complexity but keep the same nesting as the parent if
|
||||
{ context
|
||||
| increases =
|
||||
{ line = (Node.range node).start
|
||||
, increase = context.nesting
|
||||
, nesting = context.nesting - 1
|
||||
, kind = ElseIf
|
||||
}
|
||||
:: context.increases
|
||||
, elseIfToIgnore = Node.range else_ :: context.elseIfToIgnore
|
||||
}
|
||||
visitElseExpression (Node.range node) else_ context
|
||||
|
||||
Expression.CaseExpression _ ->
|
||||
{ context
|
||||
@ -408,21 +383,62 @@ expressionEnterVisitorHelp node context =
|
||||
{ context | nesting = context.nesting + 1 }
|
||||
|
||||
Expression.FunctionOrValue [] name ->
|
||||
{ context
|
||||
| references =
|
||||
if Dict.member name context.references then
|
||||
-- The reference already exists, and we want to keep the first reference
|
||||
-- for a better presentation
|
||||
context.references
|
||||
if isFunctionReference name then
|
||||
{ context
|
||||
| references =
|
||||
if Dict.member name context.references then
|
||||
-- The reference already exists, and we want to keep the first reference
|
||||
-- for a better presentation
|
||||
context.references
|
||||
|
||||
else
|
||||
Dict.insert name (Node.range node).start context.references
|
||||
}
|
||||
else
|
||||
Dict.insert name (Node.range node).start context.references
|
||||
}
|
||||
|
||||
else
|
||||
context
|
||||
|
||||
_ ->
|
||||
context
|
||||
|
||||
|
||||
visitElseExpression : Range -> Node a -> ModuleContext -> ModuleContext
|
||||
visitElseExpression ifExprRange else_ context =
|
||||
if not (List.member ifExprRange context.elseIfToIgnore) then
|
||||
{ context
|
||||
| increases =
|
||||
{ line = ifExprRange.start
|
||||
, increase = context.nesting + 1
|
||||
, nesting = context.nesting
|
||||
, kind = If
|
||||
}
|
||||
:: context.increases
|
||||
, nesting = context.nesting + 1
|
||||
, elseIfToIgnore = Node.range else_ :: context.elseIfToIgnore
|
||||
}
|
||||
|
||||
else
|
||||
-- This if expression is an else if
|
||||
-- We want to increase the complexity but keep the same nesting as the parent if
|
||||
{ context
|
||||
| increases =
|
||||
{ line = ifExprRange.start
|
||||
, increase = context.nesting
|
||||
, nesting = context.nesting - 1
|
||||
, kind = ElseIf
|
||||
}
|
||||
:: context.increases
|
||||
, elseIfToIgnore = Node.range else_ :: context.elseIfToIgnore
|
||||
}
|
||||
|
||||
|
||||
isFunctionReference : String -> Bool
|
||||
isFunctionReference name =
|
||||
name
|
||||
|> String.left 1
|
||||
|> String.all Char.isLower
|
||||
|
||||
|
||||
computeRangesForLetDeclarations : List (Node Expression.LetDeclaration) -> List Range
|
||||
computeRangesForLetDeclarations declarations =
|
||||
List.filterMap
|
||||
@ -704,19 +720,18 @@ findRecursiveCalls : Dict String (Dict String a) -> RecursiveCalls
|
||||
findRecursiveCalls graph =
|
||||
graph
|
||||
|> Dict.foldl
|
||||
(\vertice _ ( recursiveCalls, visited ) ->
|
||||
(\vertice _ recursiveCalls ->
|
||||
let
|
||||
res : { recursiveCalls : RecursiveCalls, visited : Visited, stack : List String }
|
||||
res =
|
||||
processDFSTree
|
||||
graph
|
||||
[ vertice ]
|
||||
(Dict.insert vertice InStack visited)
|
||||
(Dict.singleton vertice InStack)
|
||||
in
|
||||
( mergeRecursiveCallsDict res.recursiveCalls recursiveCalls, res.visited )
|
||||
mergeRecursiveCallsDict res.recursiveCalls recursiveCalls
|
||||
)
|
||||
( Dict.empty, Dict.empty )
|
||||
|> Tuple.first
|
||||
Dict.empty
|
||||
|
||||
|
||||
mergeRecursiveCallsDict : RecursiveCalls -> RecursiveCalls -> RecursiveCalls
|
||||
@ -732,45 +747,53 @@ mergeRecursiveCallsDict left right =
|
||||
|
||||
processDFSTree : Dict String (Dict String a) -> List String -> Visited -> { recursiveCalls : RecursiveCalls, visited : Visited, stack : List String }
|
||||
processDFSTree graph stack visited =
|
||||
let
|
||||
vertices : List String
|
||||
vertices =
|
||||
List.head stack
|
||||
|> Maybe.andThen (\v -> Dict.get v graph)
|
||||
|> Maybe.withDefault Dict.empty
|
||||
|> Dict.keys
|
||||
in
|
||||
List.foldl
|
||||
(\vertice acc ->
|
||||
case Dict.get vertice visited of
|
||||
Just InStack ->
|
||||
{ acc | recursiveCalls = insertCycle stack vertice acc.recursiveCalls }
|
||||
case stack of
|
||||
[] ->
|
||||
{ recursiveCalls = Dict.empty, visited = visited, stack = [] }
|
||||
|
||||
Just Done ->
|
||||
acc
|
||||
head :: restOfStack ->
|
||||
let
|
||||
vertices : List String
|
||||
vertices =
|
||||
Dict.get head graph
|
||||
|> Maybe.withDefault Dict.empty
|
||||
|> Dict.keys
|
||||
in
|
||||
List.foldl
|
||||
(\vertice acc ->
|
||||
case Dict.get vertice visited of
|
||||
Just InStack ->
|
||||
{ acc | recursiveCalls = insertCycle stack vertice acc.recursiveCalls }
|
||||
|
||||
Nothing ->
|
||||
let
|
||||
res : { recursiveCalls : RecursiveCalls, visited : Visited, stack : List String }
|
||||
res =
|
||||
processDFSTree
|
||||
graph
|
||||
(vertice :: stack)
|
||||
(Dict.insert vertice InStack visited)
|
||||
in
|
||||
{ recursiveCalls = mergeRecursiveCallsDict res.recursiveCalls acc.recursiveCalls, visited = res.visited }
|
||||
)
|
||||
{ recursiveCalls = Dict.empty, visited = visited }
|
||||
vertices
|
||||
|> (\res ->
|
||||
{ recursiveCalls = res.recursiveCalls
|
||||
, visited =
|
||||
List.head stack
|
||||
|> Maybe.map (\v -> Dict.insert v Done res.visited)
|
||||
|> Maybe.withDefault res.visited
|
||||
, stack = List.drop 1 stack
|
||||
}
|
||||
)
|
||||
Just Done ->
|
||||
acc
|
||||
|
||||
Nothing ->
|
||||
let
|
||||
res : { recursiveCalls : RecursiveCalls, visited : Visited, stack : List String }
|
||||
res =
|
||||
processDFSTree
|
||||
graph
|
||||
(vertice :: stack)
|
||||
(Dict.insert vertice InStack visited)
|
||||
in
|
||||
{ recursiveCalls = mergeRecursiveCallsDict res.recursiveCalls acc.recursiveCalls, visited = res.visited }
|
||||
)
|
||||
{ recursiveCalls = Dict.empty, visited = visited }
|
||||
vertices
|
||||
|> updateStack head restOfStack
|
||||
|
||||
|
||||
updateStack :
|
||||
String
|
||||
-> List String
|
||||
-> { recursiveCalls : RecursiveCalls, visited : Visited }
|
||||
-> { recursiveCalls : RecursiveCalls, visited : Visited, stack : List String }
|
||||
updateStack head stack res =
|
||||
{ recursiveCalls = res.recursiveCalls
|
||||
, visited = Dict.insert head Done res.visited
|
||||
, stack = stack
|
||||
}
|
||||
|
||||
|
||||
dataExtractor : ProjectContext -> Encode.Value
|
||||
|
@ -611,6 +611,41 @@ fun5 n =
|
||||
"fun5": 1
|
||||
}
|
||||
}"""
|
||||
, test "recursive call complexity should not depend on alphabetical order" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
|
||||
b () = b ()
|
||||
|
||||
a = b ()
|
||||
|
||||
c = b ()
|
||||
"""
|
||||
|> expectAtExactly
|
||||
[ { name = "a"
|
||||
, complexity = 1
|
||||
, atExactly = { start = { row = 5, column = 1 }, end = { row = 5, column = 2 } }
|
||||
, details = [ "Line 5: +1 for the indirect recursive call to b" ]
|
||||
}
|
||||
, { name = "b"
|
||||
, complexity = 1
|
||||
, atExactly = { start = { row = 3, column = 1 }, end = { row = 3, column = 2 } }
|
||||
, details = [ "Line 3: +1 for the recursive call" ]
|
||||
}
|
||||
, { name = "c"
|
||||
, complexity = 1
|
||||
, atExactly = { start = { row = 7, column = 1 }, end = { row = 7, column = 2 } }
|
||||
, details = [ "Line 7: +1 for the indirect recursive call to b" ]
|
||||
}
|
||||
]
|
||||
"""
|
||||
{
|
||||
"A": {
|
||||
"a": 1,
|
||||
"b": 1,
|
||||
"c": 1
|
||||
}
|
||||
}"""
|
||||
, test "the complexity of a function should not affect another function's computed complexity" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
module NoUnused.ExportsTest exposing (all)
|
||||
|
||||
import NoUnused.Exports exposing (rule)
|
||||
import NoUnused.Exports exposing (annotatedBy, defaults, definedInModule, prefixedBy, reportUnusedProductionExports, rule, suffixedBy, toRule)
|
||||
import Review.Test
|
||||
import Test exposing (Test, describe, test)
|
||||
import TestProject exposing (application, lamderaApplication, package)
|
||||
@ -28,6 +28,7 @@ all =
|
||||
, importsTests
|
||||
, lamderaTests
|
||||
, unusedModuleTests
|
||||
, reportUnusedProductionExportsTest
|
||||
|
||||
-- TODO Add tests that report exposing the type's variants if they are never used.
|
||||
]
|
||||
@ -1219,3 +1220,332 @@ main = text ""
|
||||
|> Review.Test.runWithProjectData lamderaApplication rule
|
||||
|> Review.Test.expectNoErrors
|
||||
]
|
||||
|
||||
|
||||
reportUnusedProductionExportsTest : Test
|
||||
reportUnusedProductionExportsTest =
|
||||
describe "reportUnusedProductionExports"
|
||||
[ test "should report functions that are only used in ignored files (no helpers defined)" <|
|
||||
\() ->
|
||||
[ """
|
||||
module Main exposing (main)
|
||||
import A
|
||||
main = A.used
|
||||
""", """
|
||||
module A exposing (used, unusedInProductionCode)
|
||||
used = 1
|
||||
unusedInProductionCode = 2
|
||||
""", """
|
||||
module ATest exposing (..)
|
||||
import A
|
||||
import Test
|
||||
a = A.unusedInProductionCode
|
||||
""" ]
|
||||
|> Review.Test.runOnModules
|
||||
(defaults
|
||||
|> reportUnusedProductionExports
|
||||
{ isProductionFile = \{ moduleName } -> String.join "." moduleName |> String.endsWith "Test" |> not
|
||||
, exceptionsAre = []
|
||||
}
|
||||
|> toRule
|
||||
)
|
||||
|> Review.Test.expectErrorsForModules
|
||||
[ ( "A"
|
||||
, [ Review.Test.error
|
||||
{ message = "Exposed function or value `unusedInProductionCode` is never used in production code."
|
||||
, details =
|
||||
[ "This exposed element is only used in files you have marked as non-production code (e.g. the tests folder), and should therefore be removed along with the places it's used in. This will help reduce the amount of code you will need to maintain."
|
||||
, "It is possible that this element is meant to enable work in your ignored folder (test helpers for instance), in which case you should keep it. To avoid this problem being reported again, please read the documentation on how to configure the rule."
|
||||
]
|
||||
, under = "unusedInProductionCode"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 2, column = 26 }, end = { row = 2, column = 48 } }
|
||||
]
|
||||
)
|
||||
]
|
||||
, test "should report functions that are only used in ignored files (helpers defined)" <|
|
||||
\() ->
|
||||
[ """
|
||||
module Main exposing (main)
|
||||
import A
|
||||
main = A.used
|
||||
""", """
|
||||
module A exposing (used, unusedInProductionCode)
|
||||
used = 1
|
||||
unusedInProductionCode = 2
|
||||
""", """
|
||||
module ATest exposing (..)
|
||||
import A
|
||||
import Test
|
||||
a = A.unusedInProductionCode
|
||||
""" ]
|
||||
|> Review.Test.runOnModules
|
||||
(defaults
|
||||
|> reportUnusedProductionExports
|
||||
{ isProductionFile = \{ moduleName } -> String.join "." moduleName |> String.endsWith "Test" |> not
|
||||
, exceptionsAre =
|
||||
[ annotatedBy "@helper"
|
||||
, annotatedBy "@test-helper"
|
||||
, suffixedBy "_FOR_TESTS"
|
||||
, prefixedBy "test_"
|
||||
]
|
||||
}
|
||||
|> toRule
|
||||
)
|
||||
|> Review.Test.expectErrorsForModules
|
||||
[ ( "A"
|
||||
, [ Review.Test.error
|
||||
{ message = "Exposed function or value `unusedInProductionCode` is never used in production code."
|
||||
, details =
|
||||
[ "This exposed element is only used in files you have marked as non-production code (e.g. the tests folder), and should therefore be removed along with the places it's used in. This will help reduce the amount of code you will need to maintain."
|
||||
, "It is possible that this element is meant to enable work in your ignored folder (test helpers for instance), in which case you should keep it. To avoid this problem being reported again, you can:"
|
||||
, """- Include @helper in the documentation of the element
|
||||
- Include @test-helper in the documentation of the element
|
||||
- Rename the element to end with _FOR_TESTS
|
||||
- Rename the element to start with test_"""
|
||||
]
|
||||
, under = "unusedInProductionCode"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 2, column = 26 }, end = { row = 2, column = 48 } }
|
||||
]
|
||||
)
|
||||
]
|
||||
, test "should not report exposed tests even if they're in an ignored module" <|
|
||||
\() ->
|
||||
[ """
|
||||
module Main exposing (main)
|
||||
main = 1
|
||||
""", """
|
||||
module ATest exposing (tests)
|
||||
import Test exposing (Test)
|
||||
tests : Test
|
||||
tests = Test.describe "thing" []
|
||||
""" ]
|
||||
|> Review.Test.runOnModules
|
||||
(defaults
|
||||
|> reportUnusedProductionExports
|
||||
{ isProductionFile = \{ moduleName } -> String.join "." moduleName |> String.endsWith "Test" |> not
|
||||
, exceptionsAre = []
|
||||
}
|
||||
|> toRule
|
||||
)
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should not report elements from ignored modules used in other ignored modules exposed tests even if they're in an ignored module" <|
|
||||
\() ->
|
||||
[ """
|
||||
module ATest exposing (tests)
|
||||
import BTest
|
||||
import Test exposing (Test)
|
||||
tests : Test
|
||||
tests = Test.describe "thing" BTest.helper
|
||||
""", """
|
||||
module BTest exposing (helper)
|
||||
helper = 1
|
||||
""" ]
|
||||
|> Review.Test.runOnModules
|
||||
(defaults
|
||||
|> reportUnusedProductionExports
|
||||
{ isProductionFile = \{ moduleName } -> String.join "." moduleName |> String.endsWith "Test" |> not
|
||||
, exceptionsAre = []
|
||||
}
|
||||
|> toRule
|
||||
)
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should not report elements only used in ignored modules if they're annotated with a tag" <|
|
||||
\() ->
|
||||
[ """
|
||||
module ATest exposing (tests)
|
||||
import B
|
||||
import Test exposing (Test)
|
||||
tests : Test
|
||||
tests = Test.describe "thing" B.helper
|
||||
""", """
|
||||
module B exposing (helper)
|
||||
{-| @ignore-helper -}
|
||||
helper = 1
|
||||
""" ]
|
||||
|> Review.Test.runOnModules
|
||||
(defaults
|
||||
|> reportUnusedProductionExports
|
||||
{ isProductionFile = \{ moduleName } -> String.join "." moduleName |> String.endsWith "Test" |> not
|
||||
, exceptionsAre = [ annotatedBy "@ignore-helper" ]
|
||||
}
|
||||
|> toRule
|
||||
)
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should not report elements from ignored modules if they're imported only in tests but also used locally in the module" <|
|
||||
\() ->
|
||||
[ """
|
||||
module Main exposing (main)
|
||||
import B
|
||||
main = B.exposed
|
||||
""", """
|
||||
module ATest exposing (tests)
|
||||
import B
|
||||
import Test exposing (Test)
|
||||
tests : Test
|
||||
tests = Test.describe "thing" B.usedLocally
|
||||
""", """
|
||||
module B exposing (exposed, usedLocally)
|
||||
exposed = usedLocally + 1
|
||||
usedLocally = 1
|
||||
""" ]
|
||||
|> Review.Test.runOnModules
|
||||
(defaults
|
||||
|> reportUnusedProductionExports
|
||||
{ isProductionFile = \{ moduleName } -> String.join "." moduleName |> String.endsWith "Test" |> not
|
||||
, exceptionsAre = []
|
||||
}
|
||||
|> toRule
|
||||
)
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should report elements never used anywhere even if they're annotated with a tag" <|
|
||||
\() ->
|
||||
[ """
|
||||
module ATest exposing (tests)
|
||||
import Test exposing (Test)
|
||||
import B
|
||||
tests : Test
|
||||
tests = Test.describe "thing" []
|
||||
""", """
|
||||
module B exposing (helper)
|
||||
{-| @ignore-helper -}
|
||||
helper = 1
|
||||
""" ]
|
||||
|> Review.Test.runOnModules
|
||||
(defaults
|
||||
|> reportUnusedProductionExports
|
||||
{ isProductionFile = \{ moduleName } -> String.join "." moduleName |> String.endsWith "Test" |> not
|
||||
, exceptionsAre = []
|
||||
}
|
||||
|> toRule
|
||||
)
|
||||
|> Review.Test.expectErrorsForModules
|
||||
[ ( "B"
|
||||
, [ Review.Test.error
|
||||
{ message = "Exposed function or value `helper` is never used outside this module."
|
||||
, details = unusedExposedElementDetails
|
||||
, under = "helper"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 2, column = 20 }, end = { row = 2, column = 26 } }
|
||||
]
|
||||
)
|
||||
]
|
||||
, test "should report elements never used anywhere even if their name ends with the configured suffix" <|
|
||||
\() ->
|
||||
[ """
|
||||
module Main exposing (main)
|
||||
import B
|
||||
main = B.b
|
||||
""", """
|
||||
module ATest exposing (tests)
|
||||
import B
|
||||
import Test exposing (Test)
|
||||
tests : Test
|
||||
tests = Test.describe "thing" B.helperTEST
|
||||
""", """
|
||||
module B exposing (b, helperTEST)
|
||||
b = 1
|
||||
helperTEST = 1
|
||||
""" ]
|
||||
|> Review.Test.runOnModules
|
||||
(defaults
|
||||
|> reportUnusedProductionExports
|
||||
{ isProductionFile = \{ moduleName } -> String.join "." moduleName |> String.endsWith "Test" |> not
|
||||
, exceptionsAre = [ suffixedBy "TEST" ]
|
||||
}
|
||||
|> toRule
|
||||
)
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should report elements never used anywhere even if their name starts with the configured suffix" <|
|
||||
\() ->
|
||||
[ """
|
||||
module Main exposing (main)
|
||||
import B
|
||||
main = B.b
|
||||
""", """
|
||||
module ATest exposing (tests)
|
||||
import B
|
||||
import Test exposing (Test)
|
||||
tests : Test
|
||||
tests = Test.describe "thing" B.test_helper
|
||||
""", """
|
||||
module B exposing (b, test_helper)
|
||||
b = 1
|
||||
test_helper = 1
|
||||
""" ]
|
||||
|> Review.Test.runOnModules
|
||||
(defaults
|
||||
|> reportUnusedProductionExports
|
||||
{ isProductionFile = \{ moduleName } -> String.join "." moduleName |> String.endsWith "Test" |> not
|
||||
, exceptionsAre = [ prefixedBy "test_" ]
|
||||
}
|
||||
|> toRule
|
||||
)
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should report elements never used anywhere even if they're defined in a module marked as an exception" <|
|
||||
\() ->
|
||||
[ """
|
||||
module Main exposing (main)
|
||||
import Project.Utils.B as B
|
||||
main = B.b
|
||||
""", """
|
||||
module ATest exposing (tests)
|
||||
import Project.Utils.B as B
|
||||
import Test exposing (Test)
|
||||
tests : Test
|
||||
tests = Test.describe "thing" B.helper
|
||||
""", """
|
||||
module Project.Utils.B exposing (b, helper)
|
||||
b = 1
|
||||
helper = 1
|
||||
""" ]
|
||||
|> Review.Test.runOnModules
|
||||
(defaults
|
||||
|> reportUnusedProductionExports
|
||||
{ isProductionFile = \{ moduleName } -> String.join "." moduleName |> String.endsWith "Test" |> not
|
||||
, exceptionsAre = [ definedInModule (\{ moduleName } -> List.member "Utils" moduleName) ]
|
||||
}
|
||||
|> toRule
|
||||
)
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should report unused exports in ignored files as regular errors" <|
|
||||
\() ->
|
||||
[ """
|
||||
module Main exposing (main)
|
||||
import B
|
||||
main = B.b
|
||||
""", """
|
||||
module ATest exposing (tests, unused)
|
||||
import Test exposing (Test)
|
||||
tests : Test
|
||||
tests = Test.describe "thing" []
|
||||
unused = 1
|
||||
""" ]
|
||||
|> Review.Test.runOnModules
|
||||
(defaults
|
||||
|> reportUnusedProductionExports
|
||||
{ isProductionFile = \{ moduleName } -> String.join "." moduleName |> String.endsWith "Test" |> not
|
||||
, exceptionsAre = [ prefixedBy "test_" ]
|
||||
}
|
||||
|> toRule
|
||||
)
|
||||
|> Review.Test.expectErrorsForModules
|
||||
[ ( "ATest"
|
||||
, [ Review.Test.error
|
||||
{ message = "Exposed function or value `unused` is never used outside this module."
|
||||
, details = unusedExposedElementDetails
|
||||
, under = "unused"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 2, column = 31 }, end = { row = 2, column = 37 } }
|
||||
|> Review.Test.whenFixed """
|
||||
module ATest exposing (tests)
|
||||
import Test exposing (Test)
|
||||
tests : Test
|
||||
tests = Test.describe "thing" []
|
||||
unused = 1
|
||||
"""
|
||||
]
|
||||
)
|
||||
]
|
||||
]
|
||||
|
9102
tests/Simplify.elm
9102
tests/Simplify.elm
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,8 @@
|
||||
module Simplify.Evaluate exposing (getBoolean, getInt, isAlwaysBoolean)
|
||||
module Simplify.Evaluate exposing (getBoolean, getInt, isAlwaysBoolean, isEqualToSomethingFunction)
|
||||
|
||||
import Elm.Syntax.Expression as Expression exposing (Expression)
|
||||
import Elm.Syntax.Node as Node exposing (Node(..))
|
||||
import Elm.Syntax.Pattern as Pattern
|
||||
import Review.ModuleNameLookupTable as ModuleNameLookupTable
|
||||
import Simplify.AstHelpers as AstHelpers
|
||||
import Simplify.Infer as Infer
|
||||
@ -81,6 +82,41 @@ isAlwaysBoolean resources node =
|
||||
Undetermined
|
||||
|
||||
|
||||
isEqualToSomethingFunction : Node Expression -> Maybe { something : Node Expression }
|
||||
isEqualToSomethingFunction rawNode =
|
||||
case Node.value (AstHelpers.removeParens rawNode) of
|
||||
Expression.Application ((Node _ (Expression.PrefixOperator "==")) :: expr :: []) ->
|
||||
Just { something = expr }
|
||||
|
||||
Expression.LambdaExpression lambda ->
|
||||
case lambda.args of
|
||||
[ Node _ (Pattern.VarPattern var) ] ->
|
||||
case Node.value (AstHelpers.removeParens lambda.expression) of
|
||||
Expression.OperatorApplication "==" _ left right ->
|
||||
let
|
||||
nodeToFind : Expression
|
||||
nodeToFind =
|
||||
Expression.FunctionOrValue [] var
|
||||
in
|
||||
if Node.value left == nodeToFind then
|
||||
Just { something = right }
|
||||
|
||||
else if Node.value right == nodeToFind then
|
||||
Just { something = left }
|
||||
|
||||
else
|
||||
Nothing
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
|
||||
|
||||
getInt : Infer.Resources a -> Node Expression -> Maybe Int
|
||||
getInt resources baseNode =
|
||||
let
|
||||
|
@ -1,14 +1,12 @@
|
||||
module Simplify.Match exposing
|
||||
( Match(..)
|
||||
, map
|
||||
, maybeAndThen
|
||||
, map, traverse
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
@docs Match
|
||||
@docs map
|
||||
@docs maybeAndThen
|
||||
@docs map, traverse
|
||||
|
||||
-}
|
||||
|
||||
@ -28,11 +26,24 @@ map mapper match =
|
||||
Undetermined
|
||||
|
||||
|
||||
maybeAndThen : (a -> Match b) -> Maybe a -> Match b
|
||||
maybeAndThen fn maybe =
|
||||
case maybe of
|
||||
Just a ->
|
||||
fn a
|
||||
{-| If all mapped elements are Determined, returns a List of the Determined values.
|
||||
If any match is Undetermined, returns Undetermined
|
||||
-}
|
||||
traverse : (a -> Match b) -> List a -> Match (List b)
|
||||
traverse f list =
|
||||
traverseHelp f list []
|
||||
|
||||
Nothing ->
|
||||
Undetermined
|
||||
|
||||
traverseHelp : (a -> Match b) -> List a -> List b -> Match (List b)
|
||||
traverseHelp f list acc =
|
||||
case list of
|
||||
head :: tail ->
|
||||
case f head of
|
||||
Determined a ->
|
||||
traverseHelp f tail (a :: acc)
|
||||
|
||||
Undetermined ->
|
||||
Undetermined
|
||||
|
||||
[] ->
|
||||
Determined (List.reverse acc)
|
||||
|
@ -148,6 +148,9 @@ normalize resources node =
|
||||
Expression.Floatable float ->
|
||||
toNode (Expression.Floatable -float)
|
||||
|
||||
Expression.Negation subExpr ->
|
||||
subExpr
|
||||
|
||||
_ ->
|
||||
toNode (Expression.Negation normalized)
|
||||
|
||||
@ -378,6 +381,14 @@ compareHelp leftNode right canFlip =
|
||||
Expression.Floatable left ->
|
||||
compareNumbers left right
|
||||
|
||||
Expression.Negation left ->
|
||||
case Node.value (removeParens right) of
|
||||
Expression.Negation rightValue ->
|
||||
compareHelp left rightValue canFlip
|
||||
|
||||
_ ->
|
||||
fallback ()
|
||||
|
||||
Expression.OperatorApplication leftOp _ leftLeft leftRight ->
|
||||
if List.member leftOp [ "+", "-", "*", "/" ] then
|
||||
case getNumberValue leftNode of
|
||||
|
@ -236,7 +236,7 @@ simpleNormalizationTests =
|
||||
, n (OperatorApplication "^" Infix.Right (n (FunctionOrValue [] "a")) (n (FunctionOrValue [] "b")))
|
||||
, n (OperatorApplication "&&" Infix.Right (n (FunctionOrValue [] "a")) (n (FunctionOrValue [] "b")))
|
||||
, n (OperatorApplication "||" Infix.Right (n (FunctionOrValue [] "a")) (n (FunctionOrValue [] "b")))
|
||||
, n (OperatorApplication "</>" Infix.Left (n (FunctionOrValue [] "a")) (n (FunctionOrValue [] "b")))
|
||||
, n (OperatorApplication "</>" Infix.Right (n (FunctionOrValue [] "a")) (n (FunctionOrValue [] "b")))
|
||||
, n (OperatorApplication "<?>" Infix.Left (n (FunctionOrValue [] "a")) (n (FunctionOrValue [] "b")))
|
||||
, n (OperatorApplication "|." Infix.Left (n (FunctionOrValue [] "a")) (n (FunctionOrValue [] "b")))
|
||||
, n (OperatorApplication "|=" Infix.Left (n (FunctionOrValue [] "a")) (n (FunctionOrValue [] "b")))
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user