eslint-plugin-functype 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +205 -153
- package/dist/cli/list-rules.js +62 -153
- package/dist/cli/list-rules.js.map +1 -1
- package/dist/index.d.ts +11 -59
- package/dist/index.js +1021 -166
- package/dist/index.js.map +1 -1
- package/dist/rules/index.d.ts +31 -0
- package/dist/rules/index.js +1072 -0
- package/dist/rules/index.js.map +1 -0
- package/package.json +33 -13
- package/dist/configs/recommended.d.ts +0 -30
- package/dist/configs/recommended.js +0 -159
- package/dist/configs/recommended.js.map +0 -1
- package/dist/configs/strict.d.ts +0 -32
- package/dist/configs/strict.js +0 -176
- package/dist/configs/strict.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,231 +1,283 @@
|
|
|
1
1
|
# eslint-plugin-functype
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Custom ESLint rules for functional TypeScript programming with [functype](https://github.com/jordanburke/functype) library patterns. Enforces immutability, type safety, and functional programming best practices for ESLint 9+.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/eslint-plugin-functype)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
## Features
|
|
8
9
|
|
|
9
|
-
- **
|
|
10
|
-
-
|
|
11
|
-
- **
|
|
10
|
+
- 🔧 **8 Custom ESLint Rules** - Purpose-built for functional TypeScript patterns
|
|
11
|
+
- 🏗️ **Functype Library Integration** - Smart detection when functype is already being used properly
|
|
12
|
+
- 🛠️ **Auto-Fixable** - Most violations can be automatically fixed with `--fix`
|
|
13
|
+
- ⚡ **ESLint 9+ Flat Config** - Modern ESLint configuration format
|
|
14
|
+
- 🎯 **TypeScript Native** - Built specifically for TypeScript AST patterns
|
|
15
|
+
- 📊 **99 Tests** - Comprehensive test coverage including real functype integration
|
|
16
|
+
|
|
17
|
+
## Rules
|
|
18
|
+
|
|
19
|
+
| Rule | Description | Auto-Fix |
|
|
20
|
+
|------|-------------|----------|
|
|
21
|
+
| `prefer-option` | Prefer `Option<T>` over nullable types (`T \| null \| undefined`) | ✅ |
|
|
22
|
+
| `prefer-either` | Prefer `Either<E, T>` over try/catch and throw statements | ✅ |
|
|
23
|
+
| `prefer-list` | Prefer `List<T>` over native arrays for immutable collections | ✅ |
|
|
24
|
+
| `prefer-fold` | Prefer `.fold()` over complex if/else chains | ✅ |
|
|
25
|
+
| `prefer-map` | Prefer `.map()` over imperative transformations | ✅ |
|
|
26
|
+
| `prefer-flatmap` | Prefer `.flatMap()` over `.map().flat()` patterns | ✅ |
|
|
27
|
+
| `no-get-unsafe` | Disallow unsafe `.get()` calls on Option/Either types | ❌ |
|
|
28
|
+
| `no-imperative-loops` | Prefer functional iteration over imperative loops | ✅ |
|
|
12
29
|
|
|
13
30
|
## Installation
|
|
14
31
|
|
|
15
32
|
```bash
|
|
16
|
-
npm install --save-dev eslint-plugin-functype
|
|
33
|
+
npm install --save-dev eslint-plugin-functype
|
|
17
34
|
# or
|
|
18
|
-
pnpm add -D eslint-plugin-functype
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Usage
|
|
22
|
-
|
|
23
|
-
### ESLint 8 (.eslintrc) - Quick Start
|
|
24
|
-
|
|
25
|
-
```javascript
|
|
26
|
-
// .eslintrc.js
|
|
27
|
-
module.exports = {
|
|
28
|
-
extends: ["plugin:functype/recommended"],
|
|
29
|
-
parser: "@typescript-eslint/parser",
|
|
30
|
-
parserOptions: {
|
|
31
|
-
project: "./tsconfig.json",
|
|
32
|
-
},
|
|
33
|
-
}
|
|
35
|
+
pnpm add -D eslint-plugin-functype
|
|
34
36
|
```
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
**Optional:** Install functype library for enhanced integration:
|
|
37
39
|
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
parser: "@typescript-eslint/parser",
|
|
43
|
-
parserOptions: {
|
|
44
|
-
project: "./tsconfig.json",
|
|
45
|
-
},
|
|
46
|
-
}
|
|
40
|
+
```bash
|
|
41
|
+
npm install functype
|
|
42
|
+
# or
|
|
43
|
+
pnpm add functype
|
|
47
44
|
```
|
|
48
45
|
|
|
49
|
-
|
|
46
|
+
## Usage
|
|
50
47
|
|
|
51
|
-
|
|
48
|
+
### ESLint 9+ Flat Config (Recommended)
|
|
52
49
|
|
|
53
50
|
```javascript
|
|
54
|
-
// eslint.config.
|
|
55
|
-
import
|
|
56
|
-
import
|
|
57
|
-
import functional from "eslint-plugin-functional"
|
|
58
|
-
import parser from "@typescript-eslint/parser"
|
|
51
|
+
// eslint.config.mjs
|
|
52
|
+
import functypePlugin from 'eslint-plugin-functype'
|
|
53
|
+
import tsParser from '@typescript-eslint/parser'
|
|
59
54
|
|
|
60
55
|
export default [
|
|
61
|
-
js.configs.recommended,
|
|
62
56
|
{
|
|
63
|
-
files: [
|
|
57
|
+
files: ['**/*.ts', '**/*.tsx'],
|
|
58
|
+
plugins: {
|
|
59
|
+
functype: functypePlugin,
|
|
60
|
+
},
|
|
64
61
|
languageOptions: {
|
|
65
|
-
parser,
|
|
62
|
+
parser: tsParser,
|
|
66
63
|
parserOptions: {
|
|
67
|
-
ecmaVersion:
|
|
68
|
-
sourceType:
|
|
69
|
-
project: "./tsconfig.json",
|
|
64
|
+
ecmaVersion: 2022,
|
|
65
|
+
sourceType: 'module',
|
|
70
66
|
},
|
|
71
67
|
},
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
rules: {
|
|
69
|
+
// All rules as errors
|
|
70
|
+
'functype/prefer-option': 'error',
|
|
71
|
+
'functype/prefer-either': 'error',
|
|
72
|
+
'functype/prefer-list': 'error',
|
|
73
|
+
'functype/prefer-fold': 'error',
|
|
74
|
+
'functype/prefer-map': 'error',
|
|
75
|
+
'functype/prefer-flatmap': 'error',
|
|
76
|
+
'functype/no-get-unsafe': 'error',
|
|
77
|
+
'functype/no-imperative-loops': 'error',
|
|
75
78
|
},
|
|
79
|
+
},
|
|
80
|
+
]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Individual Rule Configuration
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
// eslint.config.mjs - Selective rules
|
|
87
|
+
export default [
|
|
88
|
+
{
|
|
89
|
+
files: ['**/*.ts'],
|
|
90
|
+
plugins: { functype: functypePlugin },
|
|
76
91
|
rules: {
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
92
|
+
// Start with just type safety rules
|
|
93
|
+
'functype/prefer-option': 'warn',
|
|
94
|
+
'functype/no-get-unsafe': 'error',
|
|
80
95
|
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
"@typescript-eslint/no-explicit-any": "error",
|
|
84
|
-
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
|
85
|
-
|
|
86
|
-
// Functional programming rules
|
|
87
|
-
"functional/no-let": "error",
|
|
88
|
-
"functional/immutable-data": "warn",
|
|
89
|
-
"functional/no-loop-statements": "off", // Enable as "error" for strict mode
|
|
96
|
+
// Add more as your codebase evolves
|
|
97
|
+
'functype/prefer-list': 'off', // Disable for gradual adoption
|
|
90
98
|
},
|
|
91
|
-
}
|
|
99
|
+
},
|
|
92
100
|
]
|
|
93
101
|
```
|
|
94
102
|
|
|
95
|
-
|
|
103
|
+
## Examples
|
|
96
104
|
|
|
97
|
-
|
|
105
|
+
### ❌ Before (violations flagged)
|
|
98
106
|
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
```typescript
|
|
108
|
+
// prefer-option: nullable types
|
|
109
|
+
const user: User | null = findUser(id)
|
|
110
|
+
function getAge(): number | undefined { /* ... */ }
|
|
111
|
+
|
|
112
|
+
// prefer-either: try/catch blocks
|
|
113
|
+
try {
|
|
114
|
+
const result = riskyOperation()
|
|
115
|
+
return result
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error(error)
|
|
118
|
+
return null
|
|
108
119
|
}
|
|
109
|
-
```
|
|
110
120
|
|
|
111
|
-
|
|
121
|
+
// prefer-list: native arrays
|
|
122
|
+
const items: number[] = [1, 2, 3]
|
|
123
|
+
const readonlyItems: ReadonlyArray<string> = ["a", "b"]
|
|
112
124
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
- ⚠️ `functional/no-mutation` - Warn on object/array mutations
|
|
118
|
-
- ✅ `@typescript-eslint/consistent-type-imports` - Clean imports
|
|
119
|
-
- ✅ `@typescript-eslint/no-explicit-any` - Type safety
|
|
125
|
+
// no-imperative-loops: for/while loops
|
|
126
|
+
for (let i = 0; i < items.length; i++) {
|
|
127
|
+
console.log(items[i])
|
|
128
|
+
}
|
|
120
129
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
130
|
+
// prefer-fold: complex if/else chains
|
|
131
|
+
if (condition1) {
|
|
132
|
+
return value1
|
|
133
|
+
} else if (condition2) {
|
|
134
|
+
return value2
|
|
135
|
+
} else {
|
|
136
|
+
return defaultValue
|
|
137
|
+
}
|
|
138
|
+
```
|
|
127
139
|
|
|
128
|
-
|
|
140
|
+
### ✅ After (auto-fixed or manually corrected)
|
|
129
141
|
|
|
130
142
|
```typescript
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
143
|
+
import { Option, Either, List } from 'functype'
|
|
144
|
+
|
|
145
|
+
// prefer-option: use Option<T>
|
|
146
|
+
const user: Option<User> = Option.fromNullable(findUser(id))
|
|
147
|
+
function getAge(): Option<number> { /* ... */ }
|
|
148
|
+
|
|
149
|
+
// prefer-either: use Either<E, T>
|
|
150
|
+
function safeOperation(): Either<Error, Result> {
|
|
151
|
+
try {
|
|
152
|
+
const result = riskyOperation()
|
|
153
|
+
return Either.right(result)
|
|
154
|
+
} catch (error) {
|
|
155
|
+
return Either.left(error as Error)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// prefer-list: use List<T>
|
|
160
|
+
const items: List<number> = List.from([1, 2, 3])
|
|
161
|
+
const readonlyItems: List<string> = List.from(["a", "b"])
|
|
162
|
+
|
|
163
|
+
// no-imperative-loops: use functional methods
|
|
164
|
+
items.forEach(item => console.log(item))
|
|
165
|
+
|
|
166
|
+
// prefer-fold: use fold for conditional logic
|
|
167
|
+
const result = Option.fromBoolean(condition1)
|
|
168
|
+
.map(() => value1)
|
|
169
|
+
.orElse(() => Option.fromBoolean(condition2).map(() => value2))
|
|
170
|
+
.getOrElse(defaultValue)
|
|
140
171
|
```
|
|
141
172
|
|
|
142
|
-
##
|
|
173
|
+
## Functype Integration
|
|
143
174
|
|
|
144
|
-
-
|
|
145
|
-
- **`strict`**: Maximum functional programming enforcement for pure FP codebases
|
|
175
|
+
The plugin is **functype-aware** and won't flag code that's already using functype properly:
|
|
146
176
|
|
|
147
|
-
|
|
177
|
+
```typescript
|
|
178
|
+
import { Option, List } from 'functype'
|
|
148
179
|
|
|
149
|
-
|
|
180
|
+
// ✅ These won't be flagged - already using functype correctly
|
|
181
|
+
const user = Option.some({ name: "Alice" })
|
|
182
|
+
const items = List.from([1, 2, 3])
|
|
183
|
+
const result = user.map(u => u.name).getOrElse("Unknown")
|
|
150
184
|
|
|
151
|
-
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
185
|
+
// ❌ These will still be flagged - bad patterns even with functype available
|
|
186
|
+
const badUser: User | null = null // prefer-option
|
|
187
|
+
const badItems = [1, 2, 3] // prefer-list
|
|
188
|
+
```
|
|
155
189
|
|
|
156
190
|
## CLI Tools
|
|
157
191
|
|
|
158
|
-
### List All
|
|
159
|
-
|
|
160
|
-
See exactly which rules are configured in each preset:
|
|
192
|
+
### List All Rules
|
|
161
193
|
|
|
162
194
|
```bash
|
|
163
|
-
#
|
|
195
|
+
# After installation
|
|
196
|
+
npx functype-list-rules
|
|
197
|
+
|
|
198
|
+
# During development
|
|
164
199
|
pnpm run list-rules
|
|
165
200
|
|
|
166
|
-
#
|
|
167
|
-
pnpm run list-rules:verbose
|
|
201
|
+
# Verbose output with configurations
|
|
202
|
+
pnpm run list-rules:verbose
|
|
168
203
|
|
|
169
|
-
#
|
|
204
|
+
# Usage examples
|
|
170
205
|
pnpm run list-rules:usage
|
|
206
|
+
```
|
|
171
207
|
|
|
172
|
-
|
|
173
|
-
pnpm run check-deps
|
|
208
|
+
### Development Commands
|
|
174
209
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
210
|
+
```bash
|
|
211
|
+
# Install dependencies
|
|
212
|
+
pnpm install
|
|
213
|
+
|
|
214
|
+
# Build plugin
|
|
215
|
+
pnpm run build
|
|
178
216
|
|
|
179
|
-
|
|
217
|
+
# Run tests (99 tests)
|
|
218
|
+
pnpm test
|
|
180
219
|
|
|
181
|
-
|
|
220
|
+
# Lint codebase
|
|
221
|
+
pnpm run lint
|
|
182
222
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
functype-list-rules --help
|
|
223
|
+
# Type check
|
|
224
|
+
pnpm run typecheck
|
|
186
225
|
|
|
187
|
-
#
|
|
188
|
-
|
|
226
|
+
# Run all quality checks
|
|
227
|
+
pnpm run check
|
|
189
228
|
```
|
|
190
229
|
|
|
191
|
-
##
|
|
230
|
+
## Architecture
|
|
192
231
|
|
|
193
|
-
|
|
194
|
-
- ✅ **Testing** on Node.js 18, 20, 22
|
|
195
|
-
- ✅ **Linting** with our own rules
|
|
196
|
-
- ✅ **Building** and validation
|
|
197
|
-
- ✅ **Publishing** to npm on version changes
|
|
232
|
+
### Philosophy: Custom Rules for Precise Control
|
|
198
233
|
|
|
199
|
-
|
|
234
|
+
This plugin provides **custom ESLint rules** specifically designed for functional TypeScript patterns, rather than composing existing rules. This approach offers:
|
|
200
235
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
236
|
+
- 🎯 **Precise AST Analysis** - Rules understand TypeScript-specific patterns
|
|
237
|
+
- 🔧 **Smart Auto-Fixing** - Context-aware fixes that maintain code intent
|
|
238
|
+
- 📚 **Functype Integration** - Built-in detection of functype library usage
|
|
239
|
+
- 🚀 **Better Performance** - Single-pass analysis instead of multiple rule evaluations
|
|
204
240
|
|
|
205
|
-
|
|
206
|
-
pnpm run build
|
|
241
|
+
### ESLint 9+ Flat Config Only
|
|
207
242
|
|
|
208
|
-
|
|
209
|
-
|
|
243
|
+
- **Modern Configuration** - Uses ESLint 9.x flat config format
|
|
244
|
+
- **No Legacy Support** - Clean architecture without backwards compatibility burden
|
|
245
|
+
- **Plugin-First Design** - Designed specifically as an ESLint plugin
|
|
210
246
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
247
|
+
### Test Coverage
|
|
248
|
+
|
|
249
|
+
- **99 Tests Total** across 10 test suites
|
|
250
|
+
- **Integration Tests** with real functype library usage
|
|
251
|
+
- **Auto-Fix Verification** ensures fixes produce valid code
|
|
252
|
+
- **False Positive Prevention** tests ensure proper functype patterns aren't flagged
|
|
214
253
|
|
|
215
|
-
##
|
|
254
|
+
## Contributing
|
|
216
255
|
|
|
217
|
-
|
|
256
|
+
1. **Fork** the repository
|
|
257
|
+
2. **Create** a feature branch: `git checkout -b feature-name`
|
|
258
|
+
3. **Make** your changes and add tests
|
|
259
|
+
4. **Ensure** all quality checks pass: `pnpm run check`
|
|
260
|
+
5. **Submit** a pull request
|
|
218
261
|
|
|
219
|
-
|
|
262
|
+
### Development Setup
|
|
220
263
|
|
|
221
|
-
**Quick Check:**
|
|
222
264
|
```bash
|
|
223
|
-
|
|
265
|
+
git clone https://github.com/jordanburke/eslint-plugin-functype.git
|
|
266
|
+
cd eslint-plugin-functype
|
|
267
|
+
pnpm install
|
|
268
|
+
pnpm run build
|
|
269
|
+
pnpm test
|
|
224
270
|
```
|
|
225
271
|
|
|
226
|
-
|
|
272
|
+
## License
|
|
227
273
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
274
|
+
[MIT](LICENSE) © [Jordan Burke](https://github.com/jordanburke)
|
|
275
|
+
|
|
276
|
+
## Related
|
|
277
|
+
|
|
278
|
+
- **[functype](https://github.com/jordanburke/functype)** - Functional programming library for TypeScript
|
|
279
|
+
- **[eslint-config-functype](https://github.com/jordanburke/eslint-config-functype)** - Complete ESLint config for functional TypeScript projects
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
**Need help?** [Open an issue](https://github.com/jordanburke/eslint-plugin-functype/issues) or check the [functype documentation](https://jordanburke.github.io/functype/).
|