eslint-plugin-maintainability 3.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.
Files changed (32) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/LICENSE +23 -0
  3. package/README.md +116 -0
  4. package/package.json +65 -0
  5. package/src/index.d.ts +180 -0
  6. package/src/index.js +56 -0
  7. package/src/rules/error-handling/error-message.d.ts +20 -0
  8. package/src/rules/error-handling/error-message.js +146 -0
  9. package/src/rules/error-handling/no-missing-error-context.d.ts +26 -0
  10. package/src/rules/error-handling/no-missing-error-context.js +183 -0
  11. package/src/rules/error-handling/no-silent-errors.d.ts +24 -0
  12. package/src/rules/error-handling/no-silent-errors.js +178 -0
  13. package/src/rules/error-handling/no-unhandled-promise.d.ts +26 -0
  14. package/src/rules/error-handling/no-unhandled-promise.js +351 -0
  15. package/src/rules/maintainability/cognitive-complexity.d.ts +28 -0
  16. package/src/rules/maintainability/cognitive-complexity.js +381 -0
  17. package/src/rules/maintainability/consistent-function-scoping.d.ts +20 -0
  18. package/src/rules/maintainability/consistent-function-scoping.js +226 -0
  19. package/src/rules/maintainability/identical-functions.d.ts +27 -0
  20. package/src/rules/maintainability/identical-functions.js +310 -0
  21. package/src/rules/maintainability/max-parameters.d.ts +29 -0
  22. package/src/rules/maintainability/max-parameters.js +143 -0
  23. package/src/rules/maintainability/nested-complexity-hotspots.d.ts +31 -0
  24. package/src/rules/maintainability/nested-complexity-hotspots.js +190 -0
  25. package/src/rules/maintainability/no-lonely-if.d.ts +19 -0
  26. package/src/rules/maintainability/no-lonely-if.js +120 -0
  27. package/src/rules/maintainability/no-nested-ternary.d.ts +19 -0
  28. package/src/rules/maintainability/no-nested-ternary.js +165 -0
  29. package/src/rules/maintainability/no-unreadable-iife.d.ts +24 -0
  30. package/src/rules/maintainability/no-unreadable-iife.js +239 -0
  31. package/src/types/index.d.ts +48 -0
  32. package/src/types/index.js +7 -0
