Backport rules from elm-review-unused

This commit is contained in:
Jeroen Engels 2021-04-17 21:18:43 +02:00
parent 4b47d203f3
commit 05d2622923
8 changed files with 1793 additions and 150 deletions

View File

@ -60,7 +60,7 @@ This rule attempts to detect when the custom type is used in comparisons, but it
## When not to enable this rule?
If you like giving names to all arguments when pattern matching, then this rule will not found many problems.
If you like giving names to all arguments when pattern matching, then this rule will not find many problems.
This rule will work well when enabled along with [`NoUnused.Patterns`](./NoUnused-Patterns).
Also, if you like comparing custom types in the way described above, you might pass on this rule, or want to be very careful when enabling it.
@ -424,6 +424,21 @@ expressionVisitor node context =
else
( [], context )
Expression.Application ((Node _ (Expression.PrefixOperator operator)) :: restOfArgs) ->
if operator == "==" || operator == "/=" then
let
customTypesNotToReport : Set ( ModuleName, String )
customTypesNotToReport =
List.foldl
(findCustomTypes context.lookupTable >> Set.union)
Set.empty
restOfArgs
in
( [], { context | customTypesNotToReport = Set.union customTypesNotToReport context.customTypesNotToReport } )
else
( [], context )
_ ->
( [], context )

View File

