Backport rules from elm-review-unused

This commit is contained in:
Jeroen Engels 2022-09-19 11:54:35 +02:00
parent c4f775fddb
commit 4dd3bb5dff
11 changed files with 748 additions and 387 deletions

View File

@ -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
}

View File

@ -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

View File

@ -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))
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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." ]

View File

@ -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 (..)

View File

@ -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 ++ ")"

View File

@ -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"
}

View File

@ -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

View File

@ -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)