Compare commits

...

45 Commits

Author SHA1 Message Date
Jeroen Engels
8ca0c579c9 Name things extra files instead of arbitrary files 2023-06-02 16:55:48 +02:00
Jeroen Engels
9e0836b393 Add test to make sure files only have access to the files they requested 2023-06-02 16:50:35 +02:00
Jeroen Engels
fbf0de9db2 Add the visitor in each test 2023-06-02 16:41:18 +02:00
Jeroen Engels
9501f448b4 Add helper function for creating project in tests 2023-06-02 16:39:56 +02:00
Jeroen Engels
21b60d1385 Add TODO 2023-06-02 16:36:18 +02:00
Jeroen Engels
726e6c4dd9 Remove tuple for arbitrary files visitor 2023-06-02 16:32:07 +02:00
Jeroen Engels
d47c40e00d Set arbitraryFileRequest in module rule schema 2023-06-02 16:29:52 +02:00
Jeroen Engels
315e4dd256 Remove excess filtering 2023-06-02 16:26:53 +02:00
Jeroen Engels
5fd3bd7ffe Filter files in withArbitraryFilesProjectVisitor 2023-06-02 16:26:06 +02:00
Jeroen Engels
da5a6e95f7 Filter files in withArbitraryFilesModuleVisitor 2023-06-02 16:22:43 +02:00
Jeroen Engels
07423c1144 Rename variables 2023-06-02 16:21:02 +02:00
Jeroen Engels
8b123c989e Add arbitraryFileRequest to ModuleRuleVisitor 2023-06-02 16:17:10 +02:00
Jeroen Engels
a632cdfd12 Extract function 2023-06-02 16:15:27 +02:00
Jeroen Engels
91975300cd Add withArbitraryFilesProjectVisitor 2023-06-01 23:12:37 +02:00
Jeroen Engels
8fa511404a Fix applying fixes in the arbitrary files visitor 2023-06-01 23:12:37 +02:00
Jeroen Engels
32e71d56d5 Get the errors from the cache 2023-06-01 23:12:37 +02:00
Jeroen Engels
256bc1fe2b Fix the caching for the arbitrary files visitor
It was still using the cache of the readme visitor
2023-06-01 23:12:37 +02:00
Jeroen Engels
66698d7111 Add ValidProject.arbitraryFilesHash 2023-06-01 23:12:37 +02:00
Jeroen Engels
7a9bef3195 DEBUG Add arbitrary visitor to one rule 2023-06-01 12:58:14 +02:00
Jeroen Engels
1db67dc1f9 Add Rule.ruleRequestedFiles 2023-06-01 12:58:14 +02:00
Jeroen Engels
33eeea7a24 Filter out files that were not requested 2023-06-01 12:58:14 +02:00
Jeroen Engels
6687a200c1 Remove unused import 2023-06-01 12:58:14 +02:00
Jeroen Engels
d4efde0ed3 Use exact matches for now in tests 2023-06-01 12:58:14 +02:00
Jeroen Engels
bd2b778fa5 Add files to RequestedData 2023-06-01 12:58:14 +02:00
Jeroen Engels
8469cc9e38 Add arbitraryFileRequest to ProjectRuleSchema 2023-06-01 12:58:14 +02:00
Jeroen Engels
cd1f22ea5d Extract to function 2023-06-01 12:58:14 +02:00
Jeroen Engels
6d589bb24f Add a requested files field 2023-06-01 12:58:14 +02:00
Jeroen Engels
abf9e065e6 Add files to RequestedData 2023-06-01 12:58:14 +02:00
Jeroen Engels
a5c5f1c8d5 Add test for arbitrary files visitor 2023-06-01 12:58:14 +02:00
Jeroen Engels
f75f35a447 Add withArbitraryFilesModuleVisitor 2023-06-01 12:58:14 +02:00
Jeroen Engels
eea19151df Create arbitraryFilesVisitor from module rule 2023-06-01 12:58:14 +02:00
Jeroen Engels
f76e978bf7 Add arbitraryFilesVisitor to ModuleRuleSchema 2023-06-01 12:58:13 +02:00
Jeroen Engels
f0f7d16b35 Add arbitrary files visitor 2023-06-01 12:58:13 +02:00
Jeroen Engels
879f4c619f Add arbitraryFilesVisitor to RuleProjectVisitorOperations 2023-06-01 12:58:13 +02:00
Jeroen Engels
cf88ac4e87 Add an ArbitraryFile step 2023-06-01 12:58:13 +02:00
Jeroen Engels
c7d768de57 Add function to get the initial cache 2023-06-01 12:58:13 +02:00
Jeroen Engels
5eb9672024 Add arbitraryFilesVisitor to ProjectRuleSchema 2023-06-01 12:58:13 +02:00
Jeroen Engels
f8136851ef Add arbitraryFiles in ProjectRuleCache 2023-06-01 12:58:13 +02:00
Jeroen Engels
a7463cd9a5 Add ArbitraryFilesCache 2023-06-01 12:58:13 +02:00
Jeroen Engels
290da2b538 Add Review.Cache.ArbitraryFile 2023-06-01 12:58:13 +02:00
Jeroen Engels
e5a7db8250 Add ContentHash.areEqualForList 2023-06-01 12:58:13 +02:00
Jeroen Engels
05b06cb808 Add ValidProject.arbitraryFiles 2023-06-01 12:58:13 +02:00
Jeroen Engels
d641d7a0e2 Expose addArbitraryFiles 2023-06-01 12:58:13 +02:00
Jeroen Engels
da1ec81a1e Add a function to add arbitrary files 2023-06-01 12:58:13 +02:00
Jeroen Engels
09347602d6 Add arbitraryFiles field 2023-06-01 12:58:13 +02:00
9 changed files with 568 additions and 24 deletions

View File

