git.iko.soy/rowtype-yoga/purslint
on v0.1.0 help sign in
rowtype-yoga/purslint/docs/IMPORT_CHECKING_PLAN.md
129 lines 3.5 kB history raw
1# Import Checking Plan
3## Goal
5Make lint rules safer by verifying that identifiers like `map`, `identity`, `<$>` actually refer to the standard Prelude functions, not user-defined functions with the same name.
7## Current State
9- `Purslint.Imports` module exists with:
10 - `getImportInfo :: Module Void -> ImportInfo` - extracts import info from a module
11 - `hasValue :: ImportInfo -> String -> Boolean` - check if a value is imported
12 - `hasOp :: ImportInfo -> String -> Boolean` - check if an operator is imported
13 - Lists of "Prelude-like" modules (Prelude, Data.Functor, Data.Traversable, etc.)
14 - Lists of standard values/operators we care about
16## Implementation Plan
18### Step 1: Update Rule type to receive ImportInfo
20```purescript
21-- In Purslint/Rule.purs
22type RuleContext =
23 { imports :: ImportInfo
24 }
26type Rule =
27 { ruleId :: RuleId
28 , run :: RuleContext -> Module Void -> Array LintWarning
29 }
31mkRule :: RuleId -> (RuleContext -> Module Void -> Array LintWarning) -> Rule
32```
34### Step 2: Update Runner to pass context
36```purescript
37-- In Purslint/Runner.purs
38runRules :: Array Rule -> SourceCode -> Either String LintResult
39runRules rules (SourceCode source) = do
40 mod <- parseModule source
41 let imports = getImportInfo mod
42 let ctx = { imports }
43 let warnings = rules >>= \rule -> rule.run ctx mod
44 pure $ LintResult { warnings, moduleName: getModuleName mod }
45```
47### Step 3: Update each rule to check imports
49Example for FmapId:
51```purescript
52checkExpr ctx expr = case expr of
53 ExprApp fnExpr args ->
54 case fnExpr of
55 ExprIdent qn | isMapLike qn && hasValue ctx.imports "map" ->
56 -- ... existing logic
57```
59For operators:
61```purescript
62 ExprOp lhs ops | isIdentity lhs && hasOp ctx.imports "<$>" ->
63 -- ... existing logic
64```
66### Step 4: Handle qualified imports (future)
68Track qualified imports like `import Data.Functor as F` and check for `F.map`.
70```purescript
71type ImportInfo =
72 { preludeValues :: Set String
73 , preludeOps :: Set String
74 , hasPrelude :: Boolean
75 , qualifiedModules :: Map String String -- alias -> module name
76 }
77```
79Then in rules, also check `QualifiedName { module: Just alias }` against the map.
81## Rules to Update
83All rules that check for specific identifiers:
85| Rule | Values to check | Operators to check |
86|------|-----------------|-------------------|
87| FmapId | `map`, `fmap`, `identity`, `id` | `<$>`, `<#>` |
88| MapFusion | `map`, `fmap` | `<$>`, `<#>` |
89| UseTraverse | `map`, `fmap`, `sequenceA` | `<$>` |
90| ConcatMap | `concat`, `join`, `map`, `fmap` | |
91| NotEqual | `not` | `==`, `/=` |
92| UseGuard | `pure`, `when`, `unless` | |
93| EtaReduce | (any function) | |
94| EtaReduceDecl | (any function) | |
95| RedundantBind | `pure` | `>>=` |
97## Files to Modify
991. `lib/purslint/src/Purslint/Rule.purs` - Add RuleContext
1002. `lib/purslint/src/Purslint/Runner.purs` - Pass context to rules
1013. `lib/purslint/src/Purslint/Rules/*.purs` - All 9 rule files
1024. `bin/purslint-cli/src/Main.purs` - No changes needed (uses Runner)
1035. `bin/purslint-lsp/src/LSP.purs` - No changes needed (uses Runner)
105## Testing
107After implementation, test with:
109```purescript
110module Test where
112-- Should NOT trigger (no Prelude import)
113x = map identity [1,2,3]
115-- Define our own map
116map :: forall a b. (a -> b) -> Array a -> Array b
117map f xs = xs
118```
120vs:
122```purescript
123module Test where
125import Prelude
127-- SHOULD trigger (Prelude imported)
128x = map identity [1,2,3]
129```