Continue exploring the multifile API

This commit is contained in:
Jeroen Engels 2019-10-21 10:26:11 +02:00
parent 894e3ac1c9
commit a8bbd06ae3
4 changed files with 350 additions and 57 deletions

View File

@ -24,8 +24,9 @@ Problems to fix:
- [ ] Figure if it makes sense to have a finalEvaluationFn, and potentially forbid it?
- [ ] Be able to run both types of rules and get a list of errors
- [ ] Find great type and functions names
- [ ] Make a nice API for when the multifile context is different as the file visitor's
- [ ] Make a nice API for when the multifile context is the same as the file visitor's
- [ ] Folding context
- [ ] Make a nice API for when the multi-file context is different as the file visitor's
- [ ] Make a nice API for when the multi-file context is the same as the file visitor's
- [ ] Add a way to test multi-file rules
- [ ] Make sure that the order does not matter by running a rule several
times with a different order for the files every time.
@ -33,12 +34,19 @@ Problems to fix:
Errors
- [ ] Define a way to report errors in other files?
A FileKey/FileId similar to a Navigation.Key? It has no useful meaning, but
makes it so you can't give an error to a non-existing file or file you haven't visited.
- A FileKey/FileId similar to a Navigation.Key? It has no useful meaning, but
makes it so you can't give an error to a non-existing file or file you haven't visited.
- Needed
- [ ] Define a way to report errors in elm.json?
- [ ] Get rid of Review.Error
- [ ] Need to be able to create an error without a file in Review.Rule
Renaming
- [ ] Have the current package be more multi-purpose?
It paves the way for other potential tools, like codemods, code crawlers
(to gather data and do something with it, like code generation).
## Definition

View File

@ -77,13 +77,19 @@ rule =
|> Rule.fromSchema
fileVisitor : Context -> Rule.Schema { multiFile : (), hasNoVisitor : (), hasAtLeastOneVisitor : () } Context
fileVisitor context =
Rule.schemaForFile
|> Rule.withInitialContext context
Rule.newFileVisitorSchema context
|> Rule.withFileKeyVisitor fileKeyVisitor
|> Rule.withModuleDefinitionVisitor moduleDefinitionVisitor
|> Rule.withImportVisitor importVisitor
fileKeyVisitor : Rule.FileKey -> Context -> Context
fileKeyVisitor fileKey context =
{ context | fileKey = fileKey }
error : { file : File, moduleNameLocation : Range } -> List String -> Error
error { file, range } moduleName =
Rule.errorForFile file

View File

