mirror of
https://github.com/jfmengels/elm-review.git
synced 2024-08-16 19:40:22 +03:00
Compare commits
145 Commits
8ca0c579c9
...
4ad1dcfcdd
Author | SHA1 | Date | |
---|---|---|---|
|
4ad1dcfcdd | ||
|
45842b2234 | ||
|
038819ae49 | ||
|
04b3b9fa2a | ||
|
4129685142 | ||
|
a3413facb2 | ||
|
701eac7b95 | ||
|
f9f0f1b0b5 | ||
|
98f08aa62a | ||
|
a70dae14d7 | ||
|
e2eb764566 | ||
|
2d073c9317 | ||
|
be560d38be | ||
|
847decf1b4 | ||
|
67e94e484a | ||
|
bdf9173cd0 | ||
|
d18a7452c8 | ||
|
2e92713315 | ||
|
a9eef34293 | ||
|
2a0cc68767 | ||
|
d620dde379 | ||
|
8b7662cdfc | ||
|
081aefae99 | ||
|
348e70e256 | ||
|
47f662db3e | ||
|
955d2837de | ||
|
d1ef356e3f | ||
|
56c17ba5fa | ||
|
3b62c5d831 | ||
|
113cf7f061 | ||
|
abb2dce7a7 | ||
|
56e75c931f | ||
|
7a230f309e | ||
|
620abd615e | ||
|
ca62e5159f | ||
|
cc6847f48b | ||
|
3d045ddf8a | ||
|
078e2d0414 | ||
|
4245fc1fb0 | ||
|
aeec25f60a | ||
|
d6aed416fb | ||
|
9f34b4cba7 | ||
|
5d64d1073e | ||
|
2e82646094 | ||
|
b9f0160325 | ||
|
0f787768a1 | ||
|
c155011766 | ||
|
83c8b17f60 | ||
|
79b56c6066 | ||
|
05fb48fcca | ||
|
500923da91 | ||
|
ce66fafdfe | ||
|
c78297f483 | ||
|
aa48c3df63 | ||
|
ca33d96ef2 | ||
|
d16f175dd0 | ||
|
04ad09644d | ||
|
3538b75cac | ||
|
a549883b89 | ||
|
e0fc0df147 | ||
|
df6cb154ff | ||
|
3024910771 | ||
|
8752f2316f | ||
|
f92ee4b901 | ||
|
45774ce35b | ||
|
608855d1e0 | ||
|
14227c4b35 | ||
|
676742d633 | ||
|
91bfcb1baf | ||
|
b29d9f3fef | ||
|
eecf1e4f24 | ||
|
167e5f15a1 | ||
|
2cb805bf91 | ||
|
da70f8c64f | ||
|
5322a8a5d1 | ||
|
64554ef45f | ||
|
04f9a1527b | ||
|
d01d870fc0 | ||
|
3c1107df56 | ||
|
b00b58cf1e | ||
|
368ce17a56 | ||
|
d377aeb436 | ||
|
fa7f8c67af | ||
|
ba699a7a86 | ||
|
46f0746234 | ||
|
ee05040efb | ||
|
56c734163f | ||
|
acb6cce062 | ||
|
095abbcb4f | ||
|
ac9fefd13e | ||
|
e809ae75ba | ||
|
9fa185ebbc | ||
|
8886306ef5 | ||
|
06760b9076 | ||
|
174e586fa5 | ||
|
4abbc530e8 | ||
|
284cdb6c3d | ||
|
125e28c710 | ||
|
201b93311a | ||
|
6614d677b6 | ||
|
7935afd767 | ||
|
a6f7aa7a71 | ||
|
1e41edb186 | ||
|
6f86b12a6d | ||
|
e9285810df | ||
|
7460c22e1a | ||
|
a64f4fe18d | ||
|
40e619410f | ||
|
eee733e68f | ||
|
12e0702ed6 | ||
|
7dcd85648d | ||
|
ddae5608cc | ||
|
0f472777dd | ||
|
331b7b1429 | ||
|
a98bc1a64d | ||
|
94c5571e40 | ||
|
45018398dd | ||
|
71541bb3b2 | ||
|
ce3442ddfe | ||
|
625bbb5f14 | ||
|
a628db975f | ||
|
0dc931abd5 | ||
|
ff4469ace9 | ||
|
57118354be | ||
|
9c1984c218 | ||
|
080506814a | ||
|
f05deb520c | ||
|
a6298e737d | ||
|
993944a2ba | ||
|
330352a269 | ||
|
1a22611506 | ||
|
b7a7543b07 | ||
|
b025706c85 | ||
|
c160d6ee34 | ||
|
9e7e51768e | ||
|
c4aef02f8f | ||
|
c1b7f06790 | ||
|
b905d93b47 | ||
|
f3e9a05134 | ||
|
4a1e6673d2 | ||
|
3673dabbf3 | ||
|
218973ef70 | ||
|
267d2038ba | ||
|
929d8508fd | ||
|
84aae3b64b |
26
CHANGELOG.md
26
CHANGELOG.md
@ -1,29 +1,8 @@
|
||||
# Changelog
|
||||
|
||||
## [2.13.0] - 2023-04-16
|
||||
|
||||
1) Changed the order in which rules are applied on modules. [#153](https://github.com/jfmengels/elm-review/pull/153)
|
||||
|
||||
Instead of visiting the entire project for each rule sequentially, we now visit the entire project once but apply each
|
||||
rule on each module. This should hopefully result in a small speed improvement, and make it more interesting to precompute
|
||||
interesting information to provide the rules (such as the module name lookup table)
|
||||
|
||||
2) Made it less costly to compute whether cached analysis can be reused [#154](https://github.com/jfmengels/elm-review/pull/154)
|
||||
|
||||
The caching mechanism introduced in [2.11.0] felt inefficient. It improved the performance a bit but not as significantly as expected.
|
||||
The reason for that was that the method to check whether a cached analysis could be re-used or not was extremely inefficient.
|
||||
Changing the representation of that key vastly improved the performance of the whole cache system, which now feels worth it.
|
||||
|
||||
3) Applying fixes for all targets [#155](https://github.com/jfmengels/elm-review/pull/155)
|
||||
|
||||
In [2.10.0] the package introduced the ability to apply fixes on its own, without the need of the CLI. It did however not
|
||||
support applying fixes for the `elm.json` file, as that can have important repercussions on the analysis (if
|
||||
`source-directories` or dependencies are changed). These fixes are now applied as well.
|
||||
|
||||
Applying all fixes in the package means that there is no need to try and apply fixes in the CLI, which will be removed in its v2.10.0.
|
||||
The CLI was responsible for annotating fixes as failing, which is why this release introduces [`Review.Rule.errorFixFailure`] to allow
|
||||
the CLI to show when a fix failed to apply.
|
||||
## [Unreleased]
|
||||
|
||||
Stuff happened
|
||||
|
||||
## [2.12.2] - 2023-02-02
|
||||
|
||||
@ -159,7 +138,6 @@ Help would be appreciated to fill the blanks!
|
||||
[`Review.Rule.errorFixFailure`]: https://package.elm-lang.org/packages/jfmengels/elm-review/latest/Review-Rule#errorFixFailure
|
||||
[`Review.Test.ignoredFilesImpactResults`]: https://package.elm-lang.org/packages/jfmengels/elm-review/latest/Review-Rule-Test#ignoredFilesImpactResults
|
||||
|
||||
[2.13.0]: https://github.com/jfmengels/elm-review/releases/tag/2.13.0
|
||||
[2.12.2]: https://github.com/jfmengels/elm-review/releases/tag/2.12.2
|
||||
[2.12.1]: https://github.com/jfmengels/elm-review/releases/tag/2.12.1
|
||||
[2.12.0]: https://github.com/jfmengels/elm-review/releases/tag/2.12.0
|
||||
|
@ -116,7 +116,7 @@ Before you start adding rules or an unfamiliar existing configuration, I suggest
|
||||
## Write your own rule
|
||||
|
||||
You can write your own rule using this package's API and [`elm-syntax`](https://package.elm-lang.org/packages/stil4m/elm-syntax/7.2.1/).
|
||||
Check out the [`Review.Rule`](https://package.elm-lang.org/packages/jfmengels/elm-review/2.13.0/Review-Rule/) documentation for how to get started.
|
||||
Check out the [`Review.Rule`](https://package.elm-lang.org/packages/jfmengels/elm-review/2.13.1/Review-Rule/) documentation for how to get started.
|
||||
|
||||
**NOTE**: If you want to **create a package** containing `elm-review` rules, I highly recommend using the
|
||||
[CLI's](https://github.com/jfmengels/node-elm-review/) `elm-review new-package` subcommand. This will create a new package that will help you use the best practices and give you helpful tools like easy auto-publishing. More information is available in the maintenance file generated along with it.
|
||||
@ -236,7 +236,7 @@ It does provide 2 systems that I think are better alternatives for the health of
|
||||
|
||||
### Configuring exceptions
|
||||
|
||||
You can [configure exceptions](https://package.elm-lang.org/packages/jfmengels/elm-review/2.13.0/Review-Rule/#configuring-exceptions),
|
||||
You can [configure exceptions](https://package.elm-lang.org/packages/jfmengels/elm-review/2.13.1/Review-Rule/#configuring-exceptions),
|
||||
which consists of marking specific directories or files as not relevant to a rule or set of rules, preventing errors to be reported for those.
|
||||
|
||||
It is a good fit if you wish for `elm-review` to not report errors in vendored or generated code,
|
||||
@ -282,7 +282,7 @@ the codebase. You can use this to gain insight into your codebase, or provide in
|
||||
powerful integrations.
|
||||
|
||||
To make use of this feature, run `elm-review --extract --report=json` with a configuration containing a rule that uses
|
||||
[`Rule.withDataExtractor`](https://package.elm-lang.org/packages/jfmengels/elm-review/2.13.0/Review-Rule/#withDataExtractor).
|
||||
[`Rule.withDataExtractor`](https://package.elm-lang.org/packages/jfmengels/elm-review/2.13.1/Review-Rule/#withDataExtractor).
|
||||
|
||||
The result for a rule will be stored under `<json>.extracts.<YourRuleName>`. To access it, you can then pipe the result
|
||||
into either a `Node.js` script, a tool that expects JSON, or [`jq`](https://stedolan.github.io/jq/) as in the example below.
|
||||
|
9
elm.json
9
elm.json
@ -3,12 +3,13 @@
|
||||
"name": "jfmengels/elm-review",
|
||||
"summary": "Analyzes Elm projects, to help find mistakes before your users find them.",
|
||||
"license": "BSD-3-Clause",
|
||||
"version": "2.13.0",
|
||||
"version": "2.13.1",
|
||||
"exposed-modules": [
|
||||
"Review.Rule",
|
||||
"Review.ModuleNameLookupTable",
|
||||
"Review.Project",
|
||||
"Review.Project.Dependency",
|
||||
"Review.FilePattern",
|
||||
"Review.Fix",
|
||||
"Review.Test",
|
||||
"Review.Test.Dependencies",
|
||||
@ -18,13 +19,13 @@
|
||||
"dependencies": {
|
||||
"elm/core": "1.0.2 <= v < 2.0.0",
|
||||
"elm/json": "1.1.3 <= v < 2.0.0",
|
||||
"elm/parser": "1.1.0 <= v < 2.0.0",
|
||||
"elm/project-metadata-utils": "1.0.0 <= v < 2.0.0",
|
||||
"elm/regex": "1.0.0 <= v < 2.0.0",
|
||||
"elm-explorations/test": "2.0.1 <= v < 3.0.0",
|
||||
"stil4m/elm-syntax": "7.2.7 <= v < 8.0.0"
|
||||
},
|
||||
"test-dependencies": {
|
||||
"elm/parser": "1.1.0 <= v < 2.0.0",
|
||||
"elm/regex": "1.0.0 <= v < 2.0.0",
|
||||
"pzp1997/assoc-list": "1.0.0 <= v < 2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ when inside the directory containing this file.
|
||||
-}
|
||||
|
||||
import Docs.NoMissing exposing (exposedModules, onlyExposed)
|
||||
import Docs.NoMissingChangelogEntry
|
||||
import Docs.ReviewAtDocs
|
||||
import Docs.ReviewLinksAndSections
|
||||
import Docs.UpToDateReadmeLinks
|
||||
@ -43,6 +44,7 @@ config =
|
||||
, Docs.ReviewAtDocs.rule
|
||||
|> Rule.ignoreErrorsForDirectories [ "tests/" ]
|
||||
, Docs.NoMissing.rule { document = onlyExposed, from = exposedModules }
|
||||
, Docs.NoMissingChangelogEntry.rule Docs.NoMissingChangelogEntry.defaults
|
||||
, NoDebug.Log.rule
|
||||
, NoDebug.TodoOrToString.rule
|
||||
|> Rule.ignoreErrorsForDirectories [ "tests/" ]
|
||||
|
268
src/Glob.elm
Normal file
268
src/Glob.elm
Normal file
@ -0,0 +1,268 @@
|
||||
module Glob exposing
|
||||
( Glob, fromString, never
|
||||
, match
|
||||
)
|
||||
|
||||
{-| A library for working with [glob].
|
||||
|
||||
[glob]: https://en.wikipedia.org/wiki/Glob_%28programming%29
|
||||
|
||||
@docs Glob, fromString, never
|
||||
@docs match
|
||||
|
||||
-}
|
||||
|
||||
import Parser exposing ((|.), (|=), Parser)
|
||||
import Regex exposing (Regex)
|
||||
import Set
|
||||
|
||||
|
||||
{-| A Glob expression.
|
||||
-}
|
||||
type Glob
|
||||
= Glob (List Component)
|
||||
|
||||
|
||||
type Component
|
||||
= TwoAsterisks
|
||||
| Fragments Regex
|
||||
|
||||
|
||||
type Fragment
|
||||
= Literal String
|
||||
| Alternatives (List String)
|
||||
| Class { negative : Bool, inner : String }
|
||||
| QuestionMark
|
||||
| Asterisk
|
||||
|
||||
|
||||
{-| Match a file path against a glob.
|
||||
-}
|
||||
match : Glob -> String -> Bool
|
||||
match (Glob parsed) input =
|
||||
matchComponents parsed (String.split "/" input)
|
||||
|
||||
|
||||
matchComponents : List Component -> List String -> Bool
|
||||
matchComponents components segments =
|
||||
case ( components, segments ) of
|
||||
( [], [] ) ->
|
||||
True
|
||||
|
||||
( _ :: _, [] ) ->
|
||||
False
|
||||
|
||||
( [], _ :: _ ) ->
|
||||
False
|
||||
|
||||
( TwoAsterisks :: ctail, _ :: stail ) ->
|
||||
if matchComponents components stail then
|
||||
True
|
||||
|
||||
else
|
||||
matchComponents ctail segments
|
||||
|
||||
( (Fragments chead) :: ctail, shead :: stail ) ->
|
||||
if Regex.contains chead shead then
|
||||
matchComponents ctail stail
|
||||
|
||||
else
|
||||
False
|
||||
|
||||
|
||||
{-| A `glob` that never matches.
|
||||
-}
|
||||
never : Glob
|
||||
never =
|
||||
Glob []
|
||||
|
||||
|
||||
{-| Parse a string into a `glob`.
|
||||
-}
|
||||
fromString : String -> Result (List Parser.DeadEnd) Glob
|
||||
fromString input =
|
||||
input
|
||||
|> Parser.run parser
|
||||
|> Result.map Glob
|
||||
|
||||
|
||||
parser : Parser (List Component)
|
||||
parser =
|
||||
Parser.sequence
|
||||
{ start = ""
|
||||
, end = ""
|
||||
, separator = "/"
|
||||
, trailing = Parser.Optional
|
||||
, spaces = Parser.succeed ()
|
||||
, item = componentParser
|
||||
}
|
||||
|. Parser.end
|
||||
|
||||
|
||||
componentParser : Parser Component
|
||||
componentParser =
|
||||
Parser.oneOf
|
||||
[ Parser.succeed TwoAsterisks
|
||||
|. Parser.symbol "**"
|
||||
, Parser.succeed fragmentsToRegex
|
||||
|= Parser.getOffset
|
||||
|= Parser.sequence
|
||||
{ start = ""
|
||||
, end = ""
|
||||
, separator = ""
|
||||
, trailing = Parser.Optional
|
||||
, spaces = Parser.succeed ()
|
||||
, item = fragmentParser
|
||||
}
|
||||
|= Parser.getOffset
|
||||
|= Parser.getSource
|
||||
|> Parser.andThen identity
|
||||
|> Parser.map Fragments
|
||||
]
|
||||
|
||||
|
||||
fragmentsToRegex : Int -> List Fragment -> Int -> String -> Parser Regex
|
||||
fragmentsToRegex before fragments after source =
|
||||
let
|
||||
regexString : String
|
||||
regexString =
|
||||
List.foldl
|
||||
(\fragment acc -> acc ++ fragmentToRegexString fragment)
|
||||
"^"
|
||||
fragments
|
||||
in
|
||||
case Regex.fromStringWith { caseInsensitive = False, multiline = True } (regexString ++ "$") of
|
||||
Nothing ->
|
||||
Parser.problem <|
|
||||
"Could not parse \""
|
||||
++ regexString
|
||||
++ "\" as a regex, obtained from "
|
||||
++ String.slice before after source
|
||||
|
||||
Just regex ->
|
||||
Parser.succeed regex
|
||||
|
||||
|
||||
fragmentToRegexString : Fragment -> String
|
||||
fragmentToRegexString fragment =
|
||||
case fragment of
|
||||
Literal literal ->
|
||||
regexEscape literal
|
||||
|
||||
Alternatives alternatives ->
|
||||
"(" ++ String.join "|" (List.map regexEscape alternatives) ++ ")"
|
||||
|
||||
Class { negative, inner } ->
|
||||
let
|
||||
cut : String
|
||||
cut =
|
||||
inner
|
||||
|> String.replace "^" "\\^"
|
||||
|> String.replace "\\" "\\\\"
|
||||
in
|
||||
if negative then
|
||||
"[^" ++ cut ++ "]"
|
||||
|
||||
else
|
||||
"[" ++ cut ++ "]"
|
||||
|
||||
QuestionMark ->
|
||||
"."
|
||||
|
||||
Asterisk ->
|
||||
".*"
|
||||
|
||||
|
||||
regexEscape : String -> String
|
||||
regexEscape input =
|
||||
input
|
||||
|> String.foldr
|
||||
(\c acc ->
|
||||
if Char.isAlphaNum c then
|
||||
c :: acc
|
||||
|
||||
else
|
||||
case c of
|
||||
'\\' ->
|
||||
'\\' :: '\\' :: acc
|
||||
|
||||
']' ->
|
||||
'\\' :: c :: acc
|
||||
|
||||
_ ->
|
||||
'[' :: c :: ']' :: acc
|
||||
)
|
||||
[]
|
||||
|> String.fromList
|
||||
|
||||
|
||||
fragmentParser : Parser Fragment
|
||||
fragmentParser =
|
||||
Parser.oneOf
|
||||
[ Parser.succeed Literal
|
||||
|. Parser.symbol "\\"
|
||||
|= Parser.getChompedString (Parser.chompIf (\_ -> True))
|
||||
, Parser.succeed QuestionMark
|
||||
|. Parser.symbol "?"
|
||||
, Parser.succeed Asterisk
|
||||
|. Parser.symbol "*"
|
||||
, Parser.succeed (Alternatives << Set.toList << Set.fromList)
|
||||
|= Parser.sequence
|
||||
{ start = "{"
|
||||
, end = "}"
|
||||
, separator = ","
|
||||
, trailing = Parser.Forbidden
|
||||
, spaces = Parser.succeed ()
|
||||
, item = nonemptyChomper <| \c -> notSpecial c && c /= ','
|
||||
}
|
||||
, Parser.succeed
|
||||
(\negative inner closed source ->
|
||||
if closed then
|
||||
Class { negative = negative, inner = inner }
|
||||
|
||||
else
|
||||
Literal source
|
||||
)
|
||||
|. Parser.symbol "["
|
||||
|= Parser.oneOf
|
||||
[ Parser.succeed True
|
||||
|. Parser.symbol "!"
|
||||
, Parser.succeed False
|
||||
]
|
||||
|= Parser.getChompedString
|
||||
(Parser.chompWhile (\c -> c /= ']'))
|
||||
|= Parser.oneOf
|
||||
[ Parser.symbol "]"
|
||||
|> Parser.map (\() -> True)
|
||||
, Parser.end
|
||||
|> Parser.map (\() -> False)
|
||||
]
|
||||
|= Parser.getSource
|
||||
, Parser.succeed Literal
|
||||
|= nonemptyChomper notSpecial
|
||||
, Parser.problem "fragmentParser"
|
||||
]
|
||||
|
||||
|
||||
nonemptyChomper : (Char -> Bool) -> Parser String
|
||||
nonemptyChomper f =
|
||||
Parser.getChompedString
|
||||
(Parser.chompIf f
|
||||
|. Parser.chompWhile f
|
||||
)
|
||||
|
||||
|
||||
notSpecial : Char -> Bool
|
||||
notSpecial c =
|
||||
not (isSpecialChar c)
|
||||
|
||||
|
||||
isSpecialChar : Char -> Bool
|
||||
isSpecialChar c =
|
||||
(c == '/')
|
||||
|| (c == '*')
|
||||
|| (c == '{')
|
||||
|| (c == '}')
|
||||
|| (c == '[')
|
||||
|| (c == ']')
|
||||
|| (c == '?')
|
@ -1,4 +1,4 @@
|
||||
module Review.Cache.ContentHash exposing (ContentHash, areEqual, areEqualForMaybe, hash)
|
||||
module Review.Cache.ContentHash exposing (ContentHash, areEqual, areEqualForList, areEqualForMaybe, hash)
|
||||
|
||||
import Vendor.Murmur3 as Murmur3
|
||||
|
||||
@ -28,3 +28,18 @@ areEqualForMaybe a b =
|
||||
|
||||
_ ->
|
||||
False
|
||||
|
||||
|
||||
areEqualForList : List ContentHash -> List ContentHash -> Bool
|
||||
areEqualForList aList bList =
|
||||
case aList of
|
||||
[] ->
|
||||
List.isEmpty bList
|
||||
|
||||
a :: aTail ->
|
||||
case bList of
|
||||
[] ->
|
||||
False
|
||||
|
||||
b :: bTail ->
|
||||
areEqual a b && areEqualForList aTail bTail
|
||||
|
86
src/Review/Cache/ExtraFile.elm
Normal file
86
src/Review/Cache/ExtraFile.elm
Normal file
@ -0,0 +1,86 @@
|
||||
module Review.Cache.ExtraFile exposing
|
||||
( Entry, create
|
||||
, match
|
||||
, errors, errorsForMaybe, setErrors
|
||||
, outputContext, outputContextHash
|
||||
)
|
||||
|
||||
{-| Cache for the result of the analysis of arbitrary files.
|
||||
|
||||
@docs Entry, create
|
||||
@docs match
|
||||
@docs errors, errorsForMaybe, setErrors
|
||||
@docs outputContext, outputContextHash
|
||||
|
||||
-}
|
||||
|
||||
import Review.Cache.ContentHash as ContentHash exposing (ContentHash)
|
||||
import Review.Cache.ContextHash as ContextHash exposing (ComparableContextHash, ContextHash)
|
||||
|
||||
|
||||
type Entry error context
|
||||
= Entry
|
||||
{ contentHashes : List ContentHash
|
||||
, inputContextHash : ComparableContextHash context
|
||||
, errors : List error
|
||||
, outputContext : context
|
||||
, outputContextHash : ContextHash context
|
||||
}
|
||||
|
||||
|
||||
create :
|
||||
{ contentHashes : List ContentHash
|
||||
, inputContextHash : ComparableContextHash context
|
||||
, errors : List error
|
||||
, outputContext : context
|
||||
}
|
||||
-> Entry error context
|
||||
create entry =
|
||||
Entry
|
||||
{ contentHashes = entry.contentHashes
|
||||
, inputContextHash = entry.inputContextHash
|
||||
, errors = entry.errors
|
||||
, outputContext = entry.outputContext
|
||||
, outputContextHash = ContextHash.create entry.outputContext
|
||||
}
|
||||
|
||||
|
||||
match : List ContentHash -> ComparableContextHash context -> Entry error context -> Bool
|
||||
match contentHashes contexts (Entry entry) =
|
||||
ContentHash.areEqualForList contentHashes entry.contentHashes
|
||||
&& (contexts == entry.inputContextHash)
|
||||
|
||||
|
||||
errors : Entry error context -> List error
|
||||
errors (Entry entry) =
|
||||
entry.errors
|
||||
|
||||
|
||||
errorsForMaybe : Maybe (Entry error context) -> List error
|
||||
errorsForMaybe maybeEntry =
|
||||
case maybeEntry of
|
||||
Just (Entry entry) ->
|
||||
entry.errors
|
||||
|
||||
Nothing ->
|
||||
[]
|
||||
|
||||
|
||||
setErrors : List error -> Maybe (Entry error context) -> Maybe (Entry error context)
|
||||
setErrors newErrors maybeEntry =
|
||||
case maybeEntry of
|
||||
Just (Entry entry) ->
|
||||
Just (Entry { entry | errors = newErrors })
|
||||
|
||||
Nothing ->
|
||||
Nothing
|
||||
|
||||
|
||||
outputContext : Entry error context -> context
|
||||
outputContext (Entry entry) =
|
||||
entry.outputContext
|
||||
|
||||
|
||||
outputContextHash : Entry error context -> ContextHash context
|
||||
outputContextHash (Entry entry) =
|
||||
entry.outputContextHash
|
@ -13,6 +13,7 @@ type Target
|
||||
= Module
|
||||
| ElmJson
|
||||
| Readme
|
||||
| ExtraFile
|
||||
| Global
|
||||
| UserGlobal
|
||||
|
||||
|
238
src/Review/FilePattern.elm
Normal file
238
src/Review/FilePattern.elm
Normal file
@ -0,0 +1,238 @@
|
||||
module Review.FilePattern exposing (FilePattern, compact, exclude, excludeFolder, include, match, toStrings)
|
||||
|
||||
import Glob exposing (Glob)
|
||||
|
||||
|
||||
type FilePattern
|
||||
= Include String
|
||||
| Exclude String
|
||||
| ExcludeFolder String
|
||||
| InvalidGlob String
|
||||
|
||||
|
||||
type alias Summary =
|
||||
{ includeExclude : List CompactFilePattern
|
||||
, excludedFolders : List Glob
|
||||
, strings : List { pattern : String, included : Bool }
|
||||
, excludedFoldersStrings : List String
|
||||
}
|
||||
|
||||
|
||||
toStrings : Summary -> { files : List { pattern : String, included : Bool }, excludedFolders : List String }
|
||||
toStrings summary =
|
||||
{ files = summary.strings
|
||||
, excludedFolders = summary.excludedFoldersStrings
|
||||
}
|
||||
|
||||
|
||||
type CompactFilePattern
|
||||
= CompactInclude (List Glob)
|
||||
| CompactExclude (List Glob)
|
||||
|
||||
|
||||
compact : List FilePattern -> Result (List String) Summary
|
||||
compact filePatterns =
|
||||
compactBase filePatterns
|
||||
{ includeExclude = []
|
||||
, excludedFolders = []
|
||||
, strings = []
|
||||
, excludedFoldersStrings = []
|
||||
}
|
||||
|> Result.map
|
||||
(\summary ->
|
||||
{ includeExclude = summary.includeExclude
|
||||
, excludedFolders = summary.excludedFolders
|
||||
, strings = List.reverse summary.strings
|
||||
, excludedFoldersStrings = List.reverse summary.excludedFoldersStrings
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
compactBase : List FilePattern -> Summary -> Result (List String) Summary
|
||||
compactBase filePatterns accSummary =
|
||||
case filePatterns of
|
||||
[] ->
|
||||
Ok accSummary
|
||||
|
||||
(Include raw) :: rest ->
|
||||
case Glob.fromString raw of
|
||||
Ok pattern ->
|
||||
compactHelp rest [ pattern ] True (addRawIncludeExclude raw True accSummary)
|
||||
|
||||
Err _ ->
|
||||
Err (compactErrors rest [ raw ])
|
||||
|
||||
(Exclude raw) :: rest ->
|
||||
case Glob.fromString raw of
|
||||
Ok pattern ->
|
||||
compactHelp rest [ pattern ] False (addRawIncludeExclude raw False accSummary)
|
||||
|
||||
Err _ ->
|
||||
Err (compactErrors rest [ raw ])
|
||||
|
||||
(ExcludeFolder raw) :: rest ->
|
||||
case Glob.fromString (toFolder raw) of
|
||||
Ok pattern ->
|
||||
compactBase rest
|
||||
{ includeExclude = accSummary.includeExclude
|
||||
, excludedFolders = pattern :: accSummary.excludedFolders
|
||||
, strings = accSummary.strings
|
||||
, excludedFoldersStrings = raw :: accSummary.excludedFoldersStrings
|
||||
}
|
||||
|
||||
Err _ ->
|
||||
Err (compactErrors rest [ raw ])
|
||||
|
||||
(InvalidGlob pattern) :: rest ->
|
||||
Err (compactErrors rest [ pattern ])
|
||||
|
||||
|
||||
compactHelp : List FilePattern -> List Glob -> Bool -> Summary -> Result (List String) Summary
|
||||
compactHelp filePatterns accGlobs included accSummary =
|
||||
case filePatterns of
|
||||
[] ->
|
||||
Ok
|
||||
{ includeExclude =
|
||||
(if included then
|
||||
CompactInclude accGlobs
|
||||
|
||||
else
|
||||
CompactExclude accGlobs
|
||||
)
|
||||
:: accSummary.includeExclude
|
||||
, excludedFolders = accSummary.excludedFolders
|
||||
, strings = accSummary.strings
|
||||
, excludedFoldersStrings = accSummary.excludedFoldersStrings
|
||||
}
|
||||
|
||||
(Include raw) :: rest ->
|
||||
case Glob.fromString raw of
|
||||
Ok pattern ->
|
||||
if included then
|
||||
compactHelp rest (pattern :: accGlobs) included (addRawIncludeExclude raw included accSummary)
|
||||
|
||||
else
|
||||
compactHelp rest
|
||||
[ pattern ]
|
||||
True
|
||||
{ includeExclude = CompactExclude accGlobs :: accSummary.includeExclude
|
||||
, excludedFolders = accSummary.excludedFolders
|
||||
, strings = { pattern = raw, included = True } :: accSummary.strings
|
||||
, excludedFoldersStrings = accSummary.excludedFoldersStrings
|
||||
}
|
||||
|
||||
Err _ ->
|
||||
Err (compactErrors rest [ raw ])
|
||||
|
||||
(Exclude raw) :: rest ->
|
||||
case Glob.fromString raw of
|
||||
Ok pattern ->
|
||||
if included then
|
||||
compactHelp rest
|
||||
[ pattern ]
|
||||
False
|
||||
{ includeExclude = CompactInclude accGlobs :: accSummary.includeExclude
|
||||
, excludedFolders = accSummary.excludedFolders
|
||||
, strings = { pattern = raw, included = False } :: accSummary.strings
|
||||
, excludedFoldersStrings = accSummary.excludedFoldersStrings
|
||||
}
|
||||
|
||||
else
|
||||
compactHelp rest (pattern :: accGlobs) included (addRawIncludeExclude raw included accSummary)
|
||||
|
||||
Err _ ->
|
||||
Err (compactErrors rest [ raw ])
|
||||
|
||||
(ExcludeFolder raw) :: rest ->
|
||||
case Glob.fromString (toFolder raw) of
|
||||
Ok pattern ->
|
||||
compactHelp rest
|
||||
accGlobs
|
||||
included
|
||||
{ includeExclude = accSummary.includeExclude
|
||||
, excludedFolders = pattern :: accSummary.excludedFolders
|
||||
, strings = accSummary.strings
|
||||
, excludedFoldersStrings = raw :: accSummary.excludedFoldersStrings
|
||||
}
|
||||
|
||||
Err _ ->
|
||||
Err (compactErrors rest [ raw ])
|
||||
|
||||
(InvalidGlob invalidGlobStr) :: rest ->
|
||||
Err (compactErrors rest [ invalidGlobStr ])
|
||||
|
||||
|
||||
compactErrors : List FilePattern -> List String -> List String
|
||||
compactErrors filePatterns accGlobStrings =
|
||||
case filePatterns of
|
||||
[] ->
|
||||
List.reverse accGlobStrings
|
||||
|
||||
(InvalidGlob invalidGlobStr) :: rest ->
|
||||
compactErrors rest (invalidGlobStr :: accGlobStrings)
|
||||
|
||||
_ :: rest ->
|
||||
compactErrors rest accGlobStrings
|
||||
|
||||
|
||||
addRawIncludeExclude : String -> Bool -> Summary -> Summary
|
||||
addRawIncludeExclude string included summary =
|
||||
{ includeExclude = summary.includeExclude
|
||||
, excludedFolders = summary.excludedFolders
|
||||
, strings = { pattern = string, included = included } :: summary.strings
|
||||
, excludedFoldersStrings = summary.excludedFoldersStrings
|
||||
}
|
||||
|
||||
|
||||
include : String -> FilePattern
|
||||
include =
|
||||
Include
|
||||
|
||||
|
||||
exclude : String -> FilePattern
|
||||
exclude =
|
||||
Exclude
|
||||
|
||||
|
||||
excludeFolder : String -> FilePattern
|
||||
excludeFolder =
|
||||
ExcludeFolder
|
||||
|
||||
|
||||
toFolder : String -> String
|
||||
toFolder globStr =
|
||||
if String.endsWith "/" globStr then
|
||||
globStr ++ "**/*"
|
||||
|
||||
else
|
||||
globStr ++ "/**/*"
|
||||
|
||||
|
||||
match : Summary -> String -> Bool
|
||||
match summary str =
|
||||
if List.any (\folderGlob -> Glob.match folderGlob str) summary.excludedFolders then
|
||||
False
|
||||
|
||||
else
|
||||
matchHelp summary.includeExclude str
|
||||
|
||||
|
||||
matchHelp : List CompactFilePattern -> String -> Bool
|
||||
matchHelp filePatterns str =
|
||||
case filePatterns of
|
||||
[] ->
|
||||
False
|
||||
|
||||
(CompactInclude globs) :: rest ->
|
||||
if List.any (\glob -> Glob.match glob str) globs then
|
||||
True
|
||||
|
||||
else
|
||||
matchHelp rest str
|
||||
|
||||
(CompactExclude globs) :: rest ->
|
||||
if List.any (\glob -> Glob.match glob str) globs then
|
||||
False
|
||||
|
||||
else
|
||||
matchHelp rest str
|
@ -212,6 +212,12 @@ fix target fixes sourceCode =
|
||||
sourceCode
|
||||
(always True)
|
||||
|
||||
Error.ExtraFile ->
|
||||
tryToApplyFix
|
||||
fixes
|
||||
sourceCode
|
||||
(always True)
|
||||
|
||||
Error.ElmJson ->
|
||||
tryToApplyFix
|
||||
fixes
|
||||
|
@ -1,4 +1,4 @@
|
||||
module Review.Fix.Internal exposing (Fix(..), applyFix, containRangeCollisions, fixElmJson, fixModule, fixReadme, rangePosition)
|
||||
module Review.Fix.Internal exposing (Fix(..), applyFix, containRangeCollisions, fixElmJson, fixExtraFile, fixModule, fixReadme, rangePosition)
|
||||
|
||||
import Array
|
||||
import Elm.Project
|
||||
@ -124,6 +124,13 @@ fixReadme fixes originalSourceCode =
|
||||
tryToApplyFix fixes originalSourceCode
|
||||
|
||||
|
||||
{-| Apply the changes on an extra file.
|
||||
-}
|
||||
fixExtraFile : List Fix -> String -> Result FixProblem.FixProblem String
|
||||
fixExtraFile fixes originalSourceCode =
|
||||
tryToApplyFix fixes originalSourceCode
|
||||
|
||||
|
||||
tryToApplyFix : List Fix -> String -> Result FixProblem.FixProblem String
|
||||
tryToApplyFix fixes sourceCode =
|
||||
if containRangeCollisions fixes then
|
||||
|
@ -1220,11 +1220,28 @@ expressionEnterVisitor node context =
|
||||
(\declaration scopes ->
|
||||
case Node.value declaration of
|
||||
Expression.LetFunction function ->
|
||||
registerVariable
|
||||
{ variableType = LetVariable
|
||||
, node = (Node.value function.declaration).name
|
||||
}
|
||||
scopes
|
||||
let
|
||||
{ name, expression, arguments } =
|
||||
Node.value function.declaration
|
||||
|
||||
withLetVariable : NonEmpty Scope
|
||||
withLetVariable =
|
||||
registerVariable
|
||||
{ variableType = LetVariable
|
||||
, node = name
|
||||
}
|
||||
scopes
|
||||
in
|
||||
if List.isEmpty arguments then
|
||||
withLetVariable
|
||||
|
||||
else
|
||||
let
|
||||
names : Dict String VariableInfo
|
||||
names =
|
||||
collectNamesFromPattern PatternVariable arguments Dict.empty
|
||||
in
|
||||
NonEmpty.mapHead (\scope -> { scope | cases = ( expression, names ) :: scope.cases }) withLetVariable
|
||||
|
||||
Expression.LetDestructuring _ _ ->
|
||||
scopes
|
||||
|
@ -3,6 +3,7 @@ module Review.Project exposing
|
||||
, ProjectModule, addModule, addParsedModule, removeModule, modules, modulesThatFailedToParse, precomputeModuleGraph
|
||||
, addElmJson, elmJson
|
||||
, addReadme, readme
|
||||
, addExtraFiles, extraFiles
|
||||
, addDependency, removeDependency, removeDependencies, directDependencies, dependencies
|
||||
)
|
||||
|
||||
@ -35,6 +36,11 @@ does not look at project information (like the `elm.json`, dependencies, ...).
|
||||
@docs addReadme, readme
|
||||
|
||||
|
||||
# REPLACEME
|
||||
|
||||
@docs addExtraFiles, extraFiles
|
||||
|
||||
|
||||
# Project dependencies
|
||||
|
||||
@docs addDependency, removeDependency, removeDependencies, directDependencies, dependencies
|
||||
@ -79,6 +85,7 @@ new =
|
||||
, modulesThatFailedToParse = []
|
||||
, elmJson = Nothing
|
||||
, readme = Nothing
|
||||
, extraFiles = []
|
||||
, dependencies = Dict.empty
|
||||
, moduleGraph = Nothing
|
||||
, sourceDirectories = [ "src/" ]
|
||||
@ -300,6 +307,18 @@ readme (Internal.Project project) =
|
||||
Maybe.map Tuple.first project.readme
|
||||
|
||||
|
||||
{-| REPLACEME
|
||||
-}
|
||||
addExtraFiles : List { path : String, content : String } -> Project -> Project
|
||||
addExtraFiles files (Internal.Project project) =
|
||||
Internal.Project { project | extraFiles = List.map (\file -> ( file, ContentHash.hash file.content )) files }
|
||||
|
||||
|
||||
extraFiles : Project -> List { path : String, content : String }
|
||||
extraFiles (Internal.Project project) =
|
||||
List.map Tuple.first project.extraFiles
|
||||
|
||||
|
||||
{-| Add a dependency to the project. These will be available for rules to make
|
||||
better assumptions on what is happening in the code.
|
||||
|
||||
|
@ -25,6 +25,7 @@ type Project
|
||||
, modulesThatFailedToParse : List { path : String, source : String }
|
||||
, elmJson : Maybe ( { path : String, raw : String, project : Elm.Project.Project }, ContentHash )
|
||||
, readme : Maybe ( { path : String, content : String }, ContentHash )
|
||||
, extraFiles : List ( { path : String, content : String }, ContentHash )
|
||||
, dependencies : Dict String Dependency
|
||||
, moduleGraph : Maybe (Graph FilePath ())
|
||||
, sourceDirectories : List String
|
||||
|
@ -1,6 +1,7 @@
|
||||
module Review.Project.Valid exposing
|
||||
( ValidProject
|
||||
, addElmJson
|
||||
, addExtraFile
|
||||
, addParsedModule
|
||||
, addReadme
|
||||
, dependencies
|
||||
@ -9,6 +10,8 @@ module Review.Project.Valid exposing
|
||||
, doesModuleExist
|
||||
, elmJson
|
||||
, elmJsonHash
|
||||
, extraFiles
|
||||
, extraFilesHash
|
||||
, getModuleByPath
|
||||
, moduleGraph
|
||||
, moduleZipper
|
||||
@ -50,6 +53,7 @@ type alias ValidProjectData =
|
||||
, modulesByModuleName : Dict ModuleName OpaqueProjectModule
|
||||
, elmJson : Maybe ( { path : String, raw : String, project : Elm.Project.Project }, ContentHash )
|
||||
, readme : Maybe ( { path : String, content : String }, ContentHash )
|
||||
, extraFiles : List ( { path : String, content : String }, ContentHash )
|
||||
, dependencies : Dict String Dependency
|
||||
, directDependencies : Dict String Dependency
|
||||
, dependencyModules : Set ModuleName
|
||||
@ -67,6 +71,7 @@ toRegularProject (ValidProject validProject) =
|
||||
, modulesThatFailedToParse = []
|
||||
, elmJson = validProject.elmJson
|
||||
, readme = validProject.readme
|
||||
, extraFiles = validProject.extraFiles
|
||||
, dependencies = validProject.dependencies
|
||||
, moduleGraph = Just validProject.moduleGraph
|
||||
, sourceDirectories = validProject.sourceDirectories
|
||||
@ -133,6 +138,7 @@ fromProjectAndGraph moduleGraph_ acyclicGraph (Project project) =
|
||||
, modulesByModuleName = computeModulesByModuleName project.modules
|
||||
, elmJson = project.elmJson
|
||||
, readme = project.readme
|
||||
, extraFiles = project.extraFiles
|
||||
, dependencies = project.dependencies
|
||||
, directDependencies = directDependencies_
|
||||
, dependencyModules = computeDependencyModules directDependencies_
|
||||
@ -308,6 +314,16 @@ readmeHash (ValidProject project) =
|
||||
Maybe.map Tuple.second project.readme
|
||||
|
||||
|
||||
extraFiles : ValidProject -> List { path : String, content : String }
|
||||
extraFiles (ValidProject project) =
|
||||
List.map Tuple.first project.extraFiles
|
||||
|
||||
|
||||
extraFilesHash : ValidProject -> List ContentHash
|
||||
extraFilesHash (ValidProject project) =
|
||||
List.map Tuple.second project.extraFiles
|
||||
|
||||
|
||||
dependencies : ValidProject -> Dict String Dependency
|
||||
dependencies (ValidProject project) =
|
||||
project.dependencies
|
||||
@ -496,6 +512,30 @@ addReadme readme_ (ValidProject project) =
|
||||
ValidProject { project | readme = Just ( readme_, ContentHash.hash readme_.content ) }
|
||||
|
||||
|
||||
{-| Add the content of the `README.md` file to the project, making it
|
||||
available for rules to access using
|
||||
[`Review.Rule.withReadmeModuleVisitor`](./Review-Rule#withReadmeModuleVisitor) and
|
||||
[`Review.Rule.withReadmeProjectVisitor`](./Review-Rule#withReadmeProjectVisitor).
|
||||
-}
|
||||
addExtraFile : { path : String, content : String } -> ValidProject -> ValidProject
|
||||
addExtraFile newFile (ValidProject project) =
|
||||
-- TODO Make faster
|
||||
-- TODO Add file if it doesn't already exist
|
||||
ValidProject
|
||||
{ project
|
||||
| extraFiles =
|
||||
List.map
|
||||
(\(( file, _ ) as untouched) ->
|
||||
if file.path == newFile.path then
|
||||
( newFile, ContentHash.hash newFile.content )
|
||||
|
||||
else
|
||||
untouched
|
||||
)
|
||||
project.extraFiles
|
||||
}
|
||||
|
||||
|
||||
addElmJson : { path : String, raw : String, project : Elm.Project.Project } -> ValidProject -> ValidProject
|
||||
addElmJson elmJson_ (ValidProject project) =
|
||||
ValidProject { project | elmJson = Just ( elmJson_, ContentHash.hash elmJson_.raw ) }
|
||||
|
@ -1,4 +1,4 @@
|
||||
module Review.RequestedData exposing (RequestedData(..), combine, combineJust, none)
|
||||
module Review.RequestedData exposing (RequestedData(..), combine, combineJust, none, withFiles)
|
||||
|
||||
|
||||
type RequestedData
|
||||
@ -6,6 +6,7 @@ type RequestedData
|
||||
{ moduleNameLookupTable : Bool
|
||||
, sourceCodeExtractor : Bool
|
||||
, ignoredFiles : Bool
|
||||
, files : List { files : List { pattern : String, included : Bool }, excludedFolders : List String }
|
||||
}
|
||||
|
||||
|
||||
@ -15,6 +16,7 @@ none =
|
||||
{ moduleNameLookupTable = False
|
||||
, sourceCodeExtractor = False
|
||||
, ignoredFiles = False
|
||||
, files = []
|
||||
}
|
||||
|
||||
|
||||
@ -33,10 +35,20 @@ combine maybeA maybeB =
|
||||
a
|
||||
|
||||
|
||||
withFiles : List { files : List { pattern : String, included : Bool }, excludedFolders : List String } -> RequestedData -> RequestedData
|
||||
withFiles files ((RequestedData requested) as untouched) =
|
||||
if List.isEmpty files then
|
||||
untouched
|
||||
|
||||
else
|
||||
RequestedData { requested | files = files }
|
||||
|
||||
|
||||
combineJust : RequestedData -> RequestedData -> RequestedData
|
||||
combineJust (RequestedData a) (RequestedData b) =
|
||||
RequestedData
|
||||
{ moduleNameLookupTable = a.moduleNameLookupTable || b.moduleNameLookupTable
|
||||
, sourceCodeExtractor = a.sourceCodeExtractor || b.sourceCodeExtractor
|
||||
, ignoredFiles = a.ignoredFiles || b.ignoredFiles
|
||||
, files = a.files ++ b.files
|
||||
}
|
||||
|
@ -14,16 +14,20 @@ module Review.Rule exposing
|
||||
, providesFixesForModuleRule
|
||||
, withFinalModuleEvaluation
|
||||
, withElmJsonModuleVisitor, withReadmeModuleVisitor, withDirectDependenciesModuleVisitor, withDependenciesModuleVisitor
|
||||
, ProjectRuleSchema, newProjectRuleSchema, fromProjectRuleSchema, withModuleVisitor, withModuleContext, withModuleContextUsingContextCreator, withElmJsonProjectVisitor, withReadmeProjectVisitor, withDirectDependenciesProjectVisitor, withDependenciesProjectVisitor, withFinalProjectEvaluation, withContextFromImportedModules
|
||||
, withExtraFilesModuleVisitor
|
||||
, ProjectRuleSchema, newProjectRuleSchema, fromProjectRuleSchema, withModuleVisitor, withModuleContext, withModuleContextUsingContextCreator, withElmJsonProjectVisitor, withReadmeProjectVisitor, withDirectDependenciesProjectVisitor, withDependenciesProjectVisitor, withFinalProjectEvaluation, withExtraFilesProjectVisitor, withContextFromImportedModules
|
||||
, providesFixesForProjectRule
|
||||
, ContextCreator, initContextCreator, withModuleName, withModuleNameNode, withIsInSourceDirectories, withFilePath, withIsFileIgnored, withModuleNameLookupTable, withModuleKey, withSourceCodeExtractor, withFullAst, withModuleDocumentation
|
||||
, Metadata, withMetadata, moduleNameFromMetadata, moduleNameNodeFromMetadata, isInSourceDirectories
|
||||
, Error, error, errorWithFix, ModuleKey, errorForModule, errorForModuleWithFix, ElmJsonKey, errorForElmJson, errorForElmJsonWithFix, ReadmeKey, errorForReadme, errorForReadmeWithFix
|
||||
, Error, error, errorWithFix, ModuleKey, errorForModule, errorForModuleWithFix
|
||||
, ElmJsonKey, errorForElmJson, errorForElmJsonWithFix
|
||||
, ReadmeKey, errorForReadme, errorForReadmeWithFix
|
||||
, ExtraFileKey, errorForExtraFile, errorForExtraFileWithFix
|
||||
, globalError, configurationError
|
||||
, ReviewError, errorRuleName, errorMessage, errorDetails, errorRange, errorFilePath, errorTarget, errorFixes, errorFixFailure
|
||||
, ignoreErrorsForDirectories, ignoreErrorsForFiles, filterErrorsForFiles
|
||||
, withDataExtractor, preventExtract
|
||||
, reviewV3, reviewV2, review, ProjectData, ruleName, ruleProvidesFixes, ruleKnowsAboutIgnoredFiles, withRuleId, getConfigurationError
|
||||
, reviewV3, reviewV2, review, ProjectData, ruleName, ruleProvidesFixes, ruleKnowsAboutIgnoredFiles, ruleRequestedFiles, withRuleId, getConfigurationError
|
||||
, Required, Forbidden
|
||||
)
|
||||
|
||||
@ -211,6 +215,7 @@ Evaluating/visiting a node means two things:
|
||||
## Builder functions to analyze the project's data
|
||||
|
||||
@docs withElmJsonModuleVisitor, withReadmeModuleVisitor, withDirectDependenciesModuleVisitor, withDependenciesModuleVisitor
|
||||
@docs withExtraFilesModuleVisitor
|
||||
|
||||
|
||||
## Creating a project rule
|
||||
@ -226,7 +231,7 @@ Project rules can also report errors in the `elm.json` or the `README.md` files.
|
||||
If you are new to writing rules, I would recommend learning [how to build a module rule](#creating-a-module-rule)
|
||||
first, as they are in practice a simpler version of project rules.
|
||||
|
||||
@docs ProjectRuleSchema, newProjectRuleSchema, fromProjectRuleSchema, withModuleVisitor, withModuleContext, withModuleContextUsingContextCreator, withElmJsonProjectVisitor, withReadmeProjectVisitor, withDirectDependenciesProjectVisitor, withDependenciesProjectVisitor, withFinalProjectEvaluation, withContextFromImportedModules
|
||||
@docs ProjectRuleSchema, newProjectRuleSchema, fromProjectRuleSchema, withModuleVisitor, withModuleContext, withModuleContextUsingContextCreator, withElmJsonProjectVisitor, withReadmeProjectVisitor, withDirectDependenciesProjectVisitor, withDependenciesProjectVisitor, withFinalProjectEvaluation, withExtraFilesProjectVisitor, withContextFromImportedModules
|
||||
@docs providesFixesForProjectRule
|
||||
|
||||
|
||||
@ -242,7 +247,10 @@ first, as they are in practice a simpler version of project rules.
|
||||
|
||||
## Errors
|
||||
|
||||
@docs Error, error, errorWithFix, ModuleKey, errorForModule, errorForModuleWithFix, ElmJsonKey, errorForElmJson, errorForElmJsonWithFix, ReadmeKey, errorForReadme, errorForReadmeWithFix
|
||||
@docs Error, error, errorWithFix, ModuleKey, errorForModule, errorForModuleWithFix
|
||||
@docs ElmJsonKey, errorForElmJson, errorForElmJsonWithFix
|
||||
@docs ReadmeKey, errorForReadme, errorForReadmeWithFix
|
||||
@docs ExtraFileKey, errorForExtraFile, errorForExtraFileWithFix
|
||||
@docs globalError, configurationError
|
||||
@docs ReviewError, errorRuleName, errorMessage, errorDetails, errorRange, errorFilePath, errorTarget, errorFixes, errorFixFailure
|
||||
|
||||
@ -284,7 +292,7 @@ find the tools to extract data below.
|
||||
|
||||
# Running rules
|
||||
|
||||
@docs reviewV3, reviewV2, review, ProjectData, ruleName, ruleProvidesFixes, ruleKnowsAboutIgnoredFiles, withRuleId, getConfigurationError
|
||||
@docs reviewV3, reviewV2, review, ProjectData, ruleName, ruleProvidesFixes, ruleKnowsAboutIgnoredFiles, ruleRequestedFiles, withRuleId, getConfigurationError
|
||||
|
||||
|
||||
# Internals
|
||||
@ -306,17 +314,20 @@ import Elm.Syntax.ModuleName exposing (ModuleName)
|
||||
import Elm.Syntax.Node as Node exposing (Node(..))
|
||||
import Elm.Syntax.Pattern exposing (Pattern)
|
||||
import Elm.Syntax.Range as Range exposing (Range)
|
||||
import Glob exposing (Glob)
|
||||
import Json.Encode as Encode
|
||||
import Review.Cache.ContentHash exposing (ContentHash)
|
||||
import Review.Cache.ContextHash as ContextHash exposing (ComparableContextHash, ContextHash)
|
||||
import Review.Cache.EndAnalysis as EndAnalysisCache
|
||||
import Review.Cache.ExtraFile as ExtraFile
|
||||
import Review.Cache.Module as ModuleCache
|
||||
import Review.Cache.ProjectFile as ProjectFileCache
|
||||
import Review.ElmProjectEncoder
|
||||
import Review.Error exposing (InternalError)
|
||||
import Review.Exceptions as Exceptions exposing (Exceptions)
|
||||
import Review.FilePath exposing (FilePath)
|
||||
import Review.Fix as Fix exposing (Fix)
|
||||
import Review.FilePattern as FilePattern exposing (FilePattern)
|
||||
import Review.Fix as Fix exposing (Fix, FixResult(..))
|
||||
import Review.Fix.FixProblem as FixProblem
|
||||
import Review.Fix.FixedErrors as FixedErrors exposing (FixedErrors)
|
||||
import Review.Fix.Internal as InternalFix
|
||||
@ -335,6 +346,7 @@ import Review.Project.Valid as ValidProject exposing (ValidProject)
|
||||
import Review.RequestedData as RequestedData exposing (RequestedData(..))
|
||||
import Vendor.Graph as Graph exposing (Graph)
|
||||
import Vendor.IntDict as IntDict
|
||||
import Vendor.ListExtra as ListExtra
|
||||
import Vendor.Zipper as Zipper exposing (Zipper)
|
||||
|
||||
|
||||
@ -394,12 +406,24 @@ type alias ModuleRuleSchemaData moduleContext =
|
||||
|
||||
-- Project visitors
|
||||
, elmJsonVisitor : Maybe (Maybe Elm.Project.Project -> moduleContext -> moduleContext)
|
||||
, extraFileRequest : ExtraFileRequest
|
||||
, readmeVisitor : Maybe (Maybe String -> moduleContext -> moduleContext)
|
||||
, extraFilesVisitor : Maybe (List { path : String, content : String } -> moduleContext -> moduleContext)
|
||||
, dependenciesVisitor : Maybe (Dict String Review.Project.Dependency.Dependency -> moduleContext -> moduleContext)
|
||||
, directDependenciesVisitor : Maybe (Dict String Review.Project.Dependency.Dependency -> moduleContext -> moduleContext)
|
||||
}
|
||||
|
||||
|
||||
type alias ExtraFileRequest =
|
||||
Result (List String) (List { files : List { pattern : String, included : Bool }, excludedFolders : List String })
|
||||
|
||||
|
||||
type alias StringableGlob =
|
||||
{ glob : Glob
|
||||
, string : String
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- REVIEWING
|
||||
|
||||
@ -868,6 +892,17 @@ ruleKnowsAboutIgnoredFiles (Rule rule) =
|
||||
requestedData.ignoredFiles
|
||||
|
||||
|
||||
{-| REPLACEME
|
||||
-}
|
||||
ruleRequestedFiles : Rule -> List { files : List { pattern : String, included : Bool }, excludedFolders : List String }
|
||||
ruleRequestedFiles (Rule rule) =
|
||||
let
|
||||
(RequestedData requestedData) =
|
||||
rule.requestedData
|
||||
in
|
||||
requestedData.files
|
||||
|
||||
|
||||
{-| Assign an id to a rule. This id should be unique.
|
||||
|
||||
config =
|
||||
@ -1020,6 +1055,8 @@ newModuleRuleSchema name initialModuleContext =
|
||||
, finalEvaluationFn = Nothing
|
||||
, elmJsonVisitor = Nothing
|
||||
, readmeVisitor = Nothing
|
||||
, extraFilesVisitor = Nothing
|
||||
, extraFileRequest = Ok []
|
||||
, dependenciesVisitor = Nothing
|
||||
, directDependenciesVisitor = Nothing
|
||||
, providesFixes = False
|
||||
@ -1087,6 +1124,8 @@ newModuleRuleSchemaUsingContextCreator name moduleContextCreator =
|
||||
, finalEvaluationFn = Nothing
|
||||
, elmJsonVisitor = Nothing
|
||||
, readmeVisitor = Nothing
|
||||
, extraFilesVisitor = Nothing
|
||||
, extraFileRequest = Ok []
|
||||
, dependenciesVisitor = Nothing
|
||||
, directDependenciesVisitor = Nothing
|
||||
, providesFixes = False
|
||||
@ -1105,6 +1144,8 @@ fromModuleRuleSchema ((ModuleRuleSchema schema) as moduleVisitor) =
|
||||
, initialProjectContext = initialModuleContext
|
||||
, elmJsonVisitor = compactProjectDataVisitors (Maybe.map .project) schema.elmJsonVisitor
|
||||
, readmeVisitor = compactProjectDataVisitors (Maybe.map .content) schema.readmeVisitor
|
||||
, extraFilesVisitor = compactExtraFilesVisitor schema.extraFilesVisitor
|
||||
, extraFileRequest = schema.extraFileRequest
|
||||
, directDependenciesVisitor = compactProjectDataVisitors identity schema.directDependenciesVisitor
|
||||
, dependenciesVisitor = compactProjectDataVisitors identity schema.dependenciesVisitor
|
||||
, moduleVisitors = [ removeExtensibleRecordTypeVariable (always moduleVisitor) ]
|
||||
@ -1123,6 +1164,8 @@ fromModuleRuleSchema ((ModuleRuleSchema schema) as moduleVisitor) =
|
||||
, initialProjectContext = ()
|
||||
, elmJsonVisitor = Nothing
|
||||
, readmeVisitor = Nothing
|
||||
, extraFilesVisitor = Nothing
|
||||
, extraFileRequest = Ok []
|
||||
, directDependenciesVisitor = Nothing
|
||||
, dependenciesVisitor = Nothing
|
||||
, moduleVisitors = [ removeExtensibleRecordTypeVariable (always moduleVisitor) ]
|
||||
@ -1146,6 +1189,21 @@ compactProjectDataVisitors getData maybeVisitor =
|
||||
Just (\rawData moduleContext -> ( [], visitor (getData rawData) moduleContext ))
|
||||
|
||||
|
||||
compactExtraFilesVisitor : Maybe (List { path : String, content : String } -> moduleContext -> moduleContext) -> Maybe (List { fileKey : ExtraFileKey, path : String, content : String } -> moduleContext -> ( List nothing, moduleContext ))
|
||||
compactExtraFilesVisitor maybeExtraFilesVisitor =
|
||||
case maybeExtraFilesVisitor of
|
||||
Just extraFilesVisitor ->
|
||||
Just (\files moduleContext -> ( [], extraFilesVisitor (List.map (\{ path, content } -> { path = path, content = content }) files) moduleContext ))
|
||||
|
||||
Nothing ->
|
||||
Nothing
|
||||
|
||||
|
||||
globMatch : List Glob -> { a | path : String } -> Bool
|
||||
globMatch globs file =
|
||||
List.any (\glob -> Glob.match glob file.path) globs
|
||||
|
||||
|
||||
|
||||
-- PROJECT RULES
|
||||
|
||||
@ -1165,6 +1223,8 @@ type alias ProjectRuleSchemaData projectContext moduleContext =
|
||||
, initialProjectContext : projectContext
|
||||
, elmJsonVisitor : Maybe (Maybe { elmJsonKey : ElmJsonKey, project : Elm.Project.Project } -> projectContext -> ( List (Error {}), projectContext ))
|
||||
, readmeVisitor : Maybe (Maybe { readmeKey : ReadmeKey, content : String } -> projectContext -> ( List (Error {}), projectContext ))
|
||||
, extraFilesVisitor : Maybe (List { fileKey : ExtraFileKey, path : String, content : String } -> projectContext -> ( List (Error {}), projectContext ))
|
||||
, extraFileRequest : ExtraFileRequest
|
||||
, directDependenciesVisitor : Maybe (Dict String Review.Project.Dependency.Dependency -> projectContext -> ( List (Error {}), projectContext ))
|
||||
, dependenciesVisitor : Maybe (Dict String Review.Project.Dependency.Dependency -> projectContext -> ( List (Error {}), projectContext ))
|
||||
, moduleVisitors : List (ModuleRuleSchema {} moduleContext -> ModuleRuleSchema { hasAtLeastOneVisitor : () } moduleContext)
|
||||
@ -1206,6 +1266,7 @@ Project rules traverse the project in the following order:
|
||||
- Read and/or report errors in project files
|
||||
- The `elm.json` file, visited by [`withElmJsonProjectVisitor`](#withElmJsonProjectVisitor)
|
||||
- The `README.md` file, visited by [`withReadmeProjectVisitor`](#withReadmeProjectVisitor)
|
||||
- TODO Extra files
|
||||
- The definition for dependencies, visited by [`withDependenciesProjectVisitor`](#withDependenciesProjectVisitor)
|
||||
- The Elm modules one by one, visited by [`withModuleVisitor`](#withModuleVisitor),
|
||||
following the same traversal order as for module rules but without reading the project files (`elm.json`, ...).
|
||||
@ -1225,6 +1286,8 @@ newProjectRuleSchema name initialProjectContext =
|
||||
, initialProjectContext = initialProjectContext
|
||||
, elmJsonVisitor = Nothing
|
||||
, readmeVisitor = Nothing
|
||||
, extraFilesVisitor = Nothing
|
||||
, extraFileRequest = Ok []
|
||||
, directDependenciesVisitor = Nothing
|
||||
, dependenciesVisitor = Nothing
|
||||
, moduleVisitors = []
|
||||
@ -1241,25 +1304,40 @@ newProjectRuleSchema name initialProjectContext =
|
||||
-}
|
||||
fromProjectRuleSchema : ProjectRuleSchema { schemaState | withModuleContext : Forbidden, hasAtLeastOneVisitor : () } projectContext moduleContext -> Rule
|
||||
fromProjectRuleSchema (ProjectRuleSchema schema) =
|
||||
Rule
|
||||
{ name = schema.name
|
||||
, id = 0
|
||||
, exceptions = Exceptions.init
|
||||
, requestedData =
|
||||
RequestedData.combine
|
||||
(Maybe.map requestedDataFromContextCreator schema.moduleContextCreator)
|
||||
(Maybe.map (.fromModuleToProject >> requestedDataFromContextCreator) schema.folder)
|
||||
, providesFixes = schema.providesFixes
|
||||
, ruleProjectVisitor =
|
||||
Ok
|
||||
(\project ruleData ->
|
||||
createRuleProjectVisitor
|
||||
schema
|
||||
project
|
||||
ruleData
|
||||
(initialCacheMarker schema.name ruleData.ruleId emptyCache)
|
||||
)
|
||||
}
|
||||
case schema.extraFileRequest of
|
||||
Ok extraFileGlobs ->
|
||||
Rule
|
||||
{ name = schema.name
|
||||
, id = 0
|
||||
, exceptions = Exceptions.init
|
||||
, requestedData =
|
||||
RequestedData.combine
|
||||
(Maybe.map requestedDataFromContextCreator schema.moduleContextCreator)
|
||||
(Maybe.map (.fromModuleToProject >> requestedDataFromContextCreator) schema.folder)
|
||||
-- TODO Keep the original globs as strings and pass them here
|
||||
|> RequestedData.withFiles extraFileGlobs
|
||||
, providesFixes = schema.providesFixes
|
||||
, ruleProjectVisitor =
|
||||
Ok
|
||||
(\project ruleData ->
|
||||
createRuleProjectVisitor
|
||||
schema
|
||||
project
|
||||
ruleData
|
||||
(initialCacheMarker schema.name ruleData.ruleId emptyCache)
|
||||
)
|
||||
}
|
||||
|
||||
Err faultyGlobs ->
|
||||
configurationError schema.name
|
||||
{ message = "Invalid globs provided when requesting extra files"
|
||||
, details =
|
||||
[ "This rule requested additional files, but did so by specifying globs that I could not make sense of:"
|
||||
, faultyGlobs
|
||||
|> List.indexedMap (\index glob -> " " ++ String.fromInt (index + 1) ++ ". " ++ glob)
|
||||
|> String.join "\n"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
initialCacheMarker : String -> Int -> ProjectRuleCache projectContext -> ProjectRuleCache projectContext
|
||||
@ -1276,6 +1354,7 @@ emptyCache : ProjectRuleCache projectContext
|
||||
emptyCache =
|
||||
{ elmJson = Nothing
|
||||
, readme = Nothing
|
||||
, extraFiles = Nothing
|
||||
, dependencies = Nothing
|
||||
, moduleContexts = Dict.empty
|
||||
, finalEvaluationErrors = Nothing
|
||||
@ -1361,6 +1440,8 @@ mergeModuleVisitorsHelp ruleName_ initialProjectContext moduleContextCreator vis
|
||||
, finalEvaluationFn = Nothing
|
||||
, elmJsonVisitor = Nothing
|
||||
, readmeVisitor = Nothing
|
||||
, extraFilesVisitor = Nothing
|
||||
, extraFileRequest = Ok []
|
||||
, dependenciesVisitor = Nothing
|
||||
, directDependenciesVisitor = Nothing
|
||||
, providesFixes = False
|
||||
@ -1824,6 +1905,106 @@ withReadmeProjectVisitor visitor (ProjectRuleSchema schema) =
|
||||
ProjectRuleSchema { schema | readmeVisitor = Just (combineVisitors (removeErrorPhantomTypeFromVisitor visitor) schema.readmeVisitor) }
|
||||
|
||||
|
||||
{-| REPLACEME
|
||||
-}
|
||||
withExtraFilesProjectVisitor :
|
||||
List FilePattern
|
||||
-> (List { fileKey : ExtraFileKey, path : String, content : String } -> projectContext -> ( List (Error { useErrorForModule : () }), projectContext ))
|
||||
-> ProjectRuleSchema schemaState projectContext moduleContext
|
||||
-> ProjectRuleSchema { schemaState | hasAtLeastOneVisitor : () } projectContext moduleContext
|
||||
withExtraFilesProjectVisitor filePatterns baseVisitor (ProjectRuleSchema schema) =
|
||||
case FilePattern.compact filePatterns of
|
||||
Ok filePatternSummary ->
|
||||
let
|
||||
visitor : List { fileKey : ExtraFileKey, path : String, content : String } -> projectContext -> ( List (Error {}), projectContext )
|
||||
visitor files context =
|
||||
baseVisitor (List.filter (\file -> FilePattern.match filePatternSummary file.path) files) context
|
||||
|> Tuple.mapFirst removeErrorPhantomTypes
|
||||
in
|
||||
ProjectRuleSchema
|
||||
{ schema
|
||||
| extraFilesVisitor = Just (combineVisitors visitor schema.extraFilesVisitor)
|
||||
, extraFileRequest =
|
||||
case schema.extraFileRequest of
|
||||
Ok previous ->
|
||||
Ok (FilePattern.toStrings filePatternSummary :: previous)
|
||||
|
||||
Err _ ->
|
||||
schema.extraFileRequest
|
||||
}
|
||||
|
||||
Err globErrors ->
|
||||
ProjectRuleSchema
|
||||
{ schema
|
||||
| extraFileRequest =
|
||||
case schema.extraFileRequest of
|
||||
Err previous ->
|
||||
Err (previous ++ globErrors)
|
||||
|
||||
Ok _ ->
|
||||
Err globErrors
|
||||
}
|
||||
|
||||
|
||||
parseGlobs : List String -> Result (List String) (List StringableGlob)
|
||||
parseGlobs requestedFiles =
|
||||
requestedFiles
|
||||
|> List.map
|
||||
(\str ->
|
||||
Glob.fromString str
|
||||
|> Result.map (\glob -> { glob = glob, string = str })
|
||||
|> Result.mapError (always str)
|
||||
)
|
||||
|> toResults
|
||||
|
||||
|
||||
combineGlobs : Result appendable appendable -> Result appendable appendable -> Result appendable appendable
|
||||
combineGlobs previous new =
|
||||
case ( previous, new ) of
|
||||
( Ok previousGlobs, Ok newGlobs ) ->
|
||||
Ok (previousGlobs ++ newGlobs)
|
||||
|
||||
( Err previousErrors, Err newErrors ) ->
|
||||
Err (previousErrors ++ newErrors)
|
||||
|
||||
( Err errors, _ ) ->
|
||||
previous
|
||||
|
||||
( _, Err _ ) ->
|
||||
new
|
||||
|
||||
|
||||
toResults : List (Result String a) -> Result (List String) (List a)
|
||||
toResults results =
|
||||
toResultsHelp results []
|
||||
|
||||
|
||||
toResultsHelp : List (Result String a) -> List a -> Result (List String) (List a)
|
||||
toResultsHelp results acc =
|
||||
case results of
|
||||
[] ->
|
||||
Ok acc
|
||||
|
||||
(Ok result) :: rest ->
|
||||
toResultsHelp rest (result :: acc)
|
||||
|
||||
(Err str) :: rest ->
|
||||
collectErrs rest [ str ]
|
||||
|
||||
|
||||
collectErrs : List (Result error value) -> List error -> Result (List error) never
|
||||
collectErrs results acc =
|
||||
case results of
|
||||
[] ->
|
||||
Err acc
|
||||
|
||||
(Err str) :: rest ->
|
||||
collectErrs rest (str :: acc)
|
||||
|
||||
(Ok _) :: rest ->
|
||||
collectErrs rest []
|
||||
|
||||
|
||||
{-| Add a visitor to the [`ProjectRuleSchema`](#ProjectRuleSchema) which will examine the project's
|
||||
[dependencies](./Review-Project-Dependency).
|
||||
|
||||
@ -2278,6 +2459,46 @@ withElmJsonModuleVisitor visitor (ModuleRuleSchema schema) =
|
||||
ModuleRuleSchema { schema | elmJsonVisitor = Just (combineContextOnlyVisitor visitor schema.elmJsonVisitor) }
|
||||
|
||||
|
||||
{-| REPLACEME
|
||||
-}
|
||||
withExtraFilesModuleVisitor :
|
||||
List FilePattern
|
||||
-> (List { path : String, content : String } -> moduleContext -> moduleContext)
|
||||
-> ModuleRuleSchema { schemaState | canCollectProjectData : () } moduleContext
|
||||
-> ModuleRuleSchema { schemaState | canCollectProjectData : () } moduleContext
|
||||
withExtraFilesModuleVisitor filePatterns baseVisitor (ModuleRuleSchema schema) =
|
||||
case FilePattern.compact filePatterns of
|
||||
Ok filePatternSummary ->
|
||||
let
|
||||
visitor : List { path : String, content : String } -> moduleContext -> moduleContext
|
||||
visitor files context =
|
||||
baseVisitor (List.filter (\file -> FilePattern.match filePatternSummary file.path) files) context
|
||||
in
|
||||
ModuleRuleSchema
|
||||
{ schema
|
||||
| extraFilesVisitor = Just (combineContextOnlyVisitor visitor schema.extraFilesVisitor)
|
||||
, extraFileRequest =
|
||||
case schema.extraFileRequest of
|
||||
Ok previous ->
|
||||
Ok (FilePattern.toStrings filePatternSummary :: previous)
|
||||
|
||||
Err _ ->
|
||||
schema.extraFileRequest
|
||||
}
|
||||
|
||||
Err globErrors ->
|
||||
ModuleRuleSchema
|
||||
{ schema
|
||||
| extraFileRequest =
|
||||
case schema.extraFileRequest of
|
||||
Err previous ->
|
||||
Err (previous ++ globErrors)
|
||||
|
||||
Ok _ ->
|
||||
Err globErrors
|
||||
}
|
||||
|
||||
|
||||
{-| Add a visitor to the [`ModuleRuleSchema`](#ModuleRuleSchema) which will visit
|
||||
the project's `README.md` file.
|
||||
-}
|
||||
@ -3686,6 +3907,39 @@ errorForReadmeWithFix readmeKey info range fixes =
|
||||
|> withFixes fixes
|
||||
|
||||
|
||||
{-| REPLACEME
|
||||
-}
|
||||
type ExtraFileKey
|
||||
= ExtraFileKey
|
||||
{ path : String
|
||||
, content : String
|
||||
}
|
||||
|
||||
|
||||
{-| REPLACEME
|
||||
-}
|
||||
errorForExtraFile : ExtraFileKey -> { message : String, details : List String } -> Range -> Error scope
|
||||
errorForExtraFile (ExtraFileKey { path }) { message, details } range =
|
||||
Error
|
||||
{ message = message
|
||||
, ruleName = ""
|
||||
, filePath = path
|
||||
, details = details
|
||||
, range = range
|
||||
, fixes = Review.Error.NoFixes
|
||||
, target = Review.Error.ExtraFile
|
||||
, preventsExtract = False
|
||||
}
|
||||
|
||||
|
||||
{-| REPLACEME
|
||||
-}
|
||||
errorForExtraFileWithFix : ExtraFileKey -> { message : String, details : List String } -> Range -> List Fix -> Error scope
|
||||
errorForExtraFileWithFix readmeKey info range fixes =
|
||||
errorForExtraFile readmeKey info range
|
||||
|> withFixes fixes
|
||||
|
||||
|
||||
elmReviewGlobalError : { message : String, details : List String } -> Error scope
|
||||
elmReviewGlobalError { message, details } =
|
||||
Error
|
||||
@ -3802,6 +4056,9 @@ withFixes fixes error_ =
|
||||
Review.Error.Readme ->
|
||||
{ err | fixes = Review.Error.Available fixes }
|
||||
|
||||
Review.Error.ExtraFile ->
|
||||
{ err | fixes = Review.Error.Available fixes }
|
||||
|
||||
Review.Error.ElmJson ->
|
||||
err
|
||||
|
||||
@ -4166,6 +4423,10 @@ type alias ProjectFileCache projectContext =
|
||||
ProjectFileCache.Entry (Error {}) projectContext
|
||||
|
||||
|
||||
type alias ExtraFilesCache projectContext =
|
||||
ExtraFile.Entry (Error {}) projectContext
|
||||
|
||||
|
||||
type alias FinalProjectEvaluationCache projectContext =
|
||||
EndAnalysisCache.Entry (List (Error {})) projectContext
|
||||
|
||||
@ -4229,7 +4490,7 @@ computeFinalContextHashes : ProjectRuleSchemaData projectContext moduleContext -
|
||||
computeFinalContextHashes schema cache =
|
||||
let
|
||||
( projectContextHash, _ ) =
|
||||
findInitialInputContext schema.initialProjectContext [ cache.dependencies, cache.readme, cache.elmJson ]
|
||||
findInitialInputContext cache AfterProjectFilesStep schema.initialProjectContext
|
||||
|
||||
traversalAndFolder : TraversalAndFolder projectContext moduleContext
|
||||
traversalAndFolder =
|
||||
@ -4259,7 +4520,7 @@ computeFinalContext : ProjectRuleSchemaData projectContext moduleContext -> Proj
|
||||
computeFinalContext schema cache =
|
||||
let
|
||||
( _, projectContext ) =
|
||||
findInitialInputContext schema.initialProjectContext [ cache.dependencies, cache.readme, cache.elmJson ]
|
||||
findInitialInputContext cache AfterProjectFilesStep schema.initialProjectContext
|
||||
|
||||
traversalAndFolder : TraversalAndFolder projectContext moduleContext
|
||||
traversalAndFolder =
|
||||
@ -4295,6 +4556,7 @@ errorsFromCache cache =
|
||||
[ Dict.foldl (\_ cacheEntry acc -> List.append (ModuleCache.errors cacheEntry) acc) [] cache.moduleContexts
|
||||
, ProjectFileCache.errorsForMaybe cache.elmJson
|
||||
, ProjectFileCache.errorsForMaybe cache.readme
|
||||
, ExtraFile.errorsForMaybe cache.extraFiles
|
||||
, ProjectFileCache.errorsForMaybe cache.dependencies
|
||||
, Maybe.map EndAnalysisCache.output cache.finalEvaluationErrors |> Maybe.withDefault []
|
||||
]
|
||||
@ -4307,6 +4569,7 @@ errorsFromCache cache =
|
||||
type alias ProjectRuleCache projectContext =
|
||||
{ elmJson : Maybe (ProjectFileCache projectContext)
|
||||
, readme : Maybe (ProjectFileCache projectContext)
|
||||
, extraFiles : Maybe (ExtraFilesCache projectContext)
|
||||
, dependencies : Maybe (ProjectFileCache projectContext)
|
||||
, moduleContexts : Dict String (ModuleCacheEntry projectContext)
|
||||
, finalEvaluationErrors : Maybe (FinalProjectEvaluationCache projectContext)
|
||||
@ -4352,6 +4615,16 @@ computeStepsForProject reviewOptions { project, ruleProjectVisitors, fixedErrors
|
||||
reviewOptions
|
||||
(computeReadme reviewOptions project fixedErrors readmeData ruleProjectVisitors [])
|
||||
|
||||
ExtraFiles ->
|
||||
let
|
||||
extraFiles : List { path : String, content : String }
|
||||
extraFiles =
|
||||
ValidProject.extraFiles project
|
||||
in
|
||||
computeStepsForProject
|
||||
reviewOptions
|
||||
(computeExtraFiles reviewOptions project fixedErrors extraFiles ruleProjectVisitors [])
|
||||
|
||||
Dependencies ->
|
||||
let
|
||||
dependenciesData : { all : Dict String Review.Project.Dependency.Dependency, direct : Dict String Review.Project.Dependency.Dependency }
|
||||
@ -4390,16 +4663,26 @@ computeStepsForProject reviewOptions { project, ruleProjectVisitors, fixedErrors
|
||||
type Step
|
||||
= ElmJson
|
||||
| Readme
|
||||
| ExtraFiles
|
||||
| Dependencies
|
||||
| Modules (Zipper GraphModule)
|
||||
| FinalProjectEvaluation
|
||||
| EndAnalysis
|
||||
|
||||
|
||||
type StepToComputeContext
|
||||
= ElmJsonStep
|
||||
| ReadmeStep
|
||||
| ExtraFilesStep
|
||||
| DependenciesStep
|
||||
| AfterProjectFilesStep
|
||||
|
||||
|
||||
type NextStep
|
||||
= ModuleVisitStep (Maybe (Zipper GraphModule))
|
||||
| BackToElmJson
|
||||
| BackToReadme
|
||||
| BackToExtraFiles
|
||||
| NextStepAbort
|
||||
|
||||
|
||||
@ -4466,7 +4749,7 @@ computeReadme reviewOptions project fixedErrors readmeData remainingRules accRul
|
||||
case remainingRules of
|
||||
[] ->
|
||||
{ project = project
|
||||
, step = Dependencies
|
||||
, step = ExtraFiles
|
||||
, ruleProjectVisitors = accRules
|
||||
, fixedErrors = fixedErrors
|
||||
}
|
||||
@ -4505,6 +4788,57 @@ computeReadme reviewOptions project fixedErrors readmeData remainingRules accRul
|
||||
(untouched :: accRules)
|
||||
|
||||
|
||||
computeExtraFiles :
|
||||
ReviewOptionsData
|
||||
-> ValidProject
|
||||
-> FixedErrors
|
||||
-> List { path : String, content : String }
|
||||
-> List RuleProjectVisitor
|
||||
-> List RuleProjectVisitor
|
||||
-> { project : ValidProject, ruleProjectVisitors : List RuleProjectVisitor, step : Step, fixedErrors : FixedErrors }
|
||||
computeExtraFiles reviewOptions project fixedErrors extraFiles remainingRules accRules =
|
||||
case remainingRules of
|
||||
[] ->
|
||||
{ project = project
|
||||
, step = Dependencies
|
||||
, ruleProjectVisitors = accRules
|
||||
, fixedErrors = fixedErrors
|
||||
}
|
||||
|
||||
((RuleProjectVisitor rule) as untouched) :: rest ->
|
||||
case rule.extraFilesVisitor of
|
||||
Just visitor ->
|
||||
let
|
||||
( errors, RuleProjectVisitor updatedRule ) =
|
||||
visitor project (List.map (\file -> { fileKey = ExtraFileKey file, path = file.path, content = file.content }) extraFiles)
|
||||
in
|
||||
case standardFindFix reviewOptions project fixedErrors updatedRule.setErrorsForExtraFiles errors of
|
||||
FoundFixStandard { newProject, newRule, newFixedErrors, step } ->
|
||||
{ project = newProject
|
||||
, ruleProjectVisitors = newRule :: (rest ++ accRules)
|
||||
, step = step
|
||||
, fixedErrors = newFixedErrors
|
||||
}
|
||||
|
||||
FoundNoFixesStandard newRule ->
|
||||
computeExtraFiles
|
||||
reviewOptions
|
||||
project
|
||||
fixedErrors
|
||||
extraFiles
|
||||
rest
|
||||
(newRule :: accRules)
|
||||
|
||||
Nothing ->
|
||||
computeExtraFiles
|
||||
reviewOptions
|
||||
project
|
||||
fixedErrors
|
||||
extraFiles
|
||||
rest
|
||||
(untouched :: accRules)
|
||||
|
||||
|
||||
computeDependencies :
|
||||
ReviewOptionsData
|
||||
-> ValidProject
|
||||
@ -4863,6 +5197,14 @@ findFixInComputeModuleResults ({ reviewOptions, module_, project, moduleZipper,
|
||||
, fixedErrors = FixedErrors.insert fixResult.error fixedErrors
|
||||
}
|
||||
|
||||
FixedExtraFile ->
|
||||
ContinueWithNextStep
|
||||
{ project = fixResult.project
|
||||
, ruleProjectVisitors = newRule :: (rest ++ rulesSoFar)
|
||||
, nextStep = BackToExtraFiles
|
||||
, fixedErrors = FixedErrors.insert fixResult.error fixedErrors
|
||||
}
|
||||
|
||||
FoundNoFixes newRule ->
|
||||
findFixInComputeModuleResults
|
||||
params
|
||||
@ -4916,6 +5258,13 @@ computeModules reviewOptions maybeModuleZipper initialProject ruleProjectVisitor
|
||||
, fixedErrors = result.fixedErrors
|
||||
}
|
||||
|
||||
BackToExtraFiles ->
|
||||
{ project = result.project
|
||||
, ruleProjectVisitors = result.ruleProjectVisitors
|
||||
, step = ExtraFiles
|
||||
, fixedErrors = result.fixedErrors
|
||||
}
|
||||
|
||||
NextStepAbort ->
|
||||
{ project = result.project
|
||||
, ruleProjectVisitors = result.ruleProjectVisitors
|
||||
@ -5053,6 +5402,7 @@ type FixedFile
|
||||
= FixedElmModule { source : String, ast : File } (Zipper (Graph.NodeContext FilePath ()))
|
||||
| FixedElmJson
|
||||
| FixedReadme
|
||||
| FixedExtraFile
|
||||
|
||||
|
||||
type PostFixStatus
|
||||
@ -5079,15 +5429,20 @@ standardFindFix reviewOptions project fixedErrors updateErrors errors =
|
||||
( newFixedErrors_, EndAnalysis )
|
||||
|
||||
ShouldContinue newFixedErrors_ ->
|
||||
case fixResult.fixedFile of
|
||||
( newFixedErrors_
|
||||
, case fixResult.fixedFile of
|
||||
FixedElmJson ->
|
||||
( newFixedErrors_, ElmJson )
|
||||
ElmJson
|
||||
|
||||
FixedReadme ->
|
||||
( newFixedErrors_, Readme )
|
||||
Readme
|
||||
|
||||
FixedExtraFile ->
|
||||
ExtraFiles
|
||||
|
||||
FixedElmModule _ zipper ->
|
||||
( newFixedErrors_, Modules zipper )
|
||||
Modules zipper
|
||||
)
|
||||
in
|
||||
FoundFixStandard { newProject = fixResult.project, newRule = newRule, newFixedErrors = newFixedErrors, step = step }
|
||||
|
||||
@ -5223,7 +5578,28 @@ findFixHelp project fixablePredicate errors accErrors maybeModuleZipper =
|
||||
, error = errorToReviewError (Error headError)
|
||||
}
|
||||
|
||||
_ ->
|
||||
Review.Error.ExtraFile ->
|
||||
case ListExtra.find (\file -> file.path == headError.filePath) (ValidProject.extraFiles project) of
|
||||
Nothing ->
|
||||
findFixHelp project fixablePredicate restOfErrors (err :: accErrors) maybeModuleZipper
|
||||
|
||||
Just file ->
|
||||
case InternalFix.fixExtraFile fixes file.content of
|
||||
Err fixProblem ->
|
||||
findFixHelp project fixablePredicate restOfErrors (Error (Review.Error.markFixesAsProblem fixProblem headError) :: accErrors) maybeModuleZipper
|
||||
|
||||
Ok newFileContent ->
|
||||
FoundFixHelp
|
||||
(errors ++ accErrors)
|
||||
{ project = ValidProject.addExtraFile { path = headError.filePath, content = newFileContent } project
|
||||
, fixedFile = FixedExtraFile
|
||||
, error = errorToReviewError (Error headError)
|
||||
}
|
||||
|
||||
Review.Error.Global ->
|
||||
findFixHelp project fixablePredicate restOfErrors (err :: accErrors) maybeModuleZipper
|
||||
|
||||
Review.Error.UserGlobal ->
|
||||
findFixHelp project fixablePredicate restOfErrors (err :: accErrors) maybeModuleZipper
|
||||
|
||||
|
||||
@ -5419,6 +5795,7 @@ The hidden state is `{ cache : ProjectRuleCache projectContext, ruleData : Chang
|
||||
type alias RuleProjectVisitorOperations =
|
||||
{ elmJsonVisitor : Maybe (ValidProject -> Maybe { elmJsonKey : ElmJsonKey, project : Elm.Project.Project } -> ( List (Error {}), RuleProjectVisitor ))
|
||||
, readmeVisitor : Maybe (ValidProject -> Maybe { readmeKey : ReadmeKey, content : String } -> ( List (Error {}), RuleProjectVisitor ))
|
||||
, extraFilesVisitor : Maybe (ValidProject -> List { fileKey : ExtraFileKey, path : String, content : String } -> ( List (Error {}), RuleProjectVisitor ))
|
||||
, dependenciesVisitor : Maybe (ValidProject -> { all : Dict String Review.Project.Dependency.Dependency, direct : Dict String Review.Project.Dependency.Dependency } -> ( List (Error {}), RuleProjectVisitor ))
|
||||
, createModuleVisitorFromProjectVisitor : Maybe (ValidProject -> String -> ContentHash -> Graph.Adjacency () -> Maybe (AvailableData -> RuleModuleVisitor))
|
||||
, finalProjectEvaluation : Maybe (() -> ( List (Error {}), RuleProjectVisitor ))
|
||||
@ -5427,6 +5804,7 @@ type alias RuleProjectVisitorOperations =
|
||||
, getErrors : () -> List (Error {})
|
||||
, setErrorsForModule : String -> List (Error {}) -> RuleProjectVisitor
|
||||
, setErrorsForElmJson : List (Error {}) -> RuleProjectVisitor
|
||||
, setErrorsForExtraFiles : List (Error {}) -> RuleProjectVisitor
|
||||
, setErrorsForReadme : List (Error {}) -> RuleProjectVisitor
|
||||
, setErrorsForDependencies : List (Error {}) -> RuleProjectVisitor
|
||||
, setErrorsForFinalEvaluation : List (Error {}) -> RuleProjectVisitor
|
||||
@ -5446,8 +5824,9 @@ createRuleProjectVisitor schema initialProject ruleData initialCache =
|
||||
raise { cache = newCache, ruleData = hidden.ruleData }
|
||||
in
|
||||
RuleProjectVisitor
|
||||
{ elmJsonVisitor = createProjectVisitor schema hidden schema.elmJsonVisitor [] ValidProject.elmJsonHash .elmJson (\entry -> raiseCache { cache | elmJson = Just entry }) (\() -> raise hidden)
|
||||
, readmeVisitor = createProjectVisitor schema hidden schema.readmeVisitor [ cache.elmJson ] ValidProject.readmeHash .readme (\entry -> raiseCache { cache | readme = Just entry }) (\() -> raise hidden)
|
||||
{ elmJsonVisitor = createProjectVisitor schema hidden schema.elmJsonVisitor ElmJsonStep ValidProject.elmJsonHash .elmJson (\entry -> raiseCache { cache | elmJson = Just entry }) (\() -> raise hidden)
|
||||
, readmeVisitor = createProjectVisitor schema hidden schema.readmeVisitor ReadmeStep ValidProject.readmeHash .readme (\entry -> raiseCache { cache | readme = Just entry }) (\() -> raise hidden)
|
||||
, extraFilesVisitor = createExtraFilesVisitor schema hidden raise raiseCache
|
||||
, dependenciesVisitor = createDependenciesVisitor schema hidden.ruleData raiseCache cache { allVisitor = schema.dependenciesVisitor, directVisitor = schema.directDependenciesVisitor }
|
||||
, createModuleVisitorFromProjectVisitor = createModuleVisitorFromProjectVisitor schema raiseCache hidden
|
||||
, finalProjectEvaluation = createFinalProjectEvaluationVisitor schema hidden.ruleData raiseCache cache
|
||||
@ -5457,6 +5836,7 @@ createRuleProjectVisitor schema initialProject ruleData initialCache =
|
||||
, setErrorsForModule = \filePath newErrors -> raiseCache { cache | moduleContexts = Dict.update filePath (Maybe.map (\entry -> ModuleCache.setErrors newErrors entry)) cache.moduleContexts }
|
||||
, setErrorsForElmJson = \newErrors -> raiseCache { cache | elmJson = ProjectFileCache.setErrors newErrors cache.elmJson }
|
||||
, setErrorsForReadme = \newErrors -> raiseCache { cache | readme = ProjectFileCache.setErrors newErrors cache.readme }
|
||||
, setErrorsForExtraFiles = \newErrors -> raiseCache { cache | extraFiles = ExtraFile.setErrors newErrors cache.extraFiles }
|
||||
, setErrorsForDependencies = \newErrors -> raiseCache { cache | dependencies = ProjectFileCache.setErrors newErrors cache.dependencies }
|
||||
, setErrorsForFinalEvaluation = \newErrors -> raiseCache { cache | finalEvaluationErrors = EndAnalysisCache.setOutput newErrors cache.finalEvaluationErrors }
|
||||
, backToRule =
|
||||
@ -5482,7 +5862,7 @@ createProjectVisitor :
|
||||
ProjectRuleSchemaData projectContext moduleContext
|
||||
-> RuleProjectVisitorHidden projectContext
|
||||
-> Maybe (data -> projectContext -> ( List (Error {}), projectContext ))
|
||||
-> List (Maybe (ProjectFileCache projectContext))
|
||||
-> StepToComputeContext
|
||||
-> (ValidProject -> Maybe ContentHash)
|
||||
-> (ProjectRuleCache projectContext -> Maybe (ProjectFileCache projectContext))
|
||||
-> (ProjectFileCache projectContext -> RuleProjectVisitor)
|
||||
@ -5493,7 +5873,7 @@ createProjectVisitor :
|
||||
-> data
|
||||
-> ( List (Error {}), RuleProjectVisitor )
|
||||
)
|
||||
createProjectVisitor schema hidden maybeVisitor possibleInputContexts computeContentHash cacheGetter toRuleProjectVisitor toRuleProjectVisitorWithoutChangingCache =
|
||||
createProjectVisitor schema hidden maybeVisitor step computeContentHash cacheGetter toRuleProjectVisitor toRuleProjectVisitorWithoutChangingCache =
|
||||
case maybeVisitor of
|
||||
Nothing ->
|
||||
Nothing
|
||||
@ -5503,7 +5883,7 @@ createProjectVisitor schema hidden maybeVisitor possibleInputContexts computeCon
|
||||
(\project data ->
|
||||
let
|
||||
( baseInputContextHash, inputContext ) =
|
||||
findInitialInputContext schema.initialProjectContext possibleInputContexts
|
||||
findInitialInputContext hidden.cache step schema.initialProjectContext
|
||||
|
||||
inputContextHash : ComparableContextHash projectContext
|
||||
inputContextHash =
|
||||
@ -5542,6 +5922,69 @@ createProjectVisitor schema hidden maybeVisitor possibleInputContexts computeCon
|
||||
)
|
||||
|
||||
|
||||
createExtraFilesVisitor :
|
||||
ProjectRuleSchemaData projectContext moduleContext
|
||||
-> RuleProjectVisitorHidden projectContext
|
||||
-> ({ cache : ProjectRuleCache projectContext, ruleData : ChangeableRuleData } -> RuleProjectVisitor)
|
||||
-> (ProjectRuleCache projectContext -> RuleProjectVisitor)
|
||||
->
|
||||
Maybe
|
||||
(ValidProject
|
||||
-> List { fileKey : ExtraFileKey, content : String, path : String }
|
||||
-> ( List (Error {}), RuleProjectVisitor )
|
||||
)
|
||||
createExtraFilesVisitor schema ({ cache } as hidden) raise raiseCache =
|
||||
case schema.extraFilesVisitor of
|
||||
Nothing ->
|
||||
Nothing
|
||||
|
||||
Just visitor ->
|
||||
Just
|
||||
(\project data ->
|
||||
let
|
||||
( baseInputContextHash, inputContext ) =
|
||||
findInitialInputContext hidden.cache ExtraFilesStep schema.initialProjectContext
|
||||
|
||||
inputContextHash : ComparableContextHash projectContext
|
||||
inputContextHash =
|
||||
ContextHash.toComparable baseInputContextHash
|
||||
|
||||
contentHashes : List ContentHash
|
||||
contentHashes =
|
||||
ValidProject.extraFilesHash project
|
||||
|
||||
cachePredicate : ExtraFilesCache projectContext -> Bool
|
||||
cachePredicate extraFiles =
|
||||
ExtraFile.match contentHashes inputContextHash extraFiles
|
||||
in
|
||||
case reuseProjectRuleCache cachePredicate .extraFiles hidden.cache of
|
||||
Just entry ->
|
||||
( ExtraFile.errors entry, raise hidden )
|
||||
|
||||
Nothing ->
|
||||
let
|
||||
( errorsForVisitor, outputContext ) =
|
||||
visitor data inputContext
|
||||
|
||||
errors : List (Error {})
|
||||
errors =
|
||||
filterExceptionsAndSetName hidden.ruleData.exceptions schema.name errorsForVisitor
|
||||
|
||||
entry : ExtraFilesCache projectContext
|
||||
entry =
|
||||
ExtraFile.create
|
||||
{ contentHashes = contentHashes
|
||||
, errors = errors
|
||||
, inputContextHash = inputContextHash
|
||||
, outputContext = outputContext
|
||||
}
|
||||
in
|
||||
( errors
|
||||
, raiseCache { cache | extraFiles = Just entry }
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
createDependenciesVisitor :
|
||||
ProjectRuleSchemaData projectContext moduleContext
|
||||
-> ChangeableRuleData
|
||||
@ -5567,7 +6010,7 @@ createDependenciesVisitor schema { exceptions } raise cache { allVisitor, direct
|
||||
(\project { all, direct } ->
|
||||
let
|
||||
( baseInputContextHash, inputContext ) =
|
||||
findInitialInputContext schema.initialProjectContext [ cache.readme, cache.elmJson ]
|
||||
findInitialInputContext cache DependenciesStep schema.initialProjectContext
|
||||
|
||||
inputContextHash : ComparableContextHash projectContext
|
||||
inputContextHash =
|
||||
@ -5622,17 +6065,47 @@ createDependenciesVisitor schema { exceptions } raise cache { allVisitor, direct
|
||||
)
|
||||
|
||||
|
||||
findInitialInputContext : projectContext -> List (Maybe (ProjectFileCache projectContext)) -> ( List (ContextHash projectContext), projectContext )
|
||||
findInitialInputContext defaultContext possibleInputContexts =
|
||||
case possibleInputContexts of
|
||||
[] ->
|
||||
findInitialInputContext :
|
||||
ProjectRuleCache projectContext
|
||||
-> StepToComputeContext
|
||||
-> projectContext
|
||||
-> ( List (ContextHash projectContext), projectContext )
|
||||
findInitialInputContext cache step defaultContext =
|
||||
case step of
|
||||
ElmJsonStep ->
|
||||
( [], defaultContext )
|
||||
|
||||
(Just cacheEntry) :: _ ->
|
||||
( [ ProjectFileCache.outputContextHash cacheEntry ], ProjectFileCache.outputContext cacheEntry )
|
||||
ReadmeStep ->
|
||||
case cache.elmJson of
|
||||
Just entry ->
|
||||
( [ ProjectFileCache.outputContextHash entry ], ProjectFileCache.outputContext entry )
|
||||
|
||||
Nothing :: rest ->
|
||||
findInitialInputContext defaultContext rest
|
||||
Nothing ->
|
||||
findInitialInputContext cache ElmJsonStep defaultContext
|
||||
|
||||
ExtraFilesStep ->
|
||||
case cache.readme of
|
||||
Just entry ->
|
||||
( [ ProjectFileCache.outputContextHash entry ], ProjectFileCache.outputContext entry )
|
||||
|
||||
Nothing ->
|
||||
findInitialInputContext cache ReadmeStep defaultContext
|
||||
|
||||
DependenciesStep ->
|
||||
case cache.extraFiles of
|
||||
Just entry ->
|
||||
( [ ExtraFile.outputContextHash entry ], ExtraFile.outputContext entry )
|
||||
|
||||
Nothing ->
|
||||
findInitialInputContext cache ExtraFilesStep defaultContext
|
||||
|
||||
AfterProjectFilesStep ->
|
||||
case cache.dependencies of
|
||||
Just entry ->
|
||||
( [ ProjectFileCache.outputContextHash entry ], ProjectFileCache.outputContext entry )
|
||||
|
||||
Nothing ->
|
||||
findInitialInputContext cache DependenciesStep defaultContext
|
||||
|
||||
|
||||
createFinalProjectEvaluationVisitor :
|
||||
@ -5770,7 +6243,7 @@ createModuleVisitorFromProjectVisitorHelp schema raise hidden traversalAndFolder
|
||||
\project filePath moduleContentHash incoming ->
|
||||
let
|
||||
( initialProjectContextHash, initialProjectContext ) =
|
||||
findInitialInputContext schema.initialProjectContext [ hidden.cache.dependencies, hidden.cache.readme, hidden.cache.elmJson ]
|
||||
findInitialInputContext hidden.cache AfterProjectFilesStep schema.initialProjectContext
|
||||
|
||||
inputContextHashes : ComparableContextHash projectContext
|
||||
inputContextHashes =
|
||||
|
@ -39,6 +39,8 @@ module Review.Test exposing
|
||||
]
|
||||
]
|
||||
|
||||
AUTRE CHOSE
|
||||
|
||||
|
||||
# Strategies for effective testing
|
||||
|
||||
@ -125,7 +127,7 @@ for this module.
|
||||
-}
|
||||
|
||||
import Array exposing (Array)
|
||||
import Dict
|
||||
import Dict exposing (Dict)
|
||||
import Elm.Syntax.Module as Module
|
||||
import Elm.Syntax.Node as Node
|
||||
import Elm.Syntax.Range exposing (Range)
|
||||
@ -464,6 +466,7 @@ runOnModulesWithProjectDataHelp project rule sources =
|
||||
[ List.map (\module_ -> moduleToRunResult errors module_) modules
|
||||
, elmJsonRunResult errors projectWithModules
|
||||
, readmeRunResult errors projectWithModules
|
||||
, extraFileRunResult errors projectWithModules
|
||||
]
|
||||
|
||||
foundGlobalErrors : List GlobalError
|
||||
@ -541,6 +544,52 @@ readmeRunResult errors project =
|
||||
[]
|
||||
|
||||
|
||||
extraFileRunResult : List ReviewError -> Project -> List SuccessfulRunResult
|
||||
extraFileRunResult errors project =
|
||||
let
|
||||
extraFilePaths : Dict String { path : String, content : String }
|
||||
extraFilePaths =
|
||||
Project.extraFiles project
|
||||
|> List.map (\file -> ( file.path, file ))
|
||||
|> Dict.fromList
|
||||
in
|
||||
Dict.foldl
|
||||
(\path errorsForFile acc ->
|
||||
case Dict.get path extraFilePaths of
|
||||
Just file ->
|
||||
{ moduleName = path
|
||||
, inspector = codeInspectorForSource False file.content
|
||||
, errors = errorsForFile
|
||||
}
|
||||
:: acc
|
||||
|
||||
Nothing ->
|
||||
acc
|
||||
)
|
||||
[]
|
||||
(errorsPerFile errors)
|
||||
|
||||
|
||||
errorsPerFile : List ReviewError -> Dict String (List ReviewError)
|
||||
errorsPerFile reviewErrors =
|
||||
List.foldl
|
||||
(\error_ acc ->
|
||||
Dict.update
|
||||
(Rule.errorFilePath error_)
|
||||
(\maybePreviousErrors ->
|
||||
case maybePreviousErrors of
|
||||
Nothing ->
|
||||
Just [ error_ ]
|
||||
|
||||
Just previousErrors ->
|
||||
Just (error_ :: previousErrors)
|
||||
)
|
||||
acc
|
||||
)
|
||||
Dict.empty
|
||||
reviewErrors
|
||||
|
||||
|
||||
indexOf : a -> List a -> Maybe Int
|
||||
indexOf elementToFind aList =
|
||||
indexOfHelp elementToFind aList 0
|
||||
@ -879,6 +928,7 @@ doCheckResultsAreTheSameWhenIgnoringFiles allErrors rule project =
|
||||
Project.modules project
|
||||
|> List.map .path
|
||||
|> maybeCons .path (Project.elmJson project)
|
||||
-- TODO Add extra files? Are there other remaining places?
|
||||
|> maybeCons .path (Project.readme project)
|
||||
|
||||
combinationsOfFilesToIgnore : List (List String)
|
||||
|
220
tests/Docs/NoMissingChangelogEntry.elm
Normal file
220
tests/Docs/NoMissingChangelogEntry.elm
Normal file
@ -0,0 +1,220 @@
|
||||
module Docs.NoMissingChangelogEntry exposing
|
||||
( rule
|
||||
, Configuration, defaults, withPathToChangelog
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
@docs rule
|
||||
|
||||
config =
|
||||
[ Docs.NoMissingChangelogEntry.rule Docs.NoMissingChangelogEntry.defaults
|
||||
]
|
||||
|
||||
@docs Configuration, defaults, withPathToChangelog
|
||||
|
||||
|
||||
## Fail
|
||||
|
||||
a =
|
||||
"REPLACEME example to replace"
|
||||
|
||||
|
||||
## Success
|
||||
|
||||
a =
|
||||
"REPLACEME example to replace"
|
||||
|
||||
|
||||
## When (not) to enable this rule
|
||||
|
||||
This rule is useful when REPLACEME.
|
||||
This rule is not useful when REPLACEME.
|
||||
|
||||
|
||||
## Try it out
|
||||
|
||||
You can try this rule out by running the following command:
|
||||
|
||||
```bash
|
||||
elm-review --template jfmengels/elm-review/example --rules Docs.NoMissingChangelogEntry
|
||||
```
|
||||
|
||||
-}
|
||||
|
||||
import Elm.Project exposing (Project)
|
||||
import Elm.Syntax.Range exposing (Range)
|
||||
import Elm.Version
|
||||
import Review.FilePattern as FilePattern
|
||||
import Review.Fix as Fix
|
||||
import Review.Rule as Rule exposing (Rule)
|
||||
|
||||
|
||||
{-| Reports... REPLACEME
|
||||
-}
|
||||
rule : Configuration -> Rule
|
||||
rule (Configuration { changelogPath }) =
|
||||
Rule.newProjectRuleSchema "Docs.NoMissingChangelogEntry" initialProjectContext
|
||||
|> Rule.withElmJsonProjectVisitor elmJsonVisitor
|
||||
|> Rule.withExtraFilesProjectVisitor [ FilePattern.include (Maybe.withDefault defaultPath changelogPath) ] (extraFilesVisitor changelogPath)
|
||||
|> Rule.providesFixesForProjectRule
|
||||
|> Rule.fromProjectRuleSchema
|
||||
|
||||
|
||||
type Configuration
|
||||
= Configuration { changelogPath : Maybe String }
|
||||
|
||||
|
||||
defaults : Configuration
|
||||
defaults =
|
||||
Configuration { changelogPath = Nothing }
|
||||
|
||||
|
||||
defaultPath : String
|
||||
defaultPath =
|
||||
"CHANGELOG.md"
|
||||
|
||||
|
||||
withPathToChangelog : String -> Configuration -> Configuration
|
||||
withPathToChangelog changelogPath _ =
|
||||
Configuration { changelogPath = Just changelogPath }
|
||||
|
||||
|
||||
type alias ProjectContext =
|
||||
{ elmJsonVersion : Maybe String
|
||||
}
|
||||
|
||||
|
||||
initialProjectContext : ProjectContext
|
||||
initialProjectContext =
|
||||
{ elmJsonVersion = Nothing
|
||||
}
|
||||
|
||||
|
||||
elmJsonVisitor : Maybe { a | project : Project } -> ProjectContext -> ( List (Rule.Error scope), ProjectContext )
|
||||
elmJsonVisitor maybeElmJsonData context =
|
||||
case maybeElmJsonData of
|
||||
Just { project } ->
|
||||
case project of
|
||||
Elm.Project.Package { version } ->
|
||||
( [], { context | elmJsonVersion = Just (Elm.Version.toString version) } )
|
||||
|
||||
Elm.Project.Application _ ->
|
||||
( [ Rule.globalError
|
||||
{ message = "The Elm project is unexpectedly an application"
|
||||
, details = [ "This rule only supports Elm packages, but doesn't support Elm applications as they don't have a version number. I recommend that you remove this rule from your review configuration." ]
|
||||
}
|
||||
]
|
||||
, context
|
||||
)
|
||||
|
||||
Nothing ->
|
||||
( [], context )
|
||||
|
||||
|
||||
extraFilesVisitor : Maybe String -> List { fileKey : Rule.ExtraFileKey, path : String, content : String } -> ProjectContext -> ( List (Rule.Error { useErrorForModule : () }), ProjectContext )
|
||||
extraFilesVisitor changelogPath files context =
|
||||
case List.head files of
|
||||
Just { fileKey, content } ->
|
||||
case context.elmJsonVersion of
|
||||
Nothing ->
|
||||
( [], context )
|
||||
|
||||
Just "1.0.0" ->
|
||||
( [], context )
|
||||
|
||||
Just elmJsonVersion ->
|
||||
if String.contains elmJsonVersion content then
|
||||
( [], context )
|
||||
|
||||
else
|
||||
( [ reportError fileKey elmJsonVersion content ]
|
||||
, context
|
||||
)
|
||||
|
||||
Nothing ->
|
||||
case context.elmJsonVersion of
|
||||
Nothing ->
|
||||
( [], context )
|
||||
|
||||
Just "1.0.0" ->
|
||||
-- TODO Report an error with a changelog skeleton
|
||||
( [], context )
|
||||
|
||||
Just _ ->
|
||||
case changelogPath of
|
||||
Nothing ->
|
||||
( [ Rule.globalError
|
||||
{ message = "Could not find the CHANGELOG.md file"
|
||||
, details =
|
||||
[ "I was looking for the CHANGELOG.md file next to your project's elm.json file but couldn't find it. Please make sure that the spelling is correct."
|
||||
, "If your changelog is named differently or is in a different location, then you can configure this rule to look for it in a different location:"
|
||||
, """ config =
|
||||
[ Docs.NoMissingChangelogEntry.defaults
|
||||
|> Docs.NoMissingChangelogEntry.withPathToChangelog "path/to/your/changelog.md"
|
||||
|> Docs.NoMissingChangelogEntry.rule
|
||||
]"""
|
||||
, "Note that the path is relative your project's elm.json file."
|
||||
]
|
||||
}
|
||||
]
|
||||
, context
|
||||
)
|
||||
|
||||
Just customPath ->
|
||||
( [ Rule.globalError
|
||||
{ message = "Could not find the " ++ customPath ++ " changelog file"
|
||||
, details =
|
||||
[ "I was looking for the " ++ customPath ++ " changelog file but couldn't find it. Please make sure that the path you specified through Docs.NoMissingChangelogEntry.withPathToChangelog is correct."
|
||||
, "Also note that the path you specify has to be relative to your project's elm.json file."
|
||||
]
|
||||
}
|
||||
]
|
||||
, context
|
||||
)
|
||||
|
||||
|
||||
reportError : Rule.ExtraFileKey -> String -> String -> Rule.Error scope
|
||||
reportError fileKey elmJsonVersion content =
|
||||
let
|
||||
lines : List String
|
||||
lines =
|
||||
String.lines content
|
||||
|
||||
unreleased : Maybe ( Int, String )
|
||||
unreleased =
|
||||
findLineWithUnreleased 0 lines
|
||||
in
|
||||
Rule.errorForExtraFileWithFix
|
||||
fileKey
|
||||
{ message = "Missing entry in CHANGELOG.md for version " ++ elmJsonVersion
|
||||
, details = [ "It seems you have or are ready to release a new version of your package, but forgot to include releases notes for it in your CHANGELOG.md file." ]
|
||||
}
|
||||
(case unreleased of
|
||||
Just ( lineNumber, line ) ->
|
||||
{ start = { row = lineNumber, column = 1 }, end = { row = lineNumber, column = String.length line + 1 } }
|
||||
|
||||
Nothing ->
|
||||
{ start = { row = 1, column = 1 }, end = { row = 1, column = String.length (List.head lines |> Maybe.withDefault "") + 1 } }
|
||||
)
|
||||
(case unreleased of
|
||||
Just ( lineNumber, _ ) ->
|
||||
[ Fix.insertAt { row = lineNumber + 1, column = 1 } ("\n## [" ++ elmJsonVersion ++ "]\n\n") ]
|
||||
|
||||
Nothing ->
|
||||
[]
|
||||
)
|
||||
|
||||
|
||||
findLineWithUnreleased : Int -> List String -> Maybe ( Int, String )
|
||||
findLineWithUnreleased index lines =
|
||||
case lines of
|
||||
[] ->
|
||||
Nothing
|
||||
|
||||
line :: rest ->
|
||||
if String.contains "# [Unreleased]" line then
|
||||
Just ( index + 1, line )
|
||||
|
||||
else
|
||||
findLineWithUnreleased (index + 1) rest
|
266
tests/Docs/NoMissingChangelogEntryTest.elm
Normal file
266
tests/Docs/NoMissingChangelogEntryTest.elm
Normal file
@ -0,0 +1,266 @@
|
||||
module Docs.NoMissingChangelogEntryTest exposing (all)
|
||||
|
||||
import Docs.NoMissingChangelogEntry exposing (defaults, rule, withPathToChangelog)
|
||||
import Elm.Project
|
||||
import Json.Decode as Decode
|
||||
import Review.Project as Project exposing (Project)
|
||||
import Review.Test
|
||||
import Test exposing (Test, describe, test)
|
||||
|
||||
|
||||
all : Test
|
||||
all =
|
||||
describe "Docs.NoMissingChangelogEntry"
|
||||
[ test "should not report an error when the version of the package is found in the changelog" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.addExtraFiles
|
||||
[ { path = "CHANGELOG.md"
|
||||
, content = """
|
||||
# Changelog
|
||||
## [Unreleased]
|
||||
Stuff
|
||||
## 2.13.0
|
||||
More stuff happened
|
||||
## 2.12.0
|
||||
Stuff happened
|
||||
"""
|
||||
}
|
||||
]
|
||||
(package "2.13.0")
|
||||
in
|
||||
"module A exposing (..)\na = 1"
|
||||
|> Review.Test.runWithProjectData project (rule defaults)
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should report an error when the version in the elm.json is not found in the changelog" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.addExtraFiles
|
||||
[ { path = "CHANGELOG.md"
|
||||
, content = """# Changelog
|
||||
## [Unreleased]
|
||||
Stuff
|
||||
## 1.13.0
|
||||
More stuff happened
|
||||
## 1.12.0
|
||||
Stuff happened
|
||||
"""
|
||||
}
|
||||
]
|
||||
(package "2.13.0")
|
||||
in
|
||||
"""module A exposing (..)
|
||||
a = 1
|
||||
"""
|
||||
|> Review.Test.runWithProjectData project (rule defaults)
|
||||
|> Review.Test.expectErrorsForModules
|
||||
[ ( "CHANGELOG.md"
|
||||
, [ Review.Test.error
|
||||
{ message = "Missing entry in CHANGELOG.md for version 2.13.0"
|
||||
, details = [ "It seems you have or are ready to release a new version of your package, but forgot to include releases notes for it in your CHANGELOG.md file." ]
|
||||
, under = "## [Unreleased]"
|
||||
}
|
||||
|> Review.Test.whenFixed """# Changelog
|
||||
## [Unreleased]
|
||||
|
||||
## [2.13.0]
|
||||
|
||||
Stuff
|
||||
## 1.13.0
|
||||
More stuff happened
|
||||
## 1.12.0
|
||||
Stuff happened
|
||||
"""
|
||||
]
|
||||
)
|
||||
]
|
||||
, test "should report an error when the version in the elm.json is not found in the changelog (no unreleased section)" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.addExtraFiles
|
||||
[ { path = "CHANGELOG.md"
|
||||
, content = """# Changelog
|
||||
## 1.13.0
|
||||
More stuff happened
|
||||
## 1.12.0
|
||||
Stuff happened
|
||||
"""
|
||||
}
|
||||
]
|
||||
(package "2.13.0")
|
||||
in
|
||||
"""module A exposing (..)
|
||||
a = 1
|
||||
"""
|
||||
|> Review.Test.runWithProjectData project (rule defaults)
|
||||
|> Review.Test.expectErrorsForModules
|
||||
[ ( "CHANGELOG.md"
|
||||
, [ Review.Test.error
|
||||
{ message = "Missing entry in CHANGELOG.md for version 2.13.0"
|
||||
, details = [ "It seems you have or are ready to release a new version of your package, but forgot to include releases notes for it in your CHANGELOG.md file." ]
|
||||
, under = "# Changelog"
|
||||
}
|
||||
]
|
||||
)
|
||||
]
|
||||
, test "should report an error when the changelog could not be found (default path)" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
a = 1
|
||||
"""
|
||||
|> Review.Test.runWithProjectData (package "2.13.0") (rule defaults)
|
||||
|> Review.Test.expectGlobalErrors
|
||||
[ { message = "Could not find the CHANGELOG.md file"
|
||||
, details =
|
||||
[ "I was looking for the CHANGELOG.md file next to your project's elm.json file but couldn't find it. Please make sure that the spelling is correct."
|
||||
, "If your changelog is named differently or is in a different location, then you can configure this rule to look for it in a different location:"
|
||||
, """ config =
|
||||
[ Docs.NoMissingChangelogEntry.defaults
|
||||
|> Docs.NoMissingChangelogEntry.withPathToChangelog "path/to/your/changelog.md"
|
||||
|> Docs.NoMissingChangelogEntry.rule
|
||||
]"""
|
||||
, "Note that the path is relative your project's elm.json file."
|
||||
]
|
||||
}
|
||||
]
|
||||
, test "should report an error when the changelog could not be found (custom path)" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
a = 1
|
||||
"""
|
||||
|> Review.Test.runWithProjectData (package "2.13.0") (defaults |> withPathToChangelog "path/not-found.md" |> rule)
|
||||
|> Review.Test.expectGlobalErrors
|
||||
[ { message = "Could not find the path/not-found.md changelog file"
|
||||
, details =
|
||||
[ "I was looking for the path/not-found.md changelog file but couldn't find it. Please make sure that the path you specified through Docs.NoMissingChangelogEntry.withPathToChangelog is correct."
|
||||
, "Also note that the path you specify has to be relative to your project's elm.json file."
|
||||
]
|
||||
}
|
||||
]
|
||||
, test "should report an error when the project is an application" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.addExtraFiles
|
||||
[ { path = "CHANGELOG.md"
|
||||
, content = "# something"
|
||||
}
|
||||
]
|
||||
application
|
||||
in
|
||||
"""module A exposing (..)
|
||||
a = 1
|
||||
"""
|
||||
|> Review.Test.runWithProjectData project (rule defaults)
|
||||
|> Review.Test.expectGlobalErrors
|
||||
[ { message = "The Elm project is unexpectedly an application"
|
||||
, details = [ "This rule only supports Elm packages, but doesn't support Elm applications as they don't have a version number. I recommend that you remove this rule from your review configuration." ]
|
||||
}
|
||||
]
|
||||
, test "should not report an error when the version is 1.0.0 (no changelog)" <|
|
||||
\() ->
|
||||
"""module A exposing (..)
|
||||
a = 1
|
||||
"""
|
||||
|> Review.Test.runWithProjectData (package "1.0.0") (rule defaults)
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should not report an error when the version is 1.0.0 (empty changelog)" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
Project.addExtraFiles
|
||||
[ { path = "CHANGELOG.md"
|
||||
, content = ""
|
||||
}
|
||||
]
|
||||
(package "1.0.0")
|
||||
in
|
||||
"""module A exposing (..)
|
||||
a = 1
|
||||
"""
|
||||
|> Review.Test.runWithProjectData project (rule defaults)
|
||||
|> Review.Test.expectNoErrors
|
||||
]
|
||||
|
||||
|
||||
package : String -> Project
|
||||
package version =
|
||||
let
|
||||
raw : String
|
||||
raw =
|
||||
packageElmJson version
|
||||
in
|
||||
case Decode.decodeString Elm.Project.decoder raw of
|
||||
Ok project ->
|
||||
Project.new
|
||||
|> Project.addElmJson
|
||||
{ path = "elm.json"
|
||||
, raw = raw
|
||||
, project = project
|
||||
}
|
||||
|
||||
Err err ->
|
||||
Debug.todo ("Invalid elm.json supplied to test: " ++ Debug.toString err)
|
||||
|
||||
|
||||
packageElmJson : String -> String
|
||||
packageElmJson version =
|
||||
"""{
|
||||
"type": "package",
|
||||
"name": "author/package",
|
||||
"summary": "Summary",
|
||||
"license": "BSD-3-Clause",
|
||||
"version": \"""" ++ version ++ """",
|
||||
"exposed-modules": [
|
||||
"Exposed"
|
||||
],
|
||||
"elm-version": "0.19.0 <= v < 0.20.0",
|
||||
"dependencies": {
|
||||
"elm/core": "1.0.0 <= v < 2.0.0"
|
||||
},
|
||||
"test-dependencies": {}
|
||||
}"""
|
||||
|
||||
|
||||
application : Project
|
||||
application =
|
||||
case Decode.decodeString Elm.Project.decoder applicationElmJson of
|
||||
Ok project ->
|
||||
Project.new
|
||||
|> Project.addElmJson
|
||||
{ path = "elm.json"
|
||||
, raw = applicationElmJson
|
||||
, project = project
|
||||
}
|
||||
|
||||
Err err ->
|
||||
Debug.todo ("Invalid elm.json supplied to test: " ++ Debug.toString err)
|
||||
|
||||
|
||||
applicationElmJson : String
|
||||
applicationElmJson =
|
||||
"""{
|
||||
"type": "application",
|
||||
"source-directories": [
|
||||
"src"
|
||||
],
|
||||
"elm-version": "0.19.1",
|
||||
"dependencies": {
|
||||
"direct": {
|
||||
"elm/core": "1.0.0"
|
||||
},
|
||||
"indirect": {}
|
||||
},
|
||||
"test-dependencies": {
|
||||
"direct": {},
|
||||
"indirect": {}
|
||||
}
|
||||
}"""
|
@ -93,6 +93,10 @@ a = localValue
|
||||
True
|
||||
Just
|
||||
Cmd.none
|
||||
(let foo get = get
|
||||
in
|
||||
get
|
||||
)
|
||||
(+)
|
||||
(117 + 3)
|
||||
(<?>)
|
||||
@ -155,6 +159,8 @@ Http.get -> Http.get
|
||||
<nothing>.True -> Basics.True
|
||||
<nothing>.Just -> Maybe.Just
|
||||
Cmd.none -> Platform.Cmd.none
|
||||
<nothing>.get -> <nothing>.get
|
||||
<nothing>.get -> Http.get
|
||||
<nothing>.+ -> Basics.+
|
||||
<nothing>.+ -> Basics.+
|
||||
<nothing>.<?> -> Url.Parser.<?>
|
||||
@ -689,8 +695,11 @@ collectPatterns lookupFunction context node =
|
||||
Pattern.AsPattern subPattern _ ->
|
||||
collectPatterns lookupFunction context subPattern
|
||||
|
||||
Pattern.VarPattern _ ->
|
||||
[]
|
||||
|
||||
_ ->
|
||||
Debug.todo "Other patterns in case expressions are not handled"
|
||||
Debug.todo ("Other patterns in case expressions are not handled: " ++ Debug.toString node)
|
||||
|
||||
|
||||
getRealName : (ModuleNameLookupTable -> Range -> Maybe ModuleName) -> ModuleContext -> ModuleName -> Range -> String -> String
|
||||
|
143
tests/Review/FilePatternTest.elm
Normal file
143
tests/Review/FilePatternTest.elm
Normal file
@ -0,0 +1,143 @@
|
||||
module Review.FilePatternTest exposing (all)
|
||||
|
||||
import Expect
|
||||
import Fuzz
|
||||
import Review.FilePattern as FilePattern exposing (FilePattern, excludeFolder, include)
|
||||
import Test exposing (Test, describe, fuzz, test)
|
||||
|
||||
|
||||
all : Test
|
||||
all =
|
||||
describe "Review.FilePattern"
|
||||
[ matchTest
|
||||
, toStringsTest
|
||||
]
|
||||
|
||||
|
||||
matchTest : Test
|
||||
matchTest =
|
||||
describe "match"
|
||||
[ test "should return False when the list is empty" <|
|
||||
\() ->
|
||||
matchAgainst [] "some/file/path.ext"
|
||||
|> Expect.equal False
|
||||
, test "should return True when including the target file" <|
|
||||
\() ->
|
||||
matchAgainst
|
||||
[ FilePattern.include "some/file/path.ext"
|
||||
]
|
||||
"some/file/path.ext"
|
||||
|> Expect.equal True
|
||||
, test "should return True when including through *" <|
|
||||
\() ->
|
||||
matchAgainst
|
||||
[ FilePattern.include "some/file/**/*"
|
||||
]
|
||||
"some/file/path.ext"
|
||||
|> Expect.equal True
|
||||
, test "should return True when including through * with an extension" <|
|
||||
\() ->
|
||||
matchAgainst
|
||||
[ FilePattern.include "some/file/**/*.ext"
|
||||
]
|
||||
"some/file/path.ext"
|
||||
|> Expect.equal True
|
||||
, test "should return True when including through **/*" <|
|
||||
\() ->
|
||||
matchAgainst
|
||||
[ FilePattern.include "some/file/**/*"
|
||||
]
|
||||
"some/file/path.ext"
|
||||
|> Expect.equal True
|
||||
, test "should return False when including through **/* but excluding the target file" <|
|
||||
\() ->
|
||||
matchAgainst
|
||||
[ FilePattern.include "some/file/**/*"
|
||||
, FilePattern.exclude "some/file/path.ext"
|
||||
]
|
||||
"some/file/path.ext"
|
||||
|> Expect.equal False
|
||||
, test "should return True when excluding through **/* but re-including the target file" <|
|
||||
\() ->
|
||||
matchAgainst
|
||||
[ FilePattern.exclude "some/file/**/*"
|
||||
, FilePattern.include "some/file/path.ext"
|
||||
]
|
||||
"some/file/path.ext"
|
||||
|> Expect.equal True
|
||||
, test "should return False when excluding the folder even when re-including the target file" <|
|
||||
\() ->
|
||||
matchAgainst
|
||||
[ FilePattern.excludeFolder "some"
|
||||
, FilePattern.include "some/file/path.ext"
|
||||
]
|
||||
"some/file/path.ext"
|
||||
|> Expect.equal False
|
||||
, test "should return False when excluding the folder (with trailing /) even when re-including the target file" <|
|
||||
\() ->
|
||||
matchAgainst
|
||||
[ FilePattern.excludeFolder "some/"
|
||||
, FilePattern.include "some/file/path.ext"
|
||||
]
|
||||
"some/file/path.ext"
|
||||
|> Expect.equal False
|
||||
]
|
||||
|
||||
|
||||
matchAgainst : List FilePattern -> String -> Bool
|
||||
matchAgainst filePatterns str =
|
||||
case FilePattern.compact filePatterns of
|
||||
Ok filePatternCompact ->
|
||||
FilePattern.match filePatternCompact str
|
||||
|
||||
Err globs ->
|
||||
Debug.todo ("Invalid globs:\n" ++ String.join "\n" globs)
|
||||
|
||||
|
||||
toStringsTest : Test
|
||||
toStringsTest =
|
||||
describe "toStrings"
|
||||
[ fuzz
|
||||
(Fuzz.list (Fuzz.map2 (\str included -> { pattern = str, included = included }) Fuzz.string Fuzz.bool))
|
||||
"files should stay as before"
|
||||
<|
|
||||
\list ->
|
||||
case
|
||||
list
|
||||
|> List.map
|
||||
(\{ pattern, included } ->
|
||||
if included then
|
||||
FilePattern.include pattern
|
||||
|
||||
else
|
||||
FilePattern.exclude pattern
|
||||
)
|
||||
|> FilePattern.compact
|
||||
of
|
||||
Ok summary ->
|
||||
summary
|
||||
|> FilePattern.toStrings
|
||||
|> .files
|
||||
|> Expect.equal list
|
||||
|
||||
Err _ ->
|
||||
Expect.pass
|
||||
, fuzz
|
||||
(Fuzz.list Fuzz.string)
|
||||
"excluded folders should stay as before"
|
||||
<|
|
||||
\list ->
|
||||
case
|
||||
list
|
||||
|> List.map FilePattern.excludeFolder
|
||||
|> FilePattern.compact
|
||||
of
|
||||
Ok summary ->
|
||||
summary
|
||||
|> FilePattern.toStrings
|
||||
|> .excludedFolders
|
||||
|> Expect.equal list
|
||||
|
||||
Err _ ->
|
||||
Expect.pass
|
||||
]
|
@ -5,6 +5,8 @@ import Elm.Syntax.Expression as Expression exposing (Expression)
|
||||
import Elm.Syntax.Import exposing (Import)
|
||||
import Elm.Syntax.Node as Node exposing (Node(..))
|
||||
import Elm.Syntax.Pattern exposing (Pattern)
|
||||
import Review.FilePattern as FilePattern
|
||||
import Review.Project as Project exposing (Project)
|
||||
import Review.Rule as Rule exposing (Error, Rule)
|
||||
import Review.Test
|
||||
import Test exposing (Test, test)
|
||||
@ -31,28 +33,30 @@ all =
|
||||
|> Rule.withElmJsonModuleVisitor (\_ context -> context ++ "\n1.2 - withElmJsonModuleVisitor")
|
||||
|> Rule.withReadmeModuleVisitor (\_ context -> context ++ "\n2.1 - withReadmeModuleVisitor")
|
||||
|> Rule.withReadmeModuleVisitor (\_ context -> context ++ "\n2.2 - withReadmeModuleVisitor")
|
||||
|> Rule.withDirectDependenciesModuleVisitor (\_ context -> context ++ "\n3.1 - withDirectDependenciesModuleVisitor")
|
||||
|> Rule.withDirectDependenciesModuleVisitor (\_ context -> context ++ "\n3.2 - withDirectDependenciesModuleVisitor")
|
||||
|> Rule.withDependenciesModuleVisitor (\_ context -> context ++ "\n3.3 - withDependenciesModuleVisitor")
|
||||
|> Rule.withDependenciesModuleVisitor (\_ context -> context ++ "\n3.4 - withDependenciesModuleVisitor")
|
||||
|> Rule.withModuleDefinitionVisitor (\_ context -> ( [], context ++ "\n4.1 - withModuleDefinitionVisitor" ))
|
||||
|> Rule.withModuleDefinitionVisitor (\_ context -> ( [], context ++ "\n4.2 - withModuleDefinitionVisitor" ))
|
||||
|> Rule.withModuleDocumentationVisitor (\_ context -> ( [], context ++ "\n5.1 - withModuleDocumentationVisitor" ))
|
||||
|> Rule.withModuleDocumentationVisitor (\_ context -> ( [], context ++ "\n5.2 - withModuleDocumentationVisitor" ))
|
||||
|> Rule.withCommentsVisitor (\_ context -> ( [], context ++ "\n6.1 - withCommentsVisitor" ))
|
||||
|> Rule.withCommentsVisitor (\_ context -> ( [], context ++ "\n6.2 - withCommentsVisitor" ))
|
||||
|> Rule.withImportVisitor (\import_ context -> ( [], context ++ "\n7.1 - withImportVisitor " ++ importName import_ ))
|
||||
|> Rule.withImportVisitor (\import_ context -> ( [], context ++ "\n7.2 - withImportVisitor " ++ importName import_ ))
|
||||
|> Rule.withDeclarationListVisitor (\_ context -> ( [], context ++ "\n8.1 - withDeclarationListVisitor" ))
|
||||
|> Rule.withDeclarationListVisitor (\_ context -> ( [], context ++ "\n8.2 - withDeclarationListVisitor" ))
|
||||
|> Rule.withDeclarationEnterVisitor (\_ context -> ( [], context ++ "\n9.1 - withDeclarationEnterVisitor" ))
|
||||
|> Rule.withDeclarationEnterVisitor (\_ context -> ( [], context ++ "\n9.2 - withDeclarationEnterVisitor" ))
|
||||
|> Rule.withDeclarationExitVisitor (\_ context -> ( [], context ++ "\n12.2 - withDeclarationExitVisitor" ))
|
||||
|> Rule.withDeclarationExitVisitor (\_ context -> ( [], context ++ "\n12.1 - withDeclarationExitVisitor" ))
|
||||
|> Rule.withExpressionEnterVisitor (\_ context -> ( [], context ++ "\n10.1 - withExpressionEnterVisitor" ))
|
||||
|> Rule.withExpressionEnterVisitor (\_ context -> ( [], context ++ "\n10.2 - withExpressionEnterVisitor" ))
|
||||
|> Rule.withExpressionExitVisitor (\_ context -> ( [], context ++ "\n11.2 - withExpressionExitVisitor" ))
|
||||
|> Rule.withExpressionExitVisitor (\_ context -> ( [], context ++ "\n11.1 - withExpressionExitVisitor" ))
|
||||
|> Rule.withExtraFilesModuleVisitor [ FilePattern.include "first.txt" ] (\files context -> context ++ "\n3.1 - withExtraFilesModuleVisitor " ++ (List.map .path files |> String.join ", "))
|
||||
|> Rule.withExtraFilesModuleVisitor [ FilePattern.include "last.txt" ] (\files context -> context ++ "\n3.2 - withExtraFilesModuleVisitor " ++ (List.map .path files |> String.join ", "))
|
||||
|> Rule.withDirectDependenciesModuleVisitor (\_ context -> context ++ "\n4.1 - withDirectDependenciesModuleVisitor")
|
||||
|> Rule.withDirectDependenciesModuleVisitor (\_ context -> context ++ "\n4.2 - withDirectDependenciesModuleVisitor")
|
||||
|> Rule.withDependenciesModuleVisitor (\_ context -> context ++ "\n4.3 - withDependenciesModuleVisitor")
|
||||
|> Rule.withDependenciesModuleVisitor (\_ context -> context ++ "\n4.4 - withDependenciesModuleVisitor")
|
||||
|> Rule.withModuleDefinitionVisitor (\_ context -> ( [], context ++ "\n5.1 - withModuleDefinitionVisitor" ))
|
||||
|> Rule.withModuleDefinitionVisitor (\_ context -> ( [], context ++ "\n5.2 - withModuleDefinitionVisitor" ))
|
||||
|> Rule.withModuleDocumentationVisitor (\_ context -> ( [], context ++ "\n6.1 - withModuleDocumentationVisitor" ))
|
||||
|> Rule.withModuleDocumentationVisitor (\_ context -> ( [], context ++ "\n6.2 - withModuleDocumentationVisitor" ))
|
||||
|> Rule.withCommentsVisitor (\_ context -> ( [], context ++ "\n7.1 - withCommentsVisitor" ))
|
||||
|> Rule.withCommentsVisitor (\_ context -> ( [], context ++ "\n7.2 - withCommentsVisitor" ))
|
||||
|> Rule.withImportVisitor (\import_ context -> ( [], context ++ "\n8.1 - withImportVisitor " ++ importName import_ ))
|
||||
|> Rule.withImportVisitor (\import_ context -> ( [], context ++ "\n8.2 - withImportVisitor " ++ importName import_ ))
|
||||
|> Rule.withDeclarationListVisitor (\_ context -> ( [], context ++ "\n9.1 - withDeclarationListVisitor" ))
|
||||
|> Rule.withDeclarationListVisitor (\_ context -> ( [], context ++ "\n9.2 - withDeclarationListVisitor" ))
|
||||
|> Rule.withDeclarationEnterVisitor (\_ context -> ( [], context ++ "\n10.1 - withDeclarationEnterVisitor" ))
|
||||
|> Rule.withDeclarationEnterVisitor (\_ context -> ( [], context ++ "\n10.2 - withDeclarationEnterVisitor" ))
|
||||
|> Rule.withDeclarationExitVisitor (\_ context -> ( [], context ++ "\n13.2 - withDeclarationExitVisitor" ))
|
||||
|> Rule.withDeclarationExitVisitor (\_ context -> ( [], context ++ "\n13.1 - withDeclarationExitVisitor" ))
|
||||
|> Rule.withExpressionEnterVisitor (\_ context -> ( [], context ++ "\n11.1 - withExpressionEnterVisitor" ))
|
||||
|> Rule.withExpressionEnterVisitor (\_ context -> ( [], context ++ "\n11.2 - withExpressionEnterVisitor" ))
|
||||
|> Rule.withExpressionExitVisitor (\_ context -> ( [], context ++ "\n12.2 - withExpressionExitVisitor" ))
|
||||
|> Rule.withExpressionExitVisitor (\_ context -> ( [], context ++ "\n12.1 - withExpressionExitVisitor" ))
|
||||
|> Rule.withFinalModuleEvaluation finalEvaluation
|
||||
|> Rule.fromModuleRuleSchema
|
||||
|
||||
@ -63,13 +67,18 @@ all =
|
||||
, end = { row = 1, column = 7 }
|
||||
}
|
||||
]
|
||||
|
||||
project : Project
|
||||
project =
|
||||
Project.new
|
||||
|> Project.addExtraFiles [ { path = "first.txt", content = "" }, { path = "last.txt", content = "" } ]
|
||||
in
|
||||
"""module A exposing (..)
|
||||
import B
|
||||
import C
|
||||
a = 1
|
||||
"""
|
||||
|> Review.Test.run rule
|
||||
|> Review.Test.runWithProjectData project rule
|
||||
|> Review.Test.expectErrors
|
||||
[ Review.Test.error
|
||||
{ message = """
|
||||
@ -78,30 +87,32 @@ a = 1
|
||||
1.2 - withElmJsonModuleVisitor
|
||||
2.1 - withReadmeModuleVisitor
|
||||
2.2 - withReadmeModuleVisitor
|
||||
3.1 - withDirectDependenciesModuleVisitor
|
||||
3.2 - withDirectDependenciesModuleVisitor
|
||||
3.3 - withDependenciesModuleVisitor
|
||||
3.4 - withDependenciesModuleVisitor
|
||||
4.1 - withModuleDefinitionVisitor
|
||||
4.2 - withModuleDefinitionVisitor
|
||||
5.1 - withModuleDocumentationVisitor
|
||||
5.2 - withModuleDocumentationVisitor
|
||||
6.1 - withCommentsVisitor
|
||||
6.2 - withCommentsVisitor
|
||||
7.1 - withImportVisitor B
|
||||
7.2 - withImportVisitor B
|
||||
7.1 - withImportVisitor C
|
||||
7.2 - withImportVisitor C
|
||||
8.1 - withDeclarationListVisitor
|
||||
8.2 - withDeclarationListVisitor
|
||||
9.1 - withDeclarationEnterVisitor
|
||||
9.2 - withDeclarationEnterVisitor
|
||||
10.1 - withExpressionEnterVisitor
|
||||
10.2 - withExpressionEnterVisitor
|
||||
11.1 - withExpressionExitVisitor
|
||||
11.2 - withExpressionExitVisitor
|
||||
12.1 - withDeclarationExitVisitor
|
||||
12.2 - withDeclarationExitVisitor"""
|
||||
3.1 - withExtraFilesModuleVisitor first.txt
|
||||
3.2 - withExtraFilesModuleVisitor last.txt
|
||||
4.1 - withDirectDependenciesModuleVisitor
|
||||
4.2 - withDirectDependenciesModuleVisitor
|
||||
4.3 - withDependenciesModuleVisitor
|
||||
4.4 - withDependenciesModuleVisitor
|
||||
5.1 - withModuleDefinitionVisitor
|
||||
5.2 - withModuleDefinitionVisitor
|
||||
6.1 - withModuleDocumentationVisitor
|
||||
6.2 - withModuleDocumentationVisitor
|
||||
7.1 - withCommentsVisitor
|
||||
7.2 - withCommentsVisitor
|
||||
8.1 - withImportVisitor B
|
||||
8.2 - withImportVisitor B
|
||||
8.1 - withImportVisitor C
|
||||
8.2 - withImportVisitor C
|
||||
9.1 - withDeclarationListVisitor
|
||||
9.2 - withDeclarationListVisitor
|
||||
10.1 - withDeclarationEnterVisitor
|
||||
10.2 - withDeclarationEnterVisitor
|
||||
11.1 - withExpressionEnterVisitor
|
||||
11.2 - withExpressionEnterVisitor
|
||||
12.1 - withExpressionExitVisitor
|
||||
12.2 - withExpressionExitVisitor
|
||||
13.1 - withDeclarationExitVisitor
|
||||
13.2 - withDeclarationExitVisitor"""
|
||||
, details = [ "details" ]
|
||||
, under = "module"
|
||||
}
|
||||
|
141
tests/Review/Rule/WithExtraFilesVisitorTest.elm
Normal file
141
tests/Review/Rule/WithExtraFilesVisitorTest.elm
Normal file
@ -0,0 +1,141 @@
|
||||
module Review.Rule.WithExtraFilesVisitorTest exposing (all)
|
||||
|
||||
import Review.FilePattern as FilePattern exposing (FilePattern)
|
||||
import Review.Project as Project exposing (Project)
|
||||
import Review.Rule as Rule exposing (Error, Rule)
|
||||
import Review.Test
|
||||
import Test exposing (Test, describe, test)
|
||||
|
||||
|
||||
all : Test
|
||||
all =
|
||||
describe "Review.Rule.withExtraFilesVisitor"
|
||||
[ test "passes the list of arbitrary files to the rule" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
createProject
|
||||
[ { path = "foo/some-file.css", content = "#thing { color: red; }" }
|
||||
]
|
||||
|
||||
rule : Rule
|
||||
rule =
|
||||
createRule (Rule.withExtraFilesModuleVisitor [ FilePattern.include "foo/some-file.css" ] extraFilesModuleVisitor)
|
||||
in
|
||||
"""module A exposing (a)
|
||||
a = 1
|
||||
"""
|
||||
|> Review.Test.runWithProjectData project rule
|
||||
|> Review.Test.expectGlobalErrors
|
||||
[ { message = "Found these files"
|
||||
, details = [ "foo/some-file.css" ]
|
||||
}
|
||||
]
|
||||
, test "filters out files that were not requested" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
createProject
|
||||
[ { path = "foo/some-file.css", content = "#thing { color: red; }" }
|
||||
, { path = "foo/some-other-file.css", content = "#thing { color: red; }" }
|
||||
, { path = "bar/some-file.css", content = "#thing { color: red; }" }
|
||||
]
|
||||
|
||||
rule : Rule
|
||||
rule =
|
||||
createRule (Rule.withExtraFilesModuleVisitor [ FilePattern.include "foo/some-file.css" ] extraFilesModuleVisitor)
|
||||
in
|
||||
"""module A exposing (a)
|
||||
a = 1
|
||||
"""
|
||||
|> Review.Test.runWithProjectData project rule
|
||||
|> Review.Test.expectGlobalErrors
|
||||
[ { message = "Found these files"
|
||||
, details = [ "foo/some-file.css" ]
|
||||
}
|
||||
]
|
||||
, test "visitors should only have access to files they requested" <|
|
||||
\() ->
|
||||
let
|
||||
project : Project
|
||||
project =
|
||||
createProject
|
||||
[ { path = "a.txt", content = "A" }
|
||||
, { path = "b.txt", content = "B" }
|
||||
, { path = "c.txt", content = "C" }
|
||||
]
|
||||
|
||||
rule : Rule
|
||||
rule =
|
||||
createRule
|
||||
(Rule.withExtraFilesModuleVisitor [ FilePattern.include "a.txt", FilePattern.include "c.txt" ] (reportsFileNames "A")
|
||||
>> Rule.withExtraFilesModuleVisitor [ FilePattern.include "b.txt" ] (reportsFileNames "B")
|
||||
)
|
||||
in
|
||||
"""module A exposing (a)
|
||||
a = 1
|
||||
"""
|
||||
|> Review.Test.runWithProjectData project rule
|
||||
|> Review.Test.expectGlobalErrors
|
||||
[ { message = "Found these files"
|
||||
, details =
|
||||
[ "Visitor B saw file b.txt"
|
||||
, "Visitor A saw file a.txt"
|
||||
, "Visitor A saw file c.txt"
|
||||
]
|
||||
}
|
||||
]
|
||||
, test "Requesting invalid globs should result in a configuration error" <|
|
||||
\() ->
|
||||
createRule
|
||||
(Rule.withExtraFilesModuleVisitor
|
||||
[ FilePattern.include "** " ]
|
||||
(reportsFileNames "A")
|
||||
)
|
||||
|> Review.Test.expectConfigurationError
|
||||
{ message = "Invalid globs provided when requesting extra files"
|
||||
, details =
|
||||
[ "This rule requested additional files, but did so by specifying globs that I could not make sense of:"
|
||||
, " 1. ** "
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
type alias Context =
|
||||
List String
|
||||
|
||||
|
||||
createRule : (Rule.ModuleRuleSchema { canCollectProjectData : () } (List a) -> Rule.ModuleRuleSchema schemaState Context) -> Rule
|
||||
createRule modifier =
|
||||
Rule.newModuleRuleSchema "WithCommentsVisitorTestRule" []
|
||||
|> modifier
|
||||
|> Rule.withModuleDefinitionVisitor (\_ context -> ( [], context ))
|
||||
|> Rule.withFinalModuleEvaluation finalEvaluation
|
||||
|> Rule.fromModuleRuleSchema
|
||||
|
||||
|
||||
extraFilesModuleVisitor : List { path : String, content : String } -> Context -> Context
|
||||
extraFilesModuleVisitor files context =
|
||||
List.map .path files ++ context
|
||||
|
||||
|
||||
reportsFileNames : String -> List { path : String, content : String } -> Context -> Context
|
||||
reportsFileNames prefix files context =
|
||||
List.map (\file -> "Visitor " ++ prefix ++ " saw file " ++ file.path) files ++ context
|
||||
|
||||
|
||||
finalEvaluation : Context -> List (Error scope)
|
||||
finalEvaluation context =
|
||||
[ Rule.globalError
|
||||
{ message = "Found these files"
|
||||
, details = context
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
createProject : List { path : String, content : String } -> Project
|
||||
createProject extraFiles =
|
||||
Project.addExtraFiles extraFiles Project.new
|
Loading…
Reference in New Issue
Block a user