@@ -10,90 +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, formatModule)
15−import Purelint.Rules.MapIdentity (mapIdentityRule)
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.LetToDo (letToDoRule)
26−import Purelint.Rules.RedundantIf (redundantIfRule)
27−import Purelint.Rules.FunctorLaw (functorLawRule)
28−import Purelint.Rules.UseVoid (useVoidRule)
29−import Purelint.Rules.UseJoin (useJoinRule)
30−import Purelint.Rules.UseAny (useAnyRule)
31−import Purelint.Rules.UseAll (useAllRule)
32−import Purelint.Rules.BooleanSimplify (booleanSimplifyRule)
33−import Purelint.Rules.CollapseLambdas (collapseLambdasRule)
34−import Purelint.Rules.UseConst (useConstRule)
35−import Purelint.Rules.UseNull (useNullRule)
36−import Purelint.Rules.UseFromMaybe (useFromMaybeRule)
37−import Purelint.Rules.UseIsJust (useIsJustRule)
38−import Purelint.Rules.UseIsNothing (useIsNothingRule)
39−import Purelint.Rules.UseWhen (useWhenRule)
40−import Purelint.Rules.UseUnless (useUnlessRule)
41−import Purelint.Rules.RedundantFlip (redundantFlipRule)
42−import Purelint.Rules.UseNotElem (useNotElemRule)
43−import Purelint.Rules.UseMinMax (useMinMaxRule)
44−import Purelint.Rules.MonoidIdentity (monoidIdentityRule)
45−import Purelint.Rules.UseFold (useFoldRule)
46−import Purelint.Rules.UseSequence (useSequenceRule)
47−import Purelint.Rules.UseTraverseSequence (useTraverseSequenceRule)
48−import Purelint.Rules.RedundantNot (redundantNotRule)
49−import Purelint.Rules.UseFstSnd (useFstSndRule)
50−import Purelint.Rules.UseMapMaybe (useMapMaybeRule)
51−import Purelint.Rules.UseGuardMaybe (useGuardMaybeRule)
52−import Purelint.Rules.UseMaybeMap (useMaybeMapRule)
53−import Purelint.Rules.UseApplicative (useApplicativeRule)
54−import Purelint.Rules.UseBindFlip (useBindFlipRule)
55−import Purelint.Rules.UseFor (useForRule)
56−import Purelint.Rules.RedundantGuard (redundantGuardRule)
57−import Purelint.Rules.UseComparing (useComparingRule)
58−import Purelint.Rules.UseOn (useOnRule)
59−import Purelint.Rules.UseFindMap (useFindMapRule)
60−import Purelint.Rules.UseLastReverse (useLastReverseRule)
61−import Purelint.Rules.RedundantReverse (redundantReverseRule)
62−import Purelint.Rules.UseBreak (useBreakRule)
63−import Purelint.Rules.UseSpan (useSpanRule)
64−import Purelint.Rules.UseMinimumSort (useMinimumSortRule)
65−import Purelint.Rules.UseBimap (useBimapRule)
66−import Purelint.Rules.UseEitherMap (useEitherMapRule)
67−import Purelint.Rules.EvaluateFst (evaluateFstRule)
68−import Purelint.Rules.EvaluateConst (evaluateConstRule)
69−import Purelint.Rules.UseOr (useOrRule)
70−import Purelint.Rules.UseAnd (useAndRule)
71−import Purelint.Rules.UseFoldMap (useFoldMapRule)
72−import Purelint.Rules.UseFoldMapId (useFoldMapIdRule)
73−import Purelint.Rules.UseApplyFlipped (useApplyFlippedRule)
74−import Purelint.Rules.WhenNot (whenNotRule)
75−import Purelint.Rules.UnlessNot (unlessNotRule)
76−import Purelint.Rules.UseZip (useZipRule)
77−import Purelint.Rules.UseReplicate (useReplicateRule)
78−import Purelint.Rules.UseUncurry (useUncurryRule)
79−import Purelint.Rules.RedundantNegate (redundantNegateRule)
80−import Purelint.Rules.UseFromJust (useFromJustRule)
81−import Purelint.Rules.UseHead (useHeadRule)
82−import Purelint.Rules.RedundantId (redundantIdRule)
83−import Purelint.Rules.NothingBind (nothingBindRule)
84−import Purelint.Rules.UseElemIndex (useElemIndexRule)
85−import Purelint.Rules.MonadLaw (monadLawRule)
86−import Purelint.Rules.EvaluateBool (evaluateBoolRule)
87−import Purelint.Rules.UseFoldBool (useFoldBoolRule)
88−import Purelint.Rules.AlternativeLaw (alternativeLawRule)
89−import Purelint.Rules.EvaluateEither (evaluateEitherRule)
90−import Purelint.Rules.UseCaseOf (useCaseOfRule)
91−import Purelint.Rules.FlattenCase (flattenCaseRule)
92−import Purelint.Rules.UsePatternGuards (usePatternGuardsRule)
93−import Purelint.Rule (Rule)
94−import Purelint.Runner (runRules, getImportsFromSource, getModuleNames)
95−import Purelint.Imports (hasValue, isPreludeModule)
96−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(..))
97100
98101-- | Simple test framework
99102type TestResult = { passed :: Boolean, name :: String, message :: String }
@@ -172,7 +175,7 @@ assertFixFormatted name rules source expectedFixed =
172175-- | Run all tests and report results
173176main :: Effect Unit
174177main = do
175− log "Running purelint tests...\n"
178+ log "Running purslint tests...\n"
176179
177180 let results = mapIdentityTests
178181 <> mapFusionTests
@@ -230,6 +233,15 @@ main = do
230233 <> whenNotTests
231234 <> unlessNotTests
232235 <> useZipTests
236+ <> useReplicateTests
237+ <> useUncurryTests
238+ <> useFromJustTests
239+ <> useHeadTests
240+ <> useElemIndexTests
241+ <> useBreakTests
242+ <> useSpanTests
243+ <> useDollarTests
244+ <> useUnwrapTests
233245 <> redundantNegateTests
234246 <> redundantIdTests
235247 <> nothingBindTests
@@ -241,6 +253,7 @@ main = do
241253 <> useCaseOfTests
242254 <> flattenCaseTests
243255 <> usePatternGuardsTests
256+ <> redundantParensTests
244257
245258 -- Run effectful golden tests (with purs-tidy check)
246259 goldenResults <- goldenTests
@@ -702,19 +715,21 @@ collapseLambdasTests =
702715
703716useConstTests :: Array TestResult
704717useConstTests =
705− -- Positive cases
706− [ assertWarningCount "UseConst: \\x -> y (unused)" [useConstRule]
707− (withPrelude "x = \\a -> 42") 1
718+ -- Positive cases: only fires on wildcard binder \_
719+ [ assertWarningCount "UseConst: \\_ -> y" [useConstRule]
720+ (withPrelude "x = \\_ -> 42") 1
708721 , assertRuleId "UseConst: correct rule id" [useConstRule]
709− (withPrelude "x = \\a -> 42") "UseConst"
722+ (withPrelude "x = \\_ -> 42") "UseConst"
710723 , assertSuggestion "UseConst: suggests const" [useConstRule]
711− (withPrelude "x = \\a -> 42") "const 42"
724+ (withPrelude "x = \\_ -> 42") "const 42"
712725
713726 -- Negative cases
727+ , assertWarningCount "UseConst: no fire on named param (compiler warns)" [useConstRule]
728+ (withPrelude "x = \\a -> 42") 0
714729 , assertWarningCount "UseConst: no fire when param used" [useConstRule]
715730 (withPrelude "x = \\a -> a + 1") 0
716731 , assertWarningCount "UseConst: no fire without import" [useConstRule]
717− (withoutImports "x = \\a -> 42") 0
732+ (withoutImports "x = \\_ -> 42") 0
718733 ]
719734
720735-- ============================================================================
@@ -1248,6 +1263,20 @@ useFoldMapTests =
12481263 (withPrelude "x mx = case mx of\n Nothing -> \"\"\n Just s -> s <> \"!\"") "foldMap (\\s -> s <> \"!\") mx"
12491264 , assertWarningCount "UseFoldMap: reversed order Just/Nothing" [useFoldMapRule]
12501265 (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)"
12511280 ]
12521281
12531282-- ============================================================================
@@ -1320,6 +1349,162 @@ useZipTests =
13201349 (withPrelude "x = zipWith Tuple [1] [2]") "zip [ 1 ] [ 2 ]"
13211350 ]
13221351
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+
13231508-- ============================================================================
13241509-- RedundantNegate Tests
13251510-- ============================================================================
@@ -1478,6 +1663,18 @@ useCaseOfTests =
14781663 , assertSuggestion "UseCaseOf: two args suggestion" [useCaseOfRule]
14791664 (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"
14801665
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+
14811678 -- Negative cases: should NOT detect
14821679 , assertWarningCount "UseCaseOf: single clause" [useCaseOfRule]
14831680 (withPrelude "foo x = x + 1") 0
@@ -1534,6 +1731,155 @@ usePatternGuardsTests =
15341731 (withPrelude "foo x y = case x of\n Just a\n | Just b <- y -> a + b\n _ -> 0")
15351732 ]
15361733
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+
15371883-- | Helper for Alternative imports
15381884withAlt :: String -> String
15391885withAlt code = """
@@ -1664,9 +2010,9 @@ goldenTests = traverse identity
16642010 (withPrelude "x = \\a -> \\b -> a + b")
16652011 (withPrelude "x = \\a b -> a + b")
16662012
1667− -- UseConst: \x -> y -> const y (where y doesn't mention x)
2013+ -- UseConst: \_ -> y -> const y
16682014 , assertFixFormatted "Golden: UseConst fix" [useConstRule]
1669− (withPrelude "x = \\unused -> 42")
2015+ (withPrelude "x = \\_ -> 42")
16702016 (withPrelude "x = const 42")
16712017
16722018 -- UseNull: length xs == 0 -> null xs
@@ -1793,6 +2139,21 @@ goldenTests = traverse identity
17932139 , pure $ assertFix "Golden: UsePatternGuards preserves do-let" [usePatternGuardsRule]
17942140 (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 _ -> []")
17952141 (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")
17962157 ]
17972158
17982159-- ============================================================================