@@ -10,79 +10,93 @@ import Data.Newtype (unwrap)
1010import Data.Traversable (traverse)
1111import Effect (Effect)
1212import Effect.Class.Console (log)
13−import Purelint.Fix (applyAllFixes)
14−import Purelint.Tidy (formatExpr)
15−import Purelint.Rules.FmapId (fmapIdRule)
16−import Purelint.Rules.MapFusion (mapFusionRule)
17−import Purelint.Rules.UseTraverse (useTraverseRule)
18−import Purelint.Rules.ConcatMap (concatMapRule)
19−import Purelint.Rules.NotEqual (notEqualRule)
20−import Purelint.Rules.UseGuard (useGuardRule)
21−import Purelint.Rules.EtaReduce (etaReduceRule)
22−import Purelint.Rules.EtaReduceDecl (etaReduceDeclRule)
23−import Purelint.Rules.RedundantBind (redundantBindRule)
24−import Purelint.Rules.LetToWhere (letToWhereRule)
25−import Purelint.Rules.RedundantIf (redundantIfRule)
26−import Purelint.Rules.FunctorLaw (functorLawRule)
27−import Purelint.Rules.UseVoid (useVoidRule)
28−import Purelint.Rules.UseJoin (useJoinRule)
29−import Purelint.Rules.UseAny (useAnyRule)
30−import Purelint.Rules.UseAll (useAllRule)
31−import Purelint.Rules.BooleanSimplify (booleanSimplifyRule)
32−import Purelint.Rules.CollapseLambdas (collapseLambdasRule)
33−import Purelint.Rules.UseConst (useConstRule)
34−import Purelint.Rules.UseNull (useNullRule)
35−import Purelint.Rules.UseFromMaybe (useFromMaybeRule)
36−import Purelint.Rules.UseIsJust (useIsJustRule)
37−import Purelint.Rules.UseIsNothing (useIsNothingRule)
38−import Purelint.Rules.UseWhen (useWhenRule)
39−import Purelint.Rules.UseUnless (useUnlessRule)
40−import Purelint.Rules.RedundantFlip (redundantFlipRule)
41−import Purelint.Rules.UseNotElem (useNotElemRule)
42−import Purelint.Rules.UseMinMax (useMinMaxRule)
43−import Purelint.Rules.MonoidIdentity (monoidIdentityRule)
44−import Purelint.Rules.UseFold (useFoldRule)
45−import Purelint.Rules.UseSequence (useSequenceRule)
46−import Purelint.Rules.UseTraverseSequence (useTraverseSequenceRule)
47−import Purelint.Rules.RedundantNot (redundantNotRule)
48−import Purelint.Rules.UseFstSnd (useFstSndRule)
49−import Purelint.Rules.UseMapMaybe (useMapMaybeRule)
50−import Purelint.Rules.UseGuardMaybe (useGuardMaybeRule)
51−import Purelint.Rules.UseMaybeMap (useMaybeMapRule)
52−import Purelint.Rules.UseApplicative (useApplicativeRule)
53−import Purelint.Rules.UseBindFlip (useBindFlipRule)
54−import Purelint.Rules.UseFor (useForRule)
55−import Purelint.Rules.RedundantGuard (redundantGuardRule)
56−import Purelint.Rules.UseComparing (useComparingRule)
57−import Purelint.Rules.UseOn (useOnRule)
58−import Purelint.Rules.UseFindMap (useFindMapRule)
59−import Purelint.Rules.UseLastReverse (useLastReverseRule)
60−import Purelint.Rules.RedundantReverse (redundantReverseRule)
61−import Purelint.Rules.UseBreak (useBreakRule)
62−import Purelint.Rules.UseSpan (useSpanRule)
63−import Purelint.Rules.UseMinimumSort (useMinimumSortRule)
64−import Purelint.Rules.UseBimap (useBimapRule)
65−import Purelint.Rules.UseEitherMap (useEitherMapRule)
66−import Purelint.Rules.EvaluateFst (evaluateFstRule)
67−import Purelint.Rules.EvaluateConst (evaluateConstRule)
68−import Purelint.Rules.UseOr (useOrRule)
69−import Purelint.Rules.UseAnd (useAndRule)
70−import Purelint.Rules.UseFoldMapId (useFoldMapIdRule)
71−import Purelint.Rules.WhenNot (whenNotRule)
72−import Purelint.Rules.UnlessNot (unlessNotRule)
73−import Purelint.Rules.UseZip (useZipRule)
74−import Purelint.Rules.UseReplicate (useReplicateRule)
75−import Purelint.Rules.UseUncurry (useUncurryRule)
76−import Purelint.Rules.RedundantNegate (redundantNegateRule)
77−import Purelint.Rules.UseFromJust (useFromJustRule)
78−import Purelint.Rules.UseHead (useHeadRule)
79−import Purelint.Rules.RedundantId (redundantIdRule)
80−import Purelint.Rules.NothingBind (nothingBindRule)
81−import Purelint.Rules.UseElemIndex (useElemIndexRule)
82−import Purelint.Rule (Rule)
83−import Purelint.Runner (runRules, getImportsFromSource, getModuleNames)
84−import Purelint.Imports (hasValue, isPreludeModule)
85−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(..))
86100
87101-- | Simple test framework
88102type TestResult = { passed :: Boolean, name :: String, message :: String }
@@ -143,12 +157,27 @@ assertFix name rules source expectedFixed =
143157 then runTest name true "OK"
144158 else runTest name false ("Expected:\n" <> expectedFixed <> "\nGot:\n" <> fixed)
145159
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+
146175-- | Run all tests and report results
147176main :: Effect Unit
148177main = do
149− log "Running purelint tests...\n"
178+ log "Running purslint tests...\n"
150179
151− let results = fmapIdTests
180+ let results = mapIdentityTests
152181 <> mapFusionTests
153182 <> useTraverseTests
154183 <> concatMapTests
@@ -198,20 +227,43 @@ main = do
198227 <> evaluateConstTests
199228 <> useOrTests
200229 <> useAndTests
230+ <> useFoldMapTests
231+ <> useApplyFlippedTests
201232 <> useFoldMapIdTests
202233 <> whenNotTests
203234 <> unlessNotTests
204235 <> useZipTests
236+ <> useReplicateTests
237+ <> useUncurryTests
238+ <> useFromJustTests
239+ <> useHeadTests
240+ <> useElemIndexTests
241+ <> useBreakTests
242+ <> useSpanTests
243+ <> useDollarTests
244+ <> useUnwrapTests
205245 <> redundantNegateTests
206246 <> redundantIdTests
207247 <> nothingBindTests
208− <> goldenTests
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
209261
210− let passed = Array.length $ Array.filter _.passed results
211− let total = Array.length results
262+ let passed = Array.length $ Array.filter _.passed allResults
263+ let total = Array.length allResults
212264
213265 -- Report failures
214− let failures = Array.filter (not <<< _.passed) results
266+ let failures = Array.filter (not <<< _.passed) allResults
215267 when (Array.length failures > 0) do
216268 log "FAILURES:"
217269 for_ failures (\r -> log (" ✗ " <> r.name <> ": " <> r.message))
@@ -239,42 +291,40 @@ main = do
239291
240292-- | Helper: wrap code in a module with Prelude import
241293withPrelude :: String -> String
242−withPrelude code = "module Test where\nimport Prelude\nimport Data.Maybe\nimport Data.Foldable\nimport Data.Tuple\nimport Data.Monoid\nimport Data.Traversable\nimport Data.Array\nimport Control.Alternative\nimport Data.Ord\nimport Data.Either\nimport Data.Bifunctor\nimport Data.List\n" <> code
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
243295
244296-- | Helper: wrap code in module without imports
245297withoutImports :: String -> String
246298withoutImports code = "module Test where\n" <> code
247299
248300-- ============================================================================
249−-- FmapId Tests
301+-- MapIdentity Tests
250302-- ============================================================================
251303
252−fmapIdTests :: Array TestResult
253−fmapIdTests =
304+mapIdentityTests :: Array TestResult
305+mapIdentityTests =
254306 -- Positive cases: should detect
255− [ assertWarningCount "FmapId: map identity x" [fmapIdRule]
307+ [ assertWarningCount "MapIdentity: map identity x" [mapIdentityRule]
256308 (withPrelude "x = map identity [1]") 1
257− , assertRuleId "FmapId: correct rule id" [fmapIdRule]
258− (withPrelude "x = map identity [1]") "FmapId"
259− , assertSuggestion "FmapId: map identity suggestion" [fmapIdRule]
260− (withPrelude "x = map identity [1]") "[1]"
261− , assertWarningCount "FmapId: fmap identity x" [fmapIdRule]
262− (withPrelude "x = fmap identity [1]") 1
263− , assertWarningCount "FmapId: identity <$> x" [fmapIdRule]
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]
264314 (withPrelude "x = identity <$> [1]") 1
265− , assertWarningCount "FmapId: x <#> identity" [fmapIdRule]
315+ , assertWarningCount "MapIdentity: x <#> identity" [mapIdentityRule]
266316 (withPrelude "x = [1] <#> identity") 1
267317
268318 -- Negative cases: should NOT detect
269− , assertWarningCount "FmapId: no fire on map show" [fmapIdRule]
319+ , assertWarningCount "MapIdentity: no fire on map show" [mapIdentityRule]
270320 (withPrelude "x = map show [1]") 0
271− , assertWarningCount "FmapId: no fire without import" [fmapIdRule]
321+ , assertWarningCount "MapIdentity: no fire without import" [mapIdentityRule]
272322 (withoutImports "x = map identity [1]") 0
273323
274324 -- Auto-fix test
275− , assertFix "FmapId: fix map identity" [fmapIdRule]
325+ , assertFix "MapIdentity: fix map identity" [mapIdentityRule]
276326 (withPrelude "x = map identity [1]")
277− (withPrelude "x = [1]")
327+ (withPrelude "x = [ 1 ]")
278328 ]
279329
280330-- ============================================================================
@@ -440,7 +490,7 @@ redundantBindTests =
440490 , assertRuleId "RedundantBind: correct rule id" [redundantBindRule]
441491 (withPrelude "x = [1] >>= pure") "RedundantBind"
442492 , assertSuggestion "RedundantBind: x >>= pure suggestion" [redundantBindRule]
443− (withPrelude "x = [1] >>= pure") "[1]"
493+ (withPrelude "x = [1] >>= pure") "[ 1 ]"
444494 , assertWarningCount "RedundantBind: pure x >>= f" [redundantBindRule]
445495 (withPrelude "x = pure 1 >>= show") 1
446496 , assertSuggestion "RedundantBind: pure x >>= f suggestion" [redundantBindRule]
@@ -469,13 +519,16 @@ letToWhereTests =
469519 , assertWarningCount "LetToWhere: multiple bindings" [letToWhereRule]
470520 (withPrelude "x = let a = 1\n b = 2 in a + b") 1
471521
472− -- Suggestion preserves spaces
522+ -- Suggestion preserves spaces based on let column position
473523 , assertSuggestion "LetToWhere: preserves spaces in simple case" [letToWhereRule]
474− (withPrelude "x = let y = 1 in y + 1") "y + 1\n where\n y = 1"
524+ (withPrelude "x = let y = 1 in y + 1") "y + 1\n where\n y = 1"
475525 , assertSuggestion "LetToWhere: preserves spaces in expressions" [letToWhereRule]
476− (withPrelude "f x = let a = x + 1 in a * 2") "a * 2\n where\n a = x + 1"
526+ (withPrelude "f x = let a = x + 1 in a * 2") "a * 2\n where\n a = x + 1"
477527 , assertSuggestion "LetToWhere: preserves spaces with multiple bindings" [letToWhereRule]
478− (withPrelude "f x = let a = x + 1\n b = x * 2 in a + b") "a + b\n where\n a = x + 1\n b = x * 2"
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"
479532 ]
480533
481534-- ============================================================================
@@ -578,7 +631,7 @@ useAnyTests =
578631 , assertRuleId "UseAny: correct rule id" [useAnyRule]
579632 (withPrelude "x = or (map f [1])") "UseAny"
580633 , assertSuggestion "UseAny: suggests any" [useAnyRule]
581− (withPrelude "x = or (map f [1])") "any f [1]"
634+ (withPrelude "x = or (map f [1])") "any f [ 1 ]"
582635
583636 -- Negative cases
584637 , assertWarningCount "UseAny: no fire on or alone" [useAnyRule]
@@ -599,7 +652,7 @@ useAllTests =
599652 , assertRuleId "UseAll: correct rule id" [useAllRule]
600653 (withPrelude "x = and (map f [1])") "UseAll"
601654 , assertSuggestion "UseAll: suggests all" [useAllRule]
602− (withPrelude "x = and (map f [1])") "all f [1]"
655+ (withPrelude "x = and (map f [1])") "all f [ 1 ]"
603656
604657 -- Negative cases
605658 , assertWarningCount "UseAll: no fire on and alone" [useAllRule]
@@ -662,19 +715,21 @@ collapseLambdasTests =
662715
663716useConstTests :: Array TestResult
664717useConstTests =
665− -- Positive cases
666− [ assertWarningCount "UseConst: \\x -> y (unused)" [useConstRule]
667− (withPrelude "x = \\a -> 42") 1
718+ -- Positive cases: only fires on wildcard binder \_
719+ [ assertWarningCount "UseConst: \\_ -> y" [useConstRule]
720+ (withPrelude "x = \\_ -> 42") 1
668721 , assertRuleId "UseConst: correct rule id" [useConstRule]
669− (withPrelude "x = \\a -> 42") "UseConst"
722+ (withPrelude "x = \\_ -> 42") "UseConst"
670723 , assertSuggestion "UseConst: suggests const" [useConstRule]
671− (withPrelude "x = \\a -> 42") "const 42"
724+ (withPrelude "x = \\_ -> 42") "const 42"
672725
673726 -- Negative cases
727+ , assertWarningCount "UseConst: no fire on named param (compiler warns)" [useConstRule]
728+ (withPrelude "x = \\a -> 42") 0
674729 , assertWarningCount "UseConst: no fire when param used" [useConstRule]
675730 (withPrelude "x = \\a -> a + 1") 0
676731 , assertWarningCount "UseConst: no fire without import" [useConstRule]
677− (withoutImports "x = \\a -> 42") 0
732+ (withoutImports "x = \\_ -> 42") 0
678733 ]
679734
680735-- ============================================================================
@@ -689,7 +744,7 @@ useNullTests =
689744 , assertRuleId "UseNull: correct rule id" [useNullRule]
690745 (withPrelude "x = length [1] == 0") "UseNull"
691746 , assertSuggestion "UseNull: suggests null" [useNullRule]
692− (withPrelude "x = length [1] == 0") "null [1]"
747+ (withPrelude "x = length [1] == 0") "null [ 1 ]"
693748 , assertWarningCount "UseNull: 0 == length x" [useNullRule]
694749 (withPrelude "x = 0 == length [1]") 1
695750
@@ -815,7 +870,7 @@ useNotElemTests =
815870 , assertRuleId "UseNotElem: correct rule id" [useNotElemRule]
816871 (withPrelude "x = not (elem 1 [1,2,3])") "UseNotElem"
817872 , assertSuggestion "UseNotElem: suggests notElem" [useNotElemRule]
818− (withPrelude "x = not (elem 1 [1,2,3])") "notElem 1 [1,2,3]"
873+ (withPrelude "x = not (elem 1 [1,2,3])") "notElem 1 [ 1, 2, 3 ]"
819874
820875 -- Negative cases
821876 , assertWarningCount "UseNotElem: no fire on not (foo x)" [useNotElemRule]
@@ -855,7 +910,7 @@ monoidIdentityTests =
855910 , assertRuleId "MonoidIdentity: correct rule id" [monoidIdentityRule]
856911 (withPrelude "x = mempty <> [1,2,3]") "MonoidIdentity"
857912 , assertSuggestion "MonoidIdentity: suggests x" [monoidIdentityRule]
858− (withPrelude "x = mempty <> [1,2,3]") "[1,2,3]"
913+ (withPrelude "x = mempty <> [1,2,3]") "[ 1, 2, 3 ]"
859914 , assertWarningCount "MonoidIdentity: x <> mempty" [monoidIdentityRule]
860915 (withPrelude "x = [1,2,3] <> mempty") 1
861916
@@ -911,7 +966,7 @@ useTraverseSequenceTests =
911966 , assertRuleId "UseTraverseSequence: correct rule id" [useTraverseSequenceRule]
912967 (withPrelude "x = sequence (map show [1,2,3])") "UseTraverseSequence"
913968 , assertSuggestion "UseTraverseSequence: suggests traverse" [useTraverseSequenceRule]
914− (withPrelude "x = sequence (map show [1,2,3])") "traverse show [1,2,3]"
969+ (withPrelude "x = sequence (map show [1,2,3])") "traverse show [ 1, 2, 3 ]"
915970
916971 -- Negative cases
917972 , assertWarningCount "UseTraverseSequence: no fire on sequence xs" [useTraverseSequenceRule]
@@ -969,7 +1024,7 @@ useMapMaybeTests =
9691024 , assertRuleId "UseMapMaybe: correct rule id" [useMapMaybeRule]
9701025 (withPrelude "x = catMaybes (map f [1,2,3])") "UseMapMaybe"
9711026 , assertSuggestion "UseMapMaybe: suggests mapMaybe" [useMapMaybeRule]
972− (withPrelude "x = catMaybes (map f [1,2,3])") "mapMaybe f [1,2,3]"
1027+ (withPrelude "x = catMaybes (map f [1,2,3])") "mapMaybe f [ 1, 2, 3 ]"
9731028 ]
9741029
9751030-- ============================================================================
@@ -1073,7 +1128,7 @@ useFindMapTests =
10731128 , assertRuleId "UseFindMap: correct rule id" [useFindMapRule]
10741129 (withPrelude "x = head (mapMaybe f [1,2,3])") "UseFindMap"
10751130 , assertSuggestion "UseFindMap: suggests findMap" [useFindMapRule]
1076− (withPrelude "x = head (mapMaybe f [1,2,3])") "findMap f [1,2,3]"
1131+ (withPrelude "x = head (mapMaybe f [1,2,3])") "findMap f [ 1, 2, 3 ]"
10771132 ]
10781133
10791134-- ============================================================================
@@ -1087,7 +1142,7 @@ useLastReverseTests =
10871142 , assertRuleId "UseLastReverse: correct rule id" [useLastReverseRule]
10881143 (withPrelude "x = head (reverse [1,2,3])") "UseLastReverse"
10891144 , assertSuggestion "UseLastReverse: suggests last" [useLastReverseRule]
1090− (withPrelude "x = head (reverse [1,2,3])") "last [1,2,3]"
1145+ (withPrelude "x = head (reverse [1,2,3])") "last [ 1, 2, 3 ]"
10911146 ]
10921147
10931148-- ============================================================================
@@ -1115,7 +1170,7 @@ useMinimumSortTests =
11151170 , assertRuleId "UseMinimumSort: correct rule id" [useMinimumSortRule]
11161171 (withPrelude "x = head (sort [1,2,3])") "UseMinimumSort"
11171172 , assertSuggestion "UseMinimumSort: suggests minimum" [useMinimumSortRule]
1118− (withPrelude "x = head (sort [1,2,3])") "minimum [1,2,3]"
1173+ (withPrelude "x = head (sort [1,2,3])") "minimum [ 1, 2, 3 ]"
11191174 ]
11201175
11211176-- ============================================================================
@@ -1175,7 +1230,7 @@ useOrTests =
11751230 , assertRuleId "UseOr: correct rule id" [useOrRule]
11761231 (withPrelude "x = any identity [true, false]") "UseOr"
11771232 , assertSuggestion "UseOr: suggests or" [useOrRule]
1178− (withPrelude "x = any identity [true, false]") "or [true, false]"
1233+ (withPrelude "x = any identity [true, false]") "or [ true, false ]"
11791234 ]
11801235
11811236-- ============================================================================
@@ -1189,7 +1244,53 @@ useAndTests =
11891244 , assertRuleId "UseAnd: correct rule id" [useAndRule]
11901245 (withPrelude "x = all identity [true, false]") "UseAnd"
11911246 , assertSuggestion "UseAnd: suggests and" [useAndRule]
1192− (withPrelude "x = all identity [true, false]") "and [true, false]"
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 <> \"!\""
11931294 ]
11941295
11951296-- ============================================================================
@@ -1245,7 +1346,163 @@ useZipTests =
12451346 , assertRuleId "UseZip: correct rule id" [useZipRule]
12461347 (withPrelude "x = zipWith Tuple [1] [2]") "UseZip"
12471348 , assertSuggestion "UseZip: suggests zip" [useZipRule]
1248− (withPrelude "x = zipWith Tuple [1] [2]") "zip [1] [2]"
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"
12491506 ]
12501507
12511508-- ============================================================================
@@ -1291,13 +1548,351 @@ nothingBindTests =
12911548 ]
12921549
12931550-- ============================================================================
1294−-- Golden Tests: LintExamples.purs
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
12951736-- ============================================================================
12961737
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+
12971892-- | All rules for golden testing
12981893allRulesForGolden :: Array Rule
12991894allRulesForGolden =
1300− [ fmapIdRule
1895+ [ mapIdentityRule
13011896 , mapFusionRule
13021897 , useTraverseRule
13031898 , concatMapRule
@@ -1343,200 +1938,222 @@ allRulesForGolden =
13431938 , useFindMapRule
13441939 ]
13451940
1346−-- | Assert that a specific rule fires at least once when running all rules
1347−assertRuleFires :: String -> String -> String -> TestResult
1348−assertRuleFires name source ruleId =
1349− case runRules allRulesForGolden (SourceCode source) of
1350− Left err -> runTest name false ("Parse error: " <> err)
1351− Right (LintResult result) ->
1352− let matches = Array.filter (\(LintWarning w) -> unwrap w.ruleId == ruleId) result.warnings
1353− in if Array.length matches > 0
1354− then runTest name true "OK"
1355− else runTest name false ("Rule " <> ruleId <> " did not fire")
1356−
1357−-- | The embedded LintExamples source
1358−lintExamplesSource :: String
1359−lintExamplesSource = """module Test.LintExamples where
1360−
1361−import Prelude
1362−
1363−import Control.Alternative
1364−import Control.Monad
1365−import Data.Array
1366−import Data.Foldable
1367−import Data.Maybe
1368−import Data.Monoid
1369−import Data.Ord
1370−import Data.Traversable
1371−import Data.Tuple
1372−
1373−-- FmapId
1374−fmapId1 = map identity [ 1, 2, 3 ]
1375−
1376−-- MapFusion
1377−mapFusion1 = map show (map (_ + 1) [ 1, 2, 3 ])
1378−
1379−-- UseTraverse
1380−useTraverse1 f xs = sequence (map f xs)
1381−
1382−-- ConcatMap
1383−concatMap1 f xs = join (map f xs)
1384−
1385−-- NotEqual
1386−notEqual1 x y = not (x == y)
1387−
1388−-- UseGuard
1389−useGuard1 cond action = if cond then action else pure unit
1390−
1391−-- EtaReduce
1392−etaReduce1 = \x -> show x
1393−
1394−-- EtaReduceDecl
1395−etaReduceDecl1 x = show x
1396−
1397−-- RedundantBind
1398−redundantBind1 x f = pure x >>= f
1399−
1400−-- LetToWhere
1401−letToWhere1 y z = let x = y in z
1402−
1403−-- RedundantIf
1404−redundantIf1 a = if a then true else false
1405−
1406−-- UseVoid
1407−useVoid1 a = a *> pure unit
1408−
1409−-- UseJoin
1410−useJoin1 x = x >>= identity
1411−
1412−-- UseAny
1413−useAny1 f xs = or (map f xs)
1414−
1415−-- UseAll
1416−useAll1 f xs = and (map f xs)
1417−
1418−-- BooleanSimplify
1419−booleanSimplify1 x = x == true
1420−
1421−-- CollapseLambdas
1422−collapseLambdas1 = \x -> \y -> x + y
1423−
1424−-- UseConst
1425−useConst1 = \x -> 42
1426−
1427−-- UseNull
1428−useNull1 xs = length xs == 0
1429−
1430−-- UseFromMaybe
1431−useFromMaybe1 def = maybe def identity
1432−
1433−-- UseIsJust
1434−useIsJust1 = maybe false (const true)
1435−
1436−-- UseIsNothing
1437−useIsNothing1 = maybe true (const false)
1438−
1439−-- UseWhen
1440−useWhen1 cond action = if cond then action else pure unit
1441−
1442−-- UseUnless
1443−useUnless1 cond action = if cond then pure unit else action
1444−
1445−-- RedundantFlip
1446−redundantFlip1 f = flip (flip f)
1447−
1448−-- UseNotElem
1449−useNotElem1 x xs = not (elem x xs)
1450−
1451−-- UseMinMax
1452−useMax1 a b = if a > b then a else b
1453−
1454−-- MonoidIdentity
1455−monoidIdentity1 x = mempty <> x
1456−
1457−-- UseFold
1458−useFold1 = foldMap identity
1459−
1460−-- UseSequence
1461−useSequence1 = traverse identity
1462−
1463−-- UseTraverseSequence
1464−useTraverseSequence1 f xs = sequence (map f xs)
1465−
1466−-- RedundantNot
1467−redundantNot1 x = not (not x)
1468−
1469−-- UseFstSnd
1470−useFst1 = \(Tuple x y) -> x
1471−
1472−-- UseMapMaybe
1473−useMapMaybe1 = catMaybes (map f [1,2,3])
1474−
1475−-- UseApplicative
1476−useApplicative1 = pure f <*> x
1477−
1478−-- UseBindFlip
1479−useBindFlip1 = join (map f x)
1480−
1481−-- UseFor
1482−useFor1 = flip traverse
1483−
1484−-- RedundantGuard
1485−redundantGuard1 = guard true
1486−
1487−-- UseComparing
1488−useComparing1 = \a b -> compare (f a) (f b)
1489−
1490−-- UseOn
1491−useOn1 = \a b -> f (g a) (g b)
1492−
1493−-- UseFindMap
1494−useFindMap1 = head (mapMaybe f [1,2,3])
1495−"""
1496−
1497−goldenTests :: Array TestResult
1498−goldenTests =
1499− [ assertRuleFires "Golden: FmapId fires" lintExamplesSource "FmapId"
1500− , assertRuleFires "Golden: MapFusion fires" lintExamplesSource "MapFusion"
1501− , assertRuleFires "Golden: UseTraverse fires" lintExamplesSource "UseTraverse"
1502− , assertRuleFires "Golden: ConcatMap fires" lintExamplesSource "ConcatMap"
1503− , assertRuleFires "Golden: NotEqual fires" lintExamplesSource "NotEqual"
1504− , assertRuleFires "Golden: UseGuard fires" lintExamplesSource "UseGuard"
1505− , assertRuleFires "Golden: EtaReduce fires" lintExamplesSource "EtaReduce"
1506− , assertRuleFires "Golden: EtaReduceDecl fires" lintExamplesSource "EtaReduceDecl"
1507− , assertRuleFires "Golden: RedundantBind fires" lintExamplesSource "RedundantBind"
1508− , assertRuleFires "Golden: LetToWhere fires" lintExamplesSource "LetToWhere"
1509− , assertRuleFires "Golden: RedundantIf fires" lintExamplesSource "RedundantIf"
1510− , assertRuleFires "Golden: UseVoid fires" lintExamplesSource "UseVoid"
1511− , assertRuleFires "Golden: UseJoin fires" lintExamplesSource "UseJoin"
1512− , assertRuleFires "Golden: UseAny fires" lintExamplesSource "UseAny"
1513− , assertRuleFires "Golden: UseAll fires" lintExamplesSource "UseAll"
1514− , assertRuleFires "Golden: BooleanSimplify fires" lintExamplesSource "BooleanSimplify"
1515− , assertRuleFires "Golden: CollapseLambdas fires" lintExamplesSource "CollapseLambdas"
1516− , assertRuleFires "Golden: UseConst fires" lintExamplesSource "UseConst"
1517− , assertRuleFires "Golden: UseNull fires" lintExamplesSource "UseNull"
1518− , assertRuleFires "Golden: UseFromMaybe fires" lintExamplesSource "UseFromMaybe"
1519− , assertRuleFires "Golden: UseIsJust fires" lintExamplesSource "UseIsJust"
1520− , assertRuleFires "Golden: UseIsNothing fires" lintExamplesSource "UseIsNothing"
1521− , assertRuleFires "Golden: UseWhen fires" lintExamplesSource "UseWhen"
1522− , assertRuleFires "Golden: UseUnless fires" lintExamplesSource "UseUnless"
1523− , assertRuleFires "Golden: RedundantFlip fires" lintExamplesSource "RedundantFlip"
1524− , assertRuleFires "Golden: UseNotElem fires" lintExamplesSource "UseNotElem"
1525− , assertRuleFires "Golden: UseMinMax fires" lintExamplesSource "UseMinMax"
1526− , assertRuleFires "Golden: MonoidIdentity fires" lintExamplesSource "MonoidIdentity"
1527− , assertRuleFires "Golden: UseFold fires" lintExamplesSource "UseFold"
1528− , assertRuleFires "Golden: UseSequence fires" lintExamplesSource "UseSequence"
1529− , assertRuleFires "Golden: UseTraverseSequence fires" lintExamplesSource "UseTraverseSequence"
1530− , assertRuleFires "Golden: RedundantNot fires" lintExamplesSource "RedundantNot"
1531− , assertRuleFires "Golden: UseFstSnd fires" lintExamplesSource "UseFstSnd"
1532− , assertRuleFires "Golden: UseMapMaybe fires" lintExamplesSource "UseMapMaybe"
1533− , assertRuleFires "Golden: UseApplicative fires" lintExamplesSource "UseApplicative"
1534− , assertRuleFires "Golden: UseBindFlip fires" lintExamplesSource "UseBindFlip"
1535− , assertRuleFires "Golden: UseFor fires" lintExamplesSource "UseFor"
1536− , assertRuleFires "Golden: RedundantGuard fires" lintExamplesSource "RedundantGuard"
1537− , assertRuleFires "Golden: UseComparing fires" lintExamplesSource "UseComparing"
1538− , assertRuleFires "Golden: UseOn fires" lintExamplesSource "UseOn"
1539− , assertRuleFires "Golden: UseFindMap fires" lintExamplesSource "UseFindMap"
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")
15402157 ]
15412158
15422159-- ============================================================================
@@ -1575,7 +2192,7 @@ tidySuggestions =
15752192 , { name: "traverse f xs", suggestion: "traverse f xs" }
15762193 , { name: "collapsed lambda", suggestion: "\\a b -> a + b" }
15772194 , { name: "map fusion", suggestion: "map (f <<< g) x" }
1578− , { name: "fmap fusion", suggestion: "(f <<< g) <$> x" }
2195+ , { name: "map fusion with <$>", suggestion: "(f <<< g) <$> x" }
15792196 , { name: "flipped map fusion", suggestion: "x <#> (g >>> f)" }
15802197 , { name: "where clause", suggestion: "y + 1\n where\n y = 1" }
15812198 ]