Show the linting errors in the example like we do in the terminal

This commit is contained in:
Jeroen Engels 2019-07-28 12:33:29 +02:00
parent 2274505ebc
commit cff014d5ad
3 changed files with 389 additions and 36 deletions

View File

@ -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
View 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
View 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 [] [])
)