git.iko.soy/rowtype-yoga/purslint
on main help sign in
144 files changed+3565−1737
since splitsnapshots
unifiedsplit
.github/ISSUE_TEMPLATE/bug_report.yml+53 .github/workflows/publish-npm.yml+27 .github/workflows/publish-openvsx.yml+29 .github/workflows/publish-vscode.yml+26 CONTRIBUTING.md+6 LICENSE+21 README.md+100−67 bin/purelint-cli/src/Main.purs−366 bin/purelint-lsp.js−3 bin/purelint.js−3 bin/purelint-cli/spago.yaml → bin/purslint-cli/spago.yaml+3−2 bin/purslint-cli/src/Main.purs+419 bin/purelint-cli/src/Node/CLI.js → bin/purslint-cli/src/Node/CLI.js bin/purelint-cli/src/Node/CLI.purs → bin/purslint-cli/src/Node/CLI.purs bin/purelint-cli/src/Node/FS/Simple.js → bin/purslint-cli/src/Node/FS/Simple.js bin/purelint-cli/src/Node/FS/Simple.purs → bin/purslint-cli/src/Node/FS/Simple.purs bin/purslint-lsp.js+2 bin/purelint-lsp/spago.yaml → bin/purslint-lsp/spago.yaml+2−2 bin/purelint-lsp/src/LSP.js → bin/purslint-lsp/src/LSP.js+1−1 bin/purelint-lsp/src/LSP.purs → bin/purslint-lsp/src/LSP.purs+108−100 bin/purelint-lsp/src/LSP/Stdio.js → bin/purslint-lsp/src/LSP/Stdio.js+1−1 bin/purelint-lsp/src/LSP/Stdio.purs → bin/purslint-lsp/src/LSP/Stdio.purs bin/purelint-lsp/src/Main.purs → bin/purslint-lsp/src/Main.purs+1−1 bin/purslint.js+2 demo.gif docs/IMPORT_CHECKING_PLAN.md+8−8 lib/purelint/src/Purelint/Rules/RedundantReverse.purs−63 lib/purelint/src/Purelint/Rules/UseBreak.purs−95 lib/purelint/src/Purelint/Rules/UseCaseOf.purs−169 lib/purelint/src/Purelint/Rules/UseConst.purs−67 lib/purelint/src/Purelint/Rules/UseEitherMap.purs−112 lib/purelint/src/Purelint/Rules/UseFoldMap.purs−108 lib/purelint/spago.yaml → lib/purslint/spago.yaml+1−1 lib/purelint/src/Purelint/Config.purs → lib/purslint/src/Purslint/Config.purs+2−2 lib/purelint/src/Purelint/Fix.purs → lib/purslint/src/Purslint/Fix.purs+47−11 lib/purslint/src/Purslint/ImportFix.purs+17 lib/purelint/src/Purelint/Imports.purs → lib/purslint/src/Purslint/Imports.purs+1−1 lib/purelint/src/Purelint/Print.purs → lib/purslint/src/Purslint/Print.purs+8−2 lib/purelint/src/Purelint/Rule.purs → lib/purslint/src/Purslint/Rule.purs+3−3 lib/purelint/src/Purelint/Rules/AlternativeLaw.purs → lib/purslint/src/Purslint/Rules/AlternativeLaw.purs+8−5 lib/purelint/src/Purelint/Rules/BooleanSimplify.purs → lib/purslint/src/Purslint/Rules/BooleanSimplify.purs+7−5 lib/purelint/src/Purelint/Rules/CollapseLambdas.purs → lib/purslint/src/Purslint/Rules/CollapseLambdas.purs+6−4 lib/purelint/src/Purelint/Rules/ConcatMap.purs → lib/purslint/src/Purslint/Rules/ConcatMap.purs+7−5 lib/purelint/src/Purelint/Rules/EtaReduce.purs → lib/purslint/src/Purslint/Rules/EtaReduce.purs+6−4 lib/purelint/src/Purelint/Rules/EtaReduceDecl.purs → lib/purslint/src/Purslint/Rules/EtaReduceDecl.purs+6−4 lib/purelint/src/Purelint/Rules/EvaluateBool.purs → lib/purslint/src/Purslint/Rules/EvaluateBool.purs+7−5 lib/purelint/src/Purelint/Rules/EvaluateConst.purs → lib/purslint/src/Purslint/Rules/EvaluateConst.purs+7−5 lib/purelint/src/Purelint/Rules/EvaluateEither.purs → lib/purslint/src/Purslint/Rules/EvaluateEither.purs+8−5 lib/purelint/src/Purelint/Rules/EvaluateFst.purs → lib/purslint/src/Purslint/Rules/EvaluateFst.purs+8−5 lib/purelint/src/Purelint/Rules/FlattenCase.purs → lib/purslint/src/Purslint/Rules/FlattenCase.purs+6−4 lib/purelint/src/Purelint/Rules/FunctorLaw.purs → lib/purslint/src/Purslint/Rules/FunctorLaw.purs+7−5 lib/purelint/src/Purelint/Rules/LetToDo.purs → lib/purslint/src/Purslint/Rules/LetToDo.purs+14−5 lib/purelint/src/Purelint/Rules/LetToWhere.purs → lib/purslint/src/Purslint/Rules/LetToWhere.purs+12−5 lib/purelint/src/Purelint/Rules/MapFusion.purs → lib/purslint/src/Purslint/Rules/MapFusion.purs+8−5 lib/purelint/src/Purelint/Rules/MapIdentity.purs → lib/purslint/src/Purslint/Rules/MapIdentity.purs+9−5 lib/purelint/src/Purelint/Rules/MonadLaw.purs → lib/purslint/src/Purslint/Rules/MonadLaw.purs+8−5 lib/purelint/src/Purelint/Rules/MonoidIdentity.purs → lib/purslint/src/Purslint/Rules/MonoidIdentity.purs+8−5 lib/purelint/src/Purelint/Rules/NotEqual.purs → lib/purslint/src/Purslint/Rules/NotEqual.purs+7−5 lib/purelint/src/Purelint/Rules/NothingBind.purs → lib/purslint/src/Purslint/Rules/NothingBind.purs+7−4 lib/purelint/src/Purelint/Rules/RedundantBind.purs → lib/purslint/src/Purslint/Rules/RedundantBind.purs+8−5 lib/purelint/src/Purelint/Rules/RedundantFlip.purs → lib/purslint/src/Purslint/Rules/RedundantFlip.purs+7−5 lib/purelint/src/Purelint/Rules/RedundantGuard.purs → lib/purslint/src/Purslint/Rules/RedundantGuard.purs+6−4 lib/purelint/src/Purelint/Rules/RedundantId.purs → lib/purslint/src/Purslint/Rules/RedundantId.purs+11−5 lib/purelint/src/Purelint/Rules/RedundantIf.purs → lib/purslint/src/Purslint/Rules/RedundantIf.purs+8−5 lib/purelint/src/Purelint/Rules/RedundantNegate.purs → lib/purslint/src/Purslint/Rules/RedundantNegate.purs+7−5 lib/purelint/src/Purelint/Rules/RedundantNot.purs → lib/purslint/src/Purslint/Rules/RedundantNot.purs+7−5 lib/purslint/src/Purslint/Rules/RedundantParens.purs+423 lib/purslint/src/Purslint/Rules/RedundantReverse.purs+60 lib/purelint/src/Purelint/Rules/UnlessNot.purs → lib/purslint/src/Purslint/Rules/UnlessNot.purs+8−5 lib/purelint/src/Purelint/Rules/UseAll.purs → lib/purslint/src/Purslint/Rules/UseAll.purs+7−5 lib/purelint/src/Purelint/Rules/UseAnd.purs → lib/purslint/src/Purslint/Rules/UseAnd.purs+8−5 lib/purelint/src/Purelint/Rules/UseAny.purs → lib/purslint/src/Purslint/Rules/UseAny.purs+10−8 lib/purelint/src/Purelint/Rules/UseApplicative.purs → lib/purslint/src/Purslint/Rules/UseApplicative.purs+7−5 lib/purelint/src/Purelint/Rules/UseApplyFlipped.purs → lib/purslint/src/Purslint/Rules/UseApplyFlipped.purs+6−4 lib/purelint/src/Purelint/Rules/UseBimap.purs → lib/purslint/src/Purslint/Rules/UseBimap.purs+14−5 lib/purelint/src/Purelint/Rules/UseBindFlip.purs → lib/purslint/src/Purslint/Rules/UseBindFlip.purs+13−11 lib/purslint/src/Purslint/Rules/UseBreak.purs+99 lib/purslint/src/Purslint/Rules/UseCaseOf.purs+239 lib/purelint/src/Purelint/Rules/UseComparing.purs → lib/purslint/src/Purslint/Rules/UseComparing.purs+31−32 lib/purslint/src/Purslint/Rules/UseConst.purs+93 lib/purslint/src/Purslint/Rules/UseDollar.purs+97 lib/purslint/src/Purslint/Rules/UseEitherMap.purs+117 lib/purelint/src/Purelint/Rules/UseElemIndex.purs → lib/purslint/src/Purslint/Rules/UseElemIndex.purs+8−5 lib/purelint/src/Purelint/Rules/UseFindMap.purs → lib/purslint/src/Purslint/Rules/UseFindMap.purs+13−5 lib/purelint/src/Purelint/Rules/UseFold.purs → lib/purslint/src/Purslint/Rules/UseFold.purs+6−4 lib/purelint/src/Purelint/Rules/UseFoldBool.purs → lib/purslint/src/Purslint/Rules/UseFoldBool.purs+6−4 lib/purslint/src/Purslint/Rules/UseFoldMap.purs+167 lib/purelint/src/Purelint/Rules/UseFoldMapId.purs → lib/purslint/src/Purslint/Rules/UseFoldMapId.purs+8−5 lib/purelint/src/Purelint/Rules/UseFor.purs → lib/purslint/src/Purslint/Rules/UseFor.purs+8−5 lib/purelint/src/Purelint/Rules/UseFromJust.purs → lib/purslint/src/Purslint/Rules/UseFromJust.purs+9−5 lib/purelint/src/Purelint/Rules/UseFromMaybe.purs → lib/purslint/src/Purslint/Rules/UseFromMaybe.purs+7−5 lib/purelint/src/Purelint/Rules/UseFstSnd.purs → lib/purslint/src/Purslint/Rules/UseFstSnd.purs+6−3 lib/purelint/src/Purelint/Rules/UseGuard.purs → lib/purslint/src/Purslint/Rules/UseGuard.purs+8−5 lib/purelint/src/Purelint/Rules/UseGuardMaybe.purs → lib/purslint/src/Purslint/Rules/UseGuardMaybe.purs+7−5 lib/purelint/src/Purelint/Rules/UseHead.purs → lib/purslint/src/Purslint/Rules/UseHead.purs+6−4 lib/purelint/src/Purelint/Rules/UseIsJust.purs → lib/purslint/src/Purslint/Rules/UseIsJust.purs+6−4 lib/purelint/src/Purelint/Rules/UseIsNothing.purs → lib/purslint/src/Purslint/Rules/UseIsNothing.purs+6−4 lib/purelint/src/Purelint/Rules/UseJoin.purs → lib/purslint/src/Purslint/Rules/UseJoin.purs+8−5 lib/purelint/src/Purelint/Rules/UseLastReverse.purs → lib/purslint/src/Purslint/Rules/UseLastReverse.purs+7−5 lib/purelint/src/Purelint/Rules/UseMapMaybe.purs → lib/purslint/src/Purslint/Rules/UseMapMaybe.purs+13−5 lib/purelint/src/Purelint/Rules/UseMaybeMap.purs → lib/purslint/src/Purslint/Rules/UseMaybeMap.purs+5−5 lib/purelint/src/Purelint/Rules/UseMinMax.purs → lib/purslint/src/Purslint/Rules/UseMinMax.purs+7−5 lib/purelint/src/Purelint/Rules/UseMinimumSort.purs → lib/purslint/src/Purslint/Rules/UseMinimumSort.purs+7−5 lib/purelint/src/Purelint/Rules/UseNotElem.purs → lib/purslint/src/Purslint/Rules/UseNotElem.purs+7−5 lib/purelint/src/Purelint/Rules/UseNull.purs → lib/purslint/src/Purslint/Rules/UseNull.purs+7−5 lib/purelint/src/Purelint/Rules/UseOn.purs → lib/purslint/src/Purslint/Rules/UseOn.purs+7−5 lib/purelint/src/Purelint/Rules/UseOr.purs → lib/purslint/src/Purslint/Rules/UseOr.purs+8−5 lib/purelint/src/Purelint/Rules/UsePatternGuards.purs → lib/purslint/src/Purslint/Rules/UsePatternGuards.purs+6−4 lib/purelint/src/Purelint/Rules/UseReplicate.purs → lib/purslint/src/Purslint/Rules/UseReplicate.purs+13−5 lib/purelint/src/Purelint/Rules/UseSequence.purs → lib/purslint/src/Purslint/Rules/UseSequence.purs+12−4 lib/purelint/src/Purelint/Rules/UseSpan.purs → lib/purslint/src/Purslint/Rules/UseSpan.purs+8−5 lib/purelint/src/Purelint/Rules/UseTraverse.purs → lib/purslint/src/Purslint/Rules/UseTraverse.purs+17−5 lib/purelint/src/Purelint/Rules/UseTraverseSequence.purs → lib/purslint/src/Purslint/Rules/UseTraverseSequence.purs+13−5 lib/purelint/src/Purelint/Rules/UseUncurry.purs → lib/purslint/src/Purslint/Rules/UseUncurry.purs+7−5 lib/purelint/src/Purelint/Rules/UseUnless.purs → lib/purslint/src/Purslint/Rules/UseUnless.purs+7−5 lib/purelint/src/Purelint/Rules/UseUnwrap.purs → lib/purslint/src/Purslint/Rules/UseUnwrap.purs+7−4 lib/purelint/src/Purelint/Rules/UseVoid.purs → lib/purslint/src/Purslint/Rules/UseVoid.purs+7−5 lib/purelint/src/Purelint/Rules/UseWhen.purs → lib/purslint/src/Purslint/Rules/UseWhen.purs+7−5 lib/purelint/src/Purelint/Rules/UseZip.purs → lib/purslint/src/Purslint/Rules/UseZip.purs+9−5 lib/purelint/src/Purelint/Rules/WhenNot.purs → lib/purslint/src/Purslint/Rules/WhenNot.purs+8−5 lib/purelint/src/Purelint/Runner.purs → lib/purslint/src/Purslint/Runner.purs+4−4 lib/purelint/src/Purelint/Tidy.js → lib/purslint/src/Purslint/Tidy.js lib/purelint/src/Purelint/Tidy.purs → lib/purslint/src/Purslint/Tidy.purs+1−1 lib/purelint/src/Purelint/Types.purs → lib/purslint/src/Purslint/Types.purs+12−1 package.json+23−10 purelint-lsp−2 purslint-lsp+2 spago.lock+94−13 spago.yaml+3−2 test/RedundantParens.purs+134 test/Test/Main.purs+454−93 vscode-extension/out/extension.js+4−4 vscode-extension/out/test/suite/extension.test.js+4−4 vscode-extension/out/test/suite/extension.test.js.map+1−1 vscode-extension/package.json+10−9 vscode-extension/purelint-0.1.0.vsix → vscode-extension/purslint-0.1.1.vsix vscode-extension/purslint.png vscode-extension/src/extension.ts+5−5 vscode-extension/src/test/suite/extension.test.ts+4−4 zed-extension/Cargo.lock+3−3 zed-extension/Cargo.toml+1−1 zed-extension/extension.toml+3−3 zed-extension/extension.wasm zed-extension/src/lib.rs+5−5
.github/ISSUE_TEMPLATE/bug_report.ymlnew file+53
@@ -0,0 +1,53 @@
1+name: Bug report
2+description: Report incorrect lint results with input and output.
3+title: "[Bug]: "
4+labels:
5+ - bug
6+body:
7+ - type: markdown
8+ attributes:
9+ value: |
10+ Thanks for taking the time to report a bug.
11+ If possible, include a golden file with input, actual output, and expected output.
12+ - type: textarea
13+ id: input
14+ attributes:
15+ label: Input
16+ description: |
17+ Paste the input here (or attach a golden file and mention the filename).
18+ Include any relevant configuration.
19+ placeholder: |
20+ ```purs
21+ -- Your input code here
22+ ```
23+ validations:
24+ required: true
25+ - type: textarea
26+ id: actual
27+ attributes:
28+ label: Actual output
29+ description: Paste the current output from purslint.
30+ placeholder: |
31+ ```
32+ -- Warnings, fixes, or logs
33+ ```
34+ validations:
35+ required: true
36+ - type: textarea
37+ id: expected
38+ attributes:
39+ label: Expected output
40+ description: Describe the expected output or behaviour.
41+ placeholder: |
42+ ```
43+ -- Expected warnings, fixes, or logs
44+ ```
45+ validations:
46+ required: true
47+ - type: textarea
48+ id: extra
49+ attributes:
50+ label: Additional context
51+ description: Links, screenshots, or anything else helpful.
52+ validations:
53+ required: false
.github/workflows/publish-npm.ymlnew file+27
@@ -0,0 +1,27 @@
1+name: Publish npm package
2+
3+on:
4+ release:
5+ types: [published]
6+ workflow_dispatch:
7+
8+permissions:
9+ contents: read
10+
11+jobs:
12+ publish:
13+ runs-on: ubuntu-latest
14+ steps:
15+ - uses: actions/checkout@v4
16+
17+ - uses: actions/setup-node@v4
18+ with:
19+ node-version: "20"
20+ registry-url: "https://registry.npmjs.org"
21+
22+ - run: npm install -g purescript@0.15.15
23+ - run: npm install --no-audit --no-fund
24+ - run: npm run dist
25+ - run: npm publish --access public
26+ env:
27+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
.github/workflows/publish-openvsx.ymlnew file+29
@@ -0,0 +1,29 @@
1+name: Publish Open VSX
2+
3+on:
4+ workflow_dispatch:
5+ release:
6+ types: [published]
7+
8+permissions:
9+ contents: read
10+
11+jobs:
12+ publish:
13+ runs-on: ubuntu-latest
14+ steps:
15+ - uses: actions/checkout@v4
16+
17+ - uses: actions/setup-node@v4
18+ with:
19+ node-version: "20"
20+
21+ - run: npm install -g purescript@0.15.15
22+ - run: npm install
23+ working-directory: vscode-extension
24+ - run: npx vsce package
25+ working-directory: vscode-extension
26+ - run: npx ovsx publish -p "${OPENVSX_TOKEN}"
27+ working-directory: vscode-extension
28+ env:
29+ OPENVSX_TOKEN: ${{ secrets.OPENVSX_TOKEN }}
.github/workflows/publish-vscode.ymlnew file+26
@@ -0,0 +1,26 @@
1+name: Publish VS Code Marketplace
2+
3+on:
4+ workflow_dispatch:
5+ release:
6+ types: [published]
7+
8+permissions:
9+ contents: read
10+
11+jobs:
12+ publish:
13+ runs-on: ubuntu-latest
14+ steps:
15+ - uses: actions/checkout@v4
16+
17+ - uses: actions/setup-node@v4
18+ with:
19+ node-version: "20"
20+
21+ - run: npm install
22+ working-directory: vscode-extension
23+ - run: npx vsce publish --pat "${VSCE_TOKEN}"
24+ working-directory: vscode-extension
25+ env:
26+ VSCE_TOKEN: ${{ secrets.VSCE_TOKEN }}
CONTRIBUTING.mdnew file+6
@@ -0,0 +1,6 @@
1+# Contributing
2+
3+## Publishing (Marketplace)
4+
5+Publisher management and VSIX uploads live here:
6+https://marketplace.visualstudio.com/manage/publishers/RowtypeYoga
LICENSEnew file+21
@@ -0,0 +1,21 @@
1+MIT License
2+
3+Copyright (c) 2026 Rowtype Yoga
4+
5+Permission is hereby granted, free of charge, to any person obtaining a copy
6+of this software and associated documentation files (the "Software"), to deal
7+in the Software without restriction, including without limitation the rights
8+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+copies of the Software, and to permit persons to whom the Software is
10+furnished to do so, subject to the following conditions:
11+
12+The above copyright notice and this permission notice shall be included in all
13+copies or substantial portions of the Software.
14+
15+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+SOFTWARE.
README.md+100−67
@@ -1,92 +1,96 @@
1−# PureLint
1+# Purslint
22
3−A PureScript linter inspired by [hlint](https://github.com/ndmitchell/hlint), providing suggestions for cleaner, more idiomatic PureScript code.
3+<p align="center">
4+ <img src="vscode-extension/purslint.png" alt="Purslint logo" width="180" />
5+</p>
46
5−## Features
7+<p align="center">
8+ <a href="https://github.com/user-attachments/assets/6d855014-18f5-4729-be28-e91ec2d19c8f">
9+ <img src="demo.gif" alt="Purslint demo" width="480" />
10+ </a>
11+</p>
612
7−PureLint includes 10 lint rules:
13+A PureScript linter.
814
9−| Rule | Description | Example |
10−|------|-------------|---------|
11−| `FmapId` | `map identity x` → `x` | Redundant identity mapping |
12−| `ConcatMap` | `join <<< map f` → `f =<< _` | Use bind instead of join+map |
13−| `MapFusion` | `map f <<< map g` → `map (f <<< g)` | Fuse consecutive maps |
14−| `UseTraverse` | `sequence <<< map f` → `traverse f` | Use traverse |
15−| `NotEqual` | `not (x == y)` → `x /= y` | Use not-equal operator |
16−| `UseGuard` | `if c then Just x else Nothing` → `guard` | Use guard from Control.MonadZero |
17−| `EtaReduce` | `\x -> f x` → `f` | Eta reduce lambdas |
18−| `EtaReduceDecl` | `f x = g x` → `f = g` | Eta reduce declarations |
19−| `RedundantBind` | `x >>= pure` → `x` | Remove redundant bind |
20−| `LetToWhere` | `let x = y in z` → `z where x = y` | Prefer where clauses |
15+Purslint is very new and may have bugs, so please use an editor with undo functionality. :D
2116
22−## Installation
17+## Install
2318
24−### Prerequisites
19+### VS Code (Marketplace)
2520
26−- [PureScript](https://www.purescript.org/) compiler (`purs`)
27−- [Spago](https://github.com/purescript/spago) package manager
28−- [Go](https://golang.org/) 1.19+
29−- [psgo](https://github.com/puregocore/psgo) - PureScript to Go compiler
21+Install from the Visual Studio Marketplace:
22+[Purslint](https://marketplace.visualstudio.com/items?itemName=RowtypeYoga.purslint).
3023
31−### Building
24+### CLI (npm)
3225
3326```bash
34−# Install dependencies
35−spago install
36−
37−# Build CLI
38−purs compile --codegen corefn 'lib/**/*.purs' 'bin/purelint-cli/**/*.purs' '.spago/**/*.purs'
39−psgo output/*/corefn.json
40−cp output/Main/Main.go main_cli.go
41−go build -o purelint main_cli.go
42−
43−# Build LSP
44−purs compile --codegen corefn 'lib/**/*.purs' 'bin/purelint-lsp/**/*.purs' '.spago/**/*.purs'
45−psgo output/*/corefn.json
46−cp output/Main/Main.go main_lsp.go
47−go build -o purelint-lsp main_lsp.go
48−
49−# Build and run tests
50−purs compile --codegen corefn 'lib/**/*.purs' 'test/**/*.purs' '.spago/**/*.purs'
51−psgo output/*/corefn.json
52−cp output/Test.Main/Test_Main.go test_main.go
53−go build -o test-runner test_main.go
54−./test-runner
27+npm install -g purslint
28+
29+# CLI
30+purslint --help
31+```
32+
33+### Neovim (nvim-lspconfig)
34+
35+After building `purslint-lsp` (see build steps below), you can configure Neovim like this:
36+
37+```lua
38+require("lspconfig").purslint = {
39+ default_config = {
40+ cmd = { "/path/to/purslint-lsp" },
41+ filetypes = { "purescript" },
42+ root_dir = require("lspconfig.util").root_pattern(".git", "spago.yaml", "package.json"),
43+ },
44+}
45+
46+require("lspconfig").purslint.setup({})
47+```
48+
49+If you prefer to run the bundled JS directly:
50+
51+```lua
52+cmd = { "node", "/path/to/dist/purslint-lsp.js" }
5553```
5654
55+### Zed Editor
56+
57+A Zed extension is available in the `zed-extension/` directory. To install:
58+
59+1. Open Zed
60+2. Go to Extensions (Cmd+Shift+X)
61+3. Click "Install Dev Extension"
62+4. Select the `zed-extension/` directory
63+
64+The extension will automatically use the `purslint-lsp` binary.
65+
5766## Usage
5867
5968### CLI
6069
6170```bash
6271# Lint a single file
63−./purelint src/Main.purs
72+./purslint src/Main.purs
6473
6574# Lint multiple files
66−./purelint src/*.purs
75+./purslint src/*.purs
6776
6877# Lint with config file (optional)
69−./purelint --config purelint.json src/*.purs
78+./purslint --config purslint.json src/*.purs
7079```
7180
7281### LSP (Language Server Protocol)
7382
74−The LSP binary (`purelint-lsp`) provides IDE integration. It communicates via stdio using the standard LSP protocol.
75−
76−#### Zed Editor
83+The LSP binary (`purslint-lsp`) provides IDE integration. It communicates via stdio using the standard LSP protocol.
7784
78−A Zed extension is available in the `zed-extension/` directory. To install:
85+## Features
7986
80−1. Open Zed
81−2. Go to Extensions (Cmd+Shift+X)
82−3. Click "Install Dev Extension"
83−4. Select the `zed-extension/` directory
87+Purslint currently ships with **81 lint rules**, covering common PureScript refactors and simplifications.
8488
85−The extension will automatically use the `purelint-lsp` binary.
89+For a complete list, see `lib/purslint/src/Purslint/Rules/`.
8690
8791## Configuration
8892
89−Create a `purelint.json` file in your project root:
93+Create a `purslint.json` file in your project root:
9094
9195```json
9296{
@@ -96,36 +100,63 @@ Create a `purelint.json` file in your project root:
96100
97101## Architecture
98102
99−PureLint is written in PureScript and compiled to Go using `psgo` (purescript-native). This allows:
103+Purslint is written in PureScript and compiled to JavaScript via
104+`purs-backend-es`, then bundled for Node.js. This allows:
100105
101106- Full access to the PureScript CST parser (`purescript-language-cst-parser`)
102−- Native binary performance
103−- Easy distribution without runtime dependencies
107+- Straightforward distribution via npm
108+- A single runtime across CLI and LSP
104109
105110### Project Structure
106111
107112```
108−purelint/
109−├── lib/purelint/src/ # Core library
110−│ └── Purelint/
113+purslint/
114+├── lib/purslint/src/ # Core library
115+│ └── Purslint/
111116│ ├── Rules/ # Lint rules
112117│ ├── Runner.purs # Rule runner
113118│ ├── Types.purs # Core types
114119│ └── Imports.purs # Import checking
115120├── bin/
116−│ ├── purelint-cli/ # CLI application
117−│ └── purelint-lsp/ # LSP server
121+│ ├── purslint-cli/ # CLI application
122+│ └── purslint-lsp/ # LSP server
118123├── test/ # Test suite
119124└── zed-extension/ # Zed editor extension
120125```
121126
122127## Development
123128
129+### Prerequisites
130+
131+- [PureScript](https://www.purescript.org/) compiler (`purs`)
132+- [Spago](https://github.com/purescript/spago) package manager
133+- [Node.js](https://nodejs.org/) 18+
134+
135+### Building
136+
137+```bash
138+# Install dependencies
139+npm install
140+
141+# Build and bundle CLI + LSP
142+npm run dist
143+
144+# Run tests
145+npm test
146+```
147+
148+### Publishing to npm
149+
150+Publishing is handled by GitHub Actions. Add an `NPM_TOKEN` secret with an
151+automation or granular token that has publish rights for `purslint`, then
152+create a GitHub Release to trigger the workflow. You can also run it manually
153+via the workflow dispatch.
154+
124155### Adding a New Rule
125156
126−1. Create a new file in `lib/purelint/src/Purelint/Rules/`
157+1. Create a new file in `lib/purslint/src/Purslint/Rules/`
1271582. Implement the rule using the CST traversal API
128−3. Export the rule from `Purelint.Rules`
159+3. Export the rule from `Purslint.Rules`
1291604. Add tests in `test/Test/Main.purs`
1301615. Rebuild and test
131162
@@ -133,6 +164,8 @@ purelint/
133164
134165- `purescript-language-cst-parser` - PureScript CST parser
135166- `purescript-lsp` - LSP protocol implementation (Go FFI)
167+- `purs-backend-es` - PureScript to JavaScript backend
168+- `esbuild` - Bundler for CLI and LSP
136169
137170## License
138171
bin/purelint-cli/src/Main.pursdeleted−366
@@ -1,366 +0,0 @@
1−module Main where
2−
3−import Prelude
4−
5−import Data.Array as Array
6−import Data.Either (Either(..))
7−import Data.Foldable (for_)
8−import Data.Maybe (Maybe(..))
9−import Data.Newtype (unwrap)
10−import Data.String.CodeUnits as String
11−import Effect (Effect)
12−import Effect.Console (log)
13−import Node.FS.Simple as FS
14−import 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.UseEitherMap (useEitherMapRule)
57−import Purelint.Rules.UseElemIndex (useElemIndexRule)
58−import Purelint.Rules.UseFindMap (useFindMapRule)
59−import Purelint.Rules.UseFold (useFoldRule)
60−import Purelint.Rules.UseFoldBool (useFoldBoolRule)
61−import Purelint.Rules.UseFoldMap (useFoldMapRule)
62−import Purelint.Rules.UseFoldMapId (useFoldMapIdRule)
63−import Purelint.Rules.UseFor (useForRule)
64−import Purelint.Rules.UseFromJust (useFromJustRule)
65−import Purelint.Rules.UseFromMaybe (useFromMaybeRule)
66−import Purelint.Rules.UseFstSnd (useFstSndRule)
67−import Purelint.Rules.UseGuard (useGuardRule)
68−import Purelint.Rules.UseGuardMaybe (useGuardMaybeRule)
69−import Purelint.Rules.UseHead (useHeadRule)
70−import Purelint.Rules.UseIsJust (useIsJustRule)
71−import Purelint.Rules.UseIsNothing (useIsNothingRule)
72−import Purelint.Rules.UseJoin (useJoinRule)
73−import Purelint.Rules.UseLastReverse (useLastReverseRule)
74−import Purelint.Rules.UseMapMaybe (useMapMaybeRule)
75−import Purelint.Rules.UseMaybeMap (useMaybeMapRule)
76−import Purelint.Rules.UseMinMax (useMinMaxRule)
77−import Purelint.Rules.UseMinimumSort (useMinimumSortRule)
78−import Purelint.Rules.UseNotElem (useNotElemRule)
79−import Purelint.Rules.UseNull (useNullRule)
80−import Purelint.Rules.UseOn (useOnRule)
81−import Purelint.Rules.UseOr (useOrRule)
82−import Purelint.Rules.UsePatternGuards (usePatternGuardsRule)
83−import Purelint.Rules.UseReplicate (useReplicateRule)
84−import Purelint.Rules.UseSequence (useSequenceRule)
85−import Purelint.Rules.UseSpan (useSpanRule)
86−import Purelint.Rules.UseTraverse (useTraverseRule)
87−import Purelint.Rules.UseTraverseSequence (useTraverseSequenceRule)
88−import Purelint.Rules.UseUncurry (useUncurryRule)
89−import Purelint.Rules.UseUnless (useUnlessRule)
90−import Purelint.Rules.UseUnwrap (useUnwrapRule)
91−import Purelint.Rules.UseVoid (useVoidRule)
92−import Purelint.Rules.UseWhen (useWhenRule)
93−import Purelint.Rules.UseZip (useZipRule)
94−import Purelint.Rules.WhenNot (whenNotRule)
95−import Purelint.Runner (runRules)
96−import Purelint.Types (LintResult(..), LintWarning(..), Severity(..), SourceCode(..), Suggestion(..))
97−
98−-- | All available rules (76 total)
99−allRules :: Array Rule
100−allRules =
101− [ alternativeLawRule
102− , booleanSimplifyRule
103− , collapseLambdasRule
104− , concatMapRule
105− , etaReduceRule
106− , etaReduceDeclRule
107− , evaluateBoolRule
108− , evaluateConstRule
109− , evaluateEitherRule
110− , evaluateFstRule
111− , mapIdentityRule
112− , functorLawRule
113− , letToDoRule
114− , letToWhereRule
115− , mapFusionRule
116− , monadLawRule
117− , monoidIdentityRule
118− , notEqualRule
119− , nothingBindRule
120− , redundantBindRule
121− , redundantFlipRule
122− , redundantGuardRule
123− , redundantIdRule
124− , redundantIfRule
125− , redundantNegateRule
126− , redundantNotRule
127− , redundantReverseRule
128− , unlessNotRule
129− , useAllRule
130− , useAndRule
131− , useAnyRule
132− , useApplicativeRule
133− , useApplyFlippedRule
134− , useBimapRule
135− , useBindFlipRule
136− , useBreakRule
137− , useComparingRule
138− , useConstRule
139− , useEitherMapRule
140− , useElemIndexRule
141− , useFindMapRule
142− , useFoldRule
143− , useFoldBoolRule
144− , useFoldMapRule
145− , useFoldMapIdRule
146− , useForRule
147− , useFromJustRule
148− , useFromMaybeRule
149− , useFstSndRule
150− , useGuardRule
151− , useGuardMaybeRule
152− , useHeadRule
153− , useIsJustRule
154− , useIsNothingRule
155− , useJoinRule
156− , useLastReverseRule
157− , useMapMaybeRule
158− , useMaybeMapRule
159− , useMinMaxRule
160− , useMinimumSortRule
161− , useNotElemRule
162− , useNullRule
163− , useOnRule
164− , useOrRule
165− , usePatternGuardsRule
166− , useReplicateRule
167− , useSequenceRule
168− , useSpanRule
169− , useTraverseRule
170− , useTraverseSequenceRule
171− , useUncurryRule
172− , useUnlessRule
173− , useUnwrapRule
174− , useVoidRule
175− , useWhenRule
176− , useZipRule
177− , whenNotRule
178− ]
179−
180−-- | Load config from .purelintrc file
181−loadConfig :: Effect _
182−loadConfig = do
183− hasConfig <- FS.exists ".purelintrc"
184− if hasConfig then do
185− content <- FS.readTextFile ".purelintrc"
186− pure $ parseConfig content
187− else
188− pure defaultConfig
189−
190−-- | Format a source range for display
191−formatRange :: { start :: { line :: Int, column :: Int }, end :: { line :: Int, column :: Int } } -> String
192−formatRange r = show (r.start.line + 1) <> ":" <> show (r.start.column + 1) <> "-" <> show (r.end.line + 1) <> ":" <> show (r.end.column + 1)
193−
194−-- | Format severity for display
195−formatSeverity :: Severity -> String
196−formatSeverity Refactor = "[refactor]"
197−formatSeverity Hint = "[hint]"
198−formatSeverity Warning = "[warning]"
199−formatSeverity Error = "[error]"
200−
201−-- | Format a single warning for display
202−formatWarning :: String -> LintWarning -> String
203−formatWarning filePath (LintWarning w) =
204− filePath <> ":" <> formatRange w.range <> " " <> formatSeverity w.severity <> " " <> unwrap w.ruleId <> ": " <> unwrap w.message <>
205− case w.suggestion of
206− Nothing -> ""
207− Just (Suggestion s) -> "\n Replace with: " <> unwrap s.replacement
208−
209−-- | Lint a single file with config
210−lintFileWithConfig :: Array Rule -> String -> Effect Int
211−lintFileWithConfig rules filePath = do
212− content <- FS.readTextFile filePath
213− case runRules rules (SourceCode content) of
214− Left err -> do
215− log $ filePath <> ": " <> err
216− pure 0
217− Right (LintResult result) -> do
218− for_ result.warnings \w -> do
219− log $ formatWarning filePath w
220− pure (Array.length result.warnings)
221−
222−-- | Fix a single file
223−fixFile :: Array Rule -> String -> Effect Int
224−fixFile rules filePath = do
225− content <- FS.readTextFile filePath
226− case runRules rules (SourceCode content) of
227− Left err -> do
228− log $ filePath <> ": " <> err
229− pure 0
230− Right result@(LintResult r) -> do
231− let fixedContent = applyAllFixes content result
232− when (fixedContent /= content) do
233− FS.writeTextFile filePath fixedContent
234− log $ filePath <> ": fixed " <> show (Array.length r.warnings) <> " issue(s)"
235− pure (Array.length r.warnings)
236−
237−-- | Recursively find all .purs files in a directory
238−findPursFiles :: String -> Effect (Array String)
239−findPursFiles dir = do
240− entries <- FS.readdir dir
241− files <- Array.foldM (collectFiles dir) [] entries
242− pure files
243− where
244− collectFiles :: String -> Array String -> String -> Effect (Array String)
245− collectFiles parentDir acc entry = do
246− let path = parentDir <> "/" <> entry
247− -- Skip hidden directories and node_modules
248− if String.take 1 entry == "." || entry == "node_modules" || entry == ".spago" then
249− pure acc
250− else do
251− stats <- FS.stat path
252− if stats.isDirectory then do
253− subFiles <- findPursFiles path
254− pure (acc <> subFiles)
255− else if String.takeRight 5 entry == ".purs" then
256− pure (Array.snoc acc path)
257− else
258− pure acc
259−
260−-- | Lint a file or directory with config
261−lintPathWithConfig :: Array Rule -> String -> Effect Int
262−lintPathWithConfig rules path = do
263− stats <- FS.stat path
264− if stats.isDirectory then do
265− files <- findPursFiles path
266− counts <- Array.foldM (\acc file -> do
267− count <- lintFileWithConfig rules file
268− pure (acc + count)) 0 files
269− pure counts
270− else
271− lintFileWithConfig rules path
272−
273−-- | Fix a file or directory
274−fixPath :: Array Rule -> String -> Effect Int
275−fixPath rules path = do
276− stats <- FS.stat path
277− if stats.isDirectory then do
278− files <- findPursFiles path
279− counts <- Array.foldM (\acc file -> do
280− count <- fixFile rules file
281− pure (acc + count)) 0 files
282− pure counts
283− else
284− fixFile rules path
285−
286−-- | Show usage
287−showUsage :: Effect Unit
288−showUsage = do
289− log "purelint - a linter for PureScript (70 rules)"
290− log ""
291− log "Usage: purelint [OPTIONS] [FILES/DIRECTORIES...]"
292− log ""
293− log "Options:"
294− log " --help Show this help"
295− log " --fix Auto-fix issues (where possible)"
296− log " --init Create a sample .purelintrc"
297− log " --list List all available rules"
298− log ""
299− log "Configuration:"
300− log " Create .purelintrc in your project root to disable rules."
301− log " Add one rule name per line (lines starting with # are comments)."
302− log ""
303− log "Examples:"
304− log " purelint src/ Lint all .purs files in src/"
305− log " purelint --fix src/ Auto-fix issues in src/"
306− log " purelint src/Main.purs Lint a single file"
307−
308−-- | List all available rules
309−listRules :: Effect Unit
310−listRules = do
311− log $ "Available rules (" <> show (Array.length allRules) <> " total):"
312− log ""
313− for_ allRules \rule -> do
314− log $ " " <> unwrap rule.ruleId
315−
316−-- | Create sample config file
317−initConfig :: Effect Unit
318−initConfig = do
319− hasConfig <- FS.exists ".purelintrc"
320− if hasConfig then
321− log ".purelintrc already exists"
322− else do
323− let sampleConfig = """# purelint configuration
324−# Add rule names (one per line) to disable them
325−# Example:
326−# EtaReduce
327−# LetToWhere
328−"""
329− FS.writeTextFile ".purelintrc" sampleConfig
330− log "Created .purelintrc"
331−
332−main :: Effect Unit
333−main = do
334− args <- Process.argv
335− -- args[0] is node, args[1] is the script, args[2..] are the actual arguments
336− let userArgs = Array.drop 1 args
337− case userArgs of
338− [] -> showUsage
339− ["--help"] -> showUsage
340− ["-h"] -> showUsage
341− ["--list"] -> listRules
342− ["--init"] -> initConfig
343− _ -> do
344− -- Check for --fix flag
345− let hasFix = Array.elem "--fix" userArgs
346− let paths = Array.filter (\a -> a /= "--fix") userArgs
347−
348− -- Load config and filter rules
349− config <- loadConfig
350− let enabledRules = filterRules allRules config
351−
352− if hasFix then do
353− -- Fix mode
354− _ <- Array.foldM (\acc path -> do
355− count <- fixPath enabledRules path
356− pure (acc + count)) 0 paths
357− pure unit
358− else do
359− -- Lint mode
360− totalWarnings <- Array.foldM (\acc path -> do
361− count <- lintPathWithConfig enabledRules path
362− pure (acc + count)) 0 paths
363− when (totalWarnings > 0) do
364− log ""
365− log $ "Total: " <> show totalWarnings <> " warning" <> if totalWarnings == 1 then "" else "s"
366− Process.exit 1
bin/purelint-lsp.jsdeleted−3
@@ -1,3 +0,0 @@
1−#!/usr/bin/env node
2−import { main } from "../output-lsp/Main.LSP/index.js";
3−main();
bin/purelint.jsdeleted−3
@@ -1,3 +0,0 @@
1−#!/usr/bin/env node
2−import { main } from "../output-cli/Main/index.js";
3−main();
bin/purelint-cli/spago.yaml → bin/purslint-cli/spago.yamlrenamed+3−2
@@ -1,5 +1,5 @@
11package:
2− name: purelint-cli
2+ name: purslint-cli
33 dependencies:
44 - arrays
55 - console
@@ -8,8 +8,9 @@ package:
88 - foldable-traversable
99 - maybe
1010 - newtype
11+ - optparse
1112 - prelude
12− - purelint
13+ - purslint
1314 - strings
1415 - node-buffer
1516 - node-fs
bin/purslint-cli/src/Main.pursnew file+419
@@ -0,0 +1,419 @@
1+module Main where
2+
3+import Prelude
4+
5+import Data.Array as Array
6+import Data.Either (Either(..))
7+import Data.Enum (fromEnum)
8+import Data.Foldable (for_)
9+import Data.Maybe (Maybe(..))
10+import Data.Newtype (unwrap)
11+import Data.String.CodeUnits as String
12+import Data.Tuple.Nested ((/\))
13+import Effect (Effect)
14+import Effect.Console (log)
15+import Node.FS.Simple as FS
16+import Node.CLI as Process
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+ }
114+
115+-- | All available rules (81 total)
116+allRules :: Array Rule
117+allRules =
118+ [ alternativeLawRule
119+ , booleanSimplifyRule
120+ , collapseLambdasRule
121+ , concatMapRule
122+ , etaReduceRule
123+ , etaReduceDeclRule
124+ , evaluateBoolRule
125+ , evaluateConstRule
126+ , evaluateEitherRule
127+ , evaluateFstRule
128+ , mapIdentityRule
129+ , functorLawRule
130+ , letToDoRule
131+ , letToWhereRule
132+ , mapFusionRule
133+ , monadLawRule
134+ , monoidIdentityRule
135+ , notEqualRule
136+ , nothingBindRule
137+ , redundantBindRule
138+ , redundantFlipRule
139+ , redundantGuardRule
140+ , redundantIdRule
141+ , redundantIfRule
142+ , redundantNegateRule
143+ , redundantNotRule
144+ , redundantParensRule
145+ , redundantReverseRule
146+ , unlessNotRule
147+ , useAllRule
148+ , useAndRule
149+ , useAnyRule
150+ , useApplicativeRule
151+ , useApplyFlippedRule
152+ , useBimapRule
153+ , useBindFlipRule
154+ , useBreakRule
155+ , useComparingRule
156+ , useConstRule
157+ , useDollarRule
158+ , useEitherMapRule
159+ , useElemIndexRule
160+ , useFindMapRule
161+ , useFoldRule
162+ , useFoldBoolRule
163+ , useFoldMapRule
164+ , useFoldMapIdRule
165+ , useForRule
166+ , useFromJustRule
167+ , useFromMaybeRule
168+ , useFstSndRule
169+ , useGuardRule
170+ , useGuardMaybeRule
171+ , useHeadRule
172+ , useIsJustRule
173+ , useIsNothingRule
174+ , useJoinRule
175+ , useLastReverseRule
176+ , useMapMaybeRule
177+ , useMaybeMapRule
178+ , useMinMaxRule
179+ , useMinimumSortRule
180+ , useNotElemRule
181+ , useNullRule
182+ , useOnRule
183+ , useOrRule
184+ , usePatternGuardsRule
185+ , useCaseOfRule
186+ , flattenCaseRule
187+ , useReplicateRule
188+ , useSequenceRule
189+ , useSpanRule
190+ , useTraverseRule
191+ , useTraverseSequenceRule
192+ , useUncurryRule
193+ , useUnlessRule
194+ , useUnwrapRule
195+ , useVoidRule
196+ , useWhenRule
197+ , useZipRule
198+ , whenNotRule
199+ ]
200+
201+-- | Load config from .purslintrc file
202+loadConfig :: Effect _
203+loadConfig = do
204+ hasConfig <- FS.exists ".purslintrc"
205+ if hasConfig then do
206+ content <- FS.readTextFile ".purslintrc"
207+ pure $ parseConfig content
208+ else
209+ pure defaultConfig
210+
211+-- | Format a source range for display
212+formatRange :: { start :: { line :: Int, column :: Int }, end :: { line :: Int, column :: Int } } -> String
213+formatRange r = show (r.start.line + 1) <> ":" <> show (r.start.column + 1) <> "-" <> show (r.end.line + 1) <> ":" <> show (r.end.column + 1)
214+
215+-- | Format severity for display
216+formatSeverity :: Severity -> String
217+formatSeverity Refactor = "[refactor]"
218+formatSeverity Hint = "[hint]"
219+formatSeverity Warning = "[warning]"
220+formatSeverity Error = "[error]"
221+
222+-- | Format a single warning for display
223+formatWarning :: String -> LintWarning -> String
224+formatWarning filePath (LintWarning w) =
225+ filePath <> ":" <> formatRange w.range <> " " <> formatSeverity w.severity <> " " <> unwrap w.ruleId <> ": " <> unwrap w.message <>
226+ case w.suggestion of
227+ Nothing -> ""
228+ Just (Suggestion s) -> "\n Replace with: " <> unwrap s.replacement
229+
230+-- | Lint a single file with config
231+lintFileWithConfig :: Array Rule -> String -> Effect Int
232+lintFileWithConfig rules filePath = do
233+ content <- FS.readTextFile filePath
234+ case runRules rules (SourceCode content) of
235+ Left err -> do
236+ log $ filePath <> ": " <> err
237+ pure 0
238+ Right (LintResult result) -> do
239+ for_ result.warnings \w -> do
240+ log $ formatWarning filePath w
241+ pure (Array.length result.warnings)
242+
243+-- | Fix a single file
244+fixFile :: Array Rule -> String -> Effect Int
245+fixFile rules filePath = do
246+ content <- FS.readTextFile filePath
247+ case runRules rules (SourceCode content) of
248+ Left err -> do
249+ log $ filePath <> ": " <> err
250+ pure 0
251+ Right result@(LintResult r) -> do
252+ let fixedContent = applyAllFixes content result
253+ when (fixedContent /= content) do
254+ FS.writeTextFile filePath fixedContent
255+ log $ filePath <> ": fixed " <> show (Array.length r.warnings) <> " issue(s)"
256+ pure (Array.length r.warnings)
257+
258+-- | Recursively find all .purs files in a directory
259+findPursFiles :: String -> Effect (Array String)
260+findPursFiles dir = do
261+ entries <- FS.readdir dir
262+ files <- Array.foldM (collectFiles dir) [] entries
263+ pure files
264+ where
265+ collectFiles :: String -> Array String -> String -> Effect (Array String)
266+ collectFiles parentDir acc entry = do
267+ let path = parentDir <> "/" <> entry
268+ -- Skip hidden directories and node_modules
269+ if String.take 1 entry == "." || entry == "node_modules" || entry == ".spago" then
270+ pure acc
271+ else do
272+ stats <- FS.stat path
273+ if stats.isDirectory then do
274+ subFiles <- findPursFiles path
275+ pure (acc <> subFiles)
276+ else if String.takeRight 5 entry == ".purs" then
277+ pure (Array.snoc acc path)
278+ else
279+ pure acc
280+
281+-- | Lint a file or directory with config
282+lintPathWithConfig :: Array Rule -> String -> Effect Int
283+lintPathWithConfig rules path = do
284+ stats <- FS.stat path
285+ if stats.isDirectory then do
286+ files <- findPursFiles path
287+ counts <- Array.foldM (\acc file -> do
288+ count <- lintFileWithConfig rules file
289+ pure (acc + count)) 0 files
290+ pure counts
291+ else
292+ lintFileWithConfig rules path
293+
294+-- | Fix a file or directory
295+fixPath :: Array Rule -> String -> Effect Int
296+fixPath rules path = do
297+ stats <- FS.stat path
298+ if stats.isDirectory then do
299+ files <- findPursFiles path
300+ counts <- Array.foldM (\acc file -> do
301+ count <- fixFile rules file
302+ pure (acc + count)) 0 files
303+ pure counts
304+ else
305+ fixFile rules path
306+
307+-- | Show usage
308+showUsage :: Effect Unit
309+showUsage = do
310+ log "purslint - a linter for PureScript (81 rules)"
311+ log ""
312+ log "Usage: purslint [OPTIONS] [FILES/DIRECTORIES...]"
313+ log ""
314+ log "Options:"
315+ log " --help Show this help"
316+ log " --fix Auto-fix issues (where possible)"
317+ log " --init Create a sample .purslintrc"
318+ log " --list List all available rules"
319+ log ""
320+ log "Configuration:"
321+ log " Create .purslintrc in your project root to disable rules."
322+ log " Add one rule name per line (lines starting with # are comments)."
323+ log ""
324+ log "Examples:"
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"
328+
329+-- | List all available rules
330+listRules :: Effect Unit
331+listRules = do
332+ log $ "Available rules (" <> show (Array.length allRules) <> " total):"
333+ log ""
334+ for_ allRules \rule -> do
335+ log $ " " <> unwrap rule.ruleId
336+
337+-- | Create sample config file
338+initConfig :: Effect Unit
339+initConfig = do
340+ hasConfig <- FS.exists ".purslintrc"
341+ if hasConfig then
342+ log ".purslintrc already exists"
343+ else do
344+ let sampleConfig = """# purslint configuration
345+# Add rule names (one per line) to disable them
346+# Example:
347+# EtaReduce
348+# LetToWhere
349+"""
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 }
386+
387+main :: Effect Unit
388+main = do
389+ args <- Process.argv
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
400+ else do
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
bin/purelint-cli/src/Node/CLI.js → bin/purslint-cli/src/Node/CLI.jsrenamed

The content is the same.

bin/purelint-cli/src/Node/CLI.purs → bin/purslint-cli/src/Node/CLI.pursrenamed

The content is the same.

bin/purelint-cli/src/Node/FS/Simple.js → bin/purslint-cli/src/Node/FS/Simple.jsrenamed

The content is the same.

bin/purelint-cli/src/Node/FS/Simple.purs → bin/purslint-cli/src/Node/FS/Simple.pursrenamed

The content is the same.

bin/purslint-lsp.jsnew file+2
@@ -0,0 +1,2 @@
1+#!/usr/bin/env node
2+import "../dist/purslint-lsp.js";
bin/purelint-lsp/spago.yaml → bin/purslint-lsp/spago.yamlrenamed+2−2
@@ -1,5 +1,5 @@
11package:
2− name: purelint-lsp
2+ name: purslint-lsp
33 dependencies:
44 - arrays
55 - effect
@@ -8,5 +8,5 @@ package:
88 - maybe
99 - newtype
1010 - prelude
11− - purelint
11+ - purslint
1212 - unsafe-coerce
bin/purelint-lsp/src/LSP.js → bin/purslint-lsp/src/LSP.jsrenamed+1−1
@@ -1,4 +1,4 @@
1−// Purelint.LSP FFI for JavaScript
1+// Purslint.LSP FFI for JavaScript
22
33// Create a null value
44export const mkNull = (_) => null;
bin/purelint-lsp/src/LSP.purs → bin/purslint-lsp/src/LSP.pursrenamed+108−100
@@ -1,4 +1,4 @@
1−module Purelint.LSP where
1+module Purslint.LSP where
22
33import Prelude
44
@@ -13,87 +13,91 @@ import Effect.Ref (Ref)
1313import Effect.Ref as Ref
1414import Foreign (Foreign)
1515import LSP.Stdio as Stdio
16−import Purelint.Config (Config, defaultConfig, filterRules)
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.MapIdentity (mapIdentityRule)
24−import Purelint.Rules.FunctorLaw (functorLawRule)
25−import Purelint.Rules.LetToDo (letToDoRule)
26−import Purelint.Rules.LetToWhere (letToWhereRule)
27−import Purelint.Rules.MapFusion (mapFusionRule)
28−import Purelint.Rules.MonadLaw (monadLawRule)
29−import Purelint.Rules.MonoidIdentity (monoidIdentityRule)
30−import Purelint.Rules.NotEqual (notEqualRule)
31−import Purelint.Rules.RedundantBind (redundantBindRule)
32−import Purelint.Rules.RedundantFlip (redundantFlipRule)
33−import Purelint.Rules.RedundantIf (redundantIfRule)
34−import Purelint.Rules.RedundantNot (redundantNotRule)
35−import Purelint.Rules.UseAll (useAllRule)
36−import Purelint.Rules.UseAny (useAnyRule)
37−import Purelint.Rules.UseConst (useConstRule)
38−import Purelint.Rules.UseFold (useFoldRule)
39−import Purelint.Rules.UseFromMaybe (useFromMaybeRule)
40−import Purelint.Rules.UseFstSnd (useFstSndRule)
41−import Purelint.Rules.UseGuard (useGuardRule)
42−import Purelint.Rules.UseIsJust (useIsJustRule)
43−import Purelint.Rules.UseIsNothing (useIsNothingRule)
44−import Purelint.Rules.UseJoin (useJoinRule)
45−import Purelint.Rules.UseMinMax (useMinMaxRule)
46−import Purelint.Rules.UseNotElem (useNotElemRule)
47−import Purelint.Rules.UseNull (useNullRule)
48−import Purelint.Rules.UseSequence (useSequenceRule)
49−import Purelint.Rules.UseTraverse (useTraverseRule)
50−import Purelint.Rules.UseTraverseSequence (useTraverseSequenceRule)
51−import Purelint.Rules.UseUnless (useUnlessRule)
52−import Purelint.Rules.UseUnwrap (useUnwrapRule)
53−import Purelint.Rules.UseVoid (useVoidRule)
54−import Purelint.Rules.UseWhen (useWhenRule)
55−import Purelint.Rules.UseMapMaybe (useMapMaybeRule)
56−import Purelint.Rules.UseGuardMaybe (useGuardMaybeRule)
57−import Purelint.Rules.UseMaybeMap (useMaybeMapRule)
58−import Purelint.Rules.UseApplicative (useApplicativeRule)
59−import Purelint.Rules.UseApplyFlipped (useApplyFlippedRule)
60−import Purelint.Rules.UseBindFlip (useBindFlipRule)
61−import Purelint.Rules.UseFor (useForRule)
62−import Purelint.Rules.RedundantGuard (redundantGuardRule)
63−import Purelint.Rules.UseComparing (useComparingRule)
64−import Purelint.Rules.UseOn (useOnRule)
65−import Purelint.Rules.UseFindMap (useFindMapRule)
66−import Purelint.Rules.UseLastReverse (useLastReverseRule)
67−import Purelint.Rules.RedundantReverse (redundantReverseRule)
68−import Purelint.Rules.UseBreak (useBreakRule)
69−import Purelint.Rules.UseSpan (useSpanRule)
70−import Purelint.Rules.UseMinimumSort (useMinimumSortRule)
71−import Purelint.Rules.UseBimap (useBimapRule)
72−import Purelint.Rules.UseEitherMap (useEitherMapRule)
73−import Purelint.Rules.AlternativeLaw (alternativeLawRule)
74−import Purelint.Rules.EvaluateBool (evaluateBoolRule)
75−import Purelint.Rules.EvaluateConst (evaluateConstRule)
76−import Purelint.Rules.EvaluateEither (evaluateEitherRule)
77−import Purelint.Rules.EvaluateFst (evaluateFstRule)
78−import Purelint.Rules.UseOr (useOrRule)
79−import Purelint.Rules.UseAnd (useAndRule)
80−import Purelint.Rules.UseFoldMap (useFoldMapRule)
81−import Purelint.Rules.UseFoldMapId (useFoldMapIdRule)
82−import Purelint.Rules.WhenNot (whenNotRule)
83−import Purelint.Rules.UnlessNot (unlessNotRule)
84−import Purelint.Rules.UseZip (useZipRule)
85−import Purelint.Rules.UseReplicate (useReplicateRule)
86−import Purelint.Rules.UseUncurry (useUncurryRule)
87−import Purelint.Rules.RedundantNegate (redundantNegateRule)
88−import Purelint.Rules.UseFromJust (useFromJustRule)
89−import Purelint.Rules.UseHead (useHeadRule)
90−import Purelint.Rules.RedundantId (redundantIdRule)
91−import Purelint.Rules.NothingBind (nothingBindRule)
92−import Purelint.Rules.UseElemIndex (useElemIndexRule)
93−import Purelint.Rules.UseFoldBool (useFoldBoolRule)
94−import Purelint.Rules.UsePatternGuards (usePatternGuardsRule)
95−import Purelint.Runner (runRules)
96−import Purelint.Types (LintResult(..), LintWarning(..), RuleId(..), Severity(..), SourceCode(..), Suggestion(..))
16+import Purslint.Config (Config, defaultConfig, filterRules)
17+import Purslint.Rule (Rule)
18+import Purslint.Rules.BooleanSimplify (booleanSimplifyRule)
19+import Purslint.Rules.CollapseLambdas (collapseLambdasRule)
20+import Purslint.Rules.ConcatMap (concatMapRule)
21+import Purslint.Rules.EtaReduce (etaReduceRule)
22+import Purslint.Rules.EtaReduceDecl (etaReduceDeclRule)
23+import Purslint.Rules.MapIdentity (mapIdentityRule)
24+import Purslint.Rules.FunctorLaw (functorLawRule)
25+import Purslint.Rules.LetToDo (letToDoRule)
26+import Purslint.Rules.LetToWhere (letToWhereRule)
27+import Purslint.Rules.MapFusion (mapFusionRule)
28+import Purslint.Rules.MonadLaw (monadLawRule)
29+import Purslint.Rules.MonoidIdentity (monoidIdentityRule)
30+import Purslint.Rules.NotEqual (notEqualRule)
31+import Purslint.Rules.RedundantBind (redundantBindRule)
32+import Purslint.Rules.RedundantFlip (redundantFlipRule)
33+import Purslint.Rules.RedundantIf (redundantIfRule)
34+import Purslint.Rules.RedundantNot (redundantNotRule)
35+import Purslint.Rules.UseAll (useAllRule)
36+import Purslint.Rules.UseAny (useAnyRule)
37+import Purslint.Rules.UseConst (useConstRule)
38+import Purslint.Rules.UseFold (useFoldRule)
39+import Purslint.Rules.UseFromMaybe (useFromMaybeRule)
40+import Purslint.Rules.UseFstSnd (useFstSndRule)
41+import Purslint.Rules.UseGuard (useGuardRule)
42+import Purslint.Rules.UseIsJust (useIsJustRule)
43+import Purslint.Rules.UseIsNothing (useIsNothingRule)
44+import Purslint.Rules.UseJoin (useJoinRule)
45+import Purslint.Rules.UseMinMax (useMinMaxRule)
46+import Purslint.Rules.UseNotElem (useNotElemRule)
47+import Purslint.Rules.UseNull (useNullRule)
48+import Purslint.Rules.UseSequence (useSequenceRule)
49+import Purslint.Rules.UseTraverse (useTraverseRule)
50+import Purslint.Rules.UseTraverseSequence (useTraverseSequenceRule)
51+import Purslint.Rules.UseUnless (useUnlessRule)
52+import Purslint.Rules.UseUnwrap (useUnwrapRule)
53+import Purslint.Rules.UseVoid (useVoidRule)
54+import Purslint.Rules.UseWhen (useWhenRule)
55+import Purslint.Rules.UseMapMaybe (useMapMaybeRule)
56+import Purslint.Rules.UseGuardMaybe (useGuardMaybeRule)
57+import Purslint.Rules.UseMaybeMap (useMaybeMapRule)
58+import Purslint.Rules.UseApplicative (useApplicativeRule)
59+import Purslint.Rules.UseApplyFlipped (useApplyFlippedRule)
60+import Purslint.Rules.UseBindFlip (useBindFlipRule)
61+import Purslint.Rules.UseFor (useForRule)
62+import Purslint.Rules.RedundantGuard (redundantGuardRule)
63+import Purslint.Rules.UseComparing (useComparingRule)
64+import Purslint.Rules.UseOn (useOnRule)
65+import Purslint.Rules.UseFindMap (useFindMapRule)
66+import Purslint.Rules.UseLastReverse (useLastReverseRule)
67+import Purslint.Rules.RedundantReverse (redundantReverseRule)
68+import Purslint.Rules.UseBreak (useBreakRule)
69+import Purslint.Rules.UseSpan (useSpanRule)
70+import Purslint.Rules.UseMinimumSort (useMinimumSortRule)
71+import Purslint.Rules.UseBimap (useBimapRule)
72+import Purslint.Rules.UseDollar (useDollarRule)
73+import Purslint.Rules.UseEitherMap (useEitherMapRule)
74+import Purslint.Rules.AlternativeLaw (alternativeLawRule)
75+import Purslint.Rules.EvaluateBool (evaluateBoolRule)
76+import Purslint.Rules.EvaluateConst (evaluateConstRule)
77+import Purslint.Rules.EvaluateEither (evaluateEitherRule)
78+import Purslint.Rules.EvaluateFst (evaluateFstRule)
79+import Purslint.Rules.UseOr (useOrRule)
80+import Purslint.Rules.UseAnd (useAndRule)
81+import Purslint.Rules.UseFoldMap (useFoldMapRule)
82+import Purslint.Rules.UseFoldMapId (useFoldMapIdRule)
83+import Purslint.Rules.WhenNot (whenNotRule)
84+import Purslint.Rules.UnlessNot (unlessNotRule)
85+import Purslint.Rules.UseZip (useZipRule)
86+import Purslint.Rules.UseReplicate (useReplicateRule)
87+import Purslint.Rules.UseUncurry (useUncurryRule)
88+import Purslint.Rules.RedundantNegate (redundantNegateRule)
89+import Purslint.Rules.UseFromJust (useFromJustRule)
90+import Purslint.Rules.UseHead (useHeadRule)
91+import Purslint.Rules.RedundantId (redundantIdRule)
92+import Purslint.Rules.NothingBind (nothingBindRule)
93+import Purslint.Rules.UseElemIndex (useElemIndexRule)
94+import Purslint.Rules.UseFoldBool (useFoldBoolRule)
95+import Purslint.Rules.UsePatternGuards (usePatternGuardsRule)
96+import Purslint.Rules.UseCaseOf (useCaseOfRule)
97+import Purslint.Rules.FlattenCase (flattenCaseRule)
98+import Purslint.Rules.RedundantParens (redundantParensRule)
99+import Purslint.Runner (runRules)
100+import Purslint.Types (LintResult(..), LintWarning(..), RuleId(..), Severity(..), SourceCode(..), Suggestion(..))
97101import Unsafe.Coerce (unsafeCoerce)
98102
99103-- | All available rules
@@ -177,6 +181,10 @@ allRules =
177181 , nothingBindRule
178182 , useElemIndexRule
179183 , usePatternGuardsRule
184+ , useCaseOfRule
185+ , flattenCaseRule
186+ , redundantParensRule
187+ , useDollarRule
180188 ]
181189
182190-- | Get enabled rules based on config
@@ -200,7 +208,7 @@ warningToDiagnostic (LintWarning w) =
200208 base =
201209 { range: range
202210 , severity: severityToLSP w.severity
203− , source: "purelint"
211+ , source: "purslint"
204212 , message: unwrap w.ruleId <> ": " <> unwrap w.message
205213 }
206214 in case w.suggestion of
@@ -208,7 +216,7 @@ warningToDiagnostic (LintWarning w) =
208216 Just (Suggestion sug) -> unsafeCoerce
209217 { range: range
210218 , severity: severityToLSP w.severity
211− , source: "purelint"
219+ , source: "purslint"
212220 , message: unwrap w.ruleId <> ": " <> unwrap w.message
213221 , data:
214222 { hasFix: true
@@ -265,7 +273,7 @@ mkInitializeResponse reqId =
265273 }
266274 }
267275 , serverInfo:
268− { name: "purelint-lsp"
276+ { name: "purslint-lsp"
269277 , version: "0.1.0"
270278 }
271279 }
@@ -326,32 +334,32 @@ foreign import hasKey :: String -> Foreign -> Boolean
326334foreign import getKey :: String -> Foreign -> Foreign
327335
328336-- | Parse settings to get disabled rules
329−-- | Expected format: { "purelint": { "disabledRules": ["RuleId1", "RuleId2"] } }
330−-- | Or: { "purelint.disabledRules": ["RuleId1", "RuleId2"] }
331−-- | Or: { "purelint.enabled": false } to disable all rules
337+-- | Expected format: { "purslint": { "disabledRules": ["RuleId1", "RuleId2"] } }
338+-- | Or: { "purslint.disabledRules": ["RuleId1", "RuleId2"] }
339+-- | Or: { "purslint.enabled": false } to disable all rules
332340parseSettings :: Foreign -> Config
333341parseSettings settings =
334− -- Try to get purelint.enabled first
335− case getFieldObj "purelint" settings of
336− Just purelintObj ->
342+ -- Try to get purslint.enabled first
343+ case getFieldObj "purslint" settings of
344+ Just purslintObj ->
337345 -- Check if all rules are disabled
338− case getFieldBool "enabled" purelintObj of
346+ case getFieldBool "enabled" purslintObj of
339347 Just false ->
340348 { disabledRules: map (\r -> r.ruleId) allRules } -- Disable all rules
341349 _ ->
342350 -- Get disabled rules list
343− case getFieldArr "disabledRules" purelintObj of
351+ case getFieldArr "disabledRules" purslintObj of
344352 Just arr ->
345353 { disabledRules: Array.mapMaybe parseRuleId arr }
346354 Nothing -> defaultConfig
347355 Nothing ->
348− -- Try flat format: purelint.disabledRules
349− case getFieldArr "purelint.disabledRules" settings of
356+ -- Try flat format: purslint.disabledRules
357+ case getFieldArr "purslint.disabledRules" settings of
350358 Just arr ->
351359 { disabledRules: Array.mapMaybe parseRuleId arr }
352360 Nothing ->
353− -- Try purelint.enabled
354− case getFieldBool "purelint.enabled" settings of
361+ -- Try purslint.enabled
362+ case getFieldBool "purslint.enabled" settings of
355363 Just false ->
356364 { disabledRules: map (\r -> r.ruleId) allRules }
357365 _ -> defaultConfig
@@ -608,7 +616,7 @@ mkDisableRuleAction ruleId diagnostic =
608616 , isPreferred: false
609617 , command:
610618 { title: "Disable rule " <> ruleId
611− , command: "purelint.disableRule"
619+ , command: "purslint.disableRule"
612620 , arguments: [ruleId]
613621 }
614622 }
@@ -636,7 +644,7 @@ mainLoop stateRef = do
636644
637645main :: Effect Unit
638646main = do
639− Stdio.logMessage "purelint-lsp starting..."
647+ Stdio.logMessage "purslint-lsp starting..."
640648 stateRef <- Ref.new { config: defaultConfig, documents: Map.empty }
641649 mainLoop stateRef
642− Stdio.logMessage "purelint-lsp exiting"
650+ Stdio.logMessage "purslint-lsp exiting"
bin/purelint-lsp/src/LSP/Stdio.js → bin/purslint-lsp/src/LSP/Stdio.jsrenamed+1−1
@@ -74,7 +74,7 @@ export const writeMessage = (msg) => () => {
7474
7575// Log a message to stderr (for debugging)
7676export const logMessage = (msg) => () => {
77− process.stderr.write('[purelint-lsp] ' + msg + '\n');
77+ process.stderr.write('[purslint-lsp] ' + msg + '\n');
7878};
7979
8080// Parse JSON string to Foreign (returns Maybe Foreign)
bin/purelint-lsp/src/LSP/Stdio.purs → bin/purslint-lsp/src/LSP/Stdio.pursrenamed

The content is the same.

bin/purelint-lsp/src/Main.purs → bin/purslint-lsp/src/Main.pursrenamed+1−1
@@ -2,7 +2,7 @@ module Main.LSP where
22
33import Prelude
44import Effect (Effect)
5−import Purelint.LSP as LSP
5+import Purslint.LSP as LSP
66
77main :: Effect Unit
88main = LSP.main
bin/purslint.jsnew file+2
@@ -0,0 +1,2 @@
1+#!/usr/bin/env node
2+import "../dist/purslint.js";
demo.gifnew file

Binary file not shown.

docs/IMPORT_CHECKING_PLAN.md+8−8
@@ -6,7 +6,7 @@ Make lint rules safer by verifying that identifiers like `map`, `identity`, `<$>
66
77## Current State
88
9−- `Purelint.Imports` module exists with:
9+- `Purslint.Imports` module exists with:
1010 - `getImportInfo :: Module Void -> ImportInfo` - extracts import info from a module
1111 - `hasValue :: ImportInfo -> String -> Boolean` - check if a value is imported
1212 - `hasOp :: ImportInfo -> String -> Boolean` - check if an operator is imported
@@ -18,7 +18,7 @@ Make lint rules safer by verifying that identifiers like `map`, `identity`, `<$>
1818### Step 1: Update Rule type to receive ImportInfo
1919
2020```purescript
21−-- In Purelint/Rule.purs
21+-- In Purslint/Rule.purs
2222type RuleContext =
2323 { imports :: ImportInfo
2424 }
@@ -34,7 +34,7 @@ mkRule :: RuleId -> (RuleContext -> Module Void -> Array LintWarning) -> Rule
3434### Step 2: Update Runner to pass context
3535
3636```purescript
37−-- In Purelint/Runner.purs
37+-- In Purslint/Runner.purs
3838runRules :: Array Rule -> SourceCode -> Either String LintResult
3939runRules rules (SourceCode source) = do
4040 mod <- parseModule source
@@ -96,11 +96,11 @@ All rules that check for specific identifiers:
9696
9797## Files to Modify
9898
99−1. `lib/purelint/src/Purelint/Rule.purs` - Add RuleContext
100−2. `lib/purelint/src/Purelint/Runner.purs` - Pass context to rules
101−3. `lib/purelint/src/Purelint/Rules/*.purs` - All 9 rule files
102−4. `bin/purelint-cli/src/Main.purs` - No changes needed (uses Runner)
103−5. `bin/purelint-lsp/src/LSP.purs` - No changes needed (uses Runner)
99+1. `lib/purslint/src/Purslint/Rule.purs` - Add RuleContext
100+2. `lib/purslint/src/Purslint/Runner.purs` - Pass context to rules
101+3. `lib/purslint/src/Purslint/Rules/*.purs` - All 9 rule files
102+4. `bin/purslint-cli/src/Main.purs` - No changes needed (uses Runner)
103+5. `bin/purslint-lsp/src/LSP.purs` - No changes needed (uses Runner)
104104
105105## Testing
106106
lib/purelint/src/Purelint/Rules/RedundantReverse.pursdeleted−63
@@ -1,63 +0,0 @@
1−module Purelint.Rules.RedundantReverse where
2−
3−import Prelude
4−
5−import Data.Array.NonEmpty as NEA
6−import Data.Maybe (Maybe(..))
7−import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
12−import PureScript.CST.Range (rangeOf)
13−import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
14−import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
15−
16−-- | Rule: reverse (reverse x) -> x
17−redundantReverseRule :: Rule
18−redundantReverseRule = mkRule (RuleId "RedundantReverse") run
19− where
20− run :: RuleContext -> Module Void -> Array LintWarning
21− run ctx = foldMapModule visitor
22− where
23− imports = ctx.imports
24− visitor = defaultMonoidalVisitor { onExpr = checkExpr imports }
25−
26− checkExpr :: ImportInfo -> Expr Void -> Array LintWarning
27− checkExpr imports expr = case expr of
28− -- Match: reverse (reverse x)
29− ExprApp fn args
30− | isReverse imports fn ->
31− case NEA.toArray args of
32− [AppTerm arg] ->
33− case unwrapParens arg of
34− ExprApp innerFn innerArgs
35− | isReverse imports innerFn ->
36− case NEA.toArray innerArgs of
37− [AppTerm innerArg] ->
38− let x = printExpr innerArg
39− in
40− [ LintWarning
41− { ruleId: RuleId "RedundantReverse"
42− , message: WarningMessage "reverse (reverse x) is redundant"
43− , range: rangeOf expr
44− , severity: Warning
45− , suggestion: Just $ Suggestion
46− { replacement: ReplacementText x
47− , description: SuggestionDescription "Remove redundant double reverse"
48− }
49− }
50− ]
51− _ -> []
52− _ -> []
53− _ -> []
54− _ -> []
55−
56− unwrapParens :: Expr Void -> Expr Void
57− unwrapParens (ExprParens (Wrapped { value })) = unwrapParens value
58− unwrapParens e = e
59−
60− isReverse :: ImportInfo -> Expr Void -> Boolean
61− isReverse imports (ExprIdent (QualifiedName { name: Ident name })) =
62− name == "reverse" && hasValue imports "reverse"
63− isReverse _ _ = false
lib/purelint/src/Purelint/Rules/UseBreak.pursdeleted−95
@@ -1,95 +0,0 @@
1−module Purelint.Rules.UseBreak where
2−
3−import Prelude
4−
5−import Data.Array.NonEmpty as NEA
6−import Data.Maybe (Maybe(..))
7−import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
12−import PureScript.CST.Range (rangeOf)
13−import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
14−import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
15−
16−-- | Rule: span (not <<< p) -> break p (composition form)
17−-- | Also: span (\x -> not (p x)) -> break p (lambda form)
18−useBreakRule :: Rule
19−useBreakRule = mkRule (RuleId "UseBreak") run
20− where
21− run :: RuleContext -> Module Void -> Array LintWarning
22− run ctx = foldMapModule visitor
23− where
24− imports = ctx.imports
25− visitor = defaultMonoidalVisitor { onExpr = checkExpr imports }
26−
27− checkExpr :: ImportInfo -> Expr Void -> Array LintWarning
28− checkExpr imports expr = case expr of
29− -- Match: span (not <<< p) x
30− ExprApp fn args
31− | isSpan imports fn ->
32− case NEA.toArray args of
33− [AppTerm predArg, AppTerm xArg] ->
34− case getNotComposedPred imports predArg of
35− Just p ->
36− let
37− pText = printExpr p
38− x = printExpr xArg
39− in
40− [ LintWarning
41− { ruleId: RuleId "UseBreak"
42− , message: WarningMessage "span (not <<< p) can be simplified to break p"
43− , range: rangeOf expr
44− , severity: Hint
45− , suggestion: Just $ Suggestion
46− { replacement: ReplacementText ("break " <> pText <> " " <> x)
47− , description: SuggestionDescription "Use break instead of span with negated predicate"
48− }
49− }
50− ]
51− Nothing -> []
52− [AppTerm predArg] ->
53− case getNotComposedPred imports predArg of
54− Just p ->
55− let pText = printExpr p
56− in
57− [ LintWarning
58− { ruleId: RuleId "UseBreak"
59− , message: WarningMessage "span (not <<< p) can be simplified to break p"
60− , range: rangeOf expr
61− , severity: Hint
62− , suggestion: Just $ Suggestion
63− { replacement: ReplacementText ("break " <> pText)
64− , description: SuggestionDescription "Use break instead of span with negated predicate"
65− }
66− }
67− ]
68− Nothing -> []
69− _ -> []
70− _ -> []
71−
72− unwrapParens :: Expr Void -> Expr Void
73− unwrapParens (ExprParens (Wrapped { value })) = unwrapParens value
74− unwrapParens e = e
75−
76− isSpan :: ImportInfo -> Expr Void -> Boolean
77− isSpan imports (ExprIdent (QualifiedName { name: Ident name })) =
78− name == "span" && hasValue imports "span"
79− isSpan _ _ = false
80−
81− isNot :: ImportInfo -> Expr Void -> Boolean
82− isNot imports (ExprIdent (QualifiedName { name: Ident name })) =
83− name == "not" && hasValue imports "not"
84− isNot _ _ = false
85−
86− -- Check for (not <<< p) pattern
87− getNotComposedPred :: ImportInfo -> Expr Void -> Maybe (Expr Void)
88− getNotComposedPred imports predExpr =
89− case unwrapParens predExpr of
90− ExprApp notFn args
91− | isNot imports notFn ->
92− case NEA.toArray args of
93− [AppTerm p] -> Just p
94− _ -> Nothing
95− _ -> Nothing
lib/purelint/src/Purelint/Rules/UseCaseOf.pursdeleted−169
@@ -1,169 +0,0 @@
1−module Purelint.Rules.UseCaseOf where
2−
3−import Prelude
4−
5−import Data.Array as Array
6−import Data.Array.NonEmpty as NEA
7−import Data.Foldable (foldMap)
8−import Data.Maybe (Maybe(..))
9−import Data.Monoid (power)
10−import Data.Newtype (un)
11−import Data.String as String
12−import Data.Tuple (Tuple(..), snd)
13−import Data.Tuple.Nested ((/\))
14−import Data.Void (Void)
15−import Purelint.Print (printBinder, printExpr)
16−import Purelint.Rule (Rule, RuleContext, mkRule)
17−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
18−import PureScript.CST.Range (rangeOf)
19−import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
20−import PureScript.CST.Types (Binder(..), Declaration(..), Guarded(..), GuardedExpr(..), Ident(..), LetBinding(..), Module(..), ModuleBody(..), Name(..), ValueBindingFields, Where(..))
21−
22−-- | Rule: Functions with multiple clauses where the name repeats can use case _ of
23−-- |
24−-- | foo x = expr1
25−-- | foo y = expr2
26−-- |
27−-- | becomes:
28−-- |
29−-- | foo = case _ of
30−-- | x -> expr1
31−-- | y -> expr2
32−useCaseOfRule :: Rule
33−useCaseOfRule = mkRule (RuleId "UseCaseOf") run
34− where
35− run :: RuleContext -> Module Void -> Array LintWarning
36− run _ctx mod@(Module m) =
37− let
38− ModuleBody body = m.body
39− topLevelWarnings = groupByName body.decls # Array.concatMap (checkGroup 0)
40− nestedWarnings = foldMapModule visitor mod
41− in topLevelWarnings <> nestedWarnings
42− where
43− visitor = defaultMonoidalVisitor { onDecl = checkDecl }
44−
45− -- Check where bindings in declarations
46− checkDecl :: Declaration Void -> Array LintWarning
47− checkDecl = case _ of
48− DeclValue { guarded } -> checkGuarded guarded
49− _ -> []
50−
51− checkGuarded :: Guarded Void -> Array LintWarning
52− checkGuarded = case _ of
53− Unconditional _ (Where { bindings: Just (Tuple _ letBindings) }) ->
54− checkLetBindings letBindings
55− Guarded guards -> foldMap checkGuardedExpr (NEA.toArray guards)
56− _ -> []
57−
58− checkGuardedExpr :: GuardedExpr Void -> Array LintWarning
59− checkGuardedExpr (GuardedExpr { where: Where { bindings: Just (Tuple _ letBindings) } }) =
60− checkLetBindings letBindings
61− checkGuardedExpr _ = []
62−
63− checkLetBindings :: NEA.NonEmptyArray (LetBinding Void) -> Array LintWarning
64− checkLetBindings bindings =
65− let
66− vbs = NEA.toArray bindings # Array.mapMaybe case _ of
67− LetBindingName vb -> Just vb
68− _ -> Nothing
69− -- Get the base indentation from first binding
70− baseCol = case Array.head vbs of
71− Just vb -> (rangeOf (LetBindingName vb)).start.column
72− Nothing -> 0
73− groups = groupByNameVB vbs
74− in groups # Array.concatMap (checkGroup baseCol)
75−
76− -- Group consecutive DeclValue with same name (for top-level)
77− groupByName :: Array (Declaration Void) -> Array (Array (ValueBindingFields Void))
78− groupByName decls = go [] Nothing decls
79− where
80− go :: Array (Array (ValueBindingFields Void)) -> Maybe { name :: String, clauses :: Array (ValueBindingFields Void) } -> Array (Declaration Void) -> Array (Array (ValueBindingFields Void))
81− go acc current arr = case Array.uncons arr of
82− Nothing -> case current of
83− Nothing -> acc
84− Just c -> Array.snoc acc c.clauses
85− Just { head: d, tail: rest } -> case d of
86− DeclValue vb ->
87− let name = getName vb
88− in case current of
89− Nothing -> go acc (Just { name, clauses: [vb] }) rest
90− Just c | c.name == name -> go acc (Just { name, clauses: Array.snoc c.clauses vb }) rest
91− Just c -> go (Array.snoc acc c.clauses) (Just { name, clauses: [vb] }) rest
92− _ -> case current of
93− Nothing -> go acc Nothing rest
94− Just c -> go (Array.snoc acc c.clauses) Nothing rest
95−
96− -- Group consecutive ValueBindingFields with same name (for where bindings)
97− groupByNameVB :: Array (ValueBindingFields Void) -> Array (Array (ValueBindingFields Void))
98− groupByNameVB vbs = go [] Nothing vbs
99− where
100− go :: Array (Array (ValueBindingFields Void)) -> Maybe { name :: String, clauses :: Array (ValueBindingFields Void) } -> Array (ValueBindingFields Void) -> Array (Array (ValueBindingFields Void))
101− go acc current arr = case Array.uncons arr of
102− Nothing -> case current of
103− Nothing -> acc
104− Just c -> Array.snoc acc c.clauses
105− Just { head: vb, tail: rest } ->
106− let name = getName vb
107− in case current of
108− Nothing -> go acc (Just { name, clauses: [vb] }) rest
109− Just c | c.name == name -> go acc (Just { name, clauses: Array.snoc c.clauses vb }) rest
110− Just c -> go (Array.snoc acc c.clauses) (Just { name, clauses: [vb] }) rest
111−
112− getName :: ValueBindingFields Void -> String
113− getName vb = case vb.name of
114− Name { name: Ident n } -> n
115−
116− checkGroup :: Int -> Array (ValueBindingFields Void) -> Array LintWarning
117− checkGroup baseCol clauses
118− | Array.length clauses < 2 = []
119− | not (Array.all hasBindersAndUnconditional clauses) = []
120− | otherwise = case Array.head clauses of
121− Nothing -> []
122− Just first ->
123− let
124− name = getName first
125− numArgs = Array.length first.binders
126− -- Check all clauses have same number of binders
127− sameBinders = Array.all (\vb -> Array.length vb.binders == numArgs) clauses
128− in
129− if not sameBinders || numArgs == 0 then []
130− else
131− let
132− -- Build the replacement
133− indent = power " " baseCol
134− wildcards = Array.replicate numArgs "_" # String.joinWith ", "
135− caseHeader = name <> " = case " <> wildcards <> " of"
136− caseArms = clauses # Array.mapMaybe formatClause
137− replacement = caseHeader <> "\n" <> (caseArms # map ((indent <> " ") <> _) # String.joinWith "\n")
138− -- Range spans from first clause to last
139− firstRange = rangeOf (LetBindingName first)
140− lastRange = case Array.last clauses of
141− Just lst -> rangeOf (LetBindingName lst)
142− Nothing -> firstRange
143− fullRange = { start: firstRange.start, end: lastRange.end }
144− in
145− [ LintWarning
146− { ruleId: RuleId "UseCaseOf"
147− , message: WarningMessage $ "Function '" <> name <> "' has " <> show (Array.length clauses) <> " clauses - consider using case _ of"
148− , range: fullRange
149− , severity: Refactor
150− , suggestion: Just $ Suggestion
151− { replacement: ReplacementText replacement
152− , description: SuggestionDescription "Convert to case _ of"
153− }
154− }
155− ]
156−
157− hasBindersAndUnconditional :: ValueBindingFields Void -> Boolean
158− hasBindersAndUnconditional vb = case vb.guarded of
159− Unconditional _ (Where { bindings: Nothing }) -> Array.length vb.binders > 0
160− _ -> false
161−
162− formatClause :: ValueBindingFields Void -> Maybe String
163− formatClause vb = case vb.guarded of
164− Unconditional _ (Where { expr, bindings: Nothing }) ->
165− let
166− binders = vb.binders # map printBinder # String.joinWith ", "
167− body = printExpr expr
168− in Just $ binders <> " -> " <> body
169− _ -> Nothing
lib/purelint/src/Purelint/Rules/UseConst.pursdeleted−67
@@ -1,67 +0,0 @@
1−module Purelint.Rules.UseConst where
2−
3−import Prelude
4−
5−import Data.Array as Array
6−import Data.Array.NonEmpty as NEA
7−import Data.Maybe (Maybe(..))
8−import Data.Tuple (Tuple(..))
9−import Data.Void (Void)
10−import Purelint.Imports (ImportInfo, hasValue)
11−import Purelint.Print (printExpr)
12−import Purelint.Rule (Rule, RuleContext, mkRule)
13−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
14−import PureScript.CST.Range (rangeOf)
15−import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
16−import PureScript.CST.Types (AppSpine(..), Binder(..), Expr(..), Ident(..), Module, Name(..), QualifiedName(..), Wrapped(..))
17−
18−-- | Rule: \x -> y (where y doesn't use x) -> const y
19−-- | Only triggers when the binder is a simple unused variable
20−useConstRule :: Rule
21−useConstRule = mkRule (RuleId "UseConst") run
22− where
23− run :: RuleContext -> Module Void -> Array LintWarning
24− run ctx = foldMapModule visitor
25− where
26− imports = ctx.imports
27− visitor = defaultMonoidalVisitor { onExpr = checkExpr imports }
28−
29− checkExpr :: ImportInfo -> Expr Void -> Array LintWarning
30− checkExpr imports expr = case expr of
31− ExprLambda lambda | hasValue imports "const" ->
32− case NEA.toArray lambda.binders of
33− [BinderVar (Name { name: Ident paramName })] ->
34− -- Check if body doesn't mention the parameter
35− if not (mentionsVar paramName lambda.body) then
36− let bodyText = printExpr lambda.body
37− in
38− [ LintWarning
39− { ruleId: RuleId "UseConst"
40− , message: WarningMessage "Lambda ignores its argument"
41− , range: rangeOf expr
42− , severity: Hint
43− , suggestion: Just $ Suggestion
44− { replacement: ReplacementText ("const " <> bodyText)
45− , description: SuggestionDescription "\\x -> y (where y doesn't use x) can be simplified to const y"
46− }
47− }
48− ]
49− else []
50− _ -> []
51− _ -> []
52−
53− -- Simple check if variable name appears in expression
54− mentionsVar :: String -> Expr Void -> Boolean
55− mentionsVar name (ExprIdent (QualifiedName { name: Ident n })) = n == name
56− mentionsVar name (ExprApp fn args) =
57− mentionsVar name fn || Array.any (mentionsVarInSpine name) (NEA.toArray args)
58− mentionsVar name (ExprLambda l) = mentionsVar name l.body
59− mentionsVar name (ExprIf i) = mentionsVar name i.cond || mentionsVar name i.true || mentionsVar name i.false
60− mentionsVar name (ExprOp lhs ops) =
61− mentionsVar name lhs || Array.any (\(Tuple _ e) -> mentionsVar name e) (NEA.toArray ops)
62− mentionsVar name (ExprParens (Wrapped { value })) = mentionsVar name value
63− mentionsVar _ _ = false
64−
65− mentionsVarInSpine :: String -> AppSpine Expr Void -> Boolean
66− mentionsVarInSpine name (AppTerm e) = mentionsVar name e
67− mentionsVarInSpine _ _ = false
lib/purelint/src/Purelint/Rules/UseEitherMap.pursdeleted−112
@@ -1,112 +0,0 @@
1−module Purelint.Rules.UseEitherMap where
2−
3−import Prelude
4−
5−import Data.Array.NonEmpty as NEA
6−import Data.Maybe (Maybe(..))
7−import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
12−import PureScript.CST.Range (rangeOf)
13−import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
14−import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, Operator(..), QualifiedName(..), Wrapped(..))
15−import Data.Tuple (Tuple(..))
16−
17−-- | Rule: either Left (Right <<< f) -> map f
18−useEitherMapRule :: Rule
19−useEitherMapRule = mkRule (RuleId "UseEitherMap") run
20− where
21− run :: RuleContext -> Module Void -> Array LintWarning
22− run ctx = foldMapModule visitor
23− where
24− imports = ctx.imports
25− visitor = defaultMonoidalVisitor { onExpr = checkExpr imports }
26−
27− checkExpr :: ImportInfo -> Expr Void -> Array LintWarning
28− checkExpr imports expr = case expr of
29− -- Match: either Left (Right <<< f)
30− ExprApp fn args
31− | isEither imports fn ->
32− case NEA.toArray args of
33− [AppTerm firstArg, AppTerm secondArg] ->
34− if isLeft imports firstArg then
35− case getRightComposed imports secondArg of
36− Just f ->
37− let fText = printExpr f
38− in
39− [ LintWarning
40− { ruleId: RuleId "UseEitherMap"
41− , message: WarningMessage "either Left (Right <<< f) can be simplified to map f"
42− , range: rangeOf expr
43− , severity: Warning
44− , suggestion: Just $ Suggestion
45− { replacement: ReplacementText ("map " <> fText)
46− , description: SuggestionDescription "Use map instead of either Left (Right <<< f)"
47− }
48− }
49− ]
50− Nothing -> []
51− else []
52− [AppTerm firstArg, AppTerm secondArg, AppTerm xArg] ->
53− if isLeft imports firstArg then
54− case getRightComposed imports secondArg of
55− Just f ->
56− let
57− fText = printExpr f
58− x = printExpr xArg
59− in
60− [ LintWarning
61− { ruleId: RuleId "UseEitherMap"
62− , message: WarningMessage "either Left (Right <<< f) x can be simplified to map f x"
63− , range: rangeOf expr
64− , severity: Warning
65− , suggestion: Just $ Suggestion
66− { replacement: ReplacementText ("map " <> fText <> " " <> x)
67− , description: SuggestionDescription "Use map instead of either Left (Right <<< f)"
68− }
69− }
70− ]
71− Nothing -> []
72− else []
73− _ -> []
74− _ -> []
75−
76− unwrapParens :: Expr Void -> Expr Void
77− unwrapParens (ExprParens (Wrapped { value })) = unwrapParens value
78− unwrapParens e = e
79−
80− isEither :: ImportInfo -> Expr Void -> Boolean
81− isEither imports (ExprIdent (QualifiedName { name: Ident name })) =
82− name == "either" && hasValue imports "either"
83− isEither _ _ = false
84−
85− isLeft :: ImportInfo -> Expr Void -> Boolean
86− isLeft imports e =
87− case unwrapParens e of
88− ExprIdent (QualifiedName { name: Ident name }) ->
89− name == "Left" && hasValue imports "Left"
90− ExprConstructor (QualifiedName { name: _ }) -> true -- Allow constructor syntax
91− _ -> false
92−
93− isRight :: ImportInfo -> Expr Void -> Boolean
94− isRight imports e =
95− case unwrapParens e of
96− ExprIdent (QualifiedName { name: Ident name }) ->
97− name == "Right" && hasValue imports "Right"
98− ExprConstructor (QualifiedName { name: _ }) -> true
99− _ -> false
100−
101− -- Check for (Right <<< f) pattern
102− getRightComposed :: ImportInfo -> Expr Void -> Maybe (Expr Void)
103− getRightComposed imports e =
104− case unwrapParens e of
105− ExprOp lhs ops ->
106− case NEA.toArray ops of
107− [Tuple (QualifiedName { name: Operator "<<<" }) rhs]
108− | isRight imports lhs -> Just rhs
109− [Tuple (QualifiedName { name: Operator ">>>" }) rhs]
110− | isRight imports rhs -> Just lhs
111− _ -> Nothing
112− _ -> Nothing
lib/purelint/src/Purelint/Rules/UseFoldMap.pursdeleted−108
@@ -1,108 +0,0 @@
1−module Purelint.Rules.UseFoldMap where
2−
3−import Prelude
4−
5−import Data.Array as Array
6−import Data.Array.NonEmpty as NEA
7−import Data.Maybe (Maybe(..))
8−import Data.Tuple (Tuple, snd)
9−import Data.Tuple.Nested ((/\))
10−import Purelint.Print (printExpr)
11−import Purelint.Rule (Rule, RuleContext, mkRule)
12−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
13−import PureScript.CST.Range (rangeOf)
14−import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
15−import PureScript.CST.Types (Binder(..), Expr(..), Guarded(..), Ident(..), Module, Name(..), Proper(..), QualifiedName(..), Separated(..), Where(..), Wrapped(..), AppSpine(..))
16−
17−-- | Rule: case mx of Nothing -> mempty; Just x -> f x --> foldMap f mx
18−-- | Also: case mx of Nothing -> ""; Just x -> f x --> foldMap f mx (String is Monoid)
19−useFoldMapRule :: Rule
20−useFoldMapRule = mkRule (RuleId "UseFoldMap") run
21− where
22− run :: RuleContext -> Module Void -> Array LintWarning
23− run _ctx = foldMapModule visitor
24− where
25− visitor = defaultMonoidalVisitor { onExpr = checkExpr }
26−
27− checkExpr :: Expr Void -> Array LintWarning
28− checkExpr expr = case expr of
29− ExprCase caseOf
30− | [scrutinee] <- separatedToArray caseOf.head
31− , [branch1, branch2] <- NEA.toArray caseOf.branches
32− -> checkMaybeCase expr scrutinee branch1 branch2
33− <> checkMaybeCase expr scrutinee branch2 branch1
34− _ -> []
35−
36− checkMaybeCase expr scrutinee nothingBranch justBranch
37− | isNothingBranch nothingBranch
38− , isMemptyResult nothingBranch
39− , Just { var, body } <- getJustBranch justBranch
40− = let
41− -- Check if body is `f ... var` where last arg is our variable
42− suggestion = case body of
43− ExprApp fn args
44− | argsArr <- NEA.toArray args
45− , Just (AppTerm lastArg) <- Array.last argsArr
46− , isVar var lastArg
47− -> let
48− remainingArgs = Array.dropEnd 1 argsArr
49− fnPart = if Array.null remainingArgs
50− then wrapIfNeeded fn
51− else "(" <> printExpr fn <> " " <> Array.intercalate " " (map printAppSpine remainingArgs) <> ")"
52− in "foldMap " <> fnPart <> " " <> wrapIfNeeded scrutinee
53− -- Body uses var in a more complex way, need a lambda
54− _ -> "foldMap (\\" <> var <> " -> " <> printExpr body <> ") " <> wrapIfNeeded scrutinee
55− in
56− [ LintWarning
57− { ruleId: RuleId "UseFoldMap"
58− , message: WarningMessage "case of Nothing/Just with mempty can be simplified to foldMap"
59− , range: rangeOf expr
60− , severity: Warning
61− , suggestion: Just $ Suggestion
62− { replacement: ReplacementText suggestion
63− , description: SuggestionDescription "Use foldMap instead of case with Nothing -> mempty"
64− }
65− }
66− ]
67− | otherwise = []
68−
69− printAppSpine :: AppSpine Expr Void -> String
70− printAppSpine (AppTerm e) = printExpr e
71− printAppSpine (AppType _ _) = "@_"
72−
73− isNothingBranch branch = case branch of
74− (Separated { head: BinderConstructor (QualifiedName { name: Proper "Nothing" }) [] }) /\ _ -> true
75− _ -> false
76−
77− isMemptyResult branch = case branch of
78− _ /\ Unconditional _ (Where { expr: e, bindings: Nothing }) -> isMempty (unwrapParens e)
79− _ -> false
80−
81− isMempty :: Expr Void -> Boolean
82− isMempty (ExprIdent (QualifiedName { name: Ident "mempty" })) = true
83− isMempty (ExprString _ "") = true -- "" is mempty for String
84− isMempty (ExprArray (Wrapped { value: Nothing })) = true -- [] is mempty for Array
85− isMempty _ = false
86−
87− getJustBranch branch = case branch of
88− (Separated { head: BinderConstructor (QualifiedName { name: Proper "Just" }) [BinderVar (Name { name: Ident var })] })
89− /\ Unconditional _ (Where { expr: body, bindings: Nothing })
90− -> Just { var, body }
91− _ -> Nothing
92−
93− isVar :: String -> Expr Void -> Boolean
94− isVar name (ExprIdent (QualifiedName { name: Ident n })) = n == name
95− isVar _ _ = false
96−
97− unwrapParens :: Expr Void -> Expr Void
98− unwrapParens (ExprParens (Wrapped { value })) = unwrapParens value
99− unwrapParens e = e
100−
101− wrapIfNeeded :: Expr Void -> String
102− wrapIfNeeded e = case e of
103− ExprIdent _ -> printExpr e
104− ExprParens _ -> printExpr e
105− _ -> "(" <> printExpr e <> ")"
106−
107− separatedToArray :: forall a. Separated a -> Array a
108− separatedToArray (Separated { head, tail }) = Array.cons head (map snd tail)
lib/purelint/spago.yaml → lib/purslint/spago.yamlrenamed+1−1
@@ -1,5 +1,5 @@
11package:
2− name: purelint
2+ name: purslint
33 dependencies:
44 - arrays
55 - effect
lib/purelint/src/Purelint/Config.purs → lib/purslint/src/Purslint/Config.pursrenamed+2−2
@@ -1,11 +1,11 @@
1−module Purelint.Config where
1+module Purslint.Config where
22
33import Prelude
44
55import Data.Array (notElem)
66import Data.Array as Array
77import Data.String as String
8−import Purelint.Types (RuleId(..))
8+import Purslint.Types (RuleId(..))
99
1010-- | Parsed config - just enabled/disabled rules for now
1111type Config =
lib/purelint/src/Purelint/Fix.purs → lib/purslint/src/Purslint/Fix.pursrenamed+47−11
@@ -1,15 +1,17 @@
1−module Purelint.Fix where
1+module Purslint.Fix where
22
33import Prelude
44
55import Data.Array as Array
66import Data.Foldable (foldMap)
77import Data.Maybe (Maybe(..))
8+import Data.Monoid (power)
89import Data.Newtype (un, unwrap)
910import Data.String as String
1011import Data.String.CodeUnits as StringCU
1112import PureScript.CST.Types (SourceRange)
12−import Purelint.Types (LintResult(..), LintWarning(..), ReplacementText(..), Suggestion(..))
13+import Purslint.Types (LintResult(..), LintWarning(..), ReplacementText(..), Suggestion(..))
14+import Purslint.ImportFix (processImports)
1315
1416-- | Extract text at a source range from source code
1517extractRange :: String -> SourceRange -> String
@@ -69,25 +71,59 @@ applyFix :: String -> LintWarning -> Maybe String
6971applyFix source (LintWarning w) = do
7072 Suggestion s <- w.suggestion
7173 let replacement = s.replacement # un ReplacementText
72− Just $ replaceRange source w.range replacement
74+ let
75+ { source: updatedSource, replacement: updatedReplacement, linesAdded } =
76+ processImports source replacement s.requiredImports
77+ let shiftedRange = shiftRange linesAdded w.range
78+ Just $ replaceRange updatedSource shiftedRange updatedReplacement
7379
7480-- | Apply all fixes to source code (in reverse order to preserve ranges)
81+-- | Skips overlapping fixes to avoid corruption
7582applyAllFixes :: String -> LintResult -> String
7683applyAllFixes source (LintResult result) =
7784 -- Sort warnings by range in reverse order (end of file first)
78− Array.foldl applyFixIfPossible source sortedWarnings
85+ -- Then fold, tracking applied ranges to skip overlaps
86+ _.source $ Array.foldl applyFixIfPossible { source, appliedRanges: [] } sortedWarnings
7987 where
8088 sortedWarnings = result.warnings # Array.sortBy compareRangesReverse
8189
8290 compareRangesReverse :: LintWarning -> LintWarning -> Ordering
8391 compareRangesReverse (LintWarning a) (LintWarning b) =
8492 -- Compare in reverse: later positions first
85− case a.range.start.line # compare b.range.start.line of
86− EQ -> a.range.start.column # compare b.range.start.column
93+ case compare b.range.start.line a.range.start.line of
94+ EQ -> compare b.range.start.column a.range.start.column
8795 other -> other
8896
89− applyFixIfPossible :: String -> LintWarning -> String
90− applyFixIfPossible src warning =
91− case warning # applyFix src of
92− Nothing -> src
93− Just fixed -> fixed
97+ applyFixIfPossible :: { source :: String, appliedRanges :: Array SourceRange } -> LintWarning -> { source :: String, appliedRanges :: Array SourceRange }
98+ applyFixIfPossible acc (LintWarning w) =
99+ -- Skip if this warning overlaps with any already-applied range
100+ if Array.any (overlaps w.range) acc.appliedRanges then
101+ acc
102+ else case w.suggestion of
103+ Nothing -> acc
104+ Just (Suggestion s) ->
105+ let
106+ replacement = s.replacement # un ReplacementText
107+ importResult = processImports acc.source replacement s.requiredImports
108+ shiftedRange = shiftRange importResult.linesAdded w.range
109+ updatedSource = replaceRange importResult.source shiftedRange importResult.replacement
110+ in
111+ { source: updatedSource
112+ , appliedRanges: Array.cons shiftedRange acc.appliedRanges
113+ }
114+
115+ -- Check if two ranges overlap
116+ overlaps :: SourceRange -> SourceRange -> Boolean
117+ overlaps a b =
118+ not (aEndsBeforeB || bEndsBeforeA)
119+ where
120+ aEndsBeforeB = a.end.line < b.start.line || (a.end.line == b.start.line && a.end.column <= b.start.column)
121+ bEndsBeforeA = b.end.line < a.start.line || (b.end.line == a.start.line && b.end.column <= b.start.column)
122+
123+shiftRange :: Int -> SourceRange -> SourceRange
124+shiftRange lineDelta range =
125+ if lineDelta == 0 then range
126+ else
127+ { start: range.start { line = range.start.line + lineDelta }
128+ , end: range.end { line = range.end.line + lineDelta }
129+ }
lib/purslint/src/Purslint/ImportFix.pursnew file+17
@@ -0,0 +1,17 @@
1+module Purslint.ImportFix where
2+
3+import Data.Maybe (Maybe)
4+-- NOTE: Import processing is currently a no-op. We assume required imports
5+-- are already present in the source and keep replacements intact.
6+processImports :: String -> String -> Array
7+ { codeText :: Maybe String
8+ , importItem :: Maybe String
9+ , moduleName :: String
10+ , qualifier :: Maybe String
11+ }
12+ -> { linesAdded :: Int
13+ , replacement :: String
14+ , source :: String
15+ }
16+processImports source replacement _imports =
17+ { linesAdded: 0, replacement, source }
lib/purelint/src/Purelint/Imports.purs → lib/purslint/src/Purslint/Imports.pursrenamed+1−1
@@ -1,4 +1,4 @@
1−module Purelint.Imports where
1+module Purslint.Imports where
22
33import Prelude
44
lib/purelint/src/Purelint/Print.purs → lib/purslint/src/Purslint/Print.pursrenamed+8−2
@@ -1,4 +1,4 @@
1−module Purelint.Print where
1+module Purslint.Print where
22
33import Prelude
44
@@ -13,7 +13,7 @@ import Data.Void (Void)
1313import PureScript.CST.Print (printToken, printSourceToken)
1414import PureScript.CST.Range (tokensOf)
1515import PureScript.CST.Range.TokenList as TokenList
16−import PureScript.CST.Types (Binder, Expr, SourceToken, Token(..))
16+import PureScript.CST.Types (Binder, Expr, SourceToken, Token(..), Type)
1717
1818-- | Print an expression back to source code with tidy-style formatting
1919printExpr :: Expr Void -> String
@@ -37,6 +37,12 @@ printBinder binder = do
3737 let tokens = TokenList.toArray (tokensOf binder)
3838 printTokens tokens
3939
40+-- | Print a type back to source code with tidy-style formatting
41+printType :: Type Void -> String
42+printType typ = do
43+ let tokens = TokenList.toArray (tokensOf typ)
44+ printTokens tokens
45+
4046-- | Print tokens with tidy-style spacing
4147printTokens :: Array SourceToken -> String
4248printTokens tokens =
lib/purelint/src/Purelint/Rule.purs → lib/purslint/src/Purslint/Rule.pursrenamed+3−3
@@ -1,9 +1,9 @@
1−module Purelint.Rule where
1+module Purslint.Rule where
22
33import Prelude
44
5−import Purelint.Imports (ImportInfo)
6−import Purelint.Types (LintWarning, RuleId)
5+import Purslint.Imports (ImportInfo)
6+import Purslint.Types (LintWarning, RuleId)
77import PureScript.CST.Types (Module)
88import Data.Void (Void)
99
lib/purelint/src/Purelint/Rules/AlternativeLaw.purs → lib/purslint/src/Purslint/Rules/AlternativeLaw.pursrenamed+8−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.AlternativeLaw where
1+module Purslint.Rules.AlternativeLaw where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Tuple (Tuple(..))
8−import Purelint.Imports (ImportInfo, hasValue, hasOp)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue, hasOp)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (Expr(..), Ident(..), Module, Operator(..), QualifiedName(..), Proper(..), Wrapped(..))
@@ -46,6 +46,7 @@ alternativeLawRule = mkRule (RuleId "AlternativeLaw") run
4646 , suggestion: Just $ Suggestion
4747 { replacement: ReplacementText (printExpr rhs)
4848 , description: SuggestionDescription "empty is the identity for <|> (left identity)"
49+ , requiredImports: []
4950 }
5051 }
5152 ]
@@ -59,6 +60,7 @@ alternativeLawRule = mkRule (RuleId "AlternativeLaw") run
5960 , suggestion: Just $ Suggestion
6061 { replacement: ReplacementText (printExpr lhs)
6162 , description: SuggestionDescription "empty is the identity for <|> (right identity)"
63+ , requiredImports: []
6264 }
6365 }
6466 ]
@@ -72,3 +74,4 @@ alternativeLawRule = mkRule (RuleId "AlternativeLaw") run
7274 hasValue imports "Nothing"
7375 isEmpty _ (ExprArray (Wrapped { value: Nothing })) = true -- []
7476 isEmpty _ _ = false
77+
lib/purelint/src/Purelint/Rules/BooleanSimplify.purs → lib/purslint/src/Purslint/Rules/BooleanSimplify.pursrenamed+7−5
@@ -1,4 +1,4 @@
1−module Purelint.Rules.BooleanSimplify where
1+module Purslint.Rules.BooleanSimplify where
22
33import Prelude
44
@@ -6,10 +6,10 @@ import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Tuple (Tuple(..))
88import Data.Void (Void)
9−import Purelint.Imports (ImportInfo, hasOp)
10−import Purelint.Print (printExpr)
11−import Purelint.Rule (Rule, RuleContext, mkRule)
12−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
9+import Purslint.Imports (ImportInfo, hasOp)
10+import Purslint.Print (printExpr)
11+import Purslint.Rule (Rule, RuleContext, mkRule)
12+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1313import PureScript.CST.Range (rangeOf)
1414import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1515import PureScript.CST.Types (Expr(..), Module, Operator(..), QualifiedName(..))
@@ -73,6 +73,7 @@ booleanSimplifyRule = run # mkRule (RuleId "BooleanSimplify")
7373 , suggestion: Just $ Suggestion
7474 { replacement: ReplacementText replacement
7575 , description: SuggestionDescription $ from <> " can be simplified to " <> to
76+ , requiredImports: []
7677 }
7778 }
7879
@@ -82,3 +83,4 @@ booleanSimplifyRule = run # mkRule (RuleId "BooleanSimplify")
8283 isBoolLit :: Boolean -> Expr Void -> Boolean
8384 isBoolLit expected (ExprBoolean _ val) = val == expected
8485 isBoolLit _ _ = false
86+
lib/purelint/src/Purelint/Rules/CollapseLambdas.purs → lib/purslint/src/Purslint/Rules/CollapseLambdas.pursrenamed+6−4
@@ -1,4 +1,4 @@
1−module Purelint.Rules.CollapseLambdas where
1+module Purslint.Rules.CollapseLambdas where
22
33import Prelude
44
@@ -6,9 +6,9 @@ import Data.Array as Array
66import Data.Array.NonEmpty as NEA
77import Data.Maybe (Maybe(..))
88import Data.String as String
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (Binder(..), Expr(..), Ident(..), Module, Name(..))
@@ -43,6 +43,7 @@ collapseLambdasRule = run # mkRule (RuleId "CollapseLambdas")
4343 , suggestion: Just $ Suggestion
4444 { replacement: ReplacementText replacement
4545 , description: SuggestionDescription "\\x -> \\y -> body can be simplified to \\x y -> body"
46+ , requiredImports: []
4647 }
4748 }
4849 ]
@@ -52,3 +53,4 @@ collapseLambdasRule = run # mkRule (RuleId "CollapseLambdas")
5253 getBinderName :: Binder Void -> Maybe String
5354 getBinderName (BinderVar (Name { name: Ident n })) = Just n
5455 getBinderName _ = Nothing
56+
lib/purelint/src/Purelint/Rules/ConcatMap.purs → lib/purslint/src/Purslint/Rules/ConcatMap.pursrenamed+7−5
@@ -1,13 +1,13 @@
1−module Purelint.Rules.ConcatMap where
1+module Purslint.Rules.ConcatMap where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
7−import Purelint.Imports (ImportInfo, hasValue)
8−import Purelint.Print (printExpr)
9−import Purelint.Rule (Rule, RuleContext, mkRule)
10−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
7+import Purslint.Imports (ImportInfo, hasValue)
8+import Purslint.Print (printExpr)
9+import Purslint.Rule (Rule, RuleContext, mkRule)
10+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1111import PureScript.CST.Range (rangeOf)
1212import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1313import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -58,6 +58,7 @@ concatMapRule = mkRule (RuleId "ConcatMap") run
5858 , suggestion: Just $ Suggestion
5959 { replacement: ReplacementText replacement
6060 , description: SuggestionDescription msg
61+ , requiredImports: []
6162 }
6263 }
6364 ]
@@ -77,3 +78,4 @@ concatMapRule = mkRule (RuleId "ConcatMap") run
7778
7879 getIdentName :: QualifiedName Ident -> String
7980 getIdentName (QualifiedName { name: Ident name }) = name
81+
lib/purelint/src/Purelint/Rules/EtaReduce.purs → lib/purslint/src/Purslint/Rules/EtaReduce.pursrenamed+6−4
@@ -1,13 +1,13 @@
1−module Purelint.Rules.EtaReduce where
1+module Purslint.Rules.EtaReduce where
22
33import Prelude
44
55import Data.Array as Array
66import Data.Array.NonEmpty as NEA
77import Data.Maybe (Maybe(..))
8−import Purelint.Print (printExpr)
9−import Purelint.Rule (Rule, RuleContext, mkRule)
10−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Print (printExpr)
9+import Purslint.Rule (Rule, RuleContext, mkRule)
10+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1111import PureScript.CST.Range (rangeOf)
1212import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1313import PureScript.CST.Types (AppSpine(..), Binder(..), Expr(..), Ident(..), Module, Name(..), QualifiedName(..), Wrapped(..))
@@ -63,6 +63,7 @@ etaReduceRule = mkRule (RuleId "EtaReduce") run
6363 , suggestion: Just $ Suggestion
6464 { replacement: ReplacementText fnText
6565 , description: SuggestionDescription $ "\\x -> f x can be simplified to f"
66+ , requiredImports: []
6667 }
6768 }
6869 ]
@@ -89,3 +90,4 @@ etaReduceRule = mkRule (RuleId "EtaReduce") run
8990 checkArg = case _, _ of
9091 name, (AppTerm e) -> e # mentionsParam name
9192 _, _ -> false
93+
lib/purelint/src/Purelint/Rules/EtaReduceDecl.purs → lib/purslint/src/Purslint/Rules/EtaReduceDecl.pursrenamed+6−4
@@ -1,4 +1,4 @@
1−module Purelint.Rules.EtaReduceDecl where
1+module Purslint.Rules.EtaReduceDecl where
22
33import Prelude
44
@@ -7,9 +7,9 @@ import Data.Array.NonEmpty as NEA
77import Data.Maybe (Maybe(..))
88import Data.String as String
99import Data.Void (Void)
10−import Purelint.Print (printExpr)
11−import Purelint.Rule (Rule, RuleContext, mkRule)
12−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
10+import Purslint.Print (printExpr)
11+import Purslint.Rule (Rule, RuleContext, mkRule)
12+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1313import PureScript.CST.Range (rangeOf)
1414import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1515import PureScript.CST.Types as CST
@@ -59,6 +59,7 @@ etaReduceDeclRule = mkRule (RuleId "EtaReduceDecl") run
5959 , suggestion: Just $ Suggestion
6060 { replacement: ReplacementText ("= " <> newRhs)
6161 , description: SuggestionDescription "Remove redundant parameter"
62+ , requiredImports: []
6263 }
6364 }
6465 ]
@@ -87,3 +88,4 @@ etaReduceDeclRule = mkRule (RuleId "EtaReduceDecl") run
8788 checkArg :: String -> AppSpine Expr Void -> Boolean
8889 checkArg name (AppTerm e) = mentionsParam name e
8990 checkArg _ _ = false
91+
lib/purelint/src/Purelint/Rules/EvaluateBool.purs → lib/purslint/src/Purslint/Rules/EvaluateBool.pursrenamed+7−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.EvaluateBool where
1+module Purslint.Rules.EvaluateBool where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Tuple (Tuple(..))
8−import Purelint.Imports (ImportInfo, hasOp)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasOp)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (Expr(..), Module, Operator(..), QualifiedName(..))
@@ -83,6 +83,8 @@ evaluateBoolRule = run # mkRule (RuleId "EvaluateBool")
8383 , suggestion: Just $ Suggestion
8484 { replacement: ReplacementText replacement
8585 , description: SuggestionDescription $ "Simplifies to " <> desc
86+ , requiredImports: []
8687 }
8788 }
8889 ]
90+
lib/purelint/src/Purelint/Rules/EvaluateConst.purs → lib/purslint/src/Purslint/Rules/EvaluateConst.pursrenamed+7−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.EvaluateConst where
1+module Purslint.Rules.EvaluateConst where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..))
@@ -40,6 +40,7 @@ evaluateConstRule = mkRule (RuleId "EvaluateConst") run
4040 , suggestion: Just $ Suggestion
4141 { replacement: ReplacementText x
4242 , description: SuggestionDescription "Evaluate const"
43+ , requiredImports: []
4344 }
4445 }
4546 ]
@@ -50,3 +51,4 @@ evaluateConstRule = mkRule (RuleId "EvaluateConst") run
5051 isConst imports (ExprIdent (QualifiedName { name: Ident name })) =
5152 name == "const" && hasValue imports "const"
5253 isConst _ _ = false
54+
lib/purelint/src/Purelint/Rules/EvaluateEither.purs → lib/purslint/src/Purslint/Rules/EvaluateEither.pursrenamed+8−5
@@ -1,13 +1,13 @@
1−module Purelint.Rules.EvaluateEither where
1+module Purslint.Rules.EvaluateEither where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
7−import Purelint.Imports (ImportInfo, hasValue)
8−import Purelint.Print (printExpr)
9−import Purelint.Rule (Rule, RuleContext, mkRule)
10−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
7+import Purslint.Imports (ImportInfo, hasValue)
8+import Purslint.Print (printExpr)
9+import Purslint.Rule (Rule, RuleContext, mkRule)
10+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1111import PureScript.CST.Range (rangeOf)
1212import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1313import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Proper(..), Wrapped(..))
@@ -50,6 +50,7 @@ evaluateEitherRule = mkRule (RuleId "EvaluateEither") run
5050 , suggestion: Just $ Suggestion
5151 { replacement: ReplacementText replacement
5252 , description: SuggestionDescription "Apply f directly to x"
53+ , requiredImports: []
5354 }
5455 }
5556 ]
@@ -67,6 +68,7 @@ evaluateEitherRule = mkRule (RuleId "EvaluateEither") run
6768 , suggestion: Just $ Suggestion
6869 { replacement: ReplacementText replacement
6970 , description: SuggestionDescription "Apply g directly to y"
71+ , requiredImports: []
7072 }
7173 }
7274 ]
@@ -117,3 +119,4 @@ evaluateEitherRule = mkRule (RuleId "EvaluateEither") run
117119 ExprRecord _ -> printExpr e
118120 ExprParens _ -> printExpr e
119121 _ -> "(" <> printExpr e <> ")"
122+
lib/purelint/src/Purelint/Rules/EvaluateFst.purs → lib/purslint/src/Purslint/Rules/EvaluateFst.pursrenamed+8−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.EvaluateFst where
1+module Purslint.Rules.EvaluateFst where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, Proper(..), QualifiedName(..), Wrapped(..))
@@ -43,6 +43,7 @@ evaluateFstRule = mkRule (RuleId "EvaluateFst") run
4343 , suggestion: Just $ Suggestion
4444 { replacement: ReplacementText x
4545 , description: SuggestionDescription "Extract first element directly"
46+ , requiredImports: []
4647 }
4748 }
4849 ]
@@ -63,6 +64,7 @@ evaluateFstRule = mkRule (RuleId "EvaluateFst") run
6364 , suggestion: Just $ Suggestion
6465 { replacement: ReplacementText y
6566 , description: SuggestionDescription "Extract second element directly"
67+ , requiredImports: []
6668 }
6769 }
6870 ]
@@ -94,3 +96,4 @@ evaluateFstRule = mkRule (RuleId "EvaluateFst") run
9496 _ -> Nothing
9597 _ -> Nothing
9698 getTupleElements _ = Nothing
99+
lib/purelint/src/Purelint/Rules/FlattenCase.purs → lib/purslint/src/Purslint/Rules/FlattenCase.pursrenamed+6−4
@@ -1,4 +1,4 @@
1−module Purelint.Rules.FlattenCase where
1+module Purslint.Rules.FlattenCase where
22
33import Prelude
44
@@ -8,9 +8,9 @@ import Data.Maybe (Maybe(..))
88import Data.String as String
99import Data.Tuple (Tuple(..))
1010import Data.Void (Void)
11−import Purelint.Print (printBinder, printExpr)
12−import Purelint.Rule (Rule, RuleContext, mkRule)
13−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
11+import Purslint.Print (printBinder, printExpr)
12+import Purslint.Rule (Rule, RuleContext, mkRule)
13+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1414import PureScript.CST.Range (rangeOf)
1515import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1616import PureScript.CST.Types (Binder(..), Expr(..), Guarded(..), Module, Separated(..), Where(..), Wrapped(..))
@@ -56,6 +56,7 @@ flattenCaseRule = mkRule (RuleId "FlattenCase") run
5656 , suggestion: Just $ Suggestion
5757 { replacement: ReplacementText replacement
5858 , description: SuggestionDescription $ "Use case with " <> show depth <> " scrutinees instead of nested cases"
59+ , requiredImports: []
5960 }
6061 }
6162 ]
@@ -141,3 +142,4 @@ flattenCaseRule = mkRule (RuleId "FlattenCase") run
141142 formatArm :: { patterns :: Array String, body :: String } -> String
142143 formatArm { patterns, body } =
143144 String.joinWith ", " patterns <> " -> " <> body
145+
lib/purelint/src/Purelint/Rules/FunctorLaw.purs → lib/purslint/src/Purslint/Rules/FunctorLaw.pursrenamed+7−5
@@ -1,4 +1,4 @@
1−module Purelint.Rules.FunctorLaw where
1+module Purslint.Rules.FunctorLaw where
22
33import Prelude
44
@@ -6,10 +6,10 @@ import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Tuple (Tuple(..))
88import Data.Void (Void)
9−import Purelint.Imports (ImportInfo, hasValue, hasOp)
10−import Purelint.Print (printExpr)
11−import Purelint.Rule (Rule, RuleContext, mkRule)
12−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
9+import Purslint.Imports (ImportInfo, hasValue, hasOp)
10+import Purslint.Print (printExpr)
11+import Purslint.Rule (Rule, RuleContext, mkRule)
12+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1313import PureScript.CST.Range (rangeOf)
1414import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1515import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, Operator(..), QualifiedName(..), Wrapped(..))
@@ -74,6 +74,7 @@ functorLawRule = mkRule (RuleId "FunctorLaw") run
7474 , suggestion: Just $ Suggestion
7575 { replacement: ReplacementText replacement
7676 , description: SuggestionDescription $ style <> " f (" <> style <> " g x) can be fused to " <> style <> " (f <<< g) x"
77+ , requiredImports: []
7778 }
7879 }
7980 ]
@@ -90,3 +91,4 @@ functorLawRule = mkRule (RuleId "FunctorLaw") run
9091 isMapOp :: ImportInfo -> QualifiedName Operator -> Boolean
9192 isMapOp imports (QualifiedName { name: Operator op }) =
9293 op == "<$>" && hasOp imports "<$>"
94+
lib/purelint/src/Purelint/Rules/LetToDo.purs → lib/purslint/src/Purslint/Rules/LetToDo.pursrenamed+14−5
@@ -1,4 +1,4 @@
1−module Purelint.Rules.LetToDo where
1+module Purslint.Rules.LetToDo where
22
33import Prelude
44
@@ -11,13 +11,13 @@ import Data.String (joinWith) as String
1111import Data.String as Str
1212import Data.Tuple.Nested ((/\))
1313import Data.Void (Void, absurd)
14−import Purelint.Rule (Rule, RuleContext, mkRule)
15−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
14+import Purslint.Rule (Rule, RuleContext, mkRule)
15+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1616import PureScript.CST.Print (printToken)
1717import PureScript.CST.Range (rangeOf, tokensOf)
1818import PureScript.CST.Range.TokenList as TokenList
1919import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
20−import PureScript.CST.Types (Comment(..), Declaration(..), Expr(..), Guarded(..), LetBinding, LineFeed, Module, SourceToken, Where(..))
20+import PureScript.CST.Types (Comment(..), Declaration(..), Expr(..), Guarded(..), LetBinding, LineFeed(..), Module, SourceToken, Where(..))
2121
2222-- | Rule: let x = y in z -> do let x = y; z
2323-- | Suggests replacing let...in expressions with do notation
@@ -68,6 +68,7 @@ letToDoRule = mkRule (RuleId "LetToDo") run
6868 , suggestion: Just $ Suggestion
6969 { replacement: ReplacementText replacement
7070 , description: SuggestionDescription "Convert to do notation"
71+ , requiredImports: []
7172 }
7273 }
7374 ]
@@ -111,6 +112,7 @@ letToDoRule = mkRule (RuleId "LetToDo") run
111112 , suggestion: Just $ Suggestion
112113 { replacement: ReplacementText replacement
113114 , description: SuggestionDescription "Convert to do notation"
115+ , requiredImports: []
114116 }
115117 }
116118 ]
@@ -142,10 +144,17 @@ letToDoRule = mkRule (RuleId "LetToDo") run
142144 printLeadingComment shiftAmount = case _ of
143145 Comment str -> str
144146 Space n -> power " " (max 0 (n + shiftAmount))
145− Line _ n -> "\n" <> power " " (max 0 (n + shiftAmount))
147+ Line lf n -> foldMap printLineFeed (Array.replicate n lf)
148+
149+ printLineFeed :: LineFeed -> String
150+ printLineFeed = case _ of
151+ LF -> "\n"
152+ CRLF -> "\r\n"
146153
147154 printTrailingComment :: Comment Void -> String
148155 printTrailingComment = case _ of
149156 Comment str -> str
150157 Space n -> power " " n
151158 Line v _ -> absurd v
159+
160+
lib/purelint/src/Purelint/Rules/LetToWhere.purs → lib/purslint/src/Purslint/Rules/LetToWhere.pursrenamed+12−5
@@ -1,4 +1,4 @@
1−module Purelint.Rules.LetToWhere where
1+module Purslint.Rules.LetToWhere where
22
33import Prelude
44
@@ -10,13 +10,13 @@ import Data.Monoid (power)
1010import Data.String (joinWith) as String
1111import Data.String as Str
1212import Data.Tuple.Nested ((/\))
13−import Purelint.Rule (Rule, RuleContext, mkRule)
14−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
13+import Purslint.Rule (Rule, RuleContext, mkRule)
14+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1515import PureScript.CST.Print (printToken)
1616import PureScript.CST.Range (class RangeOf, rangeOf, tokensOf)
1717import PureScript.CST.Range.TokenList as TokenList
1818import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
19−import PureScript.CST.Types (Comment(..), Declaration(..), Expr(..), Guarded(..), LetBinding, LineFeed, Module, SourceToken, Where(..))
19+import PureScript.CST.Types (Comment(..), Declaration(..), Expr(..), Guarded(..), LetBinding, LineFeed(..), Module, SourceToken, Where(..))
2020
2121-- | Rule: let x = y in z -> z where x = y
2222-- | Suggests replacing let...in expressions with where clauses
@@ -77,6 +77,7 @@ letToWhereRule = mkRule (RuleId "LetToWhere") run
7777 , suggestion: Just $ Suggestion
7878 { replacement: ReplacementText replacement
7979 , description: SuggestionDescription "Convert to where clause"
80+ , requiredImports: []
8081 }
8182 }
8283 ]
@@ -105,7 +106,12 @@ letToWhereRule = mkRule (RuleId "LetToWhere") run
105106 printLeadingComment shiftAmount = case _ of
106107 Comment str -> str
107108 Space n -> power " " (max 0 (n + shiftAmount))
108− Line _ n -> "\n" <> power " " (max 0 (n + shiftAmount))
109+ Line lf n -> foldMap printLineFeed (Array.replicate n lf)
110+
111+ printLineFeed :: LineFeed -> String
112+ printLineFeed = case _ of
113+ LF -> "\n"
114+ CRLF -> "\r\n"
109115
110116 -- | Print trailing comment (has Void, so Line is impossible)
111117 printTrailingComment :: Comment Void -> String
@@ -113,3 +119,4 @@ letToWhereRule = mkRule (RuleId "LetToWhere") run
113119 Comment str -> str
114120 Space n -> power " " n
115121 Line v _ -> absurd v
122+
lib/purelint/src/Purelint/Rules/MapFusion.purs → lib/purslint/src/Purslint/Rules/MapFusion.pursrenamed+8−5
@@ -1,4 +1,4 @@
1−module Purelint.Rules.MapFusion where
1+module Purslint.Rules.MapFusion where
22
33import Prelude
44
@@ -6,10 +6,10 @@ import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Tuple (Tuple(..))
88import Data.Void (Void)
9−import Purelint.Imports (ImportInfo, hasValue, hasOp)
10−import Purelint.Print (printExpr)
11−import Purelint.Rule (Rule, RuleContext, mkRule)
12−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
9+import Purslint.Imports (ImportInfo, hasValue, hasOp)
10+import Purslint.Print (printExpr)
11+import Purslint.Rule (Rule, RuleContext, mkRule)
12+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1313import PureScript.CST.Range (rangeOf)
1414import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1515import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, Operator(..), QualifiedName(..), Wrapped(..))
@@ -84,6 +84,7 @@ mapFusionRule = mkRule (RuleId "MapFusion") run
8484 , suggestion: Just $ Suggestion
8585 { replacement: ReplacementText replacement
8686 , description: SuggestionDescription "x <#> g <#> f can be replaced with x <#> (g >>> f)"
87+ , requiredImports: []
8788 }
8889 }
8990 ]
@@ -120,6 +121,7 @@ mapFusionRule = mkRule (RuleId "MapFusion") run
120121 , suggestion: Just $ Suggestion
121122 { replacement: ReplacementText replacement
122123 , description: SuggestionDescription "Nested maps can be fused with composition"
124+ , requiredImports: []
123125 }
124126 }
125127 ]
@@ -139,3 +141,4 @@ mapFusionRule = mkRule (RuleId "MapFusion") run
139141 isFlippedMapOp :: ImportInfo -> QualifiedName Operator -> Boolean
140142 isFlippedMapOp imports (QualifiedName { name: Operator op }) =
141143 (op == "<#>" || op == "<##>") && hasOp imports "<#>"
144+
lib/purelint/src/Purelint/Rules/MapIdentity.purs → lib/purslint/src/Purslint/Rules/MapIdentity.pursrenamed+9−5
@@ -1,4 +1,4 @@
1−module Purelint.Rules.MapIdentity where
1+module Purslint.Rules.MapIdentity where
22
33import Prelude
44
@@ -7,10 +7,10 @@ import Data.Array.NonEmpty as NEA
77import Data.Maybe (Maybe(..))
88import Data.Tuple (Tuple(..))
99import Data.Void (Void)
10−import Purelint.Imports (ImportInfo, hasValue, hasOp)
11−import Purelint.Print (printExpr)
12−import Purelint.Rule (Rule, RuleContext, mkRule)
13−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
10+import Purslint.Imports (ImportInfo, hasValue, hasOp)
11+import Purslint.Print (printExpr)
12+import Purslint.Rule (Rule, RuleContext, mkRule)
13+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1414import PureScript.CST.Range (rangeOf)
1515import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1616import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, Operator(..), QualifiedName(..))
@@ -41,6 +41,7 @@ mapIdentityRule = run # mkRule (RuleId "MapIdentity")
4141 , suggestion: Just $ Suggestion
4242 { replacement: ReplacementText (printExpr xExpr)
4343 , description: SuggestionDescription "map identity x can be simplified to x"
44+ , requiredImports: []
4445 }
4546 }
4647 ]
@@ -57,6 +58,7 @@ mapIdentityRule = run # mkRule (RuleId "MapIdentity")
5758 , suggestion: Just $ Suggestion
5859 { replacement: ReplacementText (printExpr xExpr)
5960 , description: SuggestionDescription "identity <$> x can be simplified to x"
61+ , requiredImports: []
6062 }
6163 }
6264 ]
@@ -73,6 +75,7 @@ mapIdentityRule = run # mkRule (RuleId "MapIdentity")
7375 , suggestion: Just $ Suggestion
7476 { replacement: ReplacementText (printExpr lhs)
7577 , description: SuggestionDescription "x <#> identity can be simplified to x"
78+ , requiredImports: []
7679 }
7780 }
7881 ]
@@ -94,3 +97,4 @@ mapIdentityRule = run # mkRule (RuleId "MapIdentity")
9497 isIdentity imports (ExprIdent (QualifiedName { name: Ident name })) =
9598 (name == "identity" && hasValue imports "identity") || (name == "id" && hasValue imports "id")
9699 isIdentity _ _ = false
100+
lib/purelint/src/Purelint/Rules/MonadLaw.purs → lib/purslint/src/Purslint/Rules/MonadLaw.pursrenamed+8−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.MonadLaw where
1+module Purslint.Rules.MonadLaw where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Tuple (Tuple(..))
8−import Purelint.Imports (ImportInfo, hasValue, hasOp)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue, hasOp)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, Operator(..), QualifiedName(..))
@@ -52,6 +52,7 @@ monadLawRule = mkRule (RuleId "MonadLaw") run
5252 , suggestion: Just $ Suggestion
5353 { replacement: ReplacementText replacement
5454 , description: SuggestionDescription "Apply f directly to a (left identity)"
55+ , requiredImports: []
5556 }
5657 }
5758 ]
@@ -66,6 +67,7 @@ monadLawRule = mkRule (RuleId "MonadLaw") run
6667 , suggestion: Just $ Suggestion
6768 { replacement: ReplacementText (printExpr m)
6869 , description: SuggestionDescription "pure =<< m is just m (right identity)"
70+ , requiredImports: []
6971 }
7072 }
7173 ]
@@ -95,3 +97,4 @@ monadLawRule = mkRule (RuleId "MonadLaw") run
9597 ExprRecord _ -> printExpr e
9698 ExprParens _ -> printExpr e
9799 _ -> "(" <> printExpr e <> ")"
100+
lib/purelint/src/Purelint/Rules/MonoidIdentity.purs → lib/purslint/src/Purslint/Rules/MonoidIdentity.pursrenamed+8−5
@@ -1,4 +1,4 @@
1−module Purelint.Rules.MonoidIdentity where
1+module Purslint.Rules.MonoidIdentity where
22
33import Prelude
44
@@ -6,10 +6,10 @@ import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Tuple (Tuple(..))
88import Data.Void (Void)
9−import Purelint.Imports (ImportInfo, hasValue, hasOp)
10−import Purelint.Print (printExpr)
11−import Purelint.Rule (Rule, RuleContext, mkRule)
12−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
9+import Purslint.Imports (ImportInfo, hasValue, hasOp)
10+import Purslint.Print (printExpr)
11+import Purslint.Rule (Rule, RuleContext, mkRule)
12+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1313import PureScript.CST.Range (rangeOf)
1414import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1515import PureScript.CST.Types (Expr(..), Ident(..), Module, Operator(..), QualifiedName(..))
@@ -42,6 +42,7 @@ monoidIdentityRule = mkRule (RuleId "MonoidIdentity") run
4242 , suggestion: Just $ Suggestion
4343 { replacement: ReplacementText rhsText
4444 , description: SuggestionDescription "mempty <> x can be simplified to x (Monoid left identity)"
45+ , requiredImports: []
4546 }
4647 }
4748 ]
@@ -56,6 +57,7 @@ monoidIdentityRule = mkRule (RuleId "MonoidIdentity") run
5657 , suggestion: Just $ Suggestion
5758 { replacement: ReplacementText lhsText
5859 , description: SuggestionDescription "x <> mempty can be simplified to x (Monoid right identity)"
60+ , requiredImports: []
5961 }
6062 }
6163 ]
@@ -67,3 +69,4 @@ monoidIdentityRule = mkRule (RuleId "MonoidIdentity") run
6769 isMempty imports (ExprIdent (QualifiedName { name: Ident name })) =
6870 name == "mempty" && hasValue imports "mempty"
6971 isMempty _ _ = false
72+
lib/purelint/src/Purelint/Rules/NotEqual.purs → lib/purslint/src/Purslint/Rules/NotEqual.pursrenamed+7−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.NotEqual where
1+module Purslint.Rules.NotEqual where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Tuple (Tuple(..))
8−import Purelint.Imports (ImportInfo, hasValue, hasOp)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue, hasOp)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, Operator(..), QualifiedName(..), Wrapped(..))
@@ -64,6 +64,7 @@ notEqualRule = mkRule (RuleId "NotEqual") run
6464 , suggestion: Just $ Suggestion
6565 { replacement: ReplacementText replacement
6666 , description: SuggestionDescription msg
67+ , requiredImports: []
6768 }
6869 }
6970 ]
@@ -80,3 +81,4 @@ notEqualRule = mkRule (RuleId "NotEqual") run
8081
8182 isNot :: ImportInfo -> QualifiedName Ident -> Boolean
8283 isNot imports (QualifiedName { name: Ident name }) = name == "not" && hasValue imports "not"
84+
lib/purelint/src/Purelint/Rules/NothingBind.purs → lib/purslint/src/Purslint/Rules/NothingBind.pursrenamed+7−4
@@ -1,4 +1,4 @@
1−module Purelint.Rules.NothingBind where
1+module Purslint.Rules.NothingBind where
22
33import Prelude
44
@@ -6,9 +6,9 @@ import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Tuple (Tuple(..))
88import Data.Void (Void)
9−import Purelint.Imports (ImportInfo, hasOp)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
9+import Purslint.Imports (ImportInfo, hasOp)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (Expr(..), Ident(..), Module, Operator(..), Proper(..), QualifiedName(..), Wrapped(..))
@@ -40,6 +40,7 @@ nothingBindRule = mkRule (RuleId "NothingBind") run
4040 , suggestion: Just $ Suggestion
4141 { replacement: ReplacementText "Nothing"
4242 , description: SuggestionDescription "Simplify Nothing >>= f to Nothing"
43+ , requiredImports: []
4344 }
4445 }
4546 ]
@@ -54,6 +55,7 @@ nothingBindRule = mkRule (RuleId "NothingBind") run
5455 , suggestion: Just $ Suggestion
5556 { replacement: ReplacementText "Nothing"
5657 , description: SuggestionDescription "Simplify f =<< Nothing to Nothing"
58+ , requiredImports: []
5759 }
5860 }
5961 ]
@@ -69,3 +71,4 @@ nothingBindRule = mkRule (RuleId "NothingBind") run
6971 isNothing (ExprIdent (QualifiedName { name: Ident "Nothing" })) = true
7072 isNothing (ExprConstructor (QualifiedName { name: Proper "Nothing" })) = true
7173 isNothing _ = false
74+
lib/purelint/src/Purelint/Rules/RedundantBind.purs → lib/purslint/src/Purslint/Rules/RedundantBind.pursrenamed+8−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.RedundantBind where
1+module Purslint.Rules.RedundantBind where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Tuple (Tuple(..))
8−import Purelint.Imports (ImportInfo, hasValue, hasOp)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue, hasOp)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, Operator(..), QualifiedName(..))
@@ -50,6 +50,7 @@ redundantBindRule = mkRule (RuleId "RedundantBind") run
5050 , suggestion: Just $ Suggestion
5151 { replacement: ReplacementText lhsText
5252 , description: SuggestionDescription "x >>= pure can be simplified to x"
53+ , requiredImports: []
5354 }
5455 }
5556 ]
@@ -66,6 +67,7 @@ redundantBindRule = mkRule (RuleId "RedundantBind") run
6667 , suggestion: Just $ Suggestion
6768 { replacement: ReplacementText replacement
6869 , description: SuggestionDescription "Apply f directly to x"
70+ , requiredImports: []
6971 }
7072 }
7173 ]
@@ -83,3 +85,4 @@ redundantBindRule = mkRule (RuleId "RedundantBind") run
8385 [AppTerm arg] -> Just arg
8486 _ -> Nothing
8587 getPureArg _ _ = Nothing
88+
lib/purelint/src/Purelint/Rules/RedundantFlip.purs → lib/purslint/src/Purslint/Rules/RedundantFlip.pursrenamed+7−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.RedundantFlip where
1+module Purslint.Rules.RedundantFlip where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -41,6 +41,7 @@ redundantFlipRule = mkRule (RuleId "RedundantFlip") run
4141 , suggestion: Just $ Suggestion
4242 { replacement: ReplacementText fText
4343 , description: SuggestionDescription "flip (flip f) can be simplified to f"
44+ , requiredImports: []
4445 }
4546 }
4647 ]
@@ -60,3 +61,4 @@ redundantFlipRule = mkRule (RuleId "RedundantFlip") run
6061 _ -> Nothing
6162 getFlipArg imports (ExprParens (Wrapped { value })) = getFlipArg imports value
6263 getFlipArg _ _ = Nothing
64+
lib/purelint/src/Purelint/Rules/RedundantGuard.purs → lib/purslint/src/Purslint/Rules/RedundantGuard.pursrenamed+6−4
@@ -1,13 +1,13 @@
1−module Purelint.Rules.RedundantGuard where
1+module Purslint.Rules.RedundantGuard where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Rule (Rule, RuleContext, mkRule)
10−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Rule (Rule, RuleContext, mkRule)
10+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1111import PureScript.CST.Range (rangeOf)
1212import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1313import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -38,6 +38,7 @@ redundantGuardRule = mkRule (RuleId "RedundantGuard") run
3838 , suggestion: Just $ Suggestion
3939 { replacement: ReplacementText "pure unit"
4040 , description: SuggestionDescription "guard true can be replaced with pure unit"
41+ , requiredImports: []
4142 }
4243 }
4344 ]
@@ -69,3 +70,4 @@ redundantGuardRule = mkRule (RuleId "RedundantGuard") run
6970 isFalse :: Expr Void -> Boolean
7071 isFalse (ExprBoolean _ b) = not b
7172 isFalse _ = false
73+
lib/purelint/src/Purelint/Rules/RedundantId.purs → lib/purslint/src/Purslint/Rules/RedundantId.pursrenamed+11−5
@@ -1,4 +1,4 @@
1−module Purelint.Rules.RedundantId where
1+module Purslint.Rules.RedundantId where
22
33import Prelude
44
@@ -6,10 +6,10 @@ import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Tuple (Tuple(..))
88import Data.Void (Void)
9−import Purelint.Imports (ImportInfo, hasValue, hasOp)
10−import Purelint.Print (printExpr)
11−import Purelint.Rule (Rule, RuleContext, mkRule)
12−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
9+import Purslint.Imports (ImportInfo, hasValue, hasOp)
10+import Purslint.Print (printExpr)
11+import Purslint.Rule (Rule, RuleContext, mkRule)
12+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1313import PureScript.CST.Range (rangeOf)
1414import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1515import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, Operator(..), QualifiedName(..), Wrapped(..))
@@ -43,6 +43,7 @@ redundantIdRule = mkRule (RuleId "RedundantId") run
4343 , suggestion: Just $ Suggestion
4444 { replacement: ReplacementText x
4545 , description: SuggestionDescription "Remove redundant identity application"
46+ , requiredImports: []
4647 }
4748 }
4849 ]
@@ -63,6 +64,7 @@ redundantIdRule = mkRule (RuleId "RedundantId") run
6364 , suggestion: Just $ Suggestion
6465 { replacement: ReplacementText x
6566 , description: SuggestionDescription "Remove redundant identity composition"
67+ , requiredImports: []
6668 }
6769 }
6870 ]
@@ -77,6 +79,7 @@ redundantIdRule = mkRule (RuleId "RedundantId") run
7779 , suggestion: Just $ Suggestion
7880 { replacement: ReplacementText x
7981 , description: SuggestionDescription "Remove redundant identity composition"
82+ , requiredImports: []
8083 }
8184 }
8285 ]
@@ -93,6 +96,7 @@ redundantIdRule = mkRule (RuleId "RedundantId") run
9396 , suggestion: Just $ Suggestion
9497 { replacement: ReplacementText x
9598 , description: SuggestionDescription "Remove redundant identity composition"
99+ , requiredImports: []
96100 }
97101 }
98102 ]
@@ -107,6 +111,7 @@ redundantIdRule = mkRule (RuleId "RedundantId") run
107111 , suggestion: Just $ Suggestion
108112 { replacement: ReplacementText x
109113 , description: SuggestionDescription "Remove redundant identity composition"
114+ , requiredImports: []
110115 }
111116 }
112117 ]
@@ -122,3 +127,4 @@ redundantIdRule = mkRule (RuleId "RedundantId") run
122127 isIdentity imports (ExprIdent (QualifiedName { name: Ident name })) =
123128 (name == "identity" || name == "id") && hasValue imports "identity"
124129 isIdentity _ _ = false
130+
lib/purelint/src/Purelint/Rules/RedundantIf.purs → lib/purslint/src/Purslint/Rules/RedundantIf.pursrenamed+8−5
@@ -1,13 +1,13 @@
1−module Purelint.Rules.RedundantIf where
1+module Purslint.Rules.RedundantIf where
22
33import Prelude
44
55import Data.Maybe (Maybe(..))
66import Data.Void (Void)
7−import Purelint.Imports (ImportInfo, hasValue)
8−import Purelint.Print (printExpr)
9−import Purelint.Rule (Rule, RuleContext, mkRule)
10−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
7+import Purslint.Imports (ImportInfo, hasValue)
8+import Purslint.Print (printExpr)
9+import Purslint.Rule (Rule, RuleContext, mkRule)
10+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1111import PureScript.CST.Range (rangeOf)
1212import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1313import PureScript.CST.Types (Expr(..), Module)
@@ -41,6 +41,7 @@ redundantIfRule = mkRule (RuleId "RedundantIf") run
4141 , suggestion: Just $ Suggestion
4242 { replacement: ReplacementText (printExpr condExpr)
4343 , description: SuggestionDescription "if a then true else false can be simplified to a"
44+ , requiredImports: []
4445 }
4546 }
4647 ]
@@ -54,6 +55,7 @@ redundantIfRule = mkRule (RuleId "RedundantIf") run
5455 , suggestion: Just $ Suggestion
5556 { replacement: ReplacementText ("not " <> printExpr condExpr)
5657 , description: SuggestionDescription "if a then false else true can be simplified to not a"
58+ , requiredImports: []
5759 }
5860 }
5961 ]
@@ -63,3 +65,4 @@ redundantIfRule = mkRule (RuleId "RedundantIf") run
6365 isBoolLit :: Boolean -> Expr Void -> Boolean
6466 isBoolLit expected (ExprBoolean _ val) = val == expected
6567 isBoolLit _ _ = false
68+
lib/purelint/src/Purelint/Rules/RedundantNegate.purs → lib/purslint/src/Purslint/Rules/RedundantNegate.pursrenamed+7−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.RedundantNegate where
1+module Purslint.Rules.RedundantNegate where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -45,6 +45,7 @@ redundantNegateRule = mkRule (RuleId "RedundantNegate") run
4545 , suggestion: Just $ Suggestion
4646 { replacement: ReplacementText x
4747 , description: SuggestionDescription "Remove redundant double negation"
48+ , requiredImports: []
4849 }
4950 }
5051 ]
@@ -61,3 +62,4 @@ redundantNegateRule = mkRule (RuleId "RedundantNegate") run
6162 isNegate imports (ExprIdent (QualifiedName { name: Ident name })) =
6263 name == "negate" && hasValue imports "negate"
6364 isNegate _ _ = false
65+
lib/purelint/src/Purelint/Rules/RedundantNot.purs → lib/purslint/src/Purslint/Rules/RedundantNot.pursrenamed+7−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.RedundantNot where
1+module Purslint.Rules.RedundantNot where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -41,6 +41,7 @@ redundantNotRule = mkRule (RuleId "RedundantNot") run
4141 , suggestion: Just $ Suggestion
4242 { replacement: ReplacementText xText
4343 , description: SuggestionDescription "not (not x) can be simplified to x"
44+ , requiredImports: []
4445 }
4546 }
4647 ]
@@ -60,3 +61,4 @@ redundantNotRule = mkRule (RuleId "RedundantNot") run
6061 _ -> Nothing
6162 getNotArg imports (ExprParens (Wrapped { value })) = getNotArg imports value
6263 getNotArg _ _ = Nothing
64+
lib/purslint/src/Purslint/Rules/RedundantParens.pursnew file+423
@@ -0,0 +1,423 @@
1+module Purslint.Rules.RedundantParens where
2+
3+import Prelude
4+
5+import Data.Array as Array
6+import Data.Array.NonEmpty as NEA
7+import Data.Maybe (Maybe(..))
8+import Data.Tuple (snd)
9+import Data.Tuple.Nested ((/\))
10+import Purslint.Print (printBinder, printExpr, printType)
11+import Purslint.Rule (Rule, RuleContext, mkRule)
12+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
13+import PureScript.CST.Range (rangeOf)
14+import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
15+import PureScript.CST.Types (AppSpine(..), Binder(..), Declaration(..), DoStatement(..), Expr(..), Guarded(..), GuardedExpr(..), Labeled(..), LetBinding(..), Module, RecordLabeled(..), Row(..), Separated(..), Type(..), ValueBindingFields, Where(..), Wrapped(..))
16+
17+-- | Rule: Detect redundant parentheses
18+-- |
19+-- | Detects parens that don't affect parsing/semantics in various contexts:
20+-- | - Nested parens: ((x))
21+-- | - Outer parens on RHS: x = (expr)
22+-- | - Atomic expressions in function arguments
23+-- | - Atomic expressions in array/record elements
24+-- | - Atomic expressions in if/case/let branches
25+-- | - Atomic expressions in do statements
26+-- |
27+-- | NOT flagged (parens may be needed):
28+-- | - Operator precedence: (a + b) * c
29+-- | - Lambda as argument: map (\x -> x) xs
30+-- | - Sections: (_ + 1)
31+-- | - Negative as argument: f (-1)
32+-- | - Record updates: (r { a = 1 }).b
33+redundantParensRule :: Rule
34+redundantParensRule = mkRule (RuleId "RedundantParens") run
35+ where
36+ run :: RuleContext -> Module Void -> Array LintWarning
37+ run _ctx = foldMapModule visitor
38+ where
39+ visitor =
40+ { onExpr: checkExpr
41+ , onDecl: checkDecl
42+ , onBinder: checkBinder
43+ , onType: checkType
44+ }
45+
46+ -- Check declarations for outer parens on RHS
47+ checkDecl :: Declaration Void -> Array LintWarning
48+ checkDecl = case _ of
49+ DeclValue vb -> checkValueBinding vb
50+ _ -> []
51+
52+ checkValueBinding :: ValueBindingFields Void -> Array LintWarning
53+ checkValueBinding vb = case vb.guarded of
54+ Unconditional _ (Where { expr, bindings: Nothing }) ->
55+ checkOuterParens expr "Redundant parentheses around expression"
56+ Unconditional _ (Where { expr, bindings: Just _ }) ->
57+ -- Has where bindings, still check the expr
58+ checkOuterParens expr "Redundant parentheses around expression"
59+ Guarded guardedExprs ->
60+ NEA.toArray guardedExprs # Array.concatMap \(GuardedExpr ge) ->
61+ case ge.where of
62+ Where { expr } -> checkOuterParens expr "Redundant parentheses around guarded expression"
63+
64+ -- Check if outer expression is wrapped in redundant parens
65+ -- Only flag when inner is "simple" - atomic or basic application
66+ checkOuterParens :: Expr Void -> String -> Array LintWarning
67+ checkOuterParens expr msg = case expr of
68+ outer@(ExprParens (Wrapped { value: inner })) ->
69+ -- Only flag if inner is simple enough that parens are clearly redundant
70+ if isSimpleExpr inner then [ mkWarning outer inner msg ]
71+ else []
72+ _ -> []
73+
74+ -- An expression is "simple" enough that outer parens are redundant
75+ -- This is more conservative than isAtomicExpr
76+ isSimpleExpr :: Expr Void -> Boolean
77+ isSimpleExpr = case _ of
78+ -- Atomic expressions
79+ ExprIdent _ -> true
80+ ExprConstructor _ -> true
81+ ExprInt _ _ -> true
82+ ExprNumber _ _ -> true
83+ ExprString _ _ -> true
84+ ExprChar _ _ -> true
85+ ExprBoolean _ _ -> true
86+ ExprArray _ -> true
87+ ExprRecord _ -> true
88+ ExprRecordAccessor _ -> true
89+ -- Simple applications (f x y)
90+ ExprApp _ _ -> true
91+ -- Simple operators (a + b) - but NOT sections
92+ ExprOp left _ -> not (isSection left) -- Check if it contains a section
93+ -- Negate
94+ ExprNegate _ _ -> true
95+ -- Already has parens (nested) - will be caught by nested check
96+ ExprParens _ -> false
97+ -- Everything else might need parens for readability
98+ _ -> false
99+
100+ -- Check if expression or its parts contain a section
101+ isSection :: Expr Void -> Boolean
102+ isSection = case _ of
103+ ExprSection _ -> true
104+ ExprParens (Wrapped { value }) -> isSection value
105+ _ -> false
106+
107+ checkExpr :: Expr Void -> Array LintWarning
108+ checkExpr expr = case expr of
109+ -- === NESTED PARENS: Always redundant ===
110+ ExprParens (Wrapped { value: inner@(ExprParens _) }) ->
111+ [ mkWarning expr inner "Redundant nested parentheses" ]
112+
113+ -- === FUNCTION APPLICATION: Check args ===
114+ ExprApp _fn args ->
115+ NEA.toArray args # Array.mapMaybe checkAppArg
116+
117+ -- === ARRAY LITERAL: Check elements ===
118+ ExprArray (Wrapped { value: Just (Separated { head, tail }) }) ->
119+ let
120+ allExprs = Array.cons head (map snd tail)
121+ in
122+ allExprs # Array.mapMaybe (checkAtomicParens "Redundant parentheses in array element")
123+
124+ -- === RECORD LITERAL: Check field values ===
125+ ExprRecord (Wrapped { value: Just (Separated { head, tail }) }) ->
126+ let
127+ allFields = Array.cons head (map snd tail)
128+ in
129+ allFields # Array.mapMaybe checkRecordField
130+
131+ -- === IF EXPRESSION: Check condition and branches ===
132+ ExprIf ifExpr ->
133+ Array.catMaybes
134+ [ checkAtomicParens "Redundant parentheses in if condition" ifExpr.cond
135+ , checkAtomicParens "Redundant parentheses in then branch" ifExpr.true
136+ , checkAtomicParens "Redundant parentheses in else branch" ifExpr.false
137+ ]
138+
139+ -- === CASE EXPRESSION: Check scrutinee and branches ===
140+ ExprCase caseExpr ->
141+ let
142+ scrutinees = separatedToArray caseExpr.head
143+ scrutineeWarnings = scrutinees # Array.mapMaybe (checkAtomicParens "Redundant parentheses in case scrutinee")
144+ branchWarnings = NEA.toArray caseExpr.branches # Array.concatMap checkCaseBranch
145+ in
146+ scrutineeWarnings <> branchWarnings
147+
148+ -- === LET EXPRESSION: Check bindings and body ===
149+ ExprLet letExpr ->
150+ let
151+ bindingWarnings = NEA.toArray letExpr.bindings # Array.concatMap checkLetBinding
152+ bodyWarnings = Array.fromFoldable $ checkAtomicParens "Redundant parentheses in let body" letExpr.body
153+ in
154+ bindingWarnings <> bodyWarnings
155+
156+ -- === DO EXPRESSION: Check statements ===
157+ ExprDo doExpr ->
158+ NEA.toArray doExpr.statements # Array.concatMap checkDoStatement
159+
160+ _ -> []
161+
162+ -- Check a function argument
163+ checkAppArg :: AppSpine Expr Void -> Maybe LintWarning
164+ checkAppArg = case _ of
165+ AppTerm outer@(ExprParens (Wrapped { value: inner }))
166+ | isAtomicExpr inner -> Just $ mkWarning outer inner "Redundant parentheses in argument"
167+ _ -> Nothing
168+
169+ -- Check atomic parens helper
170+ checkAtomicParens :: String -> Expr Void -> Maybe LintWarning
171+ checkAtomicParens msg = case _ of
172+ outer@(ExprParens (Wrapped { value: inner }))
173+ | isAtomicExpr inner -> Just $ mkWarning outer inner msg
174+ _ -> Nothing
175+
176+ -- Check record field value
177+ checkRecordField :: RecordLabeled (Expr Void) -> Maybe LintWarning
178+ checkRecordField = case _ of
179+ RecordField _ _ outer@(ExprParens (Wrapped { value: inner }))
180+ | isAtomicExpr inner -> Just $ mkWarning outer inner "Redundant parentheses in record field"
181+ RecordPun _ -> Nothing
182+ RecordField _ _ _ -> Nothing
183+
184+ -- Check case branch body and patterns
185+ checkCaseBranch :: _ -> Array LintWarning
186+ checkCaseBranch (binders /\ guarded) =
187+ let
188+ -- Check for redundant parens around top-level case patterns
189+ binderWarnings = separatedToArray binders # Array.mapMaybe checkTopLevelBinderParens
190+ exprWarnings = case guarded of
191+ Unconditional _ (Where { expr }) ->
192+ Array.fromFoldable $ checkAtomicParens "Redundant parentheses in case branch" expr
193+ Guarded guardedExprs ->
194+ NEA.toArray guardedExprs # Array.concatMap \(GuardedExpr ge) ->
195+ case ge.where of
196+ Where { expr } -> Array.fromFoldable $ checkAtomicParens "Redundant parentheses in guarded case branch" expr
197+ in binderWarnings <> exprWarnings
198+
199+ -- Check for redundant parens around a top-level case pattern
200+ -- e.g., case x of (Just y) -> ... should be Just y -> ...
201+ checkTopLevelBinderParens :: Binder Void -> Maybe LintWarning
202+ checkTopLevelBinderParens = case _ of
203+ outer@(BinderParens (Wrapped { value: inner })) ->
204+ -- Parens are redundant unless inner is a typed binder or operator pattern
205+ case inner of
206+ BinderTyped _ _ _ -> Nothing -- (x :: Type) needs parens
207+ BinderOp _ _ -> Nothing -- (a : as) needs parens for cons
208+ _ -> Just $ mkBinderWarning outer inner "Redundant parentheses around case pattern"
209+ _ -> Nothing
210+
211+ -- Check let binding RHS
212+ checkLetBinding :: LetBinding Void -> Array LintWarning
213+ checkLetBinding = case _ of
214+ LetBindingName vb -> case vb.guarded of
215+ Unconditional _ (Where { expr }) ->
216+ Array.fromFoldable $ checkAtomicParens "Redundant parentheses in let binding" expr
217+ _ -> []
218+ _ -> []
219+
220+ -- Check do statement
221+ checkDoStatement :: DoStatement Void -> Array LintWarning
222+ checkDoStatement = case _ of
223+ DoLet _ bindings ->
224+ NEA.toArray bindings # Array.concatMap checkLetBinding
225+ DoBind _ _ expr ->
226+ Array.fromFoldable $ checkAtomicParens "Redundant parentheses in do bind" expr
227+ DoDiscard expr ->
228+ Array.fromFoldable $ checkAtomicParens "Redundant parentheses in do statement" expr
229+ DoError _ -> []
230+
231+ -- An expression is "atomic" - never needs parens for grouping
232+ isAtomicExpr :: Expr Void -> Boolean
233+ isAtomicExpr = case _ of
234+ ExprIdent _ -> true
235+ ExprConstructor _ -> true
236+ ExprInt _ _ -> true
237+ ExprNumber _ _ -> true
238+ ExprString _ _ -> true
239+ ExprChar _ _ -> true
240+ ExprBoolean _ _ -> true
241+ ExprArray _ -> true
242+ ExprRecord _ -> true
243+ ExprParens _ -> true
244+ ExprRecordAccessor _ -> true
245+ _ -> false
246+
247+ separatedToArray :: forall a. Separated a -> Array a
248+ separatedToArray (Separated { head, tail }) = Array.cons head (map snd tail)
249+
250+ mkWarning :: Expr Void -> Expr Void -> String -> LintWarning
251+ mkWarning outer inner msg = LintWarning
252+ { ruleId: RuleId "RedundantParens"
253+ , message: WarningMessage msg
254+ , range: rangeOf outer
255+ , severity: Hint
256+ , suggestion: Just $ Suggestion
257+ { replacement: ReplacementText (printExpr inner)
258+ , description: SuggestionDescription "Remove unnecessary parentheses"
259+ , requiredImports: []
260+ }
261+ }
262+
263+ -- Check binders (patterns) for redundant parens
264+ checkBinder :: Binder Void -> Array LintWarning
265+ checkBinder = case _ of
266+ -- Nested parens in patterns: ((x))
267+ outer@(BinderParens (Wrapped { value: inner@(BinderParens _) })) ->
268+ [ mkBinderWarning outer inner "Redundant nested parentheses in pattern" ]
269+ -- Parens around atomic patterns in constructor args
270+ BinderConstructor _ args ->
271+ args # Array.mapMaybe checkAtomicBinderParens
272+ -- Parens around atomic patterns in array patterns
273+ BinderArray (Wrapped { value: Just (Separated { head, tail }) }) ->
274+ let
275+ allBinders = Array.cons head (map snd tail)
276+ in
277+ allBinders # Array.mapMaybe checkAtomicBinderParens
278+ -- Parens around atomic patterns in record patterns
279+ BinderRecord (Wrapped { value: Just (Separated { head, tail }) }) ->
280+ let
281+ allFields = Array.cons head (map snd tail)
282+ in
283+ allFields # Array.mapMaybe checkRecordBinderField
284+ _ -> []
285+
286+ -- Check if a binder is atomic (never needs parens)
287+ isAtomicBinder :: Binder Void -> Boolean
288+ isAtomicBinder = case _ of
289+ BinderWildcard _ -> true
290+ BinderVar _ -> true
291+ BinderBoolean _ _ -> true
292+ BinderChar _ _ -> true
293+ BinderString _ _ -> true
294+ BinderInt Nothing _ _ -> true -- No negation
295+ BinderNumber Nothing _ _ -> true -- No negation
296+ BinderArray _ -> true
297+ BinderRecord _ -> true
298+ BinderParens _ -> true
299+ -- Constructor with args needs parens, constructor without args is atomic
300+ BinderConstructor _ args -> Array.null args
301+ _ -> false
302+
303+ checkAtomicBinderParens :: Binder Void -> Maybe LintWarning
304+ checkAtomicBinderParens = case _ of
305+ outer@(BinderParens (Wrapped { value: inner }))
306+ | isAtomicBinder inner -> Just $ mkBinderWarning outer inner "Redundant parentheses in pattern"
307+ _ -> Nothing
308+
309+ checkRecordBinderField :: RecordLabeled (Binder Void) -> Maybe LintWarning
310+ checkRecordBinderField = case _ of
311+ RecordField _ _ outer@(BinderParens (Wrapped { value: inner }))
312+ | isAtomicBinder inner -> Just $ mkBinderWarning outer inner "Redundant parentheses in record pattern"
313+ RecordPun _ -> Nothing
314+ RecordField _ _ _ -> Nothing
315+
316+ mkBinderWarning :: Binder Void -> Binder Void -> String -> LintWarning
317+ mkBinderWarning outer inner msg = LintWarning
318+ { ruleId: RuleId "RedundantParens"
319+ , message: WarningMessage msg
320+ , range: rangeOf outer
321+ , severity: Hint
322+ , suggestion: Just $ Suggestion
323+ { replacement: ReplacementText (printBinder inner)
324+ , description: SuggestionDescription "Remove unnecessary parentheses"
325+ , requiredImports: []
326+ }
327+ }
328+
329+ -- ============================================================================
330+ -- TYPE-LEVEL CHECKS
331+ -- ============================================================================
332+
333+ -- Check types for redundant parens
334+ checkType :: Type Void -> Array LintWarning
335+ checkType = case _ of
336+ -- Nested parens: ((Int))
337+ outer@(TypeParens (Wrapped { value: inner@(TypeParens _) })) ->
338+ [ mkTypeWarning outer inner "Redundant nested parentheses in type" ]
339+
340+ -- Type application with atomic arg: Maybe (Int) -> Maybe Int
341+ TypeApp _ args ->
342+ NEA.toArray args # Array.mapMaybe checkAtomicTypeParens
343+
344+ -- Arrow left side: (Int) -> String -> Int -> String
345+ -- But NOT (a -> b) -> c (function type needs parens)
346+ TypeArrow left _ _ ->
347+ Array.fromFoldable $ checkSimpleTypeParens "Redundant parentheses in function argument type" left
348+
349+ -- Record row types: { x :: (Int) } -> { x :: Int }
350+ TypeRecord (Wrapped { value: Row { labels: Just (Separated { head, tail }) } }) ->
351+ let allLabels = Array.cons head (map snd tail)
352+ in allLabels # Array.mapMaybe checkLabeledTypeParens
353+
354+ -- Constrained type: (Show a) => ... is actually fine in PureScript
355+ -- The parens are optional but conventional, so don't flag
356+
357+ _ -> []
358+
359+ -- Check if a type is atomic (never needs parens)
360+ isAtomicType :: Type Void -> Boolean
361+ isAtomicType = case _ of
362+ TypeVar _ -> true
363+ TypeConstructor _ -> true
364+ TypeWildcard _ -> true
365+ TypeHole _ -> true
366+ TypeString _ _ -> true
367+ TypeInt Nothing _ _ -> true -- No negation
368+ TypeRow _ -> true
369+ TypeRecord _ -> true
370+ TypeParens _ -> true
371+ TypeOpName _ -> true
372+ TypeArrowName _ -> true
373+ _ -> false
374+
375+ -- Check if a type is simple enough that outer parens are redundant
376+ -- More permissive than atomic - includes applications
377+ isSimpleType :: Type Void -> Boolean
378+ isSimpleType = case _ of
379+ TypeVar _ -> true
380+ TypeConstructor _ -> true
381+ TypeWildcard _ -> true
382+ TypeHole _ -> true
383+ TypeString _ _ -> true
384+ TypeInt Nothing _ _ -> true
385+ TypeRow _ -> true
386+ TypeRecord _ -> true
387+ TypeParens _ -> false -- Will be caught by nested check
388+ TypeOpName _ -> true
389+ TypeArrowName _ -> true
390+ TypeApp _ _ -> true -- f a b is simple
391+ TypeOp _ _ -> true -- a + b is simple (at top level)
392+ -- NOT simple: TypeArrow, TypeForall, TypeConstrained, TypeKinded
393+ _ -> false
394+
395+ checkAtomicTypeParens :: Type Void -> Maybe LintWarning
396+ checkAtomicTypeParens = case _ of
397+ outer@(TypeParens (Wrapped { value: inner }))
398+ | isAtomicType inner -> Just $ mkTypeWarning outer inner "Redundant parentheses in type argument"
399+ _ -> Nothing
400+
401+ checkSimpleTypeParens :: String -> Type Void -> Maybe LintWarning
402+ checkSimpleTypeParens msg = case _ of
403+ outer@(TypeParens (Wrapped { value: inner }))
404+ | isSimpleType inner -> Just $ mkTypeWarning outer inner msg
405+ _ -> Nothing
406+
407+ checkLabeledTypeParens :: Labeled _ (Type Void) -> Maybe LintWarning
408+ checkLabeledTypeParens (Labeled { value: typ }) =
409+ checkAtomicTypeParens typ
410+
411+ mkTypeWarning :: Type Void -> Type Void -> String -> LintWarning
412+ mkTypeWarning outer inner msg = LintWarning
413+ { ruleId: RuleId "RedundantParens"
414+ , message: WarningMessage msg
415+ , range: rangeOf outer
416+ , severity: Hint
417+ , suggestion: Just $ Suggestion
418+ { replacement: ReplacementText (printType inner)
419+ , description: SuggestionDescription "Remove unnecessary parentheses"
420+ , requiredImports: []
421+ }
422+ }
423+
lib/purslint/src/Purslint/Rules/RedundantReverse.pursnew file+60
@@ -0,0 +1,60 @@
1+module Purslint.Rules.RedundantReverse where
2+
3+import Prelude
4+
5+import Data.Array.NonEmpty as NEA
6+import Data.Maybe (Maybe(..))
7+import Data.Void (Void)
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
12+import PureScript.CST.Range (rangeOf)
13+import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
14+import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
15+
16+-- | Rule: reverse (reverse x) -> x
17+redundantReverseRule :: Rule
18+redundantReverseRule = mkRule (RuleId "RedundantReverse") run
19+ where
20+ run :: RuleContext -> Module Void -> Array LintWarning
21+ run ctx = foldMapModule visitor
22+ where
23+ imports = ctx.imports
24+ visitor = defaultMonoidalVisitor { onExpr = checkExpr imports }
25+
26+ checkExpr :: ImportInfo -> Expr Void -> Array LintWarning
27+ checkExpr imports expr = case expr of
28+ ExprApp fn args
29+ | isReverse imports fn
30+ , [ AppTerm arg ] <- NEA.toArray args
31+ , ExprApp innerFn innerArgs <- unwrapParens arg
32+ , isReverse imports innerFn
33+ , [ AppTerm innerArg ] <- NEA.toArray innerArgs ->
34+ let
35+ x = printExpr innerArg
36+ in
37+ [ LintWarning
38+ { ruleId: RuleId "RedundantReverse"
39+ , message: WarningMessage "reverse (reverse x) is redundant"
40+ , range: rangeOf expr
41+ , severity: Warning
42+ , suggestion: Just $ Suggestion
43+ { replacement: ReplacementText x
44+ , description: SuggestionDescription "Remove redundant double reverse"
45+ , requiredImports: []
46+ }
47+ }
48+ ]
49+ _ -> []
50+
51+ unwrapParens :: Expr Void -> Expr Void
52+ unwrapParens = case _ of
53+ (ExprParens (Wrapped { value })) -> unwrapParens value
54+ e -> e
55+
56+ isReverse :: ImportInfo -> Expr Void -> Boolean
57+ isReverse imports (ExprIdent (QualifiedName { name: Ident name })) =
58+ name == "reverse" && hasValue imports "reverse"
59+ isReverse _ _ = false
60+
lib/purelint/src/Purelint/Rules/UnlessNot.purs → lib/purslint/src/Purslint/Rules/UnlessNot.pursrenamed+8−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UnlessNot where
1+module Purslint.Rules.UnlessNot where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -44,6 +44,7 @@ unlessNotRule = mkRule (RuleId "UnlessNot") run
4444 , suggestion: Just $ Suggestion
4545 { replacement: ReplacementText ("when " <> xText <> " " <> action)
4646 , description: SuggestionDescription "Use when instead of unless (not ...)"
47+ , requiredImports: []
4748 }
4849 }
4950 ]
@@ -61,6 +62,7 @@ unlessNotRule = mkRule (RuleId "UnlessNot") run
6162 , suggestion: Just $ Suggestion
6263 { replacement: ReplacementText ("when " <> xText)
6364 , description: SuggestionDescription "Use when instead of unless (not ...)"
65+ , requiredImports: []
6466 }
6567 }
6668 ]
@@ -91,3 +93,4 @@ unlessNotRule = mkRule (RuleId "UnlessNot") run
9193 [AppTerm arg] -> Just arg
9294 _ -> Nothing
9395 _ -> Nothing
96+
lib/purelint/src/Purelint/Rules/UseAll.purs → lib/purslint/src/Purslint/Rules/UseAll.pursrenamed+7−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseAll where
1+module Purslint.Rules.UseAll where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -45,6 +45,7 @@ useAllRule = mkRule (RuleId "UseAll") run
4545 , suggestion: Just $ Suggestion
4646 { replacement: ReplacementText ("all " <> fText <> " " <> xText)
4747 , description: SuggestionDescription "and (map f x) can be simplified to all f x"
48+ , requiredImports: []
4849 }
4950 }
5051 ]
@@ -66,3 +67,4 @@ useAllRule = mkRule (RuleId "UseAll") run
6667 isMapLike imports (ExprIdent (QualifiedName { name: Ident name })) =
6768 name == "map" && hasValue imports "map"
6869 isMapLike _ _ = false
70+
lib/purelint/src/Purelint/Rules/UseAnd.purs → lib/purslint/src/Purslint/Rules/UseAnd.pursrenamed+8−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseAnd where
1+module Purslint.Rules.UseAnd where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -39,6 +39,7 @@ useAndRule = mkRule (RuleId "UseAnd") run
3939 , suggestion: Just $ Suggestion
4040 { replacement: ReplacementText "and"
4141 , description: SuggestionDescription "Use and instead of all identity"
42+ , requiredImports: []
4243 }
4344 }
4445 ]
@@ -55,6 +56,7 @@ useAndRule = mkRule (RuleId "UseAnd") run
5556 , suggestion: Just $ Suggestion
5657 { replacement: ReplacementText ("and " <> x)
5758 , description: SuggestionDescription "Use and instead of all identity"
59+ , requiredImports: []
5860 }
5961 }
6062 ]
@@ -75,3 +77,4 @@ useAndRule = mkRule (RuleId "UseAnd") run
7577 isIdentity imports (ExprIdent (QualifiedName { name: Ident name })) =
7678 (name == "identity" || name == "id") && hasValue imports "identity"
7779 isIdentity _ _ = false
80+
lib/purelint/src/Purelint/Rules/UseAny.purs → lib/purslint/src/Purslint/Rules/UseAny.pursrenamed+10−8
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseAny where
1+module Purslint.Rules.UseAny where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -41,6 +41,7 @@ useAnyRule = mkRule (RuleId "UseAny") run
4141 , suggestion: Just $ Suggestion
4242 { replacement: ReplacementText ("any " <> fText <> " " <> xText)
4343 , description: SuggestionDescription "or (map f x) can be simplified to any f x"
44+ , requiredImports: []
4445 }
4546 }
4647 ]
@@ -56,6 +57,7 @@ useAnyRule = mkRule (RuleId "UseAny") run
5657 isOr _ _ = false
5758
5859 isMapLike :: ImportInfo -> Expr Void -> Boolean
59− isMapLike imports (ExprIdent (QualifiedName { name: Ident name })) =
60− name == "map" && hasValue imports "map"
61− isMapLike _ _ = false
60+ isMapLike = case _, _ of
61+ imports, (ExprIdent (QualifiedName { name: Ident name })) -> name == "map" && hasValue imports "map"
62+ _, _ -> false
63+
lib/purelint/src/Purelint/Rules/UseApplicative.purs → lib/purslint/src/Purslint/Rules/UseApplicative.pursrenamed+7−5
@@ -1,4 +1,4 @@
1−module Purelint.Rules.UseApplicative where
1+module Purslint.Rules.UseApplicative where
22
33import Prelude
44
@@ -6,10 +6,10 @@ import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Tuple (Tuple(..))
88import Data.Void (Void)
9−import Purelint.Imports (ImportInfo, hasValue, hasOp)
10−import Purelint.Print (printExpr)
11−import Purelint.Rule (Rule, RuleContext, mkRule)
12−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
9+import Purslint.Imports (ImportInfo, hasValue, hasOp)
10+import Purslint.Print (printExpr)
11+import Purslint.Rule (Rule, RuleContext, mkRule)
12+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1313import PureScript.CST.Range (rangeOf)
1414import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1515import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, Operator(..), QualifiedName(..), Wrapped(..))
@@ -43,6 +43,7 @@ useApplicativeRule = run # mkRule (RuleId "UseApplicative")
4343 , suggestion: Just $ Suggestion
4444 { replacement: ReplacementText (f <> " <$> " <> x)
4545 , description: SuggestionDescription "Use <$> instead of pure f <*> x"
46+ , requiredImports: []
4647 }
4748 }
4849 ]
@@ -56,3 +57,4 @@ useApplicativeRule = run # mkRule (RuleId "UseApplicative")
5657 isPure imports (ExprIdent (QualifiedName { name: Ident name })) =
5758 name == "pure" && hasValue imports "pure"
5859 isPure _ _ = false
60+
lib/purelint/src/Purelint/Rules/UseApplyFlipped.purs → lib/purslint/src/Purslint/Rules/UseApplyFlipped.pursrenamed+6−4
@@ -1,13 +1,13 @@
1−module Purelint.Rules.UseApplyFlipped where
1+module Purslint.Rules.UseApplyFlipped where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Print (printExprNoComments)
9−import Purelint.Rule (Rule, RuleContext, mkRule)
10−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Print (printExprNoComments)
9+import Purslint.Rule (Rule, RuleContext, mkRule)
10+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1111import PureScript.CST.Range (rangeOf)
1212import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1313import PureScript.CST.Types (AppSpine(..), Expr(..), Module, Wrapped(..))
@@ -45,6 +45,7 @@ useApplyFlippedRule = mkRule (RuleId "UseApplyFlipped") run
4545 , suggestion: Just $ Suggestion
4646 { replacement: ReplacementText replacement
4747 , description: SuggestionDescription ("Rewrite as: " <> replacement)
48+ , requiredImports: []
4849 }
4950 }
5051 ]
@@ -53,3 +54,4 @@ useApplyFlippedRule = mkRule (RuleId "UseApplyFlipped") run
5354 unwrapParens :: Expr Void -> Expr Void
5455 unwrapParens (ExprParens (Wrapped { value })) = unwrapParens value
5556 unwrapParens e = e
57+
lib/purelint/src/Purelint/Rules/UseBimap.purs → lib/purslint/src/Purslint/Rules/UseBimap.pursrenamed+14−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseBimap where
1+module Purslint.Rules.UseBimap where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -48,6 +48,7 @@ useBimapRule = mkRule (RuleId "UseBimap") run
4848 , suggestion: Just $ Suggestion
4949 { replacement: ReplacementText ("second " <> g)
5050 , description: SuggestionDescription "Use second instead of bimap id"
51+ , requiredImports: []
5152 }
5253 }
5354 ]
@@ -63,6 +64,7 @@ useBimapRule = mkRule (RuleId "UseBimap") run
6364 , suggestion: Just $ Suggestion
6465 { replacement: ReplacementText ("first " <> f)
6566 , description: SuggestionDescription "Use first instead of bimap f id"
67+ , requiredImports: []
6668 }
6769 }
6870 ]
@@ -75,6 +77,7 @@ useBimapRule = mkRule (RuleId "UseBimap") run
7577 , suggestion: Just $ Suggestion
7678 { replacement: ReplacementText "identity"
7779 , description: SuggestionDescription "bimap id id is just identity"
80+ , requiredImports: []
7881 }
7982 }
8083 ]
@@ -97,6 +100,7 @@ useBimapRule = mkRule (RuleId "UseBimap") run
97100 , suggestion: Just $ Suggestion
98101 { replacement: ReplacementText ("second " <> g <> " " <> x)
99102 , description: SuggestionDescription "Use second instead of bimap id"
103+ , requiredImports: []
100104 }
101105 }
102106 ]
@@ -112,6 +116,7 @@ useBimapRule = mkRule (RuleId "UseBimap") run
112116 , suggestion: Just $ Suggestion
113117 { replacement: ReplacementText ("first " <> f <> " " <> x)
114118 , description: SuggestionDescription "Use first instead of bimap f id"
119+ , requiredImports: []
115120 }
116121 }
117122 ]
@@ -124,6 +129,7 @@ useBimapRule = mkRule (RuleId "UseBimap") run
124129 , suggestion: Just $ Suggestion
125130 { replacement: ReplacementText x
126131 , description: SuggestionDescription "bimap id id x is just x"
132+ , requiredImports: []
127133 }
128134 }
129135 ]
@@ -149,6 +155,7 @@ useBimapRule = mkRule (RuleId "UseBimap") run
149155 , suggestion: Just $ Suggestion
150156 { replacement: ReplacementText ("bimap " <> f <> " " <> g <> " " <> x)
151157 , description: SuggestionDescription "Use bimap instead of first and second"
158+ , requiredImports: []
152159 }
153160 }
154161 ]
@@ -172,6 +179,7 @@ useBimapRule = mkRule (RuleId "UseBimap") run
172179 , suggestion: Just $ Suggestion
173180 { replacement: ReplacementText ("bimap " <> f <> " " <> g <> " " <> x)
174181 , description: SuggestionDescription "Use bimap instead of first and second"
182+ , requiredImports: []
175183 }
176184 }
177185 ]
@@ -201,3 +209,4 @@ useBimapRule = mkRule (RuleId "UseBimap") run
201209 isIdentity imports (ExprIdent (QualifiedName { name: Ident name })) =
202210 (name == "identity" && hasValue imports "identity") || (name == "id" && hasValue imports "identity")
203211 isIdentity _ _ = false
212+
lib/purelint/src/Purelint/Rules/UseBindFlip.purs → lib/purslint/src/Purslint/Rules/UseBindFlip.pursrenamed+13−11
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseBindFlip where
1+module Purslint.Rules.UseBindFlip where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -52,17 +52,19 @@ useBindFlipRule = mkRule (RuleId "UseBindFlip") run
5252 , suggestion: Just $ Suggestion
5353 { replacement: ReplacementText (x <> " >>= " <> f)
5454 , description: SuggestionDescription "Use >>= instead of join composed with map"
55+ , requiredImports: []
5556 }
5657 }
5758 ]
5859 _ -> []
5960
6061 isJoin :: ImportInfo -> Expr Void -> Boolean
61− isJoin imports (ExprIdent (QualifiedName { name: Ident name })) =
62− name == "join" && hasValue imports "join"
63− isJoin _ _ = false
62+ isJoin = case _, _ of
63+ imports, ExprIdent (QualifiedName { name: Ident name }) -> name == "join" && hasValue imports "join"
64+ _, _ -> false
6465
6566 isMap :: ImportInfo -> Expr Void -> Boolean
66− isMap imports (ExprIdent (QualifiedName { name: Ident name })) =
67− name == "map" && hasValue imports "map"
68− isMap _ _ = false
67+ isMap = case _, _ of
68+ imports, ExprIdent (QualifiedName { name: Ident name }) -> name == "map" && hasValue imports "map"
69+ _, _ -> false
70+
lib/purslint/src/Purslint/Rules/UseBreak.pursnew file+99
@@ -0,0 +1,99 @@
1+module Purslint.Rules.UseBreak where
2+
3+import Prelude
4+
5+import Data.Array.NonEmpty as NEA
6+import Data.Maybe (Maybe(..))
7+import Data.Void (Void)
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
12+import PureScript.CST.Range (rangeOf)
13+import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
14+import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
15+
16+-- | Rule: span (not <<< p) -> break p (composition form)
17+-- | Also: span (\x -> not (p x)) -> break p (lambda form)
18+useBreakRule :: Rule
19+useBreakRule = mkRule (RuleId "UseBreak") run
20+ where
21+ run :: RuleContext -> Module Void -> Array LintWarning
22+ run ctx = foldMapModule visitor
23+ where
24+ imports = ctx.imports
25+ visitor = defaultMonoidalVisitor { onExpr = checkExpr imports }
26+
27+ checkExpr :: ImportInfo -> Expr Void -> Array LintWarning
28+ checkExpr imports expr = case expr of
29+ -- Match: span (not <<< p) x
30+ ExprApp fn args
31+ | isSpan imports fn ->
32+ case NEA.toArray args of
33+ [ AppTerm predArg, AppTerm xArg ] ->
34+ case getNotComposedPred imports predArg of
35+ Just p ->
36+ let
37+ pText = printExpr p
38+ x = printExpr xArg
39+ in
40+ [ LintWarning
41+ { ruleId: RuleId "UseBreak"
42+ , message: WarningMessage "span (not <<< p) can be simplified to break p"
43+ , range: rangeOf expr
44+ , severity: Hint
45+ , suggestion: Just $ Suggestion
46+ { replacement: ReplacementText ("break " <> pText <> " " <> x)
47+ , description: SuggestionDescription "Use break instead of span with negated predicate"
48+ , requiredImports: []
49+ }
50+ }
51+ ]
52+ Nothing -> []
53+ [ AppTerm predArg ] ->
54+ case getNotComposedPred imports predArg of
55+ Just p ->
56+ let
57+ pText = printExpr p
58+ in
59+ [ LintWarning
60+ { ruleId: RuleId "UseBreak"
61+ , message: WarningMessage "span (not <<< p) can be simplified to break p"
62+ , range: rangeOf expr
63+ , severity: Hint
64+ , suggestion: Just $ Suggestion
65+ { replacement: ReplacementText ("break " <> pText)
66+ , description: SuggestionDescription "Use break instead of span with negated predicate"
67+ , requiredImports: []
68+ }
69+ }
70+ ]
71+ Nothing -> []
72+ _ -> []
73+ _ -> []
74+
75+ unwrapParens :: Expr Void -> Expr Void
76+ unwrapParens (ExprParens (Wrapped { value })) = unwrapParens value
77+ unwrapParens e = e
78+
79+ isSpan :: ImportInfo -> Expr Void -> Boolean
80+ isSpan imports (ExprIdent (QualifiedName { name: Ident name })) =
81+ name == "span" && hasValue imports "span"
82+ isSpan _ _ = false
83+
84+ isNot :: ImportInfo -> Expr Void -> Boolean
85+ isNot imports (ExprIdent (QualifiedName { name: Ident name })) =
86+ name == "not" && hasValue imports "not"
87+ isNot _ _ = false
88+
89+ -- Check for (not <<< p) pattern
90+ getNotComposedPred :: ImportInfo -> Expr Void -> Maybe (Expr Void)
91+ getNotComposedPred imports predExpr =
92+ case unwrapParens predExpr of
93+ ExprApp notFn args
94+ | isNot imports notFn ->
95+ case NEA.toArray args of
96+ [ AppTerm p ] -> Just p
97+ _ -> Nothing
98+ _ -> Nothing
99+
lib/purslint/src/Purslint/Rules/UseCaseOf.pursnew file+239
@@ -0,0 +1,239 @@
1+module Purslint.Rules.UseCaseOf where
2+
3+import Prelude
4+
5+import Data.Array as Array
6+import Data.Array.NonEmpty as NEA
7+import Data.Foldable (foldMap)
8+import Data.Maybe (Maybe(..))
9+import Data.Monoid (power)
10+import Data.String as String
11+import Data.Tuple (Tuple(..))
12+import Data.Tuple.Nested ((/\))
13+import Data.Void (Void, absurd)
14+import Purslint.Print (printBinder, printExpr)
15+import Purslint.Rule (Rule, RuleContext, mkRule)
16+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
17+import PureScript.CST.Print (printToken)
18+import PureScript.CST.Range (rangeOf, tokensOf)
19+import PureScript.CST.Range.TokenList as TokenList
20+import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
21+import PureScript.CST.Types (Binder(..), Comment(..), Declaration(..), Expr, Guarded(..), GuardedExpr(..), Ident(..), LetBinding(..), LineFeed(..), Module(..), ModuleBody(..), Name(..), SourceToken, ValueBindingFields, Where(..))
22+
23+-- | Rule: Functions with multiple clauses where the name repeats can use case _ of
24+-- |
25+-- | foo x = expr1
26+-- | foo y = expr2
27+-- |
28+-- | becomes:
29+-- |
30+-- | foo = case _ of
31+-- | x -> expr1
32+-- | y -> expr2
33+useCaseOfRule :: Rule
34+useCaseOfRule = mkRule (RuleId "UseCaseOf") run
35+ where
36+ run :: RuleContext -> Module Void -> Array LintWarning
37+ run _ctx mod@(Module m) =
38+ let
39+ ModuleBody body = m.body
40+ topLevelWarnings = groupByName body.decls # Array.concatMap (checkGroup 0)
41+ nestedWarnings = foldMapModule visitor mod
42+ in
43+ topLevelWarnings <> nestedWarnings
44+ where
45+ visitor = defaultMonoidalVisitor { onDecl = checkDecl }
46+
47+ -- Check where bindings in declarations
48+ checkDecl :: Declaration Void -> Array LintWarning
49+ checkDecl = case _ of
50+ DeclValue { guarded } -> checkGuarded guarded
51+ _ -> []
52+
53+ checkGuarded :: Guarded Void -> Array LintWarning
54+ checkGuarded = case _ of
55+ Unconditional _ (Where { bindings: Just (Tuple _ letBindings) }) ->
56+ checkLetBindings letBindings
57+ Guarded guards -> foldMap checkGuardedExpr (NEA.toArray guards)
58+ _ -> []
59+
60+ checkGuardedExpr :: GuardedExpr Void -> Array LintWarning
61+ checkGuardedExpr (GuardedExpr { where: Where { bindings: Just (Tuple _ letBindings) } }) =
62+ checkLetBindings letBindings
63+ checkGuardedExpr _ = []
64+
65+ checkLetBindings :: NEA.NonEmptyArray (LetBinding Void) -> Array LintWarning
66+ checkLetBindings bindings =
67+ let
68+ vbs = NEA.toArray bindings # Array.mapMaybe case _ of
69+ LetBindingName vb -> Just vb
70+ _ -> Nothing
71+ -- Get the base indentation from first binding
72+ baseCol = case Array.head vbs of
73+ Just vb -> (rangeOf (LetBindingName vb)).start.column
74+ Nothing -> 0
75+ groups = groupByNameVB vbs
76+ in
77+ groups # Array.concatMap (checkGroup baseCol)
78+
79+ -- Group consecutive DeclValue with same name (for top-level)
80+ groupByName :: Array (Declaration Void) -> Array (Array (ValueBindingFields Void))
81+ groupByName decls = go [] Nothing decls
82+ where
83+ go :: Array (Array (ValueBindingFields Void)) -> Maybe { name :: String, clauses :: Array (ValueBindingFields Void) } -> Array (Declaration Void) -> Array (Array (ValueBindingFields Void))
84+ go acc current arr = case Array.uncons arr of
85+ Nothing -> case current of
86+ Nothing -> acc
87+ Just c -> Array.snoc acc c.clauses
88+ Just { head: d, tail: rest } -> case d of
89+ DeclValue vb ->
90+ let
91+ name = getName vb
92+ in
93+ case current of
94+ Nothing -> go acc (Just { name, clauses: [ vb ] }) rest
95+ Just c | c.name == name -> go acc (Just { name, clauses: Array.snoc c.clauses vb }) rest
96+ Just c -> go (Array.snoc acc c.clauses) (Just { name, clauses: [ vb ] }) rest
97+ _ -> case current of
98+ Nothing -> go acc Nothing rest
99+ Just c -> go (Array.snoc acc c.clauses) Nothing rest
100+
101+ -- Group consecutive ValueBindingFields with same name (for where bindings)
102+ groupByNameVB :: Array (ValueBindingFields Void) -> Array (Array (ValueBindingFields Void))
103+ groupByNameVB vbs = go [] Nothing vbs
104+ where
105+ go :: Array (Array (ValueBindingFields Void)) -> Maybe { name :: String, clauses :: Array (ValueBindingFields Void) } -> Array (ValueBindingFields Void) -> Array (Array (ValueBindingFields Void))
106+ go acc current arr = case Array.uncons arr of
107+ Nothing -> case current of
108+ Nothing -> acc
109+ Just c -> Array.snoc acc c.clauses
110+ Just { head: vb, tail: rest } ->
111+ let
112+ name = getName vb
113+ in
114+ case current of
115+ Nothing -> go acc (Just { name, clauses: [ vb ] }) rest
116+ Just c | c.name == name -> go acc (Just { name, clauses: Array.snoc c.clauses vb }) rest
117+ Just c -> go (Array.snoc acc c.clauses) (Just { name, clauses: [ vb ] }) rest
118+
119+ getName :: ValueBindingFields Void -> String
120+ getName vb = case vb.name of
121+ Name { name: Ident n } -> n
122+
123+ checkGroup :: Int -> Array (ValueBindingFields Void) -> Array LintWarning
124+ checkGroup baseCol clauses
125+ | Array.length clauses < 2 = []
126+ | not (Array.all hasBindersAndUnconditional clauses) = []
127+ | otherwise = foldMap
128+ ( \first ->
129+ let
130+ name = getName first
131+ numArgs = Array.length first.binders
132+ -- Check all clauses have same number of binders
133+ sameBinders = Array.all (\vb -> Array.length vb.binders == numArgs) clauses
134+ in
135+ if not sameBinders || numArgs == 0 then []
136+ else
137+ let
138+ -- Build the replacement
139+ indent = power " " baseCol
140+ wildcards = Array.replicate numArgs "_" # String.joinWith ", "
141+ caseHeader = name <> " = case " <> wildcards <> " of"
142+ caseArms = clauses # Array.mapMaybe (formatClause (baseCol + 2))
143+ replacement = caseHeader <> "\n" <> (caseArms # map ((indent <> " ") <> _) # String.joinWith "\n")
144+ -- Range spans from first clause to last
145+ firstRange = rangeOf (LetBindingName first)
146+ lastRange = case Array.last clauses of
147+ Just lst -> rangeOf (LetBindingName lst)
148+ Nothing -> firstRange
149+ fullRange = { start: firstRange.start, end: lastRange.end }
150+ in
151+ [ LintWarning
152+ { ruleId: RuleId "UseCaseOf"
153+ , message: WarningMessage $ "Function '" <> name <> "' has " <> show (Array.length clauses) <> " clauses - consider using case _ of"
154+ , range: fullRange
155+ , severity: Refactor
156+ , suggestion: Just $ Suggestion
157+ { replacement: ReplacementText replacement
158+ , description: SuggestionDescription "Convert to case _ of"
159+ , requiredImports: []
160+ }
161+ }
162+ ]
163+ )
164+ (Array.head clauses)
165+
166+ hasBindersAndUnconditional :: ValueBindingFields Void -> Boolean
167+ hasBindersAndUnconditional vb = case vb.guarded of
168+ Unconditional _ (Where { bindings: Nothing }) -> Array.length vb.binders > 0
169+ Unconditional _ (Where { bindings: Just _ }) -> Array.length vb.binders > 0
170+ _ -> false
171+
172+ formatClause :: Int -> ValueBindingFields Void -> Maybe String
173+ formatClause baseCol vb = case vb.guarded of
174+ Unconditional _ (Where { expr, bindings: Nothing }) ->
175+ let
176+ binders = vb.binders # map printBinder # String.joinWith ", "
177+ exprRange = rangeOf expr
178+ isMultiline = exprRange.start.line /= exprRange.end.line
179+ in
180+ if isMultiline
181+ then
182+ let
183+ -- Target indentation for the expression body
184+ targetCol = baseCol + 2
185+ body = printExprTokens targetCol expr
186+ in
187+ Just $ binders <> " ->\n" <> power " " targetCol <> body
188+ else Just $ binders <> " -> " <> printExpr expr
189+ Unconditional _ (Where { expr, bindings: Just (_ /\ letBindings) }) ->
190+ let
191+ binders = vb.binders # map printBinder # String.joinWith ", "
192+ body = printExpr expr
193+ indent = power " " baseCol
194+ -- Format the where bindings with proper indentation
195+ whereBindings = NEA.toArray letBindings # map (printBindingTokens (baseCol + 4))
196+ whereText = String.joinWith ("\n" <> indent <> " ") whereBindings
197+ in
198+ Just $ binders <> " ->\n" <> indent <> " " <> body <> "\n" <> indent <> " where\n" <> indent <> " " <> whereText
199+ _ -> Nothing
200+
201+ printExprTokens :: Int -> Expr Void -> String
202+ printExprTokens targetCol expr =
203+ let
204+ exprCol = (rangeOf expr).start.column
205+ shiftAmount = targetCol - exprCol
206+ in
207+ String.trim $ foldMap (printSourceToken shiftAmount) (TokenList.toArray (tokensOf expr))
208+
209+ printBindingTokens :: Int -> LetBinding Void -> String
210+ printBindingTokens targetCol b =
211+ let
212+ bindingCol = (rangeOf b).start.column
213+ shiftAmount = targetCol - bindingCol
214+ in
215+ String.trim $ foldMap (printSourceToken shiftAmount) (TokenList.toArray (tokensOf b))
216+
217+ printSourceToken :: Int -> SourceToken -> String
218+ printSourceToken shiftAmount tok =
219+ foldMap (printLeadingComment shiftAmount) tok.leadingComments
220+ <> printToken tok.value
221+ <> foldMap printTrailingComment tok.trailingComments
222+
223+ printLeadingComment :: Int -> Comment LineFeed -> String
224+ printLeadingComment shiftAmount = case _ of
225+ Comment str -> str
226+ Space n -> power " " (max 0 (n + shiftAmount))
227+ Line lf n -> foldMap printLineFeed (Array.replicate n lf)
228+
229+ printLineFeed :: LineFeed -> String
230+ printLineFeed = case _ of
231+ LF -> "\n"
232+ CRLF -> "\r\n"
233+
234+ printTrailingComment :: Comment Void -> String
235+ printTrailingComment = case _ of
236+ Comment str -> str
237+ Space n -> power " " n
238+ Line v _ -> absurd v
239+
lib/purelint/src/Purelint/Rules/UseComparing.purs → lib/purslint/src/Purslint/Rules/UseComparing.pursrenamed+31−32
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseComparing where
1+module Purslint.Rules.UseComparing where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Binder(..), Expr(..), Ident(..), Module, Name(..), QualifiedName(..), Wrapped(..))
@@ -28,10 +28,10 @@ useComparingRule = mkRule (RuleId "UseComparing") run
2828 -- Match: \x y -> compare (f x) (f y)
2929 ExprLambda { binders, body }
3030 | hasValue imports "compare" ->
31− case NEA.toArray binders of
32− [BinderVar (Name { name: Ident x }), BinderVar (Name { name: Ident y })] ->
33− checkCompareBody imports expr x y (unwrapParens body)
34− _ -> []
31+ case NEA.toArray binders of
32+ [ BinderVar (Name { name: Ident x }), BinderVar (Name { name: Ident y }) ] ->
33+ checkCompareBody imports expr x y (unwrapParens body)
34+ _ -> []
3535 _ -> []
3636
3737 unwrapParens :: Expr Void -> Expr Void
@@ -42,38 +42,37 @@ useComparingRule = mkRule (RuleId "UseComparing") run
4242 checkCompareBody imports fullExpr x y body =
4343 case body of
4444 ExprApp fnExpr args
45− | isCompare imports fnExpr ->
46− case NEA.toArray args of
47− [AppTerm fxExpr, AppTerm fyExpr] ->
48− case extractFunctionCall (unwrapParens fxExpr), extractFunctionCall (unwrapParens fyExpr) of
49− Just { fn: f1, arg: arg1 }, Just { fn: f2, arg: arg2 }
50− | f1 == f2 && arg1 == x && arg2 == y ->
51− [ LintWarning
52− { ruleId: RuleId "UseComparing"
53− , message: WarningMessage "\\x y -> compare (f x) (f y) can be simplified to comparing f"
54− , range: rangeOf fullExpr
55− , severity: Hint
56− , suggestion: Just $ Suggestion
57− { replacement: ReplacementText ("comparing " <> f1)
58− , description: SuggestionDescription "Use comparing instead of lambda with compare"
59− }
60− }
61− ]
62− _, _ -> []
63− _ -> []
45+ | isCompare imports fnExpr
46+ , [ AppTerm fxExpr, AppTerm fyExpr ] <- NEA.toArray args
47+ , Just { fn: f1, arg: arg1 } <- extractFunctionCall (unwrapParens fxExpr)
48+ , Just { fn: f2, arg: arg2 } <- extractFunctionCall (unwrapParens fyExpr)
49+ , f1 == f2 && arg1 == x && arg2 == y ->
50+ [ LintWarning
51+ { ruleId: RuleId "UseComparing"
52+ , message: WarningMessage "\\x y -> compare (f x) (f y) can be simplified to comparing f"
53+ , range: rangeOf fullExpr
54+ , severity: Hint
55+ , suggestion: Just $ Suggestion
56+ { replacement: ReplacementText ("comparing " <> f1)
57+ , description: SuggestionDescription "Use comparing instead of lambda with compare"
58+ , requiredImports: []
59+ }
60+ }
61+ ]
6462 _ -> []
6563
6664 isCompare :: ImportInfo -> Expr Void -> Boolean
67− isCompare imports (ExprIdent (QualifiedName { name: Ident name })) =
68− name == "compare" && hasValue imports "compare"
69− isCompare _ _ = false
65+ isCompare = case _, _ of
66+ imports, ExprIdent (QualifiedName { name: Ident name }) -> name == "compare" && hasValue imports "compare"
67+ _, _ -> false
7068
7169 extractFunctionCall :: Expr Void -> Maybe { fn :: String, arg :: String }
7270 extractFunctionCall (ExprApp fnExpr args) =
7371 case fnExpr, NEA.toArray args of
74− ExprIdent (QualifiedName { name: Ident fn }), [AppTerm argExpr] ->
72+ ExprIdent (QualifiedName { name: Ident fn }), [ AppTerm argExpr ] ->
7573 case argExpr of
7674 ExprIdent (QualifiedName { name: Ident arg }) -> Just { fn, arg }
7775 _ -> Nothing
7876 _, _ -> Nothing
7977 extractFunctionCall _ = Nothing
78+
lib/purslint/src/Purslint/Rules/UseConst.pursnew file+93
@@ -0,0 +1,93 @@
1+module Purslint.Rules.UseConst where
2+
3+import Prelude
4+
5+import Data.Array as Array
6+import Data.Array.NonEmpty as NEA
7+import Data.Maybe (Maybe(..))
8+import Data.Monoid (power)
9+import Data.String as String
10+import Data.Void (Void)
11+import Purslint.Imports (ImportInfo, hasValue)
12+import Purslint.Print (printExpr, printExprMultiline)
13+import Purslint.Rule (Rule, RuleContext, mkRule)
14+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
15+import PureScript.CST.Range (rangeOf)
16+import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
17+import PureScript.CST.Types (Binder(..), Expr(..), Module)
18+
19+-- | Rule: \_ -> y -> const y
20+-- | Only triggers when the binder is a simple unused variable
21+useConstRule :: Rule
22+useConstRule = mkRule (RuleId "UseConst") run
23+ where
24+ run :: RuleContext -> Module Void -> Array LintWarning
25+ run ctx = foldMapModule visitor
26+ where
27+ imports = ctx.imports
28+ visitor = defaultMonoidalVisitor { onExpr = checkExpr imports }
29+
30+ checkExpr :: ImportInfo -> Expr Void -> Array LintWarning
31+ checkExpr imports expr = case expr of
32+ ExprLambda lambda | hasValue imports "const" ->
33+ case NEA.toArray lambda.binders of
34+ -- Only match wildcard binder: \_ -> body
35+ -- Named unused params like \f -> body are already warned by the compiler
36+ [BinderWildcard _] ->
37+ let
38+ bodyRange = rangeOf lambda.body
39+ isMultiline = bodyRange.start.line /= bodyRange.end.line
40+ replacement =
41+ if isMultiline then
42+ -- Preserve multiline structure: const\n<original body>
43+ let
44+ bodyStr = printExprMultiline lambda.body
45+ -- Get the column where the body starts for indent reference
46+ bodyCol = bodyRange.start.column
47+ indent n = power " " n
48+ -- The first line has no indentation (due to trim), subsequent lines have original
49+ lines = String.split (String.Pattern "\n") bodyStr
50+ -- Find minimum non-empty line indent from lines AFTER the first
51+ minIndent = case Array.tail lines of
52+ Nothing -> 0
53+ Just rest -> rest
54+ # Array.filter (\l -> String.trim l /= "")
55+ # map getIndent
56+ # Array.foldl min 999
57+ -- Re-indent: first line gets bodyCol indent, rest get normalized
58+ reindented = case Array.uncons lines of
59+ Nothing -> ""
60+ Just { head: first, tail: rest } ->
61+ let
62+ firstReindented = indent bodyCol <> first
63+ restReindented = rest # map (\l ->
64+ if String.trim l == "" then ""
65+ else indent bodyCol <> String.drop minIndent l
66+ )
67+ in String.joinWith "\n" (Array.cons firstReindented restReindented)
68+ in
69+ "const\n" <> reindented
70+ else
71+ "const " <> printExpr lambda.body
72+ in
73+ [ LintWarning
74+ { ruleId: RuleId "UseConst"
75+ , message: WarningMessage "Lambda ignores its argument"
76+ , range: rangeOf expr
77+ , severity: Hint
78+ , suggestion: Just $ Suggestion
79+ { replacement: ReplacementText replacement
80+ , description: SuggestionDescription "\\_ -> y can be simplified to const y"
81+ , requiredImports: []
82+ }
83+ }
84+ ]
85+ _ -> []
86+ _ -> []
87+
88+ -- Get the number of leading spaces in a string
89+ getIndent :: String -> Int
90+ getIndent s =
91+ let loop i = if String.take 1 (String.drop i s) == " " then loop (i + 1) else i
92+ in loop 0
93+
lib/purslint/src/Purslint/Rules/UseDollar.pursnew file+97
@@ -0,0 +1,97 @@
1+module Purslint.Rules.UseDollar where
2+
3+import Prelude
4+
5+import Data.Array as Array
6+import Data.Array.NonEmpty as NEA
7+import Data.Maybe (Maybe(..))
8+import Purslint.Print (printExpr, printType)
9+import Purslint.Rule (Rule, RuleContext, mkRule)
10+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
11+import PureScript.CST.Range (rangeOf)
12+import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
13+import PureScript.CST.Types (AppSpine(..), Expr(..), Module, Wrapped(..))
14+
15+-- | Rule: Replace parens around last function argument with $
16+-- |
17+-- | f (expr) -> f $ expr
18+-- | map f (filter g xs) -> map f $ filter g xs
19+-- |
20+-- | Only applies when the inner expression is non-atomic (otherwise
21+-- | the parens are likely redundant and RedundantParens will catch it).
22+useDollarRule :: Rule
23+useDollarRule = mkRule (RuleId "UseDollar") run
24+ where
25+ run :: RuleContext -> Module Void -> Array LintWarning
26+ run _ctx = foldMapModule visitor
27+ where
28+ visitor = defaultMonoidalVisitor { onExpr = checkExpr }
29+
30+ checkExpr :: Expr Void -> Array LintWarning
31+ checkExpr expr = case expr of
32+ -- Match function application where last arg is parenthesized
33+ -- f a b (expr) -> f a b $ expr
34+ ExprApp fn args ->
35+ case NEA.last args of
36+ AppTerm (ExprParens (Wrapped { value: inner }))
37+ -- Don't suggest $ for atomic expressions - parens are likely redundant anyway
38+ | not (isAtomicExpr inner)
39+ -- Don't suggest $ if inner is a section (would change semantics)
40+ , not (isSection inner) ->
41+ [ mkWarning expr fn args inner ]
42+ _ -> []
43+
44+ _ -> []
45+
46+ -- Check if expression is atomic (never needs parens)
47+ isAtomicExpr :: Expr Void -> Boolean
48+ isAtomicExpr = case _ of
49+ ExprIdent _ -> true
50+ ExprConstructor _ -> true
51+ ExprInt _ _ -> true
52+ ExprNumber _ _ -> true
53+ ExprString _ _ -> true
54+ ExprChar _ _ -> true
55+ ExprBoolean _ _ -> true
56+ ExprArray _ -> true
57+ ExprRecord _ -> true
58+ ExprParens _ -> true
59+ ExprRecordAccessor _ -> true
60+ _ -> false
61+
62+ -- Check if expression is a section (would change semantics with $)
63+ isSection :: Expr Void -> Boolean
64+ isSection = case _ of
65+ ExprSection _ -> true
66+ ExprParens (Wrapped { value }) -> isSection value
67+ _ -> false
68+
69+ mkWarning :: Expr Void -> Expr Void -> NEA.NonEmptyArray (AppSpine Expr Void) -> Expr Void -> LintWarning
70+ mkWarning outer fn args inner =
71+ let
72+ argsArray = NEA.toArray args
73+ initArgs = Array.dropEnd 1 argsArray
74+
75+ prefix = case initArgs of
76+ [] -> printExpr fn
77+ _ -> printExpr fn <> " " <> (initArgs # map printAppSpine # Array.intercalate " ")
78+
79+ replacement = prefix <> " $ " <> printExpr inner
80+ in
81+ LintWarning
82+ { ruleId: RuleId "UseDollar"
83+ , message: WarningMessage "Use $ instead of parentheses"
84+ , range: rangeOf outer
85+ , severity: Hint
86+ , suggestion: Just $ Suggestion
87+ { replacement: ReplacementText replacement
88+ , description: SuggestionDescription "Replace parentheses with $"
89+ , requiredImports: []
90+ }
91+ }
92+
93+ printAppSpine :: AppSpine Expr Void -> String
94+ printAppSpine = case _ of
95+ AppTerm e -> printExpr e
96+ AppType _ t -> "@" <> printType t
97+
lib/purslint/src/Purslint/Rules/UseEitherMap.pursnew file+117
@@ -0,0 +1,117 @@
1+module Purslint.Rules.UseEitherMap where
2+
3+import Prelude
4+
5+import Data.Array.NonEmpty as NEA
6+import Data.Foldable (foldMap)
7+import Data.Maybe (Maybe(..))
8+import Data.Tuple (Tuple(..))
9+import Data.Void (Void)
10+import PureScript.CST.Range (rangeOf)
11+import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
12+import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, Operator(..), QualifiedName(..), Wrapped(..))
13+import Purslint.Imports (ImportInfo, hasValue)
14+import Purslint.Print (printExpr)
15+import Purslint.Rule (Rule, RuleContext, mkRule)
16+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
17+
18+-- | Rule: either Left (Right <<< f) -> map f
19+useEitherMapRule :: Rule
20+useEitherMapRule = mkRule (RuleId "UseEitherMap") run
21+ where
22+ run :: RuleContext -> Module Void -> Array LintWarning
23+ run ctx = foldMapModule visitor
24+ where
25+ imports = ctx.imports
26+ visitor = defaultMonoidalVisitor { onExpr = checkExpr imports }
27+
28+ checkExpr :: ImportInfo -> Expr Void -> Array LintWarning
29+ checkExpr imports expr = case expr of
30+ -- Match: either Left (Right <<< f)
31+ ExprApp fn args
32+ | isEither imports fn ->
33+ case NEA.toArray args of
34+ [ AppTerm firstArg, AppTerm secondArg ] ->
35+ if isLeft imports firstArg then
36+ foldMap
37+ ( \f -> do
38+ let
39+ fText = printExpr f
40+ [ LintWarning
41+ { ruleId: RuleId "UseEitherMap"
42+ , message: WarningMessage "either Left (Right <<< f) can be simplified to map f"
43+ , range: rangeOf expr
44+ , severity: Warning
45+ , suggestion: Just $ Suggestion
46+ { replacement: ReplacementText ("map " <> fText)
47+ , description: SuggestionDescription "Use map instead of either Left (Right <<< f)"
48+ , requiredImports: []
49+ }
50+ }
51+ ]
52+ )
53+ (getRightComposed imports secondArg)
54+ else []
55+ [ AppTerm firstArg, AppTerm secondArg, AppTerm xArg ] ->
56+ if isLeft imports firstArg then
57+ case getRightComposed imports secondArg of
58+ Just f ->
59+ let
60+ fText = printExpr f
61+ x = printExpr xArg
62+ in
63+ [ LintWarning
64+ { ruleId: RuleId "UseEitherMap"
65+ , message: WarningMessage "either Left (Right <<< f) x can be simplified to map f x"
66+ , range: rangeOf expr
67+ , severity: Warning
68+ , suggestion: Just $ Suggestion
69+ { replacement: ReplacementText ("map " <> fText <> " " <> x)
70+ , description: SuggestionDescription "Use map instead of either Left (Right <<< f)"
71+ , requiredImports: []
72+ }
73+ }
74+ ]
75+ Nothing -> []
76+ else []
77+ _ -> []
78+ _ -> []
79+
80+ unwrapParens :: Expr Void -> Expr Void
81+ unwrapParens (ExprParens (Wrapped { value })) = unwrapParens value
82+ unwrapParens e = e
83+
84+ isEither :: ImportInfo -> Expr Void -> Boolean
85+ isEither imports (ExprIdent (QualifiedName { name: Ident name })) =
86+ name == "either" && hasValue imports "either"
87+ isEither _ _ = false
88+
89+ isLeft :: ImportInfo -> Expr Void -> Boolean
90+ isLeft imports e =
91+ case unwrapParens e of
92+ ExprIdent (QualifiedName { name: Ident name }) ->
93+ name == "Left" && hasValue imports "Left"
94+ ExprConstructor (QualifiedName { name: _ }) -> true -- Allow constructor syntax
95+ _ -> false
96+
97+ isRight :: ImportInfo -> Expr Void -> Boolean
98+ isRight imports e =
99+ case unwrapParens e of
100+ ExprIdent (QualifiedName { name: Ident name }) ->
101+ name == "Right" && hasValue imports "Right"
102+ ExprConstructor (QualifiedName { name: _ }) -> true
103+ _ -> false
104+
105+ -- Check for (Right <<< f) pattern
106+ getRightComposed :: ImportInfo -> Expr Void -> Maybe (Expr Void)
107+ getRightComposed imports e =
108+ case unwrapParens e of
109+ ExprOp lhs ops ->
110+ case NEA.toArray ops of
111+ [ Tuple (QualifiedName { name: Operator "<<<" }) rhs ]
112+ | isRight imports lhs -> Just rhs
113+ [ Tuple (QualifiedName { name: Operator ">>>" }) rhs ]
114+ | isRight imports rhs -> Just lhs
115+ _ -> Nothing
116+ _ -> Nothing
117+
lib/purelint/src/Purelint/Rules/UseElemIndex.purs → lib/purslint/src/Purslint/Rules/UseElemIndex.pursrenamed+8−5
@@ -1,4 +1,4 @@
1−module Purelint.Rules.UseElemIndex where
1+module Purslint.Rules.UseElemIndex where
22
33import Prelude
44
@@ -6,10 +6,10 @@ import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Tuple (Tuple(..))
88import Data.Void (Void)
9−import Purelint.Imports (ImportInfo, hasValue, hasOp)
10−import Purelint.Print (printExpr)
11−import Purelint.Rule (Rule, RuleContext, mkRule)
12−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
9+import Purslint.Imports (ImportInfo, hasValue, hasOp)
10+import Purslint.Print (printExpr)
11+import Purslint.Rule (Rule, RuleContext, mkRule)
12+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1313import PureScript.CST.Range (rangeOf)
1414import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1515import PureScript.CST.Types (AppSpine(..), Binder(..), Expr(..), Ident(..), Module, Name(..), Operator(..), QualifiedName(..), Wrapped(..))
@@ -44,6 +44,7 @@ useElemIndexRule = mkRule (RuleId "UseElemIndex") run
4444 , suggestion: Just $ Suggestion
4545 { replacement: ReplacementText ("elemIndex " <> aText)
4646 , description: SuggestionDescription "Use elemIndex instead of findIndex (== a)"
47+ , requiredImports: []
4748 }
4849 }
4950 ]
@@ -63,6 +64,7 @@ useElemIndexRule = mkRule (RuleId "UseElemIndex") run
6364 , suggestion: Just $ Suggestion
6465 { replacement: ReplacementText ("elemIndex " <> aText <> " " <> x)
6566 , description: SuggestionDescription "Use elemIndex instead of findIndex (== a)"
67+ , requiredImports: []
6668 }
6769 }
6870 ]
@@ -126,3 +128,4 @@ useElemIndexRule = mkRule (RuleId "UseElemIndex") run
126128 getBinderName (BinderVar (Name { name: Ident name })) = Just name
127129 getBinderName (BinderParens (Wrapped { value: inner })) = getBinderName inner
128130 getBinderName _ = Nothing
131+
lib/purelint/src/Purelint/Rules/UseFindMap.purs → lib/purslint/src/Purslint/Rules/UseFindMap.pursrenamed+13−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseFindMap where
1+module Purslint.Rules.UseFindMap where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -56,6 +56,13 @@ useFindMapRule = mkRule (RuleId "UseFindMap") run
5656 , suggestion: Just $ Suggestion
5757 { replacement: ReplacementText ("findMap " <> f <> " " <> x)
5858 , description: SuggestionDescription "Use findMap instead of head composed with mapMaybe"
59+ , requiredImports:
60+ [ { moduleName: "Data.Array"
61+ , importItem: Just "findMap"
62+ , codeText: Just "findMap"
63+ , qualifier: Nothing
64+ }
65+ ]
5966 }
6067 }
6168 ]
@@ -71,3 +78,4 @@ useFindMapRule = mkRule (RuleId "UseFindMap") run
7178 isMapMaybe imports (ExprIdent (QualifiedName { name: Ident name })) =
7279 name == "mapMaybe" && hasValue imports "mapMaybe"
7380 isMapMaybe _ _ = false
81+
lib/purelint/src/Purelint/Rules/UseFold.purs → lib/purslint/src/Purslint/Rules/UseFold.pursrenamed+6−4
@@ -1,13 +1,13 @@
1−module Purelint.Rules.UseFold where
1+module Purslint.Rules.UseFold where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Rule (Rule, RuleContext, mkRule)
10−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Rule (Rule, RuleContext, mkRule)
10+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1111import PureScript.CST.Range (rangeOf)
1212import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1313import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..))
@@ -36,6 +36,7 @@ useFoldRule = mkRule (RuleId "UseFold") run
3636 , suggestion: Just $ Suggestion
3737 { replacement: ReplacementText "fold"
3838 , description: SuggestionDescription "Use fold instead of foldMap identity"
39+ , requiredImports: []
3940 }
4041 }
4142 ]
@@ -51,3 +52,4 @@ useFoldRule = mkRule (RuleId "UseFold") run
5152 isIdentity imports (ExprIdent (QualifiedName { name: Ident name })) =
5253 name == "identity" && hasValue imports "identity"
5354 isIdentity _ _ = false
55+
lib/purelint/src/Purelint/Rules/UseFoldBool.purs → lib/purslint/src/Purslint/Rules/UseFoldBool.pursrenamed+6−4
@@ -1,12 +1,12 @@
1−module Purelint.Rules.UseFoldBool where
1+module Purslint.Rules.UseFoldBool where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
7−import Purelint.Imports (ImportInfo, hasValue)
8−import Purelint.Rule (Rule, RuleContext, mkRule)
9−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
7+import Purslint.Imports (ImportInfo, hasValue)
8+import Purslint.Rule (Rule, RuleContext, mkRule)
9+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1010import PureScript.CST.Range (rangeOf)
1111import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1212import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, Operator(..), QualifiedName(..))
@@ -84,5 +84,7 @@ useFoldBoolRule = mkRule (RuleId "UseFoldBool") run
8484 , suggestion: Just $ Suggestion
8585 { replacement: ReplacementText replacement
8686 , description: SuggestionDescription $ "Replace with " <> replacement
87+ , requiredImports: []
8788 }
8889 }
90+
lib/purslint/src/Purslint/Rules/UseFoldMap.pursnew file+167
@@ -0,0 +1,167 @@
1+module Purslint.Rules.UseFoldMap where
2+
3+import Prelude
4+
5+import Data.Array as Array
6+import Data.Array.NonEmpty as NEA
7+import Data.Maybe (Maybe(..))
8+import Data.Monoid (power)
9+import Data.Tuple (Tuple, snd)
10+import Data.Tuple.Nested ((/\))
11+import Data.String as String
12+import Purslint.Print (printExpr, printExprMultiline)
13+import Purslint.Rule (Rule, RuleContext, mkRule)
14+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
15+import PureScript.CST.Range (rangeOf)
16+import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
17+import PureScript.CST.Types (Binder(..), Expr(..), Guarded(..), Ident(..), Module, Name(..), Proper(..), QualifiedName(..), Separated(..), Where(..), Wrapped(..), AppSpine(..))
18+
19+-- | Rule: case mx of Nothing -> mempty; Just x -> f x --> foldMap f mx
20+-- | Also: case mx of Nothing -> ""; Just x -> f x --> foldMap f mx (String is Monoid)
21+useFoldMapRule :: Rule
22+useFoldMapRule = mkRule (RuleId "UseFoldMap") run
23+ where
24+ run :: RuleContext -> Module Void -> Array LintWarning
25+ run _ctx = foldMapModule visitor
26+ where
27+ visitor = defaultMonoidalVisitor { onExpr = checkExpr }
28+
29+ checkExpr :: Expr Void -> Array LintWarning
30+ checkExpr expr = case expr of
31+ ExprCase caseOf
32+ | [scrutinee] <- separatedToArray caseOf.head
33+ , [branch1, branch2] <- NEA.toArray caseOf.branches
34+ -> checkMaybeCase expr scrutinee branch1 branch2
35+ <> checkMaybeCase expr scrutinee branch2 branch1
36+ _ -> []
37+
38+ checkMaybeCase expr scrutinee nothingBranch justBranch
39+ | isNothingBranch nothingBranch
40+ , Just isDefinitelyMempty <- isMemptyResult nothingBranch
41+ , Just { var, body } <- getJustBranch justBranch
42+ = let
43+ -- Get the column where this expression starts for proper indentation
44+ exprCol = (rangeOf expr).start.column
45+ indent n = power " " n
46+
47+ -- Severity: Hint if definitely mempty, Refactor if [] (needs Monoid constraint)
48+ severity = if isDefinitelyMempty then Hint else Refactor
49+ messageNote = if isDefinitelyMempty then "" else " (requires Monoid on element type)"
50+
51+ -- Check if body is `f ... var` where last arg is our variable
52+ suggestion = case body of
53+ ExprApp fn args
54+ | argsArr <- NEA.toArray args
55+ , Just (AppTerm lastArg) <- Array.last argsArr
56+ , isVar var lastArg
57+ -> let
58+ remainingArgs = Array.dropEnd 1 argsArr
59+ fnPart = if Array.null remainingArgs
60+ then wrapIfNeeded fn
61+ else "(" <> printExpr fn <> " " <> Array.intercalate " " (map printAppSpine remainingArgs) <> ")"
62+ in "foldMap " <> fnPart <> " " <> wrapIfNeeded scrutinee
63+ -- Body uses var in a more complex way, need a lambda
64+ _ ->
65+ let
66+ bodyStr = printExprMultiline body
67+ bodyRange = rangeOf body
68+ isMultiline = bodyRange.start.line /= bodyRange.end.line
69+ in
70+ if isMultiline then
71+ -- Format multiline lambda with absolute indentation based on expression column
72+ let
73+ -- Base indent for continuation lines (where foldMap is)
74+ baseIndent = exprCol
75+ -- Lambda paren indent (2 more than foldMap)
76+ parenIndent = baseIndent + 2
77+ -- Body indent (4 more than paren = 6 more than foldMap)
78+ bodyIndent = baseIndent + 6
79+
80+ -- Normalize indentation: strip original indent, add target indent
81+ lines = String.split (String.Pattern "\n") bodyStr
82+ -- Find minimum non-empty line indent (skip first line since trim removes its indent)
83+ minIndent = case Array.tail lines of
84+ Nothing -> 0
85+ Just rest -> rest
86+ # Array.filter (\l -> String.trim l /= "")
87+ # map getIndent
88+ # Array.foldl min 999
89+ -- Strip minIndent from each line (except first) and add bodyIndent
90+ reindented = case Array.uncons lines of
91+ Nothing -> ""
92+ Just { head: first, tail: rest } ->
93+ let
94+ reindentedRest = rest # map (\l ->
95+ if String.trim l == "" then ""
96+ else indent bodyIndent <> String.drop minIndent l
97+ )
98+ in String.joinWith "\n" (Array.cons (indent bodyIndent <> first) reindentedRest)
99+ in
100+ "foldMap\n" <> indent parenIndent <> "( \\" <> var <> " ->\n" <> reindented <> "\n" <> indent parenIndent <> ")\n" <> indent parenIndent <> wrapIfNeeded scrutinee
101+ else
102+ "foldMap (\\" <> var <> " -> " <> printExpr body <> ") " <> wrapIfNeeded scrutinee
103+ in
104+ [ LintWarning
105+ { ruleId: RuleId "UseFoldMap"
106+ , message: WarningMessage $ "case of Nothing/Just with mempty can be simplified to foldMap" <> messageNote
107+ , range: rangeOf expr
108+ , severity: severity
109+ , suggestion: Just $ Suggestion
110+ { replacement: ReplacementText suggestion
111+ , description: SuggestionDescription "Use foldMap instead of case with Nothing -> mempty"
112+ , requiredImports: []
113+ }
114+ }
115+ ]
116+ | otherwise = []
117+
118+ printAppSpine :: AppSpine Expr Void -> String
119+ printAppSpine (AppTerm e) = printExpr e
120+ printAppSpine (AppType _ _) = "@_"
121+
122+ isNothingBranch branch = case branch of
123+ (Separated { head: BinderConstructor (QualifiedName { name: Proper "Nothing" }) [] }) /\ _ -> true
124+ _ -> false
125+
126+ -- Returns Just true if definitely mempty, Just false if [] (needs Monoid on element), Nothing if not mempty
127+ isMemptyResult :: Tuple (Separated (Binder Void)) (Guarded Void) -> Maybe Boolean
128+ isMemptyResult branch = case branch of
129+ _ /\ Unconditional _ (Where { expr: e, bindings: Nothing }) -> isMemptyCase (unwrapParens e)
130+ _ -> Nothing
131+
132+ -- Returns Just true if definitely mempty, Just false if [] (needs Monoid on element), Nothing if not mempty
133+ isMemptyCase :: Expr Void -> Maybe Boolean
134+ isMemptyCase (ExprIdent (QualifiedName { name: Ident "mempty" })) = Just true
135+ isMemptyCase (ExprString _ "") = Just true -- "" is mempty for String
136+ isMemptyCase (ExprArray (Wrapped { value: Nothing })) = Just false -- [] needs Monoid on element type
137+ isMemptyCase _ = Nothing
138+
139+ getJustBranch branch = case branch of
140+ (Separated { head: BinderConstructor (QualifiedName { name: Proper "Just" }) [BinderVar (Name { name: Ident var })] })
141+ /\ Unconditional _ (Where { expr: body, bindings: Nothing })
142+ -> Just { var, body }
143+ _ -> Nothing
144+
145+ isVar :: String -> Expr Void -> Boolean
146+ isVar name (ExprIdent (QualifiedName { name: Ident n })) = n == name
147+ isVar _ _ = false
148+
149+ unwrapParens :: Expr Void -> Expr Void
150+ unwrapParens (ExprParens (Wrapped { value })) = unwrapParens value
151+ unwrapParens e = e
152+
153+ wrapIfNeeded :: Expr Void -> String
154+ wrapIfNeeded e = case e of
155+ ExprIdent _ -> printExpr e
156+ ExprParens _ -> printExpr e
157+ _ -> "(" <> printExpr e <> ")"
158+
159+ separatedToArray :: forall a. Separated a -> Array a
160+ separatedToArray (Separated { head, tail }) = Array.cons head (map snd tail)
161+
162+ -- Get the number of leading spaces in a string
163+ getIndent :: String -> Int
164+ getIndent s =
165+ let loop i = if String.take 1 (String.drop i s) == " " then loop (i + 1) else i
166+ in loop 0
167+
lib/purelint/src/Purelint/Rules/UseFoldMapId.purs → lib/purslint/src/Purslint/Rules/UseFoldMapId.pursrenamed+8−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseFoldMapId where
1+module Purslint.Rules.UseFoldMapId where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -39,6 +39,7 @@ useFoldMapIdRule = mkRule (RuleId "UseFoldMapId") run
3939 , suggestion: Just $ Suggestion
4040 { replacement: ReplacementText "fold"
4141 , description: SuggestionDescription "Use fold instead of foldMap identity"
42+ , requiredImports: []
4243 }
4344 }
4445 ]
@@ -55,6 +56,7 @@ useFoldMapIdRule = mkRule (RuleId "UseFoldMapId") run
5556 , suggestion: Just $ Suggestion
5657 { replacement: ReplacementText ("fold " <> x)
5758 , description: SuggestionDescription "Use fold instead of foldMap identity"
59+ , requiredImports: []
5860 }
5961 }
6062 ]
@@ -75,3 +77,4 @@ useFoldMapIdRule = mkRule (RuleId "UseFoldMapId") run
7577 isIdentity imports (ExprIdent (QualifiedName { name: Ident name })) =
7678 (name == "identity" || name == "id") && hasValue imports "identity"
7779 isIdentity _ _ = false
80+
lib/purelint/src/Purelint/Rules/UseFor.purs → lib/purslint/src/Purslint/Rules/UseFor.pursrenamed+8−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseFor where
1+module Purslint.Rules.UseFor where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -51,6 +51,7 @@ useForRule = mkRule (RuleId "UseFor") run
5151 , suggestion: Just $ Suggestion
5252 { replacement: ReplacementText "for"
5353 , description: SuggestionDescription "Use for instead of flip traverse"
54+ , requiredImports: []
5455 }
5556 }
5657 ]
@@ -63,6 +64,7 @@ useForRule = mkRule (RuleId "UseFor") run
6364 , suggestion: Just $ Suggestion
6465 { replacement: ReplacementText "for_"
6566 , description: SuggestionDescription "Use for_ instead of flip traverse_"
67+ , requiredImports: []
6668 }
6769 }
6870 ]
@@ -72,3 +74,4 @@ useForRule = mkRule (RuleId "UseFor") run
7274 isFlip imports (ExprIdent (QualifiedName { name: Ident name })) =
7375 name == "flip" && hasValue imports "flip"
7476 isFlip _ _ = false
77+
lib/purelint/src/Purelint/Rules/UseFromJust.purs → lib/purslint/src/Purslint/Rules/UseFromJust.pursrenamed+9−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseFromJust where
1+module Purslint.Rules.UseFromJust where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, Proper(..), QualifiedName(..), Wrapped(..))
@@ -43,6 +43,7 @@ useFromJustRule = mkRule (RuleId "UseFromJust") run
4343 , suggestion: Just $ Suggestion
4444 { replacement: ReplacementText xText
4545 , description: SuggestionDescription "Simplify fromMaybe with Just"
46+ , requiredImports: []
4647 }
4748 }
4849 ]
@@ -59,6 +60,7 @@ useFromJustRule = mkRule (RuleId "UseFromJust") run
5960 , suggestion: Just $ Suggestion
6061 { replacement: ReplacementText d
6162 , description: SuggestionDescription "Simplify fromMaybe with Nothing"
63+ , requiredImports: []
6264 }
6365 }
6466 ]
@@ -73,6 +75,7 @@ useFromJustRule = mkRule (RuleId "UseFromJust") run
7375 , suggestion: Just $ Suggestion
7476 { replacement: ReplacementText d
7577 , description: SuggestionDescription "Simplify fromMaybe with Nothing"
78+ , requiredImports: []
7679 }
7780 }
7881 ]
@@ -102,3 +105,4 @@ useFromJustRule = mkRule (RuleId "UseFromJust") run
102105 _ -> Nothing
103106 _ -> Nothing
104107 getJustArg _ = Nothing
108+
lib/purelint/src/Purelint/Rules/UseFromMaybe.purs → lib/purslint/src/Purslint/Rules/UseFromMaybe.pursrenamed+7−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseFromMaybe where
1+module Purslint.Rules.UseFromMaybe where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..))
@@ -39,6 +39,7 @@ useFromMaybeRule = mkRule (RuleId "UseFromMaybe") run
3939 , suggestion: Just $ Suggestion
4040 { replacement: ReplacementText ("fromMaybe " <> defaultText)
4141 , description: SuggestionDescription "Use fromMaybe instead of maybe x id"
42+ , requiredImports: []
4243 }
4344 }
4445 ]
@@ -54,3 +55,4 @@ useFromMaybeRule = mkRule (RuleId "UseFromMaybe") run
5455 isId imports (ExprIdent (QualifiedName { name: Ident name })) =
5556 name == "identity" && hasValue imports "identity"
5657 isId _ _ = false
58+
lib/purelint/src/Purelint/Rules/UseFstSnd.purs → lib/purslint/src/Purslint/Rules/UseFstSnd.pursrenamed+6−3
@@ -1,12 +1,12 @@
1−module Purelint.Rules.UseFstSnd where
1+module Purslint.Rules.UseFstSnd where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Rule (Rule, RuleContext, mkRule)
9−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Rule (Rule, RuleContext, mkRule)
9+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1010import PureScript.CST.Range (rangeOf)
1111import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1212import PureScript.CST.Types (Binder(..), Expr(..), Ident(..), Module, Name(..), Proper(..), QualifiedName(..), Wrapped(..))
@@ -40,6 +40,7 @@ useFstSndRule = mkRule (RuleId "UseFstSnd") run
4040 , suggestion: Just $ Suggestion
4141 { replacement: ReplacementText "fst"
4242 , description: SuggestionDescription "Use fst instead of lambda extracting first element"
43+ , requiredImports: []
4344 }
4445 }
4546 ]
@@ -52,6 +53,7 @@ useFstSndRule = mkRule (RuleId "UseFstSnd") run
5253 , suggestion: Just $ Suggestion
5354 { replacement: ReplacementText "snd"
5455 , description: SuggestionDescription "Use snd instead of lambda extracting second element"
56+ , requiredImports: []
5557 }
5658 }
5759 ]
@@ -77,3 +79,4 @@ useFstSndRule = mkRule (RuleId "UseFstSnd") run
7779 getBinderName (BinderWildcard _) = Nothing -- wildcards can't be referenced in body
7880 getBinderName (BinderParens (Wrapped { value: inner })) = getBinderName inner
7981 getBinderName _ = Nothing
82+
lib/purelint/src/Purelint/Rules/UseGuard.purs → lib/purslint/src/Purslint/Rules/UseGuard.pursrenamed+8−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseGuard where
1+module Purslint.Rules.UseGuard where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -54,6 +54,7 @@ useGuardRule = mkRule (RuleId "UseGuard") run
5454 , suggestion: Just $ Suggestion
5555 { replacement: ReplacementText replacement
5656 , description: SuggestionDescription "if cond then pure unit else x can be replaced with unless cond x"
57+ , requiredImports: []
5758 }
5859 }
5960 ]
@@ -71,6 +72,7 @@ useGuardRule = mkRule (RuleId "UseGuard") run
7172 , suggestion: Just $ Suggestion
7273 { replacement: ReplacementText replacement
7374 , description: SuggestionDescription "if cond then x else pure unit can be replaced with when cond x"
75+ , requiredImports: []
7476 }
7577 }
7678 ]
@@ -97,3 +99,4 @@ useGuardRule = mkRule (RuleId "UseGuard") run
9799 isUnit :: Expr Void -> Boolean
98100 isUnit (ExprIdent (QualifiedName { name: Ident name })) = name == "unit"
99101 isUnit _ = false
102+
lib/purelint/src/Purelint/Rules/UseGuardMaybe.purs → lib/purslint/src/Purslint/Rules/UseGuardMaybe.pursrenamed+7−5
@@ -1,13 +1,13 @@
1−module Purelint.Rules.UseGuardMaybe where
1+module Purslint.Rules.UseGuardMaybe where
22
33import Prelude
44
55import Data.Maybe (Maybe(..))
66import Data.Void (Void)
7−import Purelint.Imports (ImportInfo, hasValue)
8−import Purelint.Print (printExpr)
9−import Purelint.Rule (Rule, RuleContext, mkRule)
10−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
7+import Purslint.Imports (ImportInfo, hasValue)
8+import Purslint.Print (printExpr)
9+import Purslint.Rule (Rule, RuleContext, mkRule)
10+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1111import PureScript.CST.Range (rangeOf)
1212import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1313import PureScript.CST.Types (Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -42,6 +42,7 @@ useGuardMaybeRule = mkRule (RuleId "UseGuardMaybe") run
4242 , suggestion: Just $ Suggestion
4343 { replacement: ReplacementText ("guard " <> c <> " $> " <> x)
4444 , description: SuggestionDescription "Use guard c $> x instead of if-then-else with Maybe"
45+ , requiredImports: []
4546 }
4647 }
4748 ]
@@ -75,3 +76,4 @@ useGuardMaybeRule = mkRule (RuleId "UseGuardMaybe") run
7576 isNothing imports (ExprConstructor (QualifiedName { name: _ })) =
7677 hasValue imports "Nothing"
7778 isNothing _ _ = false
79+
lib/purelint/src/Purelint/Rules/UseHead.purs → lib/purslint/src/Purslint/Rules/UseHead.pursrenamed+6−4
@@ -1,12 +1,12 @@
1−module Purelint.Rules.UseHead where
1+module Purslint.Rules.UseHead where
22
33import Prelude
44
55import Data.Maybe (Maybe(..))
66import Data.Void (Void)
7−import Purelint.Imports (ImportInfo, hasValue)
8−import Purelint.Rule (Rule, RuleContext, mkRule)
9−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
7+import Purslint.Imports (ImportInfo, hasValue)
8+import Purslint.Rule (Rule, RuleContext, mkRule)
9+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1010import PureScript.CST.Range (rangeOf)
1111import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1212import PureScript.CST.Types (Expr(..), Ident(..), IntValue(..), Module, Operator(..), QualifiedName(..), Wrapped(..))
@@ -39,6 +39,7 @@ useHeadRule = mkRule (RuleId "UseHead") run
3939 , suggestion: Just $ Suggestion
4040 { replacement: ReplacementText "head"
4141 , description: SuggestionDescription "Use head instead of !! 0"
42+ , requiredImports: []
4243 }
4344 }
4445 ]
@@ -54,3 +55,4 @@ useHeadRule = mkRule (RuleId "UseHead") run
5455 isZeroLiteral (ExprInt _ (SmallInt 0)) = true
5556 isZeroLiteral (ExprInt _ (BigInt s)) = s == "0"
5657 isZeroLiteral _ = false
58+
lib/purelint/src/Purelint/Rules/UseIsJust.purs → lib/purslint/src/Purslint/Rules/UseIsJust.pursrenamed+6−4
@@ -1,13 +1,13 @@
1−module Purelint.Rules.UseIsJust where
1+module Purslint.Rules.UseIsJust where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Rule (Rule, RuleContext, mkRule)
10−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Rule (Rule, RuleContext, mkRule)
10+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1111import PureScript.CST.Range (rangeOf)
1212import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1313import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -37,6 +37,7 @@ useIsJustRule = mkRule (RuleId "UseIsJust") run
3737 , suggestion: Just $ Suggestion
3838 { replacement: ReplacementText "isJust"
3939 , description: SuggestionDescription "Use isJust instead of maybe false (const true)"
40+ , requiredImports: []
4041 }
4142 }
4243 ]
@@ -66,3 +67,4 @@ useIsJustRule = mkRule (RuleId "UseIsJust") run
6667 isTrue :: Expr Void -> Boolean
6768 isTrue (ExprBoolean _ b) = b
6869 isTrue _ = false
70+
lib/purelint/src/Purelint/Rules/UseIsNothing.purs → lib/purslint/src/Purslint/Rules/UseIsNothing.pursrenamed+6−4
@@ -1,13 +1,13 @@
1−module Purelint.Rules.UseIsNothing where
1+module Purslint.Rules.UseIsNothing where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Rule (Rule, RuleContext, mkRule)
10−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Rule (Rule, RuleContext, mkRule)
10+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1111import PureScript.CST.Range (rangeOf)
1212import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1313import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -37,6 +37,7 @@ useIsNothingRule = mkRule (RuleId "UseIsNothing") run
3737 , suggestion: Just $ Suggestion
3838 { replacement: ReplacementText "isNothing"
3939 , description: SuggestionDescription "Use isNothing instead of maybe true (const false)"
40+ , requiredImports: []
4041 }
4142 }
4243 ]
@@ -66,3 +67,4 @@ useIsNothingRule = mkRule (RuleId "UseIsNothing") run
6667 isFalse :: Expr Void -> Boolean
6768 isFalse (ExprBoolean _ b) = not b
6869 isFalse _ = false
70+
lib/purelint/src/Purelint/Rules/UseJoin.purs → lib/purslint/src/Purslint/Rules/UseJoin.pursrenamed+8−5
@@ -1,4 +1,4 @@
1−module Purelint.Rules.UseJoin where
1+module Purslint.Rules.UseJoin where
22
33import Prelude
44
@@ -6,10 +6,10 @@ import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Tuple (Tuple(..))
88import Data.Void (Void)
9−import Purelint.Imports (ImportInfo, hasValue, hasOp)
10−import Purelint.Print (printExpr)
11−import Purelint.Rule (Rule, RuleContext, mkRule)
12−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
9+import Purslint.Imports (ImportInfo, hasValue, hasOp)
10+import Purslint.Print (printExpr)
11+import Purslint.Rule (Rule, RuleContext, mkRule)
12+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1313import PureScript.CST.Range (rangeOf)
1414import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1515import PureScript.CST.Types (Expr(..), Ident(..), Module, Operator(..), QualifiedName(..))
@@ -40,6 +40,7 @@ useJoinRule = mkRule (RuleId "UseJoin") run
4040 , suggestion: Just $ Suggestion
4141 { replacement: ReplacementText ("join " <> xText)
4242 , description: SuggestionDescription "x >>= identity can be simplified to join x"
43+ , requiredImports: []
4344 }
4445 }
4546 ]
@@ -58,6 +59,7 @@ useJoinRule = mkRule (RuleId "UseJoin") run
5859 , suggestion: Just $ Suggestion
5960 { replacement: ReplacementText ("join " <> xText)
6061 , description: SuggestionDescription "identity =<< x can be simplified to join x"
62+ , requiredImports: []
6163 }
6264 }
6365 ]
@@ -76,3 +78,4 @@ useJoinRule = mkRule (RuleId "UseJoin") run
7678 isIdentity imports (ExprIdent (QualifiedName { name: Ident name })) =
7779 (name == "identity" && hasValue imports "identity") || (name == "id" && hasValue imports "id")
7880 isIdentity _ _ = false
81+
lib/purelint/src/Purelint/Rules/UseLastReverse.purs → lib/purslint/src/Purslint/Rules/UseLastReverse.pursrenamed+7−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseLastReverse where
1+module Purslint.Rules.UseLastReverse where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -50,6 +50,7 @@ useLastReverseRule = mkRule (RuleId "UseLastReverse") run
5050 , suggestion: Just $ Suggestion
5151 { replacement: ReplacementText (newFn <> " " <> x)
5252 , description: SuggestionDescription $ "Use " <> newFn <> " instead"
53+ , requiredImports: []
5354 }
5455 }
5556 ]
@@ -76,3 +77,4 @@ useLastReverseRule = mkRule (RuleId "UseLastReverse") run
7677 isReverse imports (ExprIdent (QualifiedName { name: Ident name })) =
7778 name == "reverse" && hasValue imports "reverse"
7879 isReverse _ _ = false
80+
lib/purelint/src/Purelint/Rules/UseMapMaybe.purs → lib/purslint/src/Purslint/Rules/UseMapMaybe.pursrenamed+13−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseMapMaybe where
1+module Purslint.Rules.UseMapMaybe where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -56,6 +56,13 @@ useMapMaybeRule = mkRule (RuleId "UseMapMaybe") run
5656 , suggestion: Just $ Suggestion
5757 { replacement: ReplacementText ("mapMaybe " <> f <> " " <> x)
5858 , description: SuggestionDescription "Use mapMaybe instead of catMaybes composed with map"
59+ , requiredImports:
60+ [ { moduleName: "Data.Array"
61+ , importItem: Just "mapMaybe"
62+ , codeText: Just "mapMaybe"
63+ , qualifier: Nothing
64+ }
65+ ]
5966 }
6067 }
6168 ]
@@ -71,3 +78,4 @@ useMapMaybeRule = mkRule (RuleId "UseMapMaybe") run
7178 isMap imports (ExprIdent (QualifiedName { name: Ident name })) =
7279 name == "map" && hasValue imports "map"
7380 isMap _ _ = false
81+
lib/purelint/src/Purelint/Rules/UseMaybeMap.purs → lib/purslint/src/Purslint/Rules/UseMaybeMap.pursrenamed+5−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseMaybeMap where
1+module Purslint.Rules.UseMaybeMap where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
lib/purelint/src/Purelint/Rules/UseMinMax.purs → lib/purslint/src/Purslint/Rules/UseMinMax.pursrenamed+7−5
@@ -1,13 +1,13 @@
1−module Purelint.Rules.UseMinMax where
1+module Purslint.Rules.UseMinMax where
22
33import Prelude
44
55import Data.Maybe (Maybe(..))
66import Data.Void (Void)
7−import Purelint.Imports (ImportInfo, hasOp)
8−import Purelint.Print (printExpr)
9−import Purelint.Rule (Rule, RuleContext, mkRule)
10−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
7+import Purslint.Imports (ImportInfo, hasOp)
8+import Purslint.Print (printExpr)
9+import Purslint.Rule (Rule, RuleContext, mkRule)
10+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1111import PureScript.CST.Range (rangeOf)
1212import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1313import PureScript.CST.Types (Expr(..), Ident(..), Module, Operator(..), QualifiedName(..), Wrapped(..))
@@ -64,6 +64,7 @@ useMinMaxRule = mkRule (RuleId "UseMinMax") run
6464 , suggestion: Just $ Suggestion
6565 { replacement: ReplacementText (fn <> " " <> a <> " " <> b)
6666 , description: SuggestionDescription ("Use " <> fn <> " instead of if/else with comparison")
67+ , requiredImports: []
6768 }
6869 }
6970
@@ -76,3 +77,4 @@ useMinMaxRule = mkRule (RuleId "UseMinMax") run
7677 _ -> Nothing
7778 getComparison imports (ExprParens (Wrapped { value })) = getComparison imports value
7879 getComparison _ _ = Nothing
80+
lib/purelint/src/Purelint/Rules/UseMinimumSort.purs → lib/purslint/src/Purslint/Rules/UseMinimumSort.pursrenamed+7−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseMinimumSort where
1+module Purslint.Rules.UseMinimumSort where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -50,6 +50,7 @@ useMinimumSortRule = mkRule (RuleId "UseMinimumSort") run
5050 , suggestion: Just $ Suggestion
5151 { replacement: ReplacementText (newFn <> " " <> x)
5252 , description: SuggestionDescription $ "Use " <> newFn <> " instead of sorting"
53+ , requiredImports: []
5354 }
5455 }
5556 ]
@@ -76,3 +77,4 @@ useMinimumSortRule = mkRule (RuleId "UseMinimumSort") run
7677 isSort imports (ExprIdent (QualifiedName { name: Ident name })) =
7778 name == "sort" && hasValue imports "sort"
7879 isSort _ _ = false
80+
lib/purelint/src/Purelint/Rules/UseNotElem.purs → lib/purslint/src/Purslint/Rules/UseNotElem.pursrenamed+7−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseNotElem where
1+module Purslint.Rules.UseNotElem where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -42,6 +42,7 @@ useNotElemRule = mkRule (RuleId "UseNotElem") run
4242 , suggestion: Just $ Suggestion
4343 { replacement: ReplacementText ("notElem " <> xText <> " " <> yText)
4444 , description: SuggestionDescription "Use notElem instead of not (elem ...)"
45+ , requiredImports: []
4546 }
4647 }
4748 ]
@@ -64,3 +65,4 @@ useNotElemRule = mkRule (RuleId "UseNotElem") run
6465 _ -> Nothing
6566 getElemArgs imports (ExprParens (Wrapped { value })) = getElemArgs imports value
6667 getElemArgs _ _ = Nothing
68+
lib/purelint/src/Purelint/Rules/UseNull.purs → lib/purslint/src/Purslint/Rules/UseNull.pursrenamed+7−5
@@ -1,4 +1,4 @@
1−module Purelint.Rules.UseNull where
1+module Purslint.Rules.UseNull where
22
33import Prelude
44
@@ -6,10 +6,10 @@ import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Tuple (Tuple(..))
88import Data.Void (Void)
9−import Purelint.Imports (ImportInfo, hasValue, hasOp)
10−import Purelint.Print (printExpr)
11−import Purelint.Rule (Rule, RuleContext, mkRule)
12−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
9+import Purslint.Imports (ImportInfo, hasValue, hasOp)
10+import Purslint.Print (printExpr)
11+import Purslint.Rule (Rule, RuleContext, mkRule)
12+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1313import PureScript.CST.Range (rangeOf)
1414import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1515import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), IntValue(..), Module, Operator(..), QualifiedName(..))
@@ -57,6 +57,7 @@ useNullRule = mkRule (RuleId "UseNull") run
5757 , suggestion: Just $ Suggestion
5858 { replacement: ReplacementText ("null " <> xText)
5959 , description: SuggestionDescription $ from <> " can be simplified to null x"
60+ , requiredImports: []
6061 }
6162 }
6263
@@ -77,3 +78,4 @@ useNullRule = mkRule (RuleId "UseNull") run
7778 _ -> Nothing
7879 _ -> Nothing
7980 getLengthArg _ _ = Nothing
81+
lib/purelint/src/Purelint/Rules/UseOn.purs → lib/purslint/src/Purslint/Rules/UseOn.pursrenamed+7−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseOn where
1+module Purslint.Rules.UseOn where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Binder(..), Expr(..), Ident(..), Module, Name(..), QualifiedName(..), Wrapped(..))
@@ -54,6 +54,7 @@ useOnRule = mkRule (RuleId "UseOn") run
5454 , suggestion: Just $ Suggestion
5555 { replacement: ReplacementText ("on " <> f <> " " <> g1)
5656 , description: SuggestionDescription "Use on instead of lambda applying g to both arguments"
57+ , requiredImports: []
5758 }
5859 }
5960 ]
@@ -70,3 +71,4 @@ useOnRule = mkRule (RuleId "UseOn") run
7071 _ -> Nothing
7172 _, _ -> Nothing
7273 extractFunctionCall _ = Nothing
74+
lib/purelint/src/Purelint/Rules/UseOr.purs → lib/purslint/src/Purslint/Rules/UseOr.pursrenamed+8−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseOr where
1+module Purslint.Rules.UseOr where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -40,6 +40,7 @@ useOrRule = mkRule (RuleId "UseOr") run
4040 , suggestion: Just $ Suggestion
4141 { replacement: ReplacementText "or"
4242 , description: SuggestionDescription "Use or instead of any identity"
43+ , requiredImports: []
4344 }
4445 }
4546 ]
@@ -56,6 +57,7 @@ useOrRule = mkRule (RuleId "UseOr") run
5657 , suggestion: Just $ Suggestion
5758 { replacement: ReplacementText ("or " <> x)
5859 , description: SuggestionDescription "Use or instead of any identity"
60+ , requiredImports: []
5961 }
6062 }
6163 ]
@@ -76,3 +78,4 @@ useOrRule = mkRule (RuleId "UseOr") run
7678 isIdentity imports (ExprIdent (QualifiedName { name: Ident name })) =
7779 (name == "identity" || name == "id") && hasValue imports "identity"
7880 isIdentity _ _ = false
81+
lib/purelint/src/Purelint/Rules/UsePatternGuards.purs → lib/purslint/src/Purslint/Rules/UsePatternGuards.pursrenamed+6−4
@@ -1,4 +1,4 @@
1−module Purelint.Rules.UsePatternGuards where
1+module Purslint.Rules.UsePatternGuards where
22
33import Prelude
44
@@ -15,9 +15,9 @@ import PureScript.CST.Range (rangeOf, tokensOf)
1515import PureScript.CST.Range.TokenList as TokenList
1616import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1717import PureScript.CST.Types (Binder(..), Expr(..), Guarded(..), GuardedExpr(..), Module, PatternGuard(..), Separated(..), Where(..), Wrapped(..))
18−import Purelint.Print (printBinder, printExpr, printExprMultiline)
19−import Purelint.Rule (Rule, RuleContext, mkRule)
20−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
18+import Purslint.Print (printBinder, printExpr, printExprMultiline)
19+import Purslint.Rule (Rule, RuleContext, mkRule)
20+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
2121
2222-- | Rule: Nested case expressions with single match + fallback can use pattern guards
2323-- |
@@ -72,6 +72,7 @@ usePatternGuardsRule = mkRule (RuleId "UsePatternGuards") run
7272 , suggestion: Just $ Suggestion
7373 { replacement: ReplacementText replacement
7474 , description: SuggestionDescription $ "Use pattern guards instead of " <> show result.depth <> " nested case expressions"
75+ , requiredImports: []
7576 }
7677 }
7778 ]
@@ -227,3 +228,4 @@ usePatternGuardsRule = mkRule (RuleId "UsePatternGuards") run
227228 _ -> Nothing
228229 _ -> Nothing
229230 getMatchPatternAndBody _ = Nothing
231+
lib/purelint/src/Purelint/Rules/UseReplicate.purs → lib/purslint/src/Purslint/Rules/UseReplicate.pursrenamed+13−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseReplicate where
1+module Purslint.Rules.UseReplicate where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -44,6 +44,13 @@ useReplicateRule = mkRule (RuleId "UseReplicate") run
4444 , suggestion: Just $ Suggestion
4545 { replacement: ReplacementText ("replicate " <> n <> " " <> xText)
4646 , description: SuggestionDescription "Use replicate instead of take n (repeat x)"
47+ , requiredImports:
48+ [ { moduleName: "Data.Array"
49+ , importItem: Just "replicate"
50+ , codeText: Just "replicate"
51+ , qualifier: Nothing
52+ }
53+ ]
4754 }
4855 }
4956 ]
@@ -74,3 +81,4 @@ useReplicateRule = mkRule (RuleId "UseReplicate") run
7481 [AppTerm arg] -> Just arg
7582 _ -> Nothing
7683 _ -> Nothing
84+
lib/purelint/src/Purelint/Rules/UseSequence.purs → lib/purslint/src/Purslint/Rules/UseSequence.pursrenamed+12−4
@@ -1,13 +1,13 @@
1−module Purelint.Rules.UseSequence where
1+module Purslint.Rules.UseSequence where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Rule (Rule, RuleContext, mkRule)
10−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Rule (Rule, RuleContext, mkRule)
10+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1111import PureScript.CST.Range (rangeOf)
1212import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1313import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..))
@@ -37,6 +37,13 @@ useSequenceRule = mkRule (RuleId "UseSequence") run
3737 , suggestion: Just $ Suggestion
3838 { replacement: ReplacementText "sequence"
3939 , description: SuggestionDescription "Use sequence instead of traverse identity"
40+ , requiredImports:
41+ [ { moduleName: "Data.Traversable"
42+ , importItem: Just "sequence"
43+ , codeText: Just "sequence"
44+ , qualifier: Nothing
45+ }
46+ ]
4047 }
4148 }
4249 ]
@@ -52,3 +59,4 @@ useSequenceRule = mkRule (RuleId "UseSequence") run
5259 isIdentity imports (ExprIdent (QualifiedName { name: Ident name })) =
5360 name == "identity" && hasValue imports "identity"
5461 isIdentity _ _ = false
62+
lib/purelint/src/Purelint/Rules/UseSpan.purs → lib/purslint/src/Purslint/Rules/UseSpan.pursrenamed+8−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseSpan where
1+module Purslint.Rules.UseSpan where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -44,6 +44,7 @@ useSpanRule = mkRule (RuleId "UseSpan") run
4444 , suggestion: Just $ Suggestion
4545 { replacement: ReplacementText ("span " <> pText <> " " <> x)
4646 , description: SuggestionDescription "Use span instead of break with negated predicate"
47+ , requiredImports: []
4748 }
4849 }
4950 ]
@@ -61,6 +62,7 @@ useSpanRule = mkRule (RuleId "UseSpan") run
6162 , suggestion: Just $ Suggestion
6263 { replacement: ReplacementText ("span " <> pText)
6364 , description: SuggestionDescription "Use span instead of break with negated predicate"
65+ , requiredImports: []
6466 }
6567 }
6668 ]
@@ -91,3 +93,4 @@ useSpanRule = mkRule (RuleId "UseSpan") run
9193 [AppTerm p] -> Just p
9294 _ -> Nothing
9395 _ -> Nothing
96+
lib/purelint/src/Purelint/Rules/UseTraverse.purs → lib/purslint/src/Purslint/Rules/UseTraverse.pursrenamed+17−5
@@ -1,4 +1,4 @@
1−module Purelint.Rules.UseTraverse where
1+module Purslint.Rules.UseTraverse where
22
33import Prelude
44
@@ -6,10 +6,10 @@ import Data.Array as Array
66import Data.Array.NonEmpty as NEA
77import Data.Maybe (Maybe(..))
88import Data.Tuple (Tuple(..))
9−import Purelint.Imports (ImportInfo, hasValue, hasOp)
10−import Purelint.Print (printExpr)
11−import Purelint.Rule (Rule, RuleContext, mkRule)
12−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
9+import Purslint.Imports (ImportInfo, hasValue, hasOp)
10+import Purslint.Print (printExpr)
11+import Purslint.Rule (Rule, RuleContext, mkRule)
12+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1313import PureScript.CST.Range (rangeOf)
1414import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1515import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, Operator(..), QualifiedName(..), Wrapped(..))
@@ -69,6 +69,8 @@ useTraverseRule = mkRule (RuleId "UseTraverse") run
6969 replacement = if seqName == "sequenceA" || seqName == "sequenceA_" then "traverse" else "mapM"
7070 replacementSuffix = if seqName == "sequenceA_" || seqName == "sequence_" then "_" else ""
7171 concreteReplacement = replacement <> replacementSuffix <> " " <> fText <> " " <> xText
72+ moduleName = if replacement == "mapM" then "Control.Monad" else "Data.Traversable"
73+ importItem = replacement <> replacementSuffix
7274 in
7375 [ LintWarning
7476 { ruleId: RuleId "UseTraverse"
@@ -78,6 +80,15 @@ useTraverseRule = mkRule (RuleId "UseTraverse") run
7880 , suggestion: Just $ Suggestion
7981 { replacement: ReplacementText concreteReplacement
8082 , description: SuggestionDescription $ seqName <> " (map f x) can be replaced with " <> replacement <> replacementSuffix <> " f x"
83+ , requiredImports:
84+ if replacement == "mapM" then []
85+ else
86+ [ { moduleName
87+ , importItem: Just importItem
88+ , codeText: Just importItem
89+ , qualifier: Nothing
90+ }
91+ ]
8192 }
8293 }
8394 ]
@@ -124,3 +135,4 @@ useTraverseRule = mkRule (RuleId "UseTraverse") run
124135
125136 getIdentName :: QualifiedName Ident -> String
126137 getIdentName (QualifiedName { name: Ident name }) = name
138+
lib/purelint/src/Purelint/Rules/UseTraverseSequence.purs → lib/purslint/src/Purslint/Rules/UseTraverseSequence.pursrenamed+13−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseTraverseSequence where
1+module Purslint.Rules.UseTraverseSequence where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -42,6 +42,13 @@ useTraverseSequenceRule = mkRule (RuleId "UseTraverseSequence") run
4242 , suggestion: Just $ Suggestion
4343 { replacement: ReplacementText ("traverse " <> fText <> " " <> xText)
4444 , description: SuggestionDescription "Use traverse instead of sequence (map ...)"
45+ , requiredImports:
46+ [ { moduleName: "Data.Traversable"
47+ , importItem: Just "traverse"
48+ , codeText: Just "traverse"
49+ , qualifier: Nothing
50+ }
51+ ]
4552 }
4653 }
4754 ]
@@ -64,3 +71,4 @@ useTraverseSequenceRule = mkRule (RuleId "UseTraverseSequence") run
6471 _ -> Nothing
6572 getMapArgs imports (ExprParens (Wrapped { value })) = getMapArgs imports value
6673 getMapArgs _ _ = Nothing
74+
lib/purelint/src/Purelint/Rules/UseUncurry.purs → lib/purslint/src/Purslint/Rules/UseUncurry.pursrenamed+7−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseUncurry where
1+module Purslint.Rules.UseUncurry where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, Proper(..), QualifiedName(..), Separated(..), Wrapped(..))
@@ -47,6 +47,7 @@ useUncurryRule = mkRule (RuleId "UseUncurry") run
4747 , suggestion: Just $ Suggestion
4848 { replacement: ReplacementText ("uncurry " <> f <> " " <> p1Text)
4949 , description: SuggestionDescription "Use uncurry instead of fst/snd"
50+ , requiredImports: []
5051 }
5152 }
5253 ]
@@ -88,3 +89,4 @@ useUncurryRule = mkRule (RuleId "UseUncurry") run
8889 [AppTerm arg] -> Just arg
8990 _ -> Nothing
9091 _ -> Nothing
92+
lib/purelint/src/Purelint/Rules/UseUnless.purs → lib/purslint/src/Purslint/Rules/UseUnless.pursrenamed+7−5
@@ -1,13 +1,13 @@
1−module Purelint.Rules.UseUnless where
1+module Purslint.Rules.UseUnless where
22
33import Prelude
44
55import Data.Maybe (Maybe(..))
66import Data.Void (Void)
7−import Purelint.Imports (ImportInfo, hasValue)
8−import Purelint.Print (printExpr)
9−import Purelint.Rule (Rule, RuleContext, mkRule)
10−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
7+import Purslint.Imports (ImportInfo, hasValue)
8+import Purslint.Print (printExpr)
9+import Purslint.Rule (Rule, RuleContext, mkRule)
10+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1111import PureScript.CST.Range (rangeOf)
1212import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1313import PureScript.CST.Types (Expr(..), Ident(..), Module, QualifiedName(..))
@@ -38,6 +38,7 @@ useUnlessRule = mkRule (RuleId "UseUnless") run
3838 , suggestion: Just $ Suggestion
3939 { replacement: ReplacementText ("unless " <> condText <> " " <> actionText)
4040 , description: SuggestionDescription "Use unless instead of if/else with pure unit"
41+ , requiredImports: []
4142 }
4243 }
4344 ]
@@ -53,3 +54,4 @@ useUnlessRule = mkRule (RuleId "UseUnless") run
5354 isPure imports (ExprIdent (QualifiedName { name: Ident name })) =
5455 (name == "pure" && hasValue imports "pure") || (name == "return" && hasValue imports "return")
5556 isPure _ _ = false
57+
lib/purelint/src/Purelint/Rules/UseUnwrap.purs → lib/purslint/src/Purslint/Rules/UseUnwrap.pursrenamed+7−4
@@ -1,13 +1,13 @@
1−module Purelint.Rules.UseUnwrap where
1+module Purslint.Rules.UseUnwrap where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Print (printExpr)
9−import Purelint.Rule (Rule, RuleContext, mkRule)
10−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Print (printExpr)
9+import Purslint.Rule (Rule, RuleContext, mkRule)
10+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1111import PureScript.CST.Range (rangeOf)
1212import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1313import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..))
@@ -40,6 +40,7 @@ useUnwrapRule = mkRule (RuleId "UseUnwrap") run
4040 , suggestion: Just $ Suggestion
4141 { replacement: ReplacementText replacement
4242 , description: SuggestionDescription "Use un with typed hole to find the right accessor"
43+ , requiredImports: []
4344 }
4445 }
4546 ]
@@ -54,6 +55,7 @@ useUnwrapRule = mkRule (RuleId "UseUnwrap") run
5455 , suggestion: Just $ Suggestion
5556 { replacement: ReplacementText "un ?Constructor"
5657 , description: SuggestionDescription "Use un with typed hole to find the right accessor"
58+ , requiredImports: []
5759 }
5860 }
5961 ]
@@ -65,3 +67,4 @@ useUnwrapRule = mkRule (RuleId "UseUnwrap") run
6567
6668 isUnwrapIdent :: QualifiedName Ident -> Boolean
6769 isUnwrapIdent (QualifiedName { name: Ident name }) = name == "unwrap"
70+
lib/purelint/src/Purelint/Rules/UseVoid.purs → lib/purslint/src/Purslint/Rules/UseVoid.pursrenamed+7−5
@@ -1,4 +1,4 @@
1−module Purelint.Rules.UseVoid where
1+module Purslint.Rules.UseVoid where
22
33import Prelude
44
@@ -6,10 +6,10 @@ import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Tuple (Tuple(..))
88import Data.Void (Void)
9−import Purelint.Imports (ImportInfo, hasValue, hasOp)
10−import Purelint.Print (printExpr)
11−import Purelint.Rule (Rule, RuleContext, mkRule)
12−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
9+import Purslint.Imports (ImportInfo, hasValue, hasOp)
10+import Purslint.Print (printExpr)
11+import Purslint.Rule (Rule, RuleContext, mkRule)
12+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1313import PureScript.CST.Range (rangeOf)
1414import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1515import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, Operator(..), QualifiedName(..))
@@ -41,6 +41,7 @@ useVoidRule = mkRule (RuleId "UseVoid") run
4141 , suggestion: Just $ Suggestion
4242 { replacement: ReplacementText ("void " <> aText)
4343 , description: SuggestionDescription "a *> pure unit can be simplified to void a"
44+ , requiredImports: []
4445 }
4546 }
4647 ]
@@ -64,3 +65,4 @@ useVoidRule = mkRule (RuleId "UseVoid") run
6465 isUnit :: Expr Void -> Boolean
6566 isUnit (ExprIdent (QualifiedName { name: Ident name })) = name == "unit"
6667 isUnit _ = false
68+
lib/purelint/src/Purelint/Rules/UseWhen.purs → lib/purslint/src/Purslint/Rules/UseWhen.pursrenamed+7−5
@@ -1,13 +1,13 @@
1−module Purelint.Rules.UseWhen where
1+module Purslint.Rules.UseWhen where
22
33import Prelude
44
55import Data.Maybe (Maybe(..))
66import Data.Void (Void)
7−import Purelint.Imports (ImportInfo, hasValue)
8−import Purelint.Print (printExpr)
9−import Purelint.Rule (Rule, RuleContext, mkRule)
10−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
7+import Purslint.Imports (ImportInfo, hasValue)
8+import Purslint.Print (printExpr)
9+import Purslint.Rule (Rule, RuleContext, mkRule)
10+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1111import PureScript.CST.Range (rangeOf)
1212import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1313import PureScript.CST.Types (Expr(..), Ident(..), Module, QualifiedName(..))
@@ -38,6 +38,7 @@ useWhenRule = mkRule (RuleId "UseWhen") run
3838 , suggestion: Just $ Suggestion
3939 { replacement: ReplacementText ("when " <> condText <> " " <> actionText)
4040 , description: SuggestionDescription "Use when instead of if/else with pure unit"
41+ , requiredImports: []
4142 }
4243 }
4344 ]
@@ -53,3 +54,4 @@ useWhenRule = mkRule (RuleId "UseWhen") run
5354 isPure imports (ExprIdent (QualifiedName { name: Ident name })) =
5455 (name == "pure" && hasValue imports "pure") || (name == "return" && hasValue imports "return")
5556 isPure _ _ = false
57+
lib/purelint/src/Purelint/Rules/UseZip.purs → lib/purslint/src/Purslint/Rules/UseZip.pursrenamed+9−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.UseZip where
1+module Purslint.Rules.UseZip where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, Proper(..), QualifiedName(..), Wrapped(..))
@@ -39,6 +39,7 @@ useZipRule = mkRule (RuleId "UseZip") run
3939 , suggestion: Just $ Suggestion
4040 { replacement: ReplacementText "zip"
4141 , description: SuggestionDescription "Use zip instead of zipWith Tuple"
42+ , requiredImports: []
4243 }
4344 }
4445 ]
@@ -55,6 +56,7 @@ useZipRule = mkRule (RuleId "UseZip") run
5556 , suggestion: Just $ Suggestion
5657 { replacement: ReplacementText ("zip " <> x)
5758 , description: SuggestionDescription "Use zip instead of zipWith Tuple"
59+ , requiredImports: []
5860 }
5961 }
6062 ]
@@ -73,6 +75,7 @@ useZipRule = mkRule (RuleId "UseZip") run
7375 , suggestion: Just $ Suggestion
7476 { replacement: ReplacementText ("zip " <> x <> " " <> y)
7577 , description: SuggestionDescription "Use zip instead of zipWith Tuple"
78+ , requiredImports: []
7679 }
7780 }
7881 ]
@@ -93,3 +96,4 @@ useZipRule = mkRule (RuleId "UseZip") run
9396 isTupleCtor (ExprConstructor (QualifiedName { name: Proper "Tuple" })) = true
9497 isTupleCtor (ExprIdent (QualifiedName { name: Ident "Tuple" })) = true
9598 isTupleCtor _ = false
99+
lib/purelint/src/Purelint/Rules/WhenNot.purs → lib/purslint/src/Purslint/Rules/WhenNot.pursrenamed+8−5
@@ -1,14 +1,14 @@
1−module Purelint.Rules.WhenNot where
1+module Purslint.Rules.WhenNot where
22
33import Prelude
44
55import Data.Array.NonEmpty as NEA
66import Data.Maybe (Maybe(..))
77import Data.Void (Void)
8−import Purelint.Imports (ImportInfo, hasValue)
9−import Purelint.Print (printExpr)
10−import Purelint.Rule (Rule, RuleContext, mkRule)
11−import Purelint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
8+import Purslint.Imports (ImportInfo, hasValue)
9+import Purslint.Print (printExpr)
10+import Purslint.Rule (Rule, RuleContext, mkRule)
11+import Purslint.Types (LintWarning(..), RuleId(..), Severity(..), Suggestion(..), SuggestionDescription(..), ReplacementText(..), WarningMessage(..))
1212import PureScript.CST.Range (rangeOf)
1313import PureScript.CST.Traversal (defaultMonoidalVisitor, foldMapModule)
1414import PureScript.CST.Types (AppSpine(..), Expr(..), Ident(..), Module, QualifiedName(..), Wrapped(..))
@@ -44,6 +44,7 @@ whenNotRule = mkRule (RuleId "WhenNot") run
4444 , suggestion: Just $ Suggestion
4545 { replacement: ReplacementText ("unless " <> xText <> " " <> action)
4646 , description: SuggestionDescription "Use unless instead of when (not ...)"
47+ , requiredImports: []
4748 }
4849 }
4950 ]
@@ -61,6 +62,7 @@ whenNotRule = mkRule (RuleId "WhenNot") run
6162 , suggestion: Just $ Suggestion
6263 { replacement: ReplacementText ("unless " <> xText)
6364 , description: SuggestionDescription "Use unless instead of when (not ...)"
65+ , requiredImports: []
6466 }
6567 }
6668 ]
@@ -91,3 +93,4 @@ whenNotRule = mkRule (RuleId "WhenNot") run
9193 [AppTerm arg] -> Just arg
9294 _ -> Nothing
9395 _ -> Nothing
96+
lib/purelint/src/Purelint/Runner.purs → lib/purslint/src/Purslint/Runner.pursrenamed+4−4
@@ -1,4 +1,4 @@
1−module Purelint.Runner where
1+module Purslint.Runner where
22
33import Prelude
44
@@ -8,9 +8,9 @@ import Data.Newtype (un, unwrap)
88import PureScript.CST (RecoveredParserResult(..), parseModule)
99import PureScript.CST.Errors (printParseError)
1010import PureScript.CST.Types (ImportDecl(..), Module(..), ModuleHeader(..), ModuleName(..), Name(..))
11−import Purelint.Imports (getImportInfo, ImportInfo)
12−import Purelint.Rule (Rule, RuleContext)
13−import Purelint.Types (LintResult(..), SourceCode(..))
11+import Purslint.Imports (getImportInfo, ImportInfo)
12+import Purslint.Rule (Rule, RuleContext)
13+import Purslint.Types (LintResult(..), SourceCode(..))
1414import Unsafe.Coerce (unsafeCoerce)
1515
1616-- | Run a set of rules on source code
lib/purelint/src/Purelint/Tidy.js → lib/purslint/src/Purslint/Tidy.jsrenamed

The content is the same.

lib/purelint/src/Purelint/Tidy.purs → lib/purslint/src/Purslint/Tidy.pursrenamed+1−1
@@ -1,4 +1,4 @@
1−module Purelint.Tidy where
1+module Purslint.Tidy where
22
33import Prelude
44
lib/purelint/src/Purelint/Types.purs → lib/purslint/src/Purslint/Types.pursrenamed+12−1
@@ -1,4 +1,4 @@
1−module Purelint.Types where
1+module Purslint.Types where
22
33import Prelude
44
@@ -31,10 +31,19 @@ instance Show Severity where
3131 show Warning = "warning"
3232 show Error = "error"
3333
34+-- | Information about imports required for a suggestion
35+type RequiredImport =
36+ { moduleName :: String
37+ , importItem :: Maybe String
38+ , codeText :: Maybe String
39+ , qualifier :: Maybe String
40+ }
41+
3442-- | A suggested replacement for the original code
3543newtype Suggestion = Suggestion
3644 { replacement :: ReplacementText
3745 , description :: SuggestionDescription
46+ , requiredImports :: Array RequiredImport
3847 }
3948
4049derive instance Newtype Suggestion _
@@ -93,3 +102,5 @@ newtype SourceCode = SourceCode String
93102derive instance Newtype SourceCode _
94103derive newtype instance Eq SourceCode
95104derive newtype instance Show SourceCode
105+
106+
package.json+23−10
@@ -1,36 +1,49 @@
11{
2− "name": "purelint",
2+ "name": "purslint",
33 "version": "0.1.0",
44 "description": "A linter for PureScript",
55 "type": "module",
66 "bin": {
7− "purelint": "./dist/purelint.js",
8− "purelint-lsp": "./dist/purelint-lsp.js"
7+ "purslint": "./bin/purslint.js",
8+ "purslint-lsp": "./bin/purslint-lsp.js"
99 },
1010 "scripts": {
11− "build:cli": "spago build -p purelint-cli --output output-cli",
12− "build:lsp": "spago build -p purelint-lsp --output output-lsp",
11+ "build:cli": "spago build -p purslint-cli --output output-cli",
12+ "build:lsp": "spago build -p purslint-lsp --output output-lsp",
1313 "build": "npm run build:cli && npm run build:lsp",
14− "bundle:cli": "purs-backend-es bundle-app --corefn-dir output-cli --main Main --platform node --to dist/purelint.js --int-tags",
15− "bundle:lsp": "purs-backend-es bundle-app --corefn-dir output-lsp --main Main.LSP --platform node --to dist/purelint-lsp.js --int-tags",
14+ "bundle:cli": "purs-backend-es bundle-app --corefn-dir output-cli --main Main --platform node --to dist/purslint.js --int-tags",
15+ "bundle:lsp": "purs-backend-es bundle-app --corefn-dir output-lsp --main Main.LSP --platform node --to dist/purslint-lsp.js --int-tags",
1616 "bundle": "npm run bundle:cli && npm run bundle:lsp",
1717 "dist": "npm run build && npm run bundle",
1818 "test": "spago test",
1919 "test:lsp": "node test-lsp/test-lsp.mjs",
2020 "test:vscode": "cd vscode-extension && npm test",
21− "test:all": "npm test && npm run test:lsp && npm run test:vscode",
22− "dev:cli": "node bin/purelint.js",
23− "dev:lsp": "node bin/purelint-lsp.js"
21+ "test:all": "npm test && npm run test:lsp && npm run test:vscode"
2422 },
2523 "keywords": [
2624 "purescript",
2725 "linter"
2826 ],
27+ "repository": {
28+ "type": "git",
29+ "url": "https://github.com/rowtype-yoga/purslint"
30+ },
31+ "homepage": "https://github.com/rowtype-yoga/purslint",
32+ "bugs": {
33+ "url": "https://github.com/rowtype-yoga/purslint/issues"
34+ },
35+ "files": [
36+ "bin",
37+ "dist",
38+ "README.md",
39+ "package.json"
40+ ],
2941 "license": "MIT",
3042 "dependencies": {
3143 "spago": "^0.93.45"
3244 },
3345 "devDependencies": {
46+ "esbuild": "^0.27.2",
3447 "purs-backend-es": "^1.4.3"
3548 }
3649}
purelint-lspdeleted−2
@@ -1,2 +0,0 @@
1−#!/bin/bash
2−exec node /Users/mark/Developer/purelint/dist/purelint-lsp.js "$@"
purslint-lspnew file+2
@@ -0,0 +1,2 @@
1+#!/bin/bash
2+exec node "$(cd "$(dirname "$0")" && pwd)/dist/purslint-lsp.js" "$@"
spago.lock+94−13
@@ -1,8 +1,8 @@
11{
22 "workspace": {
33 "packages": {
4− "purelint": {
5− "path": "lib/purelint",
4+ "purslint": {
5+ "path": "lib/purslint",
66 "core": {
77 "dependencies": [
88 "arrays",
@@ -69,8 +69,8 @@
6969 "build_plan": []
7070 }
7171 },
72− "purelint-cli": {
73− "path": "bin/purelint-cli",
72+ "purslint-cli": {
73+ "path": "bin/purslint-cli",
7474 "core": {
7575 "dependencies": [
7676 "arrays",
@@ -83,8 +83,9 @@
8383 "node-buffer",
8484 "node-fs",
8585 "node-process",
86+ "optparse",
8687 "prelude",
87− "purelint",
88+ "purslint",
8889 "strings"
8990 ],
9091 "build_plan": [
@@ -104,6 +105,7 @@
104105 "enums",
105106 "exceptions",
106107 "exists",
108+ "exitcodes",
107109 "foldable-traversable",
108110 "foreign",
109111 "foreign-object",
@@ -130,6 +132,8 @@
130132 "now",
131133 "nullable",
132134 "numbers",
135+ "open-memoize",
136+ "optparse",
133137 "ordered-collections",
134138 "orders",
135139 "parallel",
@@ -137,7 +141,7 @@
137141 "posix-types",
138142 "prelude",
139143 "profunctor",
140− "purelint",
144+ "purslint",
141145 "refs",
142146 "safe-coerce",
143147 "st",
@@ -156,8 +160,8 @@
156160 "build_plan": []
157161 }
158162 },
159− "purelint-lsp": {
160− "path": "bin/purelint-lsp",
163+ "purslint-lsp": {
164+ "path": "bin/purslint-lsp",
161165 "core": {
162166 "dependencies": [
163167 "arrays",
@@ -167,7 +171,7 @@
167171 "maybe",
168172 "newtype",
169173 "prelude",
170− "purelint",
174+ "purslint",
171175 "unsafe-coerce"
172176 ],
173177 "build_plan": [
@@ -204,7 +208,7 @@
204208 "partial",
205209 "prelude",
206210 "profunctor",
207− "purelint",
211+ "purslint",
208212 "refs",
209213 "safe-coerce",
210214 "st",
@@ -223,7 +227,7 @@
223227 "build_plan": []
224228 }
225229 },
226− "purelint-tests": {
230+ "purslint-tests": {
227231 "path": "./",
228232 "core": {
229233 "dependencies": [
@@ -233,10 +237,13 @@
233237 "either",
234238 "maybe",
235239 "newtype",
240+ "optparse",
236241 "prelude",
237− "purelint"
242+ "purslint"
238243 ],
239244 "build_plan": [
245+ "aff",
246+ "arraybuffer-types",
240247 "arrays",
241248 "bifunctors",
242249 "catenable-lists",
@@ -244,13 +251,17 @@
244251 "const",
245252 "contravariant",
246253 "control",
254+ "datetime",
247255 "distributive",
248256 "effect",
249257 "either",
250258 "enums",
251259 "exceptions",
252260 "exists",
261+ "exitcodes",
253262 "foldable-traversable",
263+ "foreign",
264+ "foreign-object",
254265 "free",
255266 "functions",
256267 "functors",
@@ -263,14 +274,23 @@
263274 "lists",
264275 "maybe",
265276 "newtype",
277+ "node-buffer",
278+ "node-event-emitter",
279+ "node-process",
280+ "node-streams",
266281 "nonempty",
282+ "nullable",
267283 "numbers",
284+ "open-memoize",
285+ "optparse",
268286 "ordered-collections",
269287 "orders",
288+ "parallel",
270289 "partial",
290+ "posix-types",
271291 "prelude",
272292 "profunctor",
273− "purelint",
293+ "purslint",
274294 "refs",
275295 "safe-coerce",
276296 "st",
@@ -1075,6 +1095,14 @@
10751095 "unsafe-coerce"
10761096 ]
10771097 },
1098+ "exitcodes": {
1099+ "type": "registry",
1100+ "version": "4.0.0",
1101+ "integrity": "sha256-4wxViTbyOoyKJ/WaRGI6+hZmgMKI5Miv16lSwefiLSM=",
1102+ "dependencies": [
1103+ "enums"
1104+ ]
1105+ },
10781106 "foldable-traversable": {
10791107 "type": "registry",
10801108 "version": "6.0.0",
@@ -1458,6 +1486,59 @@
14581486 "maybe"
14591487 ]
14601488 },
1489+ "open-memoize": {
1490+ "type": "registry",
1491+ "version": "6.2.0",
1492+ "integrity": "sha256-p1m7wF3aHQ80yUvqMs20OTMl496WS6YpKlmI2Nkg9j0=",
1493+ "dependencies": [
1494+ "either",
1495+ "integers",
1496+ "lazy",
1497+ "lists",
1498+ "maybe",
1499+ "partial",
1500+ "prelude",
1501+ "strings",
1502+ "tuples"
1503+ ]
1504+ },
1505+ "optparse": {
1506+ "type": "registry",
1507+ "version": "5.0.1",
1508+ "integrity": "sha256-cEzEkNW4q0gZlXl4z0zn+H2vs6l2UAp7NPHCsois73k=",
1509+ "dependencies": [
1510+ "aff",
1511+ "arrays",
1512+ "bifunctors",
1513+ "console",
1514+ "control",
1515+ "effect",
1516+ "either",
1517+ "enums",
1518+ "exists",
1519+ "exitcodes",
1520+ "foldable-traversable",
1521+ "free",
1522+ "gen",
1523+ "integers",
1524+ "lazy",
1525+ "lists",
1526+ "maybe",
1527+ "newtype",
1528+ "node-buffer",
1529+ "node-process",
1530+ "node-streams",
1531+ "nonempty",
1532+ "numbers",
1533+ "open-memoize",
1534+ "partial",
1535+ "prelude",
1536+ "strings",
1537+ "tailrec",
1538+ "transformers",
1539+ "tuples"
1540+ ]
1541+ },
14611542 "ordered-collections": {
14621543 "type": "registry",
14631544 "version": "3.2.0",
spago.yaml+3−2
@@ -4,15 +4,16 @@ workspace:
44 extraPackages: {}
55
66package:
7− name: purelint-tests
7+ name: purslint-tests
88 dependencies:
99 - console
1010 - effect
1111 - either
1212 - maybe
1313 - newtype
14+ - optparse
1415 - prelude
15− - purelint
16+ - purslint
1617 - arrays
1718 test:
1819 main: Test.Main
test/RedundantParens.pursnew file+134
@@ -0,0 +1,134 @@
1+module Test.RedundantParens where
2+
3+import Prelude
4+
5+-- =============================================================================
6+-- REDUNDANT PARENS TEST CASES
7+-- Each pair: xxx_with has parens, xxx_without doesn't
8+-- If JS output is identical, parens are redundant
9+-- =============================================================================
10+
11+-- === NESTED PARENS (always redundant) ===
12+nested_with = ((1))
13+nested_without = (1)
14+
15+nested3_with = (((1)))
16+nested3_without = 1
17+
18+-- === ATOMIC EXPRESSIONS IN FUNCTION ARGS ===
19+arg_int_with = show (42)
20+arg_int_without = show 42
21+
22+arg_string_with = show ("hello")
23+arg_string_without = show "hello"
24+
25+arg_ident_with x = show (x)
26+arg_ident_without x = show x
27+
28+arg_array_with = length ([1, 2, 3])
29+arg_array_without = length [1, 2, 3]
30+
31+arg_record_with = show ({ a: 1 })
32+arg_record_without = show { a: 1 }
33+
34+arg_bool_with = show (true)
35+arg_bool_without = show true
36+
37+-- Multiple args
38+multi_arg_with = add (1) (2)
39+multi_arg_without = add 1 2
40+
41+-- === ARRAY ELEMENTS ===
42+arr_elem_with = [(1), (2), (3)]
43+arr_elem_without = [1, 2, 3]
44+
45+arr_mixed_with = [(1), 2, (3)]
46+arr_mixed_without = [1, 2, 3]
47+
48+-- === NEEDED FOR PRECEDENCE (not redundant) ===
49+prec_mult_with = (1 + 2) * 3
50+-- prec_mult_without would be: 1 + 2 * 3 (different!)
51+
52+prec_sub_with = (1 + 2) - 3
53+-- Note: same precedence, but left-assoc so this is actually redundant
54+prec_sub_without = 1 + 2 - 3
55+
56+-- === NEEDED FOR COMPLEX ARGS (not redundant) ===
57+complex_arg_with = show (1 + 2)
58+-- complex_arg_without would be: show 1 + 2 (different!)
59+
60+-- === NEEDED FOR LAMBDA ARGS ===
61+lambda_arg_with = map (\x -> x + 1) [1]
62+-- lambda_arg_without would extend the lambda body
63+
64+-- === NEEDED FOR NEGATE AS ARG ===
65+negate_arg_with f = f (-1)
66+-- negate_arg_without would be: f - 1 (different!)
67+
68+-- === SECTIONS (parens required by syntax) ===
69+section_with = (_ + 1)
70+-- section_without is not valid syntax
71+
72+-- === OUTER PARENS ON RHS (whole expression wrapped) ===
73+rhs_lit_with = (42)
74+rhs_lit_without = 42
75+
76+rhs_op_with = (1 + 2)
77+rhs_op_without = 1 + 2
78+
79+rhs_app_with = (show 1)
80+rhs_app_without = show 1
81+
82+rhs_lambda_with = (\x -> x)
83+rhs_lambda_without = \x -> x
84+
85+rhs_if_with = (if true then 1 else 2)
86+rhs_if_without = if true then 1 else 2
87+
88+rhs_case_with = (case 1 of x -> x)
89+rhs_case_without = case 1 of x -> x
90+
91+rhs_let_with = (let x = 1 in x)
92+rhs_let_without = let x = 1 in x
93+
94+rhs_do_with = (do pure 1)
95+rhs_do_without = do pure 1
96+
97+-- === PARENS IN IF BRANCHES ===
98+if_cond_with = if (true) then 1 else 2
99+if_cond_without = if true then 1 else 2
100+
101+if_then_with = if true then (1) else 2
102+if_then_without = if true then 1 else 2
103+
104+if_else_with = if true then 1 else (2)
105+if_else_without = if true then 1 else 2
106+
107+-- === PARENS IN CASE ===
108+case_scrut_with = case (1) of x -> x
109+case_scrut_without = case 1 of x -> x
110+
111+case_body_with = case 1 of x -> (x)
112+case_body_without = case 1 of x -> x
113+
114+-- === PARENS IN LET ===
115+let_bind_with = let x = (1) in x
116+let_bind_without = let x = 1 in x
117+
118+let_body_with = let x = 1 in (x)
119+let_body_without = let x = 1 in x
120+
121+-- === PARENS IN DO ===
122+do_expr_with = do (pure 1)
123+do_expr_without = do pure 1
124+
125+-- === PARENS IN RECORD FIELDS ===
126+rec_field_with = { a: (1), b: (2) }
127+rec_field_without = { a: 1, b: 2 }
128+
129+-- === HELPERS ===
130+add :: Int -> Int -> Int
131+add a b = a + b
132+
133+length :: forall a. Array a -> Int
134+length _ = 0
test/Test/Main.purs+454−93
@@ -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-- ============================================================================
vscode-extension/out/extension.js+4−4
@@ -39,15 +39,15 @@ const vscode = __importStar(require("vscode"));
3939const node_1 = require("vscode-languageclient/node");
4040let client;
4141function activate(context) {
42− const config = vscode.workspace.getConfiguration('purelint');
42+ const config = vscode.workspace.getConfiguration('purslint');
4343 if (!config.get('enable', true)) {
4444 return;
4545 }
4646 // Get server path from config or use default
4747 let serverPath = config.get('serverPath', '');
4848 if (!serverPath) {
49− // Default to the purelint project location
50− serverPath = '/Users/mark/Developer/purelint/dist/purelint-lsp.js';
49+ // Default to the purslint project location
50+ serverPath = '/Users/mark/Developer/purslint/dist/purslint-lsp.js';
5151 }
5252 const serverOptions = {
5353 run: {
@@ -65,7 +65,7 @@ function activate(context) {
6565 fileEvents: vscode.workspace.createFileSystemWatcher('**/*.purs')
6666 }
6767 };
68− client = new node_1.LanguageClient('purelint', 'Purelint', serverOptions, clientOptions);
68+ client = new node_1.LanguageClient('purslint', 'Purslint', serverOptions, clientOptions);
6969 client.start();
7070}
7171function deactivate() {
vscode-extension/out/test/suite/extension.test.js+4−4
@@ -36,13 +36,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
3636const assert = __importStar(require("assert"));
3737const vscode = __importStar(require("vscode"));
3838const path = __importStar(require("path"));
39−suite('Purelint Extension Test Suite', () => {
39+suite('Purslint Extension Test Suite', () => {
4040 vscode.window.showInformationMessage('Start all tests.');
4141 test('Extension should be present', () => {
42− assert.ok(vscode.extensions.getExtension('purelint.purelint'));
42+ assert.ok(vscode.extensions.getExtension('RowtypeYoga.purslint'));
4343 });
4444 test('Extension should activate on PureScript file', async () => {
45− const ext = vscode.extensions.getExtension('purelint.purelint');
45+ const ext = vscode.extensions.getExtension('RowtypeYoga.purslint');
4646 assert.ok(ext);
4747 // Open a PureScript file to trigger activation
4848 const testFile = path.join(__dirname, '../../../test-workspace/Test.purs');
@@ -54,7 +54,7 @@ suite('Purelint Extension Test Suite', () => {
5454 assert.ok(ext.isActive);
5555 });
5656 test('Should provide code actions', async () => {
57− const ext = vscode.extensions.getExtension('purelint.purelint');
57+ const ext = vscode.extensions.getExtension('RowtypeYoga.purslint');
5858 if (!ext?.isActive) {
5959 await ext?.activate();
6060 }
vscode-extension/out/test/suite/extension.test.js.map+1−1
@@ -1 +1 @@
1−{"version":3,"file":"extension.test.js","sourceRoot":"","sources":["../../../src/test/suite/extension.test.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AACjC,+CAAiC;AACjC,2CAA6B;AAE7B,KAAK,CAAC,+BAA+B,EAAE,GAAG,EAAE;IAC1C,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,kBAAkB,CAAC,CAAC;IAEzD,IAAI,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACvC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,mBAAmB,CAAC,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC9D,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,mBAAmB,CAAC,CAAC;QAChE,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;QAEf,+CAA+C;QAC/C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,mCAAmC,CAAC,CAAC;QAC3E,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;QACzD,MAAM,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;QAE1C,iCAAiC;QACjC,MAAM,GAAG,CAAC,QAAQ,EAAE,CAAC;QACrB,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,6BAA6B,EAAE,KAAK,IAAI,EAAE;QAC7C,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,mBAAmB,CAAC,CAAC;QAChE,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC;YACnB,MAAM,GAAG,EAAE,QAAQ,EAAE,CAAC;QACxB,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,mCAAmC,CAAC,CAAC;QAC3E,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;QACzD,MAAM,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;QAE1C,6CAA6C;QAC7C,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;QAExD,iDAAiD;QACjD,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;QAEvD,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,cAAc,CACtD,kCAAkC,EAClC,GAAG,CAAC,GAAG,EACP,KAAK,CACN,CAAC;QAEF,qEAAqE;QACrE,wCAAwC;QACxC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,WAAW,KAAK,SAAS,EAAE,kCAAkC,CAAC,CAAC;IACzG,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
1+{"version":3,"file":"extension.test.js","sourceRoot":"","sources":["../../../src/test/suite/extension.test.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AACjC,+CAAiC;AACjC,2CAA6B;AAE7B,KAAK,CAAC,+BAA+B,EAAE,GAAG,EAAE;IAC1C,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,kBAAkB,CAAC,CAAC;IAEzD,IAAI,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACvC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,sBAAsB,CAAC,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC9D,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,sBAAsB,CAAC,CAAC;QACnE,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;QAEf,+CAA+C;QAC/C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,mCAAmC,CAAC,CAAC;QAC3E,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;QACzD,MAAM,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;QAE1C,iCAAiC;QACjC,MAAM,GAAG,CAAC,QAAQ,EAAE,CAAC;QACrB,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,6BAA6B,EAAE,KAAK,IAAI,EAAE;QAC7C,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,sBAAsB,CAAC,CAAC;QACnE,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC;YACnB,MAAM,GAAG,EAAE,QAAQ,EAAE,CAAC;QACxB,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,mCAAmC,CAAC,CAAC;QAC3E,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;QACzD,MAAM,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;QAE1C,6CAA6C;QAC7C,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;QAExD,iDAAiD;QACjD,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;QAEvD,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,cAAc,CACtD,kCAAkC,EAClC,GAAG,CAAC,GAAG,EACP,KAAK,CACN,CAAC;QAEF,qEAAqE;QACrE,wCAAwC;QACxC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,WAAW,KAAK,SAAS,EAAE,kCAAkC,CAAC,CAAC;IACzG,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
vscode-extension/package.json+10−9
@@ -1,9 +1,10 @@
11{
2− "name": "purelint",
3− "displayName": "Purelint",
2+ "name": "purslint",
3+ "displayName": "Purslint",
44 "description": "PureScript linter inspired by hlint",
5− "version": "0.1.0",
6− "publisher": "purelint",
5+ "version": "0.1.1",
6+ "publisher": "RowtypeYoga",
7+ "icon": "purslint.png",
78 "engines": {
89 "vscode": "^1.85.0"
910 },
@@ -18,17 +19,17 @@
1819 "contributes": {
1920 "configuration": {
2021 "type": "object",
21− "title": "Purelint",
22+ "title": "Purslint",
2223 "properties": {
23− "purelint.enable": {
24+ "purslint.enable": {
2425 "type": "boolean",
2526 "default": true,
26− "description": "Enable/disable purelint"
27+ "description": "Enable/disable purslint"
2728 },
28− "purelint.serverPath": {
29+ "purslint.serverPath": {
2930 "type": "string",
3031 "default": "",
31− "description": "Path to purelint-lsp executable (uses bundled version if empty)"
32+ "description": "Path to purslint-lsp executable (uses bundled version if empty)"
3233 }
3334 }
3435 }
vscode-extension/purelint-0.1.0.vsix → vscode-extension/purslint-0.1.1.vsixrenamed

Binary file not shown.

vscode-extension/purslint.pngnew file

Binary file not shown.

vscode-extension/src/extension.ts+5−5
@@ -10,7 +10,7 @@ import {
1010let client: LanguageClient | undefined;
1111
1212export function activate(context: vscode.ExtensionContext) {
13− const config = vscode.workspace.getConfiguration('purelint');
13+ const config = vscode.workspace.getConfiguration('purslint');
1414
1515 if (!config.get<boolean>('enable', true)) {
1616 return;
@@ -19,8 +19,8 @@ export function activate(context: vscode.ExtensionContext) {
1919 // Get server path from config or use default
2020 let serverPath = config.get<string>('serverPath', '');
2121 if (!serverPath) {
22− // Default to the purelint project location
23− serverPath = '/Users/mark/Developer/purelint/dist/purelint-lsp.js';
22+ // Default to the purslint project location
23+ serverPath = '/Users/mark/Developer/purslint/dist/purslint-lsp.js';
2424 }
2525
2626 const serverOptions: ServerOptions = {
@@ -42,8 +42,8 @@ export function activate(context: vscode.ExtensionContext) {
4242 };
4343
4444 client = new LanguageClient(
45− 'purelint',
46− 'Purelint',
45+ 'purslint',
46+ 'Purslint',
4747 serverOptions,
4848 clientOptions
4949 );
vscode-extension/src/test/suite/extension.test.ts+4−4
@@ -2,15 +2,15 @@ import * as assert from 'assert';
22import * as vscode from 'vscode';
33import * as path from 'path';
44
5−suite('Purelint Extension Test Suite', () => {
5+suite('Purslint Extension Test Suite', () => {
66 vscode.window.showInformationMessage('Start all tests.');
77
88 test('Extension should be present', () => {
9− assert.ok(vscode.extensions.getExtension('purelint.purelint'));
9+ assert.ok(vscode.extensions.getExtension('RowtypeYoga.purslint'));
1010 });
1111
1212 test('Extension should activate on PureScript file', async () => {
13− const ext = vscode.extensions.getExtension('purelint.purelint');
13+ const ext = vscode.extensions.getExtension('RowtypeYoga.purslint');
1414 assert.ok(ext);
1515
1616 // Open a PureScript file to trigger activation
@@ -25,7 +25,7 @@ suite('Purelint Extension Test Suite', () => {
2525 });
2626
2727 test('Should provide code actions', async () => {
28− const ext = vscode.extensions.getExtension('purelint.purelint');
28+ const ext = vscode.extensions.getExtension('RowtypeYoga.purslint');
2929 if (!ext?.isActive) {
3030 await ext?.activate();
3131 }
zed-extension/Cargo.lock+3−3
@@ -412,7 +412,7 @@ dependencies = [
412412]
413413
414414[[package]]
415−name = "purelint-zed"
415+name = "purslint-zed"
416416version = "0.0.1"
417417dependencies = [
418418 "zed_extension_api",
@@ -818,6 +818,6 @@ dependencies = [
818818
819819[[package]]
820820name = "zmij"
821−version = "1.0.16"
821+version = "1.0.17"
822822source = "registry+https://github.com/rust-lang/crates.io-index"
823−checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65"
823+checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439"
zed-extension/Cargo.toml+1−1
@@ -1,5 +1,5 @@
11[package]
2−name = "purelint-zed"
2+name = "purslint-zed"
33version = "0.0.1"
44edition = "2021"
55
zed-extension/extension.toml+3−3
@@ -1,5 +1,5 @@
1−id = "purelint"
2−name = "Purelint"
1+id = "purslint"
2+name = "Purslint"
33version = "0.0.1"
44schema_version = 1
55authors = ["Mark"]
@@ -9,5 +9,5 @@ description = "PureScript linter inspired by hlint"
99kind = "Rust"
1010version = "0.7.0"
1111
12−[language_servers.purelint]
12+[language_servers.purslint]
1313language = "PureScript"
zed-extension/extension.wasm

Binary file not shown.

zed-extension/src/lib.rs+5−5
@@ -1,10 +1,10 @@
11use zed_extension_api::{self as zed, Result};
22
3−struct PurelintExtension;
3+struct PurslintExtension;
44
5−impl zed::Extension for PurelintExtension {
5+impl zed::Extension for PurslintExtension {
66 fn new() -> Self {
7− PurelintExtension
7+ PurslintExtension
88 }
99
1010 fn language_server_command(
@@ -13,11 +13,11 @@ impl zed::Extension for PurelintExtension {
1313 _worktree: &zed::Worktree,
1414 ) -> Result<zed::Command> {
1515 Ok(zed::Command {
16− command: "/Users/mark/Developer/purelint/purelint-lsp".to_string(),
16+ command: "purslint-lsp".to_string(),
1717 args: vec![],
1818 env: Default::default(),
1919 })
2020 }
2121}
2222
23−zed::register_extension!(PurelintExtension);
23+zed::register_extension!(PurslintExtension);