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 +21 -0
- package/README.md +210 -0
- package/dist/cli/list-rules.d.ts +2 -0
- package/dist/cli/list-rules.js +233 -0
- package/dist/configs/recommended.d.ts +23 -0
- package/dist/configs/recommended.js +36 -0
- package/dist/configs/strict.d.ts +25 -0
- package/dist/configs/strict.js +20 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +13 -0
- package/dist/rules/immutable-data.d.ts +2 -0
- package/dist/rules/immutable-data.js +66 -0
- package/dist/rules/index.d.ts +5 -0
- package/dist/rules/index.js +11 -0
- package/dist/rules/no-let.d.ts +2 -0
- package/dist/rules/no-let.js +40 -0
- package/dist/rules/no-loop-statements.d.ts +2 -0
- package/dist/rules/no-loop-statements.js +57 -0
- package/package.json +51 -0
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,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
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -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,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,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,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
|
+
}
|