@ -1,5 +1,5 @@
module Review exposing
( review
( review, reviewFiles
, Error, errorModuleName, errorRuleName, errorMessage, errorDetails, errorRange, errorFixes
)
@ -8,7 +8,7 @@ module Review exposing
# Reviewing
@docs review
@docs review, reviewFiles
# Errors
@ -92,16 +92,77 @@ review config project { path, source } =
reviewWithRule : Project -> String -> File -> Rule -> List Error
reviewWithRule project path file rule =
Rule.analyzer rule project file
|> List.map (ruleErrorToReviewError (moduleName file) rule)
-- Rule.analyzer rule project file
-- |> List.map (ruleErrorToReviewError (moduleName file) rule)
Debug.todo "Not done"
moduleName : File -> String
moduleName file =
file.moduleDefinition
|> Node.value
|> Module.moduleName
|> String.join "."
type alias RawFile =
{ path : String
, source : String
}
{-| TODO documentation
-}
reviewFiles : List Rule -> Project -> List RawFile -> List Error
reviewFiles rules project files =
let
( parsedFiles, errors ) =
parseFiles files
in
rules
|> List.concatMap
(\rule ->
case Rule.analyzer rule of
Rule.Single fn ->
List.concatMap
(\file ->
fn project file
|> List.map (ruleErrorToReviewError rule)
)
parsedFiles
Rule.Multi fn ->
fn project parsedFiles
|> List.map (ruleErrorToReviewError rule)
)
|> List.append errors
parseFiles : List RawFile -> ( List File, List Error )
parseFiles files =
files
|> List.map parseFile
|> List.foldl
(\result ( files_, errors_ ) ->
case result of
Ok file ->
( file :: files_, errors_ )
Err error ->
( files_, error :: errors_ )
)
( [], [] )
parseFile : RawFile -> Result Error File
parseFile rawFile =
parseSource rawFile.source
|> Result.mapError
(\_ ->
Error
{ moduleName = Nothing
, ruleName = "ParsingError"
, message = rawFile.path ++ " is not a correct Elm file"
, details =
[ "I could not understand the content of this file, and this prevents me from analyzing it. It is highly likely that the content of the file is not correct Elm code."
, "Hint: Try running `elm make`. The compiler should give you better hints on how to resolve the problem."
]
, range = { start = { row = 0, column = 0 }, end = { row = 0, column = 0 } }
, fixes = Nothing
}
)
compareErrorPositions : Error -> Error -> Order
@ -151,10 +212,10 @@ compareRange a b =
EQ
ruleErrorToReviewError : String -> Rule -> Rule.Error -> Error
ruleErrorToReviewError moduleName_ rule error =
ruleErrorToReviewError : Rule -> Rule.Error -> Error
ruleErrorToReviewError rule error =
Error
{ moduleName = Just moduleName_
{ moduleName = Nothing
, ruleName = Rule.name rule
, message = Rule.errorMessage error
, details = Rule.errorDetails error

View File

@ -7,6 +7,8 @@ module Review.Rule exposing
, withFixes
, Error, error, errorMessage, errorDetails, errorRange, errorFixes
, name, analyzer
, Analyzer(..), newMultiSchema, fromMultiSchema, newFileVisitorSchema
, FileKey, withFileKeyVisitor, errorForFile
)
{-| This module contains functions that are used for writing rules.
@ -185,6 +187,12 @@ For more information on automatic fixing, read the documentation for [`Review.Fi
@docs name, analyzer
# TODO
@docs Analyzer, newMultiSchema, fromMultiSchema, newFileVisitorSchema
@docs FileKey, withFileKeyVisitor, errorForFile
-}
import Elm.Project
@ -202,14 +210,26 @@ import Review.Project exposing (Project)
{-| Represents a construct able to analyze a `File` and report unwanted patterns.
See [`newSchema`](#newSchema), and [`fromSchema`](#fromSchema) for how to create one.
TODO Explain about single and multi-file rules
-}
type Rule
= Rule
{ name : String
, analyzer : Project -> File -> List Error
, analyzer : Analyzer
}
{-| TODO describe
TODO move this to its module, where the details can be hidden from the package.
May need to move the Rule type in there too?
-}
type Analyzer
= -- TODO Can't Single also be (Project -> List File -> List Error)?
-- Have the file processing be done in this file rather than in Review.elm
Single (Project -> File -> List Error)
| Multi (Project -> List File -> List Error)
{-| Represents a Schema for a [`Rule`](#Rule). Create one using [`newSchema`](#newSchema).
import Review.Rule as Rule exposing (Rule)
@ -248,6 +268,7 @@ type
= Schema
{ name : String
, initialContext : context
, fileKeyVisitor : Maybe (FileKey -> context -> context)
, elmJsonVisitor : Maybe Elm.Project.Project -> context -> context
, moduleDefinitionVisitor : Node Module -> context -> ( List Error, context )
, importVisitor : Node Import -> context -> ( List Error, context )
@ -320,40 +341,149 @@ take a look at [`withInitialContext`](#withInitialContext) and "with\*" function
|> Rule.fromSchema
-}
newSchema : String -> Schema { hasNoVisitor : () } ()
newSchema : String -> Schema { singleFile : (), hasNoVisitor : () } ()
newSchema name_ =
Schema
{ name = name_
, initialContext = ()
, elmJsonVisitor = \elmJson context -> context
, moduleDefinitionVisitor = \node context -> ( [], context )
, importVisitor = \node context -> ( [], context )
, declarationListVisitor = \declarationNodes context -> ( [], context )
, declarationVisitor = \node direction context -> ( [], context )
, expressionVisitor = \node direction context -> ( [], context )
, finalEvaluationFn = \context -> []
}
emptySchema name_ ()
{-| Creates a new schema for a rule. Will require calling [`fromSchema`](#fromSchema)
to create a usable [`Rule`](#Rule). Use "with\*" functions from this module, like
[`withSimpleExpressionVisitor`](#withSimpleExpressionVisitor) or [`withSimpleImportVisitor`](#withSimpleImportVisitor)
to make it report something.
import Review.Rule as Rule exposing (Rule)
rule : Rule
rule =
Rule.newSchema "NoDebug"
|> Rule.withSimpleExpressionVisitor expressionVisitor
|> Rule.withSimpleImportVisitor importVisitor
|> Rule.fromSchema
If you wish to build a [`Rule`](#Rule) that collects data as the file gets traversed,
take a look at [`withInitialContext`](#withInitialContext) and "with\*" functions without
"Simple" in their name, like [`withExpressionVisitor`](#withExpressionVisitor),
[`withImportVisitor`](#withImportVisitor) or [`withFinalEvaluation`](#withFinalEvaluation).
import Review.Rule as Rule exposing (Rule)
rule : Rule
rule =
Rule.newSchema "NoUnusedVariables"
|> Rule.withInitialContext { declaredVariables = [], usedVariables = [] }
|> Rule.withExpressionVisitor expressionVisitor
|> Rule.withImportVisitor importVisitor
|> Rule.fromSchema
-}
newFileVisitorSchema : context -> Schema { multiFile : (), hasNoVisitor : () } context
newFileVisitorSchema initialContext =
emptySchema "" initialContext
{-| Create a [`Rule`](#Rule) from a configured [`Schema`](#Schema).
-}
fromSchema : Schema { hasAtLeastOneVisitor : () } context -> Rule
fromSchema : Schema { a | hasAtLeastOneVisitor : () } context -> Rule
fromSchema (Schema schema) =
Rule
{ name = schema.name
, analyzer =
\project file ->
schema.initialContext
|> schema.elmJsonVisitor (Review.Project.elmJson project)
|> schema.moduleDefinitionVisitor file.moduleDefinition
|> accumulateList schema.importVisitor file.imports
|> accumulate (schema.declarationListVisitor file.declarations)
|> accumulateList (visitDeclaration schema.declarationVisitor schema.expressionVisitor) file.declarations
|> makeFinalEvaluation schema.finalEvaluationFn
|> List.reverse
Single
(\project file ->
schema.initialContext
|> schema.elmJsonVisitor (Review.Project.elmJson project)
|> schema.moduleDefinitionVisitor file.moduleDefinition
|> accumulateList schema.importVisitor file.imports
|> accumulate (schema.declarationListVisitor file.declarations)
|> accumulateList (visitDeclaration schema.declarationVisitor schema.expressionVisitor) file.declarations
|> makeFinalEvaluation schema.finalEvaluationFn
|> List.reverse
)
}
type MultiSchema context
= MultiSchema
{ name : String
, initialContext : context
, elmJsonVisitor : Maybe (Maybe Elm.Project.Project -> context -> context)
, mergeContexts : context -> context -> context
, fileVisitor : context -> Schema { multiFile : (), hasAtLeastOneVisitor : () } context
, finalEvaluationFn : context -> List Error
}
newMultiSchema :
String
->
{ initialContext : context
, fileVisitor : context -> Schema { multiFile : (), hasAtLeastOneVisitor : () } context
, mergeContexts : context -> context -> context
, finalEvaluation : context -> List Error
}
-> MultiSchema context
newMultiSchema name_ { initialContext, fileVisitor, mergeContexts, finalEvaluation } =
MultiSchema
{ name = name_
, initialContext = initialContext
, elmJsonVisitor = Nothing
, mergeContexts = mergeContexts
, fileVisitor = fileVisitor
, finalEvaluationFn = finalEvaluation
}
{-| TODO documentation
-}
fromMultiSchema : MultiSchema context -> Rule
fromMultiSchema ((MultiSchema schema) as multiSchema) =
Rule
{ name = schema.name
, analyzer = Multi (multiAnalyzer multiSchema)
}
multiAnalyzer : MultiSchema context -> Project -> List File -> List Error
multiAnalyzer (MultiSchema schema) project =
let
initialContext : context
initialContext =
case schema.elmJsonVisitor of
Just elmJsonVisitor ->
elmJsonVisitor (Review.Project.elmJson project) schema.initialContext
Nothing ->
schema.initialContext
in
\files ->
let
contextsAndErrorsPerFile : List ( List Error, context )
contextsAndErrorsPerFile =
List.map
(visitFileForMulti
(schema.fileVisitor initialContext)
initialContext
)
files
in
List.concat
[ List.concatMap Tuple.first contextsAndErrorsPerFile
, contextsAndErrorsPerFile
|> List.map Tuple.second
|> List.foldl schema.mergeContexts schema.initialContext
|> schema.finalEvaluationFn
]
visitFileForMulti : Schema { multiFile : (), hasAtLeastOneVisitor : () } context -> context -> File -> ( List Error, context )
visitFileForMulti (Schema schema) initialContext file =
initialContext
|> schema.moduleDefinitionVisitor file.moduleDefinition
|> accumulateList schema.importVisitor file.imports
|> accumulate (schema.declarationListVisitor file.declarations)
|> accumulateList (visitDeclaration schema.declarationVisitor schema.expressionVisitor) file.declarations
{-| Concatenate the errors of the previous step and of the last step.
-}
makeFinalEvaluation : (context -> List Error) -> ( List Error, context ) -> List Error
@ -393,7 +523,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.
-}
withSimpleModuleDefinitionVisitor : (Node Module -> List Error) -> Schema anything context -> Schema { hasAtLeastOneVisitor : () } context
withSimpleModuleDefinitionVisitor : (Node Module -> List Error) -> Schema anything context -> Schema { anything | hasAtLeastOneVisitor : () } context
withSimpleModuleDefinitionVisitor visitor (Schema schema) =
Schema { schema | moduleDefinitionVisitor = \node context -> ( visitor node, context ) }
@ -442,7 +572,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.
-}
withSimpleImportVisitor : (Node Import -> List Error) -> Schema anything context -> Schema { hasAtLeastOneVisitor : () } context
withSimpleImportVisitor : (Node Import -> List Error) -> Schema anything context -> Schema { anything | hasAtLeastOneVisitor : () } context
withSimpleImportVisitor visitor (Schema schema) =
Schema { schema | importVisitor = \node context -> ( visitor node, context ) }
@ -496,7 +626,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.
-}
withSimpleDeclarationVisitor : (Node Declaration -> List Error) -> Schema anything context -> Schema { hasAtLeastOneVisitor : () } context
withSimpleDeclarationVisitor : (Node Declaration -> List Error) -> Schema anything context -> Schema { anything | hasAtLeastOneVisitor : () } context
withSimpleDeclarationVisitor visitor (Schema schema) =
Schema
{ schema
@ -551,7 +681,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.
-}
withSimpleExpressionVisitor : (Node Expression -> List Error) -> Schema anything context -> Schema { hasAtLeastOneVisitor : () } context
withSimpleExpressionVisitor : (Node Expression -> List Error) -> Schema anything context -> Schema { anything | hasAtLeastOneVisitor : () } context
withSimpleExpressionVisitor visitor (Schema schema) =
Schema
{ schema
@ -666,11 +796,17 @@ right after [`newSchema`](#newSchema) just like in the example above, as previou
"with\*" functions will be ignored.
-}
withInitialContext : context -> Schema { hasNoVisitor : () } () -> Schema { hasNoVisitor : () } context
withInitialContext : context -> Schema { anything | hasNoVisitor : () } () -> Schema { anything | hasNoVisitor : () } context
withInitialContext initialContext_ (Schema schema) =
emptySchema schema.name initialContext_
emptySchema : String -> context -> Schema anything context
emptySchema name_ initialContext =
Schema
{ name = schema.name
, initialContext = initialContext_
{ name = name_
, initialContext = initialContext
, fileKeyVisitor = Nothing
, elmJsonVisitor = \elmJson context -> context
, moduleDefinitionVisitor = \node context -> ( [], context )
, importVisitor = \node context -> ( [], context )
@ -744,7 +880,7 @@ The following example forbids exposing a file in an "Internal" directory in your
( [], context )
-}
withElmJsonVisitor : (Maybe Elm.Project.Project -> context -> context) -> Schema anything context -> Schema anything context
withElmJsonVisitor : (Maybe Elm.Project.Project -> context -> context) -> Schema { anything | singleFile : () } context -> Schema { anything | singleFile : () } context
withElmJsonVisitor visitor (Schema schema) =
Schema { schema | elmJsonVisitor = visitor }
@ -808,7 +944,7 @@ Tip: If you do not need to collect data in this visitor, you may wish to use the
simpler [`withSimpleModuleDefinitionVisitor`](#withSimpleModuleDefinitionVisitor) function.
-}
withModuleDefinitionVisitor : (Node Module -> context -> ( List Error, context )) -> Schema anything context -> Schema { hasAtLeastOneVisitor : () } context
withModuleDefinitionVisitor : (Node Module -> context -> ( List Error, context )) -> Schema anything context -> Schema { anything | hasAtLeastOneVisitor : () } context
withModuleDefinitionVisitor visitor (Schema schema) =
Schema { schema | moduleDefinitionVisitor = visitor }
@ -879,7 +1015,7 @@ Tip: If you do not need to collect or use the `context` in this visitor, you may
simpler [`withSimpleImportVisitor`](#withSimpleImportVisitor) function.
-}
withImportVisitor : (Node Import -> context -> ( List Error, context )) -> Schema anything context -> Schema { hasAtLeastOneVisitor : () } context
withImportVisitor : (Node Import -> context -> ( List Error, context )) -> Schema anything context -> Schema { anything | hasAtLeastOneVisitor : () } context
withImportVisitor visitor (Schema schema) =
Schema { schema | importVisitor = visitor }
@ -969,7 +1105,7 @@ Tip: If you do not need to collect or use the `context` in this visitor, you may
simpler [`withSimpleDeclarationVisitor`](#withSimpleDeclarationVisitor) function.
-}
withDeclarationVisitor : (Node Declaration -> Direction -> context -> ( List Error, context )) -> Schema anything context -> Schema { hasAtLeastOneVisitor : () } context
withDeclarationVisitor : (Node Declaration -> Direction -> context -> ( List Error, context )) -> Schema anything context -> Schema { anything | hasAtLeastOneVisitor : () } context
withDeclarationVisitor visitor (Schema schema) =
Schema { schema | declarationVisitor = visitor }
@ -988,7 +1124,7 @@ and [withExpressionVisitor](#withExpressionVisitor). Otherwise, using
[withDeclarationVisitor](#withDeclarationVisitor) is probably a simpler choice.
-}
withDeclarationListVisitor : (List (Node Declaration) -> context -> ( List Error, context )) -> Schema anything context -> Schema { hasAtLeastOneVisitor : () } context
withDeclarationListVisitor : (List (Node Declaration) -> context -> ( List Error, context )) -> Schema anything context -> Schema { anything | hasAtLeastOneVisitor : () } context
withDeclarationListVisitor visitor (Schema schema) =
Schema { schema | declarationListVisitor = visitor }
@ -1072,7 +1208,7 @@ Tip: If you do not need to collect or use the `context` in this visitor, you may
simpler [`withSimpleExpressionVisitor`](#withSimpleExpressionVisitor) function.
-}
withExpressionVisitor : (Node Expression -> Direction -> context -> ( List Error, context )) -> Schema anything context -> Schema { hasAtLeastOneVisitor : () } context
withExpressionVisitor : (Node Expression -> Direction -> context -> ( List Error, context )) -> Schema anything context -> Schema { anything | hasAtLeastOneVisitor : () } context
withExpressionVisitor visitor (Schema schema) =
Schema { schema | expressionVisitor = visitor }
@ -1121,11 +1257,60 @@ for [`withImportVisitor`](#withImportVisitor), but using [`withFinalEvaluation`]
[]
-}
withFinalEvaluation : (context -> List Error) -> Schema { hasAtLeastOneVisitor : () } context -> Schema { hasAtLeastOneVisitor : () } context
withFinalEvaluation : (context -> List Error) -> Schema { anything | hasAtLeastOneVisitor : () } context -> Schema { anything | hasAtLeastOneVisitor : () } context
withFinalEvaluation visitor (Schema schema) =
Schema { schema | finalEvaluationFn = visitor }
{-| Add a function that makes a final evaluation based only on the data that was
collected in the `context`. This can be useful if you can't or if it is hard to
determine something as you traverse the file.
The following example forbids importing both `Element` (`elm-ui`) and
`Html.Styled` (`elm-css`). Note that this is the same one written in the example
for [`withImportVisitor`](#withImportVisitor), but using [`withFinalEvaluation`](#withFinalEvaluation).
import Dict as Dict exposing (Dict)
import Elm.Syntax.Import exposing (Import)
import Elm.Syntax.Node as Node exposing (Node)
import Elm.Syntax.Range exposing (Range)
import Review.Rule as Rule exposing (Error, Rule)
type alias Context =
Dict (List String) Range
rule : Rule
rule =
Rule.newSchema "NoUsingBothHtmlAndHtmlStyled"
|> Rule.withInitialContext Dict.empty
|> Rule.withImportVisitor importVisitor
|> Rule.withFinalEvaluation finalEvaluation
|> Rule.fromSchema
importVisitor : Node Import -> Context -> ( List Error, Context )
importVisitor node context =
( [], Dict.insert (Node.value node |> .moduleName |> Node.value) (Node.range node) context )
finalEvaluation : Context -> List Error
finalEvaluation context =
case ( Dict.get [ "Element" ] context, Dict.get [ "Html", "Styled" ] context ) of
( Just elmUiRange, Just _ ) ->
[ Rule.error
{ message = "Do not use both `elm-ui` and `elm-css`"
, details = [ "At fruits.com, we use `elm-ui` in the dashboard application, and `elm-css` in the rest of the code. We want to use `elm-ui` in our new projects, but in projects using `elm-css`, we don't want to use both libraries to keep things simple." ]
}
elmUiRange
]
_ ->
[]
-}
withFileKeyVisitor : (FileKey -> context -> context) -> Schema { anything | hasNoVisitor : () } context -> Schema { anything | hasNoVisitor : () } context
withFileKeyVisitor visitor (Schema schema) =
Schema { schema | fileKeyVisitor = Just visitor }
-- ERRORS
@ -1147,6 +1332,10 @@ type Error
}
type FileKey
= FileKey String
{-| Creates an [`Error`](#Error). Use it when you find a pattern that the rule should forbid.
It takes the [message you want to display to the user](#a-helpful-error-message-and-details), and a [`Range`](https://package.elm-lang.org/packages/stil4m/elm-syntax/7.1.0/Elm-Syntax-Range),
which is the location where the error should be shown (under which to put the squiggly lines in an editor).
@ -1175,6 +1364,35 @@ error { message, details } range =
}
{-| Creates an [`Error`](#Error). Use it when you find a pattern that the rule should forbid.
It takes the [message you want to display to the user](#a-helpful-error-message-and-details), and a [`Range`](https://package.elm-lang.org/packages/stil4m/elm-syntax/7.1.0/Elm-Syntax-Range),
which is the location where the error should be shown (under which to put the squiggly lines in an editor).
In most cases, you can get it using [`Node.range`](https://package.elm-lang.org/packages/stil4m/elm-syntax/7.1.0/Elm-Syntax-Node#range).
The `details` is a list of strings, and each item will be visually separated
when shown to the user. The details may not be empty, and this will be enforced
by the tests automatically.
error : Node a -> Error
error node =
Rule.error
{ message = "Remove the use of `Debug` before shipping to production"
, details = [ "The `Debug` module is useful when developing, but is not meant to be shipped to production or published in a package. I suggest removing its use before committing and attempting to push to production." ]
}
(Node.range node)
-}
errorForFile : FileKey -> { message : String, details : List String } -> Range -> Error
errorForFile fileKey { message, details } range =
-- TODO Use fileKey
Error
{ message = message
, details = details
, range = range
, fixes = Nothing
}
{-| Give a list of fixes to automatically fix the error.
import Review.Fix as Fix
@ -1251,7 +1469,7 @@ name (Rule rule) =
{-| Get the analyzer function of a [`Rule`](#Rule).
-}
analyzer : Rule -> (Project -> File -> List Error)
analyzer : Rule -> Analyzer
analyzer (Rule rule) =
rule.analyzer