Finish first draft

This commit is contained in:
Jeroen Engels 2019-10-21 23:33:41 +02:00
parent a8bbd06ae3
commit 7576962a64
2 changed files with 72 additions and 45 deletions

View File

@ -60,24 +60,26 @@ not care about having extraneous dependencies.
-} -}
rule : Rule rule : Rule
rule = rule =
Rule.newSchema "NoUnusedModules" Rule.newMultiSchema "NoUnusedModules"
{ initialContext = { initialContext =
{ modules = Dict.empty { modules = Dict.empty
, usedModules = Set.empty , usedModules = Set.empty
, fileKey = Nothing
} }
, elmJsonVisitor = Just elmJsonVisitor
, fileVisitor = fileVisitor , fileVisitor = fileVisitor
, mergeContexts = , mergeContexts =
\contextA contextB -> \contextA contextB ->
{ modules = Dict.union contextA.modules contextB.modules { modules = Dict.union contextA.modules contextB.modules
, usedModules = Set.union contextA.usedModules contextB.usedModules , usedModules = Set.union contextA.usedModules contextB.usedModules
, fileKey = Nothing
} }
, finalEvaluation = finalEvaluationForProject , finalEvaluation = finalEvaluationForProject
} }
|> Rule.withElmJsonVisitor elmJsonVisitor |> Rule.fromMultiSchema
|> Rule.fromSchema
fileVisitor : Context -> Rule.Schema { multiFile : (), hasNoVisitor : (), hasAtLeastOneVisitor : () } Context fileVisitor : Context -> Rule.Schema { multiFile : () } { hasAtLeastOneVisitor : () } Context
fileVisitor context = fileVisitor context =
Rule.newFileVisitorSchema context Rule.newFileVisitorSchema context
|> Rule.withFileKeyVisitor fileKeyVisitor |> Rule.withFileKeyVisitor fileKeyVisitor
@ -87,56 +89,80 @@ fileVisitor context =
fileKeyVisitor : Rule.FileKey -> Context -> Context fileKeyVisitor : Rule.FileKey -> Context -> Context
fileKeyVisitor fileKey context = fileKeyVisitor fileKey context =
{ context | fileKey = fileKey } { context | fileKey = Just fileKey }
error : { file : File, moduleNameLocation : Range } -> List String -> Error error : ( List String, { fileKey : Rule.FileKey, moduleNameLocation : Range } ) -> Error
error { file, range } moduleName = error ( moduleName, { fileKey, moduleNameLocation } ) =
Rule.errorForFile file Rule.errorForFile fileKey
{ message = "`" ++ String.join "." moduleName ++ "` is never used." { message = "`" ++ 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 dependencies in your project." ] , details = [ "This module is never used. You may want to remove it to keep your project clean, and maybe detect some unused dependencies in your project." ]
} }
range moduleNameLocation
type alias Context = type alias Context =
{ modules : Dict (List String) { file : File, moduleNameLocation : Range } { modules : Dict (List String) { fileKey : Rule.FileKey, moduleNameLocation : Range }
, usedModules : Set (List String) , usedModules : Set (List String)
, fileKey : Maybe Rule.FileKey
} }
elmJsonVisitor : Maybe Project -> Context -> Context elmJsonVisitor : Maybe Project -> Context -> Context
elmJsonVisitor maybeProject context = elmJsonVisitor maybeProject context =
let let
exposedModules : List String exposedModules : List Elm.Module.Name
exposedModules = exposedModules =
case maybeProject of case maybeProject of
Just (Elm.Project.Package { exposed }) -> Just (Elm.Project.Package { exposed }) ->
case exposed of case exposed of
Elm.Project.ExposedList names -> Elm.Project.ExposedList names ->
names names
|> List.map Elm.Module.toString
Elm.Project.ExposedDict fakeDict -> Elm.Project.ExposedDict fakeDict ->
fakeDict List.concatMap Tuple.second fakeDict
|> List.concatMap Tuple.second
|> List.map Elm.Module.toString
_ -> _ ->
[] []
in in
{ context | usedModules = Set.fromList exposedModules } { context
| usedModules =
exposedModules
|> List.map (Elm.Module.toString >> String.split ".")
|> Set.fromList
}
moduleDefinitionVisitor : Node Module -> Context -> ( List Error, Context ) moduleDefinitionVisitor : Node Module -> Context -> ( List Error, Context )
moduleDefinitionVisitor node context = moduleDefinitionVisitor node context =
let let
(Node range moduleName) = (Node range moduleName) =
node |> Node.value |> .moduleName case Node.value node of
Module.NormalModule data ->
data.moduleName
Module.PortModule data ->
data.moduleName
Module.EffectModule data ->
data.moduleName
in in
( [] case context.fileKey of
, { context | modules = Dict.insert moduleName range context.modules } Just fileKey ->
) ( []
, { context
| modules =
Dict.insert
moduleName
{ fileKey = fileKey
, moduleNameLocation = range
}
context.modules
}
)
Nothing ->
( [], context )
importVisitor : Node Import -> Context -> ( List Error, Context ) importVisitor : Node Import -> Context -> ( List Error, Context )
@ -150,7 +176,7 @@ importVisitor node context =
|> Node.value |> Node.value
in in
( [] ( []
, { context | usedModules = Set.insert (Node.value moduleName) context.usedModules } , { context | usedModules = Set.insert moduleName context.usedModules }
) )
@ -159,4 +185,4 @@ finalEvaluationForProject { modules, usedModules } =
modules modules
|> Dict.filter (\moduleName _ -> not <| Set.member moduleName usedModules) |> Dict.filter (\moduleName _ -> not <| Set.member moduleName usedModules)
|> Dict.toList |> Dict.toList
|> List.map (\moduleName range -> []) |> List.map error

View File

@ -242,7 +242,7 @@ type Analyzer
-} -}
type type
Schema configurationState context Schema ruleType configurationState context
-- `configurationState` is a phantom type used to forbid using `withInitialContext` -- `configurationState` is a phantom type used to forbid using `withInitialContext`
-- after having defined some visitors already. For `withInitialContext` to -- after having defined some visitors already. For `withInitialContext` to
-- work and due to the change in `context` type value, all visitors need to be -- work and due to the change in `context` type value, all visitors need to be
@ -341,7 +341,7 @@ take a look at [`withInitialContext`](#withInitialContext) and "with\*" function
|> Rule.fromSchema |> Rule.fromSchema
-} -}
newSchema : String -> Schema { singleFile : (), hasNoVisitor : () } () newSchema : String -> Schema { singleFile : () } { hasNoVisitor : () } ()
newSchema name_ = newSchema name_ =
emptySchema name_ () emptySchema name_ ()
@ -376,14 +376,14 @@ take a look at [`withInitialContext`](#withInitialContext) and "with\*" function
|> Rule.fromSchema |> Rule.fromSchema
-} -}
newFileVisitorSchema : context -> Schema { multiFile : (), hasNoVisitor : () } context newFileVisitorSchema : context -> Schema { multiFile : () } { hasNoVisitor : () } context
newFileVisitorSchema initialContext = newFileVisitorSchema initialContext =
emptySchema "" initialContext emptySchema "" initialContext
{-| Create a [`Rule`](#Rule) from a configured [`Schema`](#Schema). {-| Create a [`Rule`](#Rule) from a configured [`Schema`](#Schema).
-} -}
fromSchema : Schema { a | hasAtLeastOneVisitor : () } context -> Rule fromSchema : Schema { singleFile : () } { hasAtLeastOneVisitor : () } context -> Rule
fromSchema (Schema schema) = fromSchema (Schema schema) =
Rule Rule
{ name = schema.name { name = schema.name
@ -408,7 +408,7 @@ type MultiSchema context
, initialContext : context , initialContext : context
, elmJsonVisitor : Maybe (Maybe Elm.Project.Project -> context -> context) , elmJsonVisitor : Maybe (Maybe Elm.Project.Project -> context -> context)
, mergeContexts : context -> context -> context , mergeContexts : context -> context -> context
, fileVisitor : context -> Schema { multiFile : (), hasAtLeastOneVisitor : () } context , fileVisitor : context -> Schema { multiFile : () } { hasAtLeastOneVisitor : () } context
, finalEvaluationFn : context -> List Error , finalEvaluationFn : context -> List Error
} }
@ -417,16 +417,17 @@ newMultiSchema :
String String
-> ->
{ initialContext : context { initialContext : context
, fileVisitor : context -> Schema { multiFile : (), hasAtLeastOneVisitor : () } context , elmJsonVisitor : Maybe (Maybe Elm.Project.Project -> context -> context)
, fileVisitor : context -> Schema { multiFile : () } { hasAtLeastOneVisitor : () } context
, mergeContexts : context -> context -> context , mergeContexts : context -> context -> context
, finalEvaluation : context -> List Error , finalEvaluation : context -> List Error
} }
-> MultiSchema context -> MultiSchema context
newMultiSchema name_ { initialContext, fileVisitor, mergeContexts, finalEvaluation } = newMultiSchema name_ { initialContext, elmJsonVisitor, fileVisitor, mergeContexts, finalEvaluation } =
MultiSchema MultiSchema
{ name = name_ { name = name_
, initialContext = initialContext , initialContext = initialContext
, elmJsonVisitor = Nothing , elmJsonVisitor = elmJsonVisitor
, mergeContexts = mergeContexts , mergeContexts = mergeContexts
, fileVisitor = fileVisitor , fileVisitor = fileVisitor
, finalEvaluationFn = finalEvaluation , finalEvaluationFn = finalEvaluation
@ -475,7 +476,7 @@ multiAnalyzer (MultiSchema schema) project =
] ]
visitFileForMulti : Schema { multiFile : (), hasAtLeastOneVisitor : () } context -> context -> File -> ( List Error, context ) visitFileForMulti : Schema { multiFile : () } { hasAtLeastOneVisitor : () } context -> context -> File -> ( List Error, context )
visitFileForMulti (Schema schema) initialContext file = visitFileForMulti (Schema schema) initialContext file =
initialContext initialContext
|> schema.moduleDefinitionVisitor file.moduleDefinition |> schema.moduleDefinitionVisitor file.moduleDefinition
@ -523,7 +524,7 @@ Note: `withSimpleModuleDefinitionVisitor` is a simplified version of [`withModul
which isn't passed a `context` and doesn't return one. You can use `withSimpleModuleDefinitionVisitor` even if you use "non-simple with\*" functions. which isn't passed a `context` and doesn't return one. You can use `withSimpleModuleDefinitionVisitor` even if you use "non-simple with\*" functions.
-} -}
withSimpleModuleDefinitionVisitor : (Node Module -> List Error) -> Schema anything context -> Schema { anything | hasAtLeastOneVisitor : () } context withSimpleModuleDefinitionVisitor : (Node Module -> List Error) -> Schema anytype anything context -> Schema anytype { hasAtLeastOneVisitor : () } context
withSimpleModuleDefinitionVisitor visitor (Schema schema) = withSimpleModuleDefinitionVisitor visitor (Schema schema) =
Schema { schema | moduleDefinitionVisitor = \node context -> ( visitor node, context ) } Schema { schema | moduleDefinitionVisitor = \node context -> ( visitor node, context ) }
@ -572,7 +573,7 @@ Note: `withSimpleImportVisitor` is a simplified version of [`withImportVisitor`]
which isn't passed a `context` and doesn't return one. You can use `withSimpleImportVisitor` even if you use "non-simple with\*" functions. which isn't passed a `context` and doesn't return one. You can use `withSimpleImportVisitor` even if you use "non-simple with\*" functions.
-} -}
withSimpleImportVisitor : (Node Import -> List Error) -> Schema anything context -> Schema { anything | hasAtLeastOneVisitor : () } context withSimpleImportVisitor : (Node Import -> List Error) -> Schema anytype anything context -> Schema anytype { hasAtLeastOneVisitor : () } context
withSimpleImportVisitor visitor (Schema schema) = withSimpleImportVisitor visitor (Schema schema) =
Schema { schema | importVisitor = \node context -> ( visitor node, context ) } Schema { schema | importVisitor = \node context -> ( visitor node, context ) }
@ -626,7 +627,7 @@ Note: `withSimpleDeclarationVisitor` is a simplified version of [`withDeclaratio
which isn't passed a [`Direction`](#Direction) (it will only be called `OnEnter`ing the node) and a `context` and doesn't return a context. You can use `withSimpleDeclarationVisitor` even if you use "non-simple with\*" functions. which isn't passed a [`Direction`](#Direction) (it will only be called `OnEnter`ing the node) and a `context` and doesn't return a context. You can use `withSimpleDeclarationVisitor` even if you use "non-simple with\*" functions.
-} -}
withSimpleDeclarationVisitor : (Node Declaration -> List Error) -> Schema anything context -> Schema { anything | hasAtLeastOneVisitor : () } context withSimpleDeclarationVisitor : (Node Declaration -> List Error) -> Schema anytype anything context -> Schema anytype { hasAtLeastOneVisitor : () } context
withSimpleDeclarationVisitor visitor (Schema schema) = withSimpleDeclarationVisitor visitor (Schema schema) =
Schema Schema
{ schema { schema
@ -681,7 +682,7 @@ Note: `withSimpleExpressionVisitor` is a simplified version of [`withExpressionV
which isn't passed a [`Direction`](#Direction) (it will only be called `OnEnter`ing the node) and a `context` and doesn't return a context. You can use `withSimpleExpressionVisitor` even if you use "non-simple with\*" functions. which isn't passed a [`Direction`](#Direction) (it will only be called `OnEnter`ing the node) and a `context` and doesn't return a context. You can use `withSimpleExpressionVisitor` even if you use "non-simple with\*" functions.
-} -}
withSimpleExpressionVisitor : (Node Expression -> List Error) -> Schema anything context -> Schema { anything | hasAtLeastOneVisitor : () } context withSimpleExpressionVisitor : (Node Expression -> List Error) -> Schema anytype anything context -> Schema anytype { hasAtLeastOneVisitor : () } context
withSimpleExpressionVisitor visitor (Schema schema) = withSimpleExpressionVisitor visitor (Schema schema) =
Schema Schema
{ schema { schema
@ -796,12 +797,12 @@ right after [`newSchema`](#newSchema) just like in the example above, as previou
"with\*" functions will be ignored. "with\*" functions will be ignored.
-} -}
withInitialContext : context -> Schema { anything | hasNoVisitor : () } () -> Schema { anything | hasNoVisitor : () } context withInitialContext : context -> Schema anytype { hasNoVisitor : () } () -> Schema anytype { hasNoVisitor : () } context
withInitialContext initialContext_ (Schema schema) = withInitialContext initialContext_ (Schema schema) =
emptySchema schema.name initialContext_ emptySchema schema.name initialContext_
emptySchema : String -> context -> Schema anything context emptySchema : String -> context -> Schema anytype anything context
emptySchema name_ initialContext = emptySchema name_ initialContext =
Schema Schema
{ name = name_ { name = name_
@ -880,7 +881,7 @@ The following example forbids exposing a file in an "Internal" directory in your
( [], context ) ( [], context )
-} -}
withElmJsonVisitor : (Maybe Elm.Project.Project -> context -> context) -> Schema { anything | singleFile : () } context -> Schema { anything | singleFile : () } context withElmJsonVisitor : (Maybe Elm.Project.Project -> context -> context) -> Schema { singleFile : () } anything context -> Schema { singleFile : () } anything context
withElmJsonVisitor visitor (Schema schema) = withElmJsonVisitor visitor (Schema schema) =
Schema { schema | elmJsonVisitor = visitor } Schema { schema | elmJsonVisitor = visitor }
@ -944,7 +945,7 @@ Tip: If you do not need to collect data in this visitor, you may wish to use the
simpler [`withSimpleModuleDefinitionVisitor`](#withSimpleModuleDefinitionVisitor) function. simpler [`withSimpleModuleDefinitionVisitor`](#withSimpleModuleDefinitionVisitor) function.
-} -}
withModuleDefinitionVisitor : (Node Module -> context -> ( List Error, context )) -> Schema anything context -> Schema { anything | hasAtLeastOneVisitor : () } context withModuleDefinitionVisitor : (Node Module -> context -> ( List Error, context )) -> Schema anytype anything context -> Schema anytype { hasAtLeastOneVisitor : () } context
withModuleDefinitionVisitor visitor (Schema schema) = withModuleDefinitionVisitor visitor (Schema schema) =
Schema { schema | moduleDefinitionVisitor = visitor } Schema { schema | moduleDefinitionVisitor = visitor }
@ -1015,7 +1016,7 @@ Tip: If you do not need to collect or use the `context` in this visitor, you may
simpler [`withSimpleImportVisitor`](#withSimpleImportVisitor) function. simpler [`withSimpleImportVisitor`](#withSimpleImportVisitor) function.
-} -}
withImportVisitor : (Node Import -> context -> ( List Error, context )) -> Schema anything context -> Schema { anything | hasAtLeastOneVisitor : () } context withImportVisitor : (Node Import -> context -> ( List Error, context )) -> Schema anytype anything context -> Schema anytype { hasAtLeastOneVisitor : () } context
withImportVisitor visitor (Schema schema) = withImportVisitor visitor (Schema schema) =
Schema { schema | importVisitor = visitor } Schema { schema | importVisitor = visitor }
@ -1105,7 +1106,7 @@ Tip: If you do not need to collect or use the `context` in this visitor, you may
simpler [`withSimpleDeclarationVisitor`](#withSimpleDeclarationVisitor) function. simpler [`withSimpleDeclarationVisitor`](#withSimpleDeclarationVisitor) function.
-} -}
withDeclarationVisitor : (Node Declaration -> Direction -> context -> ( List Error, context )) -> Schema anything context -> Schema { anything | hasAtLeastOneVisitor : () } context withDeclarationVisitor : (Node Declaration -> Direction -> context -> ( List Error, context )) -> Schema anytype anything context -> Schema anytype { hasAtLeastOneVisitor : () } context
withDeclarationVisitor visitor (Schema schema) = withDeclarationVisitor visitor (Schema schema) =
Schema { schema | declarationVisitor = visitor } Schema { schema | declarationVisitor = visitor }
@ -1124,7 +1125,7 @@ and [withExpressionVisitor](#withExpressionVisitor). Otherwise, using
[withDeclarationVisitor](#withDeclarationVisitor) is probably a simpler choice. [withDeclarationVisitor](#withDeclarationVisitor) is probably a simpler choice.
-} -}
withDeclarationListVisitor : (List (Node Declaration) -> context -> ( List Error, context )) -> Schema anything context -> Schema { anything | hasAtLeastOneVisitor : () } context withDeclarationListVisitor : (List (Node Declaration) -> context -> ( List Error, context )) -> Schema anytype anything context -> Schema anytype { hasAtLeastOneVisitor : () } context
withDeclarationListVisitor visitor (Schema schema) = withDeclarationListVisitor visitor (Schema schema) =
Schema { schema | declarationListVisitor = visitor } Schema { schema | declarationListVisitor = visitor }
@ -1208,7 +1209,7 @@ Tip: If you do not need to collect or use the `context` in this visitor, you may
simpler [`withSimpleExpressionVisitor`](#withSimpleExpressionVisitor) function. simpler [`withSimpleExpressionVisitor`](#withSimpleExpressionVisitor) function.
-} -}
withExpressionVisitor : (Node Expression -> Direction -> context -> ( List Error, context )) -> Schema anything context -> Schema { anything | hasAtLeastOneVisitor : () } context withExpressionVisitor : (Node Expression -> Direction -> context -> ( List Error, context )) -> Schema anytype anything context -> Schema anytype { hasAtLeastOneVisitor : () } context
withExpressionVisitor visitor (Schema schema) = withExpressionVisitor visitor (Schema schema) =
Schema { schema | expressionVisitor = visitor } Schema { schema | expressionVisitor = visitor }
@ -1257,7 +1258,7 @@ for [`withImportVisitor`](#withImportVisitor), but using [`withFinalEvaluation`]
[] []
-} -}
withFinalEvaluation : (context -> List Error) -> Schema { anything | hasAtLeastOneVisitor : () } context -> Schema { anything | hasAtLeastOneVisitor : () } context withFinalEvaluation : (context -> List Error) -> Schema anytype { hasAtLeastOneVisitor : () } context -> Schema anytype { hasAtLeastOneVisitor : () } context
withFinalEvaluation visitor (Schema schema) = withFinalEvaluation visitor (Schema schema) =
Schema { schema | finalEvaluationFn = visitor } Schema { schema | finalEvaluationFn = visitor }
@ -1306,7 +1307,7 @@ for [`withImportVisitor`](#withImportVisitor), but using [`withFinalEvaluation`]
[] []
-} -}
withFileKeyVisitor : (FileKey -> context -> context) -> Schema { anything | hasNoVisitor : () } context -> Schema { anything | hasNoVisitor : () } context withFileKeyVisitor : (FileKey -> context -> context) -> Schema anytype { hasNoVisitor : () } context -> Schema anytype { hasNoVisitor : () } context
withFileKeyVisitor visitor (Schema schema) = withFileKeyVisitor visitor (Schema schema) =
Schema { schema | fileKeyVisitor = Just visitor } Schema { schema | fileKeyVisitor = Just visitor }