@ -460,6 +460,26 @@ b = B
, under = "Int"
}
]
, test "should not report args for type constructors used as arguments to a prefixed equality operator (==)" <|
\() ->
"""
module MyModule exposing (a, b)
type Foo = Unused Int | B
a = (==) b (Unused 0)
b = B
"""
|> Review.Test.runWithProjectData packageProject rule
|> Review.Test.expectNoErrors
, test "should not report args for type constructors used as arguments to a prefixed inequality operator (/=)" <|
\() ->
"""
module MyModule exposing (a, b)
type Foo = Unused Int | B
a = (/=) Unused 0 b
b = B
"""
|> Review.Test.runWithProjectData packageProject rule
|> Review.Test.expectNoErrors
, test "should not report args for type constructors used in an equality expression with parenthesized expressions" <|
\() ->
"""

View File

@ -23,6 +23,7 @@ import Elm.Syntax.Range exposing (Range)
import Elm.Syntax.Type as Type
import Elm.Syntax.TypeAnnotation as TypeAnnotation exposing (TypeAnnotation)
import NoUnused.RangeDict as RangeDict exposing (RangeDict)
import Review.Fix as Fix exposing (Fix)
import Review.ModuleNameLookupTable as ModuleNameLookupTable exposing (ModuleNameLookupTable)
import Review.Rule as Rule exposing (Error, Rule)
import Set exposing (Set)
@ -165,17 +166,26 @@ type alias ConstructorName =
type ExposedConstructors
= ExposedConstructors
{ moduleKey : Rule.ModuleKey
, customTypes : Dict CustomTypeName (Dict ConstructorName (Node ConstructorName))
, customTypes : Dict CustomTypeName (Dict ConstructorName ConstructorInformation)
}
type alias ConstructorInformation =
{ name : String
, rangeToReport : Range
, rangeToRemove : Maybe Range
}
type alias ProjectContext =
{ exposedModules : Set ModuleNameAsString
, exposedConstructors : Dict ModuleNameAsString ExposedConstructors
, declaredConstructors : Dict ModuleNameAsString ExposedConstructors
, usedConstructors : Dict ModuleNameAsString (Set ConstructorName)
, phantomVariables : Dict ModuleName (List ( CustomTypeName, Int ))
, wasUsedInLocationThatNeedsItself : Set ( ModuleNameAsString, ConstructorName )
, wasUsedInComparisons : Set ( ModuleNameAsString, ConstructorName )
, wasUsedInOtherModules : Set ( ModuleNameAsString, ConstructorName )
, fixesForRemovingConstructor : Dict ( ModuleNameAsString, ConstructorName ) (List Fix)
}
@ -185,13 +195,15 @@ type alias ModuleContext =
, isExposed : Bool
, exposesEverything : Bool
, exposedConstructors : Dict ModuleNameAsString ExposedConstructors
, declaredTypesWithConstructors : Dict CustomTypeName (Dict ConstructorName (Node ConstructorName))
, declaredTypesWithConstructors : Dict CustomTypeName (Dict ConstructorName ConstructorInformation)
, usedFunctionsOrValues : Dict ModuleNameAsString (Set ConstructorName)
, phantomVariables : Dict ModuleName (List ( CustomTypeName, Int ))
, ignoreBlocks : List (RangeDict (Set ( ModuleName, String )))
, constructorsToIgnore : List (Set ( ModuleName, String ))
, wasUsedInLocationThatNeedsItself : Set ( ModuleNameAsString, ConstructorName )
, wasUsedInComparisons : Set ( ModuleNameAsString, ConstructorName )
, fixesForRemovingConstructor : Dict ConstructorName (List Fix)
, wasUsedInOtherModules : Set ( ModuleNameAsString, ConstructorName )
, ignoredComparisonRanges : List Range
}
@ -199,7 +211,7 @@ type alias ModuleContext =
initialProjectContext : List { moduleName : String, typeName : String, index : Int } -> ProjectContext
initialProjectContext phantomTypes =
{ exposedModules = Set.empty
, exposedConstructors = Dict.empty
, declaredConstructors = Dict.empty
, usedConstructors = Dict.empty
, phantomVariables =
List.foldl
@ -212,6 +224,8 @@ initialProjectContext phantomTypes =
phantomTypes
, wasUsedInLocationThatNeedsItself = Set.empty
, wasUsedInComparisons = Set.empty
, wasUsedInOtherModules = Set.empty
, fixesForRemovingConstructor = Dict.empty
}
@ -220,7 +234,7 @@ fromProjectToModule lookupTable metadata projectContext =
{ lookupTable = lookupTable
, exposedCustomTypesWithConstructors = Set.empty
, isExposed = Set.member (Rule.moduleNameFromMetadata metadata |> String.join ".") projectContext.exposedModules
, exposedConstructors = projectContext.exposedConstructors
, exposedConstructors = projectContext.declaredConstructors
, exposesEverything = False
, declaredTypesWithConstructors = Dict.empty
, usedFunctionsOrValues = Dict.empty
@ -229,6 +243,8 @@ fromProjectToModule lookupTable metadata projectContext =
, constructorsToIgnore = []
, wasUsedInLocationThatNeedsItself = Set.empty
, wasUsedInComparisons = Set.empty
, wasUsedInOtherModules = Set.empty
, fixesForRemovingConstructor = Dict.empty
, ignoredComparisonRanges = []
}
@ -257,7 +273,7 @@ fromModuleToProject moduleKey metadata moduleContext =
String.join "." moduleName
in
{ exposedModules = Set.empty
, exposedConstructors =
, declaredConstructors =
if moduleContext.isExposed then
if moduleContext.exposesEverything then
Dict.empty
@ -306,13 +322,29 @@ fromModuleToProject moduleKey metadata moduleContext =
untouched
)
moduleContext.wasUsedInComparisons
, wasUsedInOtherModules =
List.foldl
(\( moduleName_, constructors ) acc ->
Set.union
(Set.map (Tuple.pair moduleName_) constructors)
acc
)
moduleContext.wasUsedInOtherModules
-- TODO add test to make sure we don't fix something that is pattern matched in other modules
(Dict.toList <| Dict.remove "" moduleContext.usedFunctionsOrValues)
, fixesForRemovingConstructor =
mapDictKeys
(\constructorName ->
( moduleNameAsString, constructorName )
)
moduleContext.fixesForRemovingConstructor
}
foldProjectContexts : ProjectContext -> ProjectContext -> ProjectContext
foldProjectContexts newContext previousContext =
{ exposedModules = previousContext.exposedModules
, exposedConstructors = Dict.union newContext.exposedConstructors previousContext.exposedConstructors
, declaredConstructors = Dict.union newContext.declaredConstructors previousContext.declaredConstructors
, usedConstructors =
Dict.merge
Dict.insert
@ -324,9 +356,22 @@ foldProjectContexts newContext previousContext =
, phantomVariables = Dict.union newContext.phantomVariables previousContext.phantomVariables
, wasUsedInLocationThatNeedsItself = Set.union newContext.wasUsedInLocationThatNeedsItself previousContext.wasUsedInLocationThatNeedsItself
, wasUsedInComparisons = Set.union newContext.wasUsedInComparisons previousContext.wasUsedInComparisons
, wasUsedInOtherModules = Set.union newContext.wasUsedInOtherModules previousContext.wasUsedInOtherModules
, fixesForRemovingConstructor = mergeDictsWithLists newContext.fixesForRemovingConstructor previousContext.fixesForRemovingConstructor
}
mergeDictsWithLists : Dict comparable appendable -> Dict comparable appendable -> Dict comparable appendable
mergeDictsWithLists left right =
Dict.merge
(\key a dict -> Dict.insert key a dict)
(\key a b dict -> Dict.insert key (a ++ b) dict)
(\key b dict -> Dict.insert key b dict)
left
right
Dict.empty
-- ELM.JSON VISITOR
@ -455,22 +500,42 @@ declarationVisitor node context =
else
let
constructorsForCustomType : Dict String (Node String)
constructorsAndNext : List ( Maybe (Node Type.ValueConstructor), Node Type.ValueConstructor )
constructorsAndNext =
List.map2 Tuple.pair
(List.map Just (List.drop 1 constructors) ++ [ Nothing ])
constructors
constructorsForCustomType : Dict String ConstructorInformation
constructorsForCustomType =
List.foldl
(\constructor dict ->
(\( next, constructor ) ( prev, dict ) ->
let
nameNode : Node String
nameNode =
(Node.value constructor).name
constructorName : String
constructorName =
Node.value nameNode
constructorInformation : ConstructorInformation
constructorInformation =
{ name = constructorName
, rangeToReport = Node.range nameNode
, rangeToRemove = findRangeToRemove prev constructor next
}
in
Dict.insert
(Node.value nameNode)
nameNode
( Just constructor
, Dict.insert
constructorName
constructorInformation
dict
)
)
Dict.empty
constructors
( Nothing, Dict.empty )
constructorsAndNext
|> Tuple.second
in
( []
, { context
@ -496,6 +561,27 @@ declarationVisitor node context =
( [], context )
findRangeToRemove : Maybe (Node a) -> Node Type.ValueConstructor -> Maybe (Node c) -> Maybe { start : Elm.Syntax.Range.Location, end : Elm.Syntax.Range.Location }
findRangeToRemove previousConstructor constructor nextConstructor =
case previousConstructor of
Just prev ->
Just
{ start = (Node.range prev).end
, end = (Node.range constructor).end
}
Nothing ->
case nextConstructor of
Just next ->
Just
{ start = constructor |> Node.value |> .name |> Node.range |> .start
, end = (Node.range next).start
}
Nothing ->
Nothing
isPhantomCustomType : Node String -> List (Node Type.ValueConstructor) -> Bool
isPhantomCustomType name constructors =
case constructors of
@ -582,11 +668,86 @@ expressionVisitorHelp node moduleContext =
Expression.OperatorApplication operator _ left right ->
if operator == "==" || operator == "/=" then
let
ranges : List Range
ranges =
List.concatMap staticRanges [ left, right ]
constructors : Set ( ModuleNameAsString, ConstructorName )
constructors =
Set.union
(findConstructors moduleContext.lookupTable left)
(findConstructors moduleContext.lookupTable right)
replacement : String
replacement =
if operator == "==" then
"False"
else
"True"
( fromThisModule, fromOtherModules ) =
constructors
|> Set.toList
|> List.partition (\( moduleName, _ ) -> moduleName == "")
fixes : Dict ConstructorName (List Fix)
fixes =
fromThisModule
|> List.map (\( _, constructor ) -> Dict.singleton constructor [ Fix.replaceRangeBy (Node.range node) replacement ])
|> List.foldl mergeDictsWithLists Dict.empty
in
( [], { moduleContext | ignoredComparisonRanges = ranges ++ moduleContext.ignoredComparisonRanges } )
( []
, { moduleContext
| ignoredComparisonRanges = staticRanges node ++ moduleContext.ignoredComparisonRanges
, fixesForRemovingConstructor = mergeDictsWithLists fixes moduleContext.fixesForRemovingConstructor
, wasUsedInOtherModules = Set.union (Set.fromList fromOtherModules) moduleContext.wasUsedInOtherModules
}
)
else
( [], moduleContext )
Expression.Application ((Node _ (Expression.PrefixOperator operator)) :: arguments) ->
if operator == "==" || operator == "/=" then
let
constructors : Set ( ModuleNameAsString, ConstructorName )
constructors =
List.foldl
(findConstructors moduleContext.lookupTable >> Set.union)
Set.empty
arguments
replacementBoolean : String
replacementBoolean =
if operator == "==" then
"False"
else
"True"
replacement : String
replacement =
if List.length arguments == 2 then
replacementBoolean
else
"always " ++ replacementBoolean
( fromThisModule, fromOtherModules ) =
constructors
|> Set.toList
|> List.partition (\( moduleName, _ ) -> moduleName == "")
fixes : Dict ConstructorName (List Fix)
fixes =
fromThisModule
|> List.map (\( _, constructor ) -> Dict.singleton constructor [ Fix.replaceRangeBy (Node.range node) replacement ])
|> List.foldl mergeDictsWithLists Dict.empty
in
( []
, { moduleContext
| ignoredComparisonRanges = staticRanges node ++ moduleContext.ignoredComparisonRanges
, fixesForRemovingConstructor = mergeDictsWithLists fixes moduleContext.fixesForRemovingConstructor
, wasUsedInOtherModules = Set.union (Set.fromList fromOtherModules) moduleContext.wasUsedInOtherModules
}
)
else
( [], moduleContext )
@ -608,20 +769,79 @@ expressionVisitorHelp node moduleContext =
Expression.CaseExpression { cases } ->
let
newCases : RangeDict (Set ( ModuleName, String ))
newCases =
cases
|> List.map (\( pattern, body ) -> ( Node.range body, constructorsInPattern moduleContext.lookupTable pattern ))
found : List { ignoreBlock : ( Range, Set ( ModuleName, ConstructorName ) ), fixes : Dict ConstructorName (List Fix) }
found =
List.map2
(forOne moduleContext.lookupTable)
(Nothing :: List.map (Tuple.second >> Node.range >> .end >> Just) cases)
cases
ignoredBlocks : RangeDict (Set ( ModuleName, String ))
ignoredBlocks =
List.map .ignoreBlock found
|> RangeDict.fromList
wasUsedInOtherModules : Set ( ModuleNameAsString, ConstructorName )
wasUsedInOtherModules =
found
|> List.map (.ignoreBlock >> Tuple.second >> toSetOfModuleNameAsString)
|> List.foldl Set.union moduleContext.wasUsedInOtherModules
in
( []
, { moduleContext | ignoreBlocks = newCases :: moduleContext.ignoreBlocks }
, { moduleContext
| ignoreBlocks = ignoredBlocks :: moduleContext.ignoreBlocks
, wasUsedInOtherModules = wasUsedInOtherModules
, fixesForRemovingConstructor =
List.foldl
mergeDictsWithLists
moduleContext.fixesForRemovingConstructor
(List.map .fixes found)
}
)
_ ->
( [], moduleContext )
toSetOfModuleNameAsString : Set ( ModuleName, ConstructorName ) -> Set ( ModuleNameAsString, ConstructorName )
toSetOfModuleNameAsString set =
set
|> Set.map (Tuple.mapFirst (String.join "."))
|> Set.filter (\( moduleName, _ ) -> moduleName /= "")
forOne : ModuleNameLookupTable -> Maybe Elm.Syntax.Range.Location -> ( Node Pattern, Node a ) -> { ignoreBlock : ( Range, Set ( ModuleName, String ) ), fixes : Dict ConstructorName (List Fix) }
forOne lookupTable previousLocation ( pattern, body ) =
let
constructors : Set ( ModuleName, String )
constructors =
constructorsInPattern lookupTable pattern
fixes : Dict ConstructorName (List Fix)
fixes =
List.foldl
(\( moduleName, constructorName ) acc ->
if moduleName == [] then
Dict.insert
constructorName
[ Fix.removeRange
{ start = Maybe.withDefault (Node.range pattern).start previousLocation
, end = (Node.range body).end
}
]
acc
else
acc
)
Dict.empty
(Set.toList constructors)
in
{ ignoreBlock = ( Node.range body, constructors )
, fixes = fixes
}
staticRanges : Node Expression -> List Range
staticRanges node =
case Node.value node of
@ -635,6 +855,13 @@ staticRanges node =
else
[]
Expression.Application ((Node _ (Expression.PrefixOperator operator)) :: restOfArgs) ->
if List.member operator [ "+", "-", "==", "/=" ] then
List.concatMap staticRanges restOfArgs
else
[]
Expression.OperatorApplication operator _ left right ->
if List.member operator [ "+", "-", "==", "/=" ] then
List.concatMap staticRanges [ left, right ]
@ -664,6 +891,66 @@ staticRanges node =
[]
findConstructors : ModuleNameLookupTable -> Node Expression -> Set ( ModuleNameAsString, ConstructorName )
findConstructors lookupTable node =
case Node.value node of
Expression.FunctionOrValue _ name ->
if isCapitalized name then
case ModuleNameLookupTable.moduleNameFor lookupTable node of
Just realModuleName ->
Set.singleton ( String.join "." realModuleName, name )
Nothing ->
Set.empty
else
Set.empty
Expression.Application ((Node _ (Expression.FunctionOrValue _ name)) :: restOfArgs) ->
if isCapitalized name then
List.foldl
(findConstructors lookupTable >> Set.union)
(case ModuleNameLookupTable.moduleNameFor lookupTable node of
Just realModuleName ->
Set.singleton ( String.join "." realModuleName, name )
Nothing ->
Set.empty
)
restOfArgs
else
Set.empty
Expression.OperatorApplication operator _ left right ->
if List.member operator [ "+", "-" ] then
List.foldl (findConstructors lookupTable >> Set.union) Set.empty [ left, right ]
else
Set.empty
Expression.ListExpr nodes ->
List.foldl (findConstructors lookupTable >> Set.union) Set.empty nodes
Expression.TupledExpression nodes ->
List.foldl (findConstructors lookupTable >> Set.union) Set.empty nodes
Expression.ParenthesizedExpression expr ->
findConstructors lookupTable expr
Expression.RecordExpr fields ->
List.foldl (Node.value >> Tuple.second >> findConstructors lookupTable >> Set.union) Set.empty fields
Expression.RecordUpdateExpression _ fields ->
List.foldl (Node.value >> Tuple.second >> findConstructors lookupTable >> Set.union) Set.empty fields
Expression.RecordAccess expr _ ->
findConstructors lookupTable expr
_ ->
Set.empty
constructorsInPattern : ModuleNameLookupTable -> Node Pattern -> Set ( ModuleName, String )
constructorsInPattern lookupTable node =
case Node.value node of
@ -715,12 +1002,7 @@ registerUsedFunctionOrValue range moduleName name moduleContext =
}
else if List.any (Set.member ( moduleName, name )) moduleContext.constructorsToIgnore then
{ moduleContext
| wasUsedInLocationThatNeedsItself =
Set.insert
( String.join "." moduleName, name )
moduleContext.wasUsedInLocationThatNeedsItself
}
{ moduleContext | wasUsedInLocationThatNeedsItself = Set.insert ( String.join "." moduleName, name ) moduleContext.wasUsedInLocationThatNeedsItself }
else
{ moduleContext
@ -747,7 +1029,7 @@ isCapitalized name =
finalProjectEvaluation : ProjectContext -> List (Error { useErrorForModule : () })
finalProjectEvaluation projectContext =
projectContext.exposedConstructors
projectContext.declaredConstructors
|> Dict.toList
|> List.concatMap
(\( moduleName, ExposedConstructors { moduleKey, customTypes } ) ->
@ -765,13 +1047,15 @@ finalProjectEvaluation projectContext =
|> Dict.filter (\constructorName _ -> not <| Set.member constructorName usedConstructors)
|> Dict.values
|> List.map
(\constructorName ->
(\constructorInformation ->
errorForModule
moduleKey
{ wasUsedInLocationThatNeedsItself = Set.member ( moduleName, Node.value constructorName ) projectContext.wasUsedInLocationThatNeedsItself
, wasUsedInComparisons = Set.member ( moduleName, Node.value constructorName ) projectContext.wasUsedInComparisons
{ wasUsedInLocationThatNeedsItself = Set.member ( moduleName, constructorInformation.name ) projectContext.wasUsedInLocationThatNeedsItself
, wasUsedInComparisons = Set.member ( moduleName, constructorInformation.name ) projectContext.wasUsedInComparisons
, isUsedInOtherModules = Set.member ( moduleName, constructorInformation.name ) projectContext.wasUsedInOtherModules
, fixesForRemovingConstructor = Dict.get ( moduleName, constructorInformation.name ) projectContext.fixesForRemovingConstructor |> Maybe.withDefault []
}
constructorName
constructorInformation
)
)
)
@ -799,12 +1083,37 @@ defaultDetails =
"This type constructor is never used. It might be handled everywhere it might appear, but there is no location where this value actually gets created."
errorForModule : Rule.ModuleKey -> { wasUsedInLocationThatNeedsItself : Bool, wasUsedInComparisons : Bool } -> Node String -> Error scope
errorForModule moduleKey conditions node =
Rule.errorForModule
errorForModule :
Rule.ModuleKey
->
{ wasUsedInLocationThatNeedsItself : Bool
, wasUsedInComparisons : Bool
, isUsedInOtherModules : Bool
, fixesForRemovingConstructor : List Fix
}
-> ConstructorInformation
-> Error scope
errorForModule moduleKey params constructorInformation =
Rule.errorForModuleWithFix
moduleKey
(errorInformation conditions (Node.value node))
(Node.range node)
(errorInformation
{ wasUsedInLocationThatNeedsItself = params.wasUsedInLocationThatNeedsItself
, wasUsedInComparisons = params.wasUsedInComparisons
}
constructorInformation.name
)
constructorInformation.rangeToReport
(case constructorInformation.rangeToRemove of
Just rangeToRemove ->
if params.isUsedInOtherModules then
[]
else
Fix.removeRange rangeToRemove :: params.fixesForRemovingConstructor
Nothing ->
[]
)
@ -942,3 +1251,11 @@ listAtIndex index list =
( n, _ :: rest ) ->
listAtIndex (n - 1) rest
mapDictKeys : (comparable -> comparable1) -> Dict comparable v -> Dict comparable1 v
mapDictKeys keyMapper dict =
Dict.foldl
(\key value acc -> Dict.insert (keyMapper key) value acc)
Dict.empty
dict

View File

@ -138,11 +138,19 @@ type Foo = Bar | Baz"""
, details = [ defaultDetails ]
, under = "Bar"
}
|> Review.Test.whenFixed
"""
module MyModule exposing (b)
type Foo = Baz"""
, Review.Test.error
{ message = "Type constructor `Baz` is not used."
, details = [ defaultDetails ]
, under = "Baz"
}
|> Review.Test.whenFixed
"""
module MyModule exposing (b)
type Foo = Bar"""
]
, test "should report unused type constructors, even if the type is exposed" <|
\() ->
@ -156,11 +164,19 @@ type Foo = Bar | Baz"""
, details = [ defaultDetails ]
, under = "Bar"
}
|> Review.Test.whenFixed
"""
module MyModule exposing (Foo)
type Foo = Baz"""
, Review.Test.error
{ message = "Type constructor `Baz` is not used."
, details = [ defaultDetails ]
, under = "Baz"
}
|> Review.Test.whenFixed
"""
module MyModule exposing (Foo)
type Foo = Bar"""
]
, test "should report type constructors that are only used inside pattern matches that require themselves" <|
\() ->
@ -179,6 +195,39 @@ a = case () of
, under = "Unused"
}
|> Review.Test.atExactly { start = { row = 3, column = 19 }, end = { row = 3, column = 25 } }
|> Review.Test.whenFixed
("""
module MyModule exposing (a)
type Foo = Used
a = case () of
$
Used -> Used
""" |> String.replace "$" " ")
]
, test "should report type constructors that are only used inside pattern matches that require themselves (reversed order of patterns)" <|
\() ->
"""
module MyModule exposing (a)
type Foo = Used | Unused
a = case () of
Used -> Used
Unused -> Unused + Used
"""
|> Review.Test.runWithProjectData project (rule [])
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Type constructor `Unused` is not used."
, details = [ defaultDetails, recursiveNeedDetails ]
, under = "Unused"
}
|> Review.Test.atExactly { start = { row = 3, column = 19 }, end = { row = 3, column = 25 } }
|> Review.Test.whenFixed
("""
module MyModule exposing (a)
type Foo = Used
a = case () of
Used -> Used
""" |> String.replace "$" " ")
]
, test "should report type constructors that are only used inside deep pattern matches that require themselves" <|
\() ->
@ -197,6 +246,14 @@ a = case () of
, under = "Unused"
}
|> Review.Test.atExactly { start = { row = 3, column = 19 }, end = { row = 3, column = 25 } }
|> Review.Test.whenFixed
("""
module MyModule exposing (a)
type Foo = Used
a = case () of
$
Used -> Used
""" |> String.replace "$" " ")
]
, test "should properly remove the ignored constructors once the pattern has been left" <|
\() ->
@ -240,6 +297,13 @@ b = B
, under = "Unused"
}
|> Review.Test.atExactly { start = { row = 3, column = 12 }, end = { row = 3, column = 18 } }
|> Review.Test.whenFixed
"""
module MyModule exposing (a, b)
type Foo = B
a = False
b = B
"""
]
, test "should not count type constructors used in an equality expression (/=)" <|
\() ->
@ -257,6 +321,13 @@ b = B
, under = "Unused"
}
|> Review.Test.atExactly { start = { row = 3, column = 12 }, end = { row = 3, column = 18 } }
|> Review.Test.whenFixed
"""
module MyModule exposing (a, b)
type Foo = B
a = True
b = B
"""
]
, test "should count type constructors used in a function call inside an equality expression" <|
\() ->
@ -284,6 +355,109 @@ b = B
, under = "Unused"
}
|> Review.Test.atExactly { start = { row = 3, column = 12 }, end = { row = 3, column = 18 } }
|> Review.Test.whenFixed
"""
module MyModule exposing (a, b)
type Foo = B
a = True
b = B
"""
]
, test "should not count type constructors used as arguments to a prefix (==) operator (with 2 arguments)" <|
\() ->
"""
module MyModule exposing (a, b)
type Foo = Unused | B
a = (==) Unused value
b = B
"""
|> Review.Test.runWithProjectData project (rule [])
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Type constructor `Unused` is not used."
, details = [ defaultDetails, conditionDetails ]
, under = "Unused"
}
|> Review.Test.atExactly { start = { row = 3, column = 12 }, end = { row = 3, column = 18 } }
|> Review.Test.whenFixed
"""
module MyModule exposing (a, b)
type Foo = B
a = False
b = B
"""
]
, test "should not count type constructors used as arguments to a prefix (/=) operator (with 2 arguments)" <|
\() ->
"""
module MyModule exposing (a, b)
type Foo = Unused | B
a = (/=) value Unused
b = B
"""
|> Review.Test.runWithProjectData project (rule [])
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Type constructor `Unused` is not used."
, details = [ defaultDetails, conditionDetails ]
, under = "Unused"
}
|> Review.Test.atExactly { start = { row = 3, column = 12 }, end = { row = 3, column = 18 } }
|> Review.Test.whenFixed
"""
module MyModule exposing (a, b)
type Foo = B
a = True
b = B
"""
]
, test "should not count type constructors used as arguments to a prefix (==) operator (with 1 argument)" <|
\() ->
"""
module MyModule exposing (a, b)
type Foo = Unused | B
a = (==) Unused
b = B
"""
|> Review.Test.runWithProjectData project (rule [])
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Type constructor `Unused` is not used."
, details = [ defaultDetails, conditionDetails ]
, under = "Unused"
}
|> Review.Test.atExactly { start = { row = 3, column = 12 }, end = { row = 3, column = 18 } }
|> Review.Test.whenFixed
"""
module MyModule exposing (a, b)
type Foo = B
a = always False
b = B
"""
]
, test "should not count type constructors used as arguments to a prefix (/=) operator (with 1 argument)" <|
\() ->
"""
module MyModule exposing (a, b)
type Foo = Unused | B
a = (/=) Unused
b = B
"""
|> Review.Test.runWithProjectData project (rule [])
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Type constructor `Unused` is not used."
, details = [ defaultDetails, conditionDetails ]
, under = "Unused"
}
|> Review.Test.atExactly { start = { row = 3, column = 12 }, end = { row = 3, column = 18 } }
|> Review.Test.whenFixed
"""
module MyModule exposing (a, b)
type Foo = B
a = always True
b = B
"""
]
]
@ -337,11 +511,29 @@ id = Id
, details = [ defaultDetails ]
, under = "A"
}
|> Review.Test.whenFixed
"""
module MyModule exposing (id)
type Something = B
type Id a = Id
id : Id Something
id = Id
"""
, Review.Test.error
{ message = "Type constructor `B` is not used."
, details = [ defaultDetails ]
, under = "B"
}
|> Review.Test.whenFixed
"""
module MyModule exposing (id)
type Something = A
type Id a = Id
id : Id Something
id = Id
"""
]
, test "should report a custom type with one constructor, when there is a phantom type available but it isn't used" <|
\() ->
@ -465,11 +657,29 @@ id = Id
, under = "User"
}
|> Review.Test.atExactly { start = { row = 3, column = 13 }, end = { row = 3, column = 17 } }
|> Review.Test.whenFixed
"""
module MyModule exposing (id)
type User = Other
type Id a = Id a
id : Id User
id = Id
"""
, Review.Test.error
{ message = "Type constructor `Other` is not used."
, details = [ defaultDetails ]
, under = "Other"
}
|> Review.Test.whenFixed
"""
module MyModule exposing (id)
type User = User Never
type Id a = Id a
id : Id User
id = Id
"""
]
, test "should not report a phantom type if it is used in another module (directly imported)" <|
\() ->
@ -601,11 +811,21 @@ type Foo = Bar | Baz
, details = [ defaultDetails ]
, under = "Bar"
}
|> Review.Test.whenFixed
"""
module MyModule exposing (..)
type Foo = Baz
"""
, Review.Test.error
{ message = "Type constructor `Baz` is not used."
, details = [ defaultDetails ]
, under = "Baz"
}
|> Review.Test.whenFixed
"""
module MyModule exposing (..)
type Foo = Bar
"""
]
, test "should report unused type constructors when a package module is exposing all and module is exposed but types are not" <|
\() ->
@ -652,11 +872,21 @@ type Foo = Bar | Baz
, details = [ defaultDetails ]
, under = "Bar"
}
|> Review.Test.whenFixed
"""
module MyModule exposing (..)
type Foo = Baz
"""
, Review.Test.error
{ message = "Type constructor `Baz` is not used."
, details = [ defaultDetails ]
, under = "Baz"
}
|> Review.Test.whenFixed
"""
module MyModule exposing (..)
type Foo = Bar
"""
]
, test "should not report unused type constructors when package module is exposing the constructors of that type and module is exposed" <|
\() ->
@ -679,11 +909,21 @@ type Foo = Bar | Baz
, details = [ defaultDetails ]
, under = "Bar"
}
|> Review.Test.whenFixed
"""
module MyModule exposing (Foo(..))
type Foo = Baz
"""
, Review.Test.error
{ message = "Type constructor `Baz` is not used."
, details = [ defaultDetails ]
, under = "Baz"
}
|> Review.Test.whenFixed
"""
module MyModule exposing (Foo(..))
type Foo = Bar
"""
]
, test "should report unused type constructors when application module is exposing the constructors" <|
\() ->
@ -698,11 +938,21 @@ type Foo = Bar | Baz
, details = [ defaultDetails ]
, under = "Bar"
}
|> Review.Test.whenFixed
"""
module MyModule exposing (Foo(..))
type Foo = Baz
"""
, Review.Test.error
{ message = "Type constructor `Baz` is not used."
, details = [ defaultDetails ]
, under = "Baz"
}
|> Review.Test.whenFixed
"""
module MyModule exposing (Foo(..))
type Foo = Bar
"""
]
]
@ -779,6 +1029,27 @@ type Msg = NoOp
]
)
]
, test "should report but not fix if constructor is handled in a pattern" <|
\() ->
[ """module A exposing (main)
import Other exposing (Msg(..))
a = Used
main = case foo of
Unused -> 1
""", """module Other exposing (Msg(..))
type Msg = Unused | Used
""" ]
|> Review.Test.runOnModulesWithProjectData project (rule [])
|> Review.Test.expectErrorsForModules
[ ( "Other"
, [ Review.Test.error
{ message = "Type constructor `Unused` is not used."
, details = [ defaultDetails ]
, under = "Unused"
}
]
)
]
, test "should not report type constructors in confusing situations" <|
\() ->
[ """module A exposing (A(..))