@@ -0,0 +1,239 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2025 Ofri Peretz
4
+ * Licensed under the MIT License. Use of this source code is governed by the
5
+ * MIT license that can be found in the LICENSE file.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.noUnreadableIife = void 0;
9
+ const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
+ const eslint_devkit_2 = require("@interlace/eslint-devkit");
11
+ exports.noUnreadableIife = (0, eslint_devkit_1.createRule)({
12
+ name: 'no-unreadable-iife',
13
+ meta: {
14
+ type: 'suggestion',
15
+ docs: {
16
+ description: 'Prevent unreadable Immediately Invoked Function Expressions that harm code clarity',
17
+ },
18
+ hasSuggestions: true,
19
+ messages: {
20
+ unreadableIIFE: (0, eslint_devkit_2.formatLLMMessage)({
21
+ icon: eslint_devkit_2.MessageIcons.WARNING,
22
+ issueName: 'Unreadable IIFE',
23
+ description: 'Complex IIFE reduces code readability',
24
+ severity: 'MEDIUM',
25
+ fix: 'Extract to named function or simplify the IIFE structure',
26
+ documentationLink: 'https://developer.mozilla.org/en-US/docs/Glossary/IIFE',
27
+ }),
28
+ suggestNamedFunction: (0, eslint_devkit_2.formatLLMMessage)({
29
+ icon: eslint_devkit_2.MessageIcons.INFO,
30
+ issueName: 'Extract Function',
31
+ description: 'Extract complex logic to named function',
32
+ severity: 'LOW',
33
+ fix: 'function processData() { /* logic */ } processData();',
34
+ documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions',
35
+ }),
36
+ suggestBlockScope: (0, eslint_devkit_2.formatLLMMessage)({
37
+ icon: eslint_devkit_2.MessageIcons.INFO,
38
+ issueName: 'Use Block Scope',
39
+ description: 'Use block scope instead of IIFE',
40
+ severity: 'LOW',
41
+ fix: '{ const isolated = value; /* logic */ }',
42
+ documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block',
43
+ }),
44
+ complexIIFE: (0, eslint_devkit_2.formatLLMMessage)({
45
+ icon: eslint_devkit_2.MessageIcons.INFO,
46
+ issueName: 'Simplify IIFE',
47
+ description: 'Complex IIFE detected',
48
+ severity: 'LOW',
49
+ fix: 'Simplify or extract to separate function',
50
+ documentationLink: 'https://developer.mozilla.org/en-US/docs/Glossary/IIFE',
51
+ }),
52
+ },
53
+ schema: [
54
+ {
55
+ type: 'object',
56
+ properties: {
57
+ maxStatements: {
58
+ type: 'number',
59
+ minimum: 1,
60
+ default: 3,
61
+ },
62
+ maxDepth: {
63
+ type: 'number',
64
+ minimum: 1,
65
+ default: 2,
66
+ },
67
+ allowReturningIIFE: {
68
+ type: 'boolean',
69
+ default: true,
70
+ },
71
+ },
72
+ additionalProperties: false,
73
+ },
74
+ ],
75
+ },
76
+ defaultOptions: [{ maxStatements: 3, maxDepth: 2, allowReturningIIFE: true }],
77
+ create(context) {
78
+ const [options] = context.options;
79
+ const { maxStatements = 3, maxDepth = 2, allowReturningIIFE = true } = options || {};
80
+ function isIIFE(node) {
81
+ // Check if this is a call expression with a function expression or arrow function
82
+ // Note: In typescript-eslint AST, parentheses don't create a separate node type,
83
+ // so (function(){})() will have callee.type === 'FunctionExpression' directly
84
+ if (node.callee.type === 'FunctionExpression' ||
85
+ node.callee.type === 'ArrowFunctionExpression') {
86
+ return true;
87
+ }
88
+ // Check for unary operator IIFEs like !function(){}(), +function(){}(), void function(){}()
89
+ if (node.callee.type === 'UnaryExpression' &&
90
+ (node.callee.argument.type === 'FunctionExpression' ||
91
+ node.callee.argument.type === 'ArrowFunctionExpression')) {
92
+ return true;
93
+ }
94
+ return false;
95
+ }
96
+ function countStatements(body) {
97
+ if (body.type === 'BlockStatement') {
98
+ return body.body.length;
99
+ }
100
+ return 1; // Single expression
101
+ }
102
+ function calculateDepth(node, currentDepth = 0) {
103
+ let maxDepthFound = currentDepth;
104
+ // Check for nested constructs that increase complexity
105
+ if (node.type === 'IfStatement' ||
106
+ node.type === 'ForStatement' ||
107
+ node.type === 'WhileStatement' ||
108
+ node.type === 'DoWhileStatement' ||
109
+ node.type === 'SwitchStatement' ||
110
+ node.type === 'TryStatement') {
111
+ currentDepth++;
112
+ maxDepthFound = Math.max(maxDepthFound, currentDepth);
113
+ }
114
+ // Properties to skip to avoid circular references
115
+ const skipProperties = new Set(['parent', 'tokens', 'comments', 'loc', 'range']);
116
+ // Recursively check children
117
+ for (const key in node) {
118
+ // Skip properties that cause circular references or aren't AST nodes
119
+ if (skipProperties.has(key)) {
120
+ continue;
121
+ }
122
+ const child = node[key];
123
+ if (child && typeof child === 'object') {
124
+ if (Array.isArray(child)) {
125
+ for (const item of child) {
126
+ if (item && typeof item === 'object' && 'type' in item) {
127
+ maxDepthFound = Math.max(maxDepthFound, calculateDepth(item, currentDepth));
128
+ }
129
+ }
130
+ }
131
+ else if ('type' in child) {
132
+ maxDepthFound = Math.max(maxDepthFound, calculateDepth(child, currentDepth));
133
+ }
134
+ }
135
+ }
136
+ return maxDepthFound;
137
+ }
138
+ function hasComplexLogic(body) {
139
+ if (body.type !== 'BlockStatement') {
140
+ return false;
141
+ }
142
+ // Check for multiple statements
143
+ if (body.body.length > maxStatements) {
144
+ return true;
145
+ }
146
+ // Check for complex constructs
147
+ for (const statement of body.body) {
148
+ if (statement.type === 'IfStatement' ||
149
+ statement.type === 'ForStatement' ||
150
+ statement.type === 'WhileStatement' ||
151
+ statement.type === 'SwitchStatement' ||
152
+ statement.type === 'TryStatement') {
153
+ return true;
154
+ }
155
+ // Check for nested function declarations
156
+ if (statement.type === 'FunctionDeclaration' ||
157
+ statement.type === 'VariableDeclaration' &&
158
+ statement.declarations.some((decl) => decl.init?.type === 'FunctionExpression' ||
159
+ decl.init?.type === 'ArrowFunctionExpression')) {
160
+ return true;
161
+ }
162
+ }
163
+ return false;
164
+ }
165
+ function analyzeIIFE(node) {
166
+ let functionNode = null;
167
+ if (node.callee.type === 'FunctionExpression' ||
168
+ node.callee.type === 'ArrowFunctionExpression') {
169
+ functionNode = node.callee;
170
+ }
171
+ else if (node.callee.type === 'UnaryExpression' &&
172
+ (node.callee.argument.type === 'FunctionExpression' ||
173
+ node.callee.argument.type === 'ArrowFunctionExpression')) {
174
+ functionNode = node.callee.argument;
175
+ }
176
+ if (!functionNode) {
177
+ return;
178
+ }
179
+ const body = functionNode.body;
180
+ const statementCount = countStatements(body);
181
+ const nestingDepth = calculateDepth(body);
182
+ const hasComplex = hasComplexLogic(body);
183
+ const returnsValue = body.type === 'BlockStatement' &&
184
+ body.body.some((stmt) => stmt.type === 'ReturnStatement');
185
+ // Skip IIFEs that return values if allowed
186
+ if (allowReturningIIFE && returnsValue && !hasComplex) {
187
+ return;
188
+ }
189
+ // Check various conditions for unreadable IIFE
190
+ const issues = [];
191
+ if (statementCount > maxStatements) {
192
+ issues.push(`too many statements (${statementCount} > ${maxStatements})`);
193
+ }
194
+ if (nestingDepth > maxDepth) {
195
+ issues.push(`too deeply nested (${nestingDepth} > ${maxDepth})`);
196
+ }
197
+ if (hasComplex) {
198
+ issues.push('contains complex control flow');
199
+ }
200
+ if (functionNode.params.length > 2) {
201
+ issues.push('too many parameters');
202
+ }
203
+ if (issues.length > 0) {
204
+ context.report({
205
+ node,
206
+ messageId: 'unreadableIIFE',
207
+ data: {
208
+ issues: issues.join(', '),
209
+ statementCount,
210
+ nestingDepth,
211
+ suggestion: hasComplex ? 'extract to named function' : 'simplify or use block scope',
212
+ },
213
+ suggest: [
214
+ {
215
+ messageId: 'suggestNamedFunction',
216
+ fix(fixer) {
217
+ // Complex fix - would need to generate a function name and hoist it
218
+ return fixer.insertTextBefore(node, '// TODO: Extract complex IIFE to named function\n');
219
+ },
220
+ },
221
+ {
222
+ messageId: 'suggestBlockScope',
223
+ fix(fixer) {
224
+ return fixer.insertTextBefore(node, '// TODO: Consider using block scope { const x = ...; }\n');
225
+ },
226
+ },
227
+ ],
228
+ });
229
+ }
230
+ }
231
+ return {
232
+ CallExpression(node) {
233
+ if (isIIFE(node)) {
234
+ analyzeIIFE(node);
235
+ }
236
+ },
237
+ };
238
+ },
239
+ });
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Copyright (c) 2025 Ofri Peretz
3
+ * Licensed under the MIT License. Use of this source code is governed by the
4
+ * MIT license that can be found in the LICENSE file.
5
+ */
6
+ /**
7
+ * Simplified type barrel for eslint-plugin-maintainability.
8
+ * Many rules were moved/removed; provide placeholders to keep builds green while
9
+ * still exposing option types. Replace with concrete types as rules evolve.
10
+ */
11
+ type GenericRuleOptions = Record<string, unknown>;
12
+ export type NoExternalApiCallsInUtilsOptions = GenericRuleOptions;
13
+ export type ConsistentExistenceIndexCheckOptions = GenericRuleOptions;
14
+ export type PreferEventTargetOptions = GenericRuleOptions;
15
+ export type PreferAtOptions = GenericRuleOptions;
16
+ export type NoUnreadableIifeOptions = GenericRuleOptions;
17
+ export type NoAwaitInLoopOptions = GenericRuleOptions;
18
+ export type CognitiveComplexityOptions = GenericRuleOptions;
19
+ export type NestedComplexityHotspotsOptions = GenericRuleOptions;
20
+ export type NoDeprecatedApiOptions = GenericRuleOptions;
21
+ export type NoConsoleLogOptions = GenericRuleOptions;
22
+ export type PreferDependencyVersionStrategyOptions = GenericRuleOptions;
23
+ export type EnforceNamingOptions = GenericRuleOptions;
24
+ export type DddAnemicDomainModelOptions = GenericRuleOptions;
25
+ export type DddValueObjectImmutabilityOptions = GenericRuleOptions;
26
+ export type IdenticalFunctionsOptions = GenericRuleOptions;
27
+ export type ReactClassToHooksOptions = GenericRuleOptions;
28
+ export type ReactNoInlineFunctionsOptions = GenericRuleOptions;
29
+ export type NoBlockingOperationsOptions = GenericRuleOptions;
30
+ export type NoMemoryLeakListenersOptions = GenericRuleOptions;
31
+ export type NoUnboundedCacheOptions = GenericRuleOptions;
32
+ export type NoUnnecessaryRerendersOptions = GenericRuleOptions;
33
+ export type DetectNPlusOneQueriesOptions = GenericRuleOptions;
34
+ export type ReactRenderOptimizationOptions = GenericRuleOptions;
35
+ export type EnforceRestConventionsOptions = GenericRuleOptions;
36
+ export type RequiredAttributesOptions = GenericRuleOptions;
37
+ export type JsxKeyOptions = GenericRuleOptions;
38
+ export type NoDirectMutationStateOptions = GenericRuleOptions;
39
+ export type RequireOptimizationOptions = GenericRuleOptions;
40
+ export type NoCommentedCodeOptions = GenericRuleOptions;
41
+ export type MaxParametersOptions = GenericRuleOptions;
42
+ export type NoMissingNullChecksOptions = GenericRuleOptions;
43
+ export type NoUnsafeTypeNarrowingOptions = GenericRuleOptions;
44
+ export type NoUnhandledPromiseOptions = GenericRuleOptions;
45
+ export type NoSilentErrorsOptions = GenericRuleOptions;
46
+ export type NoMissingErrorContextOptions = GenericRuleOptions;
47
+ export type AllRulesOptions = Record<string, GenericRuleOptions>;
48
+ export {};
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2025 Ofri Peretz
4
+ * Licensed under the MIT License. Use of this source code is governed by the
5
+ * MIT license that can be found in the LICENSE file.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });