mirror of
https://github.com/jfmengels/elm-review.git
synced 2024-11-27 02:19:27 +03:00
Backport rules from elm-review-unused
This commit is contained in:
parent
076b299a82
commit
b71984aebb
@ -516,20 +516,21 @@ collectUsedCustomTypeArgs lookupTable (Node range pattern) =
|
||||
case pattern of
|
||||
Pattern.NamedPattern { name } args ->
|
||||
let
|
||||
usedPositions : Set Int
|
||||
usedPositions =
|
||||
args
|
||||
|> List.indexedMap Tuple.pair
|
||||
|> List.filter (\( _, subPattern ) -> not <| isWildcard subPattern)
|
||||
|> List.map Tuple.first
|
||||
|> Set.fromList
|
||||
|
||||
subList : List ( ( ModuleName, String ), Set Int )
|
||||
subList =
|
||||
List.concatMap (collectUsedCustomTypeArgs lookupTable) args
|
||||
in
|
||||
case ModuleNameLookupTable.moduleNameAt lookupTable range of
|
||||
Just moduleName ->
|
||||
let
|
||||
usedPositions : Set Int
|
||||
usedPositions =
|
||||
args
|
||||
|> List.indexedMap Tuple.pair
|
||||
|> List.filter (\( _, subPattern ) -> not <| isWildcard subPattern)
|
||||
|> List.map Tuple.first
|
||||
|> Set.fromList
|
||||
in
|
||||
( ( moduleName, name ), usedPositions ) :: subList
|
||||
|
||||
Nothing ->
|
||||
|
@ -2,9 +2,6 @@ module NoUnused.CustomTypeConstructors exposing (rule)
|
||||
|
||||
{-| Forbid having unused custom type constructors inside the project.
|
||||
|
||||
|
||||
# Rule
|
||||
|
||||
@docs rule
|
||||
|
||||
-}
|
||||
@ -22,7 +19,6 @@ import Elm.Syntax.Pattern as Pattern exposing (Pattern)
|
||||
import Elm.Syntax.Range exposing (Range)
|
||||
import Elm.Syntax.Type as Type
|
||||
import Elm.Syntax.TypeAnnotation as TypeAnnotation exposing (TypeAnnotation)
|
||||
import NoUnused.RangeDict as RangeDict exposing (RangeDict)
|
||||
import Review.Fix as Fix exposing (Fix)
|
||||
import Review.ModuleNameLookupTable as ModuleNameLookupTable exposing (ModuleNameLookupTable)
|
||||
import Review.Rule as Rule exposing (Error, Rule)
|
||||
@ -149,7 +145,8 @@ moduleVisitor schema =
|
||||
|> Rule.withDeclarationListVisitor declarationListVisitor
|
||||
|> Rule.withDeclarationEnterVisitor declarationVisitor
|
||||
|> Rule.withExpressionEnterVisitor expressionVisitor
|
||||
|> Rule.withExpressionExitVisitor expressionExitVisitor
|
||||
|> Rule.withCaseBranchEnterVisitor caseBranchEnterVisitor
|
||||
|> Rule.withCaseBranchExitVisitor caseBranchExitVisitor
|
||||
|
||||
|
||||
|
||||
@ -203,7 +200,6 @@ type alias ModuleContext =
|
||||
, declaredTypesWithConstructors : Dict CustomTypeName (Dict ConstructorName ConstructorInformation)
|
||||
, usedFunctionsOrValues : Dict ModuleNameAsString (Set ConstructorName)
|
||||
, phantomVariables : Dict ModuleName (List ( CustomTypeName, Int ))
|
||||
, ignoreBlocks : List (RangeDict (Set ( ModuleName, String )))
|
||||
, constructorsToIgnore : List (Set ( ModuleName, String ))
|
||||
, wasUsedInLocationThatNeedsItself : Set ( ModuleNameAsString, ConstructorName )
|
||||
, wasUsedInComparisons : Set ( ModuleNameAsString, ConstructorName )
|
||||
@ -244,7 +240,6 @@ fromProjectToModule lookupTable metadata projectContext =
|
||||
, declaredTypesWithConstructors = Dict.empty
|
||||
, usedFunctionsOrValues = Dict.empty
|
||||
, phantomVariables = projectContext.phantomVariables
|
||||
, ignoreBlocks = []
|
||||
, constructorsToIgnore = []
|
||||
, wasUsedInLocationThatNeedsItself = Set.empty
|
||||
, wasUsedInComparisons = Set.empty
|
||||
@ -366,12 +361,12 @@ foldProjectContexts newContext previousContext =
|
||||
}
|
||||
|
||||
|
||||
mergeDictsWithLists : Dict comparable appendable -> Dict comparable appendable -> Dict comparable appendable
|
||||
mergeDictsWithLists : Dict comparable (List a) -> Dict comparable (List a) -> Dict comparable (List a)
|
||||
mergeDictsWithLists left right =
|
||||
Dict.merge
|
||||
(\key a dict -> Dict.insert key a dict)
|
||||
(\key a b dict -> Dict.insert key (a ++ b) dict)
|
||||
(\key b dict -> Dict.insert key b dict)
|
||||
Dict.insert
|
||||
(\key a b dict -> Dict.insert key (List.append a b) dict)
|
||||
Dict.insert
|
||||
left
|
||||
right
|
||||
Dict.empty
|
||||
@ -622,52 +617,6 @@ isNeverOrItself lookupTable typeName node =
|
||||
|
||||
expressionVisitor : Node Expression -> ModuleContext -> ( List nothing, ModuleContext )
|
||||
expressionVisitor node moduleContext =
|
||||
let
|
||||
newModuleContext : ModuleContext
|
||||
newModuleContext =
|
||||
case List.head moduleContext.ignoreBlocks of
|
||||
Just expressionsWhereToIgnoreCases ->
|
||||
case RangeDict.get (Node.range node) expressionsWhereToIgnoreCases of
|
||||
Just constructorsToIgnore ->
|
||||
{ moduleContext | constructorsToIgnore = constructorsToIgnore :: moduleContext.constructorsToIgnore }
|
||||
|
||||
Nothing ->
|
||||
moduleContext
|
||||
|
||||
Nothing ->
|
||||
moduleContext
|
||||
in
|
||||
expressionVisitorHelp node newModuleContext
|
||||
|
||||
|
||||
expressionExitVisitor : Node Expression -> ModuleContext -> ( List nothing, ModuleContext )
|
||||
expressionExitVisitor node moduleContext =
|
||||
let
|
||||
newModuleContext : ModuleContext
|
||||
newModuleContext =
|
||||
case Node.value node of
|
||||
Expression.CaseExpression _ ->
|
||||
{ moduleContext | ignoreBlocks = List.drop 1 moduleContext.ignoreBlocks }
|
||||
|
||||
_ ->
|
||||
moduleContext
|
||||
in
|
||||
case List.head newModuleContext.ignoreBlocks of
|
||||
Just rangesWhereToIgnoreConstructors ->
|
||||
if RangeDict.member (Node.range node) rangesWhereToIgnoreConstructors then
|
||||
( []
|
||||
, { newModuleContext | constructorsToIgnore = List.drop 1 newModuleContext.constructorsToIgnore }
|
||||
)
|
||||
|
||||
else
|
||||
( [], newModuleContext )
|
||||
|
||||
Nothing ->
|
||||
( [], newModuleContext )
|
||||
|
||||
|
||||
expressionVisitorHelp : Node Expression -> ModuleContext -> ( List nothing, ModuleContext )
|
||||
expressionVisitorHelp node moduleContext =
|
||||
case Node.value node of
|
||||
Expression.FunctionOrValue _ name ->
|
||||
case ModuleNameLookupTable.moduleNameFor moduleContext.lookupTable node of
|
||||
@ -779,42 +728,86 @@ expressionVisitorHelp node moduleContext =
|
||||
|> List.foldl markPhantomTypesFromTypeAnnotationAsUsed moduleContext
|
||||
)
|
||||
|
||||
Expression.CaseExpression { cases } ->
|
||||
let
|
||||
found : List { ignoreBlock : ( Range, Set ( ModuleName, ConstructorName ) ), fixes : Dict ConstructorName (List Fix) }
|
||||
found =
|
||||
List.map2
|
||||
(forOne moduleContext.lookupTable)
|
||||
(Nothing :: List.map (Tuple.second >> Node.range >> .end >> Just) cases)
|
||||
cases
|
||||
|
||||
ignoredBlocks : RangeDict (Set ( ModuleName, String ))
|
||||
ignoredBlocks =
|
||||
List.map .ignoreBlock found
|
||||
|> RangeDict.fromList
|
||||
|
||||
wasUsedInOtherModules : Set ( ModuleNameAsString, ConstructorName )
|
||||
wasUsedInOtherModules =
|
||||
found
|
||||
|> List.map (.ignoreBlock >> Tuple.second >> toSetOfModuleNameAsString)
|
||||
|> List.foldl Set.union moduleContext.wasUsedInOtherModules
|
||||
in
|
||||
( []
|
||||
, { moduleContext
|
||||
| ignoreBlocks = ignoredBlocks :: moduleContext.ignoreBlocks
|
||||
, wasUsedInOtherModules = wasUsedInOtherModules
|
||||
, fixesForRemovingConstructor =
|
||||
List.foldl
|
||||
mergeDictsWithLists
|
||||
moduleContext.fixesForRemovingConstructor
|
||||
(List.map .fixes found)
|
||||
}
|
||||
)
|
||||
|
||||
_ ->
|
||||
( [], moduleContext )
|
||||
|
||||
|
||||
caseBranchEnterVisitor : Node Expression.CaseBlock -> ( Node Pattern, Node Expression ) -> ModuleContext -> ( List nothing, ModuleContext )
|
||||
caseBranchEnterVisitor caseExpression ( casePattern, body ) moduleContext =
|
||||
let
|
||||
previousLocation : Maybe Elm.Syntax.Range.Location
|
||||
previousLocation =
|
||||
findEndLocationOfPreviousElement (Node.value caseExpression).cases (Node.range casePattern) Nothing
|
||||
|
||||
constructors : Set ( ModuleName, String )
|
||||
constructors =
|
||||
constructorsInPattern moduleContext.lookupTable casePattern
|
||||
|
||||
fixes : Dict ConstructorName (List Fix)
|
||||
fixes =
|
||||
List.foldl
|
||||
(\( moduleName, constructorName ) acc ->
|
||||
if moduleName == [] then
|
||||
let
|
||||
fix : Fix
|
||||
fix =
|
||||
Fix.removeRange
|
||||
{ start = Maybe.withDefault (Node.range casePattern).start previousLocation
|
||||
, end = (Node.range body).end
|
||||
}
|
||||
in
|
||||
Dict.update
|
||||
constructorName
|
||||
(\existing ->
|
||||
case existing of
|
||||
Just list ->
|
||||
Just (fix :: list)
|
||||
|
||||
Nothing ->
|
||||
Just [ fix ]
|
||||
)
|
||||
acc
|
||||
|
||||
else
|
||||
acc
|
||||
)
|
||||
moduleContext.fixesForRemovingConstructor
|
||||
(Set.toList constructors)
|
||||
|
||||
wasUsedInOtherModules : Set ( ModuleNameAsString, ConstructorName )
|
||||
wasUsedInOtherModules =
|
||||
toSetOfModuleNameAsString constructors
|
||||
in
|
||||
( []
|
||||
, { moduleContext
|
||||
| wasUsedInOtherModules = Set.union wasUsedInOtherModules moduleContext.wasUsedInOtherModules
|
||||
, constructorsToIgnore = constructors :: moduleContext.constructorsToIgnore
|
||||
, fixesForRemovingConstructor = fixes
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
caseBranchExitVisitor : Node Expression.CaseBlock -> ( Node Pattern, Node Expression ) -> ModuleContext -> ( List nothing, ModuleContext )
|
||||
caseBranchExitVisitor _ _ moduleContext =
|
||||
( []
|
||||
, { moduleContext | constructorsToIgnore = List.drop 1 moduleContext.constructorsToIgnore }
|
||||
)
|
||||
|
||||
|
||||
findEndLocationOfPreviousElement : List ( Node a, Node b ) -> Range -> Maybe Elm.Syntax.Range.Location -> Maybe Elm.Syntax.Range.Location
|
||||
findEndLocationOfPreviousElement nodes nodeRange previousRangeEnd =
|
||||
case nodes of
|
||||
( Node patternRange _, Node bodyRange _ ) :: tail ->
|
||||
if patternRange == nodeRange then
|
||||
previousRangeEnd
|
||||
|
||||
else
|
||||
findEndLocationOfPreviousElement tail nodeRange (Just bodyRange.end)
|
||||
|
||||
[] ->
|
||||
Nothing
|
||||
|
||||
|
||||
toSetOfModuleNameAsString : Set ( ModuleName, ConstructorName ) -> Set ( ModuleNameAsString, ConstructorName )
|
||||
toSetOfModuleNameAsString set =
|
||||
set
|
||||
@ -822,38 +815,6 @@ toSetOfModuleNameAsString set =
|
||||
|> Set.filter (\( moduleName, _ ) -> moduleName /= "")
|
||||
|
||||
|
||||
forOne : ModuleNameLookupTable -> Maybe Elm.Syntax.Range.Location -> ( Node Pattern, Node a ) -> { ignoreBlock : ( Range, Set ( ModuleName, String ) ), fixes : Dict ConstructorName (List Fix) }
|
||||
forOne lookupTable previousLocation ( pattern, body ) =
|
||||
let
|
||||
constructors : Set ( ModuleName, String )
|
||||
constructors =
|
||||
constructorsInPattern lookupTable pattern
|
||||
|
||||
fixes : Dict ConstructorName (List Fix)
|
||||
fixes =
|
||||
List.foldl
|
||||
(\( moduleName, constructorName ) acc ->
|
||||
if moduleName == [] then
|
||||
Dict.insert
|
||||
constructorName
|
||||
[ Fix.removeRange
|
||||
{ start = Maybe.withDefault (Node.range pattern).start previousLocation
|
||||
, end = (Node.range body).end
|
||||
}
|
||||
]
|
||||
acc
|
||||
|
||||
else
|
||||
acc
|
||||
)
|
||||
Dict.empty
|
||||
(Set.toList constructors)
|
||||
in
|
||||
{ ignoreBlock = ( Node.range body, constructors )
|
||||
, fixes = fixes
|
||||
}
|
||||
|
||||
|
||||
staticRanges : Node Expression -> List Range
|
||||
staticRanges node =
|
||||
case Node.value node of
|
||||
|
@ -2,9 +2,6 @@ module NoUnused.Dependencies exposing (rule)
|
||||
|
||||
{-| Forbid the use of dependencies that are never used in your project.
|
||||
|
||||
|
||||
# Rule
|
||||
|
||||
@docs rule
|
||||
|
||||
-}
|
||||
@ -70,6 +67,11 @@ dependenciesVisitor dependencies projectContext =
|
||||
moduleNameToDependency : Dict String String
|
||||
moduleNameToDependency =
|
||||
dependencies
|
||||
|> Dict.filter
|
||||
(\packageName _ ->
|
||||
Set.member packageName projectContext.directProjectDependencies
|
||||
|| Set.member packageName projectContext.directTestDependencies
|
||||
)
|
||||
|> Dict.toList
|
||||
|> List.concatMap
|
||||
(\( packageName, dependency ) ->
|
||||
@ -384,56 +386,7 @@ fromProject : Dict String Dependency -> DependencyLocation -> String -> Project
|
||||
fromProject dependenciesDict dependencyLocation packageNameStr project =
|
||||
case project of
|
||||
Elm.Project.Application application ->
|
||||
let
|
||||
dependencies : Elm.Project.Deps Elm.Version.Version
|
||||
dependencies =
|
||||
case dependencyLocation of
|
||||
InProjectDeps ->
|
||||
application.depsDirect
|
||||
|
||||
InTestDeps ->
|
||||
application.testDepsDirect
|
||||
|
||||
dependencyVersionDict : Dict String Elm.Version.Version
|
||||
dependencyVersionDict =
|
||||
[ application.depsDirect
|
||||
, application.depsIndirect
|
||||
, application.testDepsDirect
|
||||
, application.testDepsIndirect
|
||||
]
|
||||
|> List.concat
|
||||
|> List.map (\( name, version ) -> ( Elm.Package.toString name, version ))
|
||||
|> Dict.fromList
|
||||
|
||||
getDependenciesAndVersion : Elm.Package.Name -> Elm.Project.Deps Elm.Version.Version
|
||||
getDependenciesAndVersion name =
|
||||
case Dict.get (Elm.Package.toString name) dependenciesDict of
|
||||
Just deps ->
|
||||
deps
|
||||
|> Dependency.elmJson
|
||||
|> packageDependencies
|
||||
|> List.filterMap
|
||||
(\depName ->
|
||||
Dict.get (Elm.Package.toString depName) dependencyVersionDict
|
||||
|> Maybe.map (Tuple.pair depName)
|
||||
)
|
||||
|
||||
Nothing ->
|
||||
[]
|
||||
in
|
||||
case List.Extra.find (isPackageWithName packageNameStr) dependencies of
|
||||
Just ( packageName, version ) ->
|
||||
Just
|
||||
(ApplicationProject
|
||||
{ application = application
|
||||
, name = packageName
|
||||
, version = version
|
||||
, getDependenciesAndVersion = getDependenciesAndVersion
|
||||
}
|
||||
)
|
||||
|
||||
Nothing ->
|
||||
Nothing
|
||||
fromApplication dependenciesDict dependencyLocation packageNameStr application
|
||||
|
||||
Elm.Project.Package packageInfo ->
|
||||
let
|
||||
@ -454,6 +407,60 @@ fromProject dependenciesDict dependencyLocation packageNameStr project =
|
||||
Nothing
|
||||
|
||||
|
||||
fromApplication : Dict String Dependency -> DependencyLocation -> String -> Elm.Project.ApplicationInfo -> Maybe ProjectAndDependencyIdentifier
|
||||
fromApplication dependenciesDict dependencyLocation packageNameStr application =
|
||||
let
|
||||
dependencies : Elm.Project.Deps Elm.Version.Version
|
||||
dependencies =
|
||||
case dependencyLocation of
|
||||
InProjectDeps ->
|
||||
application.depsDirect
|
||||
|
||||
InTestDeps ->
|
||||
application.testDepsDirect
|
||||
|
||||
dependencyVersionDict : Dict String Elm.Version.Version
|
||||
dependencyVersionDict =
|
||||
[ application.depsDirect
|
||||
, application.depsIndirect
|
||||
, application.testDepsDirect
|
||||
, application.testDepsIndirect
|
||||
]
|
||||
|> List.concat
|
||||
|> List.map (\( name, version ) -> ( Elm.Package.toString name, version ))
|
||||
|> Dict.fromList
|
||||
|
||||
getDependenciesAndVersion : Elm.Package.Name -> Elm.Project.Deps Elm.Version.Version
|
||||
getDependenciesAndVersion name =
|
||||
case Dict.get (Elm.Package.toString name) dependenciesDict of
|
||||
Just deps ->
|
||||
deps
|
||||
|> Dependency.elmJson
|
||||
|> packageDependencies
|
||||
|> List.filterMap
|
||||
(\depName ->
|
||||
Dict.get (Elm.Package.toString depName) dependencyVersionDict
|
||||
|> Maybe.map (Tuple.pair depName)
|
||||
)
|
||||
|
||||
Nothing ->
|
||||
[]
|
||||
in
|
||||
case List.Extra.find (isPackageWithName packageNameStr) dependencies of
|
||||
Just ( packageName, version ) ->
|
||||
Just
|
||||
(ApplicationProject
|
||||
{ application = application
|
||||
, name = packageName
|
||||
, version = version
|
||||
, getDependenciesAndVersion = getDependenciesAndVersion
|
||||
}
|
||||
)
|
||||
|
||||
Nothing ->
|
||||
Nothing
|
||||
|
||||
|
||||
toProject : ProjectAndDependencyIdentifier -> Elm.Project.Project
|
||||
toProject projectAndDependencyIdentifier =
|
||||
case projectAndDependencyIdentifier of
|
||||
|
@ -116,6 +116,32 @@ applicationElmJsonWithoutTestDeps =
|
||||
}"""
|
||||
|
||||
|
||||
applicationElmJsonWithIndirectOtherFooWithoutTestDeps : String
|
||||
applicationElmJsonWithIndirectOtherFooWithoutTestDeps =
|
||||
"""
|
||||
{
|
||||
"type": "application",
|
||||
"source-directories": [
|
||||
"src"
|
||||
],
|
||||
"elm-version": "0.19.1",
|
||||
"dependencies": {
|
||||
"direct": {
|
||||
"author/package-with-bar": "1.0.0",
|
||||
"author/package-with-foo": "1.0.0",
|
||||
"elm/core": "1.0.0"
|
||||
},
|
||||
"indirect": {
|
||||
"author/package-with-other-foo": "1.0.0"
|
||||
}
|
||||
},
|
||||
"test-dependencies": {
|
||||
"direct": {},
|
||||
"indirect": {}
|
||||
}
|
||||
}"""
|
||||
|
||||
|
||||
packageElmJson : String
|
||||
packageElmJson =
|
||||
"""
|
||||
@ -196,6 +222,34 @@ packageWithFoo =
|
||||
(dummyModules "Foo")
|
||||
|
||||
|
||||
packageWithOtherFoo : Dependency
|
||||
packageWithOtherFoo =
|
||||
let
|
||||
elmJson : { path : String, raw : String, project : Elm.Project.Project }
|
||||
elmJson =
|
||||
createElmJson """
|
||||
{
|
||||
"type": "package",
|
||||
"name": "author/package-with-other-foo",
|
||||
"summary": "Summary",
|
||||
"license": "BSD-3-Clause",
|
||||
"version": "1.0.0",
|
||||
"exposed-modules": [
|
||||
"Foo"
|
||||
],
|
||||
"elm-version": "0.19.0 <= v < 0.20.0",
|
||||
"dependencies": {
|
||||
"elm/core": "1.0.0 <= v < 2.0.0"
|
||||
},
|
||||
"test-dependencies": {}
|
||||
}"""
|
||||
in
|
||||
Dependency.create
|
||||
"author/package-with-other-foo"
|
||||
elmJson.project
|
||||
(dummyModules "Foo")
|
||||
|
||||
|
||||
packageWithFooDependingOnBar : Dependency
|
||||
packageWithFooDependingOnBar =
|
||||
let
|
||||
@ -474,6 +528,33 @@ a = 1
|
||||
|> String.replace "\u{000D}" ""
|
||||
|> Review.Test.runWithProjectData (createProject (Just testModule) applicationElmJson) rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should not report dependencies for an application whose modules are imported indirect name clash" <|
|
||||
\() ->
|
||||
let
|
||||
testModule : String
|
||||
testModule =
|
||||
"""module TestModule exposing (suite)
|
||||
|
||||
import TestFoo
|
||||
import TestBar
|
||||
|
||||
suite = 0
|
||||
"""
|
||||
|> String.replace "\u{000D}" ""
|
||||
in
|
||||
"""
|
||||
module A exposing (a)
|
||||
import Foo
|
||||
import Bar
|
||||
a = 1
|
||||
"""
|
||||
|> String.replace "\u{000D}" ""
|
||||
|> Review.Test.runWithProjectData
|
||||
(createProject (Just testModule) applicationElmJsonWithIndirectOtherFooWithoutTestDeps
|
||||
|> Project.addDependency packageWithOtherFoo
|
||||
)
|
||||
rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should report unused dependencies for a package when none of their modules are imported" <|
|
||||
\() ->
|
||||
"""
|
||||
|
@ -2,9 +2,6 @@ module NoUnused.Exports exposing (rule)
|
||||
|
||||
{-| Forbid the use of exposed elements that are never used in your project.
|
||||
|
||||
|
||||
# Rule
|
||||
|
||||
@docs rule
|
||||
|
||||
-}
|
||||
@ -544,12 +541,6 @@ importVisitor node moduleContext =
|
||||
declarationListVisitor : List (Node Declaration) -> ModuleContext -> ( List nothing, ModuleContext )
|
||||
declarationListVisitor declarations moduleContext =
|
||||
let
|
||||
declaredNames : Set String
|
||||
declaredNames =
|
||||
declarations
|
||||
|> List.filterMap (Node.value >> declarationName)
|
||||
|> Set.fromList
|
||||
|
||||
typesUsedInDeclaration_ : List ( List ( ModuleName, String ), Bool )
|
||||
typesUsedInDeclaration_ =
|
||||
declarations
|
||||
@ -577,6 +568,13 @@ declarationListVisitor declarations moduleContext =
|
||||
identity
|
||||
|
||||
else
|
||||
let
|
||||
declaredNames : Set String
|
||||
declaredNames =
|
||||
declarations
|
||||
|> List.filterMap (Node.value >> declarationName)
|
||||
|> Set.fromList
|
||||
in
|
||||
Dict.filter (\name _ -> Set.member name declaredNames)
|
||||
)
|
||||
, elementsNotToReport =
|
||||
|
@ -2,9 +2,6 @@ module NoUnused.Modules exposing (rule)
|
||||
|
||||
{-| Forbid the use of modules that are never used in your project.
|
||||
|
||||
|
||||
# Rule
|
||||
|
||||
@docs rule
|
||||
|
||||
-}
|
||||
|
@ -26,7 +26,7 @@ available.
|
||||
|
||||
# Access
|
||||
|
||||
@docs head, sample
|
||||
@docs head
|
||||
|
||||
|
||||
# Convert
|
||||
|
@ -2,9 +2,6 @@ module NoUnused.Parameters exposing (rule)
|
||||
|
||||
{-| Report parameters that are not used.
|
||||
|
||||
|
||||
# Rule
|
||||
|
||||
@docs rule
|
||||
|
||||
-}
|
||||
@ -15,7 +12,6 @@ import Elm.Syntax.Expression as Expression exposing (Expression)
|
||||
import Elm.Syntax.Node as Node exposing (Node(..))
|
||||
import Elm.Syntax.Pattern as Pattern exposing (Pattern)
|
||||
import Elm.Syntax.Range as Range exposing (Range)
|
||||
import NoUnused.RangeDict as RangeDict exposing (RangeDict)
|
||||
import Review.Fix as Fix exposing (Fix)
|
||||
import Review.Rule as Rule exposing (Rule)
|
||||
import Set exposing (Set)
|
||||
@ -76,9 +72,12 @@ elm-review --template jfmengels/elm-review-unused/example --rules NoUnused.Param
|
||||
rule : Rule
|
||||
rule =
|
||||
Rule.newModuleRuleSchema "NoUnused.Parameters" initialContext
|
||||
|> Rule.withDeclarationEnterVisitor declarationVisitor
|
||||
|> Rule.withDeclarationEnterVisitor declarationEnterVisitor
|
||||
|> Rule.withDeclarationExitVisitor declarationExitVisitor
|
||||
|> Rule.withExpressionEnterVisitor expressionEnterVisitor
|
||||
|> Rule.withExpressionExitVisitor expressionExitVisitor
|
||||
|> Rule.withLetDeclarationEnterVisitor letDeclarationEnterVisitor
|
||||
|> Rule.withLetDeclarationExitVisitor letDeclarationExitVisitor
|
||||
|> Rule.fromModuleRuleSchema
|
||||
|
||||
|
||||
@ -88,7 +87,6 @@ rule =
|
||||
|
||||
type alias Context =
|
||||
{ scopes : List Scope
|
||||
, scopesToCreate : RangeDict ScopeToCreate
|
||||
, knownFunctions : Dict String FunctionArgs
|
||||
, locationsToIgnoreForUsed : LocationsToIgnore
|
||||
}
|
||||
@ -102,13 +100,6 @@ type alias Scope =
|
||||
}
|
||||
|
||||
|
||||
type alias ScopeToCreate =
|
||||
{ declared : List Declared
|
||||
, functionName : String
|
||||
, functionArgs : FunctionArgs
|
||||
}
|
||||
|
||||
|
||||
type alias Declared =
|
||||
{ name : String
|
||||
, range : Range
|
||||
@ -141,7 +132,6 @@ type Source
|
||||
initialContext : Context
|
||||
initialContext =
|
||||
{ scopes = []
|
||||
, scopesToCreate = RangeDict.empty
|
||||
, knownFunctions = Dict.empty
|
||||
, locationsToIgnoreForUsed = Dict.empty
|
||||
}
|
||||
@ -151,8 +141,8 @@ initialContext =
|
||||
-- DECLARATION VISITOR
|
||||
|
||||
|
||||
declarationVisitor : Node Declaration -> Context -> ( List nothing, Context )
|
||||
declarationVisitor node context =
|
||||
declarationEnterVisitor : Node Declaration -> Context -> ( List nothing, Context )
|
||||
declarationEnterVisitor node context =
|
||||
case Node.value node of
|
||||
Declaration.FunctionDeclaration { declaration } ->
|
||||
let
|
||||
@ -163,17 +153,20 @@ declarationVisitor node context =
|
||||
declared : List (List Declared)
|
||||
declared =
|
||||
List.map (getParametersFromPatterns NamedFunction) arguments
|
||||
|
||||
functionName : String
|
||||
functionName =
|
||||
Node.value declaration |> .name |> Node.value
|
||||
in
|
||||
( []
|
||||
, { scopes = []
|
||||
, scopesToCreate =
|
||||
RangeDict.singleton
|
||||
(declaration |> Node.value |> .expression |> Node.range)
|
||||
{ declared = List.concat declared
|
||||
, functionName = Node.value declaration |> .name |> Node.value
|
||||
, functionArgs = getArgNames declared
|
||||
}
|
||||
, knownFunctions = Dict.empty
|
||||
, { scopes =
|
||||
[ { functionName = functionName
|
||||
, declared = List.concat declared
|
||||
, used = Set.empty
|
||||
, usedRecursively = Set.empty
|
||||
}
|
||||
]
|
||||
, knownFunctions = Dict.singleton functionName (getArgNames declared)
|
||||
, locationsToIgnoreForUsed = Dict.empty
|
||||
}
|
||||
)
|
||||
@ -182,6 +175,16 @@ declarationVisitor node context =
|
||||
( [], context )
|
||||
|
||||
|
||||
declarationExitVisitor : Node Declaration -> Context -> ( List (Rule.Error {}), Context )
|
||||
declarationExitVisitor node context =
|
||||
case Node.value node of
|
||||
Declaration.FunctionDeclaration _ ->
|
||||
report context
|
||||
|
||||
_ ->
|
||||
( [], context )
|
||||
|
||||
|
||||
getArgNames : List (List Declared) -> FunctionArgs
|
||||
getArgNames declared =
|
||||
declared
|
||||
@ -214,32 +217,7 @@ getParametersFromPatterns source node =
|
||||
]
|
||||
|
||||
Pattern.AsPattern pattern asName ->
|
||||
let
|
||||
parametersFromPatterns : List Declared
|
||||
parametersFromPatterns =
|
||||
getParametersFromPatterns source pattern
|
||||
|
||||
asParameter : Declared
|
||||
asParameter =
|
||||
{ name = Node.value asName
|
||||
, range = Node.range asName
|
||||
, kind = Alias
|
||||
, fix = [ Fix.removeRange { start = (Node.range pattern).end, end = (Node.range asName).end } ]
|
||||
, 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
|
||||
getParametersFromAsPattern source pattern asName
|
||||
|
||||
Pattern.RecordPattern fields ->
|
||||
case fields of
|
||||
@ -298,6 +276,36 @@ getParametersFromPatterns source node =
|
||||
[]
|
||||
|
||||
|
||||
getParametersFromAsPattern : Source -> Node Pattern -> Node String -> List Declared
|
||||
getParametersFromAsPattern source pattern asName =
|
||||
let
|
||||
parametersFromPatterns : List Declared
|
||||
parametersFromPatterns =
|
||||
getParametersFromPatterns source pattern
|
||||
|
||||
asParameter : Declared
|
||||
asParameter =
|
||||
{ name = Node.value asName
|
||||
, range = Node.range asName
|
||||
, kind = Alias
|
||||
, fix = [ Fix.removeRange { start = (Node.range pattern).end, end = (Node.range asName).end } ]
|
||||
, 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
|
||||
|
||||
|
||||
isPatternWildCard : Node Pattern -> Bool
|
||||
isPatternWildCard node =
|
||||
case Node.value node of
|
||||
@ -322,107 +330,115 @@ formatRecord fields =
|
||||
|
||||
expressionEnterVisitor : Node Expression -> Context -> ( List nothing, Context )
|
||||
expressionEnterVisitor node context =
|
||||
let
|
||||
newContext : Context
|
||||
newContext =
|
||||
case RangeDict.get (Node.range node) context.scopesToCreate of
|
||||
Just { declared, functionName, functionArgs } ->
|
||||
{ context
|
||||
| scopes =
|
||||
{ functionName = functionName
|
||||
, declared = declared
|
||||
, used = Set.empty
|
||||
, usedRecursively = Set.singleton "unused"
|
||||
}
|
||||
:: context.scopes
|
||||
, knownFunctions =
|
||||
Dict.insert
|
||||
functionName
|
||||
functionArgs
|
||||
context.knownFunctions
|
||||
}
|
||||
|
||||
Nothing ->
|
||||
context
|
||||
in
|
||||
expressionEnterVisitorHelp node newContext
|
||||
( [], expressionEnterVisitorHelp node context )
|
||||
|
||||
|
||||
expressionEnterVisitorHelp : Node Expression -> Context -> ( List nothing, Context )
|
||||
expressionEnterVisitorHelp : Node Expression -> Context -> Context
|
||||
expressionEnterVisitorHelp node context =
|
||||
case Node.value node of
|
||||
Expression.FunctionOrValue [] name ->
|
||||
( [], markValueAsUsed (Node.range node) name context )
|
||||
markValueAsUsed (Node.range node) name context
|
||||
|
||||
Expression.RecordUpdateExpression name _ ->
|
||||
( [], markValueAsUsed (Node.range name) (Node.value name) context )
|
||||
markValueAsUsed (Node.range name) (Node.value name) context
|
||||
|
||||
Expression.LetExpression letBlock ->
|
||||
let
|
||||
declaredWithRange : List ( Range, ScopeToCreate )
|
||||
declaredWithRange =
|
||||
List.filterMap
|
||||
(\letDeclaration ->
|
||||
case Node.value letDeclaration of
|
||||
Expression.LetFunction function ->
|
||||
let
|
||||
declaration : Expression.FunctionImplementation
|
||||
declaration =
|
||||
Node.value function.declaration
|
||||
|
||||
declared : List (List Declared)
|
||||
declared =
|
||||
List.map (getParametersFromPatterns NamedFunction) declaration.arguments
|
||||
in
|
||||
if List.isEmpty declared then
|
||||
Nothing
|
||||
|
||||
else
|
||||
Just
|
||||
( Node.range declaration.expression
|
||||
, { declared = List.concat declared
|
||||
, functionName = Node.value declaration.name
|
||||
, functionArgs = getArgNames declared
|
||||
}
|
||||
)
|
||||
|
||||
Expression.LetDestructuring _ _ ->
|
||||
Nothing
|
||||
)
|
||||
letBlock.declarations
|
||||
|
||||
scopesToCreate : RangeDict ScopeToCreate
|
||||
scopesToCreate =
|
||||
RangeDict.insertAll declaredWithRange context.scopesToCreate
|
||||
in
|
||||
( [], { context | scopesToCreate = scopesToCreate } )
|
||||
|
||||
Expression.LambdaExpression { args, expression } ->
|
||||
let
|
||||
scopesToCreate : RangeDict ScopeToCreate
|
||||
scopesToCreate =
|
||||
RangeDict.insert
|
||||
(Node.range expression)
|
||||
{ declared = List.concatMap (getParametersFromPatterns Lambda) args
|
||||
, functionName = "dummy lambda"
|
||||
, functionArgs = Dict.empty
|
||||
}
|
||||
context.scopesToCreate
|
||||
in
|
||||
( [], { context | scopesToCreate = scopesToCreate } )
|
||||
Expression.LambdaExpression { args } ->
|
||||
{ context
|
||||
| scopes =
|
||||
{ functionName = "dummy lambda"
|
||||
, declared = List.concatMap (getParametersFromPatterns Lambda) args
|
||||
, used = Set.empty
|
||||
, usedRecursively = Set.empty
|
||||
}
|
||||
:: context.scopes
|
||||
}
|
||||
|
||||
Expression.Application ((Node _ (Expression.FunctionOrValue [] fnName)) :: arguments) ->
|
||||
( [], registerFunctionCall fnName 0 arguments context )
|
||||
registerFunctionCall fnName 0 arguments context
|
||||
|
||||
Expression.OperatorApplication "|>" _ lastArgument (Node _ (Expression.Application ((Node _ (Expression.FunctionOrValue [] fnName)) :: arguments))) ->
|
||||
-- Ignoring "arguments" because they will be visited when the Application node will be visited anyway.
|
||||
( [], registerFunctionCall fnName (List.length arguments) [ lastArgument ] context )
|
||||
registerFunctionCall fnName (List.length arguments) [ lastArgument ] context
|
||||
|
||||
Expression.OperatorApplication "<|" _ (Node _ (Expression.Application ((Node _ (Expression.FunctionOrValue [] fnName)) :: arguments))) lastArgument ->
|
||||
-- Ignoring "arguments" because they will be visited when the Application node will be visited anyway.
|
||||
( [], registerFunctionCall fnName (List.length arguments) [ lastArgument ] context )
|
||||
registerFunctionCall fnName (List.length arguments) [ lastArgument ] context
|
||||
|
||||
_ ->
|
||||
context
|
||||
|
||||
|
||||
|
||||
-- EXPRESSION EXIT VISITOR
|
||||
|
||||
|
||||
expressionExitVisitor : Node Expression -> Context -> ( List (Rule.Error {}), Context )
|
||||
expressionExitVisitor (Node _ node) context =
|
||||
case node of
|
||||
Expression.LambdaExpression _ ->
|
||||
report context
|
||||
|
||||
_ ->
|
||||
( [], context )
|
||||
|
||||
|
||||
letDeclarationEnterVisitor : a -> Node Expression.LetDeclaration -> Context -> ( List nothing, Context )
|
||||
letDeclarationEnterVisitor _ letDeclaration context =
|
||||
case Node.value letDeclaration of
|
||||
Expression.LetFunction function ->
|
||||
let
|
||||
declaration : Expression.FunctionImplementation
|
||||
declaration =
|
||||
Node.value function.declaration
|
||||
in
|
||||
if List.isEmpty declaration.arguments then
|
||||
( [], context )
|
||||
|
||||
else
|
||||
let
|
||||
functionName : String
|
||||
functionName =
|
||||
Node.value declaration.name
|
||||
|
||||
declared : List (List Declared)
|
||||
declared =
|
||||
List.map (getParametersFromPatterns NamedFunction) declaration.arguments
|
||||
|
||||
newScope : Scope
|
||||
newScope =
|
||||
{ functionName = functionName
|
||||
, declared = List.concat declared
|
||||
, used = Set.empty
|
||||
, usedRecursively = Set.empty
|
||||
}
|
||||
in
|
||||
( []
|
||||
, { context
|
||||
| scopes = newScope :: context.scopes
|
||||
, knownFunctions = Dict.insert functionName (getArgNames declared) context.knownFunctions
|
||||
}
|
||||
)
|
||||
|
||||
Expression.LetDestructuring _ _ ->
|
||||
( [], context )
|
||||
|
||||
|
||||
letDeclarationExitVisitor : a -> Node Expression.LetDeclaration -> Context -> ( List (Rule.Error {}), Context )
|
||||
letDeclarationExitVisitor _ letDeclaration context =
|
||||
case Node.value letDeclaration of
|
||||
Expression.LetFunction function ->
|
||||
let
|
||||
declaration : Expression.FunctionImplementation
|
||||
declaration =
|
||||
Node.value function.declaration
|
||||
in
|
||||
if List.isEmpty declaration.arguments then
|
||||
( [], context )
|
||||
|
||||
else
|
||||
report context
|
||||
|
||||
Expression.LetDestructuring _ _ ->
|
||||
( [], context )
|
||||
|
||||
|
||||
@ -446,7 +462,7 @@ registerFunctionCall fnName numberOfIgnoredArguments arguments context =
|
||||
| locationsToIgnoreForUsed =
|
||||
Dict.merge
|
||||
Dict.insert
|
||||
(\key new old -> Dict.insert key (new ++ old))
|
||||
(\key new old -> Dict.insert key (List.append new old))
|
||||
Dict.insert
|
||||
locationsToIgnore
|
||||
context.locationsToIgnoreForUsed
|
||||
@ -502,20 +518,6 @@ markAllAsUsed names scopes =
|
||||
{ headScope | used = Set.union names headScope.used } :: restOfScopes
|
||||
|
||||
|
||||
|
||||
-- EXPRESSION EXIT VISITOR
|
||||
|
||||
|
||||
expressionExitVisitor : Node Expression -> Context -> ( List (Rule.Error {}), Context )
|
||||
expressionExitVisitor node context =
|
||||
case RangeDict.get (Node.range node) context.scopesToCreate of
|
||||
Just _ ->
|
||||
report context
|
||||
|
||||
Nothing ->
|
||||
( [], context )
|
||||
|
||||
|
||||
report : Context -> ( List (Rule.Error {}), Context )
|
||||
report context =
|
||||
case context.scopes of
|
||||
@ -523,23 +525,7 @@ report context =
|
||||
let
|
||||
( errors, remainingUsed ) =
|
||||
List.foldl
|
||||
(\declared ( errors_, remainingUsed_ ) ->
|
||||
if Set.member declared.name headScope.usedRecursively then
|
||||
-- If variable was used as a recursive argument
|
||||
if Set.member declared.name remainingUsed_ then
|
||||
-- If variable was used somewhere else as well
|
||||
( errors_, Set.remove declared.name remainingUsed_ )
|
||||
|
||||
else
|
||||
-- If variable was used ONLY as a recursive argument
|
||||
( recursiveParameterError headScope.functionName declared :: errors_, Set.remove declared.name remainingUsed_ )
|
||||
|
||||
else if Set.member declared.name remainingUsed_ then
|
||||
( errors_, Set.remove declared.name remainingUsed_ )
|
||||
|
||||
else
|
||||
( errorsForValue declared :: errors_, remainingUsed_ )
|
||||
)
|
||||
(findErrorsAndVariablesNotPartOfScope headScope)
|
||||
( [], headScope.used )
|
||||
headScope.declared
|
||||
in
|
||||
@ -554,6 +540,25 @@ report context =
|
||||
( [], context )
|
||||
|
||||
|
||||
findErrorsAndVariablesNotPartOfScope : Scope -> Declared -> ( List (Rule.Error {}), Set String ) -> ( List (Rule.Error {}), Set String )
|
||||
findErrorsAndVariablesNotPartOfScope scope declared ( errors_, remainingUsed_ ) =
|
||||
if Set.member declared.name scope.usedRecursively then
|
||||
-- If variable was used as a recursive argument
|
||||
if Set.member declared.name remainingUsed_ then
|
||||
-- If variable was used somewhere else as well
|
||||
( errors_, Set.remove declared.name remainingUsed_ )
|
||||
|
||||
else
|
||||
-- If variable was used ONLY as a recursive argument
|
||||
( recursiveParameterError scope.functionName declared :: errors_, Set.remove declared.name remainingUsed_ )
|
||||
|
||||
else if Set.member declared.name remainingUsed_ then
|
||||
( errors_, Set.remove declared.name remainingUsed_ )
|
||||
|
||||
else
|
||||
( errorsForValue declared :: errors_, remainingUsed_ )
|
||||
|
||||
|
||||
errorsForValue : Declared -> Rule.Error {}
|
||||
errorsForValue { name, kind, range, source, fix } =
|
||||
Rule.errorWithFix
|
||||
|
@ -2,9 +2,6 @@ module NoUnused.Patterns exposing (rule)
|
||||
|
||||
{-| Report useless patterns and pattern values that are not used.
|
||||
|
||||
|
||||
# Rule
|
||||
|
||||
@docs rule
|
||||
|
||||
-}
|
||||
@ -16,7 +13,6 @@ import Elm.Syntax.Pattern as Pattern exposing (Pattern)
|
||||
import Elm.Syntax.Range as Range exposing (Range)
|
||||
import Elm.Writer as Writer
|
||||
import NoUnused.Patterns.NameVisitor as NameVisitor
|
||||
import NoUnused.RangeDict as RangeDict exposing (RangeDict)
|
||||
import Review.Fix as Fix exposing (Fix)
|
||||
import Review.Rule as Rule exposing (Rule)
|
||||
import Set exposing (Set)
|
||||
@ -69,14 +65,14 @@ rule =
|
||||
Rule.newModuleRuleSchema "NoUnused.Patterns" initialContext
|
||||
|> Rule.withExpressionEnterVisitor expressionEnterVisitor
|
||||
|> Rule.withExpressionExitVisitor expressionExitVisitor
|
||||
|> Rule.withCaseBranchEnterVisitor caseBranchEnterVisitor
|
||||
|> Rule.withCaseBranchExitVisitor caseBranchExitVisitor
|
||||
|> NameVisitor.withValueVisitor valueVisitor
|
||||
|> Rule.fromModuleRuleSchema
|
||||
|
||||
|
||||
type alias Context =
|
||||
{ scopes : List Scope
|
||||
, scopesToCreate : RangeDict (List FoundPattern)
|
||||
}
|
||||
List Scope
|
||||
|
||||
|
||||
type alias Scope =
|
||||
@ -102,9 +98,7 @@ type FoundPattern
|
||||
|
||||
initialContext : Context
|
||||
initialContext =
|
||||
{ scopes = []
|
||||
, scopesToCreate = RangeDict.empty
|
||||
}
|
||||
[]
|
||||
|
||||
|
||||
|
||||
@ -113,21 +107,6 @@ initialContext =
|
||||
|
||||
expressionEnterVisitor : Node Expression -> Context -> ( List nothing, Context )
|
||||
expressionEnterVisitor node context =
|
||||
let
|
||||
newContext : Context
|
||||
newContext =
|
||||
case RangeDict.get (Node.range node) context.scopesToCreate of
|
||||
Just declared ->
|
||||
{ context | scopes = { declared = declared, used = Set.empty } :: context.scopes }
|
||||
|
||||
Nothing ->
|
||||
context
|
||||
in
|
||||
expressionEnterVisitorHelp node newContext
|
||||
|
||||
|
||||
expressionEnterVisitorHelp : Node Expression -> Context -> ( List nothing, Context )
|
||||
expressionEnterVisitorHelp node context =
|
||||
case Node.value node of
|
||||
Expression.LetExpression { declarations } ->
|
||||
let
|
||||
@ -141,27 +120,10 @@ expressionEnterVisitorHelp node context =
|
||||
findPatterns Destructuring pattern
|
||||
in
|
||||
( []
|
||||
, { context
|
||||
| scopes =
|
||||
{ declared = List.concatMap findPatternsInLetDeclaration declarations
|
||||
, used = Set.empty
|
||||
}
|
||||
:: context.scopes
|
||||
|
||||
-- Will only be used to remove on exit, we are already adding the declared patterns to the scope above
|
||||
, scopesToCreate = RangeDict.insert (Node.range node) [] context.scopesToCreate
|
||||
}
|
||||
)
|
||||
|
||||
Expression.CaseExpression { cases } ->
|
||||
( []
|
||||
, { context
|
||||
| scopesToCreate =
|
||||
List.foldl
|
||||
(\( pattern, expr ) scopesToCreate -> RangeDict.insert (Node.range expr) (findPatterns Matching pattern) scopesToCreate)
|
||||
context.scopesToCreate
|
||||
cases
|
||||
, { declared = List.concatMap findPatternsInLetDeclaration declarations
|
||||
, used = Set.empty
|
||||
}
|
||||
:: context
|
||||
)
|
||||
|
||||
_ ->
|
||||
@ -170,17 +132,32 @@ expressionEnterVisitorHelp node context =
|
||||
|
||||
expressionExitVisitor : Node Expression -> Context -> ( List (Rule.Error {}), Context )
|
||||
expressionExitVisitor node context =
|
||||
case RangeDict.get (Node.range node) context.scopesToCreate of
|
||||
Just _ ->
|
||||
case Node.value node of
|
||||
Expression.LetExpression _ ->
|
||||
report context
|
||||
|
||||
Nothing ->
|
||||
_ ->
|
||||
( [], context )
|
||||
|
||||
|
||||
caseBranchEnterVisitor : a -> ( Node Pattern, Node Expression ) -> Context -> ( List nothing, Context )
|
||||
caseBranchEnterVisitor _ ( pattern, _ ) context =
|
||||
( []
|
||||
, { declared = findPatterns Matching pattern
|
||||
, used = Set.empty
|
||||
}
|
||||
:: context
|
||||
)
|
||||
|
||||
|
||||
caseBranchExitVisitor : a -> b -> Context -> ( List (Rule.Error {}), Context )
|
||||
caseBranchExitVisitor _ _ context =
|
||||
report context
|
||||
|
||||
|
||||
report : Context -> ( List (Rule.Error {}), Context )
|
||||
report context =
|
||||
case context.scopes of
|
||||
case context of
|
||||
headScope :: restOfScopes ->
|
||||
let
|
||||
{ singles, records, simplifiablePatterns } =
|
||||
@ -223,7 +200,7 @@ report context =
|
||||
( errors
|
||||
, List.foldl
|
||||
useValue
|
||||
{ context | scopes = restOfScopes }
|
||||
restOfScopes
|
||||
(Set.toList nonUsedVars)
|
||||
)
|
||||
|
||||
@ -687,12 +664,12 @@ errorsForValue name range context =
|
||||
|
||||
useValue : String -> Context -> Context
|
||||
useValue name context =
|
||||
case context.scopes of
|
||||
case context of
|
||||
[] ->
|
||||
context
|
||||
|
||||
headScope :: restOfScopes ->
|
||||
{ context | scopes = { headScope | used = Set.insert name headScope.used } :: restOfScopes }
|
||||
{ headScope | used = Set.insert name headScope.used } :: restOfScopes
|
||||
|
||||
|
||||
isNodeInContext : Context -> Node String -> Bool
|
||||
@ -702,7 +679,7 @@ isNodeInContext context (Node _ value) =
|
||||
|
||||
isUnused : String -> Context -> Bool
|
||||
isUnused name context =
|
||||
case context.scopes of
|
||||
case context of
|
||||
[] ->
|
||||
False
|
||||
|
||||
|
@ -1,59 +0,0 @@
|
||||
module NoUnused.RangeDict exposing (RangeDict, empty, fromList, get, insert, insertAll, member, singleton)
|
||||
|
||||
import Dict exposing (Dict)
|
||||
import Elm.Syntax.Range exposing (Range)
|
||||
|
||||
|
||||
type alias RangeDict v =
|
||||
Dict String v
|
||||
|
||||
|
||||
empty : RangeDict v
|
||||
empty =
|
||||
Dict.empty
|
||||
|
||||
|
||||
insert : Range -> v -> RangeDict v -> RangeDict v
|
||||
insert range =
|
||||
Dict.insert (rangeAsString range)
|
||||
|
||||
|
||||
insertAll : List ( Range, v ) -> RangeDict v -> RangeDict v
|
||||
insertAll list dict =
|
||||
List.foldl
|
||||
(\( range, v ) -> insert range v)
|
||||
dict
|
||||
list
|
||||
|
||||
|
||||
singleton : Range -> v -> RangeDict v
|
||||
singleton range value =
|
||||
Dict.singleton (rangeAsString range) value
|
||||
|
||||
|
||||
fromList : List ( Range, v ) -> RangeDict v
|
||||
fromList values =
|
||||
values
|
||||
|> List.map (Tuple.mapFirst rangeAsString)
|
||||
|> Dict.fromList
|
||||
|
||||
|
||||
get : Range -> RangeDict v -> Maybe v
|
||||
get range =
|
||||
Dict.get (rangeAsString range)
|
||||
|
||||
|
||||
member : Range -> RangeDict v -> Bool
|
||||
member range =
|
||||
Dict.member (rangeAsString range)
|
||||
|
||||
|
||||
rangeAsString : Range -> String
|
||||
rangeAsString range =
|
||||
[ range.start.row
|
||||
, range.start.column
|
||||
, range.end.row
|
||||
, range.end.column
|
||||
]
|
||||
|> List.map String.fromInt
|
||||
|> String.join "_"
|
@ -2,9 +2,6 @@ module NoUnused.Variables exposing (rule)
|
||||
|
||||
{-| Report variables or types that are declared or imported but never used inside of a module.
|
||||
|
||||
|
||||
# Rule
|
||||
|
||||
@docs rule
|
||||
|
||||
-}
|
||||
@ -24,7 +21,6 @@ import Elm.Syntax.Type
|
||||
import Elm.Syntax.TypeAlias exposing (TypeAlias)
|
||||
import Elm.Syntax.TypeAnnotation as TypeAnnotation exposing (TypeAnnotation)
|
||||
import NoUnused.NonemptyList as NonemptyList exposing (Nonempty)
|
||||
import NoUnused.RangeDict as RangeDict exposing (RangeDict)
|
||||
import Review.Fix as Fix exposing (Fix)
|
||||
import Review.ModuleNameLookupTable as ModuleNameLookupTable exposing (ModuleNameLookupTable)
|
||||
import Review.Project.Dependency as Dependency exposing (Dependency)
|
||||
@ -90,9 +86,13 @@ moduleVisitor schema =
|
||||
|> Rule.withModuleDefinitionVisitor moduleDefinitionVisitor
|
||||
|> Rule.withImportVisitor importVisitor
|
||||
|> Rule.withDeclarationListVisitor declarationListVisitor
|
||||
|> Rule.withDeclarationEnterVisitor declarationVisitor
|
||||
|> Rule.withDeclarationEnterVisitor declarationEnterVisitor
|
||||
|> Rule.withDeclarationExitVisitor declarationExitVisitor
|
||||
|> Rule.withExpressionEnterVisitor expressionEnterVisitor
|
||||
|> Rule.withExpressionExitVisitor expressionExitVisitor
|
||||
|> Rule.withLetDeclarationEnterVisitor letDeclarationEnterVisitor
|
||||
|> Rule.withLetDeclarationExitVisitor letDeclarationExitVisitor
|
||||
|> Rule.withCaseBranchEnterVisitor caseBranchEnterVisitor
|
||||
|> Rule.withCaseBranchExitVisitor caseBranchExitVisitor
|
||||
|> Rule.withFinalModuleEvaluation finalEvaluation
|
||||
|
||||
|
||||
@ -106,7 +106,6 @@ type alias ModuleContext =
|
||||
{ lookupTable : ModuleNameLookupTable
|
||||
, scopes : Nonempty Scope
|
||||
, inTheDeclarationOf : List String
|
||||
, declarations : RangeDict String
|
||||
, exposesEverything : Bool
|
||||
, isApplication : Bool
|
||||
, constructorNameToTypeName : Dict String String
|
||||
@ -115,7 +114,7 @@ type alias ModuleContext =
|
||||
, usedModules : Set ( ModuleName, ModuleName )
|
||||
, unusedImportedCustomTypes : Dict String ImportedCustomType
|
||||
, importedCustomTypeLookup : Dict String String
|
||||
, localCustomTypes : Dict String CustomTypeData
|
||||
, localTypes : Dict String TypeData
|
||||
, customTypes : Dict ModuleName (Dict String (List String))
|
||||
}
|
||||
|
||||
@ -130,13 +129,19 @@ type alias DeclaredModule =
|
||||
}
|
||||
|
||||
|
||||
type alias CustomTypeData =
|
||||
type alias TypeData =
|
||||
{ under : Range
|
||||
, kind : TypeKind
|
||||
, rangeToRemove : Range
|
||||
, variants : List String
|
||||
}
|
||||
|
||||
|
||||
type TypeKind
|
||||
= CustomTypeKind
|
||||
| TypeAliasKind
|
||||
|
||||
|
||||
type alias ModuleThatExposesEverything =
|
||||
{ name : ModuleName
|
||||
, alias : Maybe String
|
||||
@ -156,6 +161,7 @@ type DeclaredModuleType
|
||||
type alias Scope =
|
||||
{ declared : Dict String VariableInfo
|
||||
, used : Dict ModuleName (Set String)
|
||||
, namesToIgnore : Set String
|
||||
}
|
||||
|
||||
|
||||
@ -194,7 +200,6 @@ fromProjectToModule =
|
||||
{ lookupTable = lookupTable
|
||||
, scopes = NonemptyList.fromElement emptyScope
|
||||
, inTheDeclarationOf = []
|
||||
, declarations = Dict.empty
|
||||
, exposesEverything = False
|
||||
, isApplication = isApplication
|
||||
, constructorNameToTypeName = Dict.empty
|
||||
@ -203,7 +208,7 @@ fromProjectToModule =
|
||||
, usedModules = Set.empty
|
||||
, unusedImportedCustomTypes = Dict.empty
|
||||
, importedCustomTypeLookup = Dict.empty
|
||||
, localCustomTypes = Dict.empty
|
||||
, localTypes = Dict.empty
|
||||
, customTypes = customTypes
|
||||
}
|
||||
)
|
||||
@ -215,7 +220,7 @@ fromModuleToProject =
|
||||
Rule.initContextCreator
|
||||
(\metadata moduleContext ->
|
||||
{ customTypes =
|
||||
moduleContext.localCustomTypes
|
||||
moduleContext.localTypes
|
||||
|> Dict.map (\_ customType -> customType.variants)
|
||||
|> Dict.singleton (Rule.moduleNameFromMetadata metadata)
|
||||
|
||||
@ -237,6 +242,7 @@ emptyScope : Scope
|
||||
emptyScope =
|
||||
{ declared = Dict.empty
|
||||
, used = Dict.empty
|
||||
, namesToIgnore = Set.empty
|
||||
}
|
||||
|
||||
|
||||
@ -574,22 +580,7 @@ moduleAliasRange (Node _ { moduleName }) range =
|
||||
|
||||
|
||||
expressionEnterVisitor : Node Expression -> ModuleContext -> ( List (Error {}), ModuleContext )
|
||||
expressionEnterVisitor node context =
|
||||
let
|
||||
newContext : ModuleContext
|
||||
newContext =
|
||||
case RangeDict.get (Node.range node) context.declarations of
|
||||
Just functionName ->
|
||||
{ context | inTheDeclarationOf = functionName :: context.inTheDeclarationOf }
|
||||
|
||||
Nothing ->
|
||||
context
|
||||
in
|
||||
expressionEnterVisitorHelp node newContext
|
||||
|
||||
|
||||
expressionEnterVisitorHelp : Node Expression -> ModuleContext -> ( List (Error {}), ModuleContext )
|
||||
expressionEnterVisitorHelp (Node range value) context =
|
||||
expressionEnterVisitor (Node range value) context =
|
||||
case value of
|
||||
Expression.FunctionOrValue [] name ->
|
||||
case Dict.get name context.constructorNameToTypeName of
|
||||
@ -627,98 +618,8 @@ expressionEnterVisitorHelp (Node range value) context =
|
||||
Expression.PrefixOperator name ->
|
||||
( [], markValueAsUsed name context )
|
||||
|
||||
Expression.LetExpression { declarations, expression } ->
|
||||
let
|
||||
letBlockContext : LetBlockContext
|
||||
letBlockContext =
|
||||
if List.length declarations == 1 then
|
||||
HasNoOtherDeclarations <| rangeUpUntil range (Node.range expression |> .start)
|
||||
|
||||
else
|
||||
HasMultipleDeclarations
|
||||
in
|
||||
List.foldl
|
||||
(\declaration ( errors, foldContext ) ->
|
||||
case Node.value declaration of
|
||||
Expression.LetFunction function ->
|
||||
let
|
||||
namesUsedInArgumentPatterns : { types : List String, modules : List ( ModuleName, ModuleName ) }
|
||||
namesUsedInArgumentPatterns =
|
||||
function.declaration
|
||||
|> Node.value
|
||||
|> .arguments
|
||||
|> List.map (getUsedVariablesFromPattern context)
|
||||
|> foldUsedTypesAndModules
|
||||
|
||||
markAsInTheDeclarationOf : a -> { b | declarations : RangeDict a } -> { b | declarations : RangeDict a }
|
||||
markAsInTheDeclarationOf name ctx =
|
||||
{ ctx
|
||||
| declarations =
|
||||
RangeDict.insert
|
||||
(function.declaration |> Node.value |> .expression |> Node.range)
|
||||
name
|
||||
ctx.declarations
|
||||
}
|
||||
in
|
||||
( errors
|
||||
, List.foldl markValueAsUsed foldContext namesUsedInArgumentPatterns.types
|
||||
|> markAllModulesAsUsed namesUsedInArgumentPatterns.modules
|
||||
|> registerFunction letBlockContext function
|
||||
|> markAsInTheDeclarationOf (function.declaration |> Node.value |> .name |> Node.value)
|
||||
)
|
||||
|
||||
Expression.LetDestructuring pattern _ ->
|
||||
case removeParens pattern of
|
||||
Node wildCardRange Pattern.AllPattern ->
|
||||
( Rule.errorWithFix
|
||||
{ message = "Value assigned to `_` is unused"
|
||||
, details =
|
||||
[ "This value has been assigned to a wildcard, which makes the value unusable. You should remove it at the location I pointed at."
|
||||
]
|
||||
}
|
||||
wildCardRange
|
||||
[ Fix.removeRange (letDeclarationToRemoveRange letBlockContext (Node.range declaration)) ]
|
||||
:: errors
|
||||
, foldContext
|
||||
)
|
||||
|
||||
Node unitPattern Pattern.UnitPattern ->
|
||||
( Rule.errorWithFix
|
||||
{ message = "Unit value is unused"
|
||||
, details =
|
||||
[ "This value has no data, which makes the value unusable. You should remove it at the location I pointed at."
|
||||
]
|
||||
}
|
||||
unitPattern
|
||||
[ Fix.removeRange (letDeclarationToRemoveRange letBlockContext (Node.range declaration)) ]
|
||||
:: errors
|
||||
, foldContext
|
||||
)
|
||||
|
||||
_ ->
|
||||
let
|
||||
namesUsedInPattern : { types : List String, modules : List ( ModuleName, ModuleName ) }
|
||||
namesUsedInPattern =
|
||||
getUsedVariablesFromPattern context pattern
|
||||
in
|
||||
( if not (introducesVariable pattern) then
|
||||
Rule.errorWithFix
|
||||
{ message = "Pattern doesn't introduce any variables"
|
||||
, details =
|
||||
[ "This value has been computed but isn't assigned to any variable, which makes the value unusable. You should remove it at the location I pointed at." ]
|
||||
}
|
||||
(Node.range pattern)
|
||||
[ Fix.removeRange (letDeclarationToRemoveRange letBlockContext (Node.range declaration)) ]
|
||||
:: errors
|
||||
|
||||
else
|
||||
errors
|
||||
, List.foldl markValueAsUsed foldContext namesUsedInPattern.types
|
||||
|> markAllModulesAsUsed namesUsedInPattern.modules
|
||||
)
|
||||
)
|
||||
( [], { context | scopes = NonemptyList.cons emptyScope context.scopes } )
|
||||
declarations
|
||||
Expression.RecordUpdateExpression expr _ ->
|
||||
( [], markValueAsUsed (Node.value expr) context )
|
||||
|
||||
Expression.LambdaExpression { args } ->
|
||||
let
|
||||
@ -731,12 +632,155 @@ expressionEnterVisitorHelp (Node range value) context =
|
||||
( []
|
||||
, List.foldl markValueAsUsed context namesUsedInArgumentPatterns.types
|
||||
|> markAllModulesAsUsed namesUsedInArgumentPatterns.modules
|
||||
|> registerParameters args
|
||||
)
|
||||
|
||||
Expression.CaseExpression { cases } ->
|
||||
let
|
||||
usedVariables : { types : List String, modules : List ( ModuleName, ModuleName ) }
|
||||
usedVariables =
|
||||
cases
|
||||
|> List.map
|
||||
(\( patternNode, _ ) ->
|
||||
getUsedVariablesFromPattern context patternNode
|
||||
)
|
||||
|> foldUsedTypesAndModules
|
||||
in
|
||||
( []
|
||||
, List.foldl
|
||||
markValueAsUsed
|
||||
context
|
||||
usedVariables.types
|
||||
|> markAllModulesAsUsed usedVariables.modules
|
||||
)
|
||||
|
||||
_ ->
|
||||
( [], context )
|
||||
|
||||
|
||||
letDeclarationEnterVisitor : Node Expression.LetBlock -> Node Expression.LetDeclaration -> ModuleContext -> ( List (Error {}), ModuleContext )
|
||||
letDeclarationEnterVisitor (Node range { declarations, expression }) declaration context =
|
||||
let
|
||||
letBlockContext : LetBlockContext
|
||||
letBlockContext =
|
||||
if List.length declarations == 1 then
|
||||
HasNoOtherDeclarations <| rangeUpUntil range (Node.range expression |> .start)
|
||||
|
||||
else
|
||||
HasMultipleDeclarations
|
||||
in
|
||||
case Node.value declaration of
|
||||
Expression.LetFunction function ->
|
||||
let
|
||||
functionDeclaration : FunctionImplementation
|
||||
functionDeclaration =
|
||||
Node.value function.declaration
|
||||
|
||||
namesUsedInArgumentPatterns : { types : List String, modules : List ( ModuleName, ModuleName ) }
|
||||
namesUsedInArgumentPatterns =
|
||||
functionDeclaration.arguments
|
||||
|> List.map (getUsedVariablesFromPattern context)
|
||||
|> foldUsedTypesAndModules
|
||||
|
||||
namesToIgnore : Set String
|
||||
namesToIgnore =
|
||||
List.concatMap getDeclaredParametersFromPattern functionDeclaration.arguments
|
||||
|> Set.fromList
|
||||
|
||||
newContext : ModuleContext
|
||||
newContext =
|
||||
{ context | inTheDeclarationOf = Node.value functionDeclaration.name :: context.inTheDeclarationOf }
|
||||
|> markAllAsUsed namesUsedInArgumentPatterns.types
|
||||
|> markAllModulesAsUsed namesUsedInArgumentPatterns.modules
|
||||
|> registerFunction letBlockContext function
|
||||
in
|
||||
( []
|
||||
, { newContext
|
||||
| scopes = NonemptyList.cons { declared = Dict.empty, used = Dict.empty, namesToIgnore = namesToIgnore } newContext.scopes
|
||||
}
|
||||
)
|
||||
|
||||
Expression.LetDestructuring pattern _ ->
|
||||
case removeParens pattern of
|
||||
Node wildCardRange Pattern.AllPattern ->
|
||||
( [ Rule.errorWithFix
|
||||
{ message = "Value assigned to `_` is unused"
|
||||
, details =
|
||||
[ "This value has been assigned to a wildcard, which makes the value unusable. You should remove it at the location I pointed at."
|
||||
]
|
||||
}
|
||||
wildCardRange
|
||||
[ Fix.removeRange (letDeclarationToRemoveRange letBlockContext (Node.range declaration)) ]
|
||||
]
|
||||
, context
|
||||
)
|
||||
|
||||
Node unitPattern Pattern.UnitPattern ->
|
||||
( [ Rule.errorWithFix
|
||||
{ message = "Unit value is unused"
|
||||
, details =
|
||||
[ "This value has no data, which makes the value unusable. You should remove it at the location I pointed at."
|
||||
]
|
||||
}
|
||||
unitPattern
|
||||
[ Fix.removeRange (letDeclarationToRemoveRange letBlockContext (Node.range declaration)) ]
|
||||
]
|
||||
, context
|
||||
)
|
||||
|
||||
_ ->
|
||||
let
|
||||
namesUsedInPattern : { types : List String, modules : List ( ModuleName, ModuleName ) }
|
||||
namesUsedInPattern =
|
||||
getUsedVariablesFromPattern context pattern
|
||||
in
|
||||
( if not (introducesVariable pattern) then
|
||||
[ Rule.errorWithFix
|
||||
{ message = "Pattern doesn't introduce any variables"
|
||||
, details =
|
||||
[ "This value has been computed but isn't assigned to any variable, which makes the value unusable. You should remove it at the location I pointed at." ]
|
||||
}
|
||||
(Node.range pattern)
|
||||
[ Fix.removeRange (letDeclarationToRemoveRange letBlockContext (Node.range declaration)) ]
|
||||
]
|
||||
|
||||
else
|
||||
[]
|
||||
, List.foldl markValueAsUsed context namesUsedInPattern.types
|
||||
|> markAllModulesAsUsed namesUsedInPattern.modules
|
||||
)
|
||||
|
||||
|
||||
letDeclarationExitVisitor : a -> Node Expression.LetDeclaration -> ModuleContext -> ( List (Error {}), ModuleContext )
|
||||
letDeclarationExitVisitor _ declaration context =
|
||||
case Node.value declaration of
|
||||
Expression.LetFunction _ ->
|
||||
makeReport { context | inTheDeclarationOf = List.drop 1 context.inTheDeclarationOf }
|
||||
|
||||
Expression.LetDestructuring _ _ ->
|
||||
( [], context )
|
||||
|
||||
|
||||
caseBranchEnterVisitor : a -> ( Node Pattern, b ) -> ModuleContext -> ( List nothing, ModuleContext )
|
||||
caseBranchEnterVisitor _ ( pattern, _ ) context =
|
||||
( []
|
||||
, { context
|
||||
| scopes =
|
||||
NonemptyList.cons
|
||||
{ declared = Dict.empty
|
||||
, used = Dict.empty
|
||||
, namesToIgnore = Set.fromList (getDeclaredParametersFromPattern pattern)
|
||||
}
|
||||
context.scopes
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
caseBranchExitVisitor : a -> ( Node Pattern, b ) -> ModuleContext -> ( List (Rule.Error {}), ModuleContext )
|
||||
caseBranchExitVisitor _ _ context =
|
||||
makeReport context
|
||||
|
||||
|
||||
letDeclarationToRemoveRange : LetBlockContext -> Range -> Range
|
||||
letDeclarationToRemoveRange letBlockContext range =
|
||||
case letBlockContext of
|
||||
@ -759,59 +803,6 @@ removeParens node =
|
||||
node
|
||||
|
||||
|
||||
expressionExitVisitor : Node Expression -> ModuleContext -> ( List (Error {}), ModuleContext )
|
||||
expressionExitVisitor node context =
|
||||
let
|
||||
newContext : ModuleContext
|
||||
newContext =
|
||||
if RangeDict.member (Node.range node) context.declarations then
|
||||
{ context | inTheDeclarationOf = List.drop 1 context.inTheDeclarationOf }
|
||||
|
||||
else
|
||||
context
|
||||
in
|
||||
expressionExitVisitorHelp node newContext
|
||||
|
||||
|
||||
expressionExitVisitorHelp : Node Expression -> ModuleContext -> ( List (Error {}), ModuleContext )
|
||||
expressionExitVisitorHelp node context =
|
||||
case Node.value node of
|
||||
Expression.RecordUpdateExpression expr _ ->
|
||||
( [], markValueAsUsed (Node.value expr) context )
|
||||
|
||||
Expression.CaseExpression { cases } ->
|
||||
let
|
||||
usedVariables : { types : List String, modules : List ( ModuleName, ModuleName ) }
|
||||
usedVariables =
|
||||
cases
|
||||
|> List.map
|
||||
(\( patternNode, _ ) ->
|
||||
getUsedVariablesFromPattern context patternNode
|
||||
)
|
||||
|> foldUsedTypesAndModules
|
||||
in
|
||||
( []
|
||||
, List.foldl markValueAsUsed context usedVariables.types
|
||||
|> markAllModulesAsUsed usedVariables.modules
|
||||
)
|
||||
|
||||
Expression.LetExpression _ ->
|
||||
let
|
||||
( errors, remainingUsed ) =
|
||||
makeReport (NonemptyList.head context.scopes)
|
||||
|
||||
contextWithPoppedScope : ModuleContext
|
||||
contextWithPoppedScope =
|
||||
{ context | scopes = NonemptyList.pop context.scopes }
|
||||
in
|
||||
( errors
|
||||
, markAllAsUsed remainingUsed contextWithPoppedScope
|
||||
)
|
||||
|
||||
_ ->
|
||||
( [], context )
|
||||
|
||||
|
||||
getUsedVariablesFromPattern : ModuleContext -> Node Pattern -> { types : List String, modules : List ( ModuleName, ModuleName ) }
|
||||
getUsedVariablesFromPattern context patternNode =
|
||||
{ types = getUsedTypesFromPattern context.constructorNameToTypeName patternNode
|
||||
@ -819,6 +810,49 @@ getUsedVariablesFromPattern context patternNode =
|
||||
}
|
||||
|
||||
|
||||
getDeclaredParametersFromPattern : Node Pattern -> List String
|
||||
getDeclaredParametersFromPattern node =
|
||||
getDeclaredParametersFromPatternHelp [ node ] []
|
||||
|
||||
|
||||
getDeclaredParametersFromPatternHelp : List (Node Pattern) -> List String -> List String
|
||||
getDeclaredParametersFromPatternHelp nodes acc =
|
||||
case nodes of
|
||||
(Node _ node) :: tail ->
|
||||
case node of
|
||||
Pattern.ParenthesizedPattern pattern ->
|
||||
getDeclaredParametersFromPatternHelp (pattern :: tail) acc
|
||||
|
||||
Pattern.VarPattern name ->
|
||||
getDeclaredParametersFromPatternHelp tail (name :: acc)
|
||||
|
||||
Pattern.AsPattern pattern (Node _ asName) ->
|
||||
getDeclaredParametersFromPatternHelp (pattern :: tail) (asName :: acc)
|
||||
|
||||
Pattern.RecordPattern fields ->
|
||||
getDeclaredParametersFromPatternHelp
|
||||
tail
|
||||
(List.append (List.map Node.value fields) acc)
|
||||
|
||||
Pattern.TuplePattern patterns ->
|
||||
getDeclaredParametersFromPatternHelp (patterns ++ tail) acc
|
||||
|
||||
Pattern.NamedPattern _ patterns ->
|
||||
getDeclaredParametersFromPatternHelp (patterns ++ tail) acc
|
||||
|
||||
Pattern.UnConsPattern left right ->
|
||||
getDeclaredParametersFromPatternHelp (left :: right :: tail) acc
|
||||
|
||||
Pattern.ListPattern patterns ->
|
||||
getDeclaredParametersFromPatternHelp (patterns ++ tail) acc
|
||||
|
||||
_ ->
|
||||
getDeclaredParametersFromPatternHelp tail acc
|
||||
|
||||
[] ->
|
||||
acc
|
||||
|
||||
|
||||
getUsedTypesFromPattern : Dict String String -> Node Pattern -> List String
|
||||
getUsedTypesFromPattern constructorNameToTypeName patternNode =
|
||||
case Node.value patternNode of
|
||||
@ -983,51 +1017,6 @@ registerTypes node context =
|
||||
context
|
||||
|
||||
|
||||
registerTypeAlias : Range -> TypeAlias -> ModuleContext -> ModuleContext
|
||||
registerTypeAlias range { name, typeAnnotation } context =
|
||||
let
|
||||
contextWithRemovedShadowedImports : ModuleContext
|
||||
contextWithRemovedShadowedImports =
|
||||
case Node.value typeAnnotation of
|
||||
TypeAnnotation.Record _ ->
|
||||
{ context | importedCustomTypeLookup = Dict.remove (Node.value name) context.importedCustomTypeLookup }
|
||||
|
||||
_ ->
|
||||
context
|
||||
|
||||
-- TODO Rename
|
||||
typeAlias : CustomTypeData
|
||||
typeAlias =
|
||||
{ under = Node.range name
|
||||
, rangeToRemove = untilStartOfNextLine range
|
||||
, variants = []
|
||||
}
|
||||
in
|
||||
case Node.value typeAnnotation of
|
||||
TypeAnnotation.Record _ ->
|
||||
if context.exposesEverything then
|
||||
contextWithRemovedShadowedImports
|
||||
|
||||
else
|
||||
registerVariable
|
||||
{ typeName = "Type"
|
||||
, under = Node.range name
|
||||
, rangeToRemove = Just (untilStartOfNextLine range)
|
||||
, warning = ""
|
||||
}
|
||||
(Node.value name)
|
||||
contextWithRemovedShadowedImports
|
||||
|
||||
_ ->
|
||||
{ contextWithRemovedShadowedImports
|
||||
| localCustomTypes =
|
||||
Dict.insert
|
||||
(Node.value name)
|
||||
typeAlias
|
||||
contextWithRemovedShadowedImports.localCustomTypes
|
||||
}
|
||||
|
||||
|
||||
registerCustomType : Range -> Elm.Syntax.Type.Type -> ModuleContext -> ModuleContext
|
||||
registerCustomType range { name, constructors } context =
|
||||
let
|
||||
@ -1045,29 +1034,83 @@ registerCustomType range { name, constructors } context =
|
||||
|> List.map (\constructorName -> ( constructorName, typeName ))
|
||||
|> Dict.fromList
|
||||
|
||||
customType : CustomTypeData
|
||||
customType : TypeData
|
||||
customType =
|
||||
{ under = Node.range name
|
||||
{ kind = CustomTypeKind
|
||||
, under = Node.range name
|
||||
, rangeToRemove = untilStartOfNextLine range
|
||||
, variants = constructorNames
|
||||
}
|
||||
in
|
||||
{ context
|
||||
| localCustomTypes =
|
||||
| localTypes =
|
||||
Dict.insert
|
||||
(Node.value name)
|
||||
customType
|
||||
context.localCustomTypes
|
||||
context.localTypes
|
||||
, constructorNameToTypeName = Dict.union constructorsForType context.constructorNameToTypeName
|
||||
}
|
||||
|
||||
|
||||
registerTypeAlias : Range -> TypeAlias -> ModuleContext -> ModuleContext
|
||||
registerTypeAlias range { name, typeAnnotation } context =
|
||||
let
|
||||
newContext : ModuleContext
|
||||
newContext =
|
||||
case Node.value typeAnnotation of
|
||||
TypeAnnotation.Record _ ->
|
||||
{ context | importedCustomTypeLookup = Dict.remove (Node.value name) context.importedCustomTypeLookup }
|
||||
|
||||
-- DECLARATION VISITOR
|
||||
_ ->
|
||||
context
|
||||
in
|
||||
case Node.value typeAnnotation of
|
||||
TypeAnnotation.Record _ ->
|
||||
if context.exposesEverything then
|
||||
newContext
|
||||
|
||||
else
|
||||
registerVariable
|
||||
{ typeName = "Type"
|
||||
, under = Node.range name
|
||||
, rangeToRemove = Just (untilStartOfNextLine range)
|
||||
, warning = ""
|
||||
}
|
||||
(Node.value name)
|
||||
newContext
|
||||
|
||||
_ ->
|
||||
let
|
||||
typeAlias : TypeData
|
||||
typeAlias =
|
||||
{ kind = TypeAliasKind
|
||||
, under = Node.range name
|
||||
, rangeToRemove = untilStartOfNextLine range
|
||||
, variants =
|
||||
case Node.value typeAnnotation of
|
||||
TypeAnnotation.Record _ ->
|
||||
[ Node.value name ]
|
||||
|
||||
_ ->
|
||||
[]
|
||||
}
|
||||
|
||||
localCustomTypes : Dict String TypeData
|
||||
localCustomTypes =
|
||||
Dict.insert
|
||||
(Node.value name)
|
||||
typeAlias
|
||||
newContext.localTypes
|
||||
in
|
||||
{ newContext | localTypes = localCustomTypes }
|
||||
|
||||
|
||||
declarationVisitor : Node Declaration -> ModuleContext -> ( List (Error {}), ModuleContext )
|
||||
declarationVisitor node context =
|
||||
|
||||
-- DECLARATION ENTER VISITOR
|
||||
|
||||
|
||||
declarationEnterVisitor : Node Declaration -> ModuleContext -> ( List (Error {}), ModuleContext )
|
||||
declarationEnterVisitor node context =
|
||||
case Node.value node of
|
||||
Declaration.FunctionDeclaration function ->
|
||||
let
|
||||
@ -1081,9 +1124,12 @@ declarationVisitor node context =
|
||||
|
||||
namesUsedInSignature : { types : List String, modules : List ( ModuleName, ModuleName ) }
|
||||
namesUsedInSignature =
|
||||
function.signature
|
||||
|> Maybe.map (Node.value >> .typeAnnotation >> collectNamesFromTypeAnnotation context.lookupTable)
|
||||
|> Maybe.withDefault { types = [], modules = [] }
|
||||
case function.signature of
|
||||
Just signature ->
|
||||
signature |> Node.value |> .typeAnnotation |> collectNamesFromTypeAnnotation context.lookupTable
|
||||
|
||||
Nothing ->
|
||||
{ types = [], modules = [] }
|
||||
|
||||
namesUsedInArgumentPatterns : { types : List String, modules : List ( ModuleName, ModuleName ) }
|
||||
namesUsedInArgumentPatterns =
|
||||
@ -1114,7 +1160,11 @@ declarationVisitor node context =
|
||||
|
||||
newContext : ModuleContext
|
||||
newContext =
|
||||
{ newContextWhereFunctionIsRegistered | inTheDeclarationOf = [ functionName ], declarations = Dict.empty }
|
||||
{ newContextWhereFunctionIsRegistered
|
||||
| inTheDeclarationOf = [ functionName ]
|
||||
, scopes = NonemptyList.cons emptyScope newContextWhereFunctionIsRegistered.scopes
|
||||
}
|
||||
|> registerParameters functionImplementation.arguments
|
||||
|> (\ctx -> List.foldl markValueAsUsed ctx namesUsedInArgumentPatterns.types)
|
||||
|> markAllAsUsed namesUsedInSignature.types
|
||||
|> markAllModulesAsUsed namesUsedInSignature.modules
|
||||
@ -1150,7 +1200,7 @@ declarationVisitor node context =
|
||||
|> markAllModulesAsUsed modules
|
||||
)
|
||||
|
||||
Declaration.AliasDeclaration { name, typeAnnotation } ->
|
||||
Declaration.AliasDeclaration { typeAnnotation } ->
|
||||
let
|
||||
namesUsedInTypeAnnotation : { types : List String, modules : List ( ModuleName, ModuleName ) }
|
||||
namesUsedInTypeAnnotation =
|
||||
@ -1209,6 +1259,24 @@ foldUsedTypesAndModules =
|
||||
List.foldl (\a b -> { types = a.types ++ b.types, modules = a.modules ++ b.modules }) { types = [], modules = [] }
|
||||
|
||||
|
||||
|
||||
-- DECLARATION EXIT VISITOR
|
||||
|
||||
|
||||
declarationExitVisitor : Node Declaration -> ModuleContext -> ( List (Error {}), ModuleContext )
|
||||
declarationExitVisitor node context =
|
||||
case Node.value node of
|
||||
Declaration.FunctionDeclaration _ ->
|
||||
makeReport context
|
||||
|
||||
_ ->
|
||||
( [], context )
|
||||
|
||||
|
||||
|
||||
-- FINAL EVALUATION
|
||||
|
||||
|
||||
finalEvaluation : ModuleContext -> List (Error {})
|
||||
finalEvaluation context =
|
||||
let
|
||||
@ -1253,7 +1321,7 @@ finalEvaluation context =
|
||||
|> Dict.toList
|
||||
|> List.map
|
||||
(\( name, { under, rangeToRemove, openRange } ) ->
|
||||
if Set.member name usedLocally && not (Dict.member name context.localCustomTypes) then
|
||||
if Set.member name usedLocally && not (Dict.member name context.localTypes) then
|
||||
Rule.errorWithFix
|
||||
{ message = "Imported constructors for `" ++ name ++ "` are not used"
|
||||
, details = details
|
||||
@ -1364,22 +1432,13 @@ finalEvaluation context =
|
||||
[]
|
||||
|
||||
else
|
||||
context.localCustomTypes
|
||||
context.localTypes
|
||||
|> Dict.toList
|
||||
|> List.filter (\( name, _ ) -> not <| Set.member name usedLocally)
|
||||
|> List.map
|
||||
(\( name, customType ) ->
|
||||
Rule.errorWithFix
|
||||
{ message = "Type `" ++ name ++ "` is not used"
|
||||
, details = details
|
||||
}
|
||||
customType.under
|
||||
[ Fix.removeRange customType.rangeToRemove ]
|
||||
)
|
||||
|> List.map errorForLocalType
|
||||
in
|
||||
List.concat
|
||||
[ newRootScope
|
||||
|> makeReport
|
||||
[ makeReportHelp newRootScope
|
||||
|> Tuple.first
|
||||
, importedTypeErrors
|
||||
, moduleErrors
|
||||
@ -1388,6 +1447,26 @@ finalEvaluation context =
|
||||
]
|
||||
|
||||
|
||||
errorForLocalType : ( String, TypeData ) -> Error {}
|
||||
errorForLocalType ( name, type_ ) =
|
||||
let
|
||||
kind : String
|
||||
kind =
|
||||
case type_.kind of
|
||||
CustomTypeKind ->
|
||||
"Type"
|
||||
|
||||
TypeAliasKind ->
|
||||
"Type alias"
|
||||
in
|
||||
Rule.errorWithFix
|
||||
{ message = kind ++ " `" ++ name ++ "` is not used"
|
||||
, details = details
|
||||
}
|
||||
type_.under
|
||||
[ Fix.removeRange type_.rangeToRemove ]
|
||||
|
||||
|
||||
registerFunction : LetBlockContext -> Function -> ModuleContext -> ModuleContext
|
||||
registerFunction letBlockContext function context =
|
||||
let
|
||||
@ -1415,6 +1494,27 @@ registerFunction letBlockContext function context =
|
||||
(Node.value declaration.name)
|
||||
|
||||
|
||||
registerParameters : List (Node Pattern) -> ModuleContext -> ModuleContext
|
||||
registerParameters patterns context =
|
||||
{ context
|
||||
| scopes =
|
||||
NonemptyList.mapHead
|
||||
(setNamesToIgnoreFromPattern patterns)
|
||||
context.scopes
|
||||
}
|
||||
|
||||
|
||||
setNamesToIgnoreFromPattern : List (Node Pattern) -> { a | namesToIgnore : Set String } -> { a | namesToIgnore : Set String }
|
||||
setNamesToIgnoreFromPattern patterns scope =
|
||||
let
|
||||
namesToIgnore : Set String
|
||||
namesToIgnore =
|
||||
List.concatMap getDeclaredParametersFromPattern patterns
|
||||
|> Set.fromList
|
||||
in
|
||||
{ scope | namesToIgnore = namesToIgnore }
|
||||
|
||||
|
||||
type ExposedElement
|
||||
= CustomType String ImportedCustomType
|
||||
| TypeOrValue String VariableInfo
|
||||
@ -1611,8 +1711,23 @@ getModuleName name =
|
||||
String.join "." name
|
||||
|
||||
|
||||
makeReport : Scope -> ( List (Error {}), List String )
|
||||
makeReport { declared, used } =
|
||||
makeReport : ModuleContext -> ( List (Error {}), ModuleContext )
|
||||
makeReport context =
|
||||
let
|
||||
( errors, remainingUsed ) =
|
||||
makeReportHelp (NonemptyList.head context.scopes)
|
||||
|
||||
contextWithPoppedScope : ModuleContext
|
||||
contextWithPoppedScope =
|
||||
{ context | scopes = NonemptyList.pop context.scopes }
|
||||
in
|
||||
( errors
|
||||
, markAllAsUsed remainingUsed contextWithPoppedScope
|
||||
)
|
||||
|
||||
|
||||
makeReportHelp : Scope -> ( List (Error {}), List String )
|
||||
makeReportHelp { declared, used, namesToIgnore } =
|
||||
let
|
||||
usedLocally : Set String
|
||||
usedLocally =
|
||||
@ -1623,11 +1738,12 @@ makeReport { declared, used } =
|
||||
Dict.keys declared
|
||||
|> Set.fromList
|
||||
|> Set.diff usedLocally
|
||||
|> (\set -> Set.diff set namesToIgnore)
|
||||
|> Set.toList
|
||||
|
||||
errors : List (Error {})
|
||||
errors =
|
||||
Dict.filter (\key _ -> not <| Set.member key usedLocally) declared
|
||||
Dict.filter (\key _ -> not (Set.member key usedLocally)) declared
|
||||
|> Dict.toList
|
||||
|> List.map (\( key, variableInfo ) -> error variableInfo key)
|
||||
in
|
||||
|
@ -1006,7 +1006,7 @@ type C = C
|
||||
|> Review.Test.expectErrorsForModules
|
||||
[ ( "A"
|
||||
, [ Review.Test.error
|
||||
{ message = "Type `C` is not used"
|
||||
{ message = "Type alias `C` is not used"
|
||||
, details = details
|
||||
, under = "C"
|
||||
}
|
||||
@ -1042,6 +1042,26 @@ a = C""" |> String.replace "$" " ")
|
||||
]
|
||||
)
|
||||
]
|
||||
, test "should report type alias that doesn't alias a record when it has the same name as a constructor defined in the same file" <|
|
||||
\() ->
|
||||
"""module A exposing (a)
|
||||
type A = B | C
|
||||
type alias B = Int
|
||||
a = B
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Type alias `B` is not used"
|
||||
, details = details
|
||||
, under = "B"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 3, column = 12 }, end = { row = 3, column = 13 } }
|
||||
|> Review.Test.whenFixed """module A exposing (a)
|
||||
type A = B | C
|
||||
a = B
|
||||
"""
|
||||
]
|
||||
, test "should not report open type import when a constructor is used but the type is locally shadowed" <|
|
||||
\() ->
|
||||
[ """module A exposing (a)
|
||||
@ -1302,7 +1322,7 @@ c = 1"""
|
||||
]
|
||||
|> Review.Test.runOnModules rule
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should report unused import even if a let in variable is named the same way" <|
|
||||
, test "should report unused import even if a used let..in variable is named the same way" <|
|
||||
\() ->
|
||||
"""module SomeModule exposing (a)
|
||||
import Html exposing (button, div)
|
||||
@ -1321,6 +1341,126 @@ import Html exposing (div)
|
||||
a = let button = 1
|
||||
in button + div"""
|
||||
]
|
||||
, test "should report unused import even if a used function param is named in the same way" <|
|
||||
\() ->
|
||||
[ """module A exposing (identity)
|
||||
import Used exposing (shadowed)
|
||||
identity shadowed = shadowed
|
||||
"""
|
||||
, """module Used exposing (shadowed)
|
||||
shadowed = ""
|
||||
"""
|
||||
]
|
||||
|> Review.Test.runOnModules rule
|
||||
|> Review.Test.expectErrorsForModules
|
||||
[ ( "A"
|
||||
, [ Review.Test.error
|
||||
{ message = "Imported variable `shadowed` is not used"
|
||||
, details = details
|
||||
, under = "shadowed"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 2, column = 23 }, end = { row = 2, column = 31 } }
|
||||
|> Review.Test.whenFixed ("""module A exposing (identity)
|
||||
import Used$
|
||||
identity shadowed = shadowed
|
||||
""" |> String.replace "$" " ")
|
||||
]
|
||||
)
|
||||
]
|
||||
, test "should report unused import even if a used let..in function param is named in the same way" <|
|
||||
\() ->
|
||||
[ """module A exposing (identity)
|
||||
import Used exposing (shadowed)
|
||||
identity x =
|
||||
let
|
||||
identityHelp shadowed = shadowed
|
||||
in
|
||||
identityHelp x
|
||||
"""
|
||||
, """module Used exposing (shadowed)
|
||||
shadowed = ""
|
||||
"""
|
||||
]
|
||||
|> Review.Test.runOnModules rule
|
||||
|> Review.Test.expectErrorsForModules
|
||||
[ ( "A"
|
||||
, [ Review.Test.error
|
||||
{ message = "Imported variable `shadowed` is not used"
|
||||
, details = details
|
||||
, under = "shadowed"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 2, column = 23 }, end = { row = 2, column = 31 } }
|
||||
|> Review.Test.whenFixed ("""module A exposing (identity)
|
||||
import Used$
|
||||
identity x =
|
||||
let
|
||||
identityHelp shadowed = shadowed
|
||||
in
|
||||
identityHelp x
|
||||
""" |> String.replace "$" " ")
|
||||
]
|
||||
)
|
||||
]
|
||||
, test "should report unused import even if a used lambda param is named in the same way" <|
|
||||
\() ->
|
||||
[ """module A exposing (identity)
|
||||
import Used exposing (shadowed)
|
||||
identity x =
|
||||
(\\shadowed -> shadowed) x
|
||||
"""
|
||||
, """module Used exposing (shadowed)
|
||||
shadowed = ""
|
||||
"""
|
||||
]
|
||||
|> Review.Test.runOnModules rule
|
||||
|> Review.Test.expectErrorsForModules
|
||||
[ ( "A"
|
||||
, [ Review.Test.error
|
||||
{ message = "Imported variable `shadowed` is not used"
|
||||
, details = details
|
||||
, under = "shadowed"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 2, column = 23 }, end = { row = 2, column = 31 } }
|
||||
|> Review.Test.whenFixed ("""module A exposing (identity)
|
||||
import Used$
|
||||
identity x =
|
||||
(\\shadowed -> shadowed) x
|
||||
""" |> String.replace "$" " ")
|
||||
]
|
||||
)
|
||||
]
|
||||
, test "should report unused import even if a variant arg is named in the same way" <|
|
||||
\() ->
|
||||
[ """module A exposing (identity)
|
||||
import Used exposing (shadowed)
|
||||
identity x =
|
||||
case Just x of
|
||||
Nothing -> x
|
||||
Just shadowed -> shadowed
|
||||
"""
|
||||
, """module Used exposing (shadowed)
|
||||
shadowed = ""
|
||||
"""
|
||||
]
|
||||
|> Review.Test.runOnModules rule
|
||||
|> Review.Test.expectErrorsForModules
|
||||
[ ( "A"
|
||||
, [ Review.Test.error
|
||||
{ message = "Imported variable `shadowed` is not used"
|
||||
, details = details
|
||||
, under = "shadowed"
|
||||
}
|
||||
|> Review.Test.atExactly { start = { row = 2, column = 23 }, end = { row = 2, column = 31 } }
|
||||
|> Review.Test.whenFixed ("""module A exposing (identity)
|
||||
import Used$
|
||||
identity x =
|
||||
case Just x of
|
||||
Nothing -> x
|
||||
Just shadowed -> shadowed
|
||||
""" |> String.replace "$" " ")
|
||||
]
|
||||
)
|
||||
]
|
||||
, test "should report unused import alias when two modules share the same alias" <|
|
||||
\() ->
|
||||
[ """module A exposing (a)
|
||||
@ -2082,7 +2222,7 @@ a = 1
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Type `UnusedType` is not used"
|
||||
{ message = "Type alias `UnusedType` is not used"
|
||||
, details = details
|
||||
, under = "UnusedType"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user