2019-10-17 01:20:19 +03:00
|
|
|
module NoUnusedModules exposing (rule)
|
|
|
|
|
2020-01-01 15:52:13 +03:00
|
|
|
{-| Forbid the use of modules that are never used in your project.
|
2019-10-17 01:20:19 +03:00
|
|
|
|
|
|
|
|
|
|
|
# Rule
|
|
|
|
|
|
|
|
@docs rule
|
|
|
|
|
|
|
|
-}
|
|
|
|
|
|
|
|
import Dict exposing (Dict)
|
|
|
|
import Elm.Module
|
|
|
|
import Elm.Project exposing (Project)
|
2020-01-01 22:35:31 +03:00
|
|
|
import Elm.Syntax.Declaration as Declaration exposing (Declaration)
|
2019-10-17 01:20:19 +03:00
|
|
|
import Elm.Syntax.Import exposing (Import)
|
2020-01-01 15:52:13 +03:00
|
|
|
import Elm.Syntax.ModuleName exposing (ModuleName)
|
|
|
|
import Elm.Syntax.Node as Node exposing (Node)
|
2019-10-17 01:20:19 +03:00
|
|
|
import Elm.Syntax.Range exposing (Range)
|
|
|
|
import Review.Rule as Rule exposing (Error, Rule)
|
|
|
|
import Set exposing (Set)
|
|
|
|
|
|
|
|
|
2020-01-01 15:52:13 +03:00
|
|
|
{-| Forbid the use of modules that are never used in your project.
|
2019-10-17 01:20:19 +03:00
|
|
|
|
2020-01-01 22:35:31 +03:00
|
|
|
A module is considered unused if it does not contain a `main` function
|
|
|
|
(be it exposed or not), does not import `Test` module, and is never imported in
|
|
|
|
other modules. For packages, modules listed in the `elm.json`'s
|
2020-01-04 15:41:52 +03:00
|
|
|
`exposed-modules` are considered used. The `ReviewConfig` is also always
|
|
|
|
considered as used.
|
2019-10-17 01:20:19 +03:00
|
|
|
|
2020-01-01 15:52:13 +03:00
|
|
|
A module will be considered as used if it gets imported, even if none of its
|
|
|
|
functions or types are used. Other rules from this package will help detect and
|
|
|
|
remove code so that the import statement is removed.
|
2019-10-17 01:20:19 +03:00
|
|
|
|
2020-01-01 15:52:13 +03:00
|
|
|
config =
|
|
|
|
[ NoUnused.Modules.rule
|
|
|
|
]
|
2019-10-17 01:20:19 +03:00
|
|
|
|
|
|
|
|
|
|
|
# When (not) to use this rule
|
|
|
|
|
2020-01-01 15:52:13 +03:00
|
|
|
You may not want to enable this rule if you are not concerned about having
|
2020-01-01 22:35:31 +03:00
|
|
|
unused modules in your application or package.
|
2019-10-17 01:20:19 +03:00
|
|
|
|
|
|
|
-}
|
|
|
|
rule : Rule
|
|
|
|
rule =
|
2020-01-19 22:37:19 +03:00
|
|
|
Rule.newProjectRuleSchema "NoUnused.Modules"
|
2020-02-27 21:19:33 +03:00
|
|
|
{ moduleVisitor = moduleVisitor
|
2020-01-19 22:37:19 +03:00
|
|
|
, initProjectContext = initProjectContext
|
2020-01-26 15:19:46 +03:00
|
|
|
, fromProjectToModule = fromProjectToModule
|
|
|
|
, fromModuleToProject = fromModuleToProject
|
2020-01-19 22:37:19 +03:00
|
|
|
, foldProjectContexts = foldProjectContexts
|
2019-10-17 01:20:19 +03:00
|
|
|
}
|
2020-03-03 00:52:22 +03:00
|
|
|
|> Rule.withElmJsonProjectVisitor elmJsonVisitor
|
2020-01-26 15:29:00 +03:00
|
|
|
|> Rule.withFinalProjectEvaluation finalEvaluationForProject
|
2020-01-19 22:37:19 +03:00
|
|
|
|> Rule.fromProjectRuleSchema
|
2019-10-17 01:20:19 +03:00
|
|
|
|
|
|
|
|
2020-02-27 21:19:33 +03:00
|
|
|
moduleVisitor : Rule.ModuleRuleSchema {} ModuleContext -> Rule.ModuleRuleSchema { hasAtLeastOneVisitor : () } ModuleContext
|
|
|
|
moduleVisitor schema =
|
2020-01-26 23:11:51 +03:00
|
|
|
schema
|
|
|
|
|> Rule.withImportVisitor importVisitor
|
|
|
|
|> Rule.withDeclarationListVisitor declarationListVisitor
|
|
|
|
|
|
|
|
|
2019-10-17 01:20:19 +03:00
|
|
|
|
2020-01-01 21:56:31 +03:00
|
|
|
-- CONTEXT
|
2019-10-21 11:26:11 +03:00
|
|
|
|
|
|
|
|
2020-01-19 22:37:19 +03:00
|
|
|
type alias ProjectContext =
|
2020-01-01 21:56:31 +03:00
|
|
|
{ modules :
|
|
|
|
Dict ModuleName
|
2020-02-16 23:54:05 +03:00
|
|
|
{ moduleKey : Rule.ModuleKey
|
2020-01-01 21:56:31 +03:00
|
|
|
, moduleNameLocation : Range
|
|
|
|
}
|
|
|
|
, usedModules : Set ModuleName
|
2020-01-04 20:34:12 +03:00
|
|
|
, isPackage : Bool
|
2020-01-01 21:56:31 +03:00
|
|
|
}
|
2019-10-17 01:20:19 +03:00
|
|
|
|
|
|
|
|
2020-01-01 21:56:31 +03:00
|
|
|
type alias ModuleContext =
|
2020-01-01 22:18:04 +03:00
|
|
|
{ importedModules : Set ModuleName
|
2020-01-01 22:35:31 +03:00
|
|
|
, containsMainFunction : Bool
|
2020-01-04 20:34:12 +03:00
|
|
|
, isPackage : Bool
|
2020-01-01 21:56:31 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-01-19 22:37:19 +03:00
|
|
|
initProjectContext : ProjectContext
|
|
|
|
initProjectContext =
|
2020-01-01 21:56:31 +03:00
|
|
|
{ modules = Dict.empty
|
2020-01-01 22:35:31 +03:00
|
|
|
, usedModules = Set.singleton [ "ReviewConfig" ]
|
2020-01-04 20:34:12 +03:00
|
|
|
, isPackage = False
|
2020-01-01 21:56:31 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-02-16 23:54:05 +03:00
|
|
|
fromProjectToModule : Rule.ModuleKey -> Node ModuleName -> ProjectContext -> ModuleContext
|
2020-01-26 15:19:46 +03:00
|
|
|
fromProjectToModule _ _ projectContext =
|
2020-01-01 22:18:04 +03:00
|
|
|
{ importedModules = Set.empty
|
2020-01-01 22:35:31 +03:00
|
|
|
, containsMainFunction = False
|
2020-01-19 22:37:19 +03:00
|
|
|
, isPackage = projectContext.isPackage
|
2019-10-17 01:20:19 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-02-16 23:54:05 +03:00
|
|
|
fromModuleToProject : Rule.ModuleKey -> Node ModuleName -> ModuleContext -> ProjectContext
|
|
|
|
fromModuleToProject moduleKey moduleName moduleContext =
|
2020-01-01 22:18:04 +03:00
|
|
|
{ modules =
|
|
|
|
Dict.singleton
|
|
|
|
(Node.value moduleName)
|
2020-02-16 23:54:05 +03:00
|
|
|
{ moduleKey = moduleKey, moduleNameLocation = Node.range moduleName }
|
2020-01-01 22:35:31 +03:00
|
|
|
, usedModules =
|
|
|
|
if Set.member [ "Test" ] moduleContext.importedModules || moduleContext.containsMainFunction then
|
|
|
|
Set.insert (Node.value moduleName) moduleContext.importedModules
|
|
|
|
|
|
|
|
else
|
|
|
|
moduleContext.importedModules
|
2020-01-04 20:34:12 +03:00
|
|
|
, isPackage = moduleContext.isPackage
|
2020-01-01 21:56:31 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-01-19 22:37:19 +03:00
|
|
|
foldProjectContexts : ProjectContext -> ProjectContext -> ProjectContext
|
|
|
|
foldProjectContexts newContext previousContext =
|
2020-01-14 11:48:26 +03:00
|
|
|
{ modules = Dict.union previousContext.modules newContext.modules
|
|
|
|
, usedModules = Set.union previousContext.usedModules newContext.usedModules
|
|
|
|
, isPackage = previousContext.isPackage
|
2020-01-01 21:56:31 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-01-26 15:25:09 +03:00
|
|
|
-- PROJECT VISITORS
|
2020-01-01 21:56:31 +03:00
|
|
|
|
|
|
|
|
2020-03-19 01:08:06 +03:00
|
|
|
elmJsonVisitor : Maybe { a | project : Project } -> ProjectContext -> ( List nothing, ProjectContext )
|
2020-01-26 15:25:09 +03:00
|
|
|
elmJsonVisitor maybeProject projectContext =
|
2019-10-17 01:20:19 +03:00
|
|
|
let
|
2020-01-04 20:34:12 +03:00
|
|
|
( exposedModules, isPackage ) =
|
2020-02-14 20:20:45 +03:00
|
|
|
case maybeProject |> Maybe.map .project of
|
2019-10-17 01:20:19 +03:00
|
|
|
Just (Elm.Project.Package { exposed }) ->
|
|
|
|
case exposed of
|
|
|
|
Elm.Project.ExposedList names ->
|
2020-01-04 20:34:12 +03:00
|
|
|
( names, True )
|
2019-10-17 01:20:19 +03:00
|
|
|
|
|
|
|
Elm.Project.ExposedDict fakeDict ->
|
2020-01-04 20:34:12 +03:00
|
|
|
( List.concatMap Tuple.second fakeDict, True )
|
2019-10-17 01:20:19 +03:00
|
|
|
|
|
|
|
_ ->
|
2020-01-04 20:34:12 +03:00
|
|
|
( [], False )
|
2019-10-17 01:20:19 +03:00
|
|
|
in
|
2020-03-19 01:08:06 +03:00
|
|
|
( []
|
|
|
|
, { projectContext
|
2019-10-22 00:33:41 +03:00
|
|
|
| usedModules =
|
|
|
|
exposedModules
|
|
|
|
|> List.map (Elm.Module.toString >> String.split ".")
|
|
|
|
|> Set.fromList
|
2020-01-26 15:25:09 +03:00
|
|
|
|> Set.union projectContext.usedModules
|
2020-01-04 20:34:12 +03:00
|
|
|
, isPackage = isPackage
|
2020-03-19 01:08:06 +03:00
|
|
|
}
|
|
|
|
)
|
2019-10-17 01:20:19 +03:00
|
|
|
|
|
|
|
|
2020-01-19 22:37:19 +03:00
|
|
|
finalEvaluationForProject : ProjectContext -> List Error
|
2020-01-01 22:35:31 +03:00
|
|
|
finalEvaluationForProject { modules, usedModules } =
|
|
|
|
modules
|
|
|
|
|> Dict.filter (\moduleName _ -> not <| Set.member moduleName usedModules)
|
|
|
|
|> Dict.toList
|
|
|
|
|> List.map error
|
|
|
|
|
|
|
|
|
2020-02-16 23:54:05 +03:00
|
|
|
error : ( ModuleName, { moduleKey : Rule.ModuleKey, moduleNameLocation : Range } ) -> Error
|
|
|
|
error ( moduleName, { moduleKey, moduleNameLocation } ) =
|
2020-03-16 01:24:42 +03:00
|
|
|
Rule.errorForModule moduleKey
|
2020-01-01 22:35:31 +03:00
|
|
|
{ message = "Module `" ++ String.join "." moduleName ++ "` is never used."
|
|
|
|
, details = [ "This module is never used. You may want to remove it to keep your project clean, and maybe detect some unused code in your project." ]
|
|
|
|
}
|
|
|
|
moduleNameLocation
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- IMPORT VISITOR
|
|
|
|
|
|
|
|
|
2020-02-28 19:11:15 +03:00
|
|
|
importVisitor : Node Import -> ModuleContext -> ( List nothing, ModuleContext )
|
2019-10-17 01:20:19 +03:00
|
|
|
importVisitor node context =
|
|
|
|
( []
|
2020-01-01 22:18:04 +03:00
|
|
|
, { context | importedModules = Set.insert (moduleNameForImport node) context.importedModules }
|
2019-10-17 01:20:19 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2020-01-01 22:18:04 +03:00
|
|
|
moduleNameForImport : Node Import -> ModuleName
|
|
|
|
moduleNameForImport node =
|
|
|
|
node
|
|
|
|
|> Node.value
|
|
|
|
|> .moduleName
|
|
|
|
|> Node.value
|
|
|
|
|
|
|
|
|
2020-01-01 22:35:31 +03:00
|
|
|
|
|
|
|
-- DECLARATION LIST VISITOR
|
|
|
|
|
|
|
|
|
2020-02-28 19:11:15 +03:00
|
|
|
declarationListVisitor : List (Node Declaration) -> ModuleContext -> ( List nothing, ModuleContext )
|
2020-01-01 22:35:31 +03:00
|
|
|
declarationListVisitor list context =
|
2020-01-04 20:34:12 +03:00
|
|
|
if context.isPackage then
|
|
|
|
( [], context )
|
|
|
|
|
|
|
|
else
|
|
|
|
let
|
|
|
|
containsMainFunction : Bool
|
|
|
|
containsMainFunction =
|
|
|
|
List.any
|
|
|
|
(\decl ->
|
|
|
|
case Node.value decl of
|
|
|
|
Declaration.FunctionDeclaration function ->
|
|
|
|
(function.declaration |> Node.value |> .name |> Node.value) == "main"
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
False
|
|
|
|
)
|
|
|
|
list
|
|
|
|
in
|
|
|
|
( []
|
|
|
|
, { context | containsMainFunction = containsMainFunction }
|
|
|
|
)
|