View File

@ -10,12 +10,13 @@ module NoUnused.Dependencies exposing (rule)
-}
import Dict exposing (Dict)
import Elm.Constraint
import Elm.Package
import Elm.Project exposing (Project)
import Elm.Syntax.Import exposing (Import)
import Elm.Syntax.ModuleName exposing (ModuleName)
import Elm.Syntax.Node as Node exposing (Node)
import Elm.Syntax.Range exposing (Range)
import Elm.Version
import Review.Project.Dependency as Dependency exposing (Dependency)
import Review.Rule as Rule exposing (Error, Rule)
import Set exposing (Set)
@ -45,7 +46,7 @@ rule =
|> Rule.withElmJsonProjectVisitor elmJsonVisitor
|> Rule.withDependenciesProjectVisitor dependenciesVisitor
|> Rule.withModuleVisitor moduleVisitor
|> Rule.withModuleContext
|> Rule.withModuleContextUsingContextCreator
{ fromProjectToModule = fromProjectToModule
, fromModuleToProject = fromModuleToProject
, foldProjectContexts = foldProjectContexts
@ -73,7 +74,12 @@ dependenciesVisitor dependencies projectContext =
)
|> Dict.fromList
in
( [], { projectContext | moduleNameToDependency = moduleNameToDependency } )
( []
, { projectContext
| dependencies = dependencies
, moduleNameToDependency = moduleNameToDependency
}
)
@ -82,44 +88,82 @@ dependenciesVisitor dependencies projectContext =
type alias ProjectContext =
{ moduleNameToDependency : Dict String String
, dependencies : Dict String Dependency
, directProjectDependencies : Set String
, importedModuleNames : Set String
, directTestDependencies : Set String
, usedDependencies : Set String
, usedDependenciesFromTest : Set String
, elmJsonKey : Maybe Rule.ElmJsonKey
}
type alias ModuleContext =
Set String
{ moduleNameToDependency : Dict String String
, usedDependencies : Set String
}
initialProjectContext : ProjectContext
initialProjectContext =
{ moduleNameToDependency = Dict.empty
, dependencies = Dict.empty
, directProjectDependencies = Set.empty
, importedModuleNames = Set.empty
, directTestDependencies = Set.empty
, usedDependencies = Set.empty
, usedDependenciesFromTest = Set.empty
, elmJsonKey = Nothing
}
fromProjectToModule : Rule.ModuleKey -> Node ModuleName -> ProjectContext -> ModuleContext
fromProjectToModule _ _ projectContext =
projectContext.importedModuleNames
fromProjectToModule : Rule.ContextCreator ProjectContext ModuleContext
fromProjectToModule =
Rule.initContextCreator
(\projectContext ->
{ moduleNameToDependency = projectContext.moduleNameToDependency
, usedDependencies = Set.empty
}
)
fromModuleToProject : Rule.ModuleKey -> Node ModuleName -> ModuleContext -> ProjectContext
fromModuleToProject _ _ importedModuleNames =
{ moduleNameToDependency = Dict.empty
, directProjectDependencies = Set.empty
, importedModuleNames = importedModuleNames
, elmJsonKey = Nothing
}
fromModuleToProject : Rule.ContextCreator ModuleContext ProjectContext
fromModuleToProject =
Rule.initContextCreator
(\metadata { usedDependencies } ->
let
isSourceDir : Bool
isSourceDir =
Rule.isInSourceDirectories metadata
in
{ moduleNameToDependency = Dict.empty
, dependencies = Dict.empty
, directProjectDependencies = Set.empty
, directTestDependencies = Set.empty
, usedDependencies =
if isSourceDir then
usedDependencies
else
Set.empty
, usedDependenciesFromTest =
if isSourceDir then
Set.empty
else
usedDependencies
, elmJsonKey = Nothing
}
)
|> Rule.withMetadata
foldProjectContexts : ProjectContext -> ProjectContext -> ProjectContext
foldProjectContexts newContext previousContext =
{ moduleNameToDependency = previousContext.moduleNameToDependency
, dependencies = previousContext.dependencies
, directProjectDependencies = previousContext.directProjectDependencies
, importedModuleNames = Set.union previousContext.importedModuleNames newContext.importedModuleNames
, directTestDependencies = previousContext.directTestDependencies
, usedDependencies = Set.union newContext.usedDependencies previousContext.usedDependencies
, usedDependenciesFromTest = Set.union newContext.usedDependenciesFromTest previousContext.usedDependenciesFromTest
, elmJsonKey = previousContext.elmJsonKey
}
@ -133,24 +177,31 @@ elmJsonVisitor maybeProject projectContext =
case maybeProject of
Just { elmJsonKey, project } ->
let
directProjectDependencies : Set String
directProjectDependencies =
( directProjectDependencies, directTestDependencies ) =
case project of
Elm.Project.Package { deps } ->
deps
Elm.Project.Package { deps, testDeps } ->
( deps
|> List.map (Tuple.first >> Elm.Package.toString)
|> Set.fromList
, testDeps
|> List.map (Tuple.first >> Elm.Package.toString)
|> Set.fromList
)
Elm.Project.Application { depsDirect } ->
depsDirect
Elm.Project.Application { depsDirect, testDepsDirect } ->
( depsDirect
|> List.map (Tuple.first >> Elm.Package.toString)
|> Set.fromList
, testDepsDirect
|> List.map (Tuple.first >> Elm.Package.toString)
|> Set.fromList
)
in
( []
, { projectContext
| elmJsonKey = Just elmJsonKey
, directProjectDependencies =
Set.filter ((/=) "elm/core") directProjectDependencies
, directProjectDependencies = directProjectDependencies
, directTestDependencies = directTestDependencies
}
)
@ -163,9 +214,14 @@ elmJsonVisitor maybeProject projectContext =
importVisitor : Node Import -> ModuleContext -> ( List nothing, ModuleContext )
importVisitor node importedModuleNames =
importVisitor node context =
( []
, Set.insert (moduleNameForImport node) importedModuleNames
, case Dict.get (moduleNameForImport node) context.moduleNameToDependency of
Just dependency ->
{ context | usedDependencies = Set.insert dependency context.usedDependencies }
Nothing ->
context
)
@ -186,21 +242,43 @@ finalEvaluationForProject : ProjectContext -> List (Error { useErrorForModule :
finalEvaluationForProject projectContext =
case projectContext.elmJsonKey of
Just elmJsonKey ->
projectContext.importedModuleNames
|> Set.toList
|> List.filterMap (\importedModuleName -> Dict.get importedModuleName projectContext.moduleNameToDependency)
|> Set.fromList
|> Set.diff projectContext.directProjectDependencies
|> Set.toList
|> List.map (error elmJsonKey)
let
depsNotUsedInSrc : Set String
depsNotUsedInSrc =
Set.diff projectContext.directProjectDependencies projectContext.usedDependencies
depsNotUsedInSrcButUsedInTests : Set String
depsNotUsedInSrcButUsedInTests =
Set.intersect depsNotUsedInSrc projectContext.usedDependenciesFromTest
|> Set.remove "elm/core"
depsNotUsedInSrcErrors : List String
depsNotUsedInSrcErrors =
Set.diff depsNotUsedInSrc depsNotUsedInSrcButUsedInTests
|> Set.remove "elm/core"
|> Set.toList
testDepsNotUsedInTests : List String
testDepsNotUsedInTests =
Set.diff projectContext.directTestDependencies projectContext.usedDependenciesFromTest
|> Set.remove "elm/core"
|> Set.toList
in
List.map (unusedProjectDependencyError elmJsonKey projectContext.dependencies) depsNotUsedInSrcErrors
++ List.map (unusedTestDependencyError elmJsonKey projectContext.dependencies) testDepsNotUsedInTests
++ List.map (moveDependencyToTestError elmJsonKey projectContext.dependencies) (Set.toList depsNotUsedInSrcButUsedInTests)
Nothing ->
[]
error : Rule.ElmJsonKey -> String -> Error scope
error elmJsonKey packageName =
Rule.errorForElmJson elmJsonKey
-- ERROR FUNCTIONS
unusedProjectDependencyError : Rule.ElmJsonKey -> Dict String Dependency -> String -> Error scope
unusedProjectDependencyError elmJsonKey dependencies packageName =
Rule.errorForElmJsonWithFix elmJsonKey
(\elmJson ->
{ message = "Unused dependency `" ++ packageName ++ "`"
, details =
@ -210,6 +288,37 @@ error elmJsonKey packageName =
, range = findPackageNameInElmJson packageName elmJson
}
)
(fromProject dependencies InProjectDeps packageName >> Maybe.map (removeProjectDependency >> toProject))
moveDependencyToTestError : Rule.ElmJsonKey -> Dict String Dependency -> String -> Error scope
moveDependencyToTestError elmJsonKey dependencies packageName =
Rule.errorForElmJsonWithFix elmJsonKey
(\elmJson ->
{ message = "`" ++ packageName ++ "` should be moved to test-dependencies"
, details =
[ "This package is not used in the source code, but it is used in tests, and should therefore be moved to the test dependencies. To do so, I recommend running the following commands:"
, " elm-json uninstall " ++ packageName ++ "\n" ++ " elm-json install --test " ++ packageName
]
, range = findPackageNameInElmJson packageName elmJson
}
)
(fromProject dependencies InProjectDeps packageName >> Maybe.map (removeProjectDependency >> addTestDependency >> toProject))
unusedTestDependencyError : Rule.ElmJsonKey -> Dict String Dependency -> String -> Error scope
unusedTestDependencyError elmJsonKey dependencies packageName =
Rule.errorForElmJsonWithFix elmJsonKey
(\elmJson ->
{ message = "Unused test dependency `" ++ packageName ++ "`"
, details =
[ "To remove it, I recommend running the following command:"
, " elm-json uninstall " ++ packageName
]
, range = findPackageNameInElmJson packageName elmJson
}
)
(fromProject dependencies InTestDeps packageName >> Maybe.map (removeTestDependency >> toProject))
findPackageNameInElmJson : String -> String -> Range
@ -237,3 +346,269 @@ findPackageNameInElmJson packageName elmJson =
)
|> List.head
|> Maybe.withDefault { start = { row = 1, column = 1 }, end = { row = 10000, column = 1 } }
-- FIX
type ProjectAndDependencyIdentifier
= ApplicationProject
{ application : Elm.Project.ApplicationInfo
, name : Elm.Package.Name
, version : Elm.Version.Version
, getDependenciesAndVersion : Elm.Package.Name -> Elm.Project.Deps Elm.Version.Version
}
| PackageProject
{ package : Elm.Project.PackageInfo
, name : Elm.Package.Name
, constraint : Elm.Constraint.Constraint
}
type DependencyLocation
= InProjectDeps
| InTestDeps
fromProject : Dict String Dependency -> DependencyLocation -> String -> Project -> Maybe ProjectAndDependencyIdentifier
fromProject dependenciesDict dependencyLocation packageNameStr project =
case project of
Elm.Project.Application application ->
let
dependencies : Elm.Project.Deps Elm.Version.Version
dependencies =
case dependencyLocation of
InProjectDeps ->
application.depsDirect
InTestDeps ->
application.testDepsDirect
dependencyVersionDict : Dict String Elm.Version.Version
dependencyVersionDict =
[ application.depsDirect
, application.depsIndirect
, application.testDepsDirect
, application.testDepsIndirect
]
|> List.concat
|> List.map (\( name, version ) -> ( Elm.Package.toString name, version ))
|> Dict.fromList
getDependenciesAndVersion : Elm.Package.Name -> Elm.Project.Deps Elm.Version.Version
getDependenciesAndVersion name =
case Dict.get (Elm.Package.toString name) dependenciesDict of
Just deps ->
deps
|> Dependency.elmJson
|> packageDependencies
|> List.filterMap
(\depName ->
Dict.get (Elm.Package.toString depName) dependencyVersionDict
|> Maybe.map (Tuple.pair depName)
)
Nothing ->
[]
in
case find (isPackageWithName packageNameStr) dependencies of
Just ( packageName, version ) ->
Just
(ApplicationProject
{ application = application
, name = packageName
, version = version
, getDependenciesAndVersion = getDependenciesAndVersion
}
)
Nothing ->
Nothing
Elm.Project.Package packageInfo ->
let
dependencies : Elm.Project.Deps Elm.Constraint.Constraint
dependencies =
case dependencyLocation of
InProjectDeps ->
packageInfo.deps
InTestDeps ->
packageInfo.testDeps
in
case find (isPackageWithName packageNameStr) dependencies of
Just ( packageName, constraint ) ->
Just (PackageProject { package = packageInfo, name = packageName, constraint = constraint })
Nothing ->
Nothing
toProject : ProjectAndDependencyIdentifier -> Elm.Project.Project
toProject projectAndDependencyIdentifier =
case projectAndDependencyIdentifier of
ApplicationProject { application } ->
Elm.Project.Application application
PackageProject { package } ->
Elm.Project.Package package
removeProjectDependency : ProjectAndDependencyIdentifier -> ProjectAndDependencyIdentifier
removeProjectDependency projectAndDependencyIdentifier =
case projectAndDependencyIdentifier of
ApplicationProject ({ application } as project) ->
let
directDependencies : List ( Elm.Package.Name, Elm.Version.Version )
directDependencies =
List.filter (isPackageWithName (Elm.Package.toString project.name) >> not) application.depsDirect
depsIndirect : Elm.Project.Deps Elm.Version.Version
depsIndirect =
listIndirectDependencies
project.getDependenciesAndVersion
directDependencies
in
ApplicationProject
{ project
| application =
{ application
| depsDirect = directDependencies
, depsIndirect =
depsIndirect
, testDepsIndirect =
listIndirectDependencies
project.getDependenciesAndVersion
application.testDepsDirect
|> List.filter (\dep -> not (List.member dep application.depsDirect || List.member dep depsIndirect))
}
}
PackageProject ({ package } as project) ->
PackageProject
{ project
| package =
{ package
| deps = List.filter (isPackageWithName (Elm.Package.toString project.name) >> not) package.deps
}
}
listIndirectDependencies : (Elm.Package.Name -> Elm.Project.Deps Elm.Version.Version) -> Elm.Project.Deps Elm.Version.Version -> Elm.Project.Deps Elm.Version.Version
listIndirectDependencies getDependenciesAndVersion baseDependencies =
listIndirectDependenciesHelp getDependenciesAndVersion baseDependencies [] []
|> List.filter (\dep -> not (List.member dep baseDependencies))
listIndirectDependenciesHelp : (Elm.Package.Name -> Elm.Project.Deps Elm.Version.Version) -> Elm.Project.Deps Elm.Version.Version -> List Elm.Package.Name -> Elm.Project.Deps Elm.Version.Version -> Elm.Project.Deps Elm.Version.Version
listIndirectDependenciesHelp getDependenciesAndVersion dependenciesToLookAt visited indirectDependencies =
case List.filter (\( name, _ ) -> not (List.member name visited)) dependenciesToLookAt of
[] ->
indirectDependencies
( name, version ) :: restOfDependenciesToLookAt ->
listIndirectDependenciesHelp
getDependenciesAndVersion
(getDependenciesAndVersion name ++ restOfDependenciesToLookAt)
(name :: visited)
(( name, version ) :: indirectDependencies)
packageDependencies : Project -> List Elm.Package.Name
packageDependencies project =
case project of
Elm.Project.Application _ ->
[]
Elm.Project.Package package ->
List.map Tuple.first package.deps
addTestDependency : ProjectAndDependencyIdentifier -> ProjectAndDependencyIdentifier
addTestDependency projectAndDependencyIdentifier =
case projectAndDependencyIdentifier of
ApplicationProject ({ application } as project) ->
let
testDepsDirect : List ( Elm.Package.Name, Elm.Version.Version )
testDepsDirect =
( project.name, project.version ) :: application.testDepsDirect
in
ApplicationProject
{ project
| application =
{ application
| testDepsDirect = testDepsDirect
, testDepsIndirect =
listIndirectDependencies
project.getDependenciesAndVersion
testDepsDirect
|> List.filter (\dep -> not (List.member dep application.depsDirect || List.member dep application.depsIndirect))
}
}
PackageProject ({ package } as project) ->
PackageProject
{ project
| package =
{ package
| testDeps = ( project.name, project.constraint ) :: package.testDeps
}
}
removeTestDependency : ProjectAndDependencyIdentifier -> ProjectAndDependencyIdentifier
removeTestDependency projectAndDependencyIdentifier =
case projectAndDependencyIdentifier of
ApplicationProject ({ application } as project) ->
let
testDepsDirect : List ( Elm.Package.Name, Elm.Version.Version )
testDepsDirect =
List.filter (isPackageWithName (Elm.Package.toString project.name) >> not) application.testDepsDirect
in
ApplicationProject
{ project
| application =
{ application
| testDepsDirect = testDepsDirect
, testDepsIndirect =
listIndirectDependencies
project.getDependenciesAndVersion
testDepsDirect
|> List.filter (\dep -> not (List.member dep application.depsDirect || List.member dep application.depsIndirect))
}
}
PackageProject ({ package } as project) ->
PackageProject
{ project
| package =
{ package
| testDeps = List.filter (isPackageWithName (Elm.Package.toString project.name) >> not) package.testDeps
}
}
isPackageWithName : String -> ( Elm.Package.Name, a ) -> Bool
isPackageWithName packageName ( packageName_, _ ) =
packageName == Elm.Package.toString packageName_
{-| Find the first element that satisfies a predicate and return
Just that element. If none match, return Nothing.
find (\num -> num > 5) [ 2, 4, 6, 8 ] == Just 6
-}
find : (a -> Bool) -> List a -> Maybe a
find predicate list =
case list of
[] ->
Nothing
first :: rest ->
if predicate first then
Just first
else
find predicate rest

View File

@ -10,12 +10,21 @@ import Review.Test
import Test exposing (Test, describe, test)
createProject : String -> Project
createProject rawElmJson =
createProject : Maybe String -> String -> Project
createProject maybeTestModule rawElmJson =
Project.new
|> Project.addElmJson (createElmJson rawElmJson)
|> Project.addDependency packageWithFoo
|> Project.addDependency packageWithBar
|> Project.addDependency packageWithFoo
|> Project.addDependency packageWithTestBar
|> Project.addDependency packageWithTestFoo
|> (case maybeTestModule of
Just testModule ->
Project.addModule { path = "tests/TestModule.elm", source = testModule }
Nothing ->
identity
)
createElmJson : String -> { path : String, raw : String, project : Elm.Project.Project }
@ -42,9 +51,61 @@ applicationElmJson =
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"elm/core": "1.0.0",
"author/package-with-bar": "1.0.0",
"author/package-with-foo": "1.0.0",
"elm/core": "1.0.0"
},
"indirect": {}
},
"test-dependencies": {
"direct": {
"author/package-with-test-bar": "1.0.0",
"author/package-with-test-foo": "1.0.0"
},
"indirect": {}
}
}"""
applicationElmJsonWithoutBar : String
applicationElmJsonWithoutBar =
"""
{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"author/package-with-foo": "1.0.0",
"elm/core": "1.0.0"
},
"indirect": {
"author/package-with-bar": "1.0.0"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}"""
applicationElmJsonWithoutTestDeps : String
applicationElmJsonWithoutTestDeps =
"""
{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"author/package-with-bar": "1.0.0",
"author/package-with-foo": "1.0.0",
"elm/core": "1.0.0"
},
"indirect": {}
},
@ -73,27 +134,19 @@ packageElmJson =
"author/package-with-foo": "1.0.0 <= v < 2.0.0",
"author/package-with-bar": "1.0.0 <= v < 2.0.0"
},
"test-dependencies": {}
"test-dependencies": {
"author/package-with-test-foo": "1.0.0 <= v < 2.0.0",
"author/package-with-test-bar": "1.0.0 <= v < 2.0.0"
}
}"""
packageWithFoo : Dependency
packageWithFoo =
let
modules : List Elm.Docs.Module
modules =
[ { name = "Foo"
, comment = ""
, unions = []
, aliases = []
, values = []
, binops = []
}
]
elmJson : Elm.Project.Project
elmJson : { path : String, raw : String, project : Elm.Project.Project }
elmJson =
.project <| createElmJson """
createElmJson """
{
"type": "package",
"name": "author/package-with-foo",
@ -112,27 +165,45 @@ packageWithFoo =
in
Dependency.create
"author/package-with-foo"
elmJson
modules
elmJson.project
(dummyModules "Foo")
packageWithFooDependingOnBar : Dependency
packageWithFooDependingOnBar =
let
elmJson : { path : String, raw : String, project : Elm.Project.Project }
elmJson =
createElmJson """
{
"type": "package",
"name": "author/package-with-foo",
"summary": "Summary",
"license": "BSD-3-Clause",
"version": "1.0.0",
"exposed-modules": [
"Foo"
],
"elm-version": "0.19.0 <= v < 0.20.0",
"dependencies": {
"elm/core": "1.0.0 <= v < 2.0.0",
"author/package-with-bar": "1.0.0 <= v < 2.0.0"
},
"test-dependencies": {}
}"""
in
Dependency.create
"author/package-with-foo"
elmJson.project
(dummyModules "Foo")
packageWithBar : Dependency
packageWithBar =
let
modules : List Elm.Docs.Module
modules =
[ { name = "Bar"
, comment = ""
, unions = []
, aliases = []
, values = []
, binops = []
}
]
elmJson : Elm.Project.Project
elmJson : { path : String, raw : String, project : Elm.Project.Project }
elmJson =
.project <| createElmJson """
createElmJson """
{
"type": "package",
"name": "author/package-with-bar",
@ -151,8 +222,64 @@ packageWithBar =
in
Dependency.create
"author/package-with-bar"
elmJson
modules
elmJson.project
(dummyModules "Bar")
packageWithTestFoo : Dependency
packageWithTestFoo =
let
elmJson : { path : String, raw : String, project : Elm.Project.Project }
elmJson =
createElmJson """
{
"type": "package",
"name": "author/package-with-test-foo",
"summary": "Summary",
"license": "BSD-3-Clause",
"version": "1.0.0",
"exposed-modules": [
"TestFoo"
],
"elm-version": "0.19.0 <= v < 0.20.0",
"dependencies": {
"elm/core": "1.0.0 <= v < 2.0.0"
},
"test-dependencies": {}
}"""
in
Dependency.create
"author/package-with-test-foo"
elmJson.project
(dummyModules "TestFoo")
packageWithTestBar : Dependency
packageWithTestBar =
let
elmJson : { path : String, raw : String, project : Elm.Project.Project }
elmJson =
createElmJson """
{
"type": "package",
"name": "author/package-with-test-bar",
"summary": "Summary",
"license": "BSD-3-Clause",
"version": "1.0.0",
"exposed-modules": [
"TestBar"
],
"elm-version": "0.19.0 <= v < 0.20.0",
"dependencies": {
"elm/core": "1.0.0 <= v < 2.0.0"
},
"test-dependencies": {}
}"""
in
Dependency.create
"author/package-with-test-bar"
elmJson.project
(dummyModules "TestBar")
all : Test
@ -164,6 +291,7 @@ all =
module A exposing (a)
a = 1
"""
|> String.replace "\u{000D}" ""
|> Review.Test.run rule
|> Review.Test.expectNoErrors
, test "should report unused dependencies for an application when none of their modules are imported" <|
@ -172,7 +300,8 @@ a = 1
module A exposing (a)
a = 1
"""
|> Review.Test.runWithProjectData (createProject applicationElmJson) rule
|> String.replace "\u{000D}" ""
|> Review.Test.runWithProjectData (createProject Nothing applicationElmJson) rule
|> Review.Test.expectErrorsForElmJson
[ Review.Test.error
{ message = "Unused dependency `author/package-with-bar`"
@ -182,6 +311,28 @@ a = 1
]
, under = "author/package-with-bar"
}
|> Review.Test.whenFixed """{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"author/package-with-foo": "1.0.0",
"elm/core": "1.0.0"
},
"indirect": {}
},
"test-dependencies": {
"direct": {
"author/package-with-test-bar": "1.0.0",
"author/package-with-test-foo": "1.0.0"
},
"indirect": {}
}
}
"""
, Review.Test.error
{ message = "Unused dependency `author/package-with-foo`"
, details =
@ -190,16 +341,111 @@ a = 1
]
, under = "author/package-with-foo"
}
|> Review.Test.whenFixed """{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"author/package-with-bar": "1.0.0",
"elm/core": "1.0.0"
},
"indirect": {}
},
"test-dependencies": {
"direct": {
"author/package-with-test-bar": "1.0.0",
"author/package-with-test-foo": "1.0.0"
},
"indirect": {}
}
}
"""
, Review.Test.error
{ message = "Unused test dependency `author/package-with-test-bar`"
, details =
[ "To remove it, I recommend running the following command:"
, " elm-json uninstall author/package-with-test-bar"
]
, under = "author/package-with-test-bar"
}
|> Review.Test.whenFixed """{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"author/package-with-bar": "1.0.0",
"author/package-with-foo": "1.0.0",
"elm/core": "1.0.0"
},
"indirect": {}
},
"test-dependencies": {
"direct": {
"author/package-with-test-foo": "1.0.0"
},
"indirect": {}
}
}
"""
, Review.Test.error
{ message = "Unused test dependency `author/package-with-test-foo`"
, details =
[ "To remove it, I recommend running the following command:"
, " elm-json uninstall author/package-with-test-foo"
]
, under = "author/package-with-test-foo"
}
|> Review.Test.whenFixed """{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"author/package-with-bar": "1.0.0",
"author/package-with-foo": "1.0.0",
"elm/core": "1.0.0"
},
"indirect": {}
},
"test-dependencies": {
"direct": {
"author/package-with-test-bar": "1.0.0"
},
"indirect": {}
}
}
"""
]
, test "should not report dependencies for an application whose modules are imported" <|
\() ->
let
testModule : String
testModule =
"""module TestModule exposing (suite)
import TestFoo
import TestBar
suite = 0
"""
|> String.replace "\u{000D}" ""
in
"""
module A exposing (a)
import Foo
import Bar
a = 1
"""
|> Review.Test.runWithProjectData (createProject applicationElmJson) rule
|> String.replace "\u{000D}" ""
|> Review.Test.runWithProjectData (createProject (Just testModule) applicationElmJson) rule
|> Review.Test.expectNoErrors
, test "should report unused dependencies for a package when none of their modules are imported" <|
\() ->
@ -207,7 +453,8 @@ a = 1
module A exposing (a)
a = 1
"""
|> Review.Test.runWithProjectData (createProject packageElmJson) rule
|> String.replace "\u{000D}" ""
|> Review.Test.runWithProjectData (createProject Nothing packageElmJson) rule
|> Review.Test.expectErrorsForElmJson
[ Review.Test.error
{ message = "Unused dependency `author/package-with-bar`"
@ -217,6 +464,26 @@ a = 1
]
, under = "author/package-with-bar"
}
|> Review.Test.whenFixed ("""{
"type": "package",
"name": "author/package",
"summary": "Summary",
"license": "BSD-3-Clause",
"version": "1.0.0",
"exposed-modules": [
"Exposed"
],
"elm-version": "0.19.0 <= v < 0.20.0",
"dependencies": {
"author/package-with-foo": "1.0.0 <= v < 2.0.0",
"elm/core": "1.0.0 <= v < 2.0.0"
},
"test-dependencies": {
"author/package-with-test-bar": "1.0.0 <= v < 2.0.0",
"author/package-with-test-foo": "1.0.0 <= v < 2.0.0"
}
}
""" |> String.replace "\u{000D}" "")
, Review.Test.error
{ message = "Unused dependency `author/package-with-foo`"
, details =
@ -225,15 +492,393 @@ a = 1
]
, under = "author/package-with-foo"
}
|> Review.Test.whenFixed ("""{
"type": "package",
"name": "author/package",
"summary": "Summary",
"license": "BSD-3-Clause",
"version": "1.0.0",
"exposed-modules": [
"Exposed"
],
"elm-version": "0.19.0 <= v < 0.20.0",
"dependencies": {
"author/package-with-bar": "1.0.0 <= v < 2.0.0",
"elm/core": "1.0.0 <= v < 2.0.0"
},
"test-dependencies": {
"author/package-with-test-bar": "1.0.0 <= v < 2.0.0",
"author/package-with-test-foo": "1.0.0 <= v < 2.0.0"
}
}
""" |> String.replace "\u{000D}" "")
, Review.Test.error
{ message = "Unused test dependency `author/package-with-test-bar`"
, details =
[ "To remove it, I recommend running the following command:"
, " elm-json uninstall author/package-with-test-bar"
]
, under = "author/package-with-test-bar"
}
|> Review.Test.whenFixed ("""{
"type": "package",
"name": "author/package",
"summary": "Summary",
"license": "BSD-3-Clause",
"version": "1.0.0",
"exposed-modules": [
"Exposed"
],
"elm-version": "0.19.0 <= v < 0.20.0",
"dependencies": {
"author/package-with-bar": "1.0.0 <= v < 2.0.0",
"author/package-with-foo": "1.0.0 <= v < 2.0.0",
"elm/core": "1.0.0 <= v < 2.0.0"
},
"test-dependencies": {
"author/package-with-test-foo": "1.0.0 <= v < 2.0.0"
}
}
""" |> String.replace "\u{000D}" "")
, Review.Test.error
{ message = "Unused test dependency `author/package-with-test-foo`"
, details =
[ "To remove it, I recommend running the following command:"
, " elm-json uninstall author/package-with-test-foo"
]
, under = "author/package-with-test-foo"
}
|> Review.Test.whenFixed ("""{
"type": "package",
"name": "author/package",
"summary": "Summary",
"license": "BSD-3-Clause",
"version": "1.0.0",
"exposed-modules": [
"Exposed"
],
"elm-version": "0.19.0 <= v < 0.20.0",
"dependencies": {
"author/package-with-bar": "1.0.0 <= v < 2.0.0",
"author/package-with-foo": "1.0.0 <= v < 2.0.0",
"elm/core": "1.0.0 <= v < 2.0.0"
},
"test-dependencies": {
"author/package-with-test-bar": "1.0.0 <= v < 2.0.0"
}
}
""" |> String.replace "\u{000D}" "")
]
, test "should not report dependencies for a package whose modules are imported" <|
\() ->
let
testModule : String
testModule =
"""module TestModule exposing (suite)
import TestFoo
import TestBar
suite = 0
"""
|> String.replace "\u{000D}" ""
in
"""
module A exposing (a)
import Foo
import Bar
a = 1
"""
|> Review.Test.runWithProjectData (createProject packageElmJson) rule
|> String.replace "\u{000D}" ""
|> Review.Test.runWithProjectData (createProject (Just testModule) packageElmJson) rule
|> Review.Test.expectNoErrors
, test "should report dependencies that's only used in tests" <|
\() ->
let
testModule : String
testModule =
"""module TestModule exposing (suite)
import Foo
import TestFoo
import TestBar
suite = 0
"""
|> String.replace "\u{000D}" ""
in
"""
module A exposing (a)
import Bar
a = 1
"""
|> String.replace "\u{000D}" ""
|> Review.Test.runWithProjectData (createProject (Just testModule) applicationElmJson) rule
|> Review.Test.expectErrorsForElmJson
[ Review.Test.error
{ message = "`author/package-with-foo` should be moved to test-dependencies"
, details =
[ "This package is not used in the source code, but it is used in tests, and should therefore be moved to the test dependencies. To do so, I recommend running the following commands:"
, " elm-json uninstall author/package-with-foo\n"
++ " elm-json install --test author/package-with-foo"
]
, under = "author/package-with-foo"
}
|> Review.Test.whenFixed """{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"author/package-with-bar": "1.0.0",
"elm/core": "1.0.0"
},
"indirect": {}
},
"test-dependencies": {
"direct": {
"author/package-with-foo": "1.0.0",
"author/package-with-test-bar": "1.0.0",
"author/package-with-test-foo": "1.0.0"
},
"indirect": {}
}
}
"""
]
, test "should move unused dependencies to indirect deps if it's a dependency of a direct dependency" <|
\() ->
"""
module A exposing (a)
import Foo
a = 1
"""
|> String.replace "\u{000D}" ""
|> Review.Test.runWithProjectData
(createProject Nothing applicationElmJsonWithoutTestDeps
|> Project.addDependency packageWithFooDependingOnBar
)
rule
|> Review.Test.expectErrorsForElmJson
[ Review.Test.error
{ message = "Unused dependency `author/package-with-bar`"
, details =
[ "To remove it, I recommend running the following command:"
, " elm-json uninstall author/package-with-bar"
]
, under = "author/package-with-bar"
}
|> Review.Test.whenFixed """{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"author/package-with-foo": "1.0.0",
"elm/core": "1.0.0"
},
"indirect": {
"author/package-with-bar": "1.0.0"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}
"""
]
, test "should move unused dependencies to test deps and indirect deps if it's a dependency of a direct dependency" <|
\() ->
let
testModule : String
testModule =
"""module TestModule exposing (suite)
import Bar
import TestFoo
import TestBar
suite = 0
"""
|> String.replace "\u{000D}" ""
in
"""
module A exposing (a)
import Foo
a = 1
"""
|> String.replace "\u{000D}" ""
|> Review.Test.runWithProjectData
(createProject (Just testModule) applicationElmJson
|> Project.addDependency packageWithFooDependingOnBar
)
rule
|> Review.Test.expectErrorsForElmJson
[ Review.Test.error
{ message = "`author/package-with-bar` should be moved to test-dependencies"
, details =
[ "This package is not used in the source code, but it is used in tests, and should therefore be moved to the test dependencies. To do so, I recommend running the following commands:"
, " elm-json uninstall author/package-with-bar\n"
++ " elm-json install --test author/package-with-bar"
]
, under = "author/package-with-bar"
}
|> Review.Test.whenFixed """{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"author/package-with-foo": "1.0.0",
"elm/core": "1.0.0"
},
"indirect": {
"author/package-with-bar": "1.0.0"
}
},
"test-dependencies": {
"direct": {
"author/package-with-bar": "1.0.0",
"author/package-with-test-bar": "1.0.0",
"author/package-with-test-foo": "1.0.0"
},
"indirect": {}
}
}
"""
]
, test "should re-organize the indirect dependencies when a dependency gets removed" <|
\() ->
let
testModule : String
testModule =
"""module TestModule exposing (suite)
import Foo
suite = 0
"""
|> String.replace "\u{000D}" ""
in
"""
module A exposing (a)
a = 1
"""
|> String.replace "\u{000D}" ""
|> Review.Test.runWithProjectData
(createProject (Just testModule) applicationElmJsonWithoutBar
|> Project.addDependency packageWithFooDependingOnBar
|> Project.removeDependency (Dependency.name packageWithTestFoo)
|> Project.removeDependency (Dependency.name packageWithTestBar)
)
rule
|> Review.Test.expectErrorsForElmJson
[ Review.Test.error
{ message = "`author/package-with-foo` should be moved to test-dependencies"
, details =
[ "This package is not used in the source code, but it is used in tests, and should therefore be moved to the test dependencies. To do so, I recommend running the following commands:"
, " elm-json uninstall author/package-with-foo\n"
++ " elm-json install --test author/package-with-foo"
]
, under = "author/package-with-foo"
}
|> Review.Test.whenFixed """{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"elm/core": "1.0.0"
},
"indirect": {}
},
"test-dependencies": {
"direct": {
"author/package-with-foo": "1.0.0"
},
"indirect": {
"author/package-with-bar": "1.0.0"
}
}
}
"""
]
, test "should report dependencies that's only used in tests and fix it when it's a package elm.json" <|
\() ->
let
testModule : String
testModule =
"""module TestModule exposing (suite)
import Foo
import TestFoo
import TestBar
suite = 0
"""
|> String.replace "\u{000D}" ""
expected : String
expected =
"""{
"type": "package",
"name": "author/package",
"summary": "Summary",
"license": "BSD-3-Clause",
"version": "1.0.0",
"exposed-modules": [
"Exposed"
],
"elm-version": "0.19.0 <= v < 0.20.0",
"dependencies": {
"author/package-with-bar": "1.0.0 <= v < 2.0.0",
"elm/core": "1.0.0 <= v < 2.0.0"
},
"test-dependencies": {
"author/package-with-foo": "1.0.0 <= v < 2.0.0",
"author/package-with-test-bar": "1.0.0 <= v < 2.0.0",
"author/package-with-test-foo": "1.0.0 <= v < 2.0.0"
}
}
"""
|> String.replace "\u{000D}" ""
in
"""
module A exposing (a)
import Bar
a = 1
"""
|> String.replace "\u{000D}" ""
|> Review.Test.runWithProjectData (createProject (Just testModule) packageElmJson) rule
|> Review.Test.expectErrorsForElmJson
[ Review.Test.error
{ message = "`author/package-with-foo` should be moved to test-dependencies"
, details =
[ "This package is not used in the source code, but it is used in tests, and should therefore be moved to the test dependencies. To do so, I recommend running the following commands:"
, " elm-json uninstall author/package-with-foo\n"
++ " elm-json install --test author/package-with-foo"
]
, under = "author/package-with-foo"
}
|> Review.Test.whenFixed expected
]
]
dummyModules : String -> List Elm.Docs.Module
dummyModules name =
[ { name = name
, comment = ""
, unions = []
, aliases = []
, values = []
, binops = []
}
]

