eslint-plugin-functype
v1.4.0
Published
Custom ESLint rules for functional TypeScript programming with functype library patterns including Do notation (ESLint 9.x+)
Maintainers
Readme
eslint-plugin-functype
Custom ESLint rules for functional TypeScript programming with functype library patterns. Enforces immutability, type safety, and functional programming best practices for ESLint 9+.
Features
- 🔧 9 Custom ESLint Rules - Purpose-built for functional TypeScript patterns
- 🎭 Do Notation Support - New rule suggests functype's Do notation for complex monadic chains
- 🏗️ Functype Library Integration - Smart detection when functype is already being used properly
- 🛠️ Auto-Fixable - Most violations can be automatically fixed with
--fix - ⚡ ESLint 9+ Flat Config - Modern ESLint configuration format
- 🎯 TypeScript Native - Built specifically for TypeScript AST patterns
- 🎨 Visual Test Output - Beautiful before/after transformations with colorized diffs
- 📊 100+ Tests - Comprehensive test coverage including real functype integration
Rules
| Rule | Description | Auto-Fix |
| --------------------- | ----------------------------------------------------------------- | -------- |
| prefer-option | Prefer Option<T> over nullable types (T \| null \| undefined) | ✅ |
| prefer-either | Prefer Either<E, T> over try/catch and throw statements | ✅ |
| prefer-list | Prefer List<T> over native arrays for immutable collections | ✅ |
| prefer-fold | Prefer .fold() over complex if/else chains | ✅ |
| prefer-map | Prefer .map() over imperative transformations | ✅ |
| prefer-flatmap | Prefer .flatMap() over .map().flat() patterns | ✅ |
| no-get-unsafe | Disallow unsafe .get() calls on Option/Either types | ❌ |
| no-imperative-loops | Prefer functional iteration over imperative loops | ✅ |
| prefer-do-notation | Prefer Do notation for complex monadic compositions | ✅ |
Installation
npm install --save-dev eslint-plugin-functype
# or
pnpm add -D eslint-plugin-functypeOptional: Install functype library for enhanced integration:
npm install functype
# or
pnpm add functypeUsage
ESLint 9+ Flat Config (Recommended)
// eslint.config.mjs
import functypePlugin from "eslint-plugin-functype"
import tsParser from "@typescript-eslint/parser"
export default [
{
files: ["**/*.ts", "**/*.tsx"],
plugins: {
functype: functypePlugin,
},
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: "module",
},
},
rules: {
// All rules as errors
"functype/prefer-option": "error",
"functype/prefer-either": "error",
"functype/prefer-list": "error",
"functype/prefer-fold": "error",
"functype/prefer-map": "error",
"functype/prefer-flatmap": "error",
"functype/no-get-unsafe": "error",
"functype/no-imperative-loops": "error",
"functype/prefer-do-notation": "error",
},
},
]Individual Rule Configuration
// eslint.config.mjs - Selective rules
export default [
{
files: ["**/*.ts"],
plugins: { functype: functypePlugin },
rules: {
// Start with just type safety rules
"functype/prefer-option": "warn",
"functype/no-get-unsafe": "error",
// Add more as your codebase evolves
"functype/prefer-list": "off", // Disable for gradual adoption
"functype/prefer-do-notation": "warn", // New: suggest Do notation
},
},
]Examples
❌ Before (violations flagged)
// prefer-option: nullable types
const user: User | null = findUser(id)
function getAge(): number | undefined {
/* ... */
}
// prefer-either: try/catch blocks
try {
const result = riskyOperation()
return result
} catch (error) {
console.error(error)
return null
}
// prefer-list: native arrays
const items: number[] = [1, 2, 3]
const readonlyItems: ReadonlyArray<string> = ["a", "b"]
// no-imperative-loops: for/while loops
for (let i = 0; i < items.length; i++) {
console.log(items[i])
}
// prefer-fold: complex if/else chains
if (condition1) {
return value1
} else if (condition2) {
return value2
} else {
return defaultValue
}
// prefer-do-notation: nested null checks
const city = (user && user.address && user.address.city) || "Unknown"
// prefer-do-notation: chained flatMap operations
const result = option1
.flatMap((x) => getOption2(x))
.flatMap((y) => getOption3(y))
.flatMap((z) => getOption4(z))✅ After (auto-fixed or manually corrected)
import { Option, Either, List, Do, $ } from "functype"
// prefer-option: use Option<T>
const user: Option<User> = Option.fromNullable(findUser(id))
function getAge(): Option<number> {
/* ... */
}
// prefer-either: use Either<E, T>
function safeOperation(): Either<Error, Result> {
try {
const result = riskyOperation()
return Either.right(result)
} catch (error) {
return Either.left(error as Error)
}
}
// prefer-list: use List<T>
const items: List<number> = List.from([1, 2, 3])
const readonlyItems: List<string> = List.from(["a", "b"])
// no-imperative-loops: use functional methods
items.forEach((item) => console.log(item))
// prefer-fold: use fold for conditional logic
const result = Option.fromBoolean(condition1)
.map(() => value1)
.orElse(() => Option.fromBoolean(condition2).map(() => value2))
.getOrElse(defaultValue)
// prefer-do-notation: use Do notation for nested checks
const city = Do(function* () {
const u = yield* $(Option(user))
const addr = yield* $(Option(u.address))
return yield* $(Option(addr.city))
}).getOrElse("Unknown")
// prefer-do-notation: use Do for complex chains
const result = Do(function* () {
const x = yield* $(option1)
const y = yield* $(getOption2(x))
const z = yield* $(getOption3(y))
return yield* $(getOption4(z))
})Functype Integration
The plugin is functype-aware and won't flag code that's already using functype properly:
import { Option, List } from "functype"
// ✅ These won't be flagged - already using functype correctly
const user = Option.some({ name: "Alice" })
const items = List.from([1, 2, 3])
const result = user.map((u) => u.name).getOrElse("Unknown")
// ❌ These will still be flagged - bad patterns even with functype available
const badUser: User | null = null // prefer-option
const badItems = [1, 2, 3] // prefer-listCLI Tools
List All Rules
# After installation
npx functype-list-rules
# During development
pnpm run list-rules
# Verbose output with configurations
pnpm run list-rules:verbose
# Usage examples
pnpm run list-rules:usageDevelopment Commands
# Install dependencies
pnpm install
# Build plugin
pnpm run build
# Run tests (100+ tests)
pnpm test
# Visual transformation demo
pnpm test tests/rules/visual-transformation-demo.test.ts
# Lint codebase
pnpm run lint
# Type check
pnpm run typecheck
# Run all quality checks
pnpm run checkArchitecture
Philosophy: Custom Rules for Precise Control
This plugin provides custom ESLint rules specifically designed for functional TypeScript patterns, rather than composing existing rules. This approach offers:
- 🎯 Precise AST Analysis - Rules understand TypeScript-specific patterns
- 🔧 Smart Auto-Fixing - Context-aware fixes that maintain code intent
- 📚 Functype Integration - Built-in detection of functype library usage
- 🚀 Better Performance - Single-pass analysis instead of multiple rule evaluations
ESLint 9+ Flat Config Only
- Modern Configuration - Uses ESLint 9.x flat config format
- No Legacy Support - Clean architecture without backwards compatibility burden
- Plugin-First Design - Designed specifically as an ESLint plugin
Test Coverage
- 100+ Tests Total across 11 test suites (including visual tests)
- Integration Tests with real functype library usage
- Auto-Fix Verification ensures fixes produce valid code
- Visual Test Output with colorized before/after transformations
- False Positive Prevention tests ensure proper functype patterns aren't flagged
Contributing
- Fork the repository
- Create a feature branch:
git checkout -b feature-name - Make your changes and add tests
- Ensure all quality checks pass:
pnpm run check - Submit a pull request
Development Setup
Requirements:
- Node.js 22.0.0 or higher
- pnpm (recommended package manager)
git clone https://github.com/jordanburke/eslint-plugin-functype.git
cd eslint-plugin-functype
pnpm install
pnpm run build
pnpm testLicense
Related
- functype - Functional programming library for TypeScript
- eslint-config-functype - Complete ESLint config for functional TypeScript projects
Need help? Open an issue or check the functype documentation.
