mirror of
https://github.com/jfmengels/elm-review.git
synced 2024-11-23 14:55:35 +03:00
Backport rules from elm-review-unused
This commit is contained in:
parent
c4f775fddb
commit
4dd3bb5dff
@ -274,13 +274,17 @@ foldProjectContexts newContext previousContext =
|
||||
newContext.customTypeArgs
|
||||
previousContext.customTypeArgs
|
||||
, usedArguments =
|
||||
Dict.merge
|
||||
Dict.insert
|
||||
(\key newSet prevSet dict -> Dict.insert key (Set.union newSet prevSet) dict)
|
||||
Dict.insert
|
||||
newContext.usedArguments
|
||||
Dict.foldl
|
||||
(\key newSet acc ->
|
||||
case Dict.get key acc of
|
||||
Just existingSet ->
|
||||
Dict.insert key (Set.union newSet existingSet) acc
|
||||
|
||||
Nothing ->
|
||||
Dict.insert key newSet acc
|
||||
)
|
||||
previousContext.usedArguments
|
||||
Dict.empty
|
||||
newContext.usedArguments
|
||||
, customTypesNotToReport = Set.union newContext.customTypesNotToReport previousContext.customTypesNotToReport
|
||||
}
|
||||
|
||||
|
@ -123,14 +123,14 @@ elm-review --template jfmengels/elm-review-unused/example --rules NoUnused.Custo
|
||||
rule : List { moduleName : String, typeName : String, index : Int } -> Rule
|
||||
rule phantomTypes =
|
||||
Rule.newProjectRuleSchema "NoUnused.CustomTypeConstructors" (initialProjectContext phantomTypes)
|
||||
|> Rule.withContextFromImportedModules
|
||||
|> Rule.withElmJsonProjectVisitor elmJsonVisitor
|
||||
|> Rule.withModuleVisitor moduleVisitor
|
||||
|> Rule.withModuleContextUsingContextCreator
|
||||
{ fromProjectToModule = fromProjectToModule
|
||||
, fromModuleToProject = fromModuleToProject
|
||||
, foldProjectContexts = foldProjectContexts
|
||||
}
|
||||
|> Rule.withElmJsonProjectVisitor elmJsonVisitor
|
||||
|> Rule.withContextFromImportedModules
|
||||
|> Rule.withFinalProjectEvaluation finalProjectEvaluation
|
||||
|> Rule.fromProjectRuleSchema
|
||||
|
||||
@ -351,32 +351,36 @@ foldProjectContexts newContext previousContext =
|
||||
{ exposedModules = previousContext.exposedModules
|
||||
, declaredConstructors = Dict.union newContext.declaredConstructors previousContext.declaredConstructors
|
||||
, usedConstructors =
|
||||
Dict.merge
|
||||
Dict.insert
|
||||
(\key newUsed previousUsed dict -> Dict.insert key (Set.union newUsed previousUsed) dict)
|
||||
Dict.insert
|
||||
newContext.usedConstructors
|
||||
Dict.foldl
|
||||
(\key newSet acc ->
|
||||
case Dict.get key acc of
|
||||
Just existingSet ->
|
||||
Dict.insert key (Set.union newSet existingSet) acc
|
||||
|
||||
Nothing ->
|
||||
Dict.insert key newSet acc
|
||||
)
|
||||
previousContext.usedConstructors
|
||||
Dict.empty
|
||||
newContext.usedConstructors
|
||||
, 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
|
||||
, fixesForRemovingConstructor =
|
||||
Dict.foldl
|
||||
(\key newFixes acc ->
|
||||
case Dict.get key acc of
|
||||
Just existingFixes ->
|
||||
Dict.insert key (List.foldl (::) existingFixes newFixes) acc
|
||||
|
||||
Nothing ->
|
||||
Dict.insert key newFixes acc
|
||||
)
|
||||
previousContext.fixesForRemovingConstructor
|
||||
newContext.fixesForRemovingConstructor
|
||||
}
|
||||
|
||||
|
||||
mergeDictsWithLists : Dict comparable (List a) -> Dict comparable (List a) -> Dict comparable (List a)
|
||||
mergeDictsWithLists left right =
|
||||
Dict.merge
|
||||
Dict.insert
|
||||
(\key a b dict -> Dict.insert key (a ++ b) dict)
|
||||
Dict.insert
|
||||
left
|
||||
right
|
||||
Dict.empty
|
||||
|
||||
|
||||
updateToAdd : comparable -> a -> Dict comparable (List a) -> Dict comparable (List a)
|
||||
updateToAdd key value dict =
|
||||
Dict.update
|
||||
|
@ -44,7 +44,7 @@ rule : Rule
|
||||
rule =
|
||||
Rule.newProjectRuleSchema "NoUnused.Dependencies" initialProjectContext
|
||||
|> Rule.withElmJsonProjectVisitor elmJsonVisitor
|
||||
|> Rule.withDirectDependenciesProjectVisitor dependenciesVisitor
|
||||
|> Rule.withDependenciesProjectVisitor dependenciesVisitor
|
||||
|> Rule.withModuleVisitor moduleVisitor
|
||||
|> Rule.withModuleContextUsingContextCreator
|
||||
{ fromProjectToModule = fromProjectToModule
|
||||
@ -66,15 +66,18 @@ dependenciesVisitor dependencies projectContext =
|
||||
let
|
||||
moduleNameToDependency : Dict String String
|
||||
moduleNameToDependency =
|
||||
Dict.foldl
|
||||
(\packageName dependency dict ->
|
||||
List.foldl
|
||||
(\{ name } d -> Dict.insert name packageName d)
|
||||
dict
|
||||
(Dependency.modules dependency)
|
||||
)
|
||||
Dict.empty
|
||||
dependencies
|
||||
dependencies
|
||||
|> Dict.filter
|
||||
(\packageName _ ->
|
||||
Set.member packageName projectContext.directProjectDependencies
|
||||
|| Set.member packageName projectContext.directTestDependencies
|
||||
)
|
||||
|> Dict.toList
|
||||
|> List.concatMap
|
||||
(\( packageName, dependency ) ->
|
||||
List.map (\{ name } -> ( name, packageName )) (Dependency.modules dependency)
|
||||
)
|
||||
|> Dict.fromList
|
||||
in
|
||||
( []
|
||||
, { projectContext
|
||||
@ -475,28 +478,23 @@ removeProjectDependency projectAndDependencyIdentifier =
|
||||
case projectAndDependencyIdentifier of
|
||||
ApplicationProject ({ application } as project) ->
|
||||
let
|
||||
directDependencies : List ( Elm.Package.Name, Elm.Version.Version )
|
||||
directDependencies =
|
||||
depsDirect : List ( Elm.Package.Name, Elm.Version.Version )
|
||||
depsDirect =
|
||||
List.filter (\pkg -> pkg |> isPackageWithName (Elm.Package.toString project.name) |> not) application.depsDirect
|
||||
|
||||
depsIndirect : Elm.Project.Deps Elm.Version.Version
|
||||
depsIndirect =
|
||||
listIndirectDependencies
|
||||
project.getDependenciesAndVersion
|
||||
directDependencies
|
||||
listIndirectDependencies project.getDependenciesAndVersion depsDirect
|
||||
in
|
||||
ApplicationProject
|
||||
{ project
|
||||
| application =
|
||||
{ application
|
||||
| depsDirect = directDependencies
|
||||
, depsIndirect =
|
||||
depsIndirect
|
||||
| depsDirect = depsDirect
|
||||
, depsIndirect = depsIndirect
|
||||
, testDepsIndirect =
|
||||
listIndirectDependencies
|
||||
project.getDependenciesAndVersion
|
||||
application.testDepsDirect
|
||||
|> List.filter (\dep -> not (List.member dep application.depsDirect || List.member dep depsIndirect))
|
||||
listIndirectDependencies project.getDependenciesAndVersion application.testDepsDirect
|
||||
|> List.filter (\dep -> not (List.member dep depsDirect || List.member dep depsIndirect))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ import Elm.Syntax.Module as Module
|
||||
import Elm.Syntax.ModuleName exposing (ModuleName)
|
||||
import Elm.Syntax.Node as Node exposing (Node(..))
|
||||
import Elm.Syntax.Pattern as Pattern exposing (Pattern)
|
||||
import Elm.Syntax.Range exposing (Range)
|
||||
import Elm.Syntax.Range as Range exposing (Range)
|
||||
import Elm.Syntax.TypeAnnotation as TypeAnnotation exposing (TypeAnnotation)
|
||||
import List.Extra
|
||||
import NoUnused.LamderaSupport as LamderaSupport
|
||||
@ -62,7 +62,6 @@ rule =
|
||||
, fromModuleToProject = fromModuleToProject
|
||||
, foldProjectContexts = foldProjectContexts
|
||||
}
|
||||
|> Rule.withContextFromImportedModules
|
||||
|> Rule.withElmJsonProjectVisitor (\elmJson context -> ( [], elmJsonVisitor elmJson context ))
|
||||
|> Rule.withFinalProjectEvaluation finalEvaluationForProject
|
||||
|> Rule.fromProjectRuleSchema
|
||||
@ -151,7 +150,7 @@ fromProjectToModule =
|
||||
Dict.empty
|
||||
|
||||
Exposing.Explicit explicitlyExposed ->
|
||||
collectExposed moduleDocumentation explicitlyExposed ast.declarations
|
||||
collectExposedElements moduleDocumentation explicitlyExposed ast.declarations
|
||||
in
|
||||
{ lookupTable = lookupTable
|
||||
, exposed = exposed
|
||||
@ -180,9 +179,10 @@ fromModuleToProject =
|
||||
, moduleNameLocation = moduleNameRange
|
||||
}
|
||||
, used =
|
||||
moduleContext.elementsNotToReport
|
||||
|> Set.map (Tuple.pair moduleName)
|
||||
|> Set.union moduleContext.used
|
||||
Set.foldl
|
||||
(\element acc -> Set.insert ( moduleName, element ) acc)
|
||||
moduleContext.used
|
||||
moduleContext.elementsNotToReport
|
||||
, usedModules =
|
||||
if Set.member [ "Test" ] moduleContext.importedModules || moduleContext.containsMainFunction then
|
||||
Set.insert moduleName moduleContext.importedModules
|
||||
@ -213,10 +213,10 @@ fromModuleToProject =
|
||||
foldProjectContexts : ProjectContext -> ProjectContext -> ProjectContext
|
||||
foldProjectContexts newContext previousContext =
|
||||
{ projectType = previousContext.projectType
|
||||
, modules = Dict.union previousContext.modules newContext.modules
|
||||
, usedModules = Set.union previousContext.usedModules newContext.usedModules
|
||||
, modules = Dict.union newContext.modules previousContext.modules
|
||||
, usedModules = Set.union newContext.usedModules previousContext.usedModules
|
||||
, used = Set.union newContext.used previousContext.used
|
||||
, constructors = Dict.union previousContext.constructors newContext.constructors
|
||||
, constructors = Dict.union newContext.constructors previousContext.constructors
|
||||
}
|
||||
|
||||
|
||||
@ -381,12 +381,9 @@ removeReviewConfig moduleName dict =
|
||||
dict
|
||||
|
||||
|
||||
getRangesToRemove : List ( Int, String ) -> List a -> String -> Int -> Maybe Range -> Range -> Range -> List Range
|
||||
getRangesToRemove comments nodes name index maybePreviousRange range nextRange =
|
||||
if List.length nodes == 1 then
|
||||
[]
|
||||
|
||||
else
|
||||
getRangesToRemove : List ( Int, String ) -> Bool -> String -> Int -> Maybe Range -> Range -> Range -> List Range
|
||||
getRangesToRemove comments canRemoveExposed name index maybePreviousRange range nextRange =
|
||||
if canRemoveExposed then
|
||||
let
|
||||
exposeRemoval : Range
|
||||
exposeRemoval =
|
||||
@ -406,6 +403,9 @@ getRangesToRemove comments nodes name index maybePreviousRange range nextRange =
|
||||
, findMap (findDocsRangeToRemove name) comments
|
||||
]
|
||||
|
||||
else
|
||||
[]
|
||||
|
||||
|
||||
findDocsRangeToRemove : String -> ( Int, String ) -> Maybe Range
|
||||
findDocsRangeToRemove name fullComment =
|
||||
@ -518,17 +518,6 @@ collectUsedFromImport moduleName exposingList used =
|
||||
used
|
||||
|
||||
|
||||
collectExposed : Maybe (Node String) -> List (Node TopLevelExpose) -> List (Node Declaration) -> Dict String ExposedElement
|
||||
collectExposed moduleDocumentation explicitlyExposed declarations =
|
||||
let
|
||||
docsReferences : List ( Int, String )
|
||||
docsReferences =
|
||||
collectDocsReferences moduleDocumentation
|
||||
in
|
||||
collectExposedElements docsReferences explicitlyExposed declarations
|
||||
|> filterOut declarations
|
||||
|
||||
|
||||
collectDocsReferences : Maybe (Node String) -> List ( Int, String )
|
||||
collectDocsReferences maybeModuleDocumentation =
|
||||
case maybeModuleDocumentation of
|
||||
@ -556,74 +545,98 @@ collectDocsReferences maybeModuleDocumentation =
|
||||
[]
|
||||
|
||||
|
||||
collectExposedElements : List ( Int, String ) -> List (Node Exposing.TopLevelExpose) -> List (Node Declaration) -> Dict String ExposedElement
|
||||
collectExposedElements docsReferences exposingNodes declarations =
|
||||
collectExposedElements : Maybe (Node String) -> List (Node Exposing.TopLevelExpose) -> List (Node Declaration) -> Dict String ExposedElement
|
||||
collectExposedElements moduleDocumentation exposingNodes declarations =
|
||||
let
|
||||
listWithPreviousRange : List (Maybe Range)
|
||||
listWithPreviousRange =
|
||||
Nothing
|
||||
:: (exposingNodes
|
||||
|> List.take (List.length exposingNodes - 1)
|
||||
|> List.map (\(Node range _) -> Just range)
|
||||
)
|
||||
docsReferences : List ( Int, String )
|
||||
docsReferences =
|
||||
collectDocsReferences moduleDocumentation
|
||||
|
||||
listWithNextRange : List Range
|
||||
listWithNextRange =
|
||||
(exposingNodes
|
||||
|> List.map Node.range
|
||||
|> List.drop 1
|
||||
)
|
||||
++ [ { start = { row = 0, column = 0 }, end = { row = 0, column = 0 } } ]
|
||||
in
|
||||
exposingNodes
|
||||
|> List.map3 (\prev next current -> ( prev, current, next )) listWithPreviousRange listWithNextRange
|
||||
|> List.indexedMap
|
||||
(\index ( maybePreviousRange, Node range value, nextRange ) ->
|
||||
case value of
|
||||
Exposing.FunctionExpose name ->
|
||||
Just
|
||||
( name
|
||||
, { range = untilEndOfVariable name range
|
||||
, rangesToRemove = getRangesToRemove docsReferences exposingNodes name index maybePreviousRange range nextRange
|
||||
, elementType = Function
|
||||
}
|
||||
)
|
||||
|
||||
Exposing.TypeOrAliasExpose name ->
|
||||
Just
|
||||
( name
|
||||
, { range = untilEndOfVariable name range
|
||||
, rangesToRemove = getRangesToRemove docsReferences exposingNodes name index maybePreviousRange range nextRange
|
||||
, elementType = TypeOrTypeAlias
|
||||
}
|
||||
)
|
||||
|
||||
Exposing.TypeExpose { name } ->
|
||||
Just
|
||||
( name
|
||||
, { range = untilEndOfVariable name range
|
||||
, rangesToRemove = []
|
||||
, elementType = ExposedType (findConstructorsForExposedCustomType name declarations)
|
||||
}
|
||||
)
|
||||
|
||||
Exposing.InfixExpose _ ->
|
||||
Nothing
|
||||
)
|
||||
|> List.filterMap identity
|
||||
|> Dict.fromList
|
||||
|
||||
|
||||
filterOut : List (Node Declaration) -> Dict String ExposedElement -> Dict String ExposedElement
|
||||
filterOut declarations exposed =
|
||||
let
|
||||
declaredNames : Set String
|
||||
declaredNames =
|
||||
declarations
|
||||
|> List.filterMap (\(Node _ declaration) -> declarationName declaration)
|
||||
|> Set.fromList
|
||||
List.foldl
|
||||
(\(Node _ declaration) acc ->
|
||||
case declarationName declaration of
|
||||
Just name ->
|
||||
Set.insert name acc
|
||||
|
||||
Nothing ->
|
||||
acc
|
||||
)
|
||||
Set.empty
|
||||
declarations
|
||||
in
|
||||
Dict.filter (\name _ -> Set.member name declaredNames) exposed
|
||||
collectExposedElementsHelp docsReferences declarations declaredNames (List.length exposingNodes /= 1) Nothing exposingNodes 0 Dict.empty
|
||||
|
||||
|
||||
collectExposedElementsHelp : List ( Int, String ) -> List (Node Declaration) -> Set String -> Bool -> Maybe Range -> List (Node TopLevelExpose) -> Int -> Dict String ExposedElement -> Dict String ExposedElement
|
||||
collectExposedElementsHelp docsReferences declarations declaredNames canRemoveExposed maybePreviousRange exposingNodes index acc =
|
||||
case exposingNodes of
|
||||
[] ->
|
||||
acc
|
||||
|
||||
(Node range value) :: rest ->
|
||||
let
|
||||
nextRange : Range
|
||||
nextRange =
|
||||
case List.head rest of
|
||||
Just nextNode ->
|
||||
Node.range nextNode
|
||||
|
||||
Nothing ->
|
||||
Range.emptyRange
|
||||
|
||||
newAcc : Dict String ExposedElement
|
||||
newAcc =
|
||||
case value of
|
||||
Exposing.FunctionExpose name ->
|
||||
if Set.member name declaredNames then
|
||||
Dict.insert name
|
||||
{ range = untilEndOfVariable name range
|
||||
, rangesToRemove = getRangesToRemove docsReferences canRemoveExposed name index maybePreviousRange range nextRange
|
||||
, elementType = Function
|
||||
}
|
||||
acc
|
||||
|
||||
else
|
||||
acc
|
||||
|
||||
Exposing.TypeOrAliasExpose name ->
|
||||
if Set.member name declaredNames then
|
||||
Dict.insert name
|
||||
{ range = untilEndOfVariable name range
|
||||
, rangesToRemove = getRangesToRemove docsReferences canRemoveExposed name index maybePreviousRange range nextRange
|
||||
, elementType = TypeOrTypeAlias
|
||||
}
|
||||
acc
|
||||
|
||||
else
|
||||
acc
|
||||
|
||||
Exposing.TypeExpose { name } ->
|
||||
if Set.member name declaredNames then
|
||||
Dict.insert name
|
||||
{ range = untilEndOfVariable name range
|
||||
, rangesToRemove = []
|
||||
, elementType = ExposedType (findConstructorsForExposedCustomType name declarations)
|
||||
}
|
||||
acc
|
||||
|
||||
else
|
||||
acc
|
||||
|
||||
Exposing.InfixExpose _ ->
|
||||
acc
|
||||
in
|
||||
collectExposedElementsHelp
|
||||
docsReferences
|
||||
declarations
|
||||
declaredNames
|
||||
canRemoveExposed
|
||||
(Just range)
|
||||
rest
|
||||
(index + 1)
|
||||
newAcc
|
||||
|
||||
|
||||
declarationVisitor : Node Declaration -> ModuleContext -> ModuleContext
|
||||
@ -671,13 +684,13 @@ doesModuleContainMainFunction projectType declaration =
|
||||
|
||||
|
||||
isMainFunction : ElmApplicationType -> String -> Bool
|
||||
isMainFunction elmApplicationType =
|
||||
isMainFunction elmApplicationType name =
|
||||
case elmApplicationType of
|
||||
ElmApplication ->
|
||||
\name -> name == "main"
|
||||
name == "main"
|
||||
|
||||
LamderaApplication ->
|
||||
\name -> name == "main" || name == "app"
|
||||
name == "main" || name == "app"
|
||||
|
||||
|
||||
maybeSetInsert : Maybe comparable -> Set comparable -> Set comparable
|
||||
@ -779,11 +792,14 @@ typesUsedInDeclaration moduleContext declaration =
|
||||
|
||||
Declaration.CustomTypeDeclaration type_ ->
|
||||
let
|
||||
arguments : List (Node TypeAnnotation)
|
||||
arguments =
|
||||
List.concatMap (\constructor -> (Node.value constructor).arguments) type_.constructors
|
||||
typesUsedInArguments : List ( ModuleName, String )
|
||||
typesUsedInArguments =
|
||||
List.foldl
|
||||
(\constructor acc -> collectTypesFromTypeAnnotation moduleContext (Node.value constructor).arguments acc)
|
||||
[]
|
||||
type_.constructors
|
||||
in
|
||||
( collectTypesFromTypeAnnotation moduleContext arguments []
|
||||
( typesUsedInArguments
|
||||
, case Dict.get (Node.value type_.name) moduleContext.exposed |> Maybe.map .elementType of
|
||||
Just (ExposedType _) ->
|
||||
False
|
||||
|
@ -144,8 +144,8 @@ fromModuleToProject =
|
||||
|
||||
foldProjectContexts : ProjectContext -> ProjectContext -> ProjectContext
|
||||
foldProjectContexts newContext previousContext =
|
||||
{ modules = Dict.union previousContext.modules newContext.modules
|
||||
, usedModules = Set.union previousContext.usedModules newContext.usedModules
|
||||
{ modules = Dict.union newContext.modules previousContext.modules
|
||||
, usedModules = Set.union newContext.usedModules previousContext.usedModules
|
||||
, projectType = previousContext.projectType
|
||||
}
|
||||
|
||||
|
@ -120,7 +120,6 @@ type alias FunctionArgs =
|
||||
type Kind
|
||||
= Parameter
|
||||
| Alias
|
||||
| AsWithoutVariables
|
||||
| TupleWithoutVariables
|
||||
|
||||
|
||||
@ -301,18 +300,7 @@ getParametersFromAsPattern source pattern asName =
|
||||
, source = source
|
||||
}
|
||||
in
|
||||
if List.isEmpty parametersFromPatterns && isPatternWildCard pattern then
|
||||
[ asParameter
|
||||
, { name = ""
|
||||
, range = Node.range pattern
|
||||
, kind = AsWithoutVariables
|
||||
, fix = [ Fix.removeRange { start = (Node.range pattern).start, end = (Node.range asName).start } ]
|
||||
, source = source
|
||||
}
|
||||
]
|
||||
|
||||
else
|
||||
asParameter :: parametersFromPatterns
|
||||
asParameter :: parametersFromPatterns
|
||||
|
||||
|
||||
isPatternWildCard : Node Pattern -> Bool
|
||||
@ -458,30 +446,39 @@ registerFunctionCall fnName numberOfIgnoredArguments arguments context =
|
||||
let
|
||||
locationsToIgnore : LocationsToIgnore
|
||||
locationsToIgnore =
|
||||
arguments
|
||||
|> List.indexedMap Tuple.pair
|
||||
|> List.filterMap
|
||||
(\( index, arg ) ->
|
||||
Dict.get (numberOfIgnoredArguments + index) fnArgs
|
||||
|> Maybe.map (\argName -> ( argName, [ Node.range arg ] ))
|
||||
)
|
||||
|> Dict.fromList
|
||||
ignoreLocations fnArgs numberOfIgnoredArguments arguments 0 context.locationsToIgnoreForUsed
|
||||
in
|
||||
{ context
|
||||
| locationsToIgnoreForUsed =
|
||||
Dict.merge
|
||||
Dict.insert
|
||||
(\key new old -> Dict.insert key (new ++ old))
|
||||
Dict.insert
|
||||
locationsToIgnore
|
||||
context.locationsToIgnoreForUsed
|
||||
Dict.empty
|
||||
}
|
||||
{ context | locationsToIgnoreForUsed = locationsToIgnore }
|
||||
|
||||
Nothing ->
|
||||
context
|
||||
|
||||
|
||||
ignoreLocations : FunctionArgs -> Int -> List (Node a) -> Int -> LocationsToIgnore -> LocationsToIgnore
|
||||
ignoreLocations fnArgs numberOfIgnoredArguments nodes index acc =
|
||||
case nodes of
|
||||
[] ->
|
||||
acc
|
||||
|
||||
(Node range _) :: rest ->
|
||||
let
|
||||
newAcc : LocationsToIgnore
|
||||
newAcc =
|
||||
case Dict.get (numberOfIgnoredArguments + index) fnArgs of
|
||||
Just argName ->
|
||||
case Dict.get argName acc of
|
||||
Just existingLocations ->
|
||||
Dict.insert argName (range :: existingLocations) acc
|
||||
|
||||
Nothing ->
|
||||
Dict.insert argName [ range ] acc
|
||||
|
||||
Nothing ->
|
||||
acc
|
||||
in
|
||||
ignoreLocations fnArgs numberOfIgnoredArguments rest (index + 1) newAcc
|
||||
|
||||
|
||||
markValueAsUsed : Range -> String -> Context -> Context
|
||||
markValueAsUsed range name context =
|
||||
case context.scopes of
|
||||
@ -589,11 +586,6 @@ errorMessage kind name =
|
||||
, details = [ "You should either use this parameter somewhere, or remove it at the location I pointed at." ]
|
||||
}
|
||||
|
||||
AsWithoutVariables ->
|
||||
{ message = "Pattern does not introduce any variables"
|
||||
, details = [ "You should remove this pattern." ]
|
||||
}
|
||||
|
||||
TupleWithoutVariables ->
|
||||
{ message = "Tuple pattern is not needed"
|
||||
, details = [ "You should remove this pattern." ]
|
||||
|
@ -252,24 +252,15 @@ foo =
|
||||
bish
|
||||
"""
|
||||
]
|
||||
, test "should report unused patterns that are aliased" <|
|
||||
, test "should not report aliases to wildcards" <|
|
||||
-- Already handled by `NoUnused.Patterns`
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
foo =
|
||||
\\(_ as bar) -> bar
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Pattern does not introduce any variables"
|
||||
, details = [ "You should remove this pattern." ]
|
||||
, under = "_"
|
||||
}
|
||||
|> Review.Test.whenFixed """module A exposing (..)
|
||||
foo =
|
||||
\\(bar) -> bar
|
||||
"""
|
||||
]
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should report nested unused pattern aliases" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
@ -547,20 +538,15 @@ foo ({ bish, bash } as bosh) =
|
||||
, under = "bosh"
|
||||
}
|
||||
]
|
||||
, test "should report unused patterns that are aliased" <|
|
||||
, test "should not report aliases to wildcards" <|
|
||||
-- Already handled by `NoUnused.Patterns`
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
foo (_ as bar) =
|
||||
bar
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Pattern does not introduce any variables"
|
||||
, details = [ "You should remove this pattern." ]
|
||||
, under = "_"
|
||||
}
|
||||
]
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should report nested unused pattern aliases" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
|
@ -6,6 +6,7 @@ module NoUnused.Patterns exposing (rule)
|
||||
|
||||
-}
|
||||
|
||||
import Elm.Syntax.Declaration as Declaration exposing (Declaration)
|
||||
import Elm.Syntax.Expression as Expression exposing (Expression)
|
||||
import Elm.Syntax.ModuleName exposing (ModuleName)
|
||||
import Elm.Syntax.Node as Node exposing (Node(..))
|
||||
@ -63,6 +64,7 @@ elm-review --template jfmengels/elm-review-unused/example --rules NoUnused.Patte
|
||||
rule : Rule
|
||||
rule =
|
||||
Rule.newModuleRuleSchema "NoUnused.Patterns" initialContext
|
||||
|> Rule.withDeclarationEnterVisitor declarationEnterVisitor
|
||||
|> Rule.withExpressionEnterVisitor expressionEnterVisitor
|
||||
|> Rule.withExpressionExitVisitor expressionExitVisitor
|
||||
|> Rule.withCaseBranchEnterVisitor caseBranchEnterVisitor
|
||||
@ -105,7 +107,19 @@ initialContext =
|
||||
-- EXPRESSION ENTER VISITOR
|
||||
|
||||
|
||||
expressionEnterVisitor : Node Expression -> Context -> ( List nothing, Context )
|
||||
declarationEnterVisitor : Node Declaration -> Context -> ( List (Rule.Error {}), Context )
|
||||
declarationEnterVisitor node context =
|
||||
case Node.value node of
|
||||
Declaration.FunctionDeclaration { declaration } ->
|
||||
( findAsPatternsErrors (Node.value declaration).arguments []
|
||||
, context
|
||||
)
|
||||
|
||||
_ ->
|
||||
( [], context )
|
||||
|
||||
|
||||
expressionEnterVisitor : Node Expression -> Context -> ( List (Rule.Error {}), Context )
|
||||
expressionEnterVisitor node context =
|
||||
case Node.value node of
|
||||
Expression.LetExpression { declarations } ->
|
||||
@ -118,14 +132,26 @@ expressionEnterVisitor node context =
|
||||
|
||||
Expression.LetDestructuring pattern _ ->
|
||||
findPatterns Destructuring pattern
|
||||
|
||||
asPatternsErrors : Node Expression.LetDeclaration -> List (Rule.Error {})
|
||||
asPatternsErrors letDeclaration =
|
||||
case Node.value letDeclaration of
|
||||
Expression.LetFunction { declaration } ->
|
||||
findAsPatternsErrors (Node.value declaration).arguments []
|
||||
|
||||
Expression.LetDestructuring _ _ ->
|
||||
[]
|
||||
in
|
||||
( []
|
||||
( List.concatMap asPatternsErrors declarations
|
||||
, { declared = List.concatMap findPatternsInLetDeclaration declarations
|
||||
, used = Set.empty
|
||||
}
|
||||
:: context
|
||||
)
|
||||
|
||||
Expression.LambdaExpression { args } ->
|
||||
( findAsPatternsErrors args [], context )
|
||||
|
||||
_ ->
|
||||
( [], context )
|
||||
|
||||
@ -246,8 +272,7 @@ recordErrors context { fields, recordRange } =
|
||||
_ ->
|
||||
( Range.combine (List.map Node.range unused)
|
||||
, Node Range.emptyRange (Pattern.RecordPattern used)
|
||||
|> Writer.writePattern
|
||||
|> Writer.write
|
||||
|> writePattern
|
||||
|> Fix.replaceRangeBy recordRange
|
||||
)
|
||||
in
|
||||
@ -299,79 +324,109 @@ valueVisitor (Node _ ( moduleName, value )) context =
|
||||
|
||||
|
||||
findPatterns : PatternUse -> Node Pattern -> List FoundPattern
|
||||
findPatterns use (Node range pattern) =
|
||||
case pattern of
|
||||
Pattern.VarPattern name ->
|
||||
[ SingleValue
|
||||
{ name = name
|
||||
, message = "Value `" ++ name ++ "` is not used."
|
||||
, details = singularReplaceDetails
|
||||
, range = range
|
||||
, fix = [ Fix.replaceRangeBy range "_" ]
|
||||
}
|
||||
]
|
||||
findPatterns use pattern =
|
||||
findPatternsHelp use [ pattern ] []
|
||||
|
||||
Pattern.TuplePattern [ Node _ Pattern.AllPattern, Node _ Pattern.AllPattern ] ->
|
||||
[ SimplifiablePattern
|
||||
(Rule.errorWithFix
|
||||
{ message = "Tuple pattern is not needed"
|
||||
, details = redundantDetails
|
||||
}
|
||||
range
|
||||
[ Fix.replaceRangeBy range "_" ]
|
||||
)
|
||||
]
|
||||
|
||||
Pattern.TuplePattern [ Node _ Pattern.AllPattern, Node _ Pattern.AllPattern, Node _ Pattern.AllPattern ] ->
|
||||
[ SimplifiablePattern
|
||||
(Rule.errorWithFix
|
||||
{ message = "Tuple pattern is not needed"
|
||||
, details = redundantDetails
|
||||
}
|
||||
range
|
||||
[ Fix.replaceRangeBy range "_" ]
|
||||
)
|
||||
]
|
||||
findPatternsHelp : PatternUse -> List (Node Pattern) -> List FoundPattern -> List FoundPattern
|
||||
findPatternsHelp use patterns acc =
|
||||
case patterns of
|
||||
[] ->
|
||||
acc
|
||||
|
||||
Pattern.TuplePattern patterns ->
|
||||
List.concatMap (findPatterns use) patterns
|
||||
(Node range pattern) :: rest ->
|
||||
case pattern of
|
||||
Pattern.VarPattern name ->
|
||||
let
|
||||
foundPattern : FoundPattern
|
||||
foundPattern =
|
||||
SingleValue
|
||||
{ name = name
|
||||
, message = "Value `" ++ name ++ "` is not used"
|
||||
, details = singularReplaceDetails
|
||||
, range = range
|
||||
, fix = [ Fix.replaceRangeBy range "_" ]
|
||||
}
|
||||
in
|
||||
findPatternsHelp use rest (foundPattern :: acc)
|
||||
|
||||
Pattern.RecordPattern fields ->
|
||||
[ RecordPattern
|
||||
{ fields = fields
|
||||
, recordRange = range
|
||||
}
|
||||
]
|
||||
Pattern.TuplePattern [ Node _ Pattern.AllPattern, Node _ Pattern.AllPattern ] ->
|
||||
let
|
||||
foundPattern : FoundPattern
|
||||
foundPattern =
|
||||
SimplifiablePattern
|
||||
(Rule.errorWithFix
|
||||
{ message = "Tuple pattern is not needed"
|
||||
, details = redundantDetails
|
||||
}
|
||||
range
|
||||
[ Fix.replaceRangeBy range "_" ]
|
||||
)
|
||||
in
|
||||
findPatternsHelp use rest (foundPattern :: acc)
|
||||
|
||||
Pattern.UnConsPattern first second ->
|
||||
findPatterns use first ++ findPatterns use second
|
||||
Pattern.TuplePattern [ Node _ Pattern.AllPattern, Node _ Pattern.AllPattern, Node _ Pattern.AllPattern ] ->
|
||||
let
|
||||
foundPattern : FoundPattern
|
||||
foundPattern =
|
||||
SimplifiablePattern
|
||||
(Rule.errorWithFix
|
||||
{ message = "Tuple pattern is not needed"
|
||||
, details = redundantDetails
|
||||
}
|
||||
range
|
||||
[ Fix.replaceRangeBy range "_" ]
|
||||
)
|
||||
in
|
||||
findPatternsHelp use rest (foundPattern :: acc)
|
||||
|
||||
Pattern.ListPattern patterns ->
|
||||
List.concatMap (findPatterns use) patterns
|
||||
Pattern.TuplePattern subPatterns ->
|
||||
findPatternsHelp use (subPatterns ++ rest) acc
|
||||
|
||||
Pattern.NamedPattern _ patterns ->
|
||||
if use == Destructuring && List.all isAllPattern patterns then
|
||||
[ SimplifiablePattern
|
||||
(Rule.errorWithFix
|
||||
{ message = "Named pattern is not needed"
|
||||
, details = redundantDetails
|
||||
}
|
||||
range
|
||||
[ Fix.replaceRangeBy range "_" ]
|
||||
)
|
||||
]
|
||||
Pattern.RecordPattern fields ->
|
||||
let
|
||||
foundPattern : FoundPattern
|
||||
foundPattern =
|
||||
RecordPattern
|
||||
{ fields = fields
|
||||
, recordRange = range
|
||||
}
|
||||
in
|
||||
findPatternsHelp use rest (foundPattern :: acc)
|
||||
|
||||
else
|
||||
List.concatMap (findPatterns use) patterns
|
||||
Pattern.UnConsPattern first second ->
|
||||
findPatternsHelp use (first :: second :: rest) acc
|
||||
|
||||
Pattern.AsPattern inner name ->
|
||||
findPatternForAsPattern range inner name :: findPatterns use inner
|
||||
Pattern.ListPattern subPatterns ->
|
||||
findPatternsHelp use (subPatterns ++ rest) acc
|
||||
|
||||
Pattern.ParenthesizedPattern inner ->
|
||||
findPatterns use inner
|
||||
Pattern.NamedPattern _ subPatterns ->
|
||||
if use == Destructuring && List.all isAllPattern subPatterns then
|
||||
let
|
||||
foundPattern : FoundPattern
|
||||
foundPattern =
|
||||
SimplifiablePattern
|
||||
(Rule.errorWithFix
|
||||
{ message = "Named pattern is not needed"
|
||||
, details = redundantDetails
|
||||
}
|
||||
range
|
||||
[ Fix.replaceRangeBy range "_" ]
|
||||
)
|
||||
in
|
||||
findPatternsHelp use rest (foundPattern :: acc)
|
||||
|
||||
_ ->
|
||||
[]
|
||||
else
|
||||
findPatternsHelp use (subPatterns ++ rest) acc
|
||||
|
||||
Pattern.AsPattern inner name ->
|
||||
findPatternsHelp use (inner :: rest) (findPatternForAsPattern range inner name :: acc)
|
||||
|
||||
Pattern.ParenthesizedPattern inner ->
|
||||
findPatternsHelp use (inner :: rest) acc
|
||||
|
||||
_ ->
|
||||
findPatternsHelp use rest acc
|
||||
|
||||
|
||||
|
||||
@ -526,8 +581,7 @@ errorsForRecordValueList recordRange list context =
|
||||
_ ->
|
||||
( Range.combine (List.map Node.range unused)
|
||||
, Node Range.emptyRange (Pattern.RecordPattern used)
|
||||
|> Writer.writePattern
|
||||
|> Writer.write
|
||||
|> writePattern
|
||||
|> Fix.replaceRangeBy recordRange
|
||||
)
|
||||
in
|
||||
@ -546,10 +600,10 @@ listToMessage : String -> List String -> String
|
||||
listToMessage first rest =
|
||||
case List.reverse rest of
|
||||
[] ->
|
||||
"Value `" ++ first ++ "` is not used."
|
||||
"Value `" ++ first ++ "` is not used"
|
||||
|
||||
last :: middle ->
|
||||
"Values `" ++ String.join "`, `" (first :: middle) ++ "` and `" ++ last ++ "` are not used."
|
||||
"Values `" ++ String.join "`, `" (first :: middle) ++ "` and `" ++ last ++ "` are not used"
|
||||
|
||||
|
||||
listToDetails : String -> List String -> List String
|
||||
@ -569,13 +623,12 @@ errorsForAsPattern patternRange inner (Node range name) context =
|
||||
fix : List Fix
|
||||
fix =
|
||||
[ inner
|
||||
|> Writer.writePattern
|
||||
|> Writer.write
|
||||
|> writePattern
|
||||
|> Fix.replaceRangeBy patternRange
|
||||
]
|
||||
in
|
||||
( [ Rule.errorWithFix
|
||||
{ message = "Pattern alias `" ++ name ++ "` is not used."
|
||||
{ message = "Pattern alias `" ++ name ++ "` is not used"
|
||||
, details = singularRemoveDetails
|
||||
}
|
||||
range
|
||||
@ -599,35 +652,99 @@ errorsForAsPattern patternRange inner (Node range name) context =
|
||||
( [], context )
|
||||
|
||||
|
||||
findPatternForAsPattern : Range -> Node Pattern -> Node String -> FoundPattern
|
||||
findPatternForAsPattern patternRange inner (Node range name) =
|
||||
if isAllPattern inner then
|
||||
SimplifiablePattern
|
||||
(Rule.errorWithFix
|
||||
{ message = "Pattern `_` is not needed"
|
||||
, details = removeDetails
|
||||
}
|
||||
(Node.range inner)
|
||||
[ Fix.replaceRangeBy patternRange name ]
|
||||
)
|
||||
findAsPatternsErrors : List (Node Pattern) -> List (Rule.Error {}) -> List (Rule.Error {})
|
||||
findAsPatternsErrors patterns acc =
|
||||
case patterns of
|
||||
[] ->
|
||||
acc
|
||||
|
||||
else
|
||||
let
|
||||
fix : List Fix
|
||||
fix =
|
||||
[ inner
|
||||
|> Writer.writePattern
|
||||
|> Writer.write
|
||||
|> Fix.replaceRangeBy patternRange
|
||||
]
|
||||
in
|
||||
SingleValue
|
||||
{ name = name
|
||||
, message = "Pattern alias `" ++ name ++ "` is not used."
|
||||
, details = singularRemoveDetails
|
||||
, range = range
|
||||
, fix = fix
|
||||
}
|
||||
pattern :: rest ->
|
||||
case Node.value pattern of
|
||||
Pattern.AsPattern inner name ->
|
||||
let
|
||||
newAcc : List (Rule.Error {})
|
||||
newAcc =
|
||||
case findPatternForAsPattern (Node.range pattern) inner name of
|
||||
SimplifiablePattern error ->
|
||||
error :: acc
|
||||
|
||||
SingleValue _ ->
|
||||
acc
|
||||
|
||||
RecordPattern _ ->
|
||||
acc
|
||||
in
|
||||
findAsPatternsErrors (inner :: rest) newAcc
|
||||
|
||||
Pattern.TuplePattern subPatterns ->
|
||||
findAsPatternsErrors (subPatterns ++ rest) acc
|
||||
|
||||
Pattern.UnConsPattern first second ->
|
||||
findAsPatternsErrors (first :: second :: rest) acc
|
||||
|
||||
Pattern.ListPattern subPatterns ->
|
||||
findAsPatternsErrors (subPatterns ++ rest) acc
|
||||
|
||||
Pattern.NamedPattern _ subPatterns ->
|
||||
findAsPatternsErrors (subPatterns ++ rest) acc
|
||||
|
||||
Pattern.ParenthesizedPattern inner ->
|
||||
findAsPatternsErrors (inner :: rest) acc
|
||||
|
||||
_ ->
|
||||
findAsPatternsErrors rest acc
|
||||
|
||||
|
||||
findPatternForAsPattern : Range -> Node Pattern -> Node String -> FoundPattern
|
||||
findPatternForAsPattern patternRange pattern ((Node range name) as nameNode) =
|
||||
case Node.value pattern of
|
||||
Pattern.ParenthesizedPattern subPattern ->
|
||||
findPatternForAsPattern patternRange subPattern nameNode
|
||||
|
||||
Pattern.AllPattern ->
|
||||
SimplifiablePattern
|
||||
(Rule.errorWithFix
|
||||
{ message = "Pattern `_` is not needed"
|
||||
, details = removeDetails
|
||||
}
|
||||
(Node.range pattern)
|
||||
[ Fix.replaceRangeBy patternRange name ]
|
||||
)
|
||||
|
||||
Pattern.VarPattern innerName ->
|
||||
SimplifiablePattern
|
||||
(Rule.error
|
||||
{ message = "Unnecessary duplicate alias `" ++ name ++ "`"
|
||||
, details = [ "This alias is redundant because the value is already named `" ++ innerName ++ "`. I suggest you remove one of them." ]
|
||||
}
|
||||
range
|
||||
)
|
||||
|
||||
Pattern.AsPattern _ (Node innerRange innerName) ->
|
||||
SimplifiablePattern
|
||||
(Rule.error
|
||||
{ message = "Unnecessary duplicate alias `" ++ innerName ++ "`"
|
||||
, details = [ "This name is redundant because the value is already aliased as `" ++ name ++ "`. I suggest you remove one of them." ]
|
||||
}
|
||||
innerRange
|
||||
)
|
||||
|
||||
_ ->
|
||||
let
|
||||
fix : List Fix
|
||||
fix =
|
||||
[ pattern
|
||||
|> writePattern
|
||||
|> Fix.replaceRangeBy patternRange
|
||||
]
|
||||
in
|
||||
SingleValue
|
||||
{ name = name
|
||||
, message = "Pattern alias `" ++ name ++ "` is not used"
|
||||
, details = singularRemoveDetails
|
||||
, range = range
|
||||
, fix = fix
|
||||
}
|
||||
|
||||
|
||||
isAllPattern : Node Pattern -> Bool
|
||||
@ -685,3 +802,59 @@ isUnused name context =
|
||||
|
||||
headScope :: _ ->
|
||||
not <| Set.member name headScope.used
|
||||
|
||||
|
||||
{-| Write a pattern.
|
||||
-}
|
||||
writePattern : Node Pattern -> String
|
||||
writePattern pattern =
|
||||
case Node.value pattern of
|
||||
Pattern.AllPattern ->
|
||||
"_"
|
||||
|
||||
Pattern.UnitPattern ->
|
||||
"()"
|
||||
|
||||
Pattern.CharPattern c ->
|
||||
"'" ++ String.fromChar c ++ "'"
|
||||
|
||||
Pattern.StringPattern s ->
|
||||
"\"" ++ String.replace "\"" "\\\"" s ++ "\""
|
||||
|
||||
Pattern.HexPattern _ ->
|
||||
pattern
|
||||
|> Writer.writePattern
|
||||
|> Writer.write
|
||||
|
||||
Pattern.IntPattern i ->
|
||||
String.fromInt i
|
||||
|
||||
Pattern.FloatPattern f ->
|
||||
String.fromFloat f
|
||||
|
||||
Pattern.TuplePattern inner ->
|
||||
"( " ++ String.join ", " (List.map writePattern inner) ++ " )"
|
||||
|
||||
Pattern.RecordPattern inner ->
|
||||
"{ " ++ String.join ", " (List.map Node.value inner) ++ " }"
|
||||
|
||||
Pattern.UnConsPattern left right ->
|
||||
writePattern left ++ " :: " ++ writePattern right
|
||||
|
||||
Pattern.ListPattern inner ->
|
||||
"[ " ++ String.join ", " (List.map writePattern inner) ++ " ]"
|
||||
|
||||
Pattern.VarPattern var ->
|
||||
var
|
||||
|
||||
Pattern.NamedPattern qnr others ->
|
||||
String.join " "
|
||||
(String.join "." (qnr.moduleName ++ [ qnr.name ])
|
||||
:: List.map writePattern others
|
||||
)
|
||||
|
||||
Pattern.AsPattern innerPattern asName ->
|
||||
writePattern innerPattern ++ " as " ++ Node.value asName
|
||||
|
||||
Pattern.ParenthesizedPattern innerPattern ->
|
||||
"(" ++ writePattern innerPattern ++ ")"
|
||||
|
@ -35,12 +35,13 @@ all =
|
||||
, describe "in Let Functions" letFunctionTests
|
||||
|
||||
--- patterns
|
||||
, describe "with as pattern" asPatternTests
|
||||
, describe "with list pattern" listPatternTests
|
||||
, describe "with named pattern" namedPatternTests
|
||||
, describe "with record pattern" recordPatternTests
|
||||
, describe "with tuple pattern" tuplePatternTests
|
||||
, describe "with uncons pattern" unconsPatternTests
|
||||
, describe "with as pattern" asPatternTests
|
||||
, describe "with as pattern in parameters" asPatternInParametersTests
|
||||
]
|
||||
|
||||
|
||||
@ -60,7 +61,7 @@ foo =
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Value `bish` is not used."
|
||||
{ message = "Value `bish` is not used"
|
||||
, details = useOrReplaceDetails
|
||||
, under = "bish"
|
||||
}
|
||||
@ -75,7 +76,7 @@ foo =
|
||||
Nothing
|
||||
"""
|
||||
, Review.Test.error
|
||||
{ message = "Value `bash` is not used."
|
||||
{ message = "Value `bash` is not used"
|
||||
, details = useOrReplaceDetails
|
||||
, under = "bash"
|
||||
}
|
||||
@ -104,7 +105,7 @@ foo =
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Value `one` is not used."
|
||||
{ message = "Value `one` is not used"
|
||||
, details = useOrReplaceDetails
|
||||
, under = "one"
|
||||
}
|
||||
@ -119,7 +120,7 @@ foo =
|
||||
more -> 3
|
||||
"""
|
||||
, Review.Test.error
|
||||
{ message = "Value `first` is not used."
|
||||
{ message = "Value `first` is not used"
|
||||
, details = useOrReplaceDetails
|
||||
, under = "first"
|
||||
}
|
||||
@ -134,7 +135,7 @@ foo =
|
||||
more -> 3
|
||||
"""
|
||||
, Review.Test.error
|
||||
{ message = "Value `two` is not used."
|
||||
{ message = "Value `two` is not used"
|
||||
, details = useOrReplaceDetails
|
||||
, under = "two"
|
||||
}
|
||||
@ -149,7 +150,7 @@ foo =
|
||||
more -> 3
|
||||
"""
|
||||
, Review.Test.error
|
||||
{ message = "Value `more` is not used."
|
||||
{ message = "Value `more` is not used"
|
||||
, details = useOrReplaceDetails
|
||||
, under = "more"
|
||||
}
|
||||
@ -178,7 +179,7 @@ foo =
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Value `one` is not used."
|
||||
{ message = "Value `one` is not used"
|
||||
, details = useOrReplaceDetails
|
||||
, under = "one"
|
||||
}
|
||||
@ -193,7 +194,7 @@ foo =
|
||||
_ :: _ :: more -> 3
|
||||
"""
|
||||
, Review.Test.error
|
||||
{ message = "Value `first` is not used."
|
||||
{ message = "Value `first` is not used"
|
||||
, details = useOrReplaceDetails
|
||||
, under = "first"
|
||||
}
|
||||
@ -208,7 +209,7 @@ foo =
|
||||
_ :: _ :: more -> 3
|
||||
"""
|
||||
, Review.Test.error
|
||||
{ message = "Value `two` is not used."
|
||||
{ message = "Value `two` is not used"
|
||||
, details = useOrReplaceDetails
|
||||
, under = "two"
|
||||
}
|
||||
@ -223,7 +224,7 @@ foo =
|
||||
_ :: _ :: more -> 3
|
||||
"""
|
||||
, Review.Test.error
|
||||
{ message = "Value `more` is not used."
|
||||
{ message = "Value `more` is not used"
|
||||
, details = useOrReplaceDetails
|
||||
, under = "more"
|
||||
}
|
||||
@ -252,7 +253,7 @@ foo =
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Value `foo` is not used."
|
||||
{ message = "Value `foo` is not used"
|
||||
, details = useOrReplaceDetails
|
||||
, under = "foo"
|
||||
}
|
||||
@ -284,7 +285,7 @@ bar =
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Value `bish` is not used."
|
||||
{ message = "Value `bish` is not used"
|
||||
, details = useOrReplaceDetails
|
||||
, under = "bish"
|
||||
}
|
||||
@ -317,7 +318,7 @@ foo =
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Value `data` is not used."
|
||||
{ message = "Value `data` is not used"
|
||||
, details = useOrReplaceDetails
|
||||
, under = "data"
|
||||
}
|
||||
@ -381,7 +382,7 @@ foo =
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Value `right` is not used."
|
||||
{ message = "Value `right` is not used"
|
||||
, details = useOrReplaceDetails
|
||||
, under = "right"
|
||||
}
|
||||
@ -445,7 +446,7 @@ foo =
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Value `right` is not used."
|
||||
{ message = "Value `right` is not used"
|
||||
, details = useOrReplaceDetails
|
||||
, under = "right"
|
||||
}
|
||||
@ -523,7 +524,7 @@ foo =
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Pattern alias `bosh` is not used."
|
||||
{ message = "Pattern alias `bosh` is not used"
|
||||
, details = useOrRemoveDetails
|
||||
, under = "bosh"
|
||||
}
|
||||
@ -532,7 +533,7 @@ foo =
|
||||
module A exposing (..)
|
||||
foo =
|
||||
case bar of
|
||||
({bish, bash}) ->
|
||||
({ bish, bash }) ->
|
||||
( bish, bash )
|
||||
"""
|
||||
]
|
||||
@ -548,7 +549,7 @@ foo =
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Value `bash` is not used."
|
||||
{ message = "Value `bash` is not used"
|
||||
, details = useOrRemoveDetails
|
||||
, under = "bash"
|
||||
}
|
||||
@ -557,7 +558,7 @@ foo =
|
||||
module A exposing (..)
|
||||
foo =
|
||||
case bar of
|
||||
({bish} as bosh) ->
|
||||
({ bish } as bosh) ->
|
||||
( bish, bosh )
|
||||
"""
|
||||
]
|
||||
@ -573,7 +574,7 @@ foo =
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Value `bash` is not used."
|
||||
{ message = "Value `bash` is not used"
|
||||
, details = useOrRemoveDetails
|
||||
, under = "bash"
|
||||
}
|
||||
@ -582,11 +583,11 @@ foo =
|
||||
module A exposing (..)
|
||||
foo =
|
||||
case bar of
|
||||
({bish} as bosh) ->
|
||||
({ bish } as bosh) ->
|
||||
bish
|
||||
"""
|
||||
, Review.Test.error
|
||||
{ message = "Pattern alias `bosh` is not used."
|
||||
{ message = "Pattern alias `bosh` is not used"
|
||||
, details = useOrRemoveDetails
|
||||
, under = "bosh"
|
||||
}
|
||||
@ -595,7 +596,7 @@ foo =
|
||||
module A exposing (..)
|
||||
foo =
|
||||
case bar of
|
||||
({bish, bash}) ->
|
||||
({ bish, bash }) ->
|
||||
bish
|
||||
"""
|
||||
]
|
||||
@ -636,7 +637,7 @@ foo =
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Pattern alias `bish` is not used."
|
||||
{ message = "Pattern alias `bish` is not used"
|
||||
, details = useOrRemoveDetails
|
||||
, under = "bish"
|
||||
}
|
||||
@ -645,10 +646,135 @@ foo =
|
||||
module A exposing (..)
|
||||
foo =
|
||||
case maybeTupleMaybe of
|
||||
Just ( _, ( Just _ ) ) ->
|
||||
Just ( _, Just _ ) ->
|
||||
bash
|
||||
_ ->
|
||||
bosh
|
||||
"""
|
||||
]
|
||||
, test "should report aliases to a name" <|
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
foo =
|
||||
case maybeTupleMaybe of
|
||||
Just ( bosh as bish ) ->
|
||||
bosh + bish
|
||||
_ ->
|
||||
0
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Unnecessary duplicate alias `bish`"
|
||||
, details = [ "This alias is redundant because the value is already named `bosh`. I suggest you remove one of them." ]
|
||||
, under = "bish"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 5, column = 24 }, end = { row = 5, column = 28 } }
|
||||
]
|
||||
, test "should report duplicate aliases" <|
|
||||
\() ->
|
||||
"""
|
||||
module A exposing (..)
|
||||
foo =
|
||||
case maybeTupleMaybe of
|
||||
Just ( ((Foo bash) as bosh) as bish ) ->
|
||||
bash + bosh + bish
|
||||
_ ->
|
||||
0
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Unnecessary duplicate alias `bosh`"
|
||||
, details = [ "This name is redundant because the value is already aliased as `bish`. I suggest you remove one of them." ]
|
||||
, under = "bosh"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 5, column = 31 }, end = { row = 5, column = 35 } }
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
asPatternInParametersTests : List Test
|
||||
asPatternInParametersTests =
|
||||
[ test "should report aliases to a name (top-level declaration)" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
foo (bosh as bash) =
|
||||
bosh + bash
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Unnecessary duplicate alias `bash`"
|
||||
, details = [ "This alias is redundant because the value is already named `bosh`. I suggest you remove one of them." ]
|
||||
, under = "bash"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 2, column = 14 }, end = { row = 2, column = 18 } }
|
||||
]
|
||||
, test "should report aliases to a name (lambda)" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
foo =
|
||||
(\\(bosh as bash) -> bosh + bash)
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Unnecessary duplicate alias `bash`"
|
||||
, details = [ "This alias is redundant because the value is already named `bosh`. I suggest you remove one of them." ]
|
||||
, under = "bash"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 16 }, end = { row = 3, column = 20 } }
|
||||
]
|
||||
, test "should report aliases to a name (let function)" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
foo =
|
||||
let bar (bosh as bash) = bosh + bash
|
||||
in
|
||||
bar
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Unnecessary duplicate alias `bash`"
|
||||
, details = [ "This alias is redundant because the value is already named `bosh`. I suggest you remove one of them." ]
|
||||
, under = "bash"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 22 }, end = { row = 3, column = 26 } }
|
||||
]
|
||||
, test "should report duplicate aliases (top-level declaration)" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
foo (((Foo bash) as bosh) as bish) =
|
||||
bash + bosh + bish
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Unnecessary duplicate alias `bosh`"
|
||||
, details = [ "This name is redundant because the value is already aliased as `bish`. I suggest you remove one of them." ]
|
||||
, under = "bosh"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 2, column = 21 }, end = { row = 2, column = 25 } }
|
||||
]
|
||||
, test "should report aliasing of _ in arguments" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
foo (_ as x) =
|
||||
x
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Pattern `_` is not needed"
|
||||
, details = [ "This pattern is redundant and should be removed." ]
|
||||
, under = "_"
|
||||
}
|
||||
|> Review.Test.whenFixed """module A exposing (..)
|
||||
foo (x) =
|
||||
x
|
||||
"""
|
||||
]
|
||||
]
|
||||
@ -668,7 +794,7 @@ foo =
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Value `first` is not used."
|
||||
{ message = "Value `first` is not used"
|
||||
, details = useOrReplaceDetails
|
||||
, under = "first"
|
||||
}
|
||||
@ -681,7 +807,7 @@ foo =
|
||||
another
|
||||
"""
|
||||
, Review.Test.error
|
||||
{ message = "Value `second` is not used."
|
||||
{ message = "Value `second` is not used"
|
||||
, details = useOrReplaceDetails
|
||||
, under = "second"
|
||||
}
|
||||
@ -713,7 +839,7 @@ foo =
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Value `bish` is not used."
|
||||
{ message = "Value `bish` is not used"
|
||||
, details = useOrReplaceDetails
|
||||
, under = "bish"
|
||||
}
|
||||
@ -742,7 +868,7 @@ foo =
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Value `bish` is not used."
|
||||
{ message = "Value `bish` is not used"
|
||||
, details = useOrReplaceDetails
|
||||
, under = "bish"
|
||||
}
|
||||
@ -876,7 +1002,7 @@ foo =
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Values `bish` and `bash` are not used."
|
||||
{ message = "Values `bish` and `bash` are not used"
|
||||
, details = [ "You should either use these values somewhere or remove them." ]
|
||||
, under = "{ bish, bash }"
|
||||
}
|
||||
@ -905,7 +1031,7 @@ foo =
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Values `bish` and `bosh` are not used."
|
||||
{ message = "Values `bish` and `bosh` are not used"
|
||||
, details = [ "You should either use these values somewhere or remove them." ]
|
||||
, under = "bish, bash, bosh"
|
||||
}
|
||||
@ -914,7 +1040,7 @@ foo =
|
||||
module A exposing (..)
|
||||
foo =
|
||||
let
|
||||
{bash} =
|
||||
{ bash } =
|
||||
bar
|
||||
in
|
||||
bash
|
||||
@ -963,7 +1089,7 @@ foo =
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Values `bash` and `bosh` are not used."
|
||||
{ message = "Values `bash` and `bosh` are not used"
|
||||
, details = [ "You should either use these values somewhere or remove them." ]
|
||||
, under = "bash, bosh"
|
||||
}
|
||||
@ -972,7 +1098,7 @@ foo =
|
||||
module A exposing (..)
|
||||
foo =
|
||||
let
|
||||
{bish} =
|
||||
{ bish } =
|
||||
bar
|
||||
in
|
||||
bish
|
||||
@ -997,7 +1123,7 @@ foo =
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Value `bish` is not used."
|
||||
{ message = "Value `bish` is not used"
|
||||
, details = useOrReplaceDetails
|
||||
, under = "bish"
|
||||
}
|
||||
@ -1012,7 +1138,7 @@ foo =
|
||||
bash
|
||||
"""
|
||||
, Review.Test.error
|
||||
{ message = "Value `bosh` is not used."
|
||||
{ message = "Value `bosh` is not used"
|
||||
, details = useOrReplaceDetails
|
||||
, under = "bosh"
|
||||
}
|
||||
@ -1113,7 +1239,7 @@ foo =
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Value `first` is not used."
|
||||
{ message = "Value `first` is not used"
|
||||
, details = useOrReplaceDetails
|
||||
, under = "first"
|
||||
}
|
||||
@ -1126,7 +1252,7 @@ foo =
|
||||
list
|
||||
"""
|
||||
, Review.Test.error
|
||||
{ message = "Value `rest` is not used."
|
||||
{ message = "Value `rest` is not used"
|
||||
, details = useOrReplaceDetails
|
||||
, under = "rest"
|
||||
}
|
||||
|
@ -401,55 +401,83 @@ importVisitor ((Node importRange import_) as node) context =
|
||||
|
||||
Nothing ->
|
||||
[]
|
||||
in
|
||||
case import_.exposingList of
|
||||
Nothing ->
|
||||
( errors, registerModuleNameOrAlias node context )
|
||||
|
||||
Just declaredImports ->
|
||||
let
|
||||
contextWithAlias : ModuleContext
|
||||
contextWithAlias =
|
||||
case import_.moduleAlias of
|
||||
Just moduleAlias ->
|
||||
registerModuleAlias node moduleAlias context
|
||||
( exposingErrors, newContext ) =
|
||||
case import_.exposingList of
|
||||
Nothing ->
|
||||
( [], registerModuleNameOrAlias node context )
|
||||
|
||||
Nothing ->
|
||||
context
|
||||
in
|
||||
( errors
|
||||
, case Node.value declaredImports of
|
||||
Exposing.All _ ->
|
||||
if Dict.member (Node.value import_.moduleName) context.customTypes then
|
||||
{ contextWithAlias
|
||||
| exposingAllModules =
|
||||
{ name = Node.value import_.moduleName
|
||||
, alias = Maybe.map (Node.value >> String.join ".") import_.moduleAlias
|
||||
, moduleNameRange = Node.range import_.moduleName
|
||||
, exposingRange = Node.range declaredImports
|
||||
, importRange = importRange
|
||||
, wasUsedImplicitly = False
|
||||
, wasUsedWithModuleName = False
|
||||
}
|
||||
:: context.exposingAllModules
|
||||
}
|
||||
|
||||
else
|
||||
contextWithAlias
|
||||
|
||||
Exposing.Explicit list ->
|
||||
Just declaredImports ->
|
||||
let
|
||||
customTypesFromModule : Dict String (List String)
|
||||
customTypesFromModule =
|
||||
context.customTypes
|
||||
|> Dict.get (Node.value import_.moduleName)
|
||||
|> Maybe.withDefault Dict.empty
|
||||
contextWithAlias : ModuleContext
|
||||
contextWithAlias =
|
||||
case import_.moduleAlias of
|
||||
Just moduleAlias ->
|
||||
registerModuleAlias node moduleAlias context
|
||||
|
||||
Nothing ->
|
||||
context
|
||||
in
|
||||
List.foldl
|
||||
(registerExposedElements customTypesFromModule)
|
||||
contextWithAlias
|
||||
(collectExplicitlyExposedElements (Node.range declaredImports) list)
|
||||
)
|
||||
case Node.value declaredImports of
|
||||
Exposing.All _ ->
|
||||
if Dict.member (Node.value import_.moduleName) context.customTypes then
|
||||
( []
|
||||
, { contextWithAlias
|
||||
| exposingAllModules =
|
||||
{ name = Node.value import_.moduleName
|
||||
, alias = Maybe.map (Node.value >> String.join ".") import_.moduleAlias
|
||||
, moduleNameRange = Node.range import_.moduleName
|
||||
, exposingRange = Node.range declaredImports
|
||||
, importRange = importRange
|
||||
, wasUsedImplicitly = False
|
||||
, wasUsedWithModuleName = False
|
||||
}
|
||||
:: context.exposingAllModules
|
||||
}
|
||||
)
|
||||
|
||||
else
|
||||
( [], contextWithAlias )
|
||||
|
||||
Exposing.Explicit list ->
|
||||
let
|
||||
customTypesFromModule : Dict String (List String)
|
||||
customTypesFromModule =
|
||||
context.customTypes
|
||||
|> Dict.get (Node.value import_.moduleName)
|
||||
|> Maybe.withDefault Dict.empty
|
||||
in
|
||||
List.foldl
|
||||
(handleExposedElements (NonemptyList.head contextWithAlias.scopes).declared customTypesFromModule)
|
||||
( [], contextWithAlias )
|
||||
(collectExplicitlyExposedElements (Node.range declaredImports) list)
|
||||
in
|
||||
( exposingErrors ++ errors, newContext )
|
||||
|
||||
|
||||
handleExposedElements : Dict String VariableInfo -> Dict String (List String) -> ExposedElement -> ( List (Rule.Error {}), ModuleContext ) -> ( List (Rule.Error {}), ModuleContext )
|
||||
handleExposedElements declared customTypesFromModule =
|
||||
\importedElement ( errors, context ) ->
|
||||
let
|
||||
name : String
|
||||
name =
|
||||
case importedElement of
|
||||
CustomType elementName _ ->
|
||||
elementName
|
||||
|
||||
TypeOrValue elementName _ ->
|
||||
elementName
|
||||
|
||||
newErrors : List (Rule.Error {})
|
||||
newErrors =
|
||||
case Dict.get name declared of
|
||||
Just variableInfo ->
|
||||
error variableInfo name :: errors
|
||||
|
||||
Nothing ->
|
||||
errors
|
||||
in
|
||||
( newErrors, registerExposedElements customTypesFromModule importedElement context )
|
||||
|
||||
|
||||
registerExposedElements : Dict String (List String) -> ExposedElement -> ModuleContext -> ModuleContext
|
||||
|
@ -1310,6 +1310,40 @@ type C = C_Value
|
||||
]
|
||||
|> Review.Test.runOnModules rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should report unused imported element when another import imports the same" <|
|
||||
\() ->
|
||||
[ """module A exposing (a)
|
||||
import B exposing (SomeType, b)
|
||||
import C exposing (SomeType)
|
||||
a : SomeType
|
||||
a = b
|
||||
"""
|
||||
, """module B exposing (SomeType, b)
|
||||
type SomeType = SomeType
|
||||
b = SomeType
|
||||
"""
|
||||
, """module C exposing (SomeType)
|
||||
type SomeType = SomeType
|
||||
"""
|
||||
]
|
||||
|> Review.Test.runOnModules rule
|
||||
|> Review.Test.expectErrorsForModules
|
||||
[ ( "A"
|
||||
, [ Review.Test.error
|
||||
{ message = "Imported type `SomeType` is not used"
|
||||
, details = details
|
||||
, under = "SomeType"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 2, column = 20 }, end = { row = 2, column = 28 } }
|
||||
|> Review.Test.whenFixed """module A exposing (a)
|
||||
import B exposing (b)
|
||||
import C exposing (SomeType)
|
||||
a : SomeType
|
||||
a = b
|
||||
"""
|
||||
]
|
||||
)
|
||||
]
|
||||
, test "should report open type import when none of the exposed constructors are used, but only remove the (..) when type is used" <|
|
||||
\() ->
|
||||
[ """module A exposing (a)
|
||||
|
Loading…
Reference in New Issue
Block a user