mirror of
https://github.com/jfmengels/elm-review.git
synced 2024-12-25 18:51:41 +03:00
Show the linting errors in the example like we do in the terminal
This commit is contained in:
parent
2274505ebc
commit
cff014d5ad
@ -1,7 +1,7 @@
|
||||
module Main exposing (main)
|
||||
|
||||
import Browser
|
||||
import Html exposing (Html, button, div, input, label, li, p, text, textarea, ul)
|
||||
import Html exposing (Html, button, div, input, label, p, text, textarea)
|
||||
import Html.Attributes as Attr
|
||||
import Html.Events as Events
|
||||
import Lint exposing (LintError, lintSource)
|
||||
@ -11,6 +11,8 @@ import Lint.Rule.NoExtraBooleanComparison
|
||||
import Lint.Rule.NoImportingEverything
|
||||
import Lint.Rule.NoUnusedTypeConstructors
|
||||
import Lint.Rule.NoUnusedVariables
|
||||
import Reporter
|
||||
import Text exposing (Text)
|
||||
|
||||
|
||||
|
||||
@ -157,19 +159,39 @@ view model =
|
||||
[ Attr.style "display" "flex"
|
||||
, Attr.style "flex-direction" "row"
|
||||
]
|
||||
[ textarea
|
||||
[ Attr.id "input"
|
||||
, Events.onInput UserEditedSourceCode
|
||||
, Attr.style "height" "500px"
|
||||
, Attr.style "width" "60%"
|
||||
[ div
|
||||
[ Attr.style "width" "60%"
|
||||
]
|
||||
[ textarea
|
||||
[ Attr.id "input"
|
||||
, Events.onInput UserEditedSourceCode
|
||||
, Attr.style "width" "100%"
|
||||
, Attr.style "height" "500px"
|
||||
]
|
||||
[ text model.sourceCode ]
|
||||
, div
|
||||
[ Attr.style "border-radius" "4px"
|
||||
, Attr.style "padding" "12px"
|
||||
, Attr.style "max-width" "100%"
|
||||
, Attr.style "width" "calc(100vw - 24px)"
|
||||
, Attr.style "overflow-x" "auto"
|
||||
, Attr.style "white-space" "pre"
|
||||
, Attr.style "color" "white"
|
||||
, Attr.style "font-family" "'Source Code Pro', monospace"
|
||||
, Attr.style "font-size" "12px"
|
||||
, Attr.style "background-color" "black"
|
||||
]
|
||||
[ lintErrors model
|
||||
|> Text.view
|
||||
]
|
||||
]
|
||||
, div
|
||||
[ Attr.style "margin-left" "2rem"
|
||||
, Attr.style "width" "40%"
|
||||
]
|
||||
[ text model.sourceCode ]
|
||||
, div [ Attr.style "margin-left" "2rem" ]
|
||||
[ viewConfigurationPanel model
|
||||
, viewConfigurationAsText model
|
||||
, p [ Attr.class "title" ] [ text "Linting errors" ]
|
||||
, ul [ Attr.id "lint" ]
|
||||
(lintErrors model)
|
||||
]
|
||||
]
|
||||
]
|
||||
@ -199,6 +221,7 @@ viewConfigurationAsText model =
|
||||
div
|
||||
[ Attr.style "display" "flex"
|
||||
, Attr.style "flex-direction" "column"
|
||||
, Attr.style "width" "100%"
|
||||
]
|
||||
[ button
|
||||
[ Attr.style "margin-top" "2rem"
|
||||
@ -294,35 +317,25 @@ viewCheckbox onClick name checked =
|
||||
]
|
||||
|
||||
|
||||
lintErrors : Model -> List (Html Msg)
|
||||
lintErrors : Model -> List Text
|
||||
lintErrors model =
|
||||
let
|
||||
messages : List String
|
||||
messages =
|
||||
case model.lintResult of
|
||||
Err errors ->
|
||||
errors
|
||||
case model.lintResult of
|
||||
Err errors ->
|
||||
errors
|
||||
|> List.map Text.from
|
||||
|
||||
Ok errors ->
|
||||
if List.isEmpty errors then
|
||||
[ "No errors." ]
|
||||
Ok errors ->
|
||||
if List.isEmpty errors then
|
||||
[ Text.from "I found no linting errors.\nYou're all good!" ]
|
||||
|
||||
else
|
||||
List.map errorToString errors
|
||||
in
|
||||
List.map
|
||||
(\message -> li [] [ text message ])
|
||||
messages
|
||||
|
||||
|
||||
errorToString : LintError -> String
|
||||
errorToString { ruleName, message, range } =
|
||||
let
|
||||
location : String
|
||||
location =
|
||||
"(line " ++ String.fromInt range.start.row ++ ", column " ++ String.fromInt range.start.column ++ ")"
|
||||
in
|
||||
ruleName ++ ": " ++ message ++ " " ++ location
|
||||
else
|
||||
Reporter.formatReport
|
||||
[ ( { name = "IN SOURCE CODE"
|
||||
, source = model.sourceCode
|
||||
}
|
||||
, errors
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
main : Program () Model Msg
|
||||
|
209
example/Reporter.elm
Normal file
209
example/Reporter.elm
Normal file
@ -0,0 +1,209 @@
|
||||
module Reporter exposing (formatReport)
|
||||
|
||||
import Array exposing (Array)
|
||||
import Elm.Syntax.Range exposing (Range)
|
||||
import Lint exposing (LintError)
|
||||
import Text exposing (Text)
|
||||
|
||||
|
||||
type alias File =
|
||||
{ name : String
|
||||
, source : String
|
||||
}
|
||||
|
||||
|
||||
formatReportForFileWithExtract : ( File, List LintError ) -> List Text
|
||||
formatReportForFileWithExtract ( file, errors ) =
|
||||
let
|
||||
formattedErrors : List (List Text)
|
||||
formattedErrors =
|
||||
List.map (formatErrorWithExtract file) errors
|
||||
|
||||
prefix : String
|
||||
prefix =
|
||||
"-- ELM-LINT ERROR "
|
||||
|
||||
header : Text
|
||||
header =
|
||||
(prefix ++ String.padLeft (80 - String.length prefix) '-' (" " ++ file.name))
|
||||
|> Text.from
|
||||
|> Text.inGreen
|
||||
in
|
||||
header :: Text.from "\n\n" :: Text.join "\n\n\n" formattedErrors
|
||||
|
||||
|
||||
formatErrorWithExtract : File -> LintError -> List Text
|
||||
formatErrorWithExtract file { ruleName, message, details, range } =
|
||||
let
|
||||
title : List Text
|
||||
title =
|
||||
[ Text.from ruleName
|
||||
|> Text.inRed
|
||||
, Text.from <| ": " ++ message
|
||||
]
|
||||
|
||||
codeExtract_ : List Text
|
||||
codeExtract_ =
|
||||
codeExtract file range
|
||||
|
||||
details_ : List Text
|
||||
details_ =
|
||||
List.map Text.from details
|
||||
|> List.intersperse (Text.from "\n\n")
|
||||
in
|
||||
[ title, codeExtract_, details_ ]
|
||||
|> List.filter (List.isEmpty >> not)
|
||||
|> List.intersperse [ Text.from "\n\n" ]
|
||||
|> List.concat
|
||||
|
||||
|
||||
codeExtract : File -> Range -> List Text
|
||||
codeExtract file =
|
||||
let
|
||||
getRowAtLine_ : Int -> String
|
||||
getRowAtLine_ =
|
||||
getRowAtLine file
|
||||
in
|
||||
\({ start, end } as range) ->
|
||||
if range.start == range.end then
|
||||
[]
|
||||
|
||||
else if start.row == end.row then
|
||||
[ Text.from <| getRowAtLine_ (start.row - 2)
|
||||
, Text.from <| getRowAtLine_ (start.row - 1)
|
||||
, underlineError (start.row - 1) { start = start.column, end = end.column }
|
||||
, Text.from <| getRowAtLine_ end.row
|
||||
]
|
||||
|
||||
else
|
||||
let
|
||||
startLine : String
|
||||
startLine =
|
||||
getRowAtLine_ (start.row - 1)
|
||||
|
||||
linesBetweenStartAndEnd : List String
|
||||
linesBetweenStartAndEnd =
|
||||
List.range start.row (end.row - 2)
|
||||
|> List.map getRowAtLine_
|
||||
|
||||
endLine : String
|
||||
endLine =
|
||||
getRowAtLine_ (end.row - 1)
|
||||
in
|
||||
List.concat
|
||||
[ [ Text.from <| getRowAtLine_ (start.row - 2)
|
||||
, Text.from <| startLine
|
||||
, underlineError
|
||||
(start.row - 1)
|
||||
{ start = start.column
|
||||
, end = String.length startLine - offsetBecauseOfLineNumber (start.row - 1)
|
||||
}
|
||||
]
|
||||
, linesBetweenStartAndEnd
|
||||
|> List.indexedMap Tuple.pair
|
||||
|> List.concatMap
|
||||
(\( lineNumber, line ) ->
|
||||
[ Text.from <| line
|
||||
, underlineError
|
||||
lineNumber
|
||||
{ start = getIndexOfFirstNonSpace (offsetBecauseOfLineNumber lineNumber) line
|
||||
, end = String.length line - offsetBecauseOfLineNumber lineNumber
|
||||
}
|
||||
]
|
||||
)
|
||||
, [ Text.from <| endLine
|
||||
, underlineError
|
||||
(end.row - 1)
|
||||
{ start = getIndexOfFirstNonSpace (offsetBecauseOfLineNumber (end.row - 1)) endLine
|
||||
, end = String.length endLine - offsetBecauseOfLineNumber (end.row - 1)
|
||||
}
|
||||
, Text.from <| getRowAtLine_ end.row
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
getIndexOfFirstNonSpace : Int -> String -> Int
|
||||
getIndexOfFirstNonSpace offset string =
|
||||
string
|
||||
|> String.indexes (String.trim <| String.dropLeft offset string)
|
||||
|> List.head
|
||||
|> Maybe.withDefault 0
|
||||
|> (\n -> n - offset + 1)
|
||||
|
||||
|
||||
getRowAtLine : File -> Int -> String
|
||||
getRowAtLine file =
|
||||
let
|
||||
lines : Array String
|
||||
lines =
|
||||
file.source
|
||||
|> String.lines
|
||||
|> Array.fromList
|
||||
in
|
||||
\rowIndex ->
|
||||
case Array.get rowIndex lines of
|
||||
Just line ->
|
||||
if String.trim line /= "" then
|
||||
String.fromInt (rowIndex + 1) ++ "| " ++ line ++ "\n"
|
||||
|
||||
else
|
||||
""
|
||||
|
||||
Nothing ->
|
||||
""
|
||||
|
||||
|
||||
underlineError : Int -> { start : Int, end : Int } -> Text
|
||||
underlineError lineNumber { start, end } =
|
||||
let
|
||||
baseText : String
|
||||
baseText =
|
||||
String.repeat (offsetBecauseOfLineNumber lineNumber + start - 1) " " ++ String.repeat (end - start) "^" ++ "\n"
|
||||
in
|
||||
baseText
|
||||
|> Text.from
|
||||
|> Text.inRed
|
||||
|
||||
|
||||
offsetBecauseOfLineNumber : Int -> Int
|
||||
offsetBecauseOfLineNumber lineNumber =
|
||||
lineNumber
|
||||
|> String.fromInt
|
||||
|> String.length
|
||||
|> (+) 2
|
||||
|
||||
|
||||
summary : List ( File, List LintError ) -> String
|
||||
summary errors =
|
||||
let
|
||||
errorCount : Int
|
||||
errorCount =
|
||||
errors
|
||||
|> List.concatMap Tuple.second
|
||||
|> List.length
|
||||
in
|
||||
if errorCount == 0 then
|
||||
""
|
||||
|
||||
else
|
||||
String.fromInt errorCount ++ " problem(s)."
|
||||
|
||||
|
||||
formatReport : List ( File, List LintError ) -> List Text
|
||||
formatReport errors =
|
||||
case List.isEmpty errors of
|
||||
True ->
|
||||
[ Text.from "I found no linting errors.\nYou're all good!" ]
|
||||
|
||||
False ->
|
||||
let
|
||||
fileReports : List Text
|
||||
fileReports =
|
||||
errors
|
||||
|> List.map formatReportForFileWithExtract
|
||||
|> Text.join "\n\n\n\n"
|
||||
in
|
||||
[ fileReports
|
||||
, [ Text.from <| "\n\n\n\n" ++ summary errors ]
|
||||
]
|
||||
|> List.concat
|
131
example/Text.elm
Normal file
131
example/Text.elm
Normal file
@ -0,0 +1,131 @@
|
||||
module Text exposing
|
||||
( Text
|
||||
, from
|
||||
, inGreen, inRed
|
||||
, join
|
||||
, view
|
||||
)
|
||||
|
||||
{-| Represents text with some styling applied to it.
|
||||
|
||||
text : List Text
|
||||
text =
|
||||
[ Text.from "My name is "
|
||||
, Text.from "John"
|
||||
|> Text.withColor
|
||||
, Text.from "."
|
||||
]
|
||||
|
||||
|
||||
# Definition
|
||||
|
||||
@docs Text
|
||||
|
||||
|
||||
# Constructors
|
||||
|
||||
@docs from
|
||||
|
||||
|
||||
# Modifiers
|
||||
|
||||
@docs inGreen, inRed
|
||||
|
||||
|
||||
# Working with lists
|
||||
|
||||
@docs join
|
||||
|
||||
|
||||
# ACCESS
|
||||
|
||||
@docs length
|
||||
|
||||
|
||||
# Encoding
|
||||
|
||||
@docs encode
|
||||
|
||||
-}
|
||||
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as Attr
|
||||
|
||||
|
||||
|
||||
-- DEFINITION
|
||||
|
||||
|
||||
{-| Represents text with some styling applied to it.
|
||||
-}
|
||||
type Text
|
||||
= Text
|
||||
{ str : String
|
||||
, color : Maybe String
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- CONSTRUCTORS
|
||||
|
||||
|
||||
{-| Create an unstyled `Text` from a string.
|
||||
-}
|
||||
from : String -> Text
|
||||
from value =
|
||||
Text
|
||||
{ str = value
|
||||
, color = Nothing
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- MODIFIERS
|
||||
|
||||
|
||||
inGreen : Text -> Text
|
||||
inGreen (Text text) =
|
||||
Text { text | color = Just "green" }
|
||||
|
||||
|
||||
inRed : Text -> Text
|
||||
inRed (Text text) =
|
||||
Text { text | color = Just "red" }
|
||||
|
||||
|
||||
|
||||
-- WORKING WITH LISTS
|
||||
|
||||
|
||||
join : String -> List (List Text) -> List Text
|
||||
join sep chunks =
|
||||
List.intersperse [ from sep ] chunks
|
||||
|> List.concatMap identity
|
||||
|
||||
|
||||
|
||||
-- VIEW
|
||||
|
||||
|
||||
view : List Text -> Html msg
|
||||
view texts =
|
||||
Html.div
|
||||
[]
|
||||
(List.map viewPart texts)
|
||||
|
||||
|
||||
viewPart : Text -> Html msg
|
||||
viewPart (Text text) =
|
||||
Html.span
|
||||
[ case text.color of
|
||||
Just color ->
|
||||
Attr.style "color" color
|
||||
|
||||
Nothing ->
|
||||
Attr.classList []
|
||||
]
|
||||
(text.str
|
||||
|> String.lines
|
||||
|> List.map Html.text
|
||||
|> List.intersperse (Html.br [] [])
|
||||
)
|
Loading…
Reference in New Issue
Block a user