This commit is contained in:
Jeroen Engels 2019-10-30 19:51:57 +01:00
parent 841430be60
commit a879710561
5 changed files with 109 additions and 89 deletions

View File

@ -8,6 +8,7 @@
"Review",
"Review.Rule",
"Review.Project",
"Review.File",
"Review.Fix",
"Review.Test"
],

View File

@ -95,7 +95,7 @@ fileKeyVisitor fileKey context =
error : ( List String, { fileKey : Rule.FileKey, moduleNameLocation : Range } ) -> Error
error ( moduleName, { fileKey, moduleNameLocation } ) =
Rule.errorForFile fileKey
{ message = "`" ++ String.join "." moduleName ++ "` is never used."
{ message = "Module `" ++ String.join "." moduleName ++ "` is never used."
, details = [ "This module is never used. You may want to remove it to keep your project clean, and maybe detect some unused dependencies in your project." ]
}
moduleNameLocation

View File

@ -1,11 +1,17 @@
module Review exposing
( review, reviewFiles
, Error, errorModuleName, errorRuleName, errorMessage, errorDetails, errorRange, errorFixes
( parseFile, parseFiles
, review, reviewFiles
, Error, errorModuleName, errorRuleName, errorMessage, errorDetails, errorRange, errorFixes, errorFilePath
)
{-| Module to configure your review configuration and run it on a source file.
# Parsing files
@docs parseFile, parseFiles
# Reviewing
@docs review, reviewFiles
@ -13,7 +19,7 @@ module Review exposing
# Errors
@docs Error, errorModuleName, errorRuleName, errorMessage, errorDetails, errorRange, errorFixes
@docs Error, errorModuleName, errorRuleName, errorMessage, errorDetails, errorRange, errorFixes, errorFilePath
-}
@ -23,6 +29,7 @@ import Elm.Syntax.File exposing (File)
import Elm.Syntax.Module as Module
import Elm.Syntax.Node as Node
import Elm.Syntax.Range exposing (Range)
import Review.File exposing (ParsedFile, RawFile)
import Review.Fix exposing (Fix)
import Review.Project exposing (Project)
import Review.Rule as Rule exposing (Rule)
@ -38,6 +45,7 @@ like the name of the rule that emitted it and the file name.
type Error
= Error
{ moduleName : Maybe String
, filePath : String
, ruleName : String
, message : String
, details : List String
@ -78,6 +86,7 @@ review config project { path, source } =
Err _ ->
[ Error
{ moduleName = Nothing
, filePath = path
, ruleName = "ParsingError"
, message = path ++ " is not a correct Elm file"
, details =
@ -105,12 +114,8 @@ type alias RawFile =
{-| TODO documentation
-}
reviewFiles : List Rule -> Project -> List RawFile -> List Error
reviewFiles : List Rule -> Project -> List ParsedFile -> List Error
reviewFiles rules project files =
let
( parsedFiles, errors ) =
parseFiles files
in
rules
|> List.concatMap
(\rule ->
@ -121,16 +126,15 @@ reviewFiles rules project files =
fn project file
|> List.map (ruleErrorToReviewError rule)
)
parsedFiles
files
Rule.Multi fn ->
fn project parsedFiles
fn project files
|> List.map (ruleErrorToReviewError rule)
)
|> List.append errors
parseFiles : List RawFile -> ( List File, List Error )
parseFiles : List RawFile -> ( List ParsedFile, List Error )
parseFiles files =
files
|> List.map parseFile
@ -146,13 +150,21 @@ parseFiles files =
( [], [] )
parseFile : RawFile -> Result Error File
parseFile : RawFile -> Result Error ParsedFile
parseFile rawFile =
parseSource rawFile.source
|> Result.mapError
(\_ ->
case parseSource rawFile.source of
Ok ast ->
Ok
{ path = rawFile.path
, source = rawFile.source
, ast = ast
}
Err _ ->
Err <|
Error
{ moduleName = Nothing
, filePath = rawFile.path
, ruleName = "ParsingError"
, message = rawFile.path ++ " is not a correct Elm file"
, details =
@ -162,7 +174,6 @@ parseFile rawFile =
, range = { start = { row = 0, column = 0 }, end = { row = 0, column = 0 } }
, fixes = Nothing
}
)
compareErrorPositions : Error -> Error -> Order
@ -216,6 +227,7 @@ ruleErrorToReviewError : Rule -> Rule.Error -> Error
ruleErrorToReviewError rule error =
Error
{ moduleName = Nothing
, filePath = Rule.errorFilePath error
, ruleName = Rule.name rule
, message = Rule.errorMessage error
, details = Rule.errorDetails error
@ -278,3 +290,10 @@ errorRange (Error error) =
errorFixes : Error -> Maybe (List Fix)
errorFixes (Error error) =
error.fixes
{-| TODO
-}
errorFilePath : Error -> String
errorFilePath (Error error) =
error.filePath

19
src/Review/File.elm Normal file
View File

@ -0,0 +1,19 @@
module Review.File exposing (ParsedFile, RawFile)
{-| TODO Documentation
-}
import Elm.Syntax.File
type alias RawFile =
{ path : String
, source : String
}
type alias ParsedFile =
{ path : String
, source : String
, ast : Elm.Syntax.File.File
}

View File

@ -5,7 +5,7 @@ module Review.Rule exposing
, withInitialContext, withModuleDefinitionVisitor, withImportVisitor, Direction(..), withDeclarationVisitor, withDeclarationListVisitor, withExpressionVisitor, withFinalEvaluation
, withElmJsonVisitor
, withFixes
, Error, error, errorMessage, errorDetails, errorRange, errorFixes
, Error, error, errorMessage, errorDetails, errorRange, errorFixes, errorFilePath
, name, analyzer
, Analyzer(..), newMultiSchema, fromMultiSchema, newFileVisitorSchema
, FileKey, withFileKeyVisitor, errorForFile
@ -180,7 +180,7 @@ For more information on automatic fixing, read the documentation for [`Review.Fi
## Errors
@docs Error, error, errorMessage, errorDetails, errorRange, errorFixes
@docs Error, error, errorMessage, errorDetails, errorRange, errorFixes, errorFilePath
# ACCESS
@ -204,6 +204,7 @@ import Elm.Syntax.Infix exposing (InfixDirection(..))
import Elm.Syntax.Module exposing (Module)
import Elm.Syntax.Node as Node exposing (Node)
import Elm.Syntax.Range exposing (Range)
import Review.File exposing (ParsedFile)
import Review.Fix exposing (Fix)
import Review.Project exposing (Project)
@ -226,8 +227,8 @@ 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)
Single (Project -> ParsedFile -> List Error)
| Multi (Project -> List ParsedFile -> List Error)
{-| Represents a Schema for a [`Rule`](#Rule). Create one using [`newSchema`](#newSchema).
@ -389,19 +390,30 @@ fromSchema (Schema schema) =
{ name = schema.name
, analyzer =
Single
(\project file ->
(\project { path, ast } ->
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
|> schema.moduleDefinitionVisitor ast.moduleDefinition
|> accumulateList schema.importVisitor ast.imports
|> accumulate (schema.declarationListVisitor ast.declarations)
|> accumulateList (visitDeclaration schema.declarationVisitor schema.expressionVisitor) ast.declarations
|> makeFinalEvaluation schema.finalEvaluationFn
|> List.map (\(Error err) -> Error { err | filePath = path })
|> List.reverse
)
}
maybeApply : Maybe (b -> a -> a) -> b -> a -> a
maybeApply maybeFn argument =
case maybeFn of
Just fn ->
fn argument
Nothing ->
identity
type MultiSchema context
= MultiSchema
{ name : String
@ -434,17 +446,7 @@ newMultiSchema name_ { initialContext, elmJsonVisitor, fileVisitor, mergeContext
}
{-| 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 context -> Project -> List ParsedFile -> List Error
multiAnalyzer (MultiSchema schema) project =
let
initialContext : context
@ -476,13 +478,24 @@ multiAnalyzer (MultiSchema schema) project =
]
visitFileForMulti : Schema { multiFile : () } { hasAtLeastOneVisitor : () } context -> context -> File -> ( List Error, context )
visitFileForMulti (Schema schema) initialContext file =
visitFileForMulti : Schema { multiFile : () } { hasAtLeastOneVisitor : () } context -> context -> ParsedFile -> ( List Error, context )
visitFileForMulti (Schema schema) initialContext { path, ast } =
initialContext
|> schema.moduleDefinitionVisitor file.moduleDefinition
|> accumulateList schema.importVisitor file.imports
|> accumulate (schema.declarationListVisitor file.declarations)
|> accumulateList (visitDeclaration schema.declarationVisitor schema.expressionVisitor) file.declarations
|> maybeApply schema.fileKeyVisitor (FileKey path)
|> schema.moduleDefinitionVisitor ast.moduleDefinition
|> accumulateList schema.importVisitor ast.imports
|> accumulate (schema.declarationListVisitor ast.declarations)
|> accumulateList (visitDeclaration schema.declarationVisitor schema.expressionVisitor) ast.declarations
{-| TODO documentation
-}
fromMultiSchema : MultiSchema context -> Rule
fromMultiSchema ((MultiSchema schema) as multiSchema) =
Rule
{ name = schema.name
, analyzer = Multi (multiAnalyzer multiSchema)
}
{-| Concatenate the errors of the previous step and of the last step.
@ -1263,51 +1276,9 @@ 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
]
_ ->
[]
{-| TODO
-}
withFileKeyVisitor : (FileKey -> context -> context) -> Schema anytype { hasNoVisitor : () } context -> Schema anytype { hasNoVisitor : () } context
withFileKeyVisitor : (FileKey -> context -> context) -> Schema { multiFile : () } { hasNoVisitor : () } context -> Schema { multiFile : () } { hasNoVisitor : () } context
withFileKeyVisitor visitor (Schema schema) =
Schema { schema | fileKeyVisitor = Just visitor }
@ -1327,6 +1298,7 @@ name of the rule that emitted it and the file name.
type Error
= Error
{ message : String
, filePath : String
, details : List String
, range : Range
, fixes : Maybe (List Fix)
@ -1359,6 +1331,7 @@ error : { message : String, details : List String } -> Range -> Error
error { message, details } range =
Error
{ message = message
, filePath = ""
, details = details
, range = range
, fixes = Nothing
@ -1384,12 +1357,13 @@ by the tests automatically.
-}
errorForFile : FileKey -> { message : String, details : List String } -> Range -> Error
errorForFile fileKey { message, details } range =
errorForFile (FileKey path) { message, details } range =
-- TODO Use fileKey
Error
{ message = message
, details = details
, range = range
, filePath = path
, fixes = Nothing
}
@ -1457,6 +1431,13 @@ errorFixes (Error err) =
err.fixes
{-| TODO
-}
errorFilePath : Error -> String
errorFilePath (Error err) =
err.filePath
-- ACCESS