View File

@ -47,8 +47,8 @@ applicationElmJson =
}
package_ : Project
package_ =
package : Project
package =
Project.new
|> Project.addElmJson (createPackageElmJson ())
@ -203,7 +203,7 @@ module A exposing (exposed)
exposed = 1
main = exposed
"""
|> Review.Test.runWithProjectData package_ rule
|> Review.Test.runWithProjectData package rule
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Exposed function or value `exposed` is never used outside this module."
@ -219,7 +219,7 @@ module A exposing (exposed1, exposed2)
exposed1 = 1
exposed2 = 2
"""
|> Review.Test.runWithProjectData package_ rule
|> Review.Test.runWithProjectData package rule
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Exposed function or value `exposed1` is never used outside this module."
@ -250,7 +250,7 @@ exposed2 = 2
module A exposing (..)
a = 1
"""
|> Review.Test.runWithProjectData package_ rule
|> Review.Test.runWithProjectData package rule
|> Review.Test.expectNoErrors
, test "should not report the `main` function for an application even if it is unused" <|
\() ->
@ -266,7 +266,7 @@ main = text ""
module Main exposing (main)
main = text ""
"""
|> Review.Test.runWithProjectData package_ rule
|> Review.Test.runWithProjectData package rule
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Exposed function or value `main` is never used outside this module."
@ -281,7 +281,7 @@ main = text ""
module A exposing (b)
a = 1
"""
|> Review.Test.runWithProjectData package_ rule
|> Review.Test.runWithProjectData package rule
|> Review.Test.expectNoErrors
, test "should not report exposed tests" <|
\() ->
@ -291,7 +291,7 @@ import Test exposing (Test)
a : Test
a = Test.describe "thing" []
"""
|> Review.Test.runWithProjectData package_ rule
|> Review.Test.runWithProjectData package rule
|> Review.Test.expectNoErrors
, test "should not ReviewConfig.config" <|
\() ->
@ -299,7 +299,7 @@ a = Test.describe "thing" []
module ReviewConfig exposing (config)
config = []
"""
|> Review.Test.runWithProjectData package_ rule
|> Review.Test.runWithProjectData package rule
|> Review.Test.expectNoErrors
, test "should not report a function in a 'shadowed' module" <|
\() ->
@ -360,7 +360,7 @@ import A
type alias B = A.ExposedB
type alias C = A.ExposedC
""" ]
|> Review.Test.runOnModulesWithProjectData package_ rule
|> Review.Test.runOnModulesWithProjectData package rule
|> Review.Test.expectNoErrors
, test "should not report an unused exposed custom type if it's part of the package's exposed API" <|
\() ->
@ -368,7 +368,7 @@ type alias C = A.ExposedC
module Exposed exposing (MyType)
type MyType = VariantA | VariantB
"""
|> Review.Test.runWithProjectData package_ rule
|> Review.Test.runWithProjectData package rule
|> Review.Test.expectNoErrors
, test "should not report an unused exposed custom type if it's present in the signature of an exposed function" <|
\() ->
@ -391,7 +391,7 @@ module Exposed exposing (..)
import A
type alias B = A.OtherType
""" ]
|> Review.Test.runOnModulesWithProjectData package_ rule
|> Review.Test.runOnModulesWithProjectData package rule
|> Review.Test.expectNoErrors
, test "should not report an unused exposed custom type if it's present in an exposed type alias" <|
\() ->
@ -404,7 +404,7 @@ module Exposed exposing (..)
import A
type alias B = A.OtherType
""" ]
|> Review.Test.runOnModulesWithProjectData package_ rule
|> Review.Test.runOnModulesWithProjectData package rule
|> Review.Test.expectNoErrors
, test "should not report an unused exposed custom type if it's present in an exposed type alias (nested)" <|
\() ->
@ -417,7 +417,7 @@ module Exposed exposing (..)
import A
type alias B = A.OtherType
""" ]
|> Review.Test.runOnModulesWithProjectData package_ rule
|> Review.Test.runOnModulesWithProjectData package rule
|> Review.Test.expectNoErrors
, test "should not report an unused exposed custom type if it's present in an exposed custom type constructor's arguments" <|
\() ->
@ -430,7 +430,7 @@ module Exposed exposing (..)
import A
type alias B = A.OtherType
""" ]
|> Review.Test.runOnModulesWithProjectData package_ rule
|> Review.Test.runOnModulesWithProjectData package rule
|> Review.Test.expectNoErrors
, test "should not report an unused exposed custom type if it's present in an exposed custom type constructor's arguments but the constructors are not exposed" <|
\() ->
@ -443,7 +443,7 @@ module Exposed exposing (..)
import A
type alias B = A.OtherType
""" ]
|> Review.Test.runOnModulesWithProjectData package_ rule
|> Review.Test.runOnModulesWithProjectData package rule
|> Review.Test.expectErrorsForModules
[ ( "A"
, [ Review.Test.error
@ -471,7 +471,7 @@ module Exposed exposing (..)
import A
type alias B = A.OtherType
""" ]
|> Review.Test.runOnModulesWithProjectData package_ rule
|> Review.Test.runOnModulesWithProjectData package rule
|> Review.Test.expectNoErrors
, test "should not report an unused exposed custom type if it's present in an exposed custom type constructor's arguments (nested) but the constructors are not exposed" <|
\() ->
@ -484,7 +484,7 @@ module Exposed exposing (..)
import A
type alias B = A.OtherType
""" ]
|> Review.Test.runOnModulesWithProjectData package_ rule
|> Review.Test.runOnModulesWithProjectData package rule
|> Review.Test.expectErrorsForModules
[ ( "A"
, [ Review.Test.error
@ -589,7 +589,7 @@ module Exposed exposing (B)
import A
type alias B = A.ExposedB
""" ]
|> Review.Test.runOnModulesWithProjectData package_ rule
|> Review.Test.runOnModulesWithProjectData package rule
|> Review.Test.expectNoErrors
, test "should not report an unused exposed type alias if it's part of the package's exposed API" <|
\() ->
@ -597,7 +597,7 @@ type alias B = A.ExposedB
module Exposed exposing (MyType)
type alias MyType = {}
"""
|> Review.Test.runWithProjectData package_ rule
|> Review.Test.runWithProjectData package rule
|> Review.Test.expectNoErrors
, test "should not report an unused exposed type alias if it's present in the signature of an exposed function" <|
\() ->
@ -620,7 +620,7 @@ module Exposed exposing (..)
import A
type alias B = A.OtherType
""" ]
|> Review.Test.runOnModulesWithProjectData package_ rule
|> Review.Test.runOnModulesWithProjectData package rule
|> Review.Test.expectNoErrors
, test "should not report an unused exposed type alias if it's present in an exposed type alias" <|
\() ->
@ -633,7 +633,7 @@ module Exposed exposing (..)
import A
type alias B = A.OtherType
""" ]
|> Review.Test.runOnModulesWithProjectData package_ rule
|> Review.Test.runOnModulesWithProjectData package rule
|> Review.Test.expectNoErrors
, test "should not report an unused exposed type alias if it's present in an exposed type alias (nested)" <|
\() ->
@ -646,7 +646,7 @@ module Exposed exposing (..)
import A
type alias B = A.OtherType
""" ]
|> Review.Test.runOnModulesWithProjectData package_ rule
|> Review.Test.runOnModulesWithProjectData package rule
|> Review.Test.expectNoErrors
, test "should not report an unused exposed type alias if it's present in an exposed custom type constructor's arguments" <|
\() ->
@ -659,7 +659,7 @@ module Exposed exposing (..)
import A
type alias B = A.OtherType
""" ]
|> Review.Test.runOnModulesWithProjectData package_ rule
|> Review.Test.runOnModulesWithProjectData package rule
|> Review.Test.expectNoErrors
, test "should not report an unused exposed type alias if it's present in an exposed custom type constructor's arguments but the constructors are not exposed" <|
\() ->
@ -672,7 +672,7 @@ module Exposed exposing (..)
import A
type alias B = A.OtherType
""" ]
|> Review.Test.runOnModulesWithProjectData package_ rule
|> Review.Test.runOnModulesWithProjectData package rule
|> Review.Test.expectErrorsForModules
[ ( "A"
, [ Review.Test.error
@ -700,7 +700,7 @@ module Exposed exposing (..)
import A
type alias B = A.OtherType
""" ]
|> Review.Test.runOnModulesWithProjectData package_ rule
|> Review.Test.runOnModulesWithProjectData package rule
|> Review.Test.expectNoErrors
, test "should report an unused exposed type alias if it's present in an exposed custom type constructor's arguments (nested) but the constructors are not exposed" <|
\() ->
@ -713,7 +713,7 @@ module Exposed exposing (..)
import A
type alias B = A.OtherType
""" ]
|> Review.Test.runOnModulesWithProjectData package_ rule
|> Review.Test.runOnModulesWithProjectData package rule
|> Review.Test.expectErrorsForModules
[ ( "A"
, [ Review.Test.error
@ -746,7 +746,7 @@ module Exposed exposing (..)
import A
a = A.Card A.init A.toElement
""" ]
|> Review.Test.runOnModulesWithProjectData package_ rule
|> Review.Test.runOnModulesWithProjectData package rule
|> Review.Test.expectErrorsForModules
[ ( "A"
, [ Review.Test.error

View File

@ -47,8 +47,8 @@ applicationElmJson =
}
package_ : Project
package_ =
package : Project
package =
Project.new
|> Project.addElmJson (createPackageElmJson ())
@ -203,7 +203,7 @@ config = []
module Exposed exposing (..)
a = 1
"""
|> Review.Test.runWithProjectData package_ rule
|> Review.Test.runWithProjectData package rule
|> Review.Test.expectNoErrors
, test "should report non-exposed and non-used modules from a package" <|
\() ->
@ -211,7 +211,7 @@ a = 1
module NotExposed exposing (..)
a = 1
"""
|> Review.Test.runWithProjectData package_ rule
|> Review.Test.runWithProjectData package rule
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Module `NotExposed` is never used."
@ -225,7 +225,7 @@ a = 1
module Reported exposing (main)
main = text ""
"""
|> Review.Test.runWithProjectData package_ rule
|> Review.Test.runWithProjectData package rule
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Module `Reported` is never used."
@ -240,7 +240,7 @@ module Reported exposing (a)
main = text ""
a = 1
"""
|> Review.Test.runWithProjectData package_ rule
|> Review.Test.runWithProjectData package rule
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Module `Reported` is never used."