Add project visitors to module visitors

This commit is contained in:
Jeroen Engels 2020-06-25 23:52:56 +02:00
parent 1d5ca98a61
commit 7bd35b1941
3 changed files with 160 additions and 57 deletions

View File

@ -11,7 +11,9 @@ module Review.Rule3 exposing
, withDeclarationExitVisitor , withDeclarationExitVisitor
, withDeclarationListVisitor , withDeclarationListVisitor
, withDeclarationVisitor , withDeclarationVisitor
, withDependenciesModuleVisitor
, withDependenciesProjectVisitor , withDependenciesProjectVisitor
, withElmJsonModuleVisitor
, withElmJsonProjectVisitor , withElmJsonProjectVisitor
, withExpressionEnterVisitor , withExpressionEnterVisitor
, withExpressionExitVisitor , withExpressionExitVisitor
@ -22,6 +24,7 @@ module Review.Rule3 exposing
, withModuleContext , withModuleContext
, withModuleDefinitionVisitor , withModuleDefinitionVisitor
, withModuleVisitor , withModuleVisitor
, withReadmeModuleVisitor
, withReadmeProjectVisitor , withReadmeProjectVisitor
, withSimpleCommentsVisitor , withSimpleCommentsVisitor
, withSimpleDeclarationVisitor , withSimpleDeclarationVisitor
@ -101,6 +104,11 @@ type ModuleRuleSchema schemaState moduleContext
, expressionVisitorsOnEnter : List (Visitor Expression moduleContext) , expressionVisitorsOnEnter : List (Visitor Expression moduleContext)
, expressionVisitorsOnExit : List (Visitor Expression moduleContext) , expressionVisitorsOnExit : List (Visitor Expression moduleContext)
, finalEvaluationFns : List (moduleContext -> List (Error {})) , finalEvaluationFns : List (moduleContext -> List (Error {}))
-- Project visitors
, elmJsonVisitors : List (Maybe Elm.Project.Project -> moduleContext -> moduleContext)
, readmeVisitors : List (Maybe String -> moduleContext -> moduleContext)
, dependenciesVisitors : List (Dict String Review.Project.Dependency.Dependency -> moduleContext -> moduleContext)
} }
@ -113,7 +121,7 @@ withModuleVisitor visitor (ProjectRuleSchema schema) =
ProjectRuleSchema { schema | moduleVisitors = visitor :: schema.moduleVisitors } ProjectRuleSchema { schema | moduleVisitors = visitor :: schema.moduleVisitors }
newModuleRuleSchema : String -> moduleContext -> ModuleRuleSchema { moduleContext : Required } moduleContext newModuleRuleSchema : String -> moduleContext -> ModuleRuleSchema { canCollectProjectData : () } moduleContext
newModuleRuleSchema name initialModuleContext = newModuleRuleSchema name initialModuleContext =
ModuleRuleSchema ModuleRuleSchema
{ name = name { name = name
@ -128,6 +136,9 @@ newModuleRuleSchema name initialModuleContext =
, expressionVisitorsOnEnter = [] , expressionVisitorsOnEnter = []
, expressionVisitorsOnExit = [] , expressionVisitorsOnExit = []
, finalEvaluationFns = [] , finalEvaluationFns = []
, elmJsonVisitors = []
, readmeVisitors = []
, dependenciesVisitors = []
} }
@ -185,19 +196,16 @@ fromModuleRuleSchema ((ModuleRuleSchema schema) as moduleVisitor) =
Nothing -> Nothing ->
Debug.todo "Define initial module context" Debug.todo "Define initial module context"
--elmJsonVisitors : List (Maybe { elmJsonKey : ElmJsonKey, project : Elm.Project.Project } -> moduleContext -> ( List (Error {}), moduleContext ))
--elmJsonVisitors =
-- schema.elmJsonVisitors
projectRule : ProjectRuleSchema { schemaState | hasAtLeastOneVisitor : () } moduleContext moduleContext projectRule : ProjectRuleSchema { schemaState | hasAtLeastOneVisitor : () } moduleContext moduleContext
projectRule = projectRule =
ProjectRuleSchema ProjectRuleSchema
{ name = schema.name { name = schema.name
, initialProjectContext = initialContext , initialProjectContext = initialContext
, elmJsonVisitors = [] -- List (Maybe { elmJsonKey : ElmJsonKey, project : Elm.Project.Project } -> projectContext -> ( List (Error {}), projectContext )) , elmJsonVisitors = compactProjectDataVisitors (Maybe.map .project) schema.elmJsonVisitors
, readmeVisitors = [] -- (Maybe { readmeKey : ReadmeKey, content : String } -> projectContext -> ( List (Error {}), projectContext )) , readmeVisitors = compactProjectDataVisitors (Maybe.map .content) schema.readmeVisitors
, dependenciesVisitors = [] -- (Dict String Review.Project.Dependency.Dependency -> projectContext -> ( List (Error {}), projectContext )) , dependenciesVisitors = compactProjectDataVisitors identity schema.dependenciesVisitors
, moduleVisitors = [ removeExtensibleRecordTypeVariable (always moduleVisitor) ] , moduleVisitors = [ removeExtensibleRecordTypeVariable (always moduleVisitor) ]
, moduleContextCreator = Just (Context.init (always initialContext)) , moduleContextCreator = Just (Context.init identity)
, folder = Nothing , folder = Nothing
-- TODO Jeroen Only allow to set it if there is a folder, but not several times -- TODO Jeroen Only allow to set it if there is a folder, but not several times
@ -208,6 +216,27 @@ fromModuleRuleSchema ((ModuleRuleSchema schema) as moduleVisitor) =
fromProjectRuleSchema projectRule fromProjectRuleSchema projectRule
compactProjectDataVisitors : (rawData -> data) -> List (data -> moduleContext -> moduleContext) -> List (rawData -> moduleContext -> ( List nothing, moduleContext ))
compactProjectDataVisitors getData visitors =
if List.isEmpty visitors then
[]
else
[ \rawData moduleContext ->
let
data : data
data =
getData rawData
in
( []
, List.foldl
(\visitor moduleContext_ -> visitor data moduleContext_)
moduleContext
(List.reverse visitors)
)
]
{-| This function that is supplied by the user will be stored in the `ProjectRuleSchema`, {-| This function that is supplied by the user will be stored in the `ProjectRuleSchema`,
but it contains an extensible record. This means that `ProjectRuleSchema` will but it contains an extensible record. This means that `ProjectRuleSchema` will
need an additional type variable for no useful value. Because we have full control need an additional type variable for no useful value. Because we have full control
@ -222,30 +251,99 @@ removeExtensibleRecordTypeVariable function =
function >> (\(ModuleRuleSchema param) -> ModuleRuleSchema param) function >> (\(ModuleRuleSchema param) -> ModuleRuleSchema param)
{-| Add a visitor to the [`ModuleRuleSchema`](#ModuleRuleSchema) which will visit the project's
[`elm.json`](https://package.elm-lang.org/packages/elm/project-metadata-utils/latest/Elm-Project) file.
--runModuleRule_New The following example forbids exposing a module in an "Internal" directory in your `elm.json` file.
-- (reverseVisitors_New moduleVisitor)
-- Nothing import Elm.Module
-- |> Rule schema.name Exceptions.init import Elm.Project
--fromProjectRuleSchema : ProjectRuleSchema { schemaState | hasAtLeastOneVisitor : () } projectContext moduleContext -> Rule import Elm.Syntax.Module as Module exposing (Module)
--fromProjectRuleSchema ((ProjectRuleSchema schema) as projectRuleSchema) = import Elm.Syntax.Node as Node exposing (Node)
-- Rule schema.name import Review.Rule as Rule exposing (Error, Rule)
-- Exceptions.init
-- (Review.Visitor.run (fromProjectRuleSchemaToRunnableProjectVisitor projectRuleSchema) Nothing) type alias Context =
--fromProjectRuleSchemaToRunnableProjectVisitor : ProjectRuleSchema schemaState projectContext moduleContext -> Review.Visitor.RunnableProjectVisitor projectContext moduleContext Maybe Elm.Project.Project
--fromProjectRuleSchemaToRunnableProjectVisitor (ProjectRuleSchema schema) =
-- { name = schema.name rule : Rule
-- , initialProjectContext = schema.initialProjectContext rule =
-- , elmJsonVisitors = List.reverse schema.elmJsonVisitors Rule.newModuleRuleSchema "DoNoExposeInternalModules" Nothing
-- , readmeVisitors = List.reverse schema.readmeVisitors |> Rule.withElmJsonModuleVisitor elmJsonVisitor
-- , dependenciesVisitors = List.reverse schema.dependenciesVisitors |> Rule.withModuleDefinitionVisitor moduleDefinitionVisitor
-- , moduleVisitor = mergeModuleVisitors schema.name schema.moduleContextCreator schema.moduleVisitors |> Rule.fromModuleRuleSchema
-- , folder = schema.folder
-- elmJsonVisitor : Maybe Elm.Project.Project -> Context -> Context
-- -- TODO Jeroen Only allow to set it if there is a folder, but not several times elmJsonVisitor elmJson context =
-- , traversalType = schema.traversalType elmJson
-- , finalEvaluationFns = List.reverse schema.finalEvaluationFns
-- } moduleDefinitionVisitor : Node Module -> Context -> ( List (Error {}), Context )
moduleDefinitionVisitor node context =
let
moduleName : List String
moduleName =
Node.value node |> Module.moduleName
in
if List.member "Internal" moduleName then
case context of
Just (Elm.Project.Package { exposed }) ->
let
exposedModules : List String
exposedModules =
case exposed of
Elm.Project.ExposedList names ->
names
|> List.map Elm.Module.toString
Elm.Project.ExposedDict fakeDict ->
fakeDict
|> List.concatMap Tuple.second
|> List.map Elm.Module.toString
in
if List.member (String.join "." moduleName) exposedModules then
( [ Rule.error "Do not expose modules in `Internal` as part of the public API" (Node.range node) ], context )
else
( [], context )
_ ->
( [], context )
else
( [], context )
-}
withElmJsonModuleVisitor :
(Maybe Elm.Project.Project -> moduleContext -> moduleContext)
-> ModuleRuleSchema { schemaState | canCollectProjectData : () } moduleContext
-> ModuleRuleSchema { schemaState | canCollectProjectData : () } moduleContext
withElmJsonModuleVisitor visitor (ModuleRuleSchema schema) =
ModuleRuleSchema { schema | elmJsonVisitors = visitor :: schema.elmJsonVisitors }
{-| Add a visitor to the [`ModuleRuleSchema`](#ModuleRuleSchema) which will visit
the project's `README.md` file.
-}
withReadmeModuleVisitor :
(Maybe String -> moduleContext -> moduleContext)
-> ModuleRuleSchema { schemaState | canCollectProjectData : () } moduleContext
-> ModuleRuleSchema { schemaState | canCollectProjectData : () } moduleContext
withReadmeModuleVisitor visitor (ModuleRuleSchema schema) =
ModuleRuleSchema { schema | readmeVisitors = visitor :: schema.readmeVisitors }
{-| Add a visitor to the [`ProjectRuleSchema`](#ProjectRuleSchema) which will visit the project's
[dependencies](./Review-Project-Dependency).
You can use this look at the modules contained in dependencies, which can make the rule very precise when it targets
specific functions.
-}
withDependenciesModuleVisitor :
(Dict String Review.Project.Dependency.Dependency -> moduleContext -> moduleContext)
-> ModuleRuleSchema { schemaState | canCollectProjectData : () } moduleContext
-> ModuleRuleSchema { schemaState | canCollectProjectData : () } moduleContext
withDependenciesModuleVisitor visitor (ModuleRuleSchema schema) =
ModuleRuleSchema { schema | dependenciesVisitors = visitor :: schema.dependenciesVisitors }
withElmJsonProjectVisitor : withElmJsonProjectVisitor :
@ -360,6 +458,9 @@ mergeModuleVisitors initialProjectContext maybeModuleContextCreator visitors =
, expressionVisitorsOnEnter = [] , expressionVisitorsOnEnter = []
, expressionVisitorsOnExit = [] , expressionVisitorsOnExit = []
, finalEvaluationFns = [] , finalEvaluationFns = []
, elmJsonVisitors = []
, readmeVisitors = []
, dependenciesVisitors = []
} }
in in
Just Just

View File

@ -248,7 +248,7 @@ computeProjectContext projectVisitor project maybePreviousCache =
computeReadme () = computeReadme () =
let let
( errorsForVisitor, contextForVisitor ) = ( errorsForVisitor, contextForVisitor ) =
( elmJsonCacheEntry.errors, elmJsonCacheEntry.context ) ( [], elmJsonCacheEntry.context )
|> accumulateWithListOfVisitors projectVisitor.readmeVisitors readmeData |> accumulateWithListOfVisitors projectVisitor.readmeVisitors readmeData
in in
{ value = readmeData { value = readmeData
@ -274,6 +274,7 @@ computeProjectContext projectVisitor project maybePreviousCache =
dependenciesCacheEntry : CacheEntryFor (Dict String Review.Project.Dependency.Dependency) projectContext dependenciesCacheEntry : CacheEntryFor (Dict String Review.Project.Dependency.Dependency) projectContext
dependenciesCacheEntry = dependenciesCacheEntry =
-- TODO Rewrite these steps to make it less likely to make an error at every step
let let
dependencies : Dict String Review.Project.Dependency.Dependency dependencies : Dict String Review.Project.Dependency.Dependency
dependencies = dependencies =
@ -283,7 +284,7 @@ computeProjectContext projectVisitor project maybePreviousCache =
computeDependencies () = computeDependencies () =
let let
( errorsForVisitor, contextForVisitor ) = ( errorsForVisitor, contextForVisitor ) =
( elmJsonCacheEntry.errors, elmJsonCacheEntry.context ) ( [], readmeCacheEntry.context )
|> accumulateWithListOfVisitors projectVisitor.dependenciesVisitors dependencies |> accumulateWithListOfVisitors projectVisitor.dependenciesVisitors dependencies
in in
{ value = dependencies { value = dependencies
@ -296,7 +297,7 @@ computeProjectContext projectVisitor project maybePreviousCache =
if if
-- If the previous context stayed the same -- If the previous context stayed the same
(previousCache.readme.context /= readmeCacheEntry.context) (previousCache.readme.context /= readmeCacheEntry.context)
-- and the readme stayed the same -- and the dependencies stayed the same
|| (previousCache.dependencies.value == dependencies) || (previousCache.dependencies.value == dependencies)
then then
previousCache.dependencies previousCache.dependencies

View File

@ -4,6 +4,7 @@ import Elm.Syntax.Declaration exposing (Declaration)
import Elm.Syntax.Expression exposing (Expression) import Elm.Syntax.Expression exposing (Expression)
import Elm.Syntax.Node exposing (Node) import Elm.Syntax.Node exposing (Node)
import Review.Rule as Rule exposing (Error, Rule) import Review.Rule as Rule exposing (Error, Rule)
import Review.Rule3 as Rule3
import Review.Test import Review.Test
import Test exposing (Test, test) import Test exposing (Test, test)
@ -20,29 +21,29 @@ all =
let let
rule : Rule rule : Rule
rule = rule =
Rule.newModuleRuleSchema "TestRule" "\n0 - initial context" Rule3.newModuleRuleSchema "Visitor order" "\n0 - initial context"
|> Rule.withElmJsonModuleVisitor (\_ context -> context ++ "\n1.1 - withElmJsonModuleVisitor") |> Rule3.withElmJsonModuleVisitor (\_ context -> context ++ "\n1.1 - withElmJsonModuleVisitor")
|> Rule.withElmJsonModuleVisitor (\_ context -> context ++ "\n1.2 - withElmJsonModuleVisitor") |> Rule3.withElmJsonModuleVisitor (\_ context -> context ++ "\n1.2 - withElmJsonModuleVisitor")
|> Rule.withReadmeModuleVisitor (\_ context -> context ++ "\n2.1 - withReadmeModuleVisitor") |> Rule3.withReadmeModuleVisitor (\_ context -> context ++ "\n2.1 - withReadmeModuleVisitor")
|> Rule.withReadmeModuleVisitor (\_ context -> context ++ "\n2.2 - withReadmeModuleVisitor") |> Rule3.withReadmeModuleVisitor (\_ context -> context ++ "\n2.2 - withReadmeModuleVisitor")
|> Rule.withDependenciesModuleVisitor (\_ context -> context ++ "\n3.1 - withDependenciesModuleVisitor") |> Rule3.withDependenciesModuleVisitor (\_ context -> context ++ "\n3.1 - withDependenciesModuleVisitor")
|> Rule.withDependenciesModuleVisitor (\_ context -> context ++ "\n3.2 - withDependenciesModuleVisitor") |> Rule3.withDependenciesModuleVisitor (\_ context -> context ++ "\n3.2 - withDependenciesModuleVisitor")
|> Rule.withModuleDefinitionVisitor (\_ context -> ( [], context ++ "\n4.1 - withModuleDefinitionVisitor" )) |> Rule3.withModuleDefinitionVisitor (\_ context -> ( [], context ++ "\n4.1 - withModuleDefinitionVisitor" ))
|> Rule.withModuleDefinitionVisitor (\_ context -> ( [], context ++ "\n4.2 - withModuleDefinitionVisitor" )) |> Rule3.withModuleDefinitionVisitor (\_ context -> ( [], context ++ "\n4.2 - withModuleDefinitionVisitor" ))
|> Rule.withImportVisitor (\_ context -> ( [], context ++ "\n5.1 - withImportVisitor" )) |> Rule3.withImportVisitor (\_ context -> ( [], context ++ "\n5.1 - withImportVisitor" ))
|> Rule.withImportVisitor (\_ context -> ( [], context ++ "\n5.2 - withImportVisitor" )) |> Rule3.withImportVisitor (\_ context -> ( [], context ++ "\n5.2 - withImportVisitor" ))
|> Rule.withDeclarationListVisitor (\_ context -> ( [], context ++ "\n6.1 - withDeclarationListVisitor" )) |> Rule3.withDeclarationListVisitor (\_ context -> ( [], context ++ "\n6.1 - withDeclarationListVisitor" ))
|> Rule.withDeclarationListVisitor (\_ context -> ( [], context ++ "\n6.2 - withDeclarationListVisitor" )) |> Rule3.withDeclarationListVisitor (\_ context -> ( [], context ++ "\n6.2 - withDeclarationListVisitor" ))
|> Rule.withDeclarationEnterVisitor (\_ context -> ( [], context ++ "\n7.1 - withDeclarationEnterVisitor" )) |> Rule3.withDeclarationEnterVisitor (\_ context -> ( [], context ++ "\n7.1 - withDeclarationEnterVisitor" ))
|> Rule.withDeclarationEnterVisitor (\_ context -> ( [], context ++ "\n7.2 - withDeclarationEnterVisitor" )) |> Rule3.withDeclarationEnterVisitor (\_ context -> ( [], context ++ "\n7.2 - withDeclarationEnterVisitor" ))
|> Rule.withDeclarationExitVisitor (\_ context -> ( [], context ++ "\n10.2 - withDeclarationExitVisitor" )) |> Rule3.withDeclarationExitVisitor (\_ context -> ( [], context ++ "\n10.2 - withDeclarationExitVisitor" ))
|> Rule.withDeclarationExitVisitor (\_ context -> ( [], context ++ "\n10.1 - withDeclarationExitVisitor" )) |> Rule3.withDeclarationExitVisitor (\_ context -> ( [], context ++ "\n10.1 - withDeclarationExitVisitor" ))
|> Rule.withExpressionEnterVisitor (\_ context -> ( [], context ++ "\n8.1 - withExpressionEnterVisitor" )) |> Rule3.withExpressionEnterVisitor (\_ context -> ( [], context ++ "\n8.1 - withExpressionEnterVisitor" ))
|> Rule.withExpressionEnterVisitor (\_ context -> ( [], context ++ "\n8.2 - withExpressionEnterVisitor" )) |> Rule3.withExpressionEnterVisitor (\_ context -> ( [], context ++ "\n8.2 - withExpressionEnterVisitor" ))
|> Rule.withExpressionExitVisitor (\_ context -> ( [], context ++ "\n9.2 - withExpressionExitVisitor" )) |> Rule3.withExpressionExitVisitor (\_ context -> ( [], context ++ "\n9.2 - withExpressionExitVisitor" ))
|> Rule.withExpressionExitVisitor (\_ context -> ( [], context ++ "\n9.1 - withExpressionExitVisitor" )) |> Rule3.withExpressionExitVisitor (\_ context -> ( [], context ++ "\n9.1 - withExpressionExitVisitor" ))
|> Rule.withFinalModuleEvaluation finalEvaluation |> Rule3.withFinalModuleEvaluation finalEvaluation
|> Rule.fromModuleRuleSchema |> Rule3.fromModuleRuleSchema
finalEvaluation : Context -> List (Error {}) finalEvaluation : Context -> List (Error {})
finalEvaluation context = finalEvaluation context =