mirror of
https://github.com/jfmengels/elm-review.git
synced 2024-12-25 02:34:36 +03:00
Move error messages out of Lint.Test and into Lint.Test.ErrorMessage
This commit is contained in:
parent
1d3333f489
commit
b6464dbff2
@ -1,638 +0,0 @@
|
||||
module Lint.Internal.Test exposing
|
||||
( LintResult, run
|
||||
, ExpectedError, expectErrors, expectNoErrors, error, atExactly
|
||||
, parsingErrorMessage, messageMismatchError, wrongLocationError, didNotExpectErrors
|
||||
, underMismatchError, notEnoughErrors, tooManyErrors, locationIsAmbiguousInSourceCodeError
|
||||
)
|
||||
|
||||
{-| Module that helps you test your linting rules, using [`elm-test`](https://package.elm-lang.org/packages/elm-explorations/test/latest).
|
||||
|
||||
import Lint.Test exposing (LintResult)
|
||||
import Test exposing (Test, describe, test)
|
||||
import The.Rule.You.Want.To.Test exposing (rule)
|
||||
|
||||
testRule : String -> LintResult
|
||||
testRule string =
|
||||
Lint.Test.run rule string
|
||||
|
||||
-- In this example, the rule we're testing is `NoDebug`
|
||||
tests : Test
|
||||
tests =
|
||||
describe "NoDebug"
|
||||
[ test "should not report calls to normal functions" <|
|
||||
\() ->
|
||||
testRule """module A exposing (..)
|
||||
a = foo n"""
|
||||
|> Lint.Test.expectNoErrors
|
||||
, test "should report Debug.log use" <|
|
||||
\() ->
|
||||
testRule """module A exposing (..)
|
||||
a = Debug.log "some" "message\""""
|
||||
|> Lint.Test.expectErrors
|
||||
[ Lint.Test.error
|
||||
{ message = "Forbidden use of Debug"
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
# Running tests
|
||||
|
||||
@docs LintResult, run
|
||||
|
||||
|
||||
# Making assertions
|
||||
|
||||
@docs ExpectedError, expectErrors, expectNoErrors, error, atExactly
|
||||
|
||||
|
||||
# Error messages
|
||||
|
||||
@docs parsingErrorMessage, messageMismatchError, wrongLocationError, didNotExpectErrors
|
||||
@docs underMismatchError, notEnoughErrors, tooManyErrors, locationIsAmbiguousInSourceCodeError
|
||||
|
||||
|
||||
# Tips on testing
|
||||
|
||||
|
||||
## What should you test?
|
||||
|
||||
TODO Add helpful tips
|
||||
|
||||
-}
|
||||
|
||||
import Array exposing (Array)
|
||||
import Elm.Syntax.Range exposing (Range)
|
||||
import Expect exposing (Expectation)
|
||||
import Lint exposing (Severity(..), lintSource)
|
||||
import Lint.Rule as Rule exposing (Error, Rule)
|
||||
import List.Extra
|
||||
|
||||
|
||||
{-| The result of running a rule on a `String` containing source code.
|
||||
-}
|
||||
type LintResult
|
||||
= ParseFailure
|
||||
| SuccessfulRun CodeInspector (List Error)
|
||||
|
||||
|
||||
type alias CodeInspector =
|
||||
{ getCodeAtLocation : Range -> Maybe String
|
||||
, checkIfLocationIsAmbiguous : Error -> String -> Expectation
|
||||
}
|
||||
|
||||
|
||||
{-| An expectation for an error. Use [`error`](#error) to create one.
|
||||
-}
|
||||
type ExpectedError
|
||||
= ExpectedError
|
||||
{ message : String
|
||||
, under : Under
|
||||
}
|
||||
|
||||
|
||||
type Under
|
||||
= Under String
|
||||
| UnderExactly String Range
|
||||
|
||||
|
||||
type alias SourceCode =
|
||||
String
|
||||
|
||||
|
||||
{-| Run a `Rule` on a `String` containing source code. You can then use
|
||||
[`expectNoErrors`](#expectNoErrors) or [`expectErrors`](#expectErrors) to assert
|
||||
the errors reported by the rule.
|
||||
|
||||
The source code needs to be syntactically valid Elm code. If the code
|
||||
can't be parsed, the test will fail regardless of the expectations you set on it.
|
||||
|
||||
Note that t be syntactically valid, you need at least a module declaration at the
|
||||
top of the file (like `module A exposing (..)`) and one declaration (like `a = 1`).
|
||||
You can't just have an expression like `1 + 2`.
|
||||
|
||||
-}
|
||||
run : Rule -> String -> LintResult
|
||||
run rule sourceCode =
|
||||
case lintSource [ ( Critical, rule ) ] sourceCode of
|
||||
Ok errors ->
|
||||
SuccessfulRun
|
||||
{ getCodeAtLocation = getCodeAtLocationInSourceCode sourceCode
|
||||
, checkIfLocationIsAmbiguous = checkIfLocationIsAmbiguousInSourceCode sourceCode
|
||||
}
|
||||
(List.map (\( _, error_ ) -> Rule.error error_.message error_.range) errors)
|
||||
|
||||
Err _ ->
|
||||
ParseFailure
|
||||
|
||||
|
||||
{-| Assert that the rule reprted no errors. Note, this is equivalent to using [`expectErrors`](#expectErrors)
|
||||
like `expectErrors []`.
|
||||
|
||||
import Lint.Test exposing (LintResult)
|
||||
import Test exposing (Test, describe, test)
|
||||
import The.Rule.You.Want.To.Test exposing (rule)
|
||||
|
||||
testRule : String -> LintResult
|
||||
testRule string =
|
||||
Lint.Test.run rule string
|
||||
|
||||
-- In this example, the rule we're testing is `NoDebug`
|
||||
tests : Test
|
||||
tests =
|
||||
describe "NoDebug"
|
||||
[ test "should not report calls to normal functions" <|
|
||||
\() ->
|
||||
testRule """module A exposing (..)
|
||||
a = foo n"""
|
||||
|> Lint.Test.expectNoErrors
|
||||
]
|
||||
|
||||
-}
|
||||
expectNoErrors : LintResult -> Expectation
|
||||
expectNoErrors lintResult =
|
||||
case lintResult of
|
||||
ParseFailure ->
|
||||
Expect.fail parsingErrorMessage
|
||||
|
||||
SuccessfulRun _ errors ->
|
||||
Expect.true
|
||||
(didNotExpectErrors errors)
|
||||
(List.isEmpty errors)
|
||||
|
||||
|
||||
didNotExpectErrors : List Error -> String
|
||||
didNotExpectErrors errors =
|
||||
"""I expected no errors but found:
|
||||
|
||||
""" ++ (List.map errorToString errors |> String.join "\n ")
|
||||
|
||||
|
||||
{-| Assert that the rule reprted some errors, by specifying which one.
|
||||
|
||||
Assert which errors are reported using [`error`](#error). The test will fail if
|
||||
a different number of errors than expected are reported, or if the message or the
|
||||
location is incorrect.
|
||||
|
||||
The errors should be in the order of where they appear in the source code. An error
|
||||
at the start of the source code should appear earlier in the list than
|
||||
an error at the end of the source code.
|
||||
|
||||
import Lint.Test exposing (LintResult)
|
||||
import Test exposing (Test, describe, test)
|
||||
import The.Rule.You.Want.To.Test exposing (rule)
|
||||
|
||||
testRule : String -> LintResult
|
||||
testRule string =
|
||||
Lint.Test.run rule string
|
||||
|
||||
-- In this example, the rule we're testing is `NoDebug`
|
||||
tests : Test
|
||||
tests =
|
||||
describe "NoDebug"
|
||||
[ test "should report Debug.log use" <|
|
||||
\() ->
|
||||
testRule """module A exposing (..)
|
||||
a = Debug.log "some" "message\""""
|
||||
|> Lint.Test.expectErrors
|
||||
[ Lint.Test.error
|
||||
{ message = "Forbidden use of Debug"
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
-}
|
||||
expectErrors : List ExpectedError -> LintResult -> Expectation
|
||||
expectErrors expectedErrors lintResult =
|
||||
case lintResult of
|
||||
ParseFailure ->
|
||||
Expect.fail parsingErrorMessage
|
||||
|
||||
SuccessfulRun codeInspector errors ->
|
||||
checkAllErrorsMatch codeInspector expectedErrors errors
|
||||
|
||||
|
||||
{-| Create an expectation for an error.
|
||||
|
||||
`message` should be the message you're expecting to be shown to the user.
|
||||
|
||||
`under` is the part of the code where you are expecting the error to be shown to
|
||||
the user. If it helps, imagine `under` to be the text under which the squiggly
|
||||
lines will appear if the error appeared in an editor.
|
||||
|
||||
tests : Test
|
||||
tests =
|
||||
describe "NoDebug"
|
||||
[ test "should report Debug.log use" <|
|
||||
\() ->
|
||||
testRule """module A exposing (..)
|
||||
a = Debug.log "some" "message\""""
|
||||
|> Lint.Test.expectErrors
|
||||
[ Lint.Test.error
|
||||
{ message = "Forbidden use of Debug"
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
If there are multiple locations where the value of `under` appears, the test will
|
||||
fail unless you use [`atExactly`](#atExactly) to remove any ambiguity of where the
|
||||
error should be used.
|
||||
|
||||
-}
|
||||
error : { message : String, under : String } -> ExpectedError
|
||||
error input =
|
||||
ExpectedError
|
||||
{ message = input.message
|
||||
, under = Under input.under
|
||||
}
|
||||
|
||||
|
||||
getUnder : ExpectedError -> String
|
||||
getUnder (ExpectedError expectedError) =
|
||||
case expectedError.under of
|
||||
Under str ->
|
||||
str
|
||||
|
||||
UnderExactly str _ ->
|
||||
str
|
||||
|
||||
|
||||
{-| Precise the exact position where the error should be shown to the user. This
|
||||
is only necessary when the `under` field is ambiguous.
|
||||
|
||||
`atExactly` takes a record with start and end positions.
|
||||
|
||||
tests : Test
|
||||
tests =
|
||||
describe "NoDebug"
|
||||
[ test "should report multiple Debug.log calls" <|
|
||||
\() ->
|
||||
testRule """
|
||||
a = Debug.log z
|
||||
b = Debug.log z
|
||||
"""
|
||||
|> Lint.Test.expectErrors
|
||||
[ Lint.Test.error
|
||||
{ message = message
|
||||
, under = "Debug.log"
|
||||
}
|
||||
|> Lint.Test.atExactly { start = { row = 4, column = 5 }, end = { row = 4, column = 14 } }
|
||||
, Lint.Test.error
|
||||
{ message = message
|
||||
, under = "Debug.log"
|
||||
}
|
||||
|> Lint.Test.atExactly { start = { row = 5, column = 5 }, end = { row = 5, column = 14 } }
|
||||
]
|
||||
]
|
||||
|
||||
Tip: By default, do not provide this field. If the test fails because there is some
|
||||
ambiguity, the test error will give you a recommendation of what to use as a parameter
|
||||
of `atExactly`, so you do not have to bother writing this hard to write argument.
|
||||
|
||||
-}
|
||||
atExactly : { start : { row : Int, column : Int }, end : { row : Int, column : Int } } -> ExpectedError -> ExpectedError
|
||||
atExactly range ((ExpectedError expectedError_) as expectedError) =
|
||||
ExpectedError { expectedError_ | under = UnderExactly (getUnder expectedError) range }
|
||||
|
||||
|
||||
checkAllErrorsMatch : CodeInspector -> List ExpectedError -> List Error -> Expectation
|
||||
checkAllErrorsMatch codeInspector expectedErrors errors =
|
||||
checkErrorsMatch codeInspector expectedErrors errors
|
||||
|> List.reverse
|
||||
|> (\expectations -> Expect.all expectations ())
|
||||
|
||||
|
||||
checkErrorsMatch : CodeInspector -> List ExpectedError -> List Error -> List (() -> Expectation)
|
||||
checkErrorsMatch codeInspector expectedErrors errors =
|
||||
case ( expectedErrors, errors ) of
|
||||
( [], [] ) ->
|
||||
[ always Expect.pass ]
|
||||
|
||||
( expected :: restOfExpectedErrors, error_ :: restOfErrors ) ->
|
||||
checkErrorMatch codeInspector expected error_ :: checkErrorsMatch codeInspector restOfExpectedErrors restOfErrors
|
||||
|
||||
( expected :: restOfExpectedErrors, [] ) ->
|
||||
[ always <| Expect.fail <| notEnoughErrors (expected :: restOfExpectedErrors) ]
|
||||
|
||||
( [], error_ :: restOfErrors ) ->
|
||||
[ always <| Expect.fail <| tooManyErrors (error_ :: restOfErrors) ]
|
||||
|
||||
|
||||
checkErrorMatch : CodeInspector -> ExpectedError -> Error -> (() -> Expectation)
|
||||
checkErrorMatch codeInspector ((ExpectedError expectedError_) as expectedError) error_ =
|
||||
Expect.all
|
||||
[ always <| Expect.true (messageMismatchError expectedError error_) (expectedError_.message == Rule.errorMessage error_)
|
||||
, checkMessageAppearsUnder codeInspector error_ expectedError
|
||||
]
|
||||
|
||||
|
||||
checkMessageAppearsUnder : CodeInspector -> Error -> ExpectedError -> (() -> Expectation)
|
||||
checkMessageAppearsUnder codeInspector error_ (ExpectedError expectedError) =
|
||||
case codeInspector.getCodeAtLocation (Rule.errorRange error_) of
|
||||
Just codeAtLocation ->
|
||||
case expectedError.under of
|
||||
Under under ->
|
||||
Expect.all
|
||||
[ always <|
|
||||
Expect.true
|
||||
(underMismatchError error_ { under = under, codeAtLocation = codeAtLocation })
|
||||
(codeAtLocation == under)
|
||||
, always <| codeInspector.checkIfLocationIsAmbiguous error_ under
|
||||
]
|
||||
|
||||
UnderExactly under range ->
|
||||
Expect.all
|
||||
[ always <|
|
||||
Expect.true
|
||||
(underMismatchError error_ { under = under, codeAtLocation = codeAtLocation })
|
||||
(codeAtLocation == under)
|
||||
, always <|
|
||||
Expect.true
|
||||
(wrongLocationError error_ range under)
|
||||
(Rule.errorRange error_ == range)
|
||||
]
|
||||
|
||||
Nothing ->
|
||||
always <| Expect.fail impossibleStateError
|
||||
|
||||
|
||||
getCodeAtLocationInSourceCode : SourceCode -> Range -> Maybe String
|
||||
getCodeAtLocationInSourceCode sourceCode =
|
||||
let
|
||||
lines : Array String
|
||||
lines =
|
||||
String.lines sourceCode
|
||||
|> Array.fromList
|
||||
in
|
||||
\{ start, end } ->
|
||||
if start.row == end.row then
|
||||
Array.get (start.row - 1) lines
|
||||
|> Maybe.map (String.slice (start.column - 1) (end.column - 1))
|
||||
|
||||
else
|
||||
let
|
||||
firstLine : Maybe String
|
||||
firstLine =
|
||||
Array.get (start.row - 1) lines
|
||||
|> Maybe.map (String.dropLeft (start.column - 1))
|
||||
|
||||
lastLine : Maybe String
|
||||
lastLine =
|
||||
Array.get (end.row - 1) lines
|
||||
|> Maybe.map (String.dropRight end.column)
|
||||
in
|
||||
[ [ firstLine ]
|
||||
, Array.slice start.row (end.row - 1) lines
|
||||
|> Array.toList
|
||||
|> List.map Just
|
||||
, [ lastLine ]
|
||||
]
|
||||
|> List.concat
|
||||
|> List.filterMap identity
|
||||
|> String.join "\n"
|
||||
|> Just
|
||||
|
||||
|
||||
formatSourceCode : String -> String
|
||||
formatSourceCode string =
|
||||
let
|
||||
lines =
|
||||
String.lines string
|
||||
in
|
||||
if List.length lines == 1 then
|
||||
"`" ++ string ++ "`"
|
||||
|
||||
else
|
||||
lines
|
||||
|> List.map (\str -> " " ++ str)
|
||||
|> String.join "\n"
|
||||
|> (\str -> "```\n" ++ str ++ "\n ```")
|
||||
|
||||
|
||||
checkIfLocationIsAmbiguousInSourceCode : SourceCode -> Error -> String -> Expectation
|
||||
checkIfLocationIsAmbiguousInSourceCode sourceCode error_ under =
|
||||
let
|
||||
occurrencesInSourceCode : List Int
|
||||
occurrencesInSourceCode =
|
||||
String.indexes under sourceCode
|
||||
in
|
||||
Expect.true
|
||||
(locationIsAmbiguousInSourceCodeError sourceCode error_ under occurrencesInSourceCode)
|
||||
(List.length occurrencesInSourceCode == 1)
|
||||
|
||||
|
||||
|
||||
-- ERROR MESSAGES
|
||||
|
||||
|
||||
parsingErrorMessage : String
|
||||
parsingErrorMessage =
|
||||
"""I could not parse the test source code, because it was not syntactically valid Elm code.
|
||||
|
||||
Maybe you forgot to add the module definition at the top, like:
|
||||
|
||||
`module A exposing (..)`"""
|
||||
|
||||
|
||||
messageMismatchError : ExpectedError -> Error -> String
|
||||
messageMismatchError (ExpectedError expectedError) error_ =
|
||||
"""I was looking for the error with the following message:
|
||||
|
||||
`""" ++ expectedError.message ++ """`
|
||||
|
||||
but I found the following error message:
|
||||
|
||||
`""" ++ Rule.errorMessage error_ ++ "`"
|
||||
|
||||
|
||||
underMismatchError : Error -> { under : String, codeAtLocation : String } -> String
|
||||
underMismatchError error_ { under, codeAtLocation } =
|
||||
"""I found an error with the following message:
|
||||
|
||||
`""" ++ Rule.errorMessage error_ ++ """`
|
||||
|
||||
which I was expecting, but I found it under:
|
||||
|
||||
""" ++ formatSourceCode codeAtLocation ++ """
|
||||
|
||||
when I was expecting it under:
|
||||
|
||||
""" ++ formatSourceCode under ++ """
|
||||
|
||||
Hint: Maybe you're passing the `Range` of a wrong node when calling `Rule.error`"""
|
||||
|
||||
|
||||
wrongLocationError : Error -> Range -> String -> String
|
||||
wrongLocationError error_ range under =
|
||||
"""I was looking for the error with the following message:
|
||||
|
||||
`""" ++ Rule.errorMessage error_ ++ """`
|
||||
|
||||
under the following code:
|
||||
|
||||
""" ++ formatSourceCode under ++ """
|
||||
|
||||
and I found it, but the exact location you specified is not the one I found. I was expecting the error at:
|
||||
|
||||
""" ++ rangeAsString range ++ """
|
||||
|
||||
but I found it at:
|
||||
|
||||
""" ++ rangeAsString (Rule.errorRange error_)
|
||||
|
||||
|
||||
listOccurrencesAsLocations : SourceCode -> String -> List Int -> String
|
||||
listOccurrencesAsLocations sourceCode under occurrences =
|
||||
occurrences
|
||||
|> List.map
|
||||
(\occurrence ->
|
||||
occurrence
|
||||
|> positionAsRange sourceCode under
|
||||
|> rangeAsString
|
||||
|> (++) " - "
|
||||
)
|
||||
|> String.join "\n"
|
||||
|
||||
|
||||
positionAsRange : SourceCode -> String -> Int -> Range
|
||||
positionAsRange sourceCode under position =
|
||||
let
|
||||
linesBeforeAndIncludingPosition : List String
|
||||
linesBeforeAndIncludingPosition =
|
||||
sourceCode
|
||||
|> String.slice 0 position
|
||||
|> String.lines
|
||||
|
||||
startRow : Int
|
||||
startRow =
|
||||
List.length linesBeforeAndIncludingPosition
|
||||
|
||||
startColumn : Int
|
||||
startColumn =
|
||||
linesBeforeAndIncludingPosition
|
||||
|> List.Extra.last
|
||||
|> Maybe.withDefault ""
|
||||
|> String.length
|
||||
|> (+) 1
|
||||
|
||||
linesInUnder : List String
|
||||
linesInUnder =
|
||||
String.lines under
|
||||
|
||||
endRow : Int
|
||||
endRow =
|
||||
startRow + List.length linesInUnder - 1
|
||||
|
||||
endColumn : Int
|
||||
endColumn =
|
||||
if startRow == endRow then
|
||||
startColumn + String.length under
|
||||
|
||||
else
|
||||
linesInUnder
|
||||
|> List.Extra.last
|
||||
|> Maybe.withDefault ""
|
||||
|> String.length
|
||||
|> (+) 1
|
||||
in
|
||||
{ start =
|
||||
{ row = startRow
|
||||
, column = startColumn
|
||||
}
|
||||
, end =
|
||||
{ row = endRow
|
||||
, column = endColumn
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
errorToString : Error -> String
|
||||
errorToString error_ =
|
||||
"- \"" ++ Rule.errorMessage error_ ++ "\" at " ++ rangeAsString (Rule.errorRange error_)
|
||||
|
||||
|
||||
rangeAsString : Range -> String
|
||||
rangeAsString { start, end } =
|
||||
"{ start = { row = " ++ String.fromInt start.row ++ ", column = " ++ String.fromInt start.column ++ " }, end = { row = " ++ String.fromInt end.row ++ ", column = " ++ String.fromInt end.column ++ " } }"
|
||||
|
||||
|
||||
notEnoughErrors : List ExpectedError -> String
|
||||
notEnoughErrors missingExpectedErrors =
|
||||
let
|
||||
numberOfErrors : Int
|
||||
numberOfErrors =
|
||||
List.length missingExpectedErrors
|
||||
in
|
||||
"I expected to see "
|
||||
++ String.fromInt numberOfErrors
|
||||
++ " more "
|
||||
++ pluralizeErrors numberOfErrors
|
||||
++ ":\n\n"
|
||||
++ (missingExpectedErrors
|
||||
|> List.map expectedErrorToString
|
||||
|> String.join "\n"
|
||||
)
|
||||
|
||||
|
||||
wrapInQuotes : String -> String
|
||||
wrapInQuotes string =
|
||||
"\"" ++ string ++ "\""
|
||||
|
||||
|
||||
tooManyErrors : List Error -> String
|
||||
tooManyErrors extraErrors =
|
||||
let
|
||||
numberOfErrors : Int
|
||||
numberOfErrors =
|
||||
List.length extraErrors
|
||||
in
|
||||
"I found "
|
||||
++ String.fromInt numberOfErrors
|
||||
++ " "
|
||||
++ pluralizeErrors numberOfErrors
|
||||
++ " too many:\n\n"
|
||||
++ (extraErrors
|
||||
|> List.map errorToString
|
||||
|> String.join "\n"
|
||||
)
|
||||
|
||||
|
||||
locationIsAmbiguousInSourceCodeError : SourceCode -> Error -> String -> List Int -> String
|
||||
locationIsAmbiguousInSourceCodeError sourceCode error_ under occurrencesInSourceCode =
|
||||
"""Your test passes, but where the message appears is ambiguous.
|
||||
|
||||
You are looking for the following error message:
|
||||
|
||||
`""" ++ Rule.errorMessage error_ ++ """`
|
||||
|
||||
and expecting to see it under:
|
||||
|
||||
""" ++ formatSourceCode under ++ """
|
||||
|
||||
I found """ ++ String.fromInt (List.length occurrencesInSourceCode) ++ """ locations where that code appeared. Please use `Lint.Rule.atExactly` to make the part you were targetting unambiguous.
|
||||
|
||||
Tip: I found them at:
|
||||
""" ++ listOccurrencesAsLocations sourceCode under occurrencesInSourceCode
|
||||
|
||||
|
||||
impossibleStateError : String
|
||||
impossibleStateError =
|
||||
"Oh no! I'm in an impossible state. I found an error at a location that I could not find back. Please let me know and give me an SSCCE (http://sscce.org/) here: https://github.com/jfmengels/elm-lint/issues."
|
||||
|
||||
|
||||
pluralizeErrors : Int -> String
|
||||
pluralizeErrors n =
|
||||
case n of
|
||||
1 ->
|
||||
"error"
|
||||
|
||||
_ ->
|
||||
"errors"
|
||||
|
||||
|
||||
expectedErrorToString : ExpectedError -> String
|
||||
expectedErrorToString (ExpectedError expectedError) =
|
||||
"- " ++ wrapInQuotes expectedError.message
|
@ -59,7 +59,7 @@ import Elm.Syntax.Range exposing (Range)
|
||||
import Expect exposing (Expectation)
|
||||
import Lint exposing (Severity(..), lintSource)
|
||||
import Lint.Rule as Rule exposing (Error, Rule)
|
||||
import List.Extra
|
||||
import Lint.Test.ErrorMessage as ErrorMessage
|
||||
|
||||
|
||||
{-| The result of running a rule on a `String` containing source code.
|
||||
@ -89,8 +89,8 @@ type Under
|
||||
| UnderExactly String Range
|
||||
|
||||
|
||||
type SourceCode
|
||||
= SourceCode String
|
||||
type alias SourceCode =
|
||||
String
|
||||
|
||||
|
||||
{-| Run a `Rule` on a `String` containing source code. You can then use
|
||||
@ -110,8 +110,8 @@ run rule sourceCode =
|
||||
case lintSource [ ( Critical, rule ) ] sourceCode of
|
||||
Ok errors ->
|
||||
SuccessfulRun
|
||||
{ getCodeAtLocation = getCodeAtLocationInSourceCode (SourceCode sourceCode)
|
||||
, checkIfLocationIsAmbiguous = checkIfLocationIsAmbiguousInSourceCode (SourceCode sourceCode)
|
||||
{ getCodeAtLocation = getCodeAtLocationInSourceCode sourceCode
|
||||
, checkIfLocationIsAmbiguous = checkIfLocationIsAmbiguousInSourceCode sourceCode
|
||||
}
|
||||
(List.map (\( _, error_ ) -> Rule.error error_.message error_.range) errors)
|
||||
|
||||
@ -119,7 +119,7 @@ run rule sourceCode =
|
||||
ParseFailure
|
||||
|
||||
|
||||
{-| Assert that the rule reprted no errors. Note, this is equivalent to using [`expectErrors`](#expectErrors)
|
||||
{-| Assert that the rule reported no errors. Note, this is equivalent to using [`expectErrors`](#expectErrors)
|
||||
like `expectErrors []`.
|
||||
|
||||
import Lint.Test exposing (LintResult)
|
||||
@ -146,15 +146,15 @@ expectNoErrors : LintResult -> Expectation
|
||||
expectNoErrors lintResult =
|
||||
case lintResult of
|
||||
ParseFailure ->
|
||||
Expect.fail parsingErrorMessage
|
||||
Expect.fail ErrorMessage.parsingFailure
|
||||
|
||||
SuccessfulRun _ errors ->
|
||||
Expect.true
|
||||
("I expected no errors but found:\n\n" ++ (List.map errorToString errors |> String.join "\n"))
|
||||
(ErrorMessage.didNotExpectErrors errors)
|
||||
(List.isEmpty errors)
|
||||
|
||||
|
||||
{-| Assert that the rule reprted some errors, by specifying which one.
|
||||
{-| Assert that the rule reported some errors, by specifying which one.
|
||||
|
||||
Assert which errors are reported using [`error`](#error). The test will fail if
|
||||
a different number of errors than expected are reported, or if the message or the
|
||||
@ -193,7 +193,7 @@ expectErrors : List ExpectedError -> LintResult -> Expectation
|
||||
expectErrors expectedErrors lintResult =
|
||||
case lintResult of
|
||||
ParseFailure ->
|
||||
Expect.fail parsingErrorMessage
|
||||
Expect.fail ErrorMessage.parsingFailure
|
||||
|
||||
SuccessfulRun codeInspector errors ->
|
||||
checkAllErrorsMatch codeInspector expectedErrors errors
|
||||
@ -235,16 +235,6 @@ error input =
|
||||
}
|
||||
|
||||
|
||||
getUnder : ExpectedError -> String
|
||||
getUnder (ExpectedError expectedError) =
|
||||
case expectedError.under of
|
||||
Under str ->
|
||||
str
|
||||
|
||||
UnderExactly str _ ->
|
||||
str
|
||||
|
||||
|
||||
{-| Precise the exact position where the error should be shown to the user. This
|
||||
is only necessary when the `under` field is ambiguous.
|
||||
|
||||
@ -283,60 +273,18 @@ atExactly range ((ExpectedError expectedError_) as expectedError) =
|
||||
ExpectedError { expectedError_ | under = UnderExactly (getUnder expectedError) range }
|
||||
|
||||
|
||||
checkAllErrorsMatch : CodeInspector -> List ExpectedError -> List Error -> Expectation
|
||||
checkAllErrorsMatch codeInspector expectedErrors errors =
|
||||
checkErrorsMatch codeInspector expectedErrors errors
|
||||
|> List.reverse
|
||||
|> (\expectations -> Expect.all expectations ())
|
||||
getUnder : ExpectedError -> String
|
||||
getUnder (ExpectedError expectedError) =
|
||||
case expectedError.under of
|
||||
Under str ->
|
||||
str
|
||||
|
||||
|
||||
checkErrorsMatch : CodeInspector -> List ExpectedError -> List Error -> List (() -> Expectation)
|
||||
checkErrorsMatch codeInspector expectedErrors errors =
|
||||
case ( expectedErrors, errors ) of
|
||||
( [], [] ) ->
|
||||
[ always Expect.pass ]
|
||||
|
||||
( expected :: restOfExpectedErrors, error_ :: restOfErrors ) ->
|
||||
checkErrorMatch codeInspector expected error_ :: checkErrorsMatch codeInspector restOfExpectedErrors restOfErrors
|
||||
|
||||
( expected :: restOfExpectedErrors, [] ) ->
|
||||
[ always <| Expect.fail <| notEnoughErrors expected restOfExpectedErrors ]
|
||||
|
||||
( [], error_ :: restOfErrors ) ->
|
||||
[ always <| Expect.fail <| tooManyErrors error_ restOfErrors ]
|
||||
|
||||
|
||||
checkErrorMatch : CodeInspector -> ExpectedError -> Error -> (() -> Expectation)
|
||||
checkErrorMatch codeInspector ((ExpectedError expectedError_) as expectedError) error_ =
|
||||
Expect.all
|
||||
[ always <| Expect.true (messageMismatchError expectedError error_) (expectedError_.message == Rule.errorMessage error_)
|
||||
, checkMessageAppearsUnder codeInspector error_ expectedError
|
||||
]
|
||||
|
||||
|
||||
checkMessageAppearsUnder : CodeInspector -> Error -> ExpectedError -> (() -> Expectation)
|
||||
checkMessageAppearsUnder codeInspector error_ (ExpectedError expectedError) =
|
||||
case codeInspector.getCodeAtLocation (Rule.errorRange error_) of
|
||||
Just codeAtLocation ->
|
||||
case expectedError.under of
|
||||
Under under ->
|
||||
Expect.all
|
||||
[ always <| Expect.true (underMismatchError error_ under codeAtLocation) (codeAtLocation == under)
|
||||
, always <| codeInspector.checkIfLocationIsAmbiguous error_ under
|
||||
]
|
||||
|
||||
UnderExactly under range ->
|
||||
Expect.all
|
||||
[ always <| Expect.true (underMismatchError error_ under codeAtLocation) (codeAtLocation == under)
|
||||
, always <| Expect.true (wrongLocationError error_ range under) (Rule.errorRange error_ == range)
|
||||
]
|
||||
|
||||
Nothing ->
|
||||
always <| Expect.fail impossibleStateError
|
||||
UnderExactly str _ ->
|
||||
str
|
||||
|
||||
|
||||
getCodeAtLocationInSourceCode : SourceCode -> Range -> Maybe String
|
||||
getCodeAtLocationInSourceCode (SourceCode sourceCode) =
|
||||
getCodeAtLocationInSourceCode sourceCode =
|
||||
let
|
||||
lines : Array String
|
||||
lines =
|
||||
@ -372,233 +320,91 @@ getCodeAtLocationInSourceCode (SourceCode sourceCode) =
|
||||
|> Just
|
||||
|
||||
|
||||
formatSourceCode : String -> String
|
||||
formatSourceCode string =
|
||||
let
|
||||
lines =
|
||||
String.lines string
|
||||
in
|
||||
if List.length lines == 1 then
|
||||
"`" ++ string ++ "`"
|
||||
|
||||
else
|
||||
lines
|
||||
|> List.map (\str -> " " ++ str)
|
||||
|> String.join "\n"
|
||||
|> (\str -> "\n\n```\n" ++ str ++ "\n```")
|
||||
|
||||
|
||||
checkIfLocationIsAmbiguousInSourceCode : SourceCode -> Error -> String -> Expectation
|
||||
checkIfLocationIsAmbiguousInSourceCode ((SourceCode sourceCodeContent) as sourceCode) error_ under =
|
||||
checkIfLocationIsAmbiguousInSourceCode sourceCode error_ under =
|
||||
let
|
||||
occurrencesInSourceCode : List Int
|
||||
occurrencesInSourceCode =
|
||||
String.indexes under sourceCodeContent
|
||||
String.indexes under sourceCode
|
||||
in
|
||||
Expect.true
|
||||
(locationIsAmbiguousInSourceCodeError sourceCode error_ under occurrencesInSourceCode)
|
||||
(ErrorMessage.locationIsAmbiguousInSourceCode sourceCode error_ under occurrencesInSourceCode)
|
||||
(List.length occurrencesInSourceCode == 1)
|
||||
|
||||
|
||||
|
||||
-- ERROR MESSAGES
|
||||
-- RUNNING THE CHECKS
|
||||
|
||||
|
||||
parsingErrorMessage : String
|
||||
parsingErrorMessage =
|
||||
"""I could not parse the test source code, because it was not syntactically valid Elm code.
|
||||
|
||||
Maybe you forgot to add the module definition at the top, like:
|
||||
|
||||
module A exposing (..)"""
|
||||
checkAllErrorsMatch : CodeInspector -> List ExpectedError -> List Error -> Expectation
|
||||
checkAllErrorsMatch codeInspector expectedErrors errors =
|
||||
checkErrorsMatch codeInspector expectedErrors errors
|
||||
|> List.reverse
|
||||
|> (\expectations -> Expect.all expectations ())
|
||||
|
||||
|
||||
messageMismatchError : ExpectedError -> Error -> String
|
||||
messageMismatchError (ExpectedError expectedError) error_ =
|
||||
"""I was looking for the error with the following message:
|
||||
checkErrorsMatch : CodeInspector -> List ExpectedError -> List Error -> List (() -> Expectation)
|
||||
checkErrorsMatch codeInspector expectedErrors errors =
|
||||
case ( expectedErrors, errors ) of
|
||||
( [], [] ) ->
|
||||
[ always Expect.pass ]
|
||||
|
||||
`""" ++ expectedError.message ++ """`
|
||||
( expected :: restOfExpectedErrors, error_ :: restOfErrors ) ->
|
||||
checkErrorMatch codeInspector expected error_ :: checkErrorsMatch codeInspector restOfExpectedErrors restOfErrors
|
||||
|
||||
but I found the following error message:
|
||||
( expected :: restOfExpectedErrors, [] ) ->
|
||||
[ always <| Expect.fail <| ErrorMessage.expectedMoreErrors <| List.map extractExpectedErrorData (expected :: restOfExpectedErrors) ]
|
||||
|
||||
`""" ++ Rule.errorMessage error_ ++ "`"
|
||||
( [], error_ :: restOfErrors ) ->
|
||||
[ always <| Expect.fail <| ErrorMessage.tooManyErrors (error_ :: restOfErrors) ]
|
||||
|
||||
|
||||
wrongLocationError : Error -> Range -> String -> String
|
||||
wrongLocationError error_ range under =
|
||||
"""I was looking for the error with the following message:
|
||||
|
||||
`""" ++ Rule.errorMessage error_ ++ """`
|
||||
|
||||
under the following code:
|
||||
|
||||
""" ++ formatSourceCode under ++ """
|
||||
|
||||
and I found it, but the exact location you specified is not the one I found. I was expecting the error at:
|
||||
|
||||
""" ++ rangeAsString range ++ """
|
||||
|
||||
but I found it at:
|
||||
|
||||
""" ++ rangeAsString (Rule.errorRange error_)
|
||||
checkErrorMatch : CodeInspector -> ExpectedError -> Error -> (() -> Expectation)
|
||||
checkErrorMatch codeInspector ((ExpectedError expectedError_) as expectedError) error_ =
|
||||
Expect.all
|
||||
[ \_ ->
|
||||
(expectedError_.message == Rule.errorMessage error_)
|
||||
|> Expect.true
|
||||
(ErrorMessage.messageMismatch
|
||||
(extractExpectedErrorData expectedError)
|
||||
error_
|
||||
)
|
||||
, checkMessageAppearsUnder codeInspector error_ expectedError
|
||||
]
|
||||
|
||||
|
||||
underMismatchError : Error -> String -> String -> String
|
||||
underMismatchError error_ under codeAtLocation =
|
||||
"""I found an error with the right message, but at the wrong location:
|
||||
checkMessageAppearsUnder : CodeInspector -> Error -> ExpectedError -> (() -> Expectation)
|
||||
checkMessageAppearsUnder codeInspector error_ (ExpectedError expectedError) =
|
||||
case codeInspector.getCodeAtLocation (Rule.errorRange error_) of
|
||||
Just codeAtLocation ->
|
||||
case expectedError.under of
|
||||
Under under ->
|
||||
Expect.all
|
||||
[ always <|
|
||||
Expect.true
|
||||
(ErrorMessage.underMismatch error_ { under = under, codeAtLocation = codeAtLocation })
|
||||
(codeAtLocation == under)
|
||||
, always <| codeInspector.checkIfLocationIsAmbiguous error_ under
|
||||
]
|
||||
|
||||
Message: `""" ++ Rule.errorMessage error_ ++ """`
|
||||
UnderExactly under range ->
|
||||
Expect.all
|
||||
[ always <|
|
||||
Expect.true
|
||||
(ErrorMessage.underMismatch error_ { under = under, codeAtLocation = codeAtLocation })
|
||||
(codeAtLocation == under)
|
||||
, always <|
|
||||
Expect.true
|
||||
(ErrorMessage.wrongLocation error_ range under)
|
||||
(Rule.errorRange error_ == range)
|
||||
]
|
||||
|
||||
I saw it under: """ ++ formatSourceCode codeAtLocation ++ """
|
||||
|
||||
But I expected to see it under: """ ++ formatSourceCode under
|
||||
Nothing ->
|
||||
always <| Expect.fail ErrorMessage.impossibleState
|
||||
|
||||
|
||||
listOccurrencesAsLocations : SourceCode -> String -> List Int -> String
|
||||
listOccurrencesAsLocations sourceCode under occurrences =
|
||||
occurrences
|
||||
|> List.map
|
||||
(\occurrence ->
|
||||
occurrence
|
||||
|> positionAsRange sourceCode under
|
||||
|> rangeAsString
|
||||
|> (++) " - "
|
||||
)
|
||||
|> String.join "\n"
|
||||
|
||||
|
||||
positionAsRange : SourceCode -> String -> Int -> Range
|
||||
positionAsRange (SourceCode sourceCode) under position =
|
||||
let
|
||||
linesBeforeAndIncludingPosition : List String
|
||||
linesBeforeAndIncludingPosition =
|
||||
sourceCode
|
||||
|> String.slice 0 position
|
||||
|> String.lines
|
||||
|
||||
startRow : Int
|
||||
startRow =
|
||||
List.length linesBeforeAndIncludingPosition
|
||||
|
||||
startColumn : Int
|
||||
startColumn =
|
||||
linesBeforeAndIncludingPosition
|
||||
|> List.Extra.last
|
||||
|> Maybe.withDefault ""
|
||||
|> String.length
|
||||
|> (+) 1
|
||||
|
||||
linesInUnder : List String
|
||||
linesInUnder =
|
||||
String.lines under
|
||||
|
||||
endRow : Int
|
||||
endRow =
|
||||
startRow + List.length linesInUnder - 1
|
||||
|
||||
endColumn : Int
|
||||
endColumn =
|
||||
if startRow == endRow then
|
||||
startColumn + String.length under
|
||||
|
||||
else
|
||||
linesInUnder
|
||||
|> Debug.log "linesInUnder"
|
||||
|> List.Extra.last
|
||||
|> Debug.log "last"
|
||||
|> Maybe.withDefault ""
|
||||
|> String.length
|
||||
|> (+) 1
|
||||
in
|
||||
{ start =
|
||||
{ row = startRow
|
||||
, column = startColumn
|
||||
}
|
||||
, end =
|
||||
{ row = endRow
|
||||
, column = endColumn
|
||||
}
|
||||
extractExpectedErrorData : ExpectedError -> ErrorMessage.ExpectedErrorData
|
||||
extractExpectedErrorData ((ExpectedError expectedErrorContent) as expectedError) =
|
||||
{ message = expectedErrorContent.message
|
||||
, under = getUnder expectedError
|
||||
}
|
||||
|
||||
|
||||
errorToString : Error -> String
|
||||
errorToString error_ =
|
||||
"- \"" ++ Rule.errorMessage error_ ++ "\" at " ++ rangeAsString (Rule.errorRange error_)
|
||||
|
||||
|
||||
rangeAsString : Range -> String
|
||||
rangeAsString { start, end } =
|
||||
"{ start = { row = " ++ String.fromInt start.row ++ ", column = " ++ String.fromInt start.column ++ " }, end = { row = " ++ String.fromInt end.row ++ ", column = " ++ String.fromInt end.column ++ " } }"
|
||||
|
||||
|
||||
notEnoughErrors : ExpectedError -> List ExpectedError -> String
|
||||
notEnoughErrors expected restOfExpectedErrors =
|
||||
let
|
||||
numberOfErrors : Int
|
||||
numberOfErrors =
|
||||
List.length restOfExpectedErrors + 1
|
||||
in
|
||||
"I expected to see "
|
||||
++ String.fromInt numberOfErrors
|
||||
++ " more "
|
||||
++ pluralizeErrors numberOfErrors
|
||||
++ ":\n\n"
|
||||
++ (List.map expectedErrorToString (expected :: restOfExpectedErrors) |> String.join "\n")
|
||||
|
||||
|
||||
wrapInQuotes : String -> String
|
||||
wrapInQuotes string =
|
||||
"\"" ++ string ++ "\""
|
||||
|
||||
|
||||
tooManyErrors : Error -> List Error -> String
|
||||
tooManyErrors error_ restOfErrors =
|
||||
let
|
||||
numberOfErrors : Int
|
||||
numberOfErrors =
|
||||
List.length restOfErrors + 1
|
||||
in
|
||||
"I found "
|
||||
++ String.fromInt numberOfErrors
|
||||
++ " "
|
||||
++ pluralizeErrors numberOfErrors
|
||||
++ " too many:\n"
|
||||
++ (List.map errorToString (error_ :: restOfErrors) |> String.join "\n")
|
||||
|
||||
|
||||
locationIsAmbiguousInSourceCodeError : SourceCode -> Error -> String -> List Int -> String
|
||||
locationIsAmbiguousInSourceCodeError sourceCode error_ under occurrencesInSourceCode =
|
||||
"""Your test passes, but where the message appears is ambiguous.
|
||||
|
||||
You are looking for the following error message:
|
||||
|
||||
`""" ++ Rule.errorMessage error_ ++ """`
|
||||
|
||||
and expecting to see it under:
|
||||
|
||||
""" ++ formatSourceCode under ++ """
|
||||
|
||||
I found """ ++ String.fromInt (List.length occurrencesInSourceCode) ++ """ locations where that code appeared. Please use `Lint.Rule.atExactly` to make the part you were targetting unambiguous.
|
||||
|
||||
Tip: I found them at:
|
||||
""" ++ listOccurrencesAsLocations sourceCode under occurrencesInSourceCode
|
||||
|
||||
|
||||
impossibleStateError : String
|
||||
impossibleStateError =
|
||||
"Oh no! I'm in an impossible state. I found an error at a location that I could not find back. Please let me know and give me an SSCCE (http://sscce.org/) here: https://github.com/jfmengels/elm-lint/issues."
|
||||
|
||||
|
||||
pluralizeErrors : Int -> String
|
||||
pluralizeErrors n =
|
||||
case n of
|
||||
1 ->
|
||||
"error"
|
||||
|
||||
_ ->
|
||||
"errors"
|
||||
|
||||
|
||||
expectedErrorToString : ExpectedError -> String
|
||||
expectedErrorToString (ExpectedError expectedError) =
|
||||
"- " ++ wrapInQuotes expectedError.message
|
||||
|
270
src/Lint/Test/ErrorMessage.elm
Normal file
270
src/Lint/Test/ErrorMessage.elm
Normal file
@ -0,0 +1,270 @@
|
||||
module Lint.Test.ErrorMessage exposing
|
||||
( ExpectedErrorData
|
||||
, parsingFailure, messageMismatch, wrongLocation, didNotExpectErrors
|
||||
, underMismatch, expectedMoreErrors, tooManyErrors, locationIsAmbiguousInSourceCode
|
||||
, impossibleState
|
||||
)
|
||||
|
||||
{-| Error messages for the `Lint.Test` module.
|
||||
|
||||
|
||||
# Error messages
|
||||
|
||||
@docs ExpectedErrorData
|
||||
@docs parsingFailure, messageMismatch, wrongLocation, didNotExpectErrors
|
||||
@docs underMismatch, expectedMoreErrors, tooManyErrors, locationIsAmbiguousInSourceCode
|
||||
@docs impossibleState
|
||||
|
||||
-}
|
||||
|
||||
import Elm.Syntax.Range exposing (Range)
|
||||
import Lint.Rule as Rule exposing (Error, Rule)
|
||||
import List.Extra
|
||||
|
||||
|
||||
{-| An expectation for an error. Use [`error`](#error) to create one.
|
||||
-}
|
||||
type alias ExpectedErrorData =
|
||||
{ message : String
|
||||
, under : String
|
||||
}
|
||||
|
||||
|
||||
type alias SourceCode =
|
||||
String
|
||||
|
||||
|
||||
|
||||
-- ERROR MESSAGES
|
||||
|
||||
|
||||
didNotExpectErrors : List Error -> String
|
||||
didNotExpectErrors errors =
|
||||
"""I expected no errors but found:
|
||||
|
||||
""" ++ (List.map errorToString errors |> String.join "\n ")
|
||||
|
||||
|
||||
parsingFailure : String
|
||||
parsingFailure =
|
||||
"""I could not parse the test source code, because it was not syntactically valid Elm code.
|
||||
|
||||
Maybe you forgot to add the module definition at the top, like:
|
||||
|
||||
`module A exposing (..)`"""
|
||||
|
||||
|
||||
messageMismatch : ExpectedErrorData -> Error -> String
|
||||
messageMismatch expectedError error_ =
|
||||
"""I was looking for the error with the following message:
|
||||
|
||||
`""" ++ expectedError.message ++ """`
|
||||
|
||||
but I found the following error message:
|
||||
|
||||
`""" ++ Rule.errorMessage error_ ++ "`"
|
||||
|
||||
|
||||
underMismatch : Error -> { under : String, codeAtLocation : String } -> String
|
||||
underMismatch error_ { under, codeAtLocation } =
|
||||
"""I found an error with the following message:
|
||||
|
||||
`""" ++ Rule.errorMessage error_ ++ """`
|
||||
|
||||
which I was expecting, but I found it under:
|
||||
|
||||
""" ++ formatSourceCode codeAtLocation ++ """
|
||||
|
||||
when I was expecting it under:
|
||||
|
||||
""" ++ formatSourceCode under ++ """
|
||||
|
||||
Hint: Maybe you're passing the `Range` of a wrong node when calling `Rule.error`"""
|
||||
|
||||
|
||||
wrongLocation : Error -> Range -> String -> String
|
||||
wrongLocation error_ range under =
|
||||
"""I was looking for the error with the following message:
|
||||
|
||||
`""" ++ Rule.errorMessage error_ ++ """`
|
||||
|
||||
under the following code:
|
||||
|
||||
""" ++ formatSourceCode under ++ """
|
||||
|
||||
and I found it, but the exact location you specified is not the one I found. I was expecting the error at:
|
||||
|
||||
""" ++ rangeAsString range ++ """
|
||||
|
||||
but I found it at:
|
||||
|
||||
""" ++ rangeAsString (Rule.errorRange error_)
|
||||
|
||||
|
||||
listOccurrencesAsLocations : SourceCode -> String -> List Int -> String
|
||||
listOccurrencesAsLocations sourceCode under occurrences =
|
||||
occurrences
|
||||
|> List.map
|
||||
(\occurrence ->
|
||||
occurrence
|
||||
|> positionAsRange sourceCode under
|
||||
|> rangeAsString
|
||||
|> (++) " - "
|
||||
)
|
||||
|> String.join "\n"
|
||||
|
||||
|
||||
positionAsRange : SourceCode -> String -> Int -> Range
|
||||
positionAsRange sourceCode under position =
|
||||
let
|
||||
linesBeforeAndIncludingPosition : List String
|
||||
linesBeforeAndIncludingPosition =
|
||||
sourceCode
|
||||
|> String.slice 0 position
|
||||
|> String.lines
|
||||
|
||||
startRow : Int
|
||||
startRow =
|
||||
List.length linesBeforeAndIncludingPosition
|
||||
|
||||
startColumn : Int
|
||||
startColumn =
|
||||
linesBeforeAndIncludingPosition
|
||||
|> List.Extra.last
|
||||
|> Maybe.withDefault ""
|
||||
|> String.length
|
||||
|> (+) 1
|
||||
|
||||
linesInUnder : List String
|
||||
linesInUnder =
|
||||
String.lines under
|
||||
|
||||
endRow : Int
|
||||
endRow =
|
||||
startRow + List.length linesInUnder - 1
|
||||
|
||||
endColumn : Int
|
||||
endColumn =
|
||||
if startRow == endRow then
|
||||
startColumn + String.length under
|
||||
|
||||
else
|
||||
linesInUnder
|
||||
|> List.Extra.last
|
||||
|> Maybe.withDefault ""
|
||||
|> String.length
|
||||
|> (+) 1
|
||||
in
|
||||
{ start =
|
||||
{ row = startRow
|
||||
, column = startColumn
|
||||
}
|
||||
, end =
|
||||
{ row = endRow
|
||||
, column = endColumn
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
errorToString : Error -> String
|
||||
errorToString error_ =
|
||||
"- \"" ++ Rule.errorMessage error_ ++ "\" at " ++ rangeAsString (Rule.errorRange error_)
|
||||
|
||||
|
||||
rangeAsString : Range -> String
|
||||
rangeAsString { start, end } =
|
||||
"{ start = { row = " ++ String.fromInt start.row ++ ", column = " ++ String.fromInt start.column ++ " }, end = { row = " ++ String.fromInt end.row ++ ", column = " ++ String.fromInt end.column ++ " } }"
|
||||
|
||||
|
||||
expectedMoreErrors : List ExpectedErrorData -> String
|
||||
expectedMoreErrors missingExpectedErrors =
|
||||
let
|
||||
numberOfErrors : Int
|
||||
numberOfErrors =
|
||||
List.length missingExpectedErrors
|
||||
in
|
||||
"I expected to see "
|
||||
++ String.fromInt numberOfErrors
|
||||
++ " more "
|
||||
++ pluralizeErrors numberOfErrors
|
||||
++ ":\n\n"
|
||||
++ (missingExpectedErrors
|
||||
|> List.map expectedErrorToString
|
||||
|> String.join "\n"
|
||||
)
|
||||
|
||||
|
||||
wrapInQuotes : String -> String
|
||||
wrapInQuotes string =
|
||||
"\"" ++ string ++ "\""
|
||||
|
||||
|
||||
tooManyErrors : List Error -> String
|
||||
tooManyErrors extraErrors =
|
||||
let
|
||||
numberOfErrors : Int
|
||||
numberOfErrors =
|
||||
List.length extraErrors
|
||||
in
|
||||
"I found "
|
||||
++ String.fromInt numberOfErrors
|
||||
++ " "
|
||||
++ pluralizeErrors numberOfErrors
|
||||
++ " too many:\n\n"
|
||||
++ (extraErrors
|
||||
|> List.map errorToString
|
||||
|> String.join "\n"
|
||||
)
|
||||
|
||||
|
||||
locationIsAmbiguousInSourceCode : SourceCode -> Error -> String -> List Int -> String
|
||||
locationIsAmbiguousInSourceCode sourceCode error_ under occurrencesInSourceCode =
|
||||
"""Your test passes, but where the message appears is ambiguous.
|
||||
|
||||
You are looking for the following error message:
|
||||
|
||||
`""" ++ Rule.errorMessage error_ ++ """`
|
||||
|
||||
and expecting to see it under:
|
||||
|
||||
""" ++ formatSourceCode under ++ """
|
||||
|
||||
I found """ ++ String.fromInt (List.length occurrencesInSourceCode) ++ """ locations where that code appeared. Please use `Lint.Rule.atExactly` to make the part you were targetting unambiguous.
|
||||
|
||||
Tip: I found them at:
|
||||
""" ++ listOccurrencesAsLocations sourceCode under occurrencesInSourceCode
|
||||
|
||||
|
||||
impossibleState : String
|
||||
impossibleState =
|
||||
"Oh no! I'm in an impossible state. I found an error at a location that I could not find back. Please let me know and give me an SSCCE (http://sscce.org/) here: https://github.com/jfmengels/elm-lint/issues."
|
||||
|
||||
|
||||
pluralizeErrors : Int -> String
|
||||
pluralizeErrors n =
|
||||
if n == 1 then
|
||||
"error"
|
||||
|
||||
else
|
||||
"errors"
|
||||
|
||||
|
||||
expectedErrorToString : ExpectedErrorData -> String
|
||||
expectedErrorToString expectedError =
|
||||
"- " ++ wrapInQuotes expectedError.message
|
||||
|
||||
|
||||
formatSourceCode : String -> String
|
||||
formatSourceCode string =
|
||||
let
|
||||
lines =
|
||||
String.lines string
|
||||
in
|
||||
if List.length lines == 1 then
|
||||
"`" ++ string ++ "`"
|
||||
|
||||
else
|
||||
lines
|
||||
|> List.map (\str -> " " ++ str)
|
||||
|> String.join "\n"
|
||||
|> (\str -> "```\n" ++ str ++ "\n ```")
|
@ -2,29 +2,29 @@ module ErrorMessageTest exposing (all)
|
||||
|
||||
import Elm.Syntax.Range exposing (Range)
|
||||
import Expect
|
||||
import Lint.Internal.Test exposing (ExpectedError, LintResult)
|
||||
import Lint.Rule as Rule exposing (Error)
|
||||
import Lint.Test.ErrorMessage as ErrorMessage exposing (ExpectedErrorData)
|
||||
import Test exposing (Test, describe, test)
|
||||
|
||||
|
||||
all : Test
|
||||
all =
|
||||
describe "Test.ErrorMessage"
|
||||
[ parsingErrorMessageTest
|
||||
[ parsingFailureTest
|
||||
, didNotExpectErrorsTest
|
||||
, messageMismatchErrorTest
|
||||
, underMismatchErrorTest
|
||||
, wrongLocationErrorTest
|
||||
, notEnoughErrorsTest
|
||||
, messageMismatchTest
|
||||
, underMismatchTest
|
||||
, wrongLocationTest
|
||||
, expectedMoreErrorsTest
|
||||
, tooManyErrorsTest
|
||||
]
|
||||
|
||||
|
||||
parsingErrorMessageTest : Test
|
||||
parsingErrorMessageTest =
|
||||
test "parsingErrorMessage" <|
|
||||
parsingFailureTest : Test
|
||||
parsingFailureTest =
|
||||
test "parsingFailure" <|
|
||||
\() ->
|
||||
Lint.Internal.Test.parsingErrorMessage
|
||||
ErrorMessage.parsingFailure
|
||||
|> Expect.equal (String.trim """
|
||||
I could not parse the test source code, because it was not syntactically valid Elm code.
|
||||
|
||||
@ -44,7 +44,7 @@ didNotExpectErrorsTest =
|
||||
, Rule.error "Some other error" dummyRange
|
||||
]
|
||||
in
|
||||
Lint.Internal.Test.didNotExpectErrors errors
|
||||
ErrorMessage.didNotExpectErrors errors
|
||||
|> Expect.equal (String.trim """
|
||||
I expected no errors but found:
|
||||
|
||||
@ -53,23 +53,22 @@ I expected no errors but found:
|
||||
""")
|
||||
|
||||
|
||||
messageMismatchErrorTest : Test
|
||||
messageMismatchErrorTest =
|
||||
test "messageMismatchError" <|
|
||||
messageMismatchTest : Test
|
||||
messageMismatchTest =
|
||||
test "messageMismatch" <|
|
||||
\() ->
|
||||
let
|
||||
expectedError : ExpectedError
|
||||
expectedError : ExpectedErrorData
|
||||
expectedError =
|
||||
Lint.Internal.Test.error
|
||||
{ message = "Forbidden use of Debug"
|
||||
, under = "Debug.log"
|
||||
}
|
||||
{ message = "Forbidden use of Debug"
|
||||
, under = "Debug.log"
|
||||
}
|
||||
|
||||
error : Error
|
||||
error =
|
||||
Rule.error "Forbidden use of Debu" dummyRange
|
||||
in
|
||||
Lint.Internal.Test.messageMismatchError expectedError error
|
||||
ErrorMessage.messageMismatch expectedError error
|
||||
|> Expect.equal (String.trim """
|
||||
I was looking for the error with the following message:
|
||||
|
||||
@ -80,9 +79,9 @@ but I found the following error message:
|
||||
`Forbidden use of Debu`""")
|
||||
|
||||
|
||||
underMismatchErrorTest : Test
|
||||
underMismatchErrorTest =
|
||||
describe "underMismatchError"
|
||||
underMismatchTest : Test
|
||||
underMismatchTest =
|
||||
describe "underMismatch"
|
||||
[ test "with single-line extracts" <|
|
||||
\() ->
|
||||
let
|
||||
@ -90,7 +89,7 @@ underMismatchErrorTest =
|
||||
error =
|
||||
Rule.error "Some error" dummyRange
|
||||
in
|
||||
Lint.Internal.Test.underMismatchError
|
||||
ErrorMessage.underMismatch
|
||||
error
|
||||
{ under = "abcd"
|
||||
, codeAtLocation = "abcd = 1"
|
||||
@ -116,7 +115,7 @@ Hint: Maybe you're passing the `Range` of a wrong node when calling `Rule.error`
|
||||
error =
|
||||
Rule.error "Some other error" dummyRange
|
||||
in
|
||||
Lint.Internal.Test.underMismatchError
|
||||
ErrorMessage.underMismatch
|
||||
error
|
||||
{ under = "abcd =\n 1\n + 2"
|
||||
, codeAtLocation = "abcd =\n 1"
|
||||
@ -145,9 +144,9 @@ Hint: Maybe you're passing the `Range` of a wrong node when calling `Rule.error`
|
||||
]
|
||||
|
||||
|
||||
wrongLocationErrorTest : Test
|
||||
wrongLocationErrorTest =
|
||||
describe "wrongLocationError"
|
||||
wrongLocationTest : Test
|
||||
wrongLocationTest =
|
||||
describe "wrongLocation"
|
||||
[ test "with single-line extracts" <|
|
||||
\() ->
|
||||
let
|
||||
@ -157,7 +156,7 @@ wrongLocationErrorTest =
|
||||
"Some error"
|
||||
{ start = { row = 3, column = 1 }, end = { row = 3, column = 5 } }
|
||||
in
|
||||
Lint.Internal.Test.wrongLocationError
|
||||
ErrorMessage.wrongLocation
|
||||
error
|
||||
{ start = { row = 2, column = 1 }, end = { row = 2, column = 5 } }
|
||||
"abcd"
|
||||
@ -187,7 +186,7 @@ but I found it at:
|
||||
"Some other error"
|
||||
{ start = { row = 4, column = 1 }, end = { row = 5, column = 3 } }
|
||||
in
|
||||
Lint.Internal.Test.wrongLocationError
|
||||
ErrorMessage.wrongLocation
|
||||
error
|
||||
{ start = { row = 2, column = 1 }, end = { row = 3, column = 3 } }
|
||||
"abcd =\n 1"
|
||||
@ -214,24 +213,22 @@ but I found it at:
|
||||
]
|
||||
|
||||
|
||||
notEnoughErrorsTest : Test
|
||||
notEnoughErrorsTest =
|
||||
test "notEnoughErrors" <|
|
||||
expectedMoreErrorsTest : Test
|
||||
expectedMoreErrorsTest =
|
||||
test "expectedMoreErrors" <|
|
||||
\() ->
|
||||
let
|
||||
missingErrors : List ExpectedError
|
||||
missingErrors : List ExpectedErrorData
|
||||
missingErrors =
|
||||
[ Lint.Internal.Test.error
|
||||
{ message = "Forbidden use of Debug"
|
||||
, under = "Debug.log"
|
||||
}
|
||||
, Lint.Internal.Test.error
|
||||
{ message = "Forbidden use of Debug"
|
||||
, under = "Debug.log"
|
||||
}
|
||||
[ { message = "Forbidden use of Debug"
|
||||
, under = "Debug.log"
|
||||
}
|
||||
, { message = "Forbidden use of Debug"
|
||||
, under = "Debug.log"
|
||||
}
|
||||
]
|
||||
in
|
||||
Lint.Internal.Test.notEnoughErrors missingErrors
|
||||
ErrorMessage.expectedMoreErrors missingErrors
|
||||
|> Expect.equal (String.trim """
|
||||
I expected to see 2 more errors:
|
||||
|
||||
@ -253,7 +250,7 @@ tooManyErrorsTest =
|
||||
{ start = { row = 2, column = 1 }, end = { row = 2, column = 5 } }
|
||||
]
|
||||
in
|
||||
Lint.Internal.Test.tooManyErrors extraErrors
|
||||
ErrorMessage.tooManyErrors extraErrors
|
||||
|> Expect.equal (String.trim """
|
||||
I found 1 error too many:
|
||||
|
||||
@ -272,7 +269,7 @@ I found 1 error too many:
|
||||
{ start = { row = 3, column = 1 }, end = { row = 3, column = 5 } }
|
||||
]
|
||||
in
|
||||
Lint.Internal.Test.tooManyErrors extraErrors
|
||||
ErrorMessage.tooManyErrors extraErrors
|
||||
|> Expect.equal (String.trim """
|
||||
I found 2 errors too many:
|
||||
|
||||
@ -282,9 +279,9 @@ I found 2 errors too many:
|
||||
]
|
||||
|
||||
|
||||
locationIsAmbiguousInSourceCodeErrorTest : Test
|
||||
locationIsAmbiguousInSourceCodeErrorTest =
|
||||
describe "locationIsAmbiguousInSourceCodeError"
|
||||
locationIsAmbiguousInSourceCodeTest : Test
|
||||
locationIsAmbiguousInSourceCodeTest =
|
||||
describe "locationIsAmbiguousInSourceCode"
|
||||
[ test "with single-line extracts" <|
|
||||
\() ->
|
||||
let
|
||||
@ -302,7 +299,7 @@ locationIsAmbiguousInSourceCodeErrorTest =
|
||||
"Some error"
|
||||
{ start = { row = 3, column = 1 }, end = { row = 3, column = 5 } }
|
||||
in
|
||||
Lint.Internal.Test.locationIsAmbiguousInSourceCodeError
|
||||
ErrorMessage.locationIsAmbiguousInSourceCode
|
||||
sourceCode
|
||||
error
|
||||
under
|
||||
@ -341,7 +338,7 @@ Tip: I found them at:
|
||||
"Some other error"
|
||||
{ start = { row = 3, column = 1 }, end = { row = 4, column = 3 } }
|
||||
in
|
||||
Lint.Internal.Test.locationIsAmbiguousInSourceCodeError
|
||||
ErrorMessage.locationIsAmbiguousInSourceCode
|
||||
sourceCode
|
||||
error
|
||||
under
|
||||
|
Loading…
Reference in New Issue
Block a user