@ -1,4 +1,4 @@
module Review.Cache.ContentHash exposing (ContentHash, areEqual, areEqualForMaybe, hash)
module Review.Cache.ContentHash exposing (ContentHash, areEqual, areEqualForList, areEqualForMaybe, hash)
import Vendor.Murmur3 as Murmur3
@ -28,3 +28,18 @@ areEqualForMaybe a b =
_ ->
False
areEqualForList : List ContentHash -> List ContentHash -> Bool
areEqualForList aList bList =
case aList of
[] ->
List.isEmpty bList
a :: aTail ->
case bList of
[] ->
False
b :: bTail ->
areEqual a b && areEqualForList aTail bTail

View File

@ -0,0 +1,86 @@
module Review.Cache.ExtraFile exposing
( Entry, create
, match
, errors, errorsForMaybe, setErrors
, outputContext, outputContextHash
)
{-| Cache for the result of the analysis of arbitrary files.
@docs Entry, create
@docs match
@docs errors, errorsForMaybe, setErrors
@docs outputContext, outputContextHash
-}
import Review.Cache.ContentHash as ContentHash exposing (ContentHash)
import Review.Cache.ContextHash as ContextHash exposing (ComparableContextHash, ContextHash)
type Entry error context
= Entry
{ contentHashes : List ContentHash
, inputContextHash : ComparableContextHash context
, errors : List error
, outputContext : context
, outputContextHash : ContextHash context
}
create :
{ contentHashes : List ContentHash
, inputContextHash : ComparableContextHash context
, errors : List error
, outputContext : context
}
-> Entry error context
create entry =
Entry
{ contentHashes = entry.contentHashes
, inputContextHash = entry.inputContextHash
, errors = entry.errors
, outputContext = entry.outputContext
, outputContextHash = ContextHash.create entry.outputContext
}
match : List ContentHash -> ComparableContextHash context -> Entry error context -> Bool
match contentHashes contexts (Entry entry) =
ContentHash.areEqualForList contentHashes entry.contentHashes
&& (contexts == entry.inputContextHash)
errors : Entry error context -> List error
errors (Entry entry) =
entry.errors
errorsForMaybe : Maybe (Entry error context) -> List error
errorsForMaybe maybeEntry =
case maybeEntry of
Just (Entry entry) ->
entry.errors
Nothing ->
[]
setErrors : List error -> Maybe (Entry error context) -> Maybe (Entry error context)
setErrors newErrors maybeEntry =
case maybeEntry of
Just (Entry entry) ->
Just (Entry { entry | errors = newErrors })
Nothing ->
Nothing
outputContext : Entry error context -> context
outputContext (Entry entry) =
entry.outputContext
outputContextHash : Entry error context -> ContextHash context
outputContextHash (Entry entry) =
entry.outputContextHash

View File

@ -3,6 +3,7 @@ module Review.Project exposing
, ProjectModule, addModule, addParsedModule, removeModule, modules, modulesThatFailedToParse, precomputeModuleGraph
, addElmJson, elmJson
, addReadme, readme
, addExtraFiles
, addDependency, removeDependency, removeDependencies, directDependencies, dependencies
)
@ -35,6 +36,11 @@ does not look at project information (like the `elm.json`, dependencies, ...).
@docs addReadme, readme
# REPLACEME
@docs addExtraFiles
# Project dependencies
@docs addDependency, removeDependency, removeDependencies, directDependencies, dependencies
@ -79,6 +85,7 @@ new =
, modulesThatFailedToParse = []
, elmJson = Nothing
, readme = Nothing
, extraFiles = []
, dependencies = Dict.empty
, moduleGraph = Nothing
, sourceDirectories = [ "src/" ]
@ -293,6 +300,13 @@ addReadme readme_ (Internal.Project project) =
Internal.Project { project | readme = Just ( readme_, ContentHash.hash readme_.content ) }
{-| REPLACEME
-}
addExtraFiles : List { path : String, content : String } -> Project -> Project
addExtraFiles files (Internal.Project project) =
Internal.Project { project | extraFiles = List.map (\file -> ( file, ContentHash.hash file.content )) files }
{-| Get the contents of the `README.md` file, if available.
-}
readme : Project -> Maybe { path : String, content : String }

View File

@ -25,6 +25,7 @@ type Project
, modulesThatFailedToParse : List { path : String, source : String }
, elmJson : Maybe ( { path : String, raw : String, project : Elm.Project.Project }, ContentHash )
, readme : Maybe ( { path : String, content : String }, ContentHash )
, extraFiles : List ( { path : String, content : String }, ContentHash )
, dependencies : Dict String Dependency
, moduleGraph : Maybe (Graph FilePath ())
, sourceDirectories : List String

View File

@ -9,6 +9,8 @@ module Review.Project.Valid exposing
, doesModuleExist
, elmJson
, elmJsonHash
, extraFiles
, extraFilesHash
, getModuleByPath
, moduleGraph
, moduleZipper
@ -50,6 +52,7 @@ type alias ValidProjectData =
, modulesByModuleName : Dict ModuleName OpaqueProjectModule
, elmJson : Maybe ( { path : String, raw : String, project : Elm.Project.Project }, ContentHash )
, readme : Maybe ( { path : String, content : String }, ContentHash )
, extraFiles : List ( { path : String, content : String }, ContentHash )
, dependencies : Dict String Dependency
, directDependencies : Dict String Dependency
, dependencyModules : Set ModuleName
@ -67,6 +70,7 @@ toRegularProject (ValidProject validProject) =
, modulesThatFailedToParse = []
, elmJson = validProject.elmJson
, readme = validProject.readme
, extraFiles = validProject.extraFiles
, dependencies = validProject.dependencies
, moduleGraph = Just validProject.moduleGraph
, sourceDirectories = validProject.sourceDirectories
@ -133,6 +137,7 @@ fromProjectAndGraph moduleGraph_ acyclicGraph (Project project) =
, modulesByModuleName = computeModulesByModuleName project.modules
, elmJson = project.elmJson
, readme = project.readme
, extraFiles = project.extraFiles
, dependencies = project.dependencies
, directDependencies = directDependencies_
, dependencyModules = computeDependencyModules directDependencies_
@ -308,6 +313,16 @@ readmeHash (ValidProject project) =
Maybe.map Tuple.second project.readme
extraFiles : ValidProject -> List { path : String, content : String }
extraFiles (ValidProject project) =
List.map Tuple.first project.extraFiles
extraFilesHash : ValidProject -> List ContentHash
extraFilesHash (ValidProject project) =
List.map Tuple.second project.extraFiles
dependencies : ValidProject -> Dict String Dependency
dependencies (ValidProject project) =
project.dependencies

