eslint-plugin-functype 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024
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.
package/README.md ADDED
@@ -0,0 +1,210 @@
1
+ # eslint-plugin-functype
2
+
3
+ A curated ESLint configuration bundle for functional TypeScript programming. This plugin combines and configures rules from established ESLint plugins to enforce immutability patterns and functional programming best practices.
4
+
5
+ ## What This Plugin Does
6
+
7
+ Instead of recreating functional programming rules, this plugin provides carefully curated configurations that combine rules from:
8
+
9
+ - **eslint-plugin-functional**: Core functional programming rules
10
+ - **@typescript-eslint/eslint-plugin**: TypeScript-specific functional patterns
11
+ - **ESLint core**: JavaScript immutability basics
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install --save-dev eslint-plugin-functype @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-plugin-functional
17
+ # or
18
+ pnpm add -D eslint-plugin-functype @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-plugin-functional
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
+ }
34
+ ```
35
+
36
+ ### ESLint 8 - Strict Mode
37
+
38
+ ```javascript
39
+ // .eslintrc.js
40
+ module.exports = {
41
+ extends: ["plugin:functype/strict"],
42
+ parser: "@typescript-eslint/parser",
43
+ parserOptions: {
44
+ project: "./tsconfig.json",
45
+ },
46
+ }
47
+ ```
48
+
49
+ ### ESLint 9+ (Flat Config)
50
+
51
+ For ESLint 9+, create your own flat config using our rule selections:
52
+
53
+ ```javascript
54
+ // eslint.config.js
55
+ import js from "@eslint/js"
56
+ import tseslint from "@typescript-eslint/eslint-plugin"
57
+ import functional from "eslint-plugin-functional"
58
+ import parser from "@typescript-eslint/parser"
59
+
60
+ export default [
61
+ js.configs.recommended,
62
+ {
63
+ files: ["**/*.ts", "**/*.tsx"],
64
+ languageOptions: {
65
+ parser,
66
+ parserOptions: {
67
+ ecmaVersion: "latest",
68
+ sourceType: "module",
69
+ project: "./tsconfig.json",
70
+ },
71
+ },
72
+ plugins: {
73
+ "@typescript-eslint": tseslint,
74
+ functional,
75
+ },
76
+ rules: {
77
+ // Core immutability
78
+ "prefer-const": "error",
79
+ "no-var": "error",
80
+
81
+ // TypeScript functional patterns
82
+ "@typescript-eslint/consistent-type-imports": "error",
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
90
+ },
91
+ }
92
+ ]
93
+ ```
94
+
95
+ ### Individual Rule Usage
96
+
97
+ You can also use individual rules without our presets:
98
+
99
+ ```javascript
100
+ {
101
+ "plugins": ["@typescript-eslint", "functional"],
102
+ "rules": {
103
+ "functional/no-let": "error",
104
+ "functional/immutable-data": "warn",
105
+ "@typescript-eslint/no-explicit-any": "error",
106
+ "prefer-const": "error"
107
+ }
108
+ }
109
+ ```
110
+
111
+ ## What Rules Are Included
112
+
113
+ ### Recommended Configuration
114
+ - ✅ `prefer-const` / `no-var` - Basic immutability
115
+ - ✅ `functional/no-let` - Disallow `let` declarations
116
+ - ⚠️ `functional/immutable-data` - Warn on data mutation
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
120
+
121
+ ### Strict Configuration
122
+ All recommended rules plus:
123
+ - ✅ `functional/no-loop-statements` - Disallow imperative loops
124
+ - ✅ `functional/immutable-data` - Error on data mutation
125
+ - ✅ `functional/prefer-immutable-types` - Encourage readonly types
126
+ - ✅ `@typescript-eslint/explicit-function-return-type` - Explicit return types
127
+
128
+ ## Examples
129
+
130
+ ```typescript
131
+ // ❌ Bad (will be flagged)
132
+ let x = 1; // functional/no-let
133
+ arr.push(item); // functional/immutable-data
134
+ for(let i = 0; i < 10; i++) // functional/no-loop-statements (strict only)
135
+
136
+ // ✅ Good
137
+ const x = 1;
138
+ const newArr = [...arr, item];
139
+ arr.forEach(item => process(item));
140
+ ```
141
+
142
+ ## Configurations
143
+
144
+ - **`recommended`**: Balanced functional programming rules suitable for most projects
145
+ - **`strict`**: Maximum functional programming enforcement for pure FP codebases
146
+
147
+ ## Philosophy
148
+
149
+ This plugin follows the principle of **composition over recreation**. Rather than maintaining custom rules, we curate and combine battle-tested rules from the community, ensuring:
150
+
151
+ - ✅ Less maintenance burden
152
+ - ✅ Better rule quality and edge case handling
153
+ - ✅ Automatic updates from upstream plugins
154
+ - ✅ Community-driven improvements
155
+
156
+ ## CLI Tools
157
+
158
+ ### List All Supported Rules
159
+
160
+ See exactly which rules are configured in each preset:
161
+
162
+ ```bash
163
+ # Basic rule listing
164
+ pnpm run list-rules
165
+
166
+ # Show rule options/configuration
167
+ pnpm run list-rules:verbose
168
+
169
+ # Show usage examples
170
+ pnpm run list-rules:usage
171
+
172
+ # Show help
173
+ pnpm run cli:help
174
+ ```
175
+
176
+ ### After Installation
177
+
178
+ Once installed globally or in a project, you can also use:
179
+
180
+ ```bash
181
+ # If installed globally
182
+ functype-list-rules --help
183
+
184
+ # Or with npx
185
+ npx eslint-plugin-functype functype-list-rules
186
+ ```
187
+
188
+ ## CI/CD
189
+
190
+ This plugin includes GitHub Actions workflows for:
191
+ - ✅ **Testing** on Node.js 18, 20, 22
192
+ - ✅ **Linting** with our own rules
193
+ - ✅ **Building** and validation
194
+ - ✅ **Publishing** to npm on version changes
195
+
196
+ ## Development
197
+
198
+ ```bash
199
+ # Install dependencies
200
+ pnpm install
201
+
202
+ # Build
203
+ pnpm run build
204
+
205
+ # Lint
206
+ pnpm run lint
207
+
208
+ # List rules during development
209
+ pnpm run list-rules
210
+ ```
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,233 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ const fs_1 = __importDefault(require("fs"));
41
+ const path_1 = __importDefault(require("path"));
42
+ // Colors for console output
43
+ const colors = {
44
+ reset: '\x1b[0m',
45
+ bright: '\x1b[1m',
46
+ red: '\x1b[31m',
47
+ green: '\x1b[32m',
48
+ yellow: '\x1b[33m',
49
+ blue: '\x1b[34m',
50
+ magenta: '\x1b[35m',
51
+ cyan: '\x1b[36m',
52
+ };
53
+ function colorize(text, color) {
54
+ return colors[color] + text + colors.reset;
55
+ }
56
+ async function loadConfig(configPath) {
57
+ try {
58
+ // For built JS files, use require
59
+ if (configPath.endsWith('.js')) {
60
+ const configModule = require(configPath);
61
+ return configModule.default || configModule;
62
+ }
63
+ // For TS files, we'd need to use dynamic import
64
+ const configModule = await Promise.resolve(`${configPath}`).then(s => __importStar(require(s)));
65
+ return configModule.default || configModule;
66
+ }
67
+ catch (error) {
68
+ console.error(colorize(`Error loading config from ${configPath}:`, 'red'), error.message);
69
+ return null;
70
+ }
71
+ }
72
+ function extractRules(config) {
73
+ const rules = new Map();
74
+ if (config.rules) {
75
+ Object.entries(config.rules).forEach(([ruleName, ruleConfig]) => {
76
+ const severity = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig;
77
+ const options = Array.isArray(ruleConfig) ? ruleConfig.slice(1) : [];
78
+ rules.set(ruleName, {
79
+ severity,
80
+ options,
81
+ source: getRuleSource(ruleName)
82
+ });
83
+ });
84
+ }
85
+ return rules;
86
+ }
87
+ function getRuleSource(ruleName) {
88
+ if (ruleName.startsWith('functional/'))
89
+ return 'eslint-plugin-functional';
90
+ if (ruleName.startsWith('@typescript-eslint/'))
91
+ return '@typescript-eslint/eslint-plugin';
92
+ return 'eslint (core)';
93
+ }
94
+ function getSeverityColor(severity) {
95
+ switch (severity) {
96
+ case 'error':
97
+ case 2:
98
+ return 'red';
99
+ case 'warn':
100
+ case 1:
101
+ return 'yellow';
102
+ case 'off':
103
+ case 0:
104
+ return 'cyan';
105
+ default:
106
+ return 'reset';
107
+ }
108
+ }
109
+ function formatSeverity(severity) {
110
+ const severityMap = {
111
+ '0': 'off',
112
+ '1': 'warn',
113
+ '2': 'error',
114
+ 'off': 'off',
115
+ 'warn': 'warn',
116
+ 'error': 'error'
117
+ };
118
+ return severityMap[String(severity)] || String(severity);
119
+ }
120
+ function printRules(configName, rules) {
121
+ console.log(colorize(`\n📋 ${configName} Configuration Rules:`, 'bright'));
122
+ console.log(colorize('='.repeat(50), 'blue'));
123
+ // Group rules by source
124
+ const rulesBySource = new Map();
125
+ rules.forEach((ruleData, ruleName) => {
126
+ const source = ruleData.source;
127
+ if (!rulesBySource.has(source)) {
128
+ rulesBySource.set(source, new Map());
129
+ }
130
+ rulesBySource.get(source).set(ruleName, ruleData);
131
+ });
132
+ // Print rules grouped by source
133
+ rulesBySource.forEach((sourceRules, source) => {
134
+ console.log(colorize(`\n📦 ${source}:`, 'magenta'));
135
+ sourceRules.forEach((ruleData, ruleName) => {
136
+ const shortName = ruleName.replace(/^.*\//, '');
137
+ const severity = formatSeverity(ruleData.severity);
138
+ const severityColored = colorize(`[${severity.toUpperCase()}]`, getSeverityColor(ruleData.severity));
139
+ const hasOptions = ruleData.options && ruleData.options.length > 0;
140
+ const optionsText = hasOptions ? colorize(' (with options)', 'cyan') : '';
141
+ console.log(` ${severityColored} ${colorize(shortName, 'green')}${optionsText}`);
142
+ if (hasOptions && process.argv.includes('--verbose')) {
143
+ console.log(` ${colorize('Options:', 'cyan')} ${JSON.stringify(ruleData.options)}`);
144
+ }
145
+ });
146
+ });
147
+ }
148
+ function printSummary(configs) {
149
+ console.log(colorize('\n📊 Summary:', 'bright'));
150
+ console.log(colorize('='.repeat(30), 'blue'));
151
+ configs.forEach(({ name, rules }) => {
152
+ const totalRules = rules.size;
153
+ const errorRules = Array.from(rules.values()).filter(r => r.severity === 'error' || r.severity === 2).length;
154
+ const warnRules = Array.from(rules.values()).filter(r => r.severity === 'warn' || r.severity === 1).length;
155
+ const offRules = Array.from(rules.values()).filter(r => r.severity === 'off' || r.severity === 0).length;
156
+ console.log(`\n${colorize(name, 'bright')}: ${totalRules} total rules`);
157
+ console.log(` ${colorize('●', 'red')} ${errorRules} errors`);
158
+ console.log(` ${colorize('●', 'yellow')} ${warnRules} warnings`);
159
+ console.log(` ${colorize('●', 'cyan')} ${offRules} disabled`);
160
+ });
161
+ }
162
+ function printUsageInfo() {
163
+ console.log(colorize('\n💡 Usage Information:', 'bright'));
164
+ console.log(colorize('='.repeat(30), 'blue'));
165
+ console.log('\n📖 How to use these configurations:');
166
+ console.log('\n' + colorize('ESLint 8 (.eslintrc):', 'green'));
167
+ console.log(' extends: ["plugin:functype/recommended"]');
168
+ console.log('\n' + colorize('ESLint 9+ (flat config):', 'green'));
169
+ console.log(' Copy the rules from our documentation into your eslint.config.js');
170
+ console.log('\n' + colorize('Individual rules:', 'green'));
171
+ console.log(' You can enable any rule individually in your rules section');
172
+ }
173
+ async function main() {
174
+ const args = process.argv.slice(2);
175
+ const showHelp = args.includes('--help') || args.includes('-h');
176
+ const showUsage = args.includes('--usage') || args.includes('-u');
177
+ if (showHelp) {
178
+ console.log(colorize('📋 ESLint Plugin Functype - Rule Lister', 'bright'));
179
+ console.log('\nUsage: pnpm run list-rules [options]');
180
+ console.log('\nOptions:');
181
+ console.log(' --verbose, -v Show rule options');
182
+ console.log(' --usage, -u Show usage examples');
183
+ console.log(' --help, -h Show this help message');
184
+ console.log('\nThis command lists all rules configured in the functype plugin configurations.');
185
+ return;
186
+ }
187
+ console.log(colorize('🔧 ESLint Plugin Functype - Supported Rules', 'bright'));
188
+ const distPath = path_1.default.join(__dirname, '..', '..', 'dist');
189
+ if (!fs_1.default.existsSync(distPath)) {
190
+ console.error(colorize('❌ Build directory not found. Run `pnpm run build` first.', 'red'));
191
+ process.exit(1);
192
+ }
193
+ const configs = [
194
+ {
195
+ name: 'Recommended',
196
+ path: path_1.default.join(distPath, 'configs', 'recommended.js')
197
+ },
198
+ {
199
+ name: 'Strict',
200
+ path: path_1.default.join(distPath, 'configs', 'strict.js')
201
+ }
202
+ ];
203
+ const loadedConfigs = [];
204
+ for (const { name, path: configPath } of configs) {
205
+ const config = await loadConfig(configPath);
206
+ if (config) {
207
+ const rules = extractRules(config);
208
+ loadedConfigs.push({ name, rules });
209
+ printRules(name, rules);
210
+ }
211
+ }
212
+ if (loadedConfigs.length > 0) {
213
+ printSummary(loadedConfigs);
214
+ if (showUsage) {
215
+ printUsageInfo();
216
+ }
217
+ console.log(colorize('\n💡 Tips:', 'bright'));
218
+ console.log('• Use --verbose to see rule options');
219
+ console.log('• Use --usage to see configuration examples');
220
+ console.log('• Red rules will cause build failures');
221
+ console.log('• Yellow rules are warnings only');
222
+ console.log('• Blue rules are disabled by default');
223
+ console.log(colorize('\n🔗 Links:', 'bright'));
224
+ console.log('• Documentation: https://github.com/jordanburke/eslint-plugin-functype');
225
+ console.log('• eslint-plugin-functional: https://github.com/eslint-functional/eslint-plugin-functional');
226
+ console.log('• @typescript-eslint: https://typescript-eslint.io/');
227
+ }
228
+ }
229
+ // Run the CLI
230
+ main().catch(error => {
231
+ console.error(colorize('❌ Unexpected error:', 'red'), error);
232
+ process.exit(1);
233
+ });
@@ -0,0 +1,23 @@
1
+ declare const _default: {
2
+ plugins: string[];
3
+ extends: string[];
4
+ rules: {
5
+ "prefer-const": string;
6
+ "no-var": string;
7
+ "@typescript-eslint/consistent-type-imports": string;
8
+ "@typescript-eslint/no-explicit-any": string;
9
+ "@typescript-eslint/no-unused-vars": (string | {
10
+ argsIgnorePattern: string;
11
+ })[];
12
+ "functional/no-let": string;
13
+ "functional/immutable-data": string;
14
+ "functional/no-loop-statements": string;
15
+ "functional/prefer-immutable-types": string;
16
+ "functional/no-mixed-types": string;
17
+ "functional/functional-parameters": string;
18
+ "functional/no-conditional-statements": string;
19
+ "functional/no-expression-statements": string;
20
+ "functional/no-return-void": string;
21
+ };
22
+ };
23
+ export default _default;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ // Legacy ESLint config format for compatibility
4
+ exports.default = {
5
+ plugins: [
6
+ "@typescript-eslint",
7
+ "functional"
8
+ ],
9
+ extends: [
10
+ "eslint:recommended",
11
+ "@typescript-eslint/recommended"
12
+ ],
13
+ rules: {
14
+ // Core JavaScript immutability
15
+ "prefer-const": "error",
16
+ "no-var": "error",
17
+ // TypeScript functional patterns
18
+ "@typescript-eslint/consistent-type-imports": "error",
19
+ "@typescript-eslint/no-explicit-any": "error",
20
+ "@typescript-eslint/no-unused-vars": [
21
+ "error",
22
+ { argsIgnorePattern: "^_" }
23
+ ],
24
+ // Functional programming rules from eslint-plugin-functional
25
+ "functional/no-let": "error",
26
+ "functional/immutable-data": "warn",
27
+ "functional/no-loop-statements": "off", // Start disabled, can enable later
28
+ "functional/prefer-immutable-types": "off", // Too strict for most projects
29
+ "functional/no-mixed-types": "off",
30
+ "functional/functional-parameters": "off",
31
+ // Allow some flexibility
32
+ "functional/no-conditional-statements": "off",
33
+ "functional/no-expression-statements": "off",
34
+ "functional/no-return-void": "off",
35
+ },
36
+ };
@@ -0,0 +1,25 @@
1
+ declare const _default: {
2
+ rules: {
3
+ "functional/no-loop-statements": string;
4
+ "functional/immutable-data": string;
5
+ "functional/prefer-immutable-types": string;
6
+ "functional/functional-parameters": string;
7
+ "@typescript-eslint/explicit-function-return-type": string;
8
+ "@typescript-eslint/no-non-null-assertion": string;
9
+ "prefer-const": string;
10
+ "no-var": string;
11
+ "@typescript-eslint/consistent-type-imports": string;
12
+ "@typescript-eslint/no-explicit-any": string;
13
+ "@typescript-eslint/no-unused-vars": (string | {
14
+ argsIgnorePattern: string;
15
+ })[];
16
+ "functional/no-let": string;
17
+ "functional/no-mixed-types": string;
18
+ "functional/no-conditional-statements": string;
19
+ "functional/no-expression-statements": string;
20
+ "functional/no-return-void": string;
21
+ };
22
+ plugins: string[];
23
+ extends: string[];
24
+ };
25
+ export default _default;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const recommended_1 = __importDefault(require("./recommended"));
7
+ exports.default = {
8
+ ...recommended_1.default,
9
+ rules: {
10
+ ...recommended_1.default.rules,
11
+ // Enable stricter functional rules
12
+ "functional/no-loop-statements": "error",
13
+ "functional/immutable-data": "error",
14
+ "functional/prefer-immutable-types": "warn",
15
+ "functional/functional-parameters": "warn",
16
+ // Stricter TypeScript rules (non-type-aware)
17
+ "@typescript-eslint/explicit-function-return-type": "error",
18
+ "@typescript-eslint/no-non-null-assertion": "error",
19
+ },
20
+ };
@@ -0,0 +1,51 @@
1
+ declare const plugin: {
2
+ configs: {
3
+ recommended: {
4
+ plugins: string[];
5
+ extends: string[];
6
+ rules: {
7
+ "prefer-const": string;
8
+ "no-var": string;
9
+ "@typescript-eslint/consistent-type-imports": string;
10
+ "@typescript-eslint/no-explicit-any": string;
11
+ "@typescript-eslint/no-unused-vars": (string | {
12
+ argsIgnorePattern: string;
13
+ })[];
14
+ "functional/no-let": string;
15
+ "functional/immutable-data": string;
16
+ "functional/no-loop-statements": string;
17
+ "functional/prefer-immutable-types": string;
18
+ "functional/no-mixed-types": string;
19
+ "functional/functional-parameters": string;
20
+ "functional/no-conditional-statements": string;
21
+ "functional/no-expression-statements": string;
22
+ "functional/no-return-void": string;
23
+ };
24
+ };
25
+ strict: {
26
+ rules: {
27
+ "functional/no-loop-statements": string;
28
+ "functional/immutable-data": string;
29
+ "functional/prefer-immutable-types": string;
30
+ "functional/functional-parameters": string;
31
+ "@typescript-eslint/explicit-function-return-type": string;
32
+ "@typescript-eslint/no-non-null-assertion": string;
33
+ "prefer-const": string;
34
+ "no-var": string;
35
+ "@typescript-eslint/consistent-type-imports": string;
36
+ "@typescript-eslint/no-explicit-any": string;
37
+ "@typescript-eslint/no-unused-vars": (string | {
38
+ argsIgnorePattern: string;
39
+ })[];
40
+ "functional/no-let": string;
41
+ "functional/no-mixed-types": string;
42
+ "functional/no-conditional-statements": string;
43
+ "functional/no-expression-statements": string;
44
+ "functional/no-return-void": string;
45
+ };
46
+ plugins: string[];
47
+ extends: string[];
48
+ };
49
+ };
50
+ };
51
+ export = plugin;
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ const recommended_1 = __importDefault(require("./configs/recommended"));
6
+ const strict_1 = __importDefault(require("./configs/strict"));
7
+ const plugin = {
8
+ configs: {
9
+ recommended: recommended_1.default,
10
+ strict: strict_1.default,
11
+ },
12
+ };
13
+ module.exports = plugin;
@@ -0,0 +1,2 @@
1
+ import { ESLintUtils } from "@typescript-eslint/utils";
2
+ export declare const immutableDataRule: ESLintUtils.RuleModule<"noMutation" | "noArrayMutation", [], ESLintUtils.RuleListener>;
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.immutableDataRule = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://github.com/jordanburke/eslint-plugin-functype/blob/main/docs/rules/${name}.md`);
6
+ exports.immutableDataRule = createRule({
7
+ name: "immutable-data",
8
+ meta: {
9
+ type: "problem",
10
+ docs: {
11
+ description: "Enforce immutable data patterns",
12
+ },
13
+ messages: {
14
+ noMutation: "Avoid mutating data. Use immutable update patterns instead.",
15
+ noArrayMutation: "Array method '{{method}}' mutates the array. Use {{alternative}} instead.",
16
+ },
17
+ schema: [],
18
+ },
19
+ defaultOptions: [],
20
+ create(context) {
21
+ const mutatingArrayMethods = new Map([
22
+ ["push", "concat or spread syntax"],
23
+ ["pop", "slice(0, -1)"],
24
+ ["shift", "slice(1)"],
25
+ ["unshift", "spread syntax"],
26
+ ["splice", "slice or filter"],
27
+ ["sort", "toSorted or [...].sort()"],
28
+ ["reverse", "toReversed or [...].reverse()"],
29
+ ]);
30
+ return {
31
+ AssignmentExpression(node) {
32
+ if (node.left.type === "MemberExpression" &&
33
+ node.operator === "=") {
34
+ context.report({
35
+ node,
36
+ messageId: "noMutation",
37
+ });
38
+ }
39
+ },
40
+ CallExpression(node) {
41
+ if (node.callee.type === "MemberExpression" &&
42
+ node.callee.property.type === "Identifier") {
43
+ const method = node.callee.property.name;
44
+ const alternative = mutatingArrayMethods.get(method);
45
+ if (alternative) {
46
+ // Allow [...arr].sort() and [...arr].reverse() since they create copies first
47
+ if ((method === "sort" || method === "reverse") &&
48
+ node.callee.object.type === "ArrayExpression" &&
49
+ node.callee.object.elements.length === 1 &&
50
+ node.callee.object.elements[0]?.type === "SpreadElement") {
51
+ return;
52
+ }
53
+ context.report({
54
+ node,
55
+ messageId: "noArrayMutation",
56
+ data: {
57
+ method,
58
+ alternative,
59
+ },
60
+ });
61
+ }
62
+ }
63
+ },
64
+ };
65
+ },
66
+ });
@@ -0,0 +1,5 @@
1
+ export declare const rules: {
2
+ "no-let": import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"noLet", [], import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
3
+ "immutable-data": import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"noMutation" | "noArrayMutation", [], import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
4
+ "no-loop-statements": import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"noForLoop" | "noWhileLoop" | "noDoWhileLoop" | "noForInLoop" | "noForOfLoop", [], import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
5
+ };
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rules = void 0;
4
+ const no_let_1 = require("./no-let");
5
+ const immutable_data_1 = require("./immutable-data");
6
+ const no_loop_statements_1 = require("./no-loop-statements");
7
+ exports.rules = {
8
+ "no-let": no_let_1.noLetRule,
9
+ "immutable-data": immutable_data_1.immutableDataRule,
10
+ "no-loop-statements": no_loop_statements_1.noLoopStatementsRule,
11
+ };
@@ -0,0 +1,2 @@
1
+ import { ESLintUtils } from "@typescript-eslint/utils";
2
+ export declare const noLetRule: ESLintUtils.RuleModule<"noLet", [], ESLintUtils.RuleListener>;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noLetRule = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://github.com/jordanburke/eslint-plugin-functype/blob/main/docs/rules/${name}.md`);
6
+ exports.noLetRule = createRule({
7
+ name: "no-let",
8
+ meta: {
9
+ type: "problem",
10
+ docs: {
11
+ description: "Disallow let declarations to enforce immutability",
12
+ },
13
+ messages: {
14
+ noLet: "Use 'const' instead of 'let' for immutable bindings",
15
+ },
16
+ schema: [],
17
+ fixable: "code",
18
+ },
19
+ defaultOptions: [],
20
+ create(context) {
21
+ return {
22
+ VariableDeclaration(node) {
23
+ if (node.kind === "let") {
24
+ context.report({
25
+ node,
26
+ messageId: "noLet",
27
+ fix(fixer) {
28
+ const sourceCode = context.sourceCode;
29
+ const letToken = sourceCode.getFirstToken(node);
30
+ if (letToken) {
31
+ return fixer.replaceText(letToken, "const");
32
+ }
33
+ return null;
34
+ },
35
+ });
36
+ }
37
+ },
38
+ };
39
+ },
40
+ });
@@ -0,0 +1,2 @@
1
+ import { ESLintUtils } from "@typescript-eslint/utils";
2
+ export declare const noLoopStatementsRule: ESLintUtils.RuleModule<"noForLoop" | "noWhileLoop" | "noDoWhileLoop" | "noForInLoop" | "noForOfLoop", [], ESLintUtils.RuleListener>;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noLoopStatementsRule = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://github.com/jordanburke/eslint-plugin-functype/blob/main/docs/rules/${name}.md`);
6
+ exports.noLoopStatementsRule = createRule({
7
+ name: "no-loop-statements",
8
+ meta: {
9
+ type: "problem",
10
+ docs: {
11
+ description: "Disallow loop statements in favor of functional alternatives",
12
+ },
13
+ messages: {
14
+ noForLoop: "Avoid for loops. Use array methods like map, filter, reduce instead.",
15
+ noWhileLoop: "Avoid while loops. Use recursive functions or array methods instead.",
16
+ noDoWhileLoop: "Avoid do-while loops. Use recursive functions or array methods instead.",
17
+ noForInLoop: "Avoid for-in loops. Use Object.keys, Object.values, or Object.entries instead.",
18
+ noForOfLoop: "Avoid for-of loops. Use array methods like forEach, map, filter instead.",
19
+ },
20
+ schema: [],
21
+ },
22
+ defaultOptions: [],
23
+ create(context) {
24
+ return {
25
+ ForStatement(node) {
26
+ context.report({
27
+ node,
28
+ messageId: "noForLoop",
29
+ });
30
+ },
31
+ WhileStatement(node) {
32
+ context.report({
33
+ node,
34
+ messageId: "noWhileLoop",
35
+ });
36
+ },
37
+ DoWhileStatement(node) {
38
+ context.report({
39
+ node,
40
+ messageId: "noDoWhileLoop",
41
+ });
42
+ },
43
+ ForInStatement(node) {
44
+ context.report({
45
+ node,
46
+ messageId: "noForInLoop",
47
+ });
48
+ },
49
+ ForOfStatement(node) {
50
+ context.report({
51
+ node,
52
+ messageId: "noForOfLoop",
53
+ });
54
+ },
55
+ };
56
+ },
57
+ });
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "eslint-plugin-functype",
3
+ "version": "1.0.0",
4
+ "description": "Curated ESLint configuration bundle for functional TypeScript programming",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist/",
9
+ "README.md",
10
+ "LICENSE"
11
+ ],
12
+ "bin": {
13
+ "functype-list-rules": "dist/cli/list-rules.js"
14
+ },
15
+ "keywords": [
16
+ "eslint",
17
+ "eslintplugin",
18
+ "eslint-plugin",
19
+ "functional",
20
+ "typescript",
21
+ "immutable"
22
+ ],
23
+ "peerDependencies": {
24
+ "@typescript-eslint/eslint-plugin": "^8.39.0",
25
+ "@typescript-eslint/parser": "^8.39.0",
26
+ "eslint": "^9.32.0",
27
+ "eslint-plugin-functional": "^9.0.2"
28
+ },
29
+ "dependencies": {
30
+ "@eslint/eslintrc": "^3.3.1",
31
+ "@eslint/js": "^9.32.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^22.17.0",
35
+ "@typescript-eslint/eslint-plugin": "^8.39.0",
36
+ "@typescript-eslint/parser": "^8.39.0",
37
+ "eslint": "^9.32.0",
38
+ "eslint-plugin-functional": "^9.0.2",
39
+ "typescript": "^5.9.2"
40
+ },
41
+ "author": "",
42
+ "license": "ISC",
43
+ "scripts": {
44
+ "build": "tsc",
45
+ "lint": "eslint src --ext .ts",
46
+ "list-rules": "node dist/cli/list-rules.js",
47
+ "list-rules:verbose": "node dist/cli/list-rules.js --verbose",
48
+ "list-rules:usage": "node dist/cli/list-rules.js --usage",
49
+ "cli:help": "node dist/cli/list-rules.js --help"
50
+ }
51
+ }