@@ -4,100 +4,113 @@ import Prelude
44
55import Data.Array as Array
66import Data.Either (Either(..))
7+import Data.Enum (fromEnum)
78import Data.Foldable (for_)
89import Data.Maybe (Maybe(..))
910import Data.Newtype (unwrap)
1011import Data.String.CodeUnits as String
12+import Data.Tuple.Nested ((/\))
1113import Effect (Effect)
1214import Effect.Console (log)
1315import Node.FS.Simple as FS
1416import Node.CLI as Process
15−import Purelint.Config (defaultConfig, filterRules, parseConfig)
16−import Purelint.Fix (applyAllFixes)
17−import Purelint.Rule (Rule)
18−import Purelint.Rules.BooleanSimplify (booleanSimplifyRule)
19−import Purelint.Rules.CollapseLambdas (collapseLambdasRule)
20−import Purelint.Rules.ConcatMap (concatMapRule)
21−import Purelint.Rules.EtaReduce (etaReduceRule)
22−import Purelint.Rules.EtaReduceDecl (etaReduceDeclRule)
23−import Purelint.Rules.AlternativeLaw (alternativeLawRule)
24−import Purelint.Rules.EvaluateBool (evaluateBoolRule)
25−import Purelint.Rules.EvaluateConst (evaluateConstRule)
26−import Purelint.Rules.EvaluateEither (evaluateEitherRule)
27−import Purelint.Rules.EvaluateFst (evaluateFstRule)
28−import Purelint.Rules.MapIdentity (mapIdentityRule)
29−import Purelint.Rules.FunctorLaw (functorLawRule)
30−import Purelint.Rules.LetToDo (letToDoRule)
31−import Purelint.Rules.LetToWhere (letToWhereRule)
32−import Purelint.Rules.MapFusion (mapFusionRule)
33−import Purelint.Rules.MonadLaw (monadLawRule)
34−import Purelint.Rules.MonoidIdentity (monoidIdentityRule)
35−import Purelint.Rules.NotEqual (notEqualRule)
36−import Purelint.Rules.NothingBind (nothingBindRule)
37−import Purelint.Rules.RedundantBind (redundantBindRule)
38−import Purelint.Rules.RedundantFlip (redundantFlipRule)
39−import Purelint.Rules.RedundantGuard (redundantGuardRule)
40−import Purelint.Rules.RedundantId (redundantIdRule)
41−import Purelint.Rules.RedundantIf (redundantIfRule)
42−import Purelint.Rules.RedundantNegate (redundantNegateRule)
43−import Purelint.Rules.RedundantNot (redundantNotRule)
44−import Purelint.Rules.RedundantReverse (redundantReverseRule)
45−import Purelint.Rules.UnlessNot (unlessNotRule)
46−import Purelint.Rules.UseAll (useAllRule)
47−import Purelint.Rules.UseAnd (useAndRule)
48−import Purelint.Rules.UseAny (useAnyRule)
49−import Purelint.Rules.UseApplicative (useApplicativeRule)
50−import Purelint.Rules.UseApplyFlipped (useApplyFlippedRule)
51−import Purelint.Rules.UseBimap (useBimapRule)
52−import Purelint.Rules.UseBindFlip (useBindFlipRule)
53−import Purelint.Rules.UseBreak (useBreakRule)
54−import Purelint.Rules.UseComparing (useComparingRule)
55−import Purelint.Rules.UseConst (useConstRule)
56−import Purelint.Rules.UseDollar (useDollarRule)
57−import Purelint.Rules.UseEitherMap (useEitherMapRule)
58−import Purelint.Rules.UseElemIndex (useElemIndexRule)
59−import Purelint.Rules.UseFindMap (useFindMapRule)
60−import Purelint.Rules.UseFold (useFoldRule)
61−import Purelint.Rules.UseFoldBool (useFoldBoolRule)
62−import Purelint.Rules.UseFoldMap (useFoldMapRule)
63−import Purelint.Rules.UseFoldMapId (useFoldMapIdRule)
64−import Purelint.Rules.UseFor (useForRule)
65−import Purelint.Rules.UseFromJust (useFromJustRule)
66−import Purelint.Rules.UseFromMaybe (useFromMaybeRule)
67−import Purelint.Rules.UseFstSnd (useFstSndRule)
68−import Purelint.Rules.UseGuard (useGuardRule)
69−import Purelint.Rules.UseGuardMaybe (useGuardMaybeRule)
70−import Purelint.Rules.UseHead (useHeadRule)
71−import Purelint.Rules.UseIsJust (useIsJustRule)
72−import Purelint.Rules.UseIsNothing (useIsNothingRule)
73−import Purelint.Rules.UseJoin (useJoinRule)
74−import Purelint.Rules.UseLastReverse (useLastReverseRule)
75−import Purelint.Rules.UseMapMaybe (useMapMaybeRule)
76−import Purelint.Rules.UseMaybeMap (useMaybeMapRule)
77−import Purelint.Rules.UseMinMax (useMinMaxRule)
78−import Purelint.Rules.UseMinimumSort (useMinimumSortRule)
79−import Purelint.Rules.UseNotElem (useNotElemRule)
80−import Purelint.Rules.UseNull (useNullRule)
81−import Purelint.Rules.UseCaseOf (useCaseOfRule)
82−import Purelint.Rules.FlattenCase (flattenCaseRule)
83−import Purelint.Rules.UseOn (useOnRule)
84−import Purelint.Rules.UseOr (useOrRule)
85−import Purelint.Rules.UsePatternGuards (usePatternGuardsRule)
86−import Purelint.Rules.UseReplicate (useReplicateRule)
87−import Purelint.Rules.UseSequence (useSequenceRule)
88−import Purelint.Rules.UseSpan (useSpanRule)
89−import Purelint.Rules.UseTraverse (useTraverseRule)
90−import Purelint.Rules.UseTraverseSequence (useTraverseSequenceRule)
91−import Purelint.Rules.UseUncurry (useUncurryRule)
92−import Purelint.Rules.UseUnless (useUnlessRule)
93−import Purelint.Rules.UseUnwrap (useUnwrapRule)
94−import Purelint.Rules.UseVoid (useVoidRule)
95−import Purelint.Rules.UseWhen (useWhenRule)
96−import Purelint.Rules.UseZip (useZipRule)
97−import Purelint.Rules.WhenNot (whenNotRule)
98−import Purelint.Rules.RedundantParens (redundantParensRule)
99−import Purelint.Runner (runRules)
100−import Purelint.Types (LintResult(..), LintWarning(..), Severity(..), SourceCode(..), Suggestion(..))
17+import Options.Applicative (Parser, argument, execParserPure, fullDesc, header, help, helper, info, long, many, metavar, progDesc, str, switch, (<**>))
18+import Options.Applicative.Builder (defaultPrefs)
19+import Options.Applicative.Types (CompletionResult(..), ParserFailure(ParserFailure), ParserResult(..))
20+import Options.Applicative.Help.Types (ParserHelp(ParserHelp), renderHelp)
21+import Purslint.Config (defaultConfig, filterRules, parseConfig)
22+import Purslint.Fix (applyAllFixes)
23+import Purslint.Rule (Rule)
24+import Purslint.Rules.BooleanSimplify (booleanSimplifyRule)
25+import Purslint.Rules.CollapseLambdas (collapseLambdasRule)
26+import Purslint.Rules.ConcatMap (concatMapRule)
27+import Purslint.Rules.EtaReduce (etaReduceRule)
28+import Purslint.Rules.EtaReduceDecl (etaReduceDeclRule)
29+import Purslint.Rules.AlternativeLaw (alternativeLawRule)
30+import Purslint.Rules.EvaluateBool (evaluateBoolRule)
31+import Purslint.Rules.EvaluateConst (evaluateConstRule)
32+import Purslint.Rules.EvaluateEither (evaluateEitherRule)
33+import Purslint.Rules.EvaluateFst (evaluateFstRule)
34+import Purslint.Rules.MapIdentity (mapIdentityRule)
35+import Purslint.Rules.FunctorLaw (functorLawRule)
36+import Purslint.Rules.LetToDo (letToDoRule)
37+import Purslint.Rules.LetToWhere (letToWhereRule)
38+import Purslint.Rules.MapFusion (mapFusionRule)
39+import Purslint.Rules.MonadLaw (monadLawRule)
40+import Purslint.Rules.MonoidIdentity (monoidIdentityRule)
41+import Purslint.Rules.NotEqual (notEqualRule)
42+import Purslint.Rules.NothingBind (nothingBindRule)
43+import Purslint.Rules.RedundantBind (redundantBindRule)
44+import Purslint.Rules.RedundantFlip (redundantFlipRule)
45+import Purslint.Rules.RedundantGuard (redundantGuardRule)
46+import Purslint.Rules.RedundantId (redundantIdRule)
47+import Purslint.Rules.RedundantIf (redundantIfRule)
48+import Purslint.Rules.RedundantNegate (redundantNegateRule)
49+import Purslint.Rules.RedundantNot (redundantNotRule)
50+import Purslint.Rules.RedundantReverse (redundantReverseRule)
51+import Purslint.Rules.UnlessNot (unlessNotRule)
52+import Purslint.Rules.UseAll (useAllRule)
53+import Purslint.Rules.UseAnd (useAndRule)
54+import Purslint.Rules.UseAny (useAnyRule)
55+import Purslint.Rules.UseApplicative (useApplicativeRule)
56+import Purslint.Rules.UseApplyFlipped (useApplyFlippedRule)
57+import Purslint.Rules.UseBimap (useBimapRule)
58+import Purslint.Rules.UseBindFlip (useBindFlipRule)
59+import Purslint.Rules.UseBreak (useBreakRule)
60+import Purslint.Rules.UseComparing (useComparingRule)
61+import Purslint.Rules.UseConst (useConstRule)
62+import Purslint.Rules.UseDollar (useDollarRule)
63+import Purslint.Rules.UseEitherMap (useEitherMapRule)
64+import Purslint.Rules.UseElemIndex (useElemIndexRule)
65+import Purslint.Rules.UseFindMap (useFindMapRule)
66+import Purslint.Rules.UseFold (useFoldRule)
67+import Purslint.Rules.UseFoldBool (useFoldBoolRule)
68+import Purslint.Rules.UseFoldMap (useFoldMapRule)
69+import Purslint.Rules.UseFoldMapId (useFoldMapIdRule)
70+import Purslint.Rules.UseFor (useForRule)
71+import Purslint.Rules.UseFromJust (useFromJustRule)
72+import Purslint.Rules.UseFromMaybe (useFromMaybeRule)
73+import Purslint.Rules.UseFstSnd (useFstSndRule)
74+import Purslint.Rules.UseGuard (useGuardRule)
75+import Purslint.Rules.UseGuardMaybe (useGuardMaybeRule)
76+import Purslint.Rules.UseHead (useHeadRule)
77+import Purslint.Rules.UseIsJust (useIsJustRule)
78+import Purslint.Rules.UseIsNothing (useIsNothingRule)
79+import Purslint.Rules.UseJoin (useJoinRule)
80+import Purslint.Rules.UseLastReverse (useLastReverseRule)
81+import Purslint.Rules.UseMapMaybe (useMapMaybeRule)
82+import Purslint.Rules.UseMaybeMap (useMaybeMapRule)
83+import Purslint.Rules.UseMinMax (useMinMaxRule)
84+import Purslint.Rules.UseMinimumSort (useMinimumSortRule)
85+import Purslint.Rules.UseNotElem (useNotElemRule)
86+import Purslint.Rules.UseNull (useNullRule)
87+import Purslint.Rules.UseCaseOf (useCaseOfRule)
88+import Purslint.Rules.FlattenCase (flattenCaseRule)
89+import Purslint.Rules.UseOn (useOnRule)
90+import Purslint.Rules.UseOr (useOrRule)
91+import Purslint.Rules.UsePatternGuards (usePatternGuardsRule)
92+import Purslint.Rules.UseReplicate (useReplicateRule)
93+import Purslint.Rules.UseSequence (useSequenceRule)
94+import Purslint.Rules.UseSpan (useSpanRule)
95+import Purslint.Rules.UseTraverse (useTraverseRule)
96+import Purslint.Rules.UseTraverseSequence (useTraverseSequenceRule)
97+import Purslint.Rules.UseUncurry (useUncurryRule)
98+import Purslint.Rules.UseUnless (useUnlessRule)
99+import Purslint.Rules.UseUnwrap (useUnwrapRule)
100+import Purslint.Rules.UseVoid (useVoidRule)
101+import Purslint.Rules.UseWhen (useWhenRule)
102+import Purslint.Rules.UseZip (useZipRule)
103+import Purslint.Rules.WhenNot (whenNotRule)
104+import Purslint.Rules.RedundantParens (redundantParensRule)
105+import Purslint.Runner (runRules)
106+import Purslint.Types (LintResult(..), LintWarning(..), Severity(..), SourceCode(..), Suggestion(..))
107+
108+type CliOptions =
109+ { fix :: Boolean
110+ , listRules :: Boolean
111+ , initConfig :: Boolean
112+ , paths :: Array String
113+ }
101114
102115-- | All available rules (81 total)
103116allRules :: Array Rule
@@ -185,12 +198,12 @@ allRules =
185198 , whenNotRule
186199 ]
187200
188−-- | Load config from .purelintrc file
201+-- | Load config from .purslintrc file
189202loadConfig :: Effect _
190203loadConfig = do
191− hasConfig <- FS.exists ".purelintrc"
204+ hasConfig <- FS.exists ".purslintrc"
192205 if hasConfig then do
193− content <- FS.readTextFile ".purelintrc"
206+ content <- FS.readTextFile ".purslintrc"
194207 pure $ parseConfig content
195208 else
196209 pure defaultConfig
@@ -294,24 +307,24 @@ fixPath rules path = do
294307-- | Show usage
295308showUsage :: Effect Unit
296309showUsage = do
297− log "purelint - a linter for PureScript (70 rules)"
310+ log "purslint - a linter for PureScript (81 rules)"
298311 log ""
299− log "Usage: purelint [OPTIONS] [FILES/DIRECTORIES...]"
312+ log "Usage: purslint [OPTIONS] [FILES/DIRECTORIES...]"
300313 log ""
301314 log "Options:"
302315 log " --help Show this help"
303316 log " --fix Auto-fix issues (where possible)"
304− log " --init Create a sample .purelintrc"
317+ log " --init Create a sample .purslintrc"
305318 log " --list List all available rules"
306319 log ""
307320 log "Configuration:"
308− log " Create .purelintrc in your project root to disable rules."
321+ log " Create .purslintrc in your project root to disable rules."
309322 log " Add one rule name per line (lines starting with # are comments)."
310323 log ""
311324 log "Examples:"
312− log " purelint src/ Lint all .purs files in src/"
313− log " purelint --fix src/ Auto-fix issues in src/"
314− log " purelint src/Main.purs Lint a single file"
325+ log " purslint src/ Lint all .purs files in src/"
326+ log " purslint --fix src/ Auto-fix issues in src/"
327+ log " purslint src/Main.purs Lint a single file"
315328
316329-- | List all available rules
317330listRules :: Effect Unit
@@ -324,51 +337,83 @@ listRules = do
324337-- | Create sample config file
325338initConfig :: Effect Unit
326339initConfig = do
327− hasConfig <- FS.exists ".purelintrc"
340+ hasConfig <- FS.exists ".purslintrc"
328341 if hasConfig then
329− log ".purelintrc already exists"
342+ log ".purslintrc already exists"
330343 else do
331− let sampleConfig = """# purelint configuration
344+ let sampleConfig = """# purslint configuration
332345# Add rule names (one per line) to disable them
333346# Example:
334347# EtaReduce
335348# LetToWhere
336349"""
337− FS.writeTextFile ".purelintrc" sampleConfig
338− log "Created .purelintrc"
350+ FS.writeTextFile ".purslintrc" sampleConfig
351+ log "Created .purslintrc"
352+
353+parseFlags :: Array String -> Effect (Maybe CliOptions)
354+parseFlags args = case parseFlags' (Array.drop 2 args) of
355+ Success cli -> pure (Just cli)
356+ Failure (ParserFailure pf) -> case pf programName of
357+ ph@(ParserHelp _) /\ exitCode /\ i /\ _unit -> do
358+ log $ renderHelp i ph
359+ Process.exit (fromEnum exitCode)
360+ pure Nothing
361+ CompletionInvoked (CompletionResult { execCompletion }) -> do
362+ executedCompletion <- execCompletion programName
363+ log executedCompletion
364+ Process.exit 0
365+ pure Nothing
366+ where
367+ programName = "purslint"
368+
369+parseFlags' :: Array String -> ParserResult CliOptions
370+parseFlags' = execParserPure defaultPrefs opts
371+ where
372+ opts =
373+ info (flagsParser <**> helper) $
374+ fullDesc
375+ <> progDesc "Lint PureScript source files"
376+ <> header "purslint - a linter for PureScript"
377+
378+flagsParser :: Parser CliOptions
379+flagsParser = ado
380+ fix <- switch $ long "fix" <> help "Auto-fix issues (where possible)"
381+ listRules <- switch $ long "list" <> help "List all available rules"
382+ initConfig <- switch $ long "init" <> help "Create a sample .purslintrc"
383+ pathsList <- many $ argument str $ metavar "PATH..."
384+ let paths = Array.fromFoldable pathsList
385+ in { fix, listRules, initConfig, paths }
339386
340387main :: Effect Unit
341388main = do
342389 args <- Process.argv
343− -- args[0] is node, args[1] is the script, args[2..] are the actual arguments
344− let userArgs = Array.drop 1 args
345− case userArgs of
346− [] -> showUsage
347− ["--help"] -> showUsage
348− ["-h"] -> showUsage
349− ["--list"] -> listRules
350− ["--init"] -> initConfig
351− _ -> do
352− -- Check for --fix flag
353− let hasFix = Array.elem "--fix" userArgs
354− let paths = Array.filter (\a -> a /= "--fix") userArgs
355−
356− -- Load config and filter rules
357− config <- loadConfig
358− let enabledRules = filterRules allRules config
359−
360− if hasFix then do
361− -- Fix mode
362− _ <- Array.foldM (\acc path -> do
363− count <- fixPath enabledRules path
364− pure (acc + count)) 0 paths
365− pure unit
390+ maybeCli <- parseFlags args
391+ case maybeCli of
392+ Nothing -> pure unit
393+ Just cli ->
394+ if cli.listRules then
395+ listRules
396+ else if cli.initConfig then
397+ initConfig
398+ else if Array.null cli.paths then
399+ showUsage
366400 else do
367− -- Lint mode
368− totalWarnings <- Array.foldM (\acc path -> do
369− count <- lintPathWithConfig enabledRules path
370− pure (acc + count)) 0 paths
371− when (totalWarnings > 0) do
372− log ""
373− log $ "Total: " <> show totalWarnings <> " warning" <> if totalWarnings == 1 then "" else "s"
374− Process.exit 1
401+ -- Load config and filter rules
402+ config <- loadConfig
403+ let enabledRules = filterRules allRules config
404+
405+ if cli.fix then do
406+ -- Fix mode
407+ _ <- Array.foldM (\acc path -> do
408+ count <- fixPath enabledRules path
409+ pure (acc + count)) 0 cli.paths
410+ pure unit
411+ else do
412+ -- Lint mode
413+ totalWarnings <- Array.foldM (\acc path -> do
414+ count <- lintPathWithConfig enabledRules path
415+ pure (acc + count)) 0 cli.paths
416+ when (totalWarnings > 0) do
417+ log ""
418+ log $ "Total: " <> show totalWarnings <> " warning" <> if totalWarnings == 1 then "" else "s"
419+ Process.exit 1