View File

@ -1,4 +1,4 @@
module Review.RequestedData exposing (RequestedData(..), combine, combineJust, none)
module Review.RequestedData exposing (RequestedData(..), combine, combineJust, none, withFiles)
type RequestedData
@ -6,6 +6,7 @@ type RequestedData
{ moduleNameLookupTable : Bool
, sourceCodeExtractor : Bool
, ignoredFiles : Bool
, files : List String
}
@ -15,6 +16,7 @@ none =
{ moduleNameLookupTable = False
, sourceCodeExtractor = False
, ignoredFiles = False
, files = []
}
@ -33,10 +35,20 @@ combine maybeA maybeB =
a
withFiles : List String -> RequestedData -> RequestedData
withFiles files ((RequestedData requested) as untouched) =
if List.isEmpty files then
untouched
else
RequestedData { requested | files = files }
combineJust : RequestedData -> RequestedData -> RequestedData
combineJust (RequestedData a) (RequestedData b) =
RequestedData
{ moduleNameLookupTable = a.moduleNameLookupTable || b.moduleNameLookupTable
, sourceCodeExtractor = a.sourceCodeExtractor || b.sourceCodeExtractor
, ignoredFiles = a.ignoredFiles || b.ignoredFiles
, files = a.files ++ b.files
}

View File

