Compare commits

...

145 Commits

Author SHA1 Message Date
Jeroen Engels
4ad1dcfcdd Rename string to pattern 2024-04-07 23:23:01 +02:00
Jeroen Engels
45842b2234 Use FilePattern when requesting files 2024-04-07 22:54:21 +02:00
Jeroen Engels
038819ae49 Remove TODOs 2024-04-07 22:30:41 +02:00
Jeroen Engels
04b3b9fa2a Remove parsing inside constructors 2024-04-07 22:29:36 +02:00
Jeroen Engels
4129685142 Copy parsing inside compact 2024-04-07 22:28:29 +02:00
Jeroen Engels
a3413facb2 Rename 2024-04-07 22:22:53 +02:00
Jeroen Engels
701eac7b95 Reverse lists at the correct location 2024-04-07 22:21:57 +02:00
Jeroen Engels
f9f0f1b0b5 Add fuzz test for folders 2024-04-07 22:18:49 +02:00
Jeroen Engels
98f08aa62a Format 2024-04-07 22:15:06 +02:00
Jeroen Engels
a70dae14d7 Fix toStrings 2024-04-07 22:14:55 +02:00
Jeroen Engels
e2eb764566 Specify fuzz test 2024-04-07 22:11:48 +02:00
Jeroen Engels
2d073c9317 describe 2024-04-07 20:04:56 +02:00
Jeroen Engels
be560d38be Make a record 2024-04-07 20:03:58 +02:00
Jeroen Engels
847decf1b4 Add excludeFoldersStrings 2024-04-07 20:00:54 +02:00
Jeroen Engels
67e94e484a Collect strings 2024-04-07 19:37:39 +02:00
Jeroen Engels
bdf9173cd0 fixup! WIP on a: a9eef342 Add fuzz test 2024-04-07 19:32:36 +02:00
Jeroen Engels
d18a7452c8 Include raw string 2024-04-07 19:32:14 +02:00
Jeroen Engels
2e92713315 Use a boolean 2024-04-07 19:29:50 +02:00
Jeroen Engels
a9eef34293 Add fuzz test 2024-04-07 19:27:48 +02:00
Jeroen Engels
2a0cc68767 Merge functions 2024-04-07 19:14:40 +02:00
Jeroen Engels
d620dde379 Rename 2024-04-07 19:02:13 +02:00
Jeroen Engels
8b7662cdfc Change record field order 2024-04-07 19:01:18 +02:00
Jeroen Engels
081aefae99 Rename 2024-04-07 18:59:37 +02:00
Jeroen Engels
348e70e256 Add missing dependencies 2024-04-07 18:13:26 +02:00
Jeroen Engels
47f662db3e Remove unused import 2024-04-07 17:46:58 +02:00
Jeroen Engels
955d2837de Cleanup 2024-04-07 17:34:49 +02:00
Jeroen Engels
d1ef356e3f Remove Debug.log 2024-04-07 17:27:34 +02:00
Jeroen Engels
56c17ba5fa Publicly expose Review.FilePattern 2024-04-07 17:27:00 +02:00
Jeroen Engels
3b62c5d831 Compact globs 2024-04-07 15:27:44 +02:00
Jeroen Engels
113cf7f061 Add TODOs 2024-04-07 09:50:26 +02:00
Jeroen Engels
abb2dce7a7 Support excluding folders 2024-04-07 09:46:18 +02:00
Jeroen Engels
56e75c931f Test 2024-04-07 09:40:17 +02:00
Jeroen Engels
7a230f309e Use an accumulator 2024-04-07 09:39:44 +02:00
Jeroen Engels
620abd615e Make recursive 2024-04-07 09:37:27 +02:00
Jeroen Engels
ca62e5159f Add tests 2024-04-07 09:37:06 +02:00
Jeroen Engels
cc6847f48b Support Include 2024-04-07 09:32:26 +02:00
Jeroen Engels
3d045ddf8a Turn patterns into globs 2024-04-07 09:32:18 +02:00
Jeroen Engels
078e2d0414 Add tests 2024-04-07 09:23:43 +02:00
Jeroen Engels
4245fc1fb0 Add match function 2024-04-06 10:49:03 +02:00
Jeroen Engels
aeec25f60a Rename ExtraFiles to FilePattern 2024-04-06 10:45:09 +02:00
Jeroen Engels
d6aed416fb Add Review.ExtraFiles module 2024-02-24 18:35:56 +01:00
Jeroen Engels
9f34b4cba7 Report configuration error 2024-02-23 23:14:09 +01:00
Jeroen Engels
5d64d1073e fixup! Add tests for globToRegexString 2024-02-23 21:50:21 +01:00
Jeroen Engels
2e82646094 Report a configuration error when glob is invalid 2024-02-23 21:50:21 +01:00
Jeroen Engels
b9f0160325 Use globs for matching 2024-02-23 21:50:21 +01:00
Jeroen Engels
0f787768a1 Vendor Glob 2024-02-20 22:36:47 +01:00
Jeroen Engels
c155011766 Add placeholder for documentation 2023-07-25 23:59:45 +02:00
Jeroen Engels
83c8b17f60 DEBUG AUTRE CHOSE 2023-06-18 17:37:41 +02:00
Jeroen Engels
79b56c6066 Fix confusing test 2023-06-17 19:59:24 +02:00
Jeroen Engels
05fb48fcca Add autofix to inject a new version 2023-06-17 19:59:24 +02:00
Jeroen Engels
500923da91 Inline findUnreleasedRange 2023-06-17 19:59:24 +02:00
Jeroen Engels
ce66fafdfe Add case expression 2023-06-17 19:59:24 +02:00
Jeroen Engels
c78297f483 Use withFix 2023-06-17 19:59:24 +02:00
Jeroen Engels
aa48c3df63 Extract variable 2023-06-17 19:59:24 +02:00
Jeroen Engels
ca33d96ef2 Report the first line when unreleased could not be found 2023-06-17 19:59:24 +02:00
Jeroen Engels
d16f175dd0 Report the line for Unreleased 2023-06-17 19:59:24 +02:00
Jeroen Engels
04ad09644d Add findUnreleasedRange 2023-06-17 19:59:24 +02:00
Jeroen Engels
3538b75cac Extract to function 2023-06-17 19:59:24 +02:00
Jeroen Engels
a549883b89 Expect to be under Unreleased 2023-06-17 19:59:24 +02:00
Jeroen Engels
e0fc0df147 Add [Unreleased] to tests 2023-06-17 19:59:24 +02:00
Jeroen Engels
df6cb154ff Update range 2023-06-17 19:59:24 +02:00
Jeroen Engels
3024910771 Target changelog in tests 2023-06-17 19:59:24 +02:00
Jeroen Engels
8752f2316f Add Review.Project.extraFiles 2023-06-17 19:59:24 +02:00
Jeroen Engels
f92ee4b901 Report an error for the changelog file 2023-06-17 19:59:24 +02:00
Jeroen Engels
45774ce35b fixup! Add visitor for the changelog 2023-06-17 19:59:24 +02:00
Jeroen Engels
608855d1e0 Don't report errors if the version is 1.0.0 2023-06-17 19:59:24 +02:00
Jeroen Engels
14227c4b35 Pass a version when creating a package in tests 2023-06-17 19:59:24 +02:00
Jeroen Engels
676742d633 Report an error if the project is an application 2023-06-17 19:59:24 +02:00
Jeroen Engels
91bfcb1baf Report a global error when the path was custom but could not be found 2023-06-17 19:59:24 +02:00
Jeroen Engels
b29d9f3fef Report a global error if the changelog could not be found 2023-06-17 19:59:24 +02:00
Jeroen Engels
eecf1e4f24 Add the ability to configure the path 2023-06-17 19:59:24 +02:00
Jeroen Engels
167e5f15a1 Enable Docs.NoMissingChangelogEntry 2023-06-17 19:59:24 +02:00
Jeroen Engels
2cb805bf91 Report an error when the version could not be found 2023-06-17 19:59:24 +02:00
Jeroen Engels
da70f8c64f Add visitor for the changelog 2023-06-17 19:59:24 +02:00
Jeroen Engels
5322a8a5d1 Add elm.json visitor to get the version 2023-06-17 19:59:24 +02:00
Jeroen Engels
64554ef45f Add tests 2023-06-17 19:59:24 +02:00
Jeroen Engels
04f9a1527b Add new rule Docs.NoMissingChangelogEntry 2023-06-17 19:59:24 +02:00
Jeroen Engels
d01d870fc0 DEBUG CHANGELOG 2023-06-17 19:59:23 +02:00
Jeroen Engels
3c1107df56 Apply fixes for extra files 2023-06-17 19:57:56 +02:00
Jeroen Engels
b00b58cf1e Support applying a fix to extra files 2023-06-17 19:57:56 +02:00
Jeroen Engels
368ce17a56 Inline tuple 2023-06-17 19:57:56 +02:00
Jeroen Engels
d377aeb436 Add Valid.addExtraFile 2023-06-17 19:57:56 +02:00
Jeroen Engels
fa7f8c67af Explicit all cases 2023-06-17 19:57:56 +02:00
Jeroen Engels
ba699a7a86 Add TODO 2023-06-17 19:57:56 +02:00
Jeroen Engels
46f0746234 Find extra files in tests 2023-06-17 19:57:56 +02:00
Jeroen Engels
ee05040efb Add TODO 2023-06-17 19:57:56 +02:00
Jeroen Engels
56c734163f Add functions to report errors for extra files 2023-06-17 19:57:56 +02:00
Jeroen Engels
acb6cce062 Add ExtraFile target 2023-06-17 19:57:56 +02:00
Jeroen Engels
095abbcb4f Add a FileKey for extra files 2023-06-17 19:57:56 +02:00
Jeroen Engels
ac9fefd13e Add test for the visiting order of extra files 2023-06-17 19:57:56 +02:00
Jeroen Engels
e809ae75ba Inline toRuleProjectVisitor 2023-06-17 19:57:56 +02:00
Jeroen Engels
9fa185ebbc Inline constant argument 2023-06-17 19:57:56 +02:00
Jeroen Engels
8886306ef5 Pass raise 2023-06-17 19:57:56 +02:00
Jeroen Engels
06760b9076 Inline constant argument 2023-06-17 19:57:56 +02:00
Jeroen Engels
174e586fa5 Inline constant argument 2023-06-17 19:57:56 +02:00
Jeroen Engels
4abbc530e8 fixup! Visit extra files after readme 2023-06-17 19:57:56 +02:00
Jeroen Engels
284cdb6c3d Visit extra files after readme 2023-06-17 19:57:56 +02:00
Jeroen Engels
125e28c710 Style: Move code related to extra files after the ones for readme 2023-06-17 19:57:56 +02:00
Jeroen Engels
201b93311a Name things extra files instead of arbitrary files 2023-06-17 19:57:56 +02:00
Jeroen Engels
6614d677b6 Add test to make sure files only have access to the files they requested 2023-06-17 19:57:56 +02:00
Jeroen Engels
7935afd767 Add the visitor in each test 2023-06-17 19:57:56 +02:00
Jeroen Engels
a6f7aa7a71 Add helper function for creating project in tests 2023-06-17 19:57:56 +02:00
Jeroen Engels
1e41edb186 Add TODO 2023-06-17 19:57:56 +02:00
Jeroen Engels
6f86b12a6d Remove tuple for arbitrary files visitor 2023-06-17 19:57:56 +02:00
Jeroen Engels
e9285810df Set arbitraryFileRequest in module rule schema 2023-06-17 19:57:56 +02:00
Jeroen Engels
7460c22e1a Remove excess filtering 2023-06-17 19:57:56 +02:00
Jeroen Engels
a64f4fe18d Filter files in withArbitraryFilesProjectVisitor 2023-06-17 19:57:56 +02:00
Jeroen Engels
40e619410f Filter files in withArbitraryFilesModuleVisitor 2023-06-17 19:57:56 +02:00
Jeroen Engels
eee733e68f Rename variables 2023-06-17 19:57:56 +02:00
Jeroen Engels
12e0702ed6 Add arbitraryFileRequest to ModuleRuleVisitor 2023-06-17 19:57:56 +02:00
Jeroen Engels
7dcd85648d Extract function 2023-06-17 19:57:56 +02:00
Jeroen Engels
ddae5608cc Add withArbitraryFilesProjectVisitor 2023-06-17 19:57:56 +02:00
Jeroen Engels
0f472777dd Fix applying fixes in the arbitrary files visitor 2023-06-17 19:57:56 +02:00
Jeroen Engels
331b7b1429 Get the errors from the cache 2023-06-17 19:57:56 +02:00
Jeroen Engels
a98bc1a64d Fix the caching for the arbitrary files visitor
It was still using the cache of the readme visitor
2023-06-17 19:57:56 +02:00
Jeroen Engels
94c5571e40 Add ValidProject.arbitraryFilesHash 2023-06-17 19:57:56 +02:00
Jeroen Engels
45018398dd Add Rule.ruleRequestedFiles 2023-06-17 19:57:56 +02:00
Jeroen Engels
71541bb3b2 Filter out files that were not requested 2023-06-17 19:57:56 +02:00
Jeroen Engels
ce3442ddfe Remove unused import 2023-06-17 19:57:56 +02:00
Jeroen Engels
625bbb5f14 Use exact matches for now in tests 2023-06-17 19:57:56 +02:00
Jeroen Engels
a628db975f Add files to RequestedData 2023-06-17 19:57:56 +02:00
Jeroen Engels
0dc931abd5 Add arbitraryFileRequest to ProjectRuleSchema 2023-06-17 19:57:56 +02:00
Jeroen Engels
ff4469ace9 Extract to function 2023-06-17 19:57:56 +02:00
Jeroen Engels
57118354be Add a requested files field 2023-06-17 19:57:56 +02:00
Jeroen Engels
9c1984c218 Add files to RequestedData 2023-06-17 19:57:56 +02:00
Jeroen Engels
080506814a Add test for arbitrary files visitor 2023-06-17 19:57:56 +02:00
Jeroen Engels
f05deb520c Add withArbitraryFilesModuleVisitor 2023-06-17 19:57:56 +02:00
Jeroen Engels
a6298e737d Create arbitraryFilesVisitor from module rule 2023-06-17 19:57:56 +02:00
Jeroen Engels
993944a2ba Add arbitraryFilesVisitor to ModuleRuleSchema 2023-06-17 19:57:56 +02:00
Jeroen Engels
330352a269 Add arbitrary files visitor 2023-06-17 19:57:56 +02:00
Jeroen Engels
1a22611506 Add arbitraryFilesVisitor to RuleProjectVisitorOperations 2023-06-17 19:57:56 +02:00
Jeroen Engels
b7a7543b07 Add an ArbitraryFile step 2023-06-17 19:57:56 +02:00
Jeroen Engels
b025706c85 Add function to get the initial cache 2023-06-17 19:57:56 +02:00
Jeroen Engels
c160d6ee34 Add arbitraryFilesVisitor to ProjectRuleSchema 2023-06-17 19:57:56 +02:00
Jeroen Engels
9e7e51768e Add arbitraryFiles in ProjectRuleCache 2023-06-17 19:57:56 +02:00
Jeroen Engels
c4aef02f8f Add ArbitraryFilesCache 2023-06-17 19:57:56 +02:00
Jeroen Engels
c1b7f06790 Add Review.Cache.ArbitraryFile 2023-06-17 19:57:56 +02:00
Jeroen Engels
b905d93b47 Add ContentHash.areEqualForList 2023-06-17 19:57:56 +02:00
Jeroen Engels
f3e9a05134 Add ValidProject.arbitraryFiles 2023-06-17 19:57:56 +02:00
Jeroen Engels
4a1e6673d2 Expose addArbitraryFiles 2023-06-17 19:57:56 +02:00
Jeroen Engels
3673dabbf3 Add a function to add arbitrary files 2023-06-17 19:57:56 +02:00
Jeroen Engels
218973ef70 Add arbitraryFiles field 2023-06-17 19:57:56 +02:00
Jeroen Engels
267d2038ba 2.13.1 2023-06-17 19:52:13 +02:00
Jeroen Engels
929d8508fd Fix let function arguments not registering with the correct module name 2023-06-17 19:49:41 +02:00
Jeroen Engels
84aae3b64b Make test setup failure more helpful 2023-06-17 19:49:41 +02:00
24 changed files with 2141 additions and 137 deletions

View File

@ -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

View File

@ -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.

View File

@ -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"
}
}
}

View File

@ -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
View 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 == '?')

View File

@ -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

View 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

View File

@ -13,6 +13,7 @@ type Target
= Module
| ElmJson
| Readme
| ExtraFile
| Global
| UserGlobal

238
src/Review/FilePattern.elm Normal file
View 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

View File

@ -212,6 +212,12 @@ fix target fixes sourceCode =
sourceCode
(always True)
Error.ExtraFile ->
tryToApplyFix
fixes
sourceCode
(always True)
Error.ElmJson ->
tryToApplyFix
fixes

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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 ) }

View File

@ -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
}

View File

@ -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 =

View File

@ -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)

View 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

View 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": {}
}
}"""

View File

@ -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

View 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
]

View File

@ -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"
}

View 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