mirror of
https://github.com/jfmengels/elm-review.git
synced 2024-12-23 17:53:35 +03:00
Backport rules from elm-review-unused
This commit is contained in:
parent
4b47d203f3
commit
05d2622923
@ -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 )
|
||||
|
||||
|
@ -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" <|
|
||||
\() ->
|
||||
"""
|
||||
|
@ -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
|
||||
|
@ -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(..))
|
||||
|
@ -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
|
||||
|
@ -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 = []
|
||||
}
|
||||
]
|
||||
|
@ -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
|
||||
|
@ -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."
|
||||
|
Loading…
Reference in New Issue
Block a user