@ -14,7 +14,8 @@ module Review.Rule exposing
, providesFixesForModuleRule
, withFinalModuleEvaluation
, withElmJsonModuleVisitor, withReadmeModuleVisitor, withDirectDependenciesModuleVisitor, withDependenciesModuleVisitor
, ProjectRuleSchema, newProjectRuleSchema, fromProjectRuleSchema, withModuleVisitor, withModuleContext, withModuleContextUsingContextCreator, withElmJsonProjectVisitor, withReadmeProjectVisitor, withDirectDependenciesProjectVisitor, withDependenciesProjectVisitor, withFinalProjectEvaluation, withContextFromImportedModules
, withExtraFilesModuleVisitor
, ProjectRuleSchema, newProjectRuleSchema, fromProjectRuleSchema, withModuleVisitor, withModuleContext, withModuleContextUsingContextCreator, withElmJsonProjectVisitor, withReadmeProjectVisitor, withDirectDependenciesProjectVisitor, withDependenciesProjectVisitor, withFinalProjectEvaluation, withExtraFilesProjectVisitor, withContextFromImportedModules
, providesFixesForProjectRule
, ContextCreator, initContextCreator, withModuleName, withModuleNameNode, withIsInSourceDirectories, withFilePath, withIsFileIgnored, withModuleNameLookupTable, withModuleKey, withSourceCodeExtractor, withFullAst, withModuleDocumentation
, Metadata, withMetadata, moduleNameFromMetadata, moduleNameNodeFromMetadata, isInSourceDirectories
@ -23,7 +24,7 @@ module Review.Rule exposing
, ReviewError, errorRuleName, errorMessage, errorDetails, errorRange, errorFilePath, errorTarget, errorFixes, errorFixFailure
, ignoreErrorsForDirectories, ignoreErrorsForFiles, filterErrorsForFiles
, withDataExtractor, preventExtract
, reviewV3, reviewV2, review, ProjectData, ruleName, ruleProvidesFixes, ruleKnowsAboutIgnoredFiles, withRuleId, getConfigurationError
, reviewV3, reviewV2, review, ProjectData, ruleName, ruleProvidesFixes, ruleKnowsAboutIgnoredFiles, ruleRequestedFiles, withRuleId, getConfigurationError
, Required, Forbidden
)
@ -211,6 +212,7 @@ Evaluating/visiting a node means two things:
## Builder functions to analyze the project's data
@docs withElmJsonModuleVisitor, withReadmeModuleVisitor, withDirectDependenciesModuleVisitor, withDependenciesModuleVisitor
@docs withExtraFilesModuleVisitor
## Creating a project rule
@ -226,7 +228,7 @@ Project rules can also report errors in the `elm.json` or the `README.md` files.
If you are new to writing rules, I would recommend learning [how to build a module rule](#creating-a-module-rule)
first, as they are in practice a simpler version of project rules.
@docs ProjectRuleSchema, newProjectRuleSchema, fromProjectRuleSchema, withModuleVisitor, withModuleContext, withModuleContextUsingContextCreator, withElmJsonProjectVisitor, withReadmeProjectVisitor, withDirectDependenciesProjectVisitor, withDependenciesProjectVisitor, withFinalProjectEvaluation, withContextFromImportedModules
@docs ProjectRuleSchema, newProjectRuleSchema, fromProjectRuleSchema, withModuleVisitor, withModuleContext, withModuleContextUsingContextCreator, withElmJsonProjectVisitor, withReadmeProjectVisitor, withDirectDependenciesProjectVisitor, withDependenciesProjectVisitor, withFinalProjectEvaluation, withExtraFilesProjectVisitor, withContextFromImportedModules
@docs providesFixesForProjectRule
@ -284,7 +286,7 @@ find the tools to extract data below.
# Running rules
@docs reviewV3, reviewV2, review, ProjectData, ruleName, ruleProvidesFixes, ruleKnowsAboutIgnoredFiles, withRuleId, getConfigurationError
@docs reviewV3, reviewV2, review, ProjectData, ruleName, ruleProvidesFixes, ruleKnowsAboutIgnoredFiles, ruleRequestedFiles, withRuleId, getConfigurationError
# Internals
@ -310,6 +312,7 @@ import Json.Encode as Encode
import Review.Cache.ContentHash exposing (ContentHash)
import Review.Cache.ContextHash as ContextHash exposing (ComparableContextHash, ContextHash)
import Review.Cache.EndAnalysis as EndAnalysisCache
import Review.Cache.ExtraFile as ExtraFile
import Review.Cache.Module as ModuleCache
import Review.Cache.ProjectFile as ProjectFileCache
import Review.ElmProjectEncoder
@ -394,12 +397,18 @@ type alias ModuleRuleSchemaData moduleContext =
-- Project visitors
, elmJsonVisitor : Maybe (Maybe Elm.Project.Project -> moduleContext -> moduleContext)
, extraFilesVisitor : Maybe (List { path : String, content : String } -> moduleContext -> moduleContext)
, extraFileRequest : ExtraFileRequest
, readmeVisitor : Maybe (Maybe String -> moduleContext -> moduleContext)
, dependenciesVisitor : Maybe (Dict String Review.Project.Dependency.Dependency -> moduleContext -> moduleContext)
, directDependenciesVisitor : Maybe (Dict String Review.Project.Dependency.Dependency -> moduleContext -> moduleContext)
}
type alias ExtraFileRequest =
List String
-- REVIEWING
@ -868,6 +877,17 @@ ruleKnowsAboutIgnoredFiles (Rule rule) =
requestedData.ignoredFiles
{-| REPLACEME
-}
ruleRequestedFiles : Rule -> List String
ruleRequestedFiles (Rule rule) =
let
(RequestedData requestedData) =
rule.requestedData
in
requestedData.files
{-| Assign an id to a rule. This id should be unique.
config =
@ -1019,6 +1039,8 @@ newModuleRuleSchema name initialModuleContext =
, caseBranchVisitorOnExit = Nothing
, finalEvaluationFn = Nothing
, elmJsonVisitor = Nothing
, extraFilesVisitor = Nothing
, extraFileRequest = []
, readmeVisitor = Nothing
, dependenciesVisitor = Nothing
, directDependenciesVisitor = Nothing
@ -1087,6 +1109,8 @@ newModuleRuleSchemaUsingContextCreator name moduleContextCreator =
, finalEvaluationFn = Nothing
, elmJsonVisitor = Nothing
, readmeVisitor = Nothing
, extraFilesVisitor = Nothing
, extraFileRequest = []
, dependenciesVisitor = Nothing
, directDependenciesVisitor = Nothing
, providesFixes = False
@ -1104,6 +1128,8 @@ fromModuleRuleSchema ((ModuleRuleSchema schema) as moduleVisitor) =
{ name = schema.name
, initialProjectContext = initialModuleContext
, elmJsonVisitor = compactProjectDataVisitors (Maybe.map .project) schema.elmJsonVisitor
, extraFilesVisitor = compactExtraFilesVisitor schema.extraFilesVisitor
, extraFileRequest = schema.extraFileRequest
, readmeVisitor = compactProjectDataVisitors (Maybe.map .content) schema.readmeVisitor
, directDependenciesVisitor = compactProjectDataVisitors identity schema.directDependenciesVisitor
, dependenciesVisitor = compactProjectDataVisitors identity schema.dependenciesVisitor
@ -1122,6 +1148,8 @@ fromModuleRuleSchema ((ModuleRuleSchema schema) as moduleVisitor) =
{ name = schema.name
, initialProjectContext = ()
, elmJsonVisitor = Nothing
, extraFilesVisitor = Nothing
, extraFileRequest = []
, readmeVisitor = Nothing
, directDependenciesVisitor = Nothing
, dependenciesVisitor = Nothing
@ -1146,6 +1174,21 @@ compactProjectDataVisitors getData maybeVisitor =
Just (\rawData moduleContext -> ( [], visitor (getData rawData) moduleContext ))
compactExtraFilesVisitor : Maybe (List { a | path : String } -> moduleContext -> moduleContext) -> Maybe (List { a | path : String } -> moduleContext -> ( List nothing, moduleContext ))
compactExtraFilesVisitor maybeExtraFilesVisitor =
case maybeExtraFilesVisitor of
Just extraFilesVisitor ->
Just (\files moduleContext -> ( [], extraFilesVisitor files moduleContext ))
Nothing ->
Nothing
globMatch : List String -> { a | path : String } -> Bool
globMatch requestedFiles file =
List.member file.path requestedFiles
-- PROJECT RULES
@ -1164,6 +1207,8 @@ type alias ProjectRuleSchemaData projectContext moduleContext =
{ name : String
, initialProjectContext : projectContext
, elmJsonVisitor : Maybe (Maybe { elmJsonKey : ElmJsonKey, project : Elm.Project.Project } -> projectContext -> ( List (Error {}), projectContext ))
, extraFilesVisitor : Maybe (List { path : String, content : String } -> projectContext -> ( List (Error {}), projectContext ))
, extraFileRequest : ExtraFileRequest
, readmeVisitor : Maybe (Maybe { readmeKey : ReadmeKey, content : String } -> projectContext -> ( List (Error {}), projectContext ))
, directDependenciesVisitor : Maybe (Dict String Review.Project.Dependency.Dependency -> projectContext -> ( List (Error {}), projectContext ))
, dependenciesVisitor : Maybe (Dict String Review.Project.Dependency.Dependency -> projectContext -> ( List (Error {}), projectContext ))
@ -1224,6 +1269,8 @@ newProjectRuleSchema name initialProjectContext =
{ name = name
, initialProjectContext = initialProjectContext
, elmJsonVisitor = Nothing
, extraFilesVisitor = Nothing
, extraFileRequest = []
, readmeVisitor = Nothing
, directDependenciesVisitor = Nothing
, dependenciesVisitor = Nothing
@ -1249,6 +1296,7 @@ fromProjectRuleSchema (ProjectRuleSchema schema) =
RequestedData.combine
(Maybe.map requestedDataFromContextCreator schema.moduleContextCreator)
(Maybe.map (.fromModuleToProject >> requestedDataFromContextCreator) schema.folder)
|> RequestedData.withFiles schema.extraFileRequest
, providesFixes = schema.providesFixes
, ruleProjectVisitor =
Ok
@ -1275,6 +1323,7 @@ removeUnknownModulesFromInitialCache validProject projectRuleCache =
emptyCache : ProjectRuleCache projectContext
emptyCache =
{ elmJson = Nothing
, extraFiles = Nothing
, readme = Nothing
, dependencies = Nothing
, moduleContexts = Dict.empty
@ -1361,6 +1410,8 @@ mergeModuleVisitorsHelp ruleName_ initialProjectContext moduleContextCreator vis
, finalEvaluationFn = Nothing
, elmJsonVisitor = Nothing
, readmeVisitor = Nothing
, extraFilesVisitor = Nothing
, extraFileRequest = []
, dependenciesVisitor = Nothing
, directDependenciesVisitor = Nothing
, providesFixes = False
@ -1809,6 +1860,27 @@ withElmJsonProjectVisitor visitor (ProjectRuleSchema schema) =
ProjectRuleSchema { schema | elmJsonVisitor = Just (combineVisitors (removeErrorPhantomTypeFromVisitor visitor) schema.elmJsonVisitor) }
{-| REPLACEME
-}
withExtraFilesProjectVisitor :
ExtraFileRequest
-> (List { path : String, content : String } -> projectContext -> ( List (Error { useErrorForModule : () }), projectContext ))
-> ProjectRuleSchema schemaState projectContext moduleContext
-> ProjectRuleSchema { schemaState | hasAtLeastOneVisitor : () } projectContext moduleContext
withExtraFilesProjectVisitor requestedFiles baseVisitor (ProjectRuleSchema schema) =
let
visitor : List { path : String, content : String } -> projectContext -> ( List (Error {}), projectContext )
visitor files context =
baseVisitor (List.filter (globMatch requestedFiles) files) context
|> Tuple.mapFirst removeErrorPhantomTypes
in
ProjectRuleSchema
{ schema
| extraFilesVisitor = Just (combineVisitors visitor schema.extraFilesVisitor)
, extraFileRequest = requestedFiles ++ schema.extraFileRequest
}
{-| Add a visitor to the [`ProjectRuleSchema`](#ProjectRuleSchema) which will visit
the project's `README.md` file.
@ -2278,6 +2350,28 @@ withElmJsonModuleVisitor visitor (ModuleRuleSchema schema) =
ModuleRuleSchema { schema | elmJsonVisitor = Just (combineContextOnlyVisitor visitor schema.elmJsonVisitor) }
{-| Add a visitor to the [`ModuleRuleSchema`](#ModuleRuleSchema) which will visit
the project's `README.md` file.
-}
withExtraFilesModuleVisitor :
ExtraFileRequest
-> (List { path : String, content : String } -> moduleContext -> moduleContext)
-> ModuleRuleSchema { schemaState | canCollectProjectData : () } moduleContext
-> ModuleRuleSchema { schemaState | canCollectProjectData : () } moduleContext
withExtraFilesModuleVisitor requestedFiles baseVisitor (ModuleRuleSchema schema) =
let
visitor : List { path : String, content : String } -> moduleContext -> moduleContext
visitor files context =
-- TODO We can skip the filter if there is only a single visitor
baseVisitor (List.filter (globMatch requestedFiles) files) context
in
ModuleRuleSchema
{ schema
| extraFilesVisitor = Just (combineContextOnlyVisitor visitor schema.extraFilesVisitor)
, extraFileRequest = requestedFiles ++ schema.extraFileRequest
}
{-| Add a visitor to the [`ModuleRuleSchema`](#ModuleRuleSchema) which will visit
the project's `README.md` file.
-}
@ -4166,6 +4260,10 @@ type alias ProjectFileCache projectContext =
ProjectFileCache.Entry (Error {}) projectContext
type alias ExtraFilesCache projectContext =
ExtraFile.Entry (Error {}) projectContext
type alias FinalProjectEvaluationCache projectContext =
EndAnalysisCache.Entry (List (Error {})) projectContext
@ -4229,7 +4327,7 @@ computeFinalContextHashes : ProjectRuleSchemaData projectContext moduleContext -
computeFinalContextHashes schema cache =
let
( projectContextHash, _ ) =
findInitialInputContext schema.initialProjectContext [ cache.dependencies, cache.readme, cache.elmJson ]
findInitialInputContext cache AfterProjectFilesStep schema.initialProjectContext
traversalAndFolder : TraversalAndFolder projectContext moduleContext
traversalAndFolder =
@ -4259,7 +4357,7 @@ computeFinalContext : ProjectRuleSchemaData projectContext moduleContext -> Proj
computeFinalContext schema cache =
let
( _, projectContext ) =
findInitialInputContext schema.initialProjectContext [ cache.dependencies, cache.readme, cache.elmJson ]
findInitialInputContext cache AfterProjectFilesStep schema.initialProjectContext
traversalAndFolder : TraversalAndFolder projectContext moduleContext
traversalAndFolder =
@ -4294,6 +4392,7 @@ errorsFromCache cache =
List.concat
[ Dict.foldl (\_ cacheEntry acc -> List.append (ModuleCache.errors cacheEntry) acc) [] cache.moduleContexts
, ProjectFileCache.errorsForMaybe cache.elmJson
, ExtraFile.errorsForMaybe cache.extraFiles
, ProjectFileCache.errorsForMaybe cache.readme
, ProjectFileCache.errorsForMaybe cache.dependencies
, Maybe.map EndAnalysisCache.output cache.finalEvaluationErrors |> Maybe.withDefault []
@ -4306,6 +4405,7 @@ errorsFromCache cache =
type alias ProjectRuleCache projectContext =
{ elmJson : Maybe (ProjectFileCache projectContext)
, extraFiles : Maybe (ExtraFilesCache projectContext)
, readme : Maybe (ProjectFileCache projectContext)
, dependencies : Maybe (ProjectFileCache projectContext)
, moduleContexts : Dict String (ModuleCacheEntry projectContext)
@ -4336,6 +4436,16 @@ computeStepsForProject reviewOptions { project, ruleProjectVisitors, fixedErrors
reviewOptions
(computeElmJson reviewOptions project fixedErrors elmJsonData ruleProjectVisitors [])
ExtraFiles ->
let
extraFiles : List { path : String, content : String }
extraFiles =
ValidProject.extraFiles project
in
computeStepsForProject
reviewOptions
(computeExtraFiles reviewOptions project fixedErrors extraFiles ruleProjectVisitors [])
Readme ->
let
readmeData : Maybe { readmeKey : ReadmeKey, content : String }
@ -4389,6 +4499,7 @@ computeStepsForProject reviewOptions { project, ruleProjectVisitors, fixedErrors
type Step
= ElmJson
| ExtraFiles
| Readme
| Dependencies
| Modules (Zipper GraphModule)
@ -4396,6 +4507,14 @@ type Step
| EndAnalysis
type StepToComputeContext
= ElmJsonStep
| ExtraFilesStep
| ReadmeStep
| DependenciesStep
| AfterProjectFilesStep
type NextStep
= ModuleVisitStep (Maybe (Zipper GraphModule))
| BackToElmJson
@ -4415,7 +4534,7 @@ computeElmJson reviewOptions project fixedErrors elmJsonData remainingRules accR
case remainingRules of
[] ->
{ project = project
, step = Readme
, step = ExtraFiles
, ruleProjectVisitors = accRules
, fixedErrors = fixedErrors
}
@ -4454,6 +4573,57 @@ computeElmJson reviewOptions project fixedErrors elmJsonData remainingRules accR
(untouched :: accRules)
computeExtraFiles :
ReviewOptionsData
-> ValidProject
-> FixedErrors
-> List { path : String, content : String }
-> List RuleProjectVisitor
-> List RuleProjectVisitor
-> { project : ValidProject, ruleProjectVisitors : List RuleProjectVisitor, step : Step, fixedErrors : FixedErrors }
computeExtraFiles reviewOptions project fixedErrors extraFiles remainingRules accRules =
case remainingRules of
[] ->
{ project = project
, step = Readme
, ruleProjectVisitors = accRules
, fixedErrors = fixedErrors
}
((RuleProjectVisitor rule) as untouched) :: rest ->
case rule.extraFilesVisitor of
Just visitor ->
let
( errors, RuleProjectVisitor updatedRule ) =
visitor project extraFiles
in
case standardFindFix reviewOptions project fixedErrors updatedRule.setErrorsForExtraFiles errors of
FoundFixStandard { newProject, newRule, newFixedErrors, step } ->
{ project = newProject
, ruleProjectVisitors = newRule :: (rest ++ accRules)
, step = step
, fixedErrors = newFixedErrors
}
FoundNoFixesStandard newRule ->
computeExtraFiles
reviewOptions
project
fixedErrors
extraFiles
rest
(newRule :: accRules)
Nothing ->
computeExtraFiles
reviewOptions
project
fixedErrors
extraFiles
rest
(untouched :: accRules)
computeReadme :
ReviewOptionsData
-> ValidProject
@ -5419,6 +5589,7 @@ The hidden state is `{ cache : ProjectRuleCache projectContext, ruleData : Chang
type alias RuleProjectVisitorOperations =
{ elmJsonVisitor : Maybe (ValidProject -> Maybe { elmJsonKey : ElmJsonKey, project : Elm.Project.Project } -> ( List (Error {}), RuleProjectVisitor ))
, readmeVisitor : Maybe (ValidProject -> Maybe { readmeKey : ReadmeKey, content : String } -> ( List (Error {}), RuleProjectVisitor ))
, extraFilesVisitor : Maybe (ValidProject -> List { path : String, content : String } -> ( List (Error {}), RuleProjectVisitor ))
, dependenciesVisitor : Maybe (ValidProject -> { all : Dict String Review.Project.Dependency.Dependency, direct : Dict String Review.Project.Dependency.Dependency } -> ( List (Error {}), RuleProjectVisitor ))
, createModuleVisitorFromProjectVisitor : Maybe (ValidProject -> String -> ContentHash -> Graph.Adjacency () -> Maybe (AvailableData -> RuleModuleVisitor))
, finalProjectEvaluation : Maybe (() -> ( List (Error {}), RuleProjectVisitor ))
@ -5427,6 +5598,7 @@ type alias RuleProjectVisitorOperations =
, getErrors : () -> List (Error {})
, setErrorsForModule : String -> List (Error {}) -> RuleProjectVisitor
, setErrorsForElmJson : List (Error {}) -> RuleProjectVisitor
, setErrorsForExtraFiles : List (Error {}) -> RuleProjectVisitor
, setErrorsForReadme : List (Error {}) -> RuleProjectVisitor
, setErrorsForDependencies : List (Error {}) -> RuleProjectVisitor
, setErrorsForFinalEvaluation : List (Error {}) -> RuleProjectVisitor
@ -5446,8 +5618,9 @@ createRuleProjectVisitor schema initialProject ruleData initialCache =
raise { cache = newCache, ruleData = hidden.ruleData }
in
RuleProjectVisitor
{ elmJsonVisitor = createProjectVisitor schema hidden schema.elmJsonVisitor [] ValidProject.elmJsonHash .elmJson (\entry -> raiseCache { cache | elmJson = Just entry }) (\() -> raise hidden)
, readmeVisitor = createProjectVisitor schema hidden schema.readmeVisitor [ cache.elmJson ] ValidProject.readmeHash .readme (\entry -> raiseCache { cache | readme = Just entry }) (\() -> raise hidden)
{ elmJsonVisitor = createProjectVisitor schema hidden schema.elmJsonVisitor ElmJsonStep ValidProject.elmJsonHash .elmJson (\entry -> raiseCache { cache | elmJson = Just entry }) (\() -> raise hidden)
, extraFilesVisitor = createExtraFilesVisitor schema hidden schema.extraFilesVisitor ExtraFilesStep (\entry -> raiseCache { cache | extraFiles = Just entry }) (\() -> raise hidden)
, readmeVisitor = createProjectVisitor schema hidden schema.readmeVisitor ReadmeStep ValidProject.readmeHash .readme (\entry -> raiseCache { cache | readme = Just entry }) (\() -> raise hidden)
, dependenciesVisitor = createDependenciesVisitor schema hidden.ruleData raiseCache cache { allVisitor = schema.dependenciesVisitor, directVisitor = schema.directDependenciesVisitor }
, createModuleVisitorFromProjectVisitor = createModuleVisitorFromProjectVisitor schema raiseCache hidden
, finalProjectEvaluation = createFinalProjectEvaluationVisitor schema hidden.ruleData raiseCache cache
@ -5456,6 +5629,7 @@ createRuleProjectVisitor schema initialProject ruleData initialCache =
, getErrors = \() -> errorsFromCache (finalCacheMarker schema.name hidden.ruleData.ruleId cache)
, setErrorsForModule = \filePath newErrors -> raiseCache { cache | moduleContexts = Dict.update filePath (Maybe.map (\entry -> ModuleCache.setErrors newErrors entry)) cache.moduleContexts }
, setErrorsForElmJson = \newErrors -> raiseCache { cache | elmJson = ProjectFileCache.setErrors newErrors cache.elmJson }
, setErrorsForExtraFiles = \newErrors -> raiseCache { cache | extraFiles = ExtraFile.setErrors newErrors cache.extraFiles }
, setErrorsForReadme = \newErrors -> raiseCache { cache | readme = ProjectFileCache.setErrors newErrors cache.readme }
, setErrorsForDependencies = \newErrors -> raiseCache { cache | dependencies = ProjectFileCache.setErrors newErrors cache.dependencies }
, setErrorsForFinalEvaluation = \newErrors -> raiseCache { cache | finalEvaluationErrors = EndAnalysisCache.setOutput newErrors cache.finalEvaluationErrors }
@ -5482,7 +5656,7 @@ createProjectVisitor :
ProjectRuleSchemaData projectContext moduleContext
-> RuleProjectVisitorHidden projectContext
-> Maybe (data -> projectContext -> ( List (Error {}), projectContext ))
-> List (Maybe (ProjectFileCache projectContext))
-> StepToComputeContext
-> (ValidProject -> Maybe ContentHash)
-> (ProjectRuleCache projectContext -> Maybe (ProjectFileCache projectContext))
-> (ProjectFileCache projectContext -> RuleProjectVisitor)
@ -5493,7 +5667,7 @@ createProjectVisitor :
-> data
-> ( List (Error {}), RuleProjectVisitor )
)
createProjectVisitor schema hidden maybeVisitor possibleInputContexts computeContentHash cacheGetter toRuleProjectVisitor toRuleProjectVisitorWithoutChangingCache =
createProjectVisitor schema hidden maybeVisitor step computeContentHash cacheGetter toRuleProjectVisitor toRuleProjectVisitorWithoutChangingCache =
case maybeVisitor of
Nothing ->
Nothing
@ -5503,7 +5677,7 @@ createProjectVisitor schema hidden maybeVisitor possibleInputContexts computeCon
(\project data ->
let
( baseInputContextHash, inputContext ) =
findInitialInputContext schema.initialProjectContext possibleInputContexts
findInitialInputContext hidden.cache step schema.initialProjectContext
inputContextHash : ComparableContextHash projectContext
inputContextHash =
@ -5542,6 +5716,68 @@ createProjectVisitor schema hidden maybeVisitor possibleInputContexts computeCon
)
createExtraFilesVisitor :
ProjectRuleSchemaData projectContext moduleContext
-> RuleProjectVisitorHidden projectContext
-> Maybe (data -> projectContext -> ( List (Error {}), projectContext ))
-> StepToComputeContext
-> (ExtraFilesCache projectContext -> RuleProjectVisitor)
-> (() -> RuleProjectVisitor)
->
Maybe
(ValidProject
-> data
-> ( List (Error {}), RuleProjectVisitor )
)
createExtraFilesVisitor schema hidden maybeVisitor step toRuleProjectVisitor toRuleProjectVisitorWithoutChangingCache =
case maybeVisitor of
Nothing ->
Nothing
Just visitor ->
Just
(\project data ->
let
( baseInputContextHash, inputContext ) =
findInitialInputContext hidden.cache step schema.initialProjectContext
inputContextHash : ComparableContextHash projectContext
inputContextHash =
ContextHash.toComparable baseInputContextHash
contentHashes : List ContentHash
contentHashes =
ValidProject.extraFilesHash project
cachePredicate : ExtraFilesCache projectContext -> Bool
cachePredicate extraFiles =
ExtraFile.match contentHashes inputContextHash extraFiles
in
case reuseProjectRuleCache cachePredicate .extraFiles hidden.cache of
Just entry ->
( ExtraFile.errors entry, toRuleProjectVisitorWithoutChangingCache () )
Nothing ->
let
( errorsForVisitor, outputContext ) =
visitor data inputContext
errors : List (Error {})
errors =
filterExceptionsAndSetName hidden.ruleData.exceptions schema.name errorsForVisitor
in
( errors
, ExtraFile.create
{ contentHashes = contentHashes
, errors = errors
, inputContextHash = inputContextHash
, outputContext = outputContext
}
|> toRuleProjectVisitor
)
)
createDependenciesVisitor :
ProjectRuleSchemaData projectContext moduleContext
-> ChangeableRuleData
@ -5567,7 +5803,7 @@ createDependenciesVisitor schema { exceptions } raise cache { allVisitor, direct
(\project { all, direct } ->
let
( baseInputContextHash, inputContext ) =
findInitialInputContext schema.initialProjectContext [ cache.readme, cache.elmJson ]
findInitialInputContext cache DependenciesStep schema.initialProjectContext
inputContextHash : ComparableContextHash projectContext
inputContextHash =
@ -5622,17 +5858,47 @@ createDependenciesVisitor schema { exceptions } raise cache { allVisitor, direct
)
findInitialInputContext : projectContext -> List (Maybe (ProjectFileCache projectContext)) -> ( List (ContextHash projectContext), projectContext )
findInitialInputContext defaultContext possibleInputContexts =
case possibleInputContexts of
[] ->
findInitialInputContext :
ProjectRuleCache projectContext
-> StepToComputeContext
-> projectContext
-> ( List (ContextHash projectContext), projectContext )
findInitialInputContext cache step defaultContext =
case step of
ElmJsonStep ->
( [], defaultContext )
(Just cacheEntry) :: _ ->
( [ ProjectFileCache.outputContextHash cacheEntry ], ProjectFileCache.outputContext cacheEntry )
ExtraFilesStep ->
case cache.elmJson of
Just entry ->
( [ ProjectFileCache.outputContextHash entry ], ProjectFileCache.outputContext entry )
Nothing :: rest ->
findInitialInputContext defaultContext rest
Nothing ->
findInitialInputContext cache ElmJsonStep defaultContext
ReadmeStep ->
case cache.extraFiles of
Just entry ->
( [ ExtraFile.outputContextHash entry ], ExtraFile.outputContext entry )
Nothing ->
findInitialInputContext cache ExtraFilesStep defaultContext
DependenciesStep ->
case cache.readme of
Just entry ->
( [ ProjectFileCache.outputContextHash entry ], ProjectFileCache.outputContext entry )
Nothing ->
findInitialInputContext cache ReadmeStep defaultContext
AfterProjectFilesStep ->
case cache.dependencies of
Just entry ->
( [ ProjectFileCache.outputContextHash entry ], ProjectFileCache.outputContext entry )
Nothing ->
findInitialInputContext cache DependenciesStep defaultContext
createFinalProjectEvaluationVisitor :
@ -5770,7 +6036,7 @@ createModuleVisitorFromProjectVisitorHelp schema raise hidden traversalAndFolder
\project filePath moduleContentHash incoming ->
let
( initialProjectContextHash, initialProjectContext ) =
findInitialInputContext schema.initialProjectContext [ hidden.cache.dependencies, hidden.cache.readme, hidden.cache.elmJson ]
findInitialInputContext hidden.cache AfterProjectFilesStep schema.initialProjectContext
inputContextHashes : ComparableContextHash projectContext
inputContextHashes =

View File

@ -58,6 +58,15 @@ rule : List String -> Rule
rule exceptions =
Rule.newModuleRuleSchema "NoImportingEverything" ()
|> Rule.withSimpleImportVisitor (importVisitor <| exceptionsToSet exceptions)
|> Rule.withExtraFilesModuleVisitor
[ "CHANGELOG.md" ]
(\files context ->
let
_ =
Debug.log "files" files
in
context
)
|> Rule.fromModuleRuleSchema

View File

@ -0,0 +1,126 @@
module Review.Rule.WithExtraFilesVisitorTest exposing (all)
import Review.Project as Project exposing (Project)
import Review.Rule as Rule exposing (Error, Rule)
import Review.Test
import Test exposing (Test, describe, test)
all : Test
all =
describe "Review.Rule.withExtraFilesVisitor"
[ test "passes the list of arbitrary files to the rule" <|
\() ->
let
project : Project
project =
createProject
[ { path = "foo/some-file.css", content = "#thing { color: red; }" }
]
rule : Rule
rule =
createRule (Rule.withExtraFilesModuleVisitor [ "foo/some-file.css" ] extraFilesModuleVisitor)
in
"""module A exposing (a)
a = 1
"""
|> Review.Test.runWithProjectData project rule
|> Review.Test.expectGlobalErrors
[ { message = "Found these files"
, details = [ "foo/some-file.css" ]
}
]
, test "filters out files that were not requested" <|
\() ->
let
project : Project
project =
createProject
[ { path = "foo/some-file.css", content = "#thing { color: red; }" }
, { path = "foo/some-other-file.css", content = "#thing { color: red; }" }
, { path = "bar/some-file.css", content = "#thing { color: red; }" }
]
rule : Rule
rule =
createRule (Rule.withExtraFilesModuleVisitor [ "foo/some-file.css" ] extraFilesModuleVisitor)
in
"""module A exposing (a)
a = 1
"""
|> Review.Test.runWithProjectData project rule
|> Review.Test.expectGlobalErrors
[ { message = "Found these files"
, details = [ "foo/some-file.css" ]
}
]
, test "visitors should only have access to files they requested" <|
\() ->
let
project : Project
project =
createProject
[ { path = "a.txt", content = "A" }
, { path = "b.txt", content = "B" }
, { path = "c.txt", content = "C" }
]
rule : Rule
rule =
createRule
(Rule.withExtraFilesModuleVisitor [ "a.txt", "c.txt" ] (reportsFileNames "A")
>> Rule.withExtraFilesModuleVisitor [ "b.txt" ] (reportsFileNames "B")
)
in
"""module A exposing (a)
a = 1
"""
|> Review.Test.runWithProjectData project rule
|> Review.Test.expectGlobalErrors
[ { message = "Found these files"
, details =
[ "Visitor B saw file b.txt"
, "Visitor A saw file a.txt"
, "Visitor A saw file c.txt"
]
}
]
]
type alias Context =
List String
createRule : (Rule.ModuleRuleSchema { canCollectProjectData : () } (List a) -> Rule.ModuleRuleSchema schemaState Context) -> Rule
createRule modifier =
Rule.newModuleRuleSchema "WithCommentsVisitorTestRule" []
|> modifier
|> Rule.withModuleDefinitionVisitor (\_ context -> ( [], context ))
|> Rule.withFinalModuleEvaluation finalEvaluation
|> Rule.fromModuleRuleSchema
extraFilesModuleVisitor : List { path : String, content : String } -> Context -> Context
extraFilesModuleVisitor files context =
List.map .path files ++ context
reportsFileNames : String -> List { path : String, content : String } -> Context -> Context
reportsFileNames prefix files context =
List.map (\file -> "Visitor " ++ prefix ++ " saw file " ++ file.path) files ++ context
finalEvaluation : Context -> List (Error scope)
finalEvaluation context =
[ Rule.globalError
{ message = "Found these files"
, details = context
}
]
createProject : List { path : String, content : String } -> Project
createProject extraFiles =
Project.addExtraFiles extraFiles Project.new