Make NoUnused.Modules not report test modules or modules with a main function

This commit is contained in:
Jeroen Engels 2020-01-01 20:35:31 +01:00
parent 4ff2c2db00
commit 9cc72c659f

View File

@ -12,6 +12,7 @@ module NoUnusedModules exposing (rule)
import Dict exposing (Dict)
import Elm.Module
import Elm.Project exposing (Project)
import Elm.Syntax.Declaration as Declaration exposing (Declaration)
import Elm.Syntax.Import exposing (Import)
import Elm.Syntax.Module as Module exposing (Module)
import Elm.Syntax.ModuleName exposing (ModuleName)
@ -23,13 +24,10 @@ import Set exposing (Set)
{-| Forbid the use of modules that are never used in your project.
For an application, 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 a package, a module is considered unused if it is not exposed in the
`elm.json`'s `exposed-modules`, does not import `Test` module, and is never
imported in other modules.
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
`exposed-modules` are considered used.
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
@ -43,12 +41,12 @@ remove code so that the import statement is removed.
# When (not) to use this rule
You may not want to enable this rule if you are not concerned about having
unused modules in your application of package.
unused modules in your application or package.
-}
rule : Rule
rule =
Rule.newMultiSchema "NoUnusedModules"
Rule.newMultiSchema "NoUnused.Modules"
{ elmJsonVisitors = [ elmJsonVisitor ]
, dependenciesVisitors = []
, moduleVisitorSchema = moduleVisitorSchema
@ -79,19 +77,21 @@ type alias GlobalContext =
type alias ModuleContext =
{ importedModules : Set ModuleName
, containsMainFunction : Bool
}
initGlobalContext : GlobalContext
initGlobalContext =
{ modules = Dict.empty
, usedModules = Set.empty
, usedModules = Set.singleton [ "ReviewConfig" ]
}
initModuleContext : Rule.FileKey -> Node ModuleName -> GlobalContext -> ModuleContext
initModuleContext _ _ _ =
{ importedModules = Set.empty
, containsMainFunction = False
}
@ -101,7 +101,12 @@ toGlobalContext fileKey moduleName moduleContext =
Dict.singleton
(Node.value moduleName)
{ fileKey = fileKey, moduleNameLocation = Node.range moduleName }
, usedModules = moduleContext.importedModules
, usedModules =
if Set.member [ "Test" ] moduleContext.importedModules || moduleContext.containsMainFunction then
Set.insert (Node.value moduleName) moduleContext.importedModules
else
moduleContext.importedModules
}
@ -113,24 +118,7 @@ fold contextA contextB =
-- VISITORS
moduleVisitorSchema :
Rule.Schema Rule.ForLookingAtSeveralFiles { hasNoVisitor : () } ModuleContext
-> Rule.Schema Rule.ForLookingAtSeveralFiles { hasAtLeastOneVisitor : () } ModuleContext
moduleVisitorSchema schema =
schema
|> Rule.withImportVisitor importVisitor
error : ( ModuleName, { fileKey : Rule.FileKey, moduleNameLocation : Range } ) -> Error
error ( moduleName, { fileKey, moduleNameLocation } ) =
Rule.errorForFile fileKey
{ 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
-- GLOBAL VISITORS
elmJsonVisitor : Maybe Project -> GlobalContext -> GlobalContext
@ -155,9 +143,44 @@ elmJsonVisitor maybeProject context =
exposedModules
|> List.map (Elm.Module.toString >> String.split ".")
|> Set.fromList
|> Set.union context.usedModules
}
finalEvaluationForProject : GlobalContext -> List Error
finalEvaluationForProject { modules, usedModules } =
modules
|> Dict.filter (\moduleName _ -> not <| Set.member moduleName usedModules)
|> Dict.toList
|> List.map error
-- MODULE VISITORS
moduleVisitorSchema :
Rule.Schema Rule.ForLookingAtSeveralFiles { hasNoVisitor : () } ModuleContext
-> Rule.Schema Rule.ForLookingAtSeveralFiles { hasAtLeastOneVisitor : () } ModuleContext
moduleVisitorSchema schema =
schema
|> Rule.withImportVisitor importVisitor
|> Rule.withDeclarationListVisitor declarationListVisitor
error : ( ModuleName, { fileKey : Rule.FileKey, moduleNameLocation : Range } ) -> Error
error ( moduleName, { fileKey, moduleNameLocation } ) =
Rule.errorForFile fileKey
{ 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
importVisitor : Node Import -> ModuleContext -> ( List Error, ModuleContext )
importVisitor node context =
( []
@ -173,9 +196,26 @@ moduleNameForImport node =
|> Node.value
finalEvaluationForProject : GlobalContext -> List Error
finalEvaluationForProject { modules, usedModules } =
modules
|> Dict.filter (\moduleName _ -> not <| Set.member moduleName usedModules)
|> Dict.toList
|> List.map error
-- DECLARATION LIST VISITOR
declarationListVisitor : List (Node Declaration) -> ModuleContext -> ( List Error, ModuleContext )
declarationListVisitor list context =
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 }
)