mirror of
https://github.com/jfmengels/elm-review.git
synced 2024-12-20 00:02:11 +03:00
240 lines
7.3 KiB
Elm
240 lines
7.3 KiB
Elm
module NoUnused.Dependencies exposing (rule)
|
|
|
|
{-| Forbid the use of dependencies that are never used in your project.
|
|
|
|
|
|
# Rule
|
|
|
|
@docs rule
|
|
|
|
-}
|
|
|
|
import Dict exposing (Dict)
|
|
import Elm.Package
|
|
import Elm.Project exposing (Project)
|
|
import Elm.Syntax.Import exposing (Import)
|
|
import Elm.Syntax.ModuleName exposing (ModuleName)
|
|
import Elm.Syntax.Node as Node exposing (Node)
|
|
import Elm.Syntax.Range exposing (Range)
|
|
import Review.Project.Dependency as Dependency exposing (Dependency)
|
|
import Review.Rule as Rule exposing (Error, Rule)
|
|
import Set exposing (Set)
|
|
|
|
|
|
{-| Forbid the use of dependencies that are never used in your project.
|
|
|
|
A dependency is considered unused if none of its modules are imported in the project.
|
|
|
|
config =
|
|
[ NoUnused.Dependencies.rule
|
|
]
|
|
|
|
|
|
## Try it out
|
|
|
|
You can try this rule out by running the following command:
|
|
|
|
```bash
|
|
elm-review --template jfmengels/review-unused/example --rules NoUnused.Dependencies
|
|
```
|
|
|
|
-}
|
|
rule : Rule
|
|
rule =
|
|
Rule.newProjectRuleSchema "NoUnused.Dependencies" initialProjectContext
|
|
|> Rule.withElmJsonProjectVisitor elmJsonVisitor
|
|
|> Rule.withDependenciesProjectVisitor dependenciesVisitor
|
|
|> Rule.withModuleVisitor moduleVisitor
|
|
|> Rule.withModuleContext
|
|
{ fromProjectToModule = fromProjectToModule
|
|
, fromModuleToProject = fromModuleToProject
|
|
, foldProjectContexts = foldProjectContexts
|
|
}
|
|
|> Rule.withFinalProjectEvaluation finalEvaluationForProject
|
|
|> Rule.fromProjectRuleSchema
|
|
|
|
|
|
moduleVisitor : Rule.ModuleRuleSchema {} ModuleContext -> Rule.ModuleRuleSchema { hasAtLeastOneVisitor : () } ModuleContext
|
|
moduleVisitor schema =
|
|
schema
|
|
|> Rule.withImportVisitor importVisitor
|
|
|
|
|
|
dependenciesVisitor : Dict String Dependency -> ProjectContext -> ( List nothing, ProjectContext )
|
|
dependenciesVisitor dependencies projectContext =
|
|
let
|
|
moduleNameToDependency : Dict String String
|
|
moduleNameToDependency =
|
|
dependencies
|
|
|> Dict.toList
|
|
|> List.concatMap
|
|
(\( packageName, dependency ) ->
|
|
List.map (\{ name } -> ( name, packageName )) (Dependency.modules dependency)
|
|
)
|
|
|> Dict.fromList
|
|
in
|
|
( [], { projectContext | moduleNameToDependency = moduleNameToDependency } )
|
|
|
|
|
|
|
|
-- CONTEXT
|
|
|
|
|
|
type alias ProjectContext =
|
|
{ moduleNameToDependency : Dict String String
|
|
, directProjectDependencies : Set String
|
|
, importedModuleNames : Set String
|
|
, elmJsonKey : Maybe Rule.ElmJsonKey
|
|
}
|
|
|
|
|
|
type alias ModuleContext =
|
|
Set String
|
|
|
|
|
|
initialProjectContext : ProjectContext
|
|
initialProjectContext =
|
|
{ moduleNameToDependency = Dict.empty
|
|
, directProjectDependencies = Set.empty
|
|
, importedModuleNames = Set.empty
|
|
, elmJsonKey = Nothing
|
|
}
|
|
|
|
|
|
fromProjectToModule : Rule.ModuleKey -> Node ModuleName -> ProjectContext -> ModuleContext
|
|
fromProjectToModule _ _ projectContext =
|
|
projectContext.importedModuleNames
|
|
|
|
|
|
fromModuleToProject : Rule.ModuleKey -> Node ModuleName -> ModuleContext -> ProjectContext
|
|
fromModuleToProject _ _ importedModuleNames =
|
|
{ moduleNameToDependency = Dict.empty
|
|
, directProjectDependencies = Set.empty
|
|
, importedModuleNames = importedModuleNames
|
|
, elmJsonKey = Nothing
|
|
}
|
|
|
|
|
|
foldProjectContexts : ProjectContext -> ProjectContext -> ProjectContext
|
|
foldProjectContexts newContext previousContext =
|
|
{ moduleNameToDependency = previousContext.moduleNameToDependency
|
|
, directProjectDependencies = previousContext.directProjectDependencies
|
|
, importedModuleNames = Set.union previousContext.importedModuleNames newContext.importedModuleNames
|
|
, elmJsonKey = previousContext.elmJsonKey
|
|
}
|
|
|
|
|
|
|
|
-- PROJECT VISITORS
|
|
|
|
|
|
elmJsonVisitor : Maybe { elmJsonKey : Rule.ElmJsonKey, project : Project } -> ProjectContext -> ( List nothing, ProjectContext )
|
|
elmJsonVisitor maybeProject projectContext =
|
|
case maybeProject of
|
|
Just { elmJsonKey, project } ->
|
|
let
|
|
directProjectDependencies : Set String
|
|
directProjectDependencies =
|
|
case project of
|
|
Elm.Project.Package { deps } ->
|
|
deps
|
|
|> List.map (Tuple.first >> Elm.Package.toString)
|
|
|> Set.fromList
|
|
|
|
Elm.Project.Application { depsDirect } ->
|
|
depsDirect
|
|
|> List.map (Tuple.first >> Elm.Package.toString)
|
|
|> Set.fromList
|
|
in
|
|
( []
|
|
, { projectContext
|
|
| elmJsonKey = Just elmJsonKey
|
|
, directProjectDependencies =
|
|
Set.filter ((/=) "elm/core") directProjectDependencies
|
|
}
|
|
)
|
|
|
|
Nothing ->
|
|
( [], projectContext )
|
|
|
|
|
|
|
|
-- IMPORT VISITOR
|
|
|
|
|
|
importVisitor : Node Import -> ModuleContext -> ( List nothing, ModuleContext )
|
|
importVisitor node importedModuleNames =
|
|
( []
|
|
, Set.insert (moduleNameForImport node) importedModuleNames
|
|
)
|
|
|
|
|
|
moduleNameForImport : Node Import -> String
|
|
moduleNameForImport node =
|
|
node
|
|
|> Node.value
|
|
|> .moduleName
|
|
|> Node.value
|
|
|> String.join "."
|
|
|
|
|
|
|
|
-- FINAL EVALUATION
|
|
|
|
|
|
finalEvaluationForProject : ProjectContext -> List (Error { useErrorForModule : () })
|
|
finalEvaluationForProject projectContext =
|
|
case projectContext.elmJsonKey of
|
|
Just elmJsonKey ->
|
|
projectContext.importedModuleNames
|
|
|> Set.toList
|
|
|> List.filterMap (\importedModuleName -> Dict.get importedModuleName projectContext.moduleNameToDependency)
|
|
|> Set.fromList
|
|
|> Set.diff projectContext.directProjectDependencies
|
|
|> Set.toList
|
|
|> List.map (error elmJsonKey)
|
|
|
|
Nothing ->
|
|
[]
|
|
|
|
|
|
error : Rule.ElmJsonKey -> String -> Error scope
|
|
error elmJsonKey packageName =
|
|
Rule.errorForElmJson elmJsonKey
|
|
(\elmJson ->
|
|
{ message = "Unused dependency `" ++ packageName ++ "`"
|
|
, details =
|
|
[ "To remove it, I recommend running the following command:"
|
|
, " elm-json uninstall " ++ packageName
|
|
]
|
|
, range = findPackageNameInElmJson packageName elmJson
|
|
}
|
|
)
|
|
|
|
|
|
findPackageNameInElmJson : String -> String -> Range
|
|
findPackageNameInElmJson packageName elmJson =
|
|
elmJson
|
|
|> String.lines
|
|
|> List.indexedMap Tuple.pair
|
|
|> List.filterMap
|
|
(\( row, line ) ->
|
|
case String.indexes ("\"" ++ packageName ++ "\"") line of
|
|
[] ->
|
|
Nothing
|
|
|
|
column :: _ ->
|
|
Just
|
|
{ start =
|
|
{ row = row + 1
|
|
, column = column + 2
|
|
}
|
|
, end =
|
|
{ row = row + 1
|
|
, column = column + String.length packageName + 2
|
|
}
|
|
}
|
|
)
|
|
|> List.head
|
|
|> Maybe.withDefault { start = { row = 1, column = 1 }, end = { row = 10000, column = 1 } }
|