@@ -7,22 +7,96 @@ import Data.Either (Either(..))
77import Data.Foldable (for_)
88import Data.Maybe (Maybe(..))
99import Data.Newtype (unwrap)
10+import Data.Traversable (traverse)
1011import Effect (Effect)
1112import Effect.Class.Console (log)
12−import Purelint.Fix (applyAllFixes)
13−import Purelint.Rules.FmapId (fmapIdRule)
14−import Purelint.Rules.MapFusion (mapFusionRule)
15−import Purelint.Rules.UseTraverse (useTraverseRule)
16−import Purelint.Rules.ConcatMap (concatMapRule)
17−import Purelint.Rules.NotEqual (notEqualRule)
18−import Purelint.Rules.UseGuard (useGuardRule)
19−import Purelint.Rules.EtaReduce (etaReduceRule)
20−import Purelint.Rules.EtaReduceDecl (etaReduceDeclRule)
21−import Purelint.Rules.RedundantBind (redundantBindRule)
22−import Purelint.Rules.LetToWhere (letToWhereRule)
23−import Purelint.Rule (Rule)
24−import Purelint.Runner (runRules)
25−import Purelint.Types (LintResult(..), LintWarning(..), RuleId(..), SourceCode(..), Suggestion(..))
13+import Purslint.Fix (applyAllFixes)
14+import Purslint.Tidy (formatExpr, formatModule)
15+import Purslint.Rules.MapIdentity (mapIdentityRule)
16+import Purslint.Rules.MapFusion (mapFusionRule)
17+import Purslint.Rules.UseTraverse (useTraverseRule)
18+import Purslint.Rules.ConcatMap (concatMapRule)
19+import Purslint.Rules.NotEqual (notEqualRule)
20+import Purslint.Rules.UseGuard (useGuardRule)
21+import Purslint.Rules.EtaReduce (etaReduceRule)
22+import Purslint.Rules.EtaReduceDecl (etaReduceDeclRule)
23+import Purslint.Rules.RedundantBind (redundantBindRule)
24+import Purslint.Rules.LetToWhere (letToWhereRule)
25+import Purslint.Rules.LetToDo (letToDoRule)
26+import Purslint.Rules.RedundantIf (redundantIfRule)
27+import Purslint.Rules.FunctorLaw (functorLawRule)
28+import Purslint.Rules.UseVoid (useVoidRule)
29+import Purslint.Rules.UseJoin (useJoinRule)
30+import Purslint.Rules.UseAny (useAnyRule)
31+import Purslint.Rules.UseAll (useAllRule)
32+import Purslint.Rules.BooleanSimplify (booleanSimplifyRule)
33+import Purslint.Rules.CollapseLambdas (collapseLambdasRule)
34+import Purslint.Rules.UseConst (useConstRule)
35+import Purslint.Rules.UseDollar (useDollarRule)
36+import Purslint.Rules.UseNull (useNullRule)
37+import Purslint.Rules.UseFromMaybe (useFromMaybeRule)
38+import Purslint.Rules.UseIsJust (useIsJustRule)
39+import Purslint.Rules.UseIsNothing (useIsNothingRule)
40+import Purslint.Rules.UseWhen (useWhenRule)
41+import Purslint.Rules.UseUnless (useUnlessRule)
42+import Purslint.Rules.RedundantFlip (redundantFlipRule)
43+import Purslint.Rules.UseNotElem (useNotElemRule)
44+import Purslint.Rules.UseMinMax (useMinMaxRule)
45+import Purslint.Rules.MonoidIdentity (monoidIdentityRule)
46+import Purslint.Rules.UseFold (useFoldRule)
47+import Purslint.Rules.UseSequence (useSequenceRule)
48+import Purslint.Rules.UseTraverseSequence (useTraverseSequenceRule)
49+import Purslint.Rules.RedundantNot (redundantNotRule)
50+import Purslint.Rules.UseFstSnd (useFstSndRule)
51+import Purslint.Rules.UseMapMaybe (useMapMaybeRule)
52+import Purslint.Rules.UseGuardMaybe (useGuardMaybeRule)
53+import Purslint.Rules.UseMaybeMap (useMaybeMapRule)
54+import Purslint.Rules.UseApplicative (useApplicativeRule)
55+import Purslint.Rules.UseBindFlip (useBindFlipRule)
56+import Purslint.Rules.UseFor (useForRule)
57+import Purslint.Rules.UseUnwrap (useUnwrapRule)
58+import Purslint.Rules.RedundantGuard (redundantGuardRule)
59+import Purslint.Rules.UseComparing (useComparingRule)
60+import Purslint.Rules.UseOn (useOnRule)
61+import Purslint.Rules.UseFindMap (useFindMapRule)
62+import Purslint.Rules.UseLastReverse (useLastReverseRule)
63+import Purslint.Rules.RedundantReverse (redundantReverseRule)
64+import Purslint.Rules.UseBreak (useBreakRule)
65+import Purslint.Rules.UseSpan (useSpanRule)
66+import Purslint.Rules.UseMinimumSort (useMinimumSortRule)
67+import Purslint.Rules.UseBimap (useBimapRule)
68+import Purslint.Rules.UseEitherMap (useEitherMapRule)
69+import Purslint.Rules.EvaluateFst (evaluateFstRule)
70+import Purslint.Rules.EvaluateConst (evaluateConstRule)
71+import Purslint.Rules.UseOr (useOrRule)
72+import Purslint.Rules.UseAnd (useAndRule)
73+import Purslint.Rules.UseFoldMap (useFoldMapRule)
74+import Purslint.Rules.UseFoldMapId (useFoldMapIdRule)
75+import Purslint.Rules.UseApplyFlipped (useApplyFlippedRule)
76+import Purslint.Rules.WhenNot (whenNotRule)
77+import Purslint.Rules.UnlessNot (unlessNotRule)
78+import Purslint.Rules.UseZip (useZipRule)
79+import Purslint.Rules.UseReplicate (useReplicateRule)
80+import Purslint.Rules.UseUncurry (useUncurryRule)
81+import Purslint.Rules.RedundantNegate (redundantNegateRule)
82+import Purslint.Rules.UseFromJust (useFromJustRule)
83+import Purslint.Rules.UseHead (useHeadRule)
84+import Purslint.Rules.RedundantId (redundantIdRule)
85+import Purslint.Rules.NothingBind (nothingBindRule)
86+import Purslint.Rules.UseElemIndex (useElemIndexRule)
87+import Purslint.Rules.MonadLaw (monadLawRule)
88+import Purslint.Rules.EvaluateBool (evaluateBoolRule)
89+import Purslint.Rules.UseFoldBool (useFoldBoolRule)
90+import Purslint.Rules.AlternativeLaw (alternativeLawRule)
91+import Purslint.Rules.EvaluateEither (evaluateEitherRule)
92+import Purslint.Rules.UseCaseOf (useCaseOfRule)
93+import Purslint.Rules.FlattenCase (flattenCaseRule)
94+import Purslint.Rules.UsePatternGuards (usePatternGuardsRule)
95+import Purslint.Rules.RedundantParens (redundantParensRule)
96+import Purslint.Rule (Rule)
97+import Purslint.Runner (runRules, getImportsFromSource, getModuleNames)
98+import Purslint.Imports (hasValue, isPreludeModule)
99+import Purslint.Types (LintResult(..), LintWarning(..), RuleId(..), SourceCode(..), Suggestion(..))
26100
27101-- | Simple test framework
28102type TestResult = { passed :: Boolean, name :: String, message :: String }
@@ -83,12 +157,27 @@ assertFix name rules source expectedFixed =
83157 then runTest name true "OK"
84158 else runTest name false ("Expected:\n" <> expectedFixed <> "\nGot:\n" <> fixed)
85159
160+-- | Assert that applying fixes produces expected output AND result is tidy-formatted
161+assertFixFormatted :: String -> Array Rule -> String -> String -> Effect TestResult
162+assertFixFormatted name rules source expectedFixed =
163+ case runRules rules (SourceCode source) of
164+ Left err -> pure $ runTest name false ("Parse error: " <> err)
165+ Right result -> do
166+ let fixed = applyAllFixes source result
167+ -- Check that the fix output is already tidy-formatted
168+ tidied <- formatModule fixed
169+ if tidied /= fixed
170+ then pure $ runTest name false ("Fix output not tidy-formatted.\nGot:\n" <> fixed <> "\nTidy expects:\n" <> tidied)
171+ else if fixed /= expectedFixed
172+ then pure $ runTest name false ("Expected:\n" <> expectedFixed <> "\nGot:\n" <> fixed)
173+ else pure $ runTest name true "OK"
174+
86175-- | Run all tests and report results
87176main :: Effect Unit
88177main = do
89− log "Running purelint tests...\n"
178+ log "Running purslint tests...\n"
90179
91− let results = fmapIdTests
180+ let results = mapIdentityTests
92181 <> mapFusionTests
93182 <> useTraverseTests
94183 <> concatMapTests
@@ -98,12 +187,83 @@ main = do
98187 <> etaReduceDeclTests
99188 <> redundantBindTests
100189 <> letToWhereTests
190+ <> redundantIfTests
191+ <> functorLawTests
192+ <> useVoidTests
193+ <> useJoinTests
194+ <> useAnyTests
195+ <> useAllTests
196+ <> booleanSimplifyTests
197+ <> collapseLambdasTests
198+ <> useConstTests
199+ <> useNullTests
200+ <> useFromMaybeTests
201+ <> useIsJustTests
202+ <> useIsNothingTests
203+ <> useWhenTests
204+ <> useUnlessTests
205+ <> redundantFlipTests
206+ <> useNotElemTests
207+ <> useMinMaxTests
208+ <> monoidIdentityTests
209+ <> useFoldTests
210+ <> useSequenceTests
211+ <> useTraverseSequenceTests
212+ <> redundantNotTests
213+ <> useFstSndTests
214+ <> useMapMaybeTests
215+ <> useApplicativeTests
216+ <> useBindFlipTests
217+ <> useForTests
218+ <> redundantGuardTests
219+ <> useComparingTests
220+ <> useOnTests
221+ <> useFindMapTests
222+ <> useLastReverseTests
223+ <> redundantReverseTests
224+ <> useMinimumSortTests
225+ <> useBimapTests
226+ <> evaluateFstTests
227+ <> evaluateConstTests
228+ <> useOrTests
229+ <> useAndTests
230+ <> useFoldMapTests
231+ <> useApplyFlippedTests
232+ <> useFoldMapIdTests
233+ <> whenNotTests
234+ <> unlessNotTests
235+ <> useZipTests
236+ <> useReplicateTests
237+ <> useUncurryTests
238+ <> useFromJustTests
239+ <> useHeadTests
240+ <> useElemIndexTests
241+ <> useBreakTests
242+ <> useSpanTests
243+ <> useDollarTests
244+ <> useUnwrapTests
245+ <> redundantNegateTests
246+ <> redundantIdTests
247+ <> nothingBindTests
248+ <> monadLawTests
249+ <> evaluateBoolTests
250+ <> useFoldBoolTests
251+ <> alternativeLawTests
252+ <> evaluateEitherTests
253+ <> useCaseOfTests
254+ <> flattenCaseTests
255+ <> usePatternGuardsTests
256+ <> redundantParensTests
257+
258+ -- Run effectful golden tests (with purs-tidy check)
259+ goldenResults <- goldenTests
260+ let allResults = results <> goldenResults
101261
102− let passed = Array.length $ Array.filter _.passed results
103− let total = Array.length results
262+ let passed = Array.length $ Array.filter _.passed allResults
263+ let total = Array.length allResults
104264
105265 -- Report failures
106− let failures = Array.filter (not <<< _.passed) results
266+ let failures = Array.filter (not <<< _.passed) allResults
107267 when (Array.length failures > 0) do
108268 log "FAILURES:"
109269 for_ failures (\r -> log (" ✗ " <> r.name <> ": " <> r.message))
@@ -115,44 +275,56 @@ main = do
115275 when (passed < total) do
116276 log "\nSome tests failed!"
117277
278+ -- Run purs-tidy formatting tests
279+ log "\nRunning purs-tidy formatting tests..."
280+ tidyResults <- runTidyTests
281+ let tidyPassed = Array.length $ Array.filter _.passed tidyResults
282+ let tidyTotal = Array.length tidyResults
283+
284+ let tidyFailures = Array.filter (not <<< _.passed) tidyResults
285+ when (Array.length tidyFailures > 0) do
286+ log "TIDY FAILURES:"
287+ for_ tidyFailures (\r -> log (" ✗ " <> r.name <> ": " <> r.message))
288+ log ""
289+
290+ log $ "Tidy tests: " <> show tidyPassed <> "/" <> show tidyTotal <> " passed"
291+
118292-- | Helper: wrap code in a module with Prelude import
119293withPrelude :: String -> String
120−withPrelude code = "module Test where\nimport Prelude\n" <> code
294+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
121295
122296-- | Helper: wrap code in module without imports
123297withoutImports :: String -> String
124298withoutImports code = "module Test where\n" <> code
125299
126300-- ============================================================================
127−-- FmapId Tests
301+-- MapIdentity Tests
128302-- ============================================================================
129303
130−fmapIdTests :: Array TestResult
131−fmapIdTests =
304+mapIdentityTests :: Array TestResult
305+mapIdentityTests =
132306 -- Positive cases: should detect
133− [ assertWarningCount "FmapId: map identity x" [fmapIdRule]
307+ [ assertWarningCount "MapIdentity: map identity x" [mapIdentityRule]
134308 (withPrelude "x = map identity [1]") 1
135− , assertRuleId "FmapId: correct rule id" [fmapIdRule]
136− (withPrelude "x = map identity [1]") "FmapId"
137− , assertSuggestion "FmapId: map identity suggestion" [fmapIdRule]
138− (withPrelude "x = map identity [1]") "[1]"
139− , assertWarningCount "FmapId: fmap identity x" [fmapIdRule]
140− (withPrelude "x = fmap identity [1]") 1
141− , assertWarningCount "FmapId: identity <$> x" [fmapIdRule]
309+ , assertRuleId "MapIdentity: correct rule id" [mapIdentityRule]
310+ (withPrelude "x = map identity [1]") "MapIdentity"
311+ , assertSuggestion "MapIdentity: map identity suggestion" [mapIdentityRule]
312+ (withPrelude "x = map identity [1]") "[ 1 ]"
313+ , assertWarningCount "MapIdentity: identity <$> x" [mapIdentityRule]
142314 (withPrelude "x = identity <$> [1]") 1
143− , assertWarningCount "FmapId: x <#> identity" [fmapIdRule]
315+ , assertWarningCount "MapIdentity: x <#> identity" [mapIdentityRule]
144316 (withPrelude "x = [1] <#> identity") 1
145317
146318 -- Negative cases: should NOT detect
147− , assertWarningCount "FmapId: no fire on map show" [fmapIdRule]
319+ , assertWarningCount "MapIdentity: no fire on map show" [mapIdentityRule]
148320 (withPrelude "x = map show [1]") 0
149− , assertWarningCount "FmapId: no fire without import" [fmapIdRule]
321+ , assertWarningCount "MapIdentity: no fire without import" [mapIdentityRule]
150322 (withoutImports "x = map identity [1]") 0
151323
152324 -- Auto-fix test
153− , assertFix "FmapId: fix map identity" [fmapIdRule]
325+ , assertFix "MapIdentity: fix map identity" [mapIdentityRule]
154326 (withPrelude "x = map identity [1]")
155− (withPrelude "x = [1]")
327+ (withPrelude "x = [ 1 ]")
156328 ]
157329
158330-- ============================================================================
@@ -317,6 +489,12 @@ redundantBindTests =
317489 (withPrelude "x = [1] >>= pure") 1
318490 , assertRuleId "RedundantBind: correct rule id" [redundantBindRule]
319491 (withPrelude "x = [1] >>= pure") "RedundantBind"
492+ , assertSuggestion "RedundantBind: x >>= pure suggestion" [redundantBindRule]
493+ (withPrelude "x = [1] >>= pure") "[ 1 ]"
494+ , assertWarningCount "RedundantBind: pure x >>= f" [redundantBindRule]
495+ (withPrelude "x = pure 1 >>= show") 1
496+ , assertSuggestion "RedundantBind: pure x >>= f suggestion" [redundantBindRule]
497+ (withPrelude "x = pure 1 >>= show") "show 1"
320498
321499 -- Negative cases
322500 , assertWarningCount "RedundantBind: no fire on x >>= f" [redundantBindRule]
@@ -340,4 +518,1685 @@ letToWhereTests =
340518 -- Multiple bindings
341519 , assertWarningCount "LetToWhere: multiple bindings" [letToWhereRule]
342520 (withPrelude "x = let a = 1\n b = 2 in a + b") 1
521+
522+ -- Suggestion preserves spaces based on let column position
523+ , assertSuggestion "LetToWhere: preserves spaces in simple case" [letToWhereRule]
524+ (withPrelude "x = let y = 1 in y + 1") "y + 1\n where\n y = 1"
525+ , assertSuggestion "LetToWhere: preserves spaces in expressions" [letToWhereRule]
526+ (withPrelude "f x = let a = x + 1 in a * 2") "a * 2\n where\n a = x + 1"
527+ , assertSuggestion "LetToWhere: preserves spaces with multiple bindings" [letToWhereRule]
528+ (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"
529+ -- Nested let should preserve indentation context
530+ , assertSuggestion "LetToWhere: nested let preserves indentation" [letToWhereRule]
531+ (withPrelude "f x =\n let y = 1 in y + 1") "y + 1\n where\n y = 1"
532+ ]
533+
534+-- ============================================================================
535+-- RedundantIf Tests
536+-- ============================================================================
537+
538+redundantIfTests :: Array TestResult
539+redundantIfTests =
540+ -- Positive cases
541+ [ assertWarningCount "RedundantIf: if a then true else false" [redundantIfRule]
542+ (withPrelude "x = if foo then true else false") 1
543+ , assertRuleId "RedundantIf: correct rule id" [redundantIfRule]
544+ (withPrelude "x = if foo then true else false") "RedundantIf"
545+ , assertSuggestion "RedundantIf: suggests condition" [redundantIfRule]
546+ (withPrelude "x = if foo then true else false") "foo"
547+ , assertWarningCount "RedundantIf: if a then false else true" [redundantIfRule]
548+ (withPrelude "x = if foo then false else true") 1
549+ , assertSuggestion "RedundantIf: suggests not condition" [redundantIfRule]
550+ (withPrelude "x = if foo then false else true") "not foo"
551+
552+ -- Negative cases
553+ , assertWarningCount "RedundantIf: no fire on normal if" [redundantIfRule]
554+ (withPrelude "x = if foo then 1 else 2") 0
555+ , assertWarningCount "RedundantIf: no fire on if a then true else x" [redundantIfRule]
556+ (withPrelude "x = if foo then true else bar") 0
557+ ]
558+
559+-- ============================================================================
560+-- FunctorLaw Tests
561+-- ============================================================================
562+
563+functorLawTests :: Array TestResult
564+functorLawTests =
565+ -- Positive cases
566+ [ assertWarningCount "FunctorLaw: map f (map g x)" [functorLawRule]
567+ (withPrelude "x = map f (map g [1])") 1
568+ , assertRuleId "FunctorLaw: correct rule id" [functorLawRule]
569+ (withPrelude "x = map f (map g [1])") "FunctorLaw"
570+ , assertWarningCount "FunctorLaw: f <$> (g <$> x)" [functorLawRule]
571+ (withPrelude "x = f <$> (g <$> [1])") 1
572+
573+ -- Negative cases
574+ , assertWarningCount "FunctorLaw: no fire on single map" [functorLawRule]
575+ (withPrelude "x = map f [1]") 0
576+ , assertWarningCount "FunctorLaw: no fire without import" [functorLawRule]
577+ (withoutImports "x = map f (map g [1])") 0
578+ ]
579+
580+-- ============================================================================
581+-- UseVoid Tests
582+-- ============================================================================
583+
584+useVoidTests :: Array TestResult
585+useVoidTests =
586+ -- Positive cases
587+ [ assertWarningCount "UseVoid: a *> pure unit" [useVoidRule]
588+ (withPrelude "x = foo *> pure unit") 1
589+ , assertRuleId "UseVoid: correct rule id" [useVoidRule]
590+ (withPrelude "x = foo *> pure unit") "UseVoid"
591+ , assertSuggestion "UseVoid: suggests void" [useVoidRule]
592+ (withPrelude "x = foo *> pure unit") "void foo"
593+
594+ -- Negative cases
595+ , assertWarningCount "UseVoid: no fire on a *> pure x" [useVoidRule]
596+ (withPrelude "x = foo *> pure 1") 0
597+ , assertWarningCount "UseVoid: no fire without import" [useVoidRule]
598+ (withoutImports "x = foo *> pure unit") 0
599+ ]
600+
601+-- ============================================================================
602+-- UseJoin Tests
603+-- ============================================================================
604+
605+useJoinTests :: Array TestResult
606+useJoinTests =
607+ -- Positive cases
608+ [ assertWarningCount "UseJoin: x >>= identity" [useJoinRule]
609+ (withPrelude "x = foo >>= identity") 1
610+ , assertRuleId "UseJoin: correct rule id" [useJoinRule]
611+ (withPrelude "x = foo >>= identity") "UseJoin"
612+ , assertSuggestion "UseJoin: suggests join" [useJoinRule]
613+ (withPrelude "x = foo >>= identity") "join foo"
614+
615+ -- Negative cases
616+ , assertWarningCount "UseJoin: no fire on x >>= f" [useJoinRule]
617+ (withPrelude "x = foo >>= show") 0
618+ , assertWarningCount "UseJoin: no fire without import" [useJoinRule]
619+ (withoutImports "x = foo >>= identity") 0
620+ ]
621+
622+-- ============================================================================
623+-- UseAny Tests
624+-- ============================================================================
625+
626+useAnyTests :: Array TestResult
627+useAnyTests =
628+ -- Positive cases
629+ [ assertWarningCount "UseAny: or (map f x)" [useAnyRule]
630+ (withPrelude "x = or (map f [1])") 1
631+ , assertRuleId "UseAny: correct rule id" [useAnyRule]
632+ (withPrelude "x = or (map f [1])") "UseAny"
633+ , assertSuggestion "UseAny: suggests any" [useAnyRule]
634+ (withPrelude "x = or (map f [1])") "any f [ 1 ]"
635+
636+ -- Negative cases
637+ , assertWarningCount "UseAny: no fire on or alone" [useAnyRule]
638+ (withPrelude "x = or [true]") 0
639+ , assertWarningCount "UseAny: no fire without import" [useAnyRule]
640+ (withoutImports "x = or (map f [1])") 0
641+ ]
642+
643+-- ============================================================================
644+-- UseAll Tests
645+-- ============================================================================
646+
647+useAllTests :: Array TestResult
648+useAllTests =
649+ -- Positive cases
650+ [ assertWarningCount "UseAll: and (map f x)" [useAllRule]
651+ (withPrelude "x = and (map f [1])") 1
652+ , assertRuleId "UseAll: correct rule id" [useAllRule]
653+ (withPrelude "x = and (map f [1])") "UseAll"
654+ , assertSuggestion "UseAll: suggests all" [useAllRule]
655+ (withPrelude "x = and (map f [1])") "all f [ 1 ]"
656+
657+ -- Negative cases
658+ , assertWarningCount "UseAll: no fire on and alone" [useAllRule]
659+ (withPrelude "x = and [true]") 0
660+ , assertWarningCount "UseAll: no fire without import" [useAllRule]
661+ (withoutImports "x = and (map f [1])") 0
662+ ]
663+
664+-- ============================================================================
665+-- BooleanSimplify Tests
666+-- ============================================================================
667+
668+booleanSimplifyTests :: Array TestResult
669+booleanSimplifyTests =
670+ -- Positive cases
671+ [ assertWarningCount "BooleanSimplify: x == true" [booleanSimplifyRule]
672+ (withPrelude "x = foo == true") 1
673+ , assertRuleId "BooleanSimplify: correct rule id" [booleanSimplifyRule]
674+ (withPrelude "x = foo == true") "BooleanSimplify"
675+ , assertSuggestion "BooleanSimplify: x == true -> x" [booleanSimplifyRule]
676+ (withPrelude "x = foo == true") "foo"
677+ , assertWarningCount "BooleanSimplify: x == false" [booleanSimplifyRule]
678+ (withPrelude "x = foo == false") 1
679+ , assertSuggestion "BooleanSimplify: x == false -> not x" [booleanSimplifyRule]
680+ (withPrelude "x = foo == false") "not foo"
681+ , assertWarningCount "BooleanSimplify: true == x" [booleanSimplifyRule]
682+ (withPrelude "x = true == foo") 1
683+ , assertWarningCount "BooleanSimplify: false == x" [booleanSimplifyRule]
684+ (withPrelude "x = false == foo") 1
685+
686+ -- Negative cases
687+ , assertWarningCount "BooleanSimplify: no fire on x == y" [booleanSimplifyRule]
688+ (withPrelude "x = foo == bar") 0
689+ , assertWarningCount "BooleanSimplify: no fire without import" [booleanSimplifyRule]
690+ (withoutImports "x = foo == true") 0
691+ ]
692+
693+-- ============================================================================
694+-- CollapseLambdas Tests
695+-- ============================================================================
696+
697+collapseLambdasTests :: Array TestResult
698+collapseLambdasTests =
699+ -- Positive cases
700+ [ assertWarningCount "CollapseLambdas: \\x -> \\y -> body" [collapseLambdasRule]
701+ (withPrelude "x = \\a -> \\b -> a + b") 1
702+ , assertRuleId "CollapseLambdas: correct rule id" [collapseLambdasRule]
703+ (withPrelude "x = \\a -> \\b -> a + b") "CollapseLambdas"
704+ , assertSuggestion "CollapseLambdas: suggests collapsed" [collapseLambdasRule]
705+ (withPrelude "x = \\a -> \\b -> a + b") "\\a b -> a + b"
706+
707+ -- Negative cases
708+ , assertWarningCount "CollapseLambdas: no fire on single lambda" [collapseLambdasRule]
709+ (withPrelude "x = \\a -> a + 1") 0
710+ ]
711+
712+-- ============================================================================
713+-- UseConst Tests
714+-- ============================================================================
715+
716+useConstTests :: Array TestResult
717+useConstTests =
718+ -- Positive cases: only fires on wildcard binder \_
719+ [ assertWarningCount "UseConst: \\_ -> y" [useConstRule]
720+ (withPrelude "x = \\_ -> 42") 1
721+ , assertRuleId "UseConst: correct rule id" [useConstRule]
722+ (withPrelude "x = \\_ -> 42") "UseConst"
723+ , assertSuggestion "UseConst: suggests const" [useConstRule]
724+ (withPrelude "x = \\_ -> 42") "const 42"
725+
726+ -- Negative cases
727+ , assertWarningCount "UseConst: no fire on named param (compiler warns)" [useConstRule]
728+ (withPrelude "x = \\a -> 42") 0
729+ , assertWarningCount "UseConst: no fire when param used" [useConstRule]
730+ (withPrelude "x = \\a -> a + 1") 0
731+ , assertWarningCount "UseConst: no fire without import" [useConstRule]
732+ (withoutImports "x = \\_ -> 42") 0
733+ ]
734+
735+-- ============================================================================
736+-- UseNull Tests
737+-- ============================================================================
738+
739+useNullTests :: Array TestResult
740+useNullTests =
741+ -- Positive cases
742+ [ assertWarningCount "UseNull: length x == 0" [useNullRule]
743+ (withPrelude "x = length [1] == 0") 1
744+ , assertRuleId "UseNull: correct rule id" [useNullRule]
745+ (withPrelude "x = length [1] == 0") "UseNull"
746+ , assertSuggestion "UseNull: suggests null" [useNullRule]
747+ (withPrelude "x = length [1] == 0") "null [ 1 ]"
748+ , assertWarningCount "UseNull: 0 == length x" [useNullRule]
749+ (withPrelude "x = 0 == length [1]") 1
750+
751+ -- Negative cases
752+ , assertWarningCount "UseNull: no fire on length x == 1" [useNullRule]
753+ (withPrelude "x = length [1] == 1") 0
754+ , assertWarningCount "UseNull: no fire without import" [useNullRule]
755+ (withoutImports "x = length [1] == 0") 0
756+ ]
757+
758+-- ============================================================================
759+-- UseFromMaybe Tests
760+-- ============================================================================
761+
762+useFromMaybeTests :: Array TestResult
763+useFromMaybeTests =
764+ [ assertWarningCount "UseFromMaybe: maybe x identity" [useFromMaybeRule]
765+ (withPrelude "x = maybe 0 identity") 1
766+ , assertRuleId "UseFromMaybe: correct rule id" [useFromMaybeRule]
767+ (withPrelude "x = maybe 0 identity") "UseFromMaybe"
768+ , assertSuggestion "UseFromMaybe: suggests fromMaybe" [useFromMaybeRule]
769+ (withPrelude "x = maybe 0 identity") "fromMaybe 0"
770+
771+ -- Negative cases
772+ , assertWarningCount "UseFromMaybe: no fire on maybe x f" [useFromMaybeRule]
773+ (withPrelude "x = maybe 0 show") 0
774+ ]
775+
776+-- ============================================================================
777+-- UseIsJust Tests
778+-- ============================================================================
779+
780+useIsJustTests :: Array TestResult
781+useIsJustTests =
782+ [ assertWarningCount "UseIsJust: maybe false (const true)" [useIsJustRule]
783+ (withPrelude "x = maybe false (const true)") 1
784+ , assertRuleId "UseIsJust: correct rule id" [useIsJustRule]
785+ (withPrelude "x = maybe false (const true)") "UseIsJust"
786+ , assertSuggestion "UseIsJust: suggests isJust" [useIsJustRule]
787+ (withPrelude "x = maybe false (const true)") "isJust"
788+
789+ -- Negative cases
790+ , assertWarningCount "UseIsJust: no fire on maybe true (const false)" [useIsJustRule]
791+ (withPrelude "x = maybe true (const false)") 0
792+ ]
793+
794+-- ============================================================================
795+-- UseIsNothing Tests
796+-- ============================================================================
797+
798+useIsNothingTests :: Array TestResult
799+useIsNothingTests =
800+ [ assertWarningCount "UseIsNothing: maybe true (const false)" [useIsNothingRule]
801+ (withPrelude "x = maybe true (const false)") 1
802+ , assertRuleId "UseIsNothing: correct rule id" [useIsNothingRule]
803+ (withPrelude "x = maybe true (const false)") "UseIsNothing"
804+ , assertSuggestion "UseIsNothing: suggests isNothing" [useIsNothingRule]
805+ (withPrelude "x = maybe true (const false)") "isNothing"
806+
807+ -- Negative cases
808+ , assertWarningCount "UseIsNothing: no fire on maybe false (const true)" [useIsNothingRule]
809+ (withPrelude "x = maybe false (const true)") 0
810+ ]
811+
812+-- ============================================================================
813+-- UseWhen Tests
814+-- ============================================================================
815+
816+useWhenTests :: Array TestResult
817+useWhenTests =
818+ [ assertWarningCount "UseWhen: if x then y else pure unit" [useWhenRule]
819+ (withPrelude "x = if true then log \"hi\" else pure unit") 1
820+ , assertRuleId "UseWhen: correct rule id" [useWhenRule]
821+ (withPrelude "x = if true then log \"hi\" else pure unit") "UseWhen"
822+
823+ -- Negative cases
824+ , assertWarningCount "UseWhen: no fire on if x then pure unit else y" [useWhenRule]
825+ (withPrelude "x = if true then pure unit else log \"hi\"") 0
826+ ]
827+
828+-- ============================================================================
829+-- UseUnless Tests
830+-- ============================================================================
831+
832+useUnlessTests :: Array TestResult
833+useUnlessTests =
834+ [ assertWarningCount "UseUnless: if x then pure unit else y" [useUnlessRule]
835+ (withPrelude "x = if true then pure unit else log \"hi\"") 1
836+ , assertRuleId "UseUnless: correct rule id" [useUnlessRule]
837+ (withPrelude "x = if true then pure unit else log \"hi\"") "UseUnless"
838+
839+ -- Negative cases
840+ , assertWarningCount "UseUnless: no fire on if x then y else pure unit" [useUnlessRule]
841+ (withPrelude "x = if true then log \"hi\" else pure unit") 0
842+ ]
843+
844+-- ============================================================================
845+-- RedundantFlip Tests
846+-- ============================================================================
847+
848+redundantFlipTests :: Array TestResult
849+redundantFlipTests =
850+ [ assertWarningCount "RedundantFlip: flip (flip f)" [redundantFlipRule]
851+ (withPrelude "x = flip (flip add)") 1
852+ , assertRuleId "RedundantFlip: correct rule id" [redundantFlipRule]
853+ (withPrelude "x = flip (flip add)") "RedundantFlip"
854+ , assertSuggestion "RedundantFlip: suggests f" [redundantFlipRule]
855+ (withPrelude "x = flip (flip add)") "add"
856+
857+ -- Negative cases
858+ , assertWarningCount "RedundantFlip: no fire on flip f" [redundantFlipRule]
859+ (withPrelude "x = flip add") 0
860+ ]
861+
862+-- ============================================================================
863+-- UseNotElem Tests
864+-- ============================================================================
865+
866+useNotElemTests :: Array TestResult
867+useNotElemTests =
868+ [ assertWarningCount "UseNotElem: not (elem x y)" [useNotElemRule]
869+ (withPrelude "x = not (elem 1 [1,2,3])") 1
870+ , assertRuleId "UseNotElem: correct rule id" [useNotElemRule]
871+ (withPrelude "x = not (elem 1 [1,2,3])") "UseNotElem"
872+ , assertSuggestion "UseNotElem: suggests notElem" [useNotElemRule]
873+ (withPrelude "x = not (elem 1 [1,2,3])") "notElem 1 [ 1, 2, 3 ]"
874+
875+ -- Negative cases
876+ , assertWarningCount "UseNotElem: no fire on not (foo x)" [useNotElemRule]
877+ (withPrelude "x = not (foo 1)") 0
878+ ]
879+
880+-- ============================================================================
881+-- UseMinMax Tests
882+-- ============================================================================
883+
884+useMinMaxTests :: Array TestResult
885+useMinMaxTests =
886+ [ assertWarningCount "UseMinMax: if a > b then a else b" [useMinMaxRule]
887+ (withPrelude "x = if a > b then a else b") 1
888+ , assertRuleId "UseMinMax: correct rule id" [useMinMaxRule]
889+ (withPrelude "x = if a > b then a else b") "UseMinMax"
890+ , assertSuggestion "UseMinMax: suggests max" [useMinMaxRule]
891+ (withPrelude "x = if a > b then a else b") "max a b"
892+ , assertWarningCount "UseMinMax: if a < b then a else b -> min" [useMinMaxRule]
893+ (withPrelude "x = if a < b then a else b") 1
894+ , assertSuggestion "UseMinMax: suggests min" [useMinMaxRule]
895+ (withPrelude "x = if a < b then a else b") "min a b"
896+
897+ -- Negative cases
898+ , assertWarningCount "UseMinMax: no fire on unrelated if" [useMinMaxRule]
899+ (withPrelude "x = if a > b then c else d") 0
900+ ]
901+
902+-- ============================================================================
903+-- MonoidIdentity Tests
904+-- ============================================================================
905+
906+monoidIdentityTests :: Array TestResult
907+monoidIdentityTests =
908+ [ assertWarningCount "MonoidIdentity: mempty <> x" [monoidIdentityRule]
909+ (withPrelude "x = mempty <> [1,2,3]") 1
910+ , assertRuleId "MonoidIdentity: correct rule id" [monoidIdentityRule]
911+ (withPrelude "x = mempty <> [1,2,3]") "MonoidIdentity"
912+ , assertSuggestion "MonoidIdentity: suggests x" [monoidIdentityRule]
913+ (withPrelude "x = mempty <> [1,2,3]") "[ 1, 2, 3 ]"
914+ , assertWarningCount "MonoidIdentity: x <> mempty" [monoidIdentityRule]
915+ (withPrelude "x = [1,2,3] <> mempty") 1
916+
917+ -- Negative cases
918+ , assertWarningCount "MonoidIdentity: no fire on x <> y" [monoidIdentityRule]
919+ (withPrelude "x = [1] <> [2]") 0
920+ ]
921+
922+-- ============================================================================
923+-- UseFold Tests
924+-- ============================================================================
925+
926+useFoldTests :: Array TestResult
927+useFoldTests =
928+ [ assertWarningCount "UseFold: foldMap identity" [useFoldRule]
929+ (withPrelude "x = foldMap identity") 1
930+ , assertRuleId "UseFold: correct rule id" [useFoldRule]
931+ (withPrelude "x = foldMap identity") "UseFold"
932+ , assertSuggestion "UseFold: suggests fold" [useFoldRule]
933+ (withPrelude "x = foldMap identity") "fold"
934+
935+ -- Negative cases
936+ , assertWarningCount "UseFold: no fire on foldMap f" [useFoldRule]
937+ (withPrelude "x = foldMap show") 0
938+ ]
939+
940+-- ============================================================================
941+-- UseSequence Tests
942+-- ============================================================================
943+
944+useSequenceTests :: Array TestResult
945+useSequenceTests =
946+ [ assertWarningCount "UseSequence: traverse identity" [useSequenceRule]
947+ (withPrelude "x = traverse identity") 1
948+ , assertRuleId "UseSequence: correct rule id" [useSequenceRule]
949+ (withPrelude "x = traverse identity") "UseSequence"
950+ , assertSuggestion "UseSequence: suggests sequence" [useSequenceRule]
951+ (withPrelude "x = traverse identity") "sequence"
952+
953+ -- Negative cases
954+ , assertWarningCount "UseSequence: no fire on traverse f" [useSequenceRule]
955+ (withPrelude "x = traverse show") 0
956+ ]
957+
958+-- ============================================================================
959+-- UseTraverseSequence Tests
960+-- ============================================================================
961+
962+useTraverseSequenceTests :: Array TestResult
963+useTraverseSequenceTests =
964+ [ assertWarningCount "UseTraverseSequence: sequence (map f x)" [useTraverseSequenceRule]
965+ (withPrelude "x = sequence (map show [1,2,3])") 1
966+ , assertRuleId "UseTraverseSequence: correct rule id" [useTraverseSequenceRule]
967+ (withPrelude "x = sequence (map show [1,2,3])") "UseTraverseSequence"
968+ , assertSuggestion "UseTraverseSequence: suggests traverse" [useTraverseSequenceRule]
969+ (withPrelude "x = sequence (map show [1,2,3])") "traverse show [ 1, 2, 3 ]"
970+
971+ -- Negative cases
972+ , assertWarningCount "UseTraverseSequence: no fire on sequence xs" [useTraverseSequenceRule]
973+ (withPrelude "x = sequence [Just 1]") 0
974+ ]
975+
976+-- ============================================================================
977+-- RedundantNot Tests
978+-- ============================================================================
979+
980+redundantNotTests :: Array TestResult
981+redundantNotTests =
982+ [ assertWarningCount "RedundantNot: not (not x)" [redundantNotRule]
983+ (withPrelude "x = not (not true)") 1
984+ , assertRuleId "RedundantNot: correct rule id" [redundantNotRule]
985+ (withPrelude "x = not (not true)") "RedundantNot"
986+ , assertSuggestion "RedundantNot: suggests x" [redundantNotRule]
987+ (withPrelude "x = not (not foo)") "foo"
988+
989+ -- Negative cases
990+ , assertWarningCount "RedundantNot: no fire on not x" [redundantNotRule]
991+ (withPrelude "x = not true") 0
992+ ]
993+
994+-- ============================================================================
995+-- UseFstSnd Tests
996+-- ============================================================================
997+
998+useFstSndTests :: Array TestResult
999+useFstSndTests =
1000+ [ assertWarningCount "UseFstSnd: \\(Tuple x y) -> x" [useFstSndRule]
1001+ (withPrelude "x = \\(Tuple a b) -> a") 1
1002+ , assertRuleId "UseFstSnd: correct rule id" [useFstSndRule]
1003+ (withPrelude "x = \\(Tuple a b) -> a") "UseFstSnd"
1004+ , assertSuggestion "UseFstSnd: suggests fst" [useFstSndRule]
1005+ (withPrelude "x = \\(Tuple a b) -> a") "fst"
1006+ , assertWarningCount "UseFstSnd: \\(Tuple x y) -> y" [useFstSndRule]
1007+ (withPrelude "x = \\(Tuple a b) -> b") 1
1008+ , assertSuggestion "UseFstSnd: suggests snd" [useFstSndRule]
1009+ (withPrelude "x = \\(Tuple a b) -> b") "snd"
1010+
1011+ -- Negative cases
1012+ , assertWarningCount "UseFstSnd: no fire when using both" [useFstSndRule]
1013+ (withPrelude "x = \\(Tuple a b) -> a + b") 0
1014+ ]
1015+
1016+-- ============================================================================
1017+-- UseMapMaybe Tests
1018+-- ============================================================================
1019+
1020+useMapMaybeTests :: Array TestResult
1021+useMapMaybeTests =
1022+ [ assertWarningCount "UseMapMaybe: catMaybes (map f x)" [useMapMaybeRule]
1023+ (withPrelude "x = catMaybes (map f [1,2,3])") 1
1024+ , assertRuleId "UseMapMaybe: correct rule id" [useMapMaybeRule]
1025+ (withPrelude "x = catMaybes (map f [1,2,3])") "UseMapMaybe"
1026+ , assertSuggestion "UseMapMaybe: suggests mapMaybe" [useMapMaybeRule]
1027+ (withPrelude "x = catMaybes (map f [1,2,3])") "mapMaybe f [ 1, 2, 3 ]"
1028+ ]
1029+
1030+-- ============================================================================
1031+-- UseApplicative Tests
1032+-- ============================================================================
1033+
1034+useApplicativeTests :: Array TestResult
1035+useApplicativeTests =
1036+ [ assertWarningCount "UseApplicative: pure f <*> x" [useApplicativeRule]
1037+ (withPrelude "x = pure f <*> y") 1
1038+ , assertRuleId "UseApplicative: correct rule id" [useApplicativeRule]
1039+ (withPrelude "x = pure f <*> y") "UseApplicative"
1040+ , assertSuggestion "UseApplicative: suggests f <$> x" [useApplicativeRule]
1041+ (withPrelude "x = pure f <*> y") "f <$> y"
1042+ ]
1043+
1044+-- ============================================================================
1045+-- UseBindFlip Tests
1046+-- ============================================================================
1047+
1048+useBindFlipTests :: Array TestResult
1049+useBindFlipTests =
1050+ [ assertWarningCount "UseBindFlip: join (map f x)" [useBindFlipRule]
1051+ (withPrelude "x = join (map f y)") 1
1052+ , assertRuleId "UseBindFlip: correct rule id" [useBindFlipRule]
1053+ (withPrelude "x = join (map f y)") "UseBindFlip"
1054+ , assertSuggestion "UseBindFlip: suggests x >>= f" [useBindFlipRule]
1055+ (withPrelude "x = join (map f y)") "y >>= f"
1056+ ]
1057+
1058+-- ============================================================================
1059+-- UseFor Tests
1060+-- ============================================================================
1061+
1062+useForTests :: Array TestResult
1063+useForTests =
1064+ [ assertWarningCount "UseFor: flip traverse" [useForRule]
1065+ (withPrelude "x = flip traverse") 1
1066+ , assertRuleId "UseFor: correct rule id" [useForRule]
1067+ (withPrelude "x = flip traverse") "UseFor"
1068+ , assertSuggestion "UseFor: suggests for" [useForRule]
1069+ (withPrelude "x = flip traverse") "for"
1070+ , assertWarningCount "UseFor: flip traverse_" [useForRule]
1071+ (withPrelude "x = flip traverse_") 1
1072+ , assertSuggestion "UseFor: suggests for_" [useForRule]
1073+ (withPrelude "x = flip traverse_") "for_"
1074+ ]
1075+
1076+-- ============================================================================
1077+-- RedundantGuard Tests
1078+-- ============================================================================
1079+
1080+redundantGuardTests :: Array TestResult
1081+redundantGuardTests =
1082+ [ assertWarningCount "RedundantGuard: guard true" [redundantGuardRule]
1083+ (withPrelude "x = guard true") 1
1084+ , assertRuleId "RedundantGuard: correct rule id" [redundantGuardRule]
1085+ (withPrelude "x = guard true") "RedundantGuard"
1086+ , assertSuggestion "RedundantGuard: suggests pure unit" [redundantGuardRule]
1087+ (withPrelude "x = guard true") "pure unit"
1088+ , assertWarningCount "RedundantGuard: guard false" [redundantGuardRule]
1089+ (withPrelude "x = guard false") 1
1090+ ]
1091+
1092+-- ============================================================================
1093+-- UseComparing Tests
1094+-- ============================================================================
1095+
1096+useComparingTests :: Array TestResult
1097+useComparingTests =
1098+ [ assertWarningCount "UseComparing: \\x y -> compare (f x) (f y)" [useComparingRule]
1099+ (withPrelude "x = \\a b -> compare (f a) (f b)") 1
1100+ , assertRuleId "UseComparing: correct rule id" [useComparingRule]
1101+ (withPrelude "x = \\a b -> compare (f a) (f b)") "UseComparing"
1102+ , assertSuggestion "UseComparing: suggests comparing" [useComparingRule]
1103+ (withPrelude "x = \\a b -> compare (f a) (f b)") "comparing f"
1104+ ]
1105+
1106+-- ============================================================================
1107+-- UseOn Tests
1108+-- ============================================================================
1109+
1110+useOnTests :: Array TestResult
1111+useOnTests =
1112+ [ assertWarningCount "UseOn: \\x y -> f (g x) (g y)" [useOnRule]
1113+ (withPrelude "x = \\a b -> f (g a) (g b)") 1
1114+ , assertRuleId "UseOn: correct rule id" [useOnRule]
1115+ (withPrelude "x = \\a b -> f (g a) (g b)") "UseOn"
1116+ , assertSuggestion "UseOn: suggests on f g" [useOnRule]
1117+ (withPrelude "x = \\a b -> f (g a) (g b)") "on f g"
1118+ ]
1119+
1120+-- ============================================================================
1121+-- UseFindMap Tests
1122+-- ============================================================================
1123+
1124+useFindMapTests :: Array TestResult
1125+useFindMapTests =
1126+ [ assertWarningCount "UseFindMap: head (mapMaybe f x)" [useFindMapRule]
1127+ (withPrelude "x = head (mapMaybe f [1,2,3])") 1
1128+ , assertRuleId "UseFindMap: correct rule id" [useFindMapRule]
1129+ (withPrelude "x = head (mapMaybe f [1,2,3])") "UseFindMap"
1130+ , assertSuggestion "UseFindMap: suggests findMap" [useFindMapRule]
1131+ (withPrelude "x = head (mapMaybe f [1,2,3])") "findMap f [ 1, 2, 3 ]"
1132+ ]
1133+
1134+-- ============================================================================
1135+-- UseLastReverse Tests
1136+-- ============================================================================
1137+
1138+useLastReverseTests :: Array TestResult
1139+useLastReverseTests =
1140+ [ assertWarningCount "UseLastReverse: head (reverse x)" [useLastReverseRule]
1141+ (withPrelude "x = head (reverse [1,2,3])") 1
1142+ , assertRuleId "UseLastReverse: correct rule id" [useLastReverseRule]
1143+ (withPrelude "x = head (reverse [1,2,3])") "UseLastReverse"
1144+ , assertSuggestion "UseLastReverse: suggests last" [useLastReverseRule]
1145+ (withPrelude "x = head (reverse [1,2,3])") "last [ 1, 2, 3 ]"
1146+ ]
1147+
1148+-- ============================================================================
1149+-- RedundantReverse Tests
1150+-- ============================================================================
1151+
1152+redundantReverseTests :: Array TestResult
1153+redundantReverseTests =
1154+ [ assertWarningCount "RedundantReverse: reverse (reverse x)" [redundantReverseRule]
1155+ (withPrelude "x = reverse (reverse [1,2,3])") 1
1156+ , assertRuleId "RedundantReverse: correct rule id" [redundantReverseRule]
1157+ (withPrelude "x = reverse (reverse [1,2,3])") "RedundantReverse"
1158+ , assertSuggestion "RedundantReverse: suggests x" [redundantReverseRule]
1159+ (withPrelude "x = reverse (reverse foo)") "foo"
1160+ ]
1161+
1162+-- ============================================================================
1163+-- UseMinimumSort Tests
1164+-- ============================================================================
1165+
1166+useMinimumSortTests :: Array TestResult
1167+useMinimumSortTests =
1168+ [ assertWarningCount "UseMinimumSort: head (sort x)" [useMinimumSortRule]
1169+ (withPrelude "x = head (sort [1,2,3])") 1
1170+ , assertRuleId "UseMinimumSort: correct rule id" [useMinimumSortRule]
1171+ (withPrelude "x = head (sort [1,2,3])") "UseMinimumSort"
1172+ , assertSuggestion "UseMinimumSort: suggests minimum" [useMinimumSortRule]
1173+ (withPrelude "x = head (sort [1,2,3])") "minimum [ 1, 2, 3 ]"
3431174 ]
1175+
1176+-- ============================================================================
1177+-- UseBimap Tests
1178+-- ============================================================================
1179+
1180+useBimapTests :: Array TestResult
1181+useBimapTests =
1182+ [ assertWarningCount "UseBimap: bimap id g" [useBimapRule]
1183+ (withPrelude "x = bimap identity show") 1
1184+ , assertRuleId "UseBimap: correct rule id" [useBimapRule]
1185+ (withPrelude "x = bimap identity show") "UseBimap"
1186+ , assertSuggestion "UseBimap: suggests second" [useBimapRule]
1187+ (withPrelude "x = bimap identity show") "second show"
1188+ ]
1189+
1190+-- ============================================================================
1191+-- EvaluateFst Tests
1192+-- ============================================================================
1193+
1194+evaluateFstTests :: Array TestResult
1195+evaluateFstTests =
1196+ [ assertWarningCount "EvaluateFst: fst (Tuple x y)" [evaluateFstRule]
1197+ (withPrelude "x = fst (Tuple 1 2)") 1
1198+ , assertRuleId "EvaluateFst: correct rule id" [evaluateFstRule]
1199+ (withPrelude "x = fst (Tuple 1 2)") "EvaluateFst"
1200+ , assertSuggestion "EvaluateFst: suggests x" [evaluateFstRule]
1201+ (withPrelude "x = fst (Tuple 1 2)") "1"
1202+ , assertWarningCount "EvaluateFst: snd (Tuple x y)" [evaluateFstRule]
1203+ (withPrelude "x = snd (Tuple 1 2)") 1
1204+ , assertSuggestion "EvaluateFst: suggests y" [evaluateFstRule]
1205+ (withPrelude "x = snd (Tuple 1 2)") "2"
1206+ ]
1207+
1208+-- ============================================================================
1209+-- EvaluateConst Tests
1210+-- ============================================================================
1211+
1212+evaluateConstTests :: Array TestResult
1213+evaluateConstTests =
1214+ [ assertWarningCount "EvaluateConst: const x y" [evaluateConstRule]
1215+ (withPrelude "x = const 42 foo") 1
1216+ , assertRuleId "EvaluateConst: correct rule id" [evaluateConstRule]
1217+ (withPrelude "x = const 42 foo") "EvaluateConst"
1218+ , assertSuggestion "EvaluateConst: suggests x" [evaluateConstRule]
1219+ (withPrelude "x = const 42 foo") "42"
1220+ ]
1221+
1222+-- ============================================================================
1223+-- UseOr Tests
1224+-- ============================================================================
1225+
1226+useOrTests :: Array TestResult
1227+useOrTests =
1228+ [ assertWarningCount "UseOr: any identity" [useOrRule]
1229+ (withPrelude "x = any identity [true, false]") 1
1230+ , assertRuleId "UseOr: correct rule id" [useOrRule]
1231+ (withPrelude "x = any identity [true, false]") "UseOr"
1232+ , assertSuggestion "UseOr: suggests or" [useOrRule]
1233+ (withPrelude "x = any identity [true, false]") "or [ true, false ]"
1234+ ]
1235+
1236+-- ============================================================================
1237+-- UseAnd Tests
1238+-- ============================================================================
1239+
1240+useAndTests :: Array TestResult
1241+useAndTests =
1242+ [ assertWarningCount "UseAnd: all identity" [useAndRule]
1243+ (withPrelude "x = all identity [true, false]") 1
1244+ , assertRuleId "UseAnd: correct rule id" [useAndRule]
1245+ (withPrelude "x = all identity [true, false]") "UseAnd"
1246+ , assertSuggestion "UseAnd: suggests and" [useAndRule]
1247+ (withPrelude "x = all identity [true, false]") "and [ true, false ]"
1248+ ]
1249+
1250+-- ============================================================================
1251+-- UseFoldMap Tests
1252+-- ============================================================================
1253+
1254+useFoldMapTests :: Array TestResult
1255+useFoldMapTests =
1256+ [ assertWarningCount "UseFoldMap: case Nothing/Just with empty string" [useFoldMapRule]
1257+ (withPrelude "x mx = case mx of\n Nothing -> \"\"\n Just s -> f s") 1
1258+ , assertRuleId "UseFoldMap: correct rule id" [useFoldMapRule]
1259+ (withPrelude "x mx = case mx of\n Nothing -> \"\"\n Just s -> f s") "UseFoldMap"
1260+ , assertSuggestion "UseFoldMap: suggests foldMap f" [useFoldMapRule]
1261+ (withPrelude "x mx = case mx of\n Nothing -> \"\"\n Just s -> f s") "foldMap f mx"
1262+ , assertSuggestion "UseFoldMap: suggests lambda for complex body" [useFoldMapRule]
1263+ (withPrelude "x mx = case mx of\n Nothing -> \"\"\n Just s -> s <> \"!\"") "foldMap (\\s -> s <> \"!\") mx"
1264+ , assertWarningCount "UseFoldMap: reversed order Just/Nothing" [useFoldMapRule]
1265+ (withPrelude "x mx = case mx of\n Just s -> f s\n Nothing -> \"\"") 1
1266+
1267+ -- Complex scrutinee should be wrapped in parens
1268+ , assertSuggestion "UseFoldMap: complex scrutinee gets parens" [useFoldMapRule]
1269+ (withPrelude "x f mx = case f mx of\n Nothing -> \"\"\n Just s -> g s") "foldMap g (f mx)"
1270+
1271+ -- Negative cases: should NOT detect
1272+ , assertWarningCount "UseFoldMap: no fire on guarded Just branch" [useFoldMapRule]
1273+ (withPrelude "x mx = case mx of\n Just s | s > \"\" -> f s\n _ -> \"\"") 0
1274+ , assertWarningCount "UseFoldMap: no fire on where binding in Just branch" [useFoldMapRule]
1275+ (withPrelude "x mx = case mx of\n Just s -> g s where g = f\n Nothing -> \"\"") 0
1276+
1277+ -- Multiline body inside if-then-else should generate proper suggestion with correct indentation
1278+ , assertSuggestion "UseFoldMap: multiline in if-then-else" [useFoldMapRule]
1279+ (withPrelude "checkExpr args = case args of\n [firstArg, secondArg] ->\n if isLeft firstArg then\n case getRightComposed secondArg of\n Just f ->\n let fText = show f\n in\n [ fText ]\n Nothing -> []\n else []") "foldMap\n ( \\f ->\n let fText = show f\n in\n [ fText ]\n )\n (getRightComposed secondArg)"
1280+ ]
1281+
1282+-- ============================================================================
1283+-- UseApplyFlipped Tests
1284+-- ============================================================================
1285+
1286+useApplyFlippedTests :: Array TestResult
1287+useApplyFlippedTests =
1288+ [ assertWarningCount "UseApplyFlipped: f lambda arg" [useApplyFlippedRule]
1289+ (withPrelude "x = foldMap (\\y -> y <> \"!\") xs") 1
1290+ , assertRuleId "UseApplyFlipped: correct rule id" [useApplyFlippedRule]
1291+ (withPrelude "x = foldMap (\\y -> y <> \"!\") xs") "UseApplyFlipped"
1292+ , assertSuggestion "UseApplyFlipped: suggests # form" [useApplyFlippedRule]
1293+ (withPrelude "x = foldMap (\\y -> y <> \"!\") xs") "xs # foldMap \\y -> y <> \"!\""
1294+ ]
1295+
1296+-- ============================================================================
1297+-- UseFoldMapId Tests
1298+-- ============================================================================
1299+
1300+useFoldMapIdTests :: Array TestResult
1301+useFoldMapIdTests =
1302+ [ assertWarningCount "UseFoldMapId: foldMap identity" [useFoldMapIdRule]
1303+ (withPrelude "x = foldMap identity") 1
1304+ , assertRuleId "UseFoldMapId: correct rule id" [useFoldMapIdRule]
1305+ (withPrelude "x = foldMap identity") "UseFoldMapId"
1306+ , assertSuggestion "UseFoldMapId: suggests fold" [useFoldMapIdRule]
1307+ (withPrelude "x = foldMap identity") "fold"
1308+ ]
1309+
1310+-- ============================================================================
1311+-- WhenNot Tests
1312+-- ============================================================================
1313+
1314+whenNotTests :: Array TestResult
1315+whenNotTests =
1316+ [ assertWarningCount "WhenNot: when (not x)" [whenNotRule]
1317+ (withPrelude "x = when (not cond) action") 1
1318+ , assertRuleId "WhenNot: correct rule id" [whenNotRule]
1319+ (withPrelude "x = when (not cond) action") "WhenNot"
1320+ , assertSuggestion "WhenNot: suggests unless" [whenNotRule]
1321+ (withPrelude "x = when (not cond) action") "unless cond action"
1322+ ]
1323+
1324+-- ============================================================================
1325+-- UnlessNot Tests
1326+-- ============================================================================
1327+
1328+unlessNotTests :: Array TestResult
1329+unlessNotTests =
1330+ [ assertWarningCount "UnlessNot: unless (not x)" [unlessNotRule]
1331+ (withPrelude "x = unless (not cond) action") 1
1332+ , assertRuleId "UnlessNot: correct rule id" [unlessNotRule]
1333+ (withPrelude "x = unless (not cond) action") "UnlessNot"
1334+ , assertSuggestion "UnlessNot: suggests when" [unlessNotRule]
1335+ (withPrelude "x = unless (not cond) action") "when cond action"
1336+ ]
1337+
1338+-- ============================================================================
1339+-- UseZip Tests
1340+-- ============================================================================
1341+
1342+useZipTests :: Array TestResult
1343+useZipTests =
1344+ [ assertWarningCount "UseZip: zipWith Tuple" [useZipRule]
1345+ (withPrelude "x = zipWith Tuple [1] [2]") 1
1346+ , assertRuleId "UseZip: correct rule id" [useZipRule]
1347+ (withPrelude "x = zipWith Tuple [1] [2]") "UseZip"
1348+ , assertSuggestion "UseZip: suggests zip" [useZipRule]
1349+ (withPrelude "x = zipWith Tuple [1] [2]") "zip [ 1 ] [ 2 ]"
1350+ ]
1351+
1352+-- ============================================================================
1353+-- UseReplicate Tests
1354+-- ============================================================================
1355+
1356+useReplicateTests :: Array TestResult
1357+useReplicateTests =
1358+ [ assertWarningCount "UseReplicate: take n (repeat x)" [useReplicateRule]
1359+ (withPrelude "x = take 3 (repeat 1)") 1
1360+ , assertRuleId "UseReplicate: correct rule id" [useReplicateRule]
1361+ (withPrelude "x = take 3 (repeat 1)") "UseReplicate"
1362+ , assertSuggestion "UseReplicate: suggests replicate" [useReplicateRule]
1363+ (withPrelude "x = take 3 (repeat 1)") "replicate 3 1"
1364+ , assertWarningCount "UseReplicate: no fire on take n xs" [useReplicateRule]
1365+ (withPrelude "x = take 3 xs") 0
1366+ , assertWarningCount "UseReplicate: no fire without import" [useReplicateRule]
1367+ (withoutImports "x = take 3 (repeat 1)") 0
1368+ ]
1369+
1370+-- ============================================================================
1371+-- UseUncurry Tests
1372+-- ============================================================================
1373+
1374+useUncurryTests :: Array TestResult
1375+useUncurryTests =
1376+ [ assertWarningCount "UseUncurry: f (fst p) (snd p)" [useUncurryRule]
1377+ (withPrelude "x = f (fst p) (snd p)") 1
1378+ , assertRuleId "UseUncurry: correct rule id" [useUncurryRule]
1379+ (withPrelude "x = f (fst p) (snd p)") "UseUncurry"
1380+ , assertSuggestion "UseUncurry: suggests uncurry" [useUncurryRule]
1381+ (withPrelude "x = f (fst p) (snd p)") "uncurry f p"
1382+ , assertWarningCount "UseUncurry: no fire on different args" [useUncurryRule]
1383+ (withPrelude "x = f (fst p) (snd q)") 0
1384+ , assertWarningCount "UseUncurry: no fire without import" [useUncurryRule]
1385+ (withoutImports "x = f (fst p) (snd p)") 0
1386+ ]
1387+
1388+-- ============================================================================
1389+-- UseFromJust Tests
1390+-- ============================================================================
1391+
1392+useFromJustTests :: Array TestResult
1393+useFromJustTests =
1394+ [ assertWarningCount "UseFromJust: fromMaybe d (Just x)" [useFromJustRule]
1395+ (withPrelude "x = fromMaybe 0 (Just 1)") 1
1396+ , assertRuleId "UseFromJust: correct rule id" [useFromJustRule]
1397+ (withPrelude "x = fromMaybe 0 (Just 1)") "UseFromJust"
1398+ , assertSuggestion "UseFromJust: simplifies Just" [useFromJustRule]
1399+ (withPrelude "x = fromMaybe 0 (Just 1)") "1"
1400+ , assertWarningCount "UseFromJust: fromMaybe d Nothing" [useFromJustRule]
1401+ (withPrelude "x = fromMaybe 0 Nothing") 1
1402+ , assertSuggestion "UseFromJust: simplifies Nothing" [useFromJustRule]
1403+ (withPrelude "x = fromMaybe 0 Nothing") "0"
1404+ , assertWarningCount "UseFromJust: no fire without import" [useFromJustRule]
1405+ (withoutImports "x = fromMaybe 0 (Just 1)") 0
1406+ ]
1407+
1408+-- ============================================================================
1409+-- UseHead Tests
1410+-- ============================================================================
1411+
1412+useHeadTests :: Array TestResult
1413+useHeadTests =
1414+ [ assertWarningCount "UseHead: xs !! 0" [useHeadRule]
1415+ (withPrelude "x = xs !! 0") 1
1416+ , assertRuleId "UseHead: correct rule id" [useHeadRule]
1417+ (withPrelude "x = xs !! 0") "UseHead"
1418+ , assertSuggestion "UseHead: suggests head" [useHeadRule]
1419+ (withPrelude "x = xs !! 0") "head"
1420+ , assertWarningCount "UseHead: no fire on xs !! 1" [useHeadRule]
1421+ (withPrelude "x = xs !! 1") 0
1422+ , assertWarningCount "UseHead: no fire without import" [useHeadRule]
1423+ (withoutImports "x = xs !! 0") 0
1424+ ]
1425+
1426+-- ============================================================================
1427+-- UseElemIndex Tests
1428+-- ============================================================================
1429+
1430+useElemIndexTests :: Array TestResult
1431+useElemIndexTests =
1432+ [ assertWarningCount "UseElemIndex: findIndex (\\x -> x == a) xs" [useElemIndexRule]
1433+ (withPrelude "x = findIndex (\\y -> y == a) xs") 1
1434+ , assertRuleId "UseElemIndex: correct rule id" [useElemIndexRule]
1435+ (withPrelude "x = findIndex (\\y -> y == a) xs") "UseElemIndex"
1436+ , assertSuggestion "UseElemIndex: suggests elemIndex" [useElemIndexRule]
1437+ (withPrelude "x = findIndex (\\y -> y == a) xs") "elemIndex a xs"
1438+ , assertWarningCount "UseElemIndex: no fire on other predicate" [useElemIndexRule]
1439+ (withPrelude "x = findIndex (\\y -> y > a) xs") 0
1440+ , assertWarningCount "UseElemIndex: no fire without import" [useElemIndexRule]
1441+ (withoutImports "x = findIndex (\\y -> y == a) xs") 0
1442+ ]
1443+
1444+-- ============================================================================
1445+-- UseBreak Tests
1446+-- ============================================================================
1447+
1448+useBreakTests :: Array TestResult
1449+useBreakTests =
1450+ [ assertWarningCount "UseBreak: span (not p) xs" [useBreakRule]
1451+ (withPrelude "x = span (not p) xs") 1
1452+ , assertRuleId "UseBreak: correct rule id" [useBreakRule]
1453+ (withPrelude "x = span (not p) xs") "UseBreak"
1454+ , assertSuggestion "UseBreak: suggests break" [useBreakRule]
1455+ (withPrelude "x = span (not p) xs") "break p xs"
1456+ , assertWarningCount "UseBreak: no fire without import" [useBreakRule]
1457+ (withoutImports "x = span (not p) xs") 0
1458+ ]
1459+
1460+-- ============================================================================
1461+-- UseSpan Tests
1462+-- ============================================================================
1463+
1464+useSpanTests :: Array TestResult
1465+useSpanTests =
1466+ [ assertWarningCount "UseSpan: break (not p) xs" [useSpanRule]
1467+ (withPrelude "x = break (not p) xs") 1
1468+ , assertRuleId "UseSpan: correct rule id" [useSpanRule]
1469+ (withPrelude "x = break (not p) xs") "UseSpan"
1470+ , assertSuggestion "UseSpan: suggests span" [useSpanRule]
1471+ (withPrelude "x = break (not p) xs") "span p xs"
1472+ , assertWarningCount "UseSpan: no fire without import" [useSpanRule]
1473+ (withoutImports "x = break (not p) xs") 0
1474+ ]
1475+
1476+-- ============================================================================
1477+-- UseDollar Tests
1478+-- ============================================================================
1479+
1480+useDollarTests :: Array TestResult
1481+useDollarTests =
1482+ [ assertWarningCount "UseDollar: map f (filter g xs)" [useDollarRule]
1483+ (withPrelude "x = map f (filter g xs)") 1
1484+ , assertRuleId "UseDollar: correct rule id" [useDollarRule]
1485+ (withPrelude "x = map f (filter g xs)") "UseDollar"
1486+ , assertSuggestion "UseDollar: suggests $" [useDollarRule]
1487+ (withPrelude "x = map f (filter g xs)") "map f $ filter g xs"
1488+ , assertWarningCount "UseDollar: no fire on atomic arg" [useDollarRule]
1489+ (withPrelude "x = f (1)") 0
1490+ ]
1491+
1492+-- ============================================================================
1493+-- UseUnwrap Tests
1494+-- ============================================================================
1495+
1496+useUnwrapTests :: Array TestResult
1497+useUnwrapTests =
1498+ [ assertRuleId "UseUnwrap: correct rule id" [useUnwrapRule]
1499+ (withPrelude "x = unwrap foo") "UseUnwrap"
1500+ , assertSuggestion "UseUnwrap: suggests un ?Constructor" [useUnwrapRule]
1501+ (withPrelude "x = unwrap foo") "un ?Constructor foo"
1502+ , assertWarningCount "UseUnwrap: unwrap partial" [useUnwrapRule]
1503+ (withPrelude "x = unwrap") 1
1504+ , assertSuggestion "UseUnwrap: unwrap partial suggestion" [useUnwrapRule]
1505+ (withPrelude "x = unwrap") "un ?Constructor"
1506+ ]
1507+
1508+-- ============================================================================
1509+-- RedundantNegate Tests
1510+-- ============================================================================
1511+
1512+redundantNegateTests :: Array TestResult
1513+redundantNegateTests =
1514+ [ assertWarningCount "RedundantNegate: negate (negate x)" [redundantNegateRule]
1515+ (withPrelude "x = negate (negate 5)") 1
1516+ , assertRuleId "RedundantNegate: correct rule id" [redundantNegateRule]
1517+ (withPrelude "x = negate (negate 5)") "RedundantNegate"
1518+ , assertSuggestion "RedundantNegate: suggests x" [redundantNegateRule]
1519+ (withPrelude "x = negate (negate foo)") "foo"
1520+ ]
1521+
1522+-- ============================================================================
1523+-- RedundantId Tests
1524+-- ============================================================================
1525+
1526+redundantIdTests :: Array TestResult
1527+redundantIdTests =
1528+ [ assertWarningCount "RedundantId: identity x" [redundantIdRule]
1529+ (withPrelude "x = identity 42") 1
1530+ , assertRuleId "RedundantId: correct rule id" [redundantIdRule]
1531+ (withPrelude "x = identity 42") "RedundantId"
1532+ , assertSuggestion "RedundantId: suggests x" [redundantIdRule]
1533+ (withPrelude "x = identity foo") "foo"
1534+ ]
1535+
1536+-- ============================================================================
1537+-- NothingBind Tests
1538+-- ============================================================================
1539+
1540+nothingBindTests :: Array TestResult
1541+nothingBindTests =
1542+ [ assertWarningCount "NothingBind: Nothing >>= f" [nothingBindRule]
1543+ (withPrelude "x = Nothing >>= show") 1
1544+ , assertRuleId "NothingBind: correct rule id" [nothingBindRule]
1545+ (withPrelude "x = Nothing >>= show") "NothingBind"
1546+ , assertSuggestion "NothingBind: suggests Nothing" [nothingBindRule]
1547+ (withPrelude "x = Nothing >>= show") "Nothing"
1548+ ]
1549+
1550+-- ============================================================================
1551+-- MonadLaw Tests
1552+-- ============================================================================
1553+
1554+monadLawTests :: Array TestResult
1555+monadLawTests =
1556+ [ assertWarningCount "MonadLaw: f =<< pure a" [monadLawRule]
1557+ (withPrelude "x = show =<< pure 42") 1
1558+ , assertRuleId "MonadLaw: correct rule id" [monadLawRule]
1559+ (withPrelude "x = show =<< pure 42") "MonadLaw"
1560+ , assertSuggestion "MonadLaw: suggests f a" [monadLawRule]
1561+ (withPrelude "x = show =<< pure 42") "show 42"
1562+ , assertWarningCount "MonadLaw: pure =<< m" [monadLawRule]
1563+ (withPrelude "x = pure =<< Just 1") 1
1564+ , assertSuggestion "MonadLaw: pure =<< m suggests m" [monadLawRule]
1565+ (withPrelude "x = pure =<< Just 1") "Just 1"
1566+ ]
1567+
1568+-- ============================================================================
1569+-- EvaluateBool Tests
1570+-- ============================================================================
1571+
1572+evaluateBoolTests :: Array TestResult
1573+evaluateBoolTests =
1574+ [ assertWarningCount "EvaluateBool: true && x" [evaluateBoolRule]
1575+ (withPrelude "x = true && foo") 1
1576+ , assertSuggestion "EvaluateBool: true && x -> x" [evaluateBoolRule]
1577+ (withPrelude "x = true && foo") "foo"
1578+ , assertWarningCount "EvaluateBool: false && x" [evaluateBoolRule]
1579+ (withPrelude "x = false && foo") 1
1580+ , assertSuggestion "EvaluateBool: false && x -> false" [evaluateBoolRule]
1581+ (withPrelude "x = false && foo") "false"
1582+ , assertWarningCount "EvaluateBool: true || x" [evaluateBoolRule]
1583+ (withPrelude "x = true || foo") 1
1584+ , assertSuggestion "EvaluateBool: true || x -> true" [evaluateBoolRule]
1585+ (withPrelude "x = true || foo") "true"
1586+ , assertWarningCount "EvaluateBool: false || x" [evaluateBoolRule]
1587+ (withPrelude "x = false || foo") 1
1588+ , assertSuggestion "EvaluateBool: false || x -> x" [evaluateBoolRule]
1589+ (withPrelude "x = false || foo") "foo"
1590+ , assertWarningCount "EvaluateBool: x && true" [evaluateBoolRule]
1591+ (withPrelude "x = foo && true") 1
1592+ , assertWarningCount "EvaluateBool: x || false" [evaluateBoolRule]
1593+ (withPrelude "x = foo || false") 1
1594+ ]
1595+
1596+-- ============================================================================
1597+-- UseFoldBool Tests
1598+-- ============================================================================
1599+
1600+useFoldBoolTests :: Array TestResult
1601+useFoldBoolTests =
1602+ [ assertWarningCount "UseFoldBool: foldr (&&) true" [useFoldBoolRule]
1603+ (withPrelude "x = foldr (&&) true xs") 1
1604+ , assertSuggestion "UseFoldBool: foldr (&&) true -> and" [useFoldBoolRule]
1605+ (withPrelude "x = foldr (&&) true xs") "and"
1606+ , assertWarningCount "UseFoldBool: foldr (||) false" [useFoldBoolRule]
1607+ (withPrelude "x = foldr (||) false xs") 1
1608+ , assertSuggestion "UseFoldBool: foldr (||) false -> or" [useFoldBoolRule]
1609+ (withPrelude "x = foldr (||) false xs") "or"
1610+ ]
1611+
1612+-- ============================================================================
1613+-- AlternativeLaw Tests
1614+-- ============================================================================
1615+
1616+alternativeLawTests :: Array TestResult
1617+alternativeLawTests =
1618+ [ assertWarningCount "AlternativeLaw: empty <|> x" [alternativeLawRule]
1619+ (withAlt "x = empty <|> foo") 1
1620+ , assertSuggestion "AlternativeLaw: empty <|> x -> x" [alternativeLawRule]
1621+ (withAlt "x = empty <|> foo") "foo"
1622+ , assertWarningCount "AlternativeLaw: x <|> empty" [alternativeLawRule]
1623+ (withAlt "x = foo <|> empty") 1
1624+ , assertSuggestion "AlternativeLaw: x <|> empty -> x" [alternativeLawRule]
1625+ (withAlt "x = foo <|> empty") "foo"
1626+ , assertWarningCount "AlternativeLaw: Nothing <|> x" [alternativeLawRule]
1627+ (withAlt "x = Nothing <|> foo") 1
1628+ ]
1629+
1630+-- ============================================================================
1631+-- EvaluateEither Tests
1632+-- ============================================================================
1633+
1634+evaluateEitherTests :: Array TestResult
1635+evaluateEitherTests =
1636+ [ assertWarningCount "EvaluateEither: either f g (Left x)" [evaluateEitherRule]
1637+ (withPrelude "x = either show identity (Left 42)") 1
1638+ , assertSuggestion "EvaluateEither: either f g (Left x) -> f x" [evaluateEitherRule]
1639+ (withPrelude "x = either show identity (Left 42)") "show 42"
1640+ , assertWarningCount "EvaluateEither: either f g (Right y)" [evaluateEitherRule]
1641+ (withPrelude "x = either show identity (Right \"hi\")") 1
1642+ , assertSuggestion "EvaluateEither: either f g (Right y) -> g y" [evaluateEitherRule]
1643+ (withPrelude "x = either show identity (Right \"hi\")") "identity \"hi\""
1644+ ]
1645+
1646+-- ============================================================================
1647+-- UseCaseOf Tests
1648+-- ============================================================================
1649+
1650+useCaseOfTests :: Array TestResult
1651+useCaseOfTests =
1652+ -- Positive cases: should detect
1653+ [ assertWarningCount "UseCaseOf: two clauses" [useCaseOfRule]
1654+ (withPrelude "foo True = 1\nfoo False = 0") 1
1655+ , assertRuleId "UseCaseOf: correct rule id" [useCaseOfRule]
1656+ (withPrelude "foo True = 1\nfoo False = 0") "UseCaseOf"
1657+ , assertSuggestion "UseCaseOf: suggestion format" [useCaseOfRule]
1658+ (withPrelude "foo True = 1\nfoo False = 0") "foo = case _ of\n True -> 1\n False -> 0"
1659+ , assertWarningCount "UseCaseOf: three clauses" [useCaseOfRule]
1660+ (withPrelude "bar Nothing = 0\nbar (Just 0) = 1\nbar (Just _) = 2") 1
1661+ , assertWarningCount "UseCaseOf: two args" [useCaseOfRule]
1662+ (withPrelude "add True True = 2\nadd True False = 1\nadd False True = 1\nadd False False = 0") 1
1663+ , assertSuggestion "UseCaseOf: two args suggestion" [useCaseOfRule]
1664+ (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"
1665+
1666+ -- Clauses with where bindings
1667+ , assertWarningCount "UseCaseOf: clause with where binding" [useCaseOfRule]
1668+ (withPrelude "foo x = go x\n where\n isJoin True = x\n where x = 1\n isJoin False = 0") 1
1669+ , assertSuggestion "UseCaseOf: clause with where binding suggestion" [useCaseOfRule]
1670+ (withPrelude "foo x = go x\n where\n isJoin True = x\n where x = 1\n isJoin False = 0") "isJoin = case _ of\n True ->\n x\n where\n x = 1\n False -> 0"
1671+
1672+ -- Regression test: nested case expression should preserve multiline formatting
1673+ , assertWarningCount "UseCaseOf: nested case expression" [useCaseOfRule]
1674+ (withPrelude "extractFn (Just x) =\n case x of\n 1 -> \"one\"\n _ -> \"other\"\nextractFn Nothing = \"nothing\"") 1
1675+ , assertSuggestion "UseCaseOf: nested case should preserve newlines" [useCaseOfRule]
1676+ (withPrelude "extractFn (Just x) =\n case x of\n 1 -> \"one\"\n _ -> \"other\"\nextractFn Nothing = \"nothing\"") "extractFn = case _ of\n (Just x) ->\n case x of\n 1 -> \"one\"\n _ -> \"other\"\n Nothing -> \"nothing\""
1677+
1678+ -- Negative cases: should NOT detect
1679+ , assertWarningCount "UseCaseOf: single clause" [useCaseOfRule]
1680+ (withPrelude "foo x = x + 1") 0
1681+ , assertWarningCount "UseCaseOf: no binders" [useCaseOfRule]
1682+ (withPrelude "foo = 42\nbar = 43") 0
1683+ , assertWarningCount "UseCaseOf: guarded" [useCaseOfRule]
1684+ (withPrelude "foo x | x > 0 = 1\nfoo x = 0") 0
1685+ ]
1686+
1687+-- ============================================================================
1688+-- FlattenCase Tests
1689+-- ============================================================================
1690+
1691+flattenCaseTests :: Array TestResult
1692+flattenCaseTests =
1693+ -- Positive cases: should detect
1694+ [ assertWarningCount "FlattenCase: two levels" [flattenCaseRule]
1695+ (withPrelude "foo x y = case x of\n Just a -> case y of\n Just b -> a + b\n _ -> 0\n _ -> 0") 1
1696+ , assertRuleId "FlattenCase: correct rule id" [flattenCaseRule]
1697+ (withPrelude "foo x y = case x of\n Just a -> case y of\n Just b -> a + b\n _ -> 0\n _ -> 0") "FlattenCase"
1698+ , assertSuggestion "FlattenCase: suggestion format" [flattenCaseRule]
1699+ (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"
1700+
1701+ -- Negative cases: should NOT detect
1702+ , assertWarningCount "FlattenCase: single level" [flattenCaseRule]
1703+ (withPrelude "foo x = case x of\n Just a -> a\n _ -> 0") 0
1704+ , assertWarningCount "FlattenCase: different fallbacks" [flattenCaseRule]
1705+ (withPrelude "foo x y = case x of\n Just a -> case y of\n Just b -> a + b\n _ -> 1\n _ -> 0") 0
1706+ ]
1707+
1708+-- ============================================================================
1709+-- UsePatternGuards Tests
1710+-- ============================================================================
1711+
1712+usePatternGuardsTests :: Array TestResult
1713+usePatternGuardsTests =
1714+ -- Positive cases: should detect
1715+ [ assertWarningCount "UsePatternGuards: two levels" [usePatternGuardsRule]
1716+ (withPrelude "foo x y = case x of\n Just a -> case y of\n Just b -> a + b\n _ -> 0\n _ -> 0") 1
1717+ , assertRuleId "UsePatternGuards: correct rule id" [usePatternGuardsRule]
1718+ (withPrelude "foo x y = case x of\n Just a -> case y of\n Just b -> a + b\n _ -> 0\n _ -> 0") "UsePatternGuards"
1719+ , assertWarningCount "UsePatternGuards: three levels" [usePatternGuardsRule]
1720+ (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
1721+
1722+ -- Negative cases: should NOT detect
1723+ , assertWarningCount "UsePatternGuards: single level" [usePatternGuardsRule]
1724+ (withPrelude "foo x = case x of\n Just a -> a\n _ -> 0") 0
1725+ , assertWarningCount "UsePatternGuards: different fallbacks" [usePatternGuardsRule]
1726+ (withPrelude "foo x y = case x of\n Just a -> case y of\n Just b -> a + b\n _ -> 1\n _ -> 0") 0
1727+
1728+ -- Fix test: verifies correct indentation relative to original branch position
1729+ , assertFix "UsePatternGuards: fix with proper indentation" [usePatternGuardsRule]
1730+ (withPrelude "foo x y = case x of\n Just a -> case y of\n Just b -> a + b\n _ -> 0\n _ -> 0")
1731+ (withPrelude "foo x y = case x of\n Just a\n | Just b <- y -> a + b\n _ -> 0")
1732+ ]
1733+
1734+-- ============================================================================
1735+-- RedundantParens Tests
1736+-- ============================================================================
1737+
1738+redundantParensTests :: Array TestResult
1739+redundantParensTests =
1740+ -- Positive cases: should detect redundant parens
1741+ [ assertWarningCount "RedundantParens: nested parens ((x))" [redundantParensRule]
1742+ (withPrelude "x = ((1))") 1
1743+ , assertRuleId "RedundantParens: correct rule id" [redundantParensRule]
1744+ (withPrelude "x = ((1))") "RedundantParens"
1745+ , assertSuggestion "RedundantParens: nested parens suggestion" [redundantParensRule]
1746+ (withPrelude "x = ((1))") "(1)"
1747+
1748+ -- Function arguments with atomic expressions
1749+ , assertWarningCount "RedundantParens: literal in arg" [redundantParensRule]
1750+ (withPrelude "x = show (42)") 1
1751+ , assertSuggestion "RedundantParens: literal in arg suggestion" [redundantParensRule]
1752+ (withPrelude "x = show (42)") "42"
1753+ , assertWarningCount "RedundantParens: identifier in arg" [redundantParensRule]
1754+ (withPrelude "x y = show (y)") 1
1755+ , assertWarningCount "RedundantParens: string in arg" [redundantParensRule]
1756+ (withPrelude "x = show (\"hi\")") 1
1757+ , assertWarningCount "RedundantParens: array in arg" [redundantParensRule]
1758+ (withPrelude "x = length ([1, 2])") 1
1759+ , assertWarningCount "RedundantParens: record in arg" [redundantParensRule]
1760+ (withPrelude "x = show ({ a: 1 })") 1
1761+
1762+ -- Multiple redundant args
1763+ , assertWarningCount "RedundantParens: two redundant args" [redundantParensRule]
1764+ (withPrelude "x = add (1) (2)") 2
1765+
1766+ -- Array elements
1767+ , assertWarningCount "RedundantParens: array element literal" [redundantParensRule]
1768+ (withPrelude "x = [(1), (2)]") 2
1769+ , assertWarningCount "RedundantParens: array mixed" [redundantParensRule]
1770+ (withPrelude "x = [(1), 2, (3)]") 2
1771+ , assertWarningCount "RedundantParens: no fire on complex array element" [redundantParensRule]
1772+ (withPrelude "x = [(1 + 2)]") 0
1773+
1774+ -- Negative cases: should NOT detect
1775+ , assertWarningCount "RedundantParens: no fire on precedence (1+2)*3" [redundantParensRule]
1776+ (withPrelude "x = (1 + 2) * 3") 0
1777+ , assertWarningCount "RedundantParens: no fire on complex arg" [redundantParensRule]
1778+ (withPrelude "x = show (1 + 2)") 0
1779+ , assertWarningCount "RedundantParens: no fire on lambda arg" [redundantParensRule]
1780+ (withPrelude "x = map (\\y -> y + 1) [1]") 0
1781+ , assertWarningCount "RedundantParens: no fire on negate arg" [redundantParensRule]
1782+ (withPrelude "x f = f (-1)") 0
1783+ , assertWarningCount "RedundantParens: no fire on section" [redundantParensRule]
1784+ (withPrelude "x = (_ + 1)") 0
1785+ , assertWarningCount "RedundantParens: no fire on if" [redundantParensRule]
1786+ (withPrelude "x = (if true then 1 else 2)") 0
1787+ , assertWarningCount "RedundantParens: no fire on let" [redundantParensRule]
1788+ (withPrelude "x = (let y = 1 in y)") 0
1789+
1790+ -- Outer parens on RHS (simple cases)
1791+ , assertWarningCount "RedundantParens: outer parens on literal RHS" [redundantParensRule]
1792+ (withPrelude "x = (42)") 1
1793+ , assertWarningCount "RedundantParens: outer parens on app RHS" [redundantParensRule]
1794+ (withPrelude "x = (show 1)") 1
1795+ , assertWarningCount "RedundantParens: outer parens on op RHS" [redundantParensRule]
1796+ (withPrelude "x = (1 + 2)") 1
1797+
1798+ -- If branches
1799+ , assertWarningCount "RedundantParens: if condition" [redundantParensRule]
1800+ (withPrelude "x = if (true) then 1 else 2") 1
1801+ , assertWarningCount "RedundantParens: if then branch" [redundantParensRule]
1802+ (withPrelude "x = if true then (1) else 2") 1
1803+
1804+ -- Case branches
1805+ , assertWarningCount "RedundantParens: case scrutinee" [redundantParensRule]
1806+ (withPrelude "x = case (1) of y -> y") 1
1807+ , assertWarningCount "RedundantParens: case body" [redundantParensRule]
1808+ (withPrelude "x = case 1 of y -> (y)") 1
1809+
1810+ -- Let expression
1811+ , assertWarningCount "RedundantParens: let binding" [redundantParensRule]
1812+ (withPrelude "x = let y = (1) in y") 1
1813+ , assertWarningCount "RedundantParens: let body" [redundantParensRule]
1814+ (withPrelude "x = let y = 1 in (y)") 1
1815+
1816+ -- Record fields
1817+ , assertWarningCount "RedundantParens: record field" [redundantParensRule]
1818+ (withPrelude "x = { a: (1) }") 1
1819+ , assertWarningCount "RedundantParens: multiple record fields" [redundantParensRule]
1820+ (withPrelude "x = { a: (1), b: (2) }") 2
1821+
1822+ -- Pattern (binder) cases
1823+ , assertWarningCount "RedundantParens: nested parens in pattern" [redundantParensRule]
1824+ (withPrelude "f ((x)) = x") 1
1825+ , assertSuggestion "RedundantParens: nested parens in pattern suggestion" [redundantParensRule]
1826+ (withPrelude "f ((x)) = x") "(x)"
1827+ , assertWarningCount "RedundantParens: atomic pattern in constructor arg" [redundantParensRule]
1828+ (withPrelude "f (Just (x)) = x\nf Nothing = 0") 1
1829+ , assertSuggestion "RedundantParens: constructor arg suggestion" [redundantParensRule]
1830+ (withPrelude "f (Just (x)) = x\nf Nothing = 0") "x"
1831+ , assertWarningCount "RedundantParens: wildcard in constructor" [redundantParensRule]
1832+ (withPrelude "f (Just (_)) = 1\nf Nothing = 0") 1
1833+ , assertWarningCount "RedundantParens: literal in constructor" [redundantParensRule]
1834+ (withPrelude "f (Just (1)) = true\nf _ = false") 1
1835+ , assertWarningCount "RedundantParens: array pattern element" [redundantParensRule]
1836+ (withPrelude "f [(x), (y)] = x + y\nf _ = 0") 2
1837+
1838+ -- Negative pattern cases
1839+ , assertWarningCount "RedundantParens: no fire on constructor with args" [redundantParensRule]
1840+ (withPrelude "f (Just (Right x)) = x\nf _ = 0") 0
1841+ , assertWarningCount "RedundantParens: no fire on typed pattern" [redundantParensRule]
1842+ (withPrelude "f (x :: Int) = x") 0
1843+ , assertWarningCount "RedundantParens: no fire on negated number pattern" [redundantParensRule]
1844+ (withPrelude "f (Just (-1)) = true\nf _ = false") 0
1845+
1846+ -- Top-level case pattern parens
1847+ , assertWarningCount "RedundantParens: top-level case pattern" [redundantParensRule]
1848+ (withPrelude "f = case _ of\n (Just x) -> x\n Nothing -> 0") 1
1849+ , assertSuggestion "RedundantParens: top-level case pattern suggestion" [redundantParensRule]
1850+ (withPrelude "f = case _ of\n (Just x) -> x\n Nothing -> 0") "Just x"
1851+ , assertWarningCount "RedundantParens: top-level constructor pattern" [redundantParensRule]
1852+ (withPrelude "f = case _ of\n (Foo a b) -> a\n _ -> 0") 1
1853+ , assertWarningCount "RedundantParens: no fire on cons pattern" [redundantParensRule]
1854+ (withPrelude "f = case _ of\n (x : xs) -> x\n _ -> 0") 0
1855+ , assertWarningCount "RedundantParens: no fire on typed case pattern" [redundantParensRule]
1856+ (withPrelude "f = case _ of\n (x :: Int) -> x") 0
1857+
1858+ -- Type-level redundant parens
1859+ , assertWarningCount "RedundantParens: nested type parens" [redundantParensRule]
1860+ (withPrelude "x :: ((Int))\nx = 1") 1
1861+ , assertSuggestion "RedundantParens: nested type parens suggestion" [redundantParensRule]
1862+ (withPrelude "x :: ((Int))\nx = 1") "(Int)"
1863+ , assertWarningCount "RedundantParens: type app with atomic arg" [redundantParensRule]
1864+ (withPrelude "x :: Maybe (Int)\nx = Nothing") 1
1865+ , assertSuggestion "RedundantParens: type app atomic arg suggestion" [redundantParensRule]
1866+ (withPrelude "x :: Maybe (Int)\nx = Nothing") "Int"
1867+ , assertWarningCount "RedundantParens: type app with type var" [redundantParensRule]
1868+ (withPrelude "x :: forall a. Maybe (a)\nx = Nothing") 1
1869+ , assertWarningCount "RedundantParens: arrow left simple" [redundantParensRule]
1870+ (withPrelude "x :: (Int) -> String\nx = show") 1
1871+ , assertWarningCount "RedundantParens: record field type" [redundantParensRule]
1872+ (withPrelude "type R = { x :: (Int) }") 1
1873+
1874+ -- Type-level negative cases
1875+ , assertWarningCount "RedundantParens: no fire on function type in arg" [redundantParensRule]
1876+ (withPrelude "x :: (Int -> Int) -> Int\nx f = f 1") 0
1877+ , assertWarningCount "RedundantParens: no fire on forall in arg" [redundantParensRule]
1878+ (withPrelude "x :: (forall a. a -> a) -> Int\nx f = f 1") 0
1879+ , assertWarningCount "RedundantParens: no fire on constrained type" [redundantParensRule]
1880+ (withPrelude "x :: (Show a) => a -> String\nx = show") 0
1881+ ]
1882+
1883+-- | Helper for Alternative imports
1884+withAlt :: String -> String
1885+withAlt code = """
1886+module Test where
1887+import Prelude
1888+import Control.Alternative (empty, (<|>))
1889+import Data.Maybe (Maybe(..), Nothing, Just)
1890+""" <> code
1891+
1892+-- | All rules for golden testing
1893+allRulesForGolden :: Array Rule
1894+allRulesForGolden =
1895+ [ mapIdentityRule
1896+ , mapFusionRule
1897+ , useTraverseRule
1898+ , concatMapRule
1899+ , notEqualRule
1900+ , useGuardRule
1901+ , etaReduceRule
1902+ , etaReduceDeclRule
1903+ , redundantBindRule
1904+ , letToWhereRule
1905+ , redundantIfRule
1906+ , functorLawRule
1907+ , useVoidRule
1908+ , useJoinRule
1909+ , useAnyRule
1910+ , useAllRule
1911+ , booleanSimplifyRule
1912+ , collapseLambdasRule
1913+ , useConstRule
1914+ , useNullRule
1915+ , useFromMaybeRule
1916+ , useIsJustRule
1917+ , useIsNothingRule
1918+ , useWhenRule
1919+ , useUnlessRule
1920+ , redundantFlipRule
1921+ , useNotElemRule
1922+ , useMinMaxRule
1923+ , monoidIdentityRule
1924+ , useFoldRule
1925+ , useSequenceRule
1926+ , useTraverseSequenceRule
1927+ , redundantNotRule
1928+ , useFstSndRule
1929+ , useMapMaybeRule
1930+ , useGuardMaybeRule
1931+ , useMaybeMapRule
1932+ , useApplicativeRule
1933+ , useBindFlipRule
1934+ , useForRule
1935+ , redundantGuardRule
1936+ , useComparingRule
1937+ , useOnRule
1938+ , useFindMapRule
1939+ ]
1940+
1941+goldenTests :: Effect (Array TestResult)
1942+goldenTests = traverse identity
1943+ -- MapIdentity: map identity x -> x
1944+ [ assertFixFormatted "Golden: MapIdentity fix" [mapIdentityRule]
1945+ (withPrelude "x = map identity [ 1, 2, 3 ]")
1946+ (withPrelude "x = [ 1, 2, 3 ]")
1947+
1948+ -- MapFusion: map f (map g x) -> map (f <<< g) x
1949+ , assertFixFormatted "Golden: MapFusion fix" [mapFusionRule]
1950+ (withPrelude "x = map show (map (_ + 1) [ 1, 2, 3 ])")
1951+ (withPrelude "x = map (show <<< (_ + 1)) [ 1, 2, 3 ]")
1952+
1953+ -- UseTraverse: sequence (map f x) -> mapM f x
1954+ , assertFixFormatted "Golden: UseTraverse fix" [useTraverseRule]
1955+ (withPrelude "x f xs = sequence (map f xs)")
1956+ (withPrelude "x f xs = mapM f xs")
1957+
1958+ -- ConcatMap: join (map f x) -> (=<<) f x
1959+ , assertFixFormatted "Golden: ConcatMap fix" [concatMapRule]
1960+ (withPrelude "x f xs = join (map f xs)")
1961+ (withPrelude "x f xs = (=<<) f xs")
1962+
1963+ -- NotEqual: not (a == b) -> a /= b
1964+ , assertFixFormatted "Golden: NotEqual fix" [notEqualRule]
1965+ (withPrelude "x a b = not (a == b)")
1966+ (withPrelude "x a b = a /= b")
1967+
1968+ -- EtaReduce: \x -> f x -> f
1969+ , assertFixFormatted "Golden: EtaReduce fix" [etaReduceRule]
1970+ (withPrelude "x = \\y -> show y")
1971+ (withPrelude "x = show")
1972+
1973+ -- RedundantBind: pure x >>= f -> f x
1974+ , assertFixFormatted "Golden: RedundantBind fix" [redundantBindRule]
1975+ (withPrelude "x y f = pure y >>= f")
1976+ (withPrelude "x y f = f y")
1977+
1978+ -- RedundantIf: if a then true else false -> a
1979+ , assertFixFormatted "Golden: RedundantIf fix" [redundantIfRule]
1980+ (withPrelude "x a = if a then true else false")
1981+ (withPrelude "x a = a")
1982+
1983+ -- UseVoid: a *> pure unit -> void a
1984+ , assertFixFormatted "Golden: UseVoid fix" [useVoidRule]
1985+ (withPrelude "x a = a *> pure unit")
1986+ (withPrelude "x a = void a")
1987+
1988+ -- UseJoin: x >>= identity -> join x
1989+ , assertFixFormatted "Golden: UseJoin fix" [useJoinRule]
1990+ (withPrelude "x m = m >>= identity")
1991+ (withPrelude "x m = join m")
1992+
1993+ -- UseAny: or (map f x) -> any f x
1994+ , assertFixFormatted "Golden: UseAny fix" [useAnyRule]
1995+ (withPrelude "x f xs = or (map f xs)")
1996+ (withPrelude "x f xs = any f xs")
1997+
1998+ -- UseAll: and (map f x) -> all f x
1999+ , assertFixFormatted "Golden: UseAll fix" [useAllRule]
2000+ (withPrelude "x f xs = and (map f xs)")
2001+ (withPrelude "x f xs = all f xs")
2002+
2003+ -- BooleanSimplify: x == true -> x
2004+ , assertFixFormatted "Golden: BooleanSimplify fix" [booleanSimplifyRule]
2005+ (withPrelude "x a = a == true")
2006+ (withPrelude "x a = a")
2007+
2008+ -- CollapseLambdas: \x -> \y -> body -> \x y -> body
2009+ , assertFixFormatted "Golden: CollapseLambdas fix" [collapseLambdasRule]
2010+ (withPrelude "x = \\a -> \\b -> a + b")
2011+ (withPrelude "x = \\a b -> a + b")
2012+
2013+ -- UseConst: \_ -> y -> const y
2014+ , assertFixFormatted "Golden: UseConst fix" [useConstRule]
2015+ (withPrelude "x = \\_ -> 42")
2016+ (withPrelude "x = const 42")
2017+
2018+ -- UseNull: length xs == 0 -> null xs
2019+ , assertFixFormatted "Golden: UseNull fix" [useNullRule]
2020+ (withPrelude "x xs = length xs == 0")
2021+ (withPrelude "x xs = null xs")
2022+
2023+ -- UseFromMaybe: maybe def identity -> fromMaybe def
2024+ , assertFixFormatted "Golden: UseFromMaybe fix" [useFromMaybeRule]
2025+ (withPrelude "x def = maybe def identity")
2026+ (withPrelude "x def = fromMaybe def")
2027+
2028+ -- UseIsJust: maybe false (const true) -> isJust
2029+ , assertFixFormatted "Golden: UseIsJust fix" [useIsJustRule]
2030+ (withPrelude "x = maybe false (const true)")
2031+ (withPrelude "x = isJust")
2032+
2033+ -- UseIsNothing: maybe true (const false) -> isNothing
2034+ , assertFixFormatted "Golden: UseIsNothing fix" [useIsNothingRule]
2035+ (withPrelude "x = maybe true (const false)")
2036+ (withPrelude "x = isNothing")
2037+
2038+ -- UseWhen: if cond then action else pure unit -> when cond action
2039+ , assertFixFormatted "Golden: UseWhen fix" [useWhenRule]
2040+ (withPrelude "x cond action = if cond then action else pure unit")
2041+ (withPrelude "x cond action = when cond action")
2042+
2043+ -- UseUnless: if cond then pure unit else action -> unless cond action
2044+ , assertFixFormatted "Golden: UseUnless fix" [useUnlessRule]
2045+ (withPrelude "x cond action = if cond then pure unit else action")
2046+ (withPrelude "x cond action = unless cond action")
2047+
2048+ -- RedundantFlip: flip (flip f) -> f
2049+ , assertFixFormatted "Golden: RedundantFlip fix" [redundantFlipRule]
2050+ (withPrelude "x f = flip (flip f)")
2051+ (withPrelude "x f = f")
2052+
2053+ -- UseNotElem: not (elem x xs) -> notElem x xs
2054+ , assertFixFormatted "Golden: UseNotElem fix" [useNotElemRule]
2055+ (withPrelude "x a xs = not (elem a xs)")
2056+ (withPrelude "x a xs = notElem a xs")
2057+
2058+ -- UseMinMax: if a > b then a else b -> max a b
2059+ , assertFixFormatted "Golden: UseMinMax fix" [useMinMaxRule]
2060+ (withPrelude "x a b = if a > b then a else b")
2061+ (withPrelude "x a b = max a b")
2062+
2063+ -- MonoidIdentity: mempty <> x -> x
2064+ , assertFixFormatted "Golden: MonoidIdentity fix" [monoidIdentityRule]
2065+ (withPrelude "x xs = mempty <> xs")
2066+ (withPrelude "x xs = xs")
2067+
2068+ -- UseFold: foldMap identity -> fold
2069+ , assertFixFormatted "Golden: UseFold fix" [useFoldRule]
2070+ (withPrelude "x = foldMap identity")
2071+ (withPrelude "x = fold")
2072+
2073+ -- UseSequence: traverse identity -> sequence
2074+ , assertFixFormatted "Golden: UseSequence fix" [useSequenceRule]
2075+ (withPrelude "x = traverse identity")
2076+ (withPrelude "x = sequence")
2077+
2078+ -- RedundantNot: not (not x) -> x
2079+ , assertFixFormatted "Golden: RedundantNot fix" [redundantNotRule]
2080+ (withPrelude "x a = not (not a)")
2081+ (withPrelude "x a = a")
2082+
2083+ -- UseFstSnd: \(Tuple x _) -> x -> fst
2084+ , assertFixFormatted "Golden: UseFstSnd fix" [useFstSndRule]
2085+ (withPrelude "x = \\(Tuple a b) -> a")
2086+ (withPrelude "x = fst")
2087+
2088+ -- UseMapMaybe: catMaybes (map f x) -> mapMaybe f x
2089+ , assertFixFormatted "Golden: UseMapMaybe fix" [useMapMaybeRule]
2090+ (withPrelude "x f xs = catMaybes (map f xs)")
2091+ (withPrelude "x f xs = mapMaybe f xs")
2092+
2093+ -- UseApplicative: pure f <*> x -> f <$> x
2094+ , assertFixFormatted "Golden: UseApplicative fix" [useApplicativeRule]
2095+ (withPrelude "x f y = pure f <*> y")
2096+ (withPrelude "x f y = f <$> y")
2097+
2098+ -- UseBindFlip: join (map f x) -> x >>= f
2099+ , assertFixFormatted "Golden: UseBindFlip fix" [useBindFlipRule]
2100+ (withPrelude "x f y = join (map f y)")
2101+ (withPrelude "x f y = y >>= f")
2102+
2103+ -- UseFor: flip traverse -> for
2104+ , assertFixFormatted "Golden: UseFor fix" [useForRule]
2105+ (withPrelude "x = flip traverse")
2106+ (withPrelude "x = for")
2107+
2108+ -- RedundantGuard: guard true -> pure unit
2109+ , assertFixFormatted "Golden: RedundantGuard fix" [redundantGuardRule]
2110+ (withPrelude "x = guard true")
2111+ (withPrelude "x = pure unit")
2112+
2113+ -- UseComparing: \a b -> compare (f a) (f b) -> comparing f
2114+ , assertFixFormatted "Golden: UseComparing fix" [useComparingRule]
2115+ (withPrelude "x f = \\a b -> compare (f a) (f b)")
2116+ (withPrelude "x f = comparing f")
2117+
2118+ -- UseOn: \a b -> f (g a) (g b) -> on f g
2119+ , assertFixFormatted "Golden: UseOn fix" [useOnRule]
2120+ (withPrelude "x f g = \\a b -> f (g a) (g b)")
2121+ (withPrelude "x f g = on f g")
2122+
2123+ -- UseFindMap: head (mapMaybe f x) -> findMap f x
2124+ , assertFixFormatted "Golden: UseFindMap fix" [useFindMapRule]
2125+ (withPrelude "x f xs = head (mapMaybe f xs)")
2126+ (withPrelude "x f xs = findMap f xs")
2127+
2128+ -- LetToDo: nested let...in -> do notation (do on previous line)
2129+ , pure $ assertFix "Golden: LetToDo nested fix" [letToDoRule]
2130+ (withPrelude "x f =\n case f of\n Just y ->\n let\n z = y + 1\n in z * 2\n Nothing -> 0")
2131+ (withPrelude "x f =\n case f of\n Just y -> do\n let\n z = y + 1\n z * 2\n Nothing -> 0")
2132+
2133+ -- LetToDo: should NOT fire on do-let (no "in" keyword)
2134+ , pure $ assertWarningCount "Golden: LetToDo no fire on do-let" [letToDoRule]
2135+ (withPrelude "x f =\n case f of\n Just y -> do\n let\n z = y + 1\n [ z ]\n Nothing -> []")
2136+ 0
2137+
2138+ -- UsePatternGuards: should preserve do-let body structure
2139+ , pure $ assertFix "Golden: UsePatternGuards preserves do-let" [usePatternGuardsRule]
2140+ (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 _ -> []")
2141+ (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 _ -> [ ]")
2142+
2143+ -- UseFoldMap: should preserve multiline formatting in complex body
2144+ , pure $ assertFix "Golden: UseFoldMap preserves multiline body" [useFoldMapRule]
2145+ (withPrelude "checkExpr mx =\n case mx of\n Just p ->\n let\n pText = show p\n in\n [ pText ]\n Nothing -> []")
2146+ (withPrelude "checkExpr mx =\n foldMap\n ( \\p ->\n let\n pText = show p\n in\n [ pText ]\n )\n mx")
2147+
2148+ -- UseEitherMap: either Left (Right <<< f) -> map f
2149+ , pure $ assertFix "Golden: UseEitherMap basic" [useEitherMapRule]
2150+ (withPrelude "x = either Left (Right <<< show)")
2151+ (withPrelude "x = map show")
2152+
2153+ -- UseConst: \_ -> multiline body -> const (multiline body)
2154+ , pure $ assertFix "Golden: UseConst preserves multiline body" [useConstRule]
2155+ (withPrelude "x =\n foldMap\n ( \\_ ->\n [ LintWarning\n { ruleId: RuleId \"UseEitherMap\"\n , message: WarningMessage \"either Left (Right <<< f) can be simplified to map f\"\n }\n ]\n )\n secondArg")
2156+ (withPrelude "x =\n foldMap\n ( const\n [ LintWarning\n { ruleId: RuleId \"UseEitherMap\"\n , message: WarningMessage \"either Left (Right <<< f) can be simplified to map f\"\n }\n ]\n )\n secondArg")
2157+ ]
2158+
2159+-- ============================================================================
2160+-- Purs-Tidy Formatting Tests
2161+-- ============================================================================
2162+
2163+-- | Test that a suggestion is already purs-tidy formatted
2164+assertTidyFormatted :: String -> String -> Effect TestResult
2165+assertTidyFormatted name suggestion = do
2166+ formatted <- formatExpr suggestion
2167+ pure $ if formatted == suggestion
2168+ then runTest name true "OK"
2169+ else runTest name false ("Not tidy-formatted. Got: '" <> formatted <> "' Expected: '" <> suggestion <> "'")
2170+
2171+-- | Suggestions that should be purs-tidy formatted
2172+tidySuggestions :: Array { name :: String, suggestion :: String }
2173+tidySuggestions =
2174+ [ { name: "void foo", suggestion: "void foo" }
2175+ , { name: "join foo", suggestion: "join foo" }
2176+ , { name: "any f xs", suggestion: "any f xs" }
2177+ , { name: "all f xs", suggestion: "all f xs" }
2178+ , { name: "const 42", suggestion: "const 42" }
2179+ , { name: "notElem x xs", suggestion: "notElem x xs" }
2180+ , { name: "max a b", suggestion: "max a b" }
2181+ , { name: "min a b", suggestion: "min a b" }
2182+ , { name: "null xs", suggestion: "null xs" }
2183+ , { name: "fromMaybe x", suggestion: "fromMaybe x" }
2184+ , { name: "isJust", suggestion: "isJust" }
2185+ , { name: "isNothing", suggestion: "isNothing" }
2186+ , { name: "when cond action", suggestion: "when cond action" }
2187+ , { name: "unless cond action", suggestion: "unless cond action" }
2188+ , { name: "fst", suggestion: "fst" }
2189+ , { name: "snd", suggestion: "snd" }
2190+ , { name: "fold", suggestion: "fold" }
2191+ , { name: "sequence", suggestion: "sequence" }
2192+ , { name: "traverse f xs", suggestion: "traverse f xs" }
2193+ , { name: "collapsed lambda", suggestion: "\\a b -> a + b" }
2194+ , { name: "map fusion", suggestion: "map (f <<< g) x" }
2195+ , { name: "map fusion with <$>", suggestion: "(f <<< g) <$> x" }
2196+ , { name: "flipped map fusion", suggestion: "x <#> (g >>> f)" }
2197+ , { name: "where clause", suggestion: "y + 1\n where\n y = 1" }
2198+ ]
2199+
2200+-- | Run all tidy formatting tests
2201+runTidyTests :: Effect (Array TestResult)
2202+runTidyTests = traverse (\t -> assertTidyFormatted ("Tidy: " <> t.name) t.suggestion) tidySuggestions