@@ -11,8 +11,8 @@ import Data.Traversable (traverse)
1111import Effect (Effect)
1212import Effect.Class.Console (log)
1313import Purelint.Fix (applyAllFixes)
14โimport Purelint.Tidy (formatExpr)
15โimport Purelint.Rules.FmapId (fmapIdRule)
14+import Purelint.Tidy (formatExpr, formatModule)
15+import Purelint.Rules.MapIdentity (mapIdentityRule)
1616import Purelint.Rules.MapFusion (mapFusionRule)
1717import Purelint.Rules.UseTraverse (useTraverseRule)
1818import Purelint.Rules.ConcatMap (concatMapRule)
@@ -22,6 +22,7 @@ import Purelint.Rules.EtaReduce (etaReduceRule)
2222import Purelint.Rules.EtaReduceDecl (etaReduceDeclRule)
2323import Purelint.Rules.RedundantBind (redundantBindRule)
2424import Purelint.Rules.LetToWhere (letToWhereRule)
25+import Purelint.Rules.LetToDo (letToDoRule)
2526import Purelint.Rules.RedundantIf (redundantIfRule)
2627import Purelint.Rules.FunctorLaw (functorLawRule)
2728import Purelint.Rules.UseVoid (useVoidRule)
@@ -67,7 +68,9 @@ import Purelint.Rules.EvaluateFst (evaluateFstRule)
6768import Purelint.Rules.EvaluateConst (evaluateConstRule)
6869import Purelint.Rules.UseOr (useOrRule)
6970import Purelint.Rules.UseAnd (useAndRule)
71+import Purelint.Rules.UseFoldMap (useFoldMapRule)
7072import Purelint.Rules.UseFoldMapId (useFoldMapIdRule)
73+import Purelint.Rules.UseApplyFlipped (useApplyFlippedRule)
7174import Purelint.Rules.WhenNot (whenNotRule)
7275import Purelint.Rules.UnlessNot (unlessNotRule)
7376import Purelint.Rules.UseZip (useZipRule)
@@ -79,6 +82,14 @@ import Purelint.Rules.UseHead (useHeadRule)
7982import Purelint.Rules.RedundantId (redundantIdRule)
8083import Purelint.Rules.NothingBind (nothingBindRule)
8184import Purelint.Rules.UseElemIndex (useElemIndexRule)
85+import Purelint.Rules.MonadLaw (monadLawRule)
86+import Purelint.Rules.EvaluateBool (evaluateBoolRule)
87+import Purelint.Rules.UseFoldBool (useFoldBoolRule)
88+import Purelint.Rules.AlternativeLaw (alternativeLawRule)
89+import Purelint.Rules.EvaluateEither (evaluateEitherRule)
90+import Purelint.Rules.UseCaseOf (useCaseOfRule)
91+import Purelint.Rules.FlattenCase (flattenCaseRule)
92+import Purelint.Rules.UsePatternGuards (usePatternGuardsRule)
8293import Purelint.Rule (Rule)
8394import Purelint.Runner (runRules, getImportsFromSource, getModuleNames)
8495import Purelint.Imports (hasValue, isPreludeModule)
@@ -143,12 +154,27 @@ assertFix name rules source expectedFixed =
143154 then runTest name true "OK"
144155 else runTest name false ("Expected:\n" <> expectedFixed <> "\nGot:\n" <> fixed)
145156
157+-- | Assert that applying fixes produces expected output AND result is tidy-formatted
158+assertFixFormatted :: String -> Array Rule -> String -> String -> Effect TestResult
159+assertFixFormatted name rules source expectedFixed =
160+ case runRules rules (SourceCode source) of
161+ Left err -> pure $ runTest name false ("Parse error: " <> err)
162+ Right result -> do
163+ let fixed = applyAllFixes source result
164+ -- Check that the fix output is already tidy-formatted
165+ tidied <- formatModule fixed
166+ if tidied /= fixed
167+ then pure $ runTest name false ("Fix output not tidy-formatted.\nGot:\n" <> fixed <> "\nTidy expects:\n" <> tidied)
168+ else if fixed /= expectedFixed
169+ then pure $ runTest name false ("Expected:\n" <> expectedFixed <> "\nGot:\n" <> fixed)
170+ else pure $ runTest name true "OK"
171+
146172-- | Run all tests and report results
147173main :: Effect Unit
148174main = do
149175 log "Running purelint tests...\n"
150176
151โ let results = fmapIdTests
177+ let results = mapIdentityTests
152178 <> mapFusionTests
153179 <> useTraverseTests
154180 <> concatMapTests
@@ -198,6 +224,8 @@ main = do
198224 <> evaluateConstTests
199225 <> useOrTests
200226 <> useAndTests
227+ <> useFoldMapTests
228+ <> useApplyFlippedTests
201229 <> useFoldMapIdTests
202230 <> whenNotTests
203231 <> unlessNotTests
@@ -205,13 +233,24 @@ main = do
205233 <> redundantNegateTests
206234 <> redundantIdTests
207235 <> nothingBindTests
208โ <> goldenTests
236+ <> monadLawTests
237+ <> evaluateBoolTests
238+ <> useFoldBoolTests
239+ <> alternativeLawTests
240+ <> evaluateEitherTests
241+ <> useCaseOfTests
242+ <> flattenCaseTests
243+ <> usePatternGuardsTests
209244
210โ let passed = Array.length $ Array.filter _.passed results
211โ let total = Array.length results
245+ -- Run effectful golden tests (with purs-tidy check)
246+ goldenResults <- goldenTests
247+ let allResults = results <> goldenResults
248+
249+ let passed = Array.length $ Array.filter _.passed allResults
250+ let total = Array.length allResults
212251
213252 -- Report failures
214โ let failures = Array.filter (not <<< _.passed) results
253+ let failures = Array.filter (not <<< _.passed) allResults
215254 when (Array.length failures > 0) do
216255 log "FAILURES:"
217256 for_ failures (\r -> log (" โ " <> r.name <> ": " <> r.message))
@@ -239,42 +278,40 @@ main = do
239278
240279-- | Helper: wrap code in a module with Prelude import
241280withPrelude :: String -> String
242โwithPrelude code = "module Test where\nimport Prelude\nimport Data.Maybe\nimport Data.Foldable\nimport Data.Tuple\nimport Data.Monoid\nimport Data.Traversable\nimport Data.Array\nimport Control.Alternative\nimport Data.Ord\nimport Data.Either\nimport Data.Bifunctor\nimport Data.List\n" <> code
281+withPrelude code = "module Test where\n\nimport Prelude\nimport Data.Maybe\nimport Data.Foldable\nimport Data.Tuple\nimport Data.Monoid\nimport Data.Traversable\nimport Data.Array\nimport Control.Alternative\nimport Data.Ord\nimport Data.Either\nimport Data.Bifunctor\nimport Data.List\n\n" <> code
243282
244283-- | Helper: wrap code in module without imports
245284withoutImports :: String -> String
246285withoutImports code = "module Test where\n" <> code
247286
248287-- ============================================================================
249โ-- FmapId Tests
288+-- MapIdentity Tests
250289-- ============================================================================
251290
252โfmapIdTests :: Array TestResult
253โfmapIdTests =
291+mapIdentityTests :: Array TestResult
292+mapIdentityTests =
254293 -- Positive cases: should detect
255โ [ assertWarningCount "FmapId: map identity x" [fmapIdRule]
294+ [ assertWarningCount "MapIdentity: map identity x" [mapIdentityRule]
256295 (withPrelude "x = map identity [1]") 1
257โ , assertRuleId "FmapId: correct rule id" [fmapIdRule]
258โ (withPrelude "x = map identity [1]") "FmapId"
259โ , assertSuggestion "FmapId: map identity suggestion" [fmapIdRule]
260โ (withPrelude "x = map identity [1]") "[1]"
261โ , assertWarningCount "FmapId: fmap identity x" [fmapIdRule]
262โ (withPrelude "x = fmap identity [1]") 1
263โ , assertWarningCount "FmapId: identity <$> x" [fmapIdRule]
296+ , assertRuleId "MapIdentity: correct rule id" [mapIdentityRule]
297+ (withPrelude "x = map identity [1]") "MapIdentity"
298+ , assertSuggestion "MapIdentity: map identity suggestion" [mapIdentityRule]
299+ (withPrelude "x = map identity [1]") "[ 1 ]"
300+ , assertWarningCount "MapIdentity: identity <$> x" [mapIdentityRule]
264301 (withPrelude "x = identity <$> [1]") 1
265โ , assertWarningCount "FmapId: x <#> identity" [fmapIdRule]
302+ , assertWarningCount "MapIdentity: x <#> identity" [mapIdentityRule]
266303 (withPrelude "x = [1] <#> identity") 1
267304
268305 -- Negative cases: should NOT detect
269โ , assertWarningCount "FmapId: no fire on map show" [fmapIdRule]
306+ , assertWarningCount "MapIdentity: no fire on map show" [mapIdentityRule]
270307 (withPrelude "x = map show [1]") 0
271โ , assertWarningCount "FmapId: no fire without import" [fmapIdRule]
308+ , assertWarningCount "MapIdentity: no fire without import" [mapIdentityRule]
272309 (withoutImports "x = map identity [1]") 0
273310
274311 -- Auto-fix test
275โ , assertFix "FmapId: fix map identity" [fmapIdRule]
312+ , assertFix "MapIdentity: fix map identity" [mapIdentityRule]
276313 (withPrelude "x = map identity [1]")
277โ (withPrelude "x = [1]")
314+ (withPrelude "x = [ 1 ]")
278315 ]
279316
280317-- ============================================================================
@@ -440,7 +477,7 @@ redundantBindTests =
440477 , assertRuleId "RedundantBind: correct rule id" [redundantBindRule]
441478 (withPrelude "x = [1] >>= pure") "RedundantBind"
442479 , assertSuggestion "RedundantBind: x >>= pure suggestion" [redundantBindRule]
443โ (withPrelude "x = [1] >>= pure") "[1]"
480+ (withPrelude "x = [1] >>= pure") "[ 1 ]"
444481 , assertWarningCount "RedundantBind: pure x >>= f" [redundantBindRule]
445482 (withPrelude "x = pure 1 >>= show") 1
446483 , assertSuggestion "RedundantBind: pure x >>= f suggestion" [redundantBindRule]
@@ -469,13 +506,16 @@ letToWhereTests =
469506 , assertWarningCount "LetToWhere: multiple bindings" [letToWhereRule]
470507 (withPrelude "x = let a = 1\n b = 2 in a + b") 1
471508
472โ -- Suggestion preserves spaces
509+ -- Suggestion preserves spaces based on let column position
473510 , assertSuggestion "LetToWhere: preserves spaces in simple case" [letToWhereRule]
474โ (withPrelude "x = let y = 1 in y + 1") "y + 1\n where\n y = 1"
511+ (withPrelude "x = let y = 1 in y + 1") "y + 1\n where\n y = 1"
475512 , assertSuggestion "LetToWhere: preserves spaces in expressions" [letToWhereRule]
476โ (withPrelude "f x = let a = x + 1 in a * 2") "a * 2\n where\n a = x + 1"
513+ (withPrelude "f x = let a = x + 1 in a * 2") "a * 2\n where\n a = x + 1"
477514 , assertSuggestion "LetToWhere: preserves spaces with multiple bindings" [letToWhereRule]
478โ (withPrelude "f x = let a = x + 1\n b = x * 2 in a + b") "a + b\n where\n a = x + 1\n b = x * 2"
515+ (withPrelude "f x = let a = x + 1\n b = x * 2 in a + b") "a + b\n where\n a = x + 1\n b = x * 2"
516+ -- Nested let should preserve indentation context
517+ , assertSuggestion "LetToWhere: nested let preserves indentation" [letToWhereRule]
518+ (withPrelude "f x =\n let y = 1 in y + 1") "y + 1\n where\n y = 1"
479519 ]
480520
481521-- ============================================================================
@@ -578,7 +618,7 @@ useAnyTests =
578618 , assertRuleId "UseAny: correct rule id" [useAnyRule]
579619 (withPrelude "x = or (map f [1])") "UseAny"
580620 , assertSuggestion "UseAny: suggests any" [useAnyRule]
581โ (withPrelude "x = or (map f [1])") "any f [1]"
621+ (withPrelude "x = or (map f [1])") "any f [ 1 ]"
582622
583623 -- Negative cases
584624 , assertWarningCount "UseAny: no fire on or alone" [useAnyRule]
@@ -599,7 +639,7 @@ useAllTests =
599639 , assertRuleId "UseAll: correct rule id" [useAllRule]
600640 (withPrelude "x = and (map f [1])") "UseAll"
601641 , assertSuggestion "UseAll: suggests all" [useAllRule]
602โ (withPrelude "x = and (map f [1])") "all f [1]"
642+ (withPrelude "x = and (map f [1])") "all f [ 1 ]"
603643
604644 -- Negative cases
605645 , assertWarningCount "UseAll: no fire on and alone" [useAllRule]
@@ -689,7 +729,7 @@ useNullTests =
689729 , assertRuleId "UseNull: correct rule id" [useNullRule]
690730 (withPrelude "x = length [1] == 0") "UseNull"
691731 , assertSuggestion "UseNull: suggests null" [useNullRule]
692โ (withPrelude "x = length [1] == 0") "null [1]"
732+ (withPrelude "x = length [1] == 0") "null [ 1 ]"
693733 , assertWarningCount "UseNull: 0 == length x" [useNullRule]
694734 (withPrelude "x = 0 == length [1]") 1
695735
@@ -815,7 +855,7 @@ useNotElemTests =
815855 , assertRuleId "UseNotElem: correct rule id" [useNotElemRule]
816856 (withPrelude "x = not (elem 1 [1,2,3])") "UseNotElem"
817857 , assertSuggestion "UseNotElem: suggests notElem" [useNotElemRule]
818โ (withPrelude "x = not (elem 1 [1,2,3])") "notElem 1 [1,2,3]"
858+ (withPrelude "x = not (elem 1 [1,2,3])") "notElem 1 [ 1, 2, 3 ]"
819859
820860 -- Negative cases
821861 , assertWarningCount "UseNotElem: no fire on not (foo x)" [useNotElemRule]
@@ -855,7 +895,7 @@ monoidIdentityTests =
855895 , assertRuleId "MonoidIdentity: correct rule id" [monoidIdentityRule]
856896 (withPrelude "x = mempty <> [1,2,3]") "MonoidIdentity"
857897 , assertSuggestion "MonoidIdentity: suggests x" [monoidIdentityRule]
858โ (withPrelude "x = mempty <> [1,2,3]") "[1,2,3]"
898+ (withPrelude "x = mempty <> [1,2,3]") "[ 1, 2, 3 ]"
859899 , assertWarningCount "MonoidIdentity: x <> mempty" [monoidIdentityRule]
860900 (withPrelude "x = [1,2,3] <> mempty") 1
861901
@@ -911,7 +951,7 @@ useTraverseSequenceTests =
911951 , assertRuleId "UseTraverseSequence: correct rule id" [useTraverseSequenceRule]
912952 (withPrelude "x = sequence (map show [1,2,3])") "UseTraverseSequence"
913953 , assertSuggestion "UseTraverseSequence: suggests traverse" [useTraverseSequenceRule]
914โ (withPrelude "x = sequence (map show [1,2,3])") "traverse show [1,2,3]"
954+ (withPrelude "x = sequence (map show [1,2,3])") "traverse show [ 1, 2, 3 ]"
915955
916956 -- Negative cases
917957 , assertWarningCount "UseTraverseSequence: no fire on sequence xs" [useTraverseSequenceRule]
@@ -969,7 +1009,7 @@ useMapMaybeTests =
9691009 , assertRuleId "UseMapMaybe: correct rule id" [useMapMaybeRule]
9701010 (withPrelude "x = catMaybes (map f [1,2,3])") "UseMapMaybe"
9711011 , assertSuggestion "UseMapMaybe: suggests mapMaybe" [useMapMaybeRule]
972โ (withPrelude "x = catMaybes (map f [1,2,3])") "mapMaybe f [1,2,3]"
1012+ (withPrelude "x = catMaybes (map f [1,2,3])") "mapMaybe f [ 1, 2, 3 ]"
9731013 ]
9741014
9751015-- ============================================================================
@@ -1073,7 +1113,7 @@ useFindMapTests =
10731113 , assertRuleId "UseFindMap: correct rule id" [useFindMapRule]
10741114 (withPrelude "x = head (mapMaybe f [1,2,3])") "UseFindMap"
10751115 , assertSuggestion "UseFindMap: suggests findMap" [useFindMapRule]
1076โ (withPrelude "x = head (mapMaybe f [1,2,3])") "findMap f [1,2,3]"
1116+ (withPrelude "x = head (mapMaybe f [1,2,3])") "findMap f [ 1, 2, 3 ]"
10771117 ]
10781118
10791119-- ============================================================================
@@ -1087,7 +1127,7 @@ useLastReverseTests =
10871127 , assertRuleId "UseLastReverse: correct rule id" [useLastReverseRule]
10881128 (withPrelude "x = head (reverse [1,2,3])") "UseLastReverse"
10891129 , assertSuggestion "UseLastReverse: suggests last" [useLastReverseRule]
1090โ (withPrelude "x = head (reverse [1,2,3])") "last [1,2,3]"
1130+ (withPrelude "x = head (reverse [1,2,3])") "last [ 1, 2, 3 ]"
10911131 ]
10921132
10931133-- ============================================================================
@@ -1115,7 +1155,7 @@ useMinimumSortTests =
11151155 , assertRuleId "UseMinimumSort: correct rule id" [useMinimumSortRule]
11161156 (withPrelude "x = head (sort [1,2,3])") "UseMinimumSort"
11171157 , assertSuggestion "UseMinimumSort: suggests minimum" [useMinimumSortRule]
1118โ (withPrelude "x = head (sort [1,2,3])") "minimum [1,2,3]"
1158+ (withPrelude "x = head (sort [1,2,3])") "minimum [ 1, 2, 3 ]"
11191159 ]
11201160
11211161-- ============================================================================
@@ -1175,7 +1215,7 @@ useOrTests =
11751215 , assertRuleId "UseOr: correct rule id" [useOrRule]
11761216 (withPrelude "x = any identity [true, false]") "UseOr"
11771217 , assertSuggestion "UseOr: suggests or" [useOrRule]
1178โ (withPrelude "x = any identity [true, false]") "or [true, false]"
1218+ (withPrelude "x = any identity [true, false]") "or [ true, false ]"
11791219 ]
11801220
11811221-- ============================================================================
@@ -1189,7 +1229,39 @@ useAndTests =
11891229 , assertRuleId "UseAnd: correct rule id" [useAndRule]
11901230 (withPrelude "x = all identity [true, false]") "UseAnd"
11911231 , assertSuggestion "UseAnd: suggests and" [useAndRule]
1192โ (withPrelude "x = all identity [true, false]") "and [true, false]"
1232+ (withPrelude "x = all identity [true, false]") "and [ true, false ]"
1233+ ]
1234+
1235+-- ============================================================================
1236+-- UseFoldMap Tests
1237+-- ============================================================================
1238+
1239+useFoldMapTests :: Array TestResult
1240+useFoldMapTests =
1241+ [ assertWarningCount "UseFoldMap: case Nothing/Just with empty string" [useFoldMapRule]
1242+ (withPrelude "x mx = case mx of\n Nothing -> \"\"\n Just s -> f s") 1
1243+ , assertRuleId "UseFoldMap: correct rule id" [useFoldMapRule]
1244+ (withPrelude "x mx = case mx of\n Nothing -> \"\"\n Just s -> f s") "UseFoldMap"
1245+ , assertSuggestion "UseFoldMap: suggests foldMap f" [useFoldMapRule]
1246+ (withPrelude "x mx = case mx of\n Nothing -> \"\"\n Just s -> f s") "foldMap f mx"
1247+ , assertSuggestion "UseFoldMap: suggests lambda for complex body" [useFoldMapRule]
1248+ (withPrelude "x mx = case mx of\n Nothing -> \"\"\n Just s -> s <> \"!\"") "foldMap (\\s -> s <> \"!\") mx"
1249+ , assertWarningCount "UseFoldMap: reversed order Just/Nothing" [useFoldMapRule]
1250+ (withPrelude "x mx = case mx of\n Just s -> f s\n Nothing -> \"\"") 1
1251+ ]
1252+
1253+-- ============================================================================
1254+-- UseApplyFlipped Tests
1255+-- ============================================================================
1256+
1257+useApplyFlippedTests :: Array TestResult
1258+useApplyFlippedTests =
1259+ [ assertWarningCount "UseApplyFlipped: f lambda arg" [useApplyFlippedRule]
1260+ (withPrelude "x = foldMap (\\y -> y <> \"!\") xs") 1
1261+ , assertRuleId "UseApplyFlipped: correct rule id" [useApplyFlippedRule]
1262+ (withPrelude "x = foldMap (\\y -> y <> \"!\") xs") "UseApplyFlipped"
1263+ , assertSuggestion "UseApplyFlipped: suggests # form" [useApplyFlippedRule]
1264+ (withPrelude "x = foldMap (\\y -> y <> \"!\") xs") "xs # foldMap \\y -> y <> \"!\""
11931265 ]
11941266
11951267-- ============================================================================
@@ -1245,7 +1317,7 @@ useZipTests =
12451317 , assertRuleId "UseZip: correct rule id" [useZipRule]
12461318 (withPrelude "x = zipWith Tuple [1] [2]") "UseZip"
12471319 , assertSuggestion "UseZip: suggests zip" [useZipRule]
1248โ (withPrelude "x = zipWith Tuple [1] [2]") "zip [1] [2]"
1320+ (withPrelude "x = zipWith Tuple [1] [2]") "zip [ 1 ] [ 2 ]"
12491321 ]
12501322
12511323-- ============================================================================
@@ -1291,13 +1363,190 @@ nothingBindTests =
12911363 ]
12921364
12931365-- ============================================================================
1294โ-- Golden Tests: LintExamples.purs
1366+-- MonadLaw Tests
1367+-- ============================================================================
1368+
1369+monadLawTests :: Array TestResult
1370+monadLawTests =
1371+ [ assertWarningCount "MonadLaw: f =<< pure a" [monadLawRule]
1372+ (withPrelude "x = show =<< pure 42") 1
1373+ , assertRuleId "MonadLaw: correct rule id" [monadLawRule]
1374+ (withPrelude "x = show =<< pure 42") "MonadLaw"
1375+ , assertSuggestion "MonadLaw: suggests f a" [monadLawRule]
1376+ (withPrelude "x = show =<< pure 42") "show 42"
1377+ , assertWarningCount "MonadLaw: pure =<< m" [monadLawRule]
1378+ (withPrelude "x = pure =<< Just 1") 1
1379+ , assertSuggestion "MonadLaw: pure =<< m suggests m" [monadLawRule]
1380+ (withPrelude "x = pure =<< Just 1") "Just 1"
1381+ ]
1382+
1383+-- ============================================================================
1384+-- EvaluateBool Tests
1385+-- ============================================================================
1386+
1387+evaluateBoolTests :: Array TestResult
1388+evaluateBoolTests =
1389+ [ assertWarningCount "EvaluateBool: true && x" [evaluateBoolRule]
1390+ (withPrelude "x = true && foo") 1
1391+ , assertSuggestion "EvaluateBool: true && x -> x" [evaluateBoolRule]
1392+ (withPrelude "x = true && foo") "foo"
1393+ , assertWarningCount "EvaluateBool: false && x" [evaluateBoolRule]
1394+ (withPrelude "x = false && foo") 1
1395+ , assertSuggestion "EvaluateBool: false && x -> false" [evaluateBoolRule]
1396+ (withPrelude "x = false && foo") "false"
1397+ , assertWarningCount "EvaluateBool: true || x" [evaluateBoolRule]
1398+ (withPrelude "x = true || foo") 1
1399+ , assertSuggestion "EvaluateBool: true || x -> true" [evaluateBoolRule]
1400+ (withPrelude "x = true || foo") "true"
1401+ , assertWarningCount "EvaluateBool: false || x" [evaluateBoolRule]
1402+ (withPrelude "x = false || foo") 1
1403+ , assertSuggestion "EvaluateBool: false || x -> x" [evaluateBoolRule]
1404+ (withPrelude "x = false || foo") "foo"
1405+ , assertWarningCount "EvaluateBool: x && true" [evaluateBoolRule]
1406+ (withPrelude "x = foo && true") 1
1407+ , assertWarningCount "EvaluateBool: x || false" [evaluateBoolRule]
1408+ (withPrelude "x = foo || false") 1
1409+ ]
1410+
1411+-- ============================================================================
1412+-- UseFoldBool Tests
1413+-- ============================================================================
1414+
1415+useFoldBoolTests :: Array TestResult
1416+useFoldBoolTests =
1417+ [ assertWarningCount "UseFoldBool: foldr (&&) true" [useFoldBoolRule]
1418+ (withPrelude "x = foldr (&&) true xs") 1
1419+ , assertSuggestion "UseFoldBool: foldr (&&) true -> and" [useFoldBoolRule]
1420+ (withPrelude "x = foldr (&&) true xs") "and"
1421+ , assertWarningCount "UseFoldBool: foldr (||) false" [useFoldBoolRule]
1422+ (withPrelude "x = foldr (||) false xs") 1
1423+ , assertSuggestion "UseFoldBool: foldr (||) false -> or" [useFoldBoolRule]
1424+ (withPrelude "x = foldr (||) false xs") "or"
1425+ ]
1426+
1427+-- ============================================================================
1428+-- AlternativeLaw Tests
1429+-- ============================================================================
1430+
1431+alternativeLawTests :: Array TestResult
1432+alternativeLawTests =
1433+ [ assertWarningCount "AlternativeLaw: empty <|> x" [alternativeLawRule]
1434+ (withAlt "x = empty <|> foo") 1
1435+ , assertSuggestion "AlternativeLaw: empty <|> x -> x" [alternativeLawRule]
1436+ (withAlt "x = empty <|> foo") "foo"
1437+ , assertWarningCount "AlternativeLaw: x <|> empty" [alternativeLawRule]
1438+ (withAlt "x = foo <|> empty") 1
1439+ , assertSuggestion "AlternativeLaw: x <|> empty -> x" [alternativeLawRule]
1440+ (withAlt "x = foo <|> empty") "foo"
1441+ , assertWarningCount "AlternativeLaw: Nothing <|> x" [alternativeLawRule]
1442+ (withAlt "x = Nothing <|> foo") 1
1443+ ]
1444+
1445+-- ============================================================================
1446+-- EvaluateEither Tests
1447+-- ============================================================================
1448+
1449+evaluateEitherTests :: Array TestResult
1450+evaluateEitherTests =
1451+ [ assertWarningCount "EvaluateEither: either f g (Left x)" [evaluateEitherRule]
1452+ (withPrelude "x = either show identity (Left 42)") 1
1453+ , assertSuggestion "EvaluateEither: either f g (Left x) -> f x" [evaluateEitherRule]
1454+ (withPrelude "x = either show identity (Left 42)") "show 42"
1455+ , assertWarningCount "EvaluateEither: either f g (Right y)" [evaluateEitherRule]
1456+ (withPrelude "x = either show identity (Right \"hi\")") 1
1457+ , assertSuggestion "EvaluateEither: either f g (Right y) -> g y" [evaluateEitherRule]
1458+ (withPrelude "x = either show identity (Right \"hi\")") "identity \"hi\""
1459+ ]
1460+
1461+-- ============================================================================
1462+-- UseCaseOf Tests
12951463-- ============================================================================
12961464
1465+useCaseOfTests :: Array TestResult
1466+useCaseOfTests =
1467+ -- Positive cases: should detect
1468+ [ assertWarningCount "UseCaseOf: two clauses" [useCaseOfRule]
1469+ (withPrelude "foo True = 1\nfoo False = 0") 1
1470+ , assertRuleId "UseCaseOf: correct rule id" [useCaseOfRule]
1471+ (withPrelude "foo True = 1\nfoo False = 0") "UseCaseOf"
1472+ , assertSuggestion "UseCaseOf: suggestion format" [useCaseOfRule]
1473+ (withPrelude "foo True = 1\nfoo False = 0") "foo = case _ of\n True -> 1\n False -> 0"
1474+ , assertWarningCount "UseCaseOf: three clauses" [useCaseOfRule]
1475+ (withPrelude "bar Nothing = 0\nbar (Just 0) = 1\nbar (Just _) = 2") 1
1476+ , assertWarningCount "UseCaseOf: two args" [useCaseOfRule]
1477+ (withPrelude "add True True = 2\nadd True False = 1\nadd False True = 1\nadd False False = 0") 1
1478+ , assertSuggestion "UseCaseOf: two args suggestion" [useCaseOfRule]
1479+ (withPrelude "add True True = 2\nadd True False = 1\nadd False True = 1\nadd False False = 0") "add = case _, _ of\n True, True -> 2\n True, False -> 1\n False, True -> 1\n False, False -> 0"
1480+
1481+ -- Negative cases: should NOT detect
1482+ , assertWarningCount "UseCaseOf: single clause" [useCaseOfRule]
1483+ (withPrelude "foo x = x + 1") 0
1484+ , assertWarningCount "UseCaseOf: no binders" [useCaseOfRule]
1485+ (withPrelude "foo = 42\nbar = 43") 0
1486+ , assertWarningCount "UseCaseOf: guarded" [useCaseOfRule]
1487+ (withPrelude "foo x | x > 0 = 1\nfoo x = 0") 0
1488+ ]
1489+
1490+-- ============================================================================
1491+-- FlattenCase Tests
1492+-- ============================================================================
1493+
1494+flattenCaseTests :: Array TestResult
1495+flattenCaseTests =
1496+ -- Positive cases: should detect
1497+ [ assertWarningCount "FlattenCase: two levels" [flattenCaseRule]
1498+ (withPrelude "foo x y = case x of\n Just a -> case y of\n Just b -> a + b\n _ -> 0\n _ -> 0") 1
1499+ , assertRuleId "FlattenCase: correct rule id" [flattenCaseRule]
1500+ (withPrelude "foo x y = case x of\n Just a -> case y of\n Just b -> a + b\n _ -> 0\n _ -> 0") "FlattenCase"
1501+ , assertSuggestion "FlattenCase: suggestion format" [flattenCaseRule]
1502+ (withPrelude "foo x y = case x of\n Just a -> case y of\n Just b -> a + b\n _ -> 0\n _ -> 0") "case x, y of\n Just a, Just b -> a + b\n _, _ -> 0"
1503+
1504+ -- Negative cases: should NOT detect
1505+ , assertWarningCount "FlattenCase: single level" [flattenCaseRule]
1506+ (withPrelude "foo x = case x of\n Just a -> a\n _ -> 0") 0
1507+ , assertWarningCount "FlattenCase: different fallbacks" [flattenCaseRule]
1508+ (withPrelude "foo x y = case x of\n Just a -> case y of\n Just b -> a + b\n _ -> 1\n _ -> 0") 0
1509+ ]
1510+
1511+-- ============================================================================
1512+-- UsePatternGuards Tests
1513+-- ============================================================================
1514+
1515+usePatternGuardsTests :: Array TestResult
1516+usePatternGuardsTests =
1517+ -- Positive cases: should detect
1518+ [ assertWarningCount "UsePatternGuards: two levels" [usePatternGuardsRule]
1519+ (withPrelude "foo x y = case x of\n Just a -> case y of\n Just b -> a + b\n _ -> 0\n _ -> 0") 1
1520+ , assertRuleId "UsePatternGuards: correct rule id" [usePatternGuardsRule]
1521+ (withPrelude "foo x y = case x of\n Just a -> case y of\n Just b -> a + b\n _ -> 0\n _ -> 0") "UsePatternGuards"
1522+ , assertWarningCount "UsePatternGuards: three levels" [usePatternGuardsRule]
1523+ (withPrelude "foo x y z = case x of\n Just a -> case y of\n Just b -> case z of\n Just c -> a + b + c\n _ -> 0\n _ -> 0\n _ -> 0") 2
1524+
1525+ -- Negative cases: should NOT detect
1526+ , assertWarningCount "UsePatternGuards: single level" [usePatternGuardsRule]
1527+ (withPrelude "foo x = case x of\n Just a -> a\n _ -> 0") 0
1528+ , assertWarningCount "UsePatternGuards: different fallbacks" [usePatternGuardsRule]
1529+ (withPrelude "foo x y = case x of\n Just a -> case y of\n Just b -> a + b\n _ -> 1\n _ -> 0") 0
1530+
1531+ -- Fix test: verifies correct indentation relative to original branch position
1532+ , assertFix "UsePatternGuards: fix with proper indentation" [usePatternGuardsRule]
1533+ (withPrelude "foo x y = case x of\n Just a -> case y of\n Just b -> a + b\n _ -> 0\n _ -> 0")
1534+ (withPrelude "foo x y = case x of\n Just a\n | Just b <- y -> a + b\n _ -> 0")
1535+ ]
1536+
1537+-- | Helper for Alternative imports
1538+withAlt :: String -> String
1539+withAlt code = """
1540+module Test where
1541+import Prelude
1542+import Control.Alternative (empty, (<|>))
1543+import Data.Maybe (Maybe(..), Nothing, Just)
1544+""" <> code
1545+
12971546-- | All rules for golden testing
12981547allRulesForGolden :: Array Rule
12991548allRulesForGolden =
1300โ [ fmapIdRule
1549+ [ mapIdentityRule
13011550 , mapFusionRule
13021551 , useTraverseRule
13031552 , concatMapRule
@@ -1343,200 +1592,207 @@ allRulesForGolden =
13431592 , useFindMapRule
13441593 ]
13451594
1346โ-- | Assert that a specific rule fires at least once when running all rules
1347โassertRuleFires :: String -> String -> String -> TestResult
1348โassertRuleFires name source ruleId =
1349โ case runRules allRulesForGolden (SourceCode source) of
1350โ Left err -> runTest name false ("Parse error: " <> err)
1351โ Right (LintResult result) ->
1352โ let matches = Array.filter (\(LintWarning w) -> unwrap w.ruleId == ruleId) result.warnings
1353โ in if Array.length matches > 0
1354โ then runTest name true "OK"
1355โ else runTest name false ("Rule " <> ruleId <> " did not fire")
1356โ
1357โ-- | The embedded LintExamples source
1358โlintExamplesSource :: String
1359โlintExamplesSource = """module Test.LintExamples where
1360โ
1361โimport Prelude
1362โ
1363โimport Control.Alternative
1364โimport Control.Monad
1365โimport Data.Array
1366โimport Data.Foldable
1367โimport Data.Maybe
1368โimport Data.Monoid
1369โimport Data.Ord
1370โimport Data.Traversable
1371โimport Data.Tuple
1372โ
1373โ-- FmapId
1374โfmapId1 = map identity [ 1, 2, 3 ]
1375โ
1376โ-- MapFusion
1377โmapFusion1 = map show (map (_ + 1) [ 1, 2, 3 ])
1378โ
1379โ-- UseTraverse
1380โuseTraverse1 f xs = sequence (map f xs)
1381โ
1382โ-- ConcatMap
1383โconcatMap1 f xs = join (map f xs)
1384โ
1385โ-- NotEqual
1386โnotEqual1 x y = not (x == y)
1387โ
1388โ-- UseGuard
1389โuseGuard1 cond action = if cond then action else pure unit
1390โ
1391โ-- EtaReduce
1392โetaReduce1 = \x -> show x
1393โ
1394โ-- EtaReduceDecl
1395โetaReduceDecl1 x = show x
1396โ
1397โ-- RedundantBind
1398โredundantBind1 x f = pure x >>= f
1399โ
1400โ-- LetToWhere
1401โletToWhere1 y z = let x = y in z
1402โ
1403โ-- RedundantIf
1404โredundantIf1 a = if a then true else false
1405โ
1406โ-- UseVoid
1407โuseVoid1 a = a *> pure unit
1408โ
1409โ-- UseJoin
1410โuseJoin1 x = x >>= identity
1411โ
1412โ-- UseAny
1413โuseAny1 f xs = or (map f xs)
1414โ
1415โ-- UseAll
1416โuseAll1 f xs = and (map f xs)
1417โ
1418โ-- BooleanSimplify
1419โbooleanSimplify1 x = x == true
1420โ
1421โ-- CollapseLambdas
1422โcollapseLambdas1 = \x -> \y -> x + y
1423โ
1424โ-- UseConst
1425โuseConst1 = \x -> 42
1426โ
1427โ-- UseNull
1428โuseNull1 xs = length xs == 0
1429โ
1430โ-- UseFromMaybe
1431โuseFromMaybe1 def = maybe def identity
1432โ
1433โ-- UseIsJust
1434โuseIsJust1 = maybe false (const true)
1435โ
1436โ-- UseIsNothing
1437โuseIsNothing1 = maybe true (const false)
1438โ
1439โ-- UseWhen
1440โuseWhen1 cond action = if cond then action else pure unit
1441โ
1442โ-- UseUnless
1443โuseUnless1 cond action = if cond then pure unit else action
1444โ
1445โ-- RedundantFlip
1446โredundantFlip1 f = flip (flip f)
1447โ
1448โ-- UseNotElem
1449โuseNotElem1 x xs = not (elem x xs)
1450โ
1451โ-- UseMinMax
1452โuseMax1 a b = if a > b then a else b
1453โ
1454โ-- MonoidIdentity
1455โmonoidIdentity1 x = mempty <> x
1456โ
1457โ-- UseFold
1458โuseFold1 = foldMap identity
1459โ
1460โ-- UseSequence
1461โuseSequence1 = traverse identity
1462โ
1463โ-- UseTraverseSequence
1464โuseTraverseSequence1 f xs = sequence (map f xs)
1465โ
1466โ-- RedundantNot
1467โredundantNot1 x = not (not x)
1468โ
1469โ-- UseFstSnd
1470โuseFst1 = \(Tuple x y) -> x
1471โ
1472โ-- UseMapMaybe
1473โuseMapMaybe1 = catMaybes (map f [1,2,3])
1474โ
1475โ-- UseApplicative
1476โuseApplicative1 = pure f <*> x
1477โ
1478โ-- UseBindFlip
1479โuseBindFlip1 = join (map f x)
1480โ
1481โ-- UseFor
1482โuseFor1 = flip traverse
1483โ
1484โ-- RedundantGuard
1485โredundantGuard1 = guard true
1486โ
1487โ-- UseComparing
1488โuseComparing1 = \a b -> compare (f a) (f b)
1489โ
1490โ-- UseOn
1491โuseOn1 = \a b -> f (g a) (g b)
1492โ
1493โ-- UseFindMap
1494โuseFindMap1 = head (mapMaybe f [1,2,3])
1495โ"""
1496โ
1497โgoldenTests :: Array TestResult
1498โgoldenTests =
1499โ [ assertRuleFires "Golden: FmapId fires" lintExamplesSource "FmapId"
1500โ , assertRuleFires "Golden: MapFusion fires" lintExamplesSource "MapFusion"
1501โ , assertRuleFires "Golden: UseTraverse fires" lintExamplesSource "UseTraverse"
1502โ , assertRuleFires "Golden: ConcatMap fires" lintExamplesSource "ConcatMap"
1503โ , assertRuleFires "Golden: NotEqual fires" lintExamplesSource "NotEqual"
1504โ , assertRuleFires "Golden: UseGuard fires" lintExamplesSource "UseGuard"
1505โ , assertRuleFires "Golden: EtaReduce fires" lintExamplesSource "EtaReduce"
1506โ , assertRuleFires "Golden: EtaReduceDecl fires" lintExamplesSource "EtaReduceDecl"
1507โ , assertRuleFires "Golden: RedundantBind fires" lintExamplesSource "RedundantBind"
1508โ , assertRuleFires "Golden: LetToWhere fires" lintExamplesSource "LetToWhere"
1509โ , assertRuleFires "Golden: RedundantIf fires" lintExamplesSource "RedundantIf"
1510โ , assertRuleFires "Golden: UseVoid fires" lintExamplesSource "UseVoid"
1511โ , assertRuleFires "Golden: UseJoin fires" lintExamplesSource "UseJoin"
1512โ , assertRuleFires "Golden: UseAny fires" lintExamplesSource "UseAny"
1513โ , assertRuleFires "Golden: UseAll fires" lintExamplesSource "UseAll"
1514โ , assertRuleFires "Golden: BooleanSimplify fires" lintExamplesSource "BooleanSimplify"
1515โ , assertRuleFires "Golden: CollapseLambdas fires" lintExamplesSource "CollapseLambdas"
1516โ , assertRuleFires "Golden: UseConst fires" lintExamplesSource "UseConst"
1517โ , assertRuleFires "Golden: UseNull fires" lintExamplesSource "UseNull"
1518โ , assertRuleFires "Golden: UseFromMaybe fires" lintExamplesSource "UseFromMaybe"
1519โ , assertRuleFires "Golden: UseIsJust fires" lintExamplesSource "UseIsJust"
1520โ , assertRuleFires "Golden: UseIsNothing fires" lintExamplesSource "UseIsNothing"
1521โ , assertRuleFires "Golden: UseWhen fires" lintExamplesSource "UseWhen"
1522โ , assertRuleFires "Golden: UseUnless fires" lintExamplesSource "UseUnless"
1523โ , assertRuleFires "Golden: RedundantFlip fires" lintExamplesSource "RedundantFlip"
1524โ , assertRuleFires "Golden: UseNotElem fires" lintExamplesSource "UseNotElem"
1525โ , assertRuleFires "Golden: UseMinMax fires" lintExamplesSource "UseMinMax"
1526โ , assertRuleFires "Golden: MonoidIdentity fires" lintExamplesSource "MonoidIdentity"
1527โ , assertRuleFires "Golden: UseFold fires" lintExamplesSource "UseFold"
1528โ , assertRuleFires "Golden: UseSequence fires" lintExamplesSource "UseSequence"
1529โ , assertRuleFires "Golden: UseTraverseSequence fires" lintExamplesSource "UseTraverseSequence"
1530โ , assertRuleFires "Golden: RedundantNot fires" lintExamplesSource "RedundantNot"
1531โ , assertRuleFires "Golden: UseFstSnd fires" lintExamplesSource "UseFstSnd"
1532โ , assertRuleFires "Golden: UseMapMaybe fires" lintExamplesSource "UseMapMaybe"
1533โ , assertRuleFires "Golden: UseApplicative fires" lintExamplesSource "UseApplicative"
1534โ , assertRuleFires "Golden: UseBindFlip fires" lintExamplesSource "UseBindFlip"
1535โ , assertRuleFires "Golden: UseFor fires" lintExamplesSource "UseFor"
1536โ , assertRuleFires "Golden: RedundantGuard fires" lintExamplesSource "RedundantGuard"
1537โ , assertRuleFires "Golden: UseComparing fires" lintExamplesSource "UseComparing"
1538โ , assertRuleFires "Golden: UseOn fires" lintExamplesSource "UseOn"
1539โ , assertRuleFires "Golden: UseFindMap fires" lintExamplesSource "UseFindMap"
1595+goldenTests :: Effect (Array TestResult)
1596+goldenTests = traverse identity
1597+ -- MapIdentity: map identity x -> x
1598+ [ assertFixFormatted "Golden: MapIdentity fix" [mapIdentityRule]
1599+ (withPrelude "x = map identity [ 1, 2, 3 ]")
1600+ (withPrelude "x = [ 1, 2, 3 ]")
1601+
1602+ -- MapFusion: map f (map g x) -> map (f <<< g) x
1603+ , assertFixFormatted "Golden: MapFusion fix" [mapFusionRule]
1604+ (withPrelude "x = map show (map (_ + 1) [ 1, 2, 3 ])")
1605+ (withPrelude "x = map (show <<< (_ + 1)) [ 1, 2, 3 ]")
1606+
1607+ -- UseTraverse: sequence (map f x) -> mapM f x
1608+ , assertFixFormatted "Golden: UseTraverse fix" [useTraverseRule]
1609+ (withPrelude "x f xs = sequence (map f xs)")
1610+ (withPrelude "x f xs = mapM f xs")
1611+
1612+ -- ConcatMap: join (map f x) -> (=<<) f x
1613+ , assertFixFormatted "Golden: ConcatMap fix" [concatMapRule]
1614+ (withPrelude "x f xs = join (map f xs)")
1615+ (withPrelude "x f xs = (=<<) f xs")
1616+
1617+ -- NotEqual: not (a == b) -> a /= b
1618+ , assertFixFormatted "Golden: NotEqual fix" [notEqualRule]
1619+ (withPrelude "x a b = not (a == b)")
1620+ (withPrelude "x a b = a /= b")
1621+
1622+ -- EtaReduce: \x -> f x -> f
1623+ , assertFixFormatted "Golden: EtaReduce fix" [etaReduceRule]
1624+ (withPrelude "x = \\y -> show y")
1625+ (withPrelude "x = show")
1626+
1627+ -- RedundantBind: pure x >>= f -> f x
1628+ , assertFixFormatted "Golden: RedundantBind fix" [redundantBindRule]
1629+ (withPrelude "x y f = pure y >>= f")
1630+ (withPrelude "x y f = f y")
1631+
1632+ -- RedundantIf: if a then true else false -> a
1633+ , assertFixFormatted "Golden: RedundantIf fix" [redundantIfRule]
1634+ (withPrelude "x a = if a then true else false")
1635+ (withPrelude "x a = a")
1636+
1637+ -- UseVoid: a *> pure unit -> void a
1638+ , assertFixFormatted "Golden: UseVoid fix" [useVoidRule]
1639+ (withPrelude "x a = a *> pure unit")
1640+ (withPrelude "x a = void a")
1641+
1642+ -- UseJoin: x >>= identity -> join x
1643+ , assertFixFormatted "Golden: UseJoin fix" [useJoinRule]
1644+ (withPrelude "x m = m >>= identity")
1645+ (withPrelude "x m = join m")
1646+
1647+ -- UseAny: or (map f x) -> any f x
1648+ , assertFixFormatted "Golden: UseAny fix" [useAnyRule]
1649+ (withPrelude "x f xs = or (map f xs)")
1650+ (withPrelude "x f xs = any f xs")
1651+
1652+ -- UseAll: and (map f x) -> all f x
1653+ , assertFixFormatted "Golden: UseAll fix" [useAllRule]
1654+ (withPrelude "x f xs = and (map f xs)")
1655+ (withPrelude "x f xs = all f xs")
1656+
1657+ -- BooleanSimplify: x == true -> x
1658+ , assertFixFormatted "Golden: BooleanSimplify fix" [booleanSimplifyRule]
1659+ (withPrelude "x a = a == true")
1660+ (withPrelude "x a = a")
1661+
1662+ -- CollapseLambdas: \x -> \y -> body -> \x y -> body
1663+ , assertFixFormatted "Golden: CollapseLambdas fix" [collapseLambdasRule]
1664+ (withPrelude "x = \\a -> \\b -> a + b")
1665+ (withPrelude "x = \\a b -> a + b")
1666+
1667+ -- UseConst: \x -> y -> const y (where y doesn't mention x)
1668+ , assertFixFormatted "Golden: UseConst fix" [useConstRule]
1669+ (withPrelude "x = \\unused -> 42")
1670+ (withPrelude "x = const 42")
1671+
1672+ -- UseNull: length xs == 0 -> null xs
1673+ , assertFixFormatted "Golden: UseNull fix" [useNullRule]
1674+ (withPrelude "x xs = length xs == 0")
1675+ (withPrelude "x xs = null xs")
1676+
1677+ -- UseFromMaybe: maybe def identity -> fromMaybe def
1678+ , assertFixFormatted "Golden: UseFromMaybe fix" [useFromMaybeRule]
1679+ (withPrelude "x def = maybe def identity")
1680+ (withPrelude "x def = fromMaybe def")
1681+
1682+ -- UseIsJust: maybe false (const true) -> isJust
1683+ , assertFixFormatted "Golden: UseIsJust fix" [useIsJustRule]
1684+ (withPrelude "x = maybe false (const true)")
1685+ (withPrelude "x = isJust")
1686+
1687+ -- UseIsNothing: maybe true (const false) -> isNothing
1688+ , assertFixFormatted "Golden: UseIsNothing fix" [useIsNothingRule]
1689+ (withPrelude "x = maybe true (const false)")
1690+ (withPrelude "x = isNothing")
1691+
1692+ -- UseWhen: if cond then action else pure unit -> when cond action
1693+ , assertFixFormatted "Golden: UseWhen fix" [useWhenRule]
1694+ (withPrelude "x cond action = if cond then action else pure unit")
1695+ (withPrelude "x cond action = when cond action")
1696+
1697+ -- UseUnless: if cond then pure unit else action -> unless cond action
1698+ , assertFixFormatted "Golden: UseUnless fix" [useUnlessRule]
1699+ (withPrelude "x cond action = if cond then pure unit else action")
1700+ (withPrelude "x cond action = unless cond action")
1701+
1702+ -- RedundantFlip: flip (flip f) -> f
1703+ , assertFixFormatted "Golden: RedundantFlip fix" [redundantFlipRule]
1704+ (withPrelude "x f = flip (flip f)")
1705+ (withPrelude "x f = f")
1706+
1707+ -- UseNotElem: not (elem x xs) -> notElem x xs
1708+ , assertFixFormatted "Golden: UseNotElem fix" [useNotElemRule]
1709+ (withPrelude "x a xs = not (elem a xs)")
1710+ (withPrelude "x a xs = notElem a xs")
1711+
1712+ -- UseMinMax: if a > b then a else b -> max a b
1713+ , assertFixFormatted "Golden: UseMinMax fix" [useMinMaxRule]
1714+ (withPrelude "x a b = if a > b then a else b")
1715+ (withPrelude "x a b = max a b")
1716+
1717+ -- MonoidIdentity: mempty <> x -> x
1718+ , assertFixFormatted "Golden: MonoidIdentity fix" [monoidIdentityRule]
1719+ (withPrelude "x xs = mempty <> xs")
1720+ (withPrelude "x xs = xs")
1721+
1722+ -- UseFold: foldMap identity -> fold
1723+ , assertFixFormatted "Golden: UseFold fix" [useFoldRule]
1724+ (withPrelude "x = foldMap identity")
1725+ (withPrelude "x = fold")
1726+
1727+ -- UseSequence: traverse identity -> sequence
1728+ , assertFixFormatted "Golden: UseSequence fix" [useSequenceRule]
1729+ (withPrelude "x = traverse identity")
1730+ (withPrelude "x = sequence")
1731+
1732+ -- RedundantNot: not (not x) -> x
1733+ , assertFixFormatted "Golden: RedundantNot fix" [redundantNotRule]
1734+ (withPrelude "x a = not (not a)")
1735+ (withPrelude "x a = a")
1736+
1737+ -- UseFstSnd: \(Tuple x _) -> x -> fst
1738+ , assertFixFormatted "Golden: UseFstSnd fix" [useFstSndRule]
1739+ (withPrelude "x = \\(Tuple a b) -> a")
1740+ (withPrelude "x = fst")
1741+
1742+ -- UseMapMaybe: catMaybes (map f x) -> mapMaybe f x
1743+ , assertFixFormatted "Golden: UseMapMaybe fix" [useMapMaybeRule]
1744+ (withPrelude "x f xs = catMaybes (map f xs)")
1745+ (withPrelude "x f xs = mapMaybe f xs")
1746+
1747+ -- UseApplicative: pure f <*> x -> f <$> x
1748+ , assertFixFormatted "Golden: UseApplicative fix" [useApplicativeRule]
1749+ (withPrelude "x f y = pure f <*> y")
1750+ (withPrelude "x f y = f <$> y")
1751+
1752+ -- UseBindFlip: join (map f x) -> x >>= f
1753+ , assertFixFormatted "Golden: UseBindFlip fix" [useBindFlipRule]
1754+ (withPrelude "x f y = join (map f y)")
1755+ (withPrelude "x f y = y >>= f")
1756+
1757+ -- UseFor: flip traverse -> for
1758+ , assertFixFormatted "Golden: UseFor fix" [useForRule]
1759+ (withPrelude "x = flip traverse")
1760+ (withPrelude "x = for")
1761+
1762+ -- RedundantGuard: guard true -> pure unit
1763+ , assertFixFormatted "Golden: RedundantGuard fix" [redundantGuardRule]
1764+ (withPrelude "x = guard true")
1765+ (withPrelude "x = pure unit")
1766+
1767+ -- UseComparing: \a b -> compare (f a) (f b) -> comparing f
1768+ , assertFixFormatted "Golden: UseComparing fix" [useComparingRule]
1769+ (withPrelude "x f = \\a b -> compare (f a) (f b)")
1770+ (withPrelude "x f = comparing f")
1771+
1772+ -- UseOn: \a b -> f (g a) (g b) -> on f g
1773+ , assertFixFormatted "Golden: UseOn fix" [useOnRule]
1774+ (withPrelude "x f g = \\a b -> f (g a) (g b)")
1775+ (withPrelude "x f g = on f g")
1776+
1777+ -- UseFindMap: head (mapMaybe f x) -> findMap f x
1778+ , assertFixFormatted "Golden: UseFindMap fix" [useFindMapRule]
1779+ (withPrelude "x f xs = head (mapMaybe f xs)")
1780+ (withPrelude "x f xs = findMap f xs")
1781+
1782+ -- LetToDo: nested let...in -> do notation (do on previous line)
1783+ , pure $ assertFix "Golden: LetToDo nested fix" [letToDoRule]
1784+ (withPrelude "x f =\n case f of\n Just y ->\n let\n z = y + 1\n in z * 2\n Nothing -> 0")
1785+ (withPrelude "x f =\n case f of\n Just y -> do\n let\n z = y + 1\n z * 2\n Nothing -> 0")
1786+
1787+ -- LetToDo: should NOT fire on do-let (no "in" keyword)
1788+ , pure $ assertWarningCount "Golden: LetToDo no fire on do-let" [letToDoRule]
1789+ (withPrelude "x f =\n case f of\n Just y -> do\n let\n z = y + 1\n [ z ]\n Nothing -> []")
1790+ 0
1791+
1792+ -- UsePatternGuards: should preserve do-let body structure
1793+ , pure $ assertFix "Golden: UsePatternGuards preserves do-let" [usePatternGuardsRule]
1794+ (withPrelude "checkExpr expr =\n case expr of\n Just mapArgs ->\n case toArray mapArgs of\n [ x, y ] -> do\n let\n f = show x\n g = show y\n [ f, g ]\n _ -> []\n _ -> []")
1795+ (withPrelude "checkExpr expr =\n case expr of\n Just mapArgs\n | [ x, y ] <- toArray mapArgs -> do\n let\n f = show x\n g = show y\n [ f, g ]\n _ -> [ ]")
15401796 ]
15411797
15421798-- ============================================================================
@@ -1575,7 +1831,7 @@ tidySuggestions =
15751831 , { name: "traverse f xs", suggestion: "traverse f xs" }
15761832 , { name: "collapsed lambda", suggestion: "\\a b -> a + b" }
15771833 , { name: "map fusion", suggestion: "map (f <<< g) x" }
1578โ , { name: "fmap fusion", suggestion: "(f <<< g) <$> x" }
1834+ , { name: "map fusion with <$>", suggestion: "(f <<< g) <$> x" }
15791835 , { name: "flipped map fusion", suggestion: "x <#> (g >>> f)" }
15801836 , { name: "where clause", suggestion: "y + 1\n where\n y = 1" }
15811837 ]