git.iko.soy/rowtype-yoga/purslint
on main help sign in
LetToWhere: preserve spaces in replacement text

Print tokens with original spacing preserved but newlines collapsed to single spaces. This produces readable output like `a + b` instead of `a+b`. Added tests for space preservation in suggestions. ๐Ÿ’˜ Generated with Crush Assisted-by: Claude Opus 4.5 via Crush <crush@charm.land>

commit121746c38299d7872bc0039981e64c14e8765b2c authorMark <mark.eibes@gmail.com> dateSun 25 Jan 2026, 15:57 (8 months ago) parentd69d827
3 files changed+37โˆ’12
lib/purelint/src/Purelint/Rules/LetToWhere.purs+26โˆ’7 test/LintExamples.purs+3โˆ’5 test/Test/Main.purs+8
lib/purelint/src/Purelint/Rules/LetToWhere.purs+26โˆ’7
@@ -5,14 +5,16 @@ import Prelude
55import Data.Array.NonEmpty as NEA
66import Data.Foldable (foldMap)
77import Data.Maybe (Maybe(..))
8+import Data.Monoid (power)
89import Data.String (joinWith) as String
10+import Data.String as Str
911import Purelint.Rule (Rule, RuleContext, mkRule)
1012import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1113import PureScript.CST.Print (printToken)
1214import PureScript.CST.Range (rangeOf, tokensOf)
1315import PureScript.CST.Range.TokenList as TokenList
1416import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
15โˆ’import PureScript.CST.Types (Expr(..), LetBinding, Module)
17+import PureScript.CST.Types (Comment(..), Expr(..), LetBinding, Module, SourceToken)
1618
1719-- | Rule: let x = y in z -> z where x = y
1820-- | Suggests replacing let...in expressions with where clauses
@@ -28,8 +30,8 @@ letToWhereRule = mkRule (RuleId "LetToWhere") run
2830 checkExpr expr = case expr of
2931 ExprLet letIn ->
3032 let
31โˆ’ bodyText = printExpr letIn.body
32โˆ’ bindings = NEA.toArray letIn.bindings # map printBinding
33+ bodyText = printTokens letIn.body
34+ bindings = NEA.toArray letIn.bindings # map printBindingTokens
3335 bindingsText = String.joinWith "\n " bindings
3436 replacement = bodyText <> "\n where\n " <> bindingsText
3537 in
@@ -46,8 +48,25 @@ letToWhereRule = mkRule (RuleId "LetToWhere") run
4648 ]
4749 _ -> []
4850
49โˆ’ printExpr :: Expr Void -> String
50โˆ’ printExpr e = foldMap (\tok -> printToken tok.value) (TokenList.toArray (tokensOf e))
51+ -- | Print tokens preserving spaces but collapsing newlines to single space
52+ printTokens :: Expr Void -> String
53+ printTokens e =
54+ Str.trim $ foldMap printSourceTokenWithSpaces (TokenList.toArray (tokensOf e))
5155
52โˆ’ printBinding :: LetBinding Void -> String
53โˆ’ printBinding b = foldMap (\tok -> printToken tok.value) (TokenList.toArray (tokensOf b))
56+ printBindingTokens :: LetBinding Void -> String
57+ printBindingTokens b =
58+ Str.trim $ foldMap printSourceTokenWithSpaces (TokenList.toArray (tokensOf b))
59+
60+ -- | Print a source token, keeping spaces but converting newlines to spaces
61+ printSourceTokenWithSpaces :: SourceToken -> String
62+ printSourceTokenWithSpaces tok =
63+ foldMap printCommentSpacesOnly tok.leadingComments
64+ <> printToken tok.value
65+ <> foldMap printCommentSpacesOnly tok.trailingComments
66+
67+ -- | Print comment, keeping spaces but skipping newlines
68+ printCommentSpacesOnly :: forall l. Comment l -> String
69+ printCommentSpacesOnly = case _ of
70+ Comment str -> str
71+ Space n -> power " " n
72+ Line _ _ -> " "
test/LintExamples.purs+3โˆ’5
@@ -106,7 +106,7 @@ etaReduceDecl1 x = show x
106106
107107-- | foo a b = bar a b -> foo a = bar a
108108etaReduceDecl2 :: String -> String -> String
109โˆ’etaReduceDecl2 a b = append a b
109+etaReduceDecl2 a = append a
110110
111111-- ============================================================================
112112-- RedundantBind: x >>= pure -> x
@@ -125,10 +125,8 @@ redundantBind2 x f = pure x >>= f
125125-- ============================================================================
126126
127127-- | let x = y in z -> z where x = y
128โˆ’letToWhere1 :: Int -> Int
129โˆ’letToWhere1 x = y * 2
130โˆ’ where
131โˆ’ y = x + 1
128+letToWhere1 :: Int -> Int -> Int
129+letToWhere1 y z = let x = y in z
132130
133131-- | Multiple let bindings
134132letToWhere2 :: Int -> Int
test/Test/Main.purs+8
@@ -340,4 +340,12 @@ letToWhereTests =
340340 -- Multiple bindings
341341 , assertWarningCount "LetToWhere: multiple bindings" [letToWhereRule]
342342 (withPrelude "x = let a = 1\n b = 2 in a + b") 1
343+
344+ -- Suggestion preserves spaces
345+ , assertSuggestion "LetToWhere: preserves spaces in simple case" [letToWhereRule]
346+ (withPrelude "x = let y = 1 in y + 1") "y + 1\n where\n y = 1"
347+ , assertSuggestion "LetToWhere: preserves spaces in expressions" [letToWhereRule]
348+ (withPrelude "f x = let a = x + 1 in a * 2") "a * 2\n where\n a = x + 1"
349+ , assertSuggestion "LetToWhere: preserves spaces with multiple bindings" [letToWhereRule]
350+ (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"
343351 ]