eslint-plugin-maintainability 3.0.9 → 3.0.10
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/package.json +5 -17
- package/src/index.d.ts +15 -250
- package/src/index.js +1 -17
- package/src/oxlint.d.ts +17 -20
- package/src/oxlint.js +0 -18
- package/src/rules/error-handling/error-message.js +0 -17
- package/src/rules/error-handling/no-missing-error-context.js +1 -21
- package/src/rules/error-handling/no-silent-errors.js +1 -21
- package/src/rules/error-handling/no-unhandled-promise.js +4 -100
- package/src/rules/maintainability/cognitive-complexity.js +3 -46
- package/src/rules/maintainability/consistent-function-scoping.js +0 -45
- package/src/rules/maintainability/identical-functions.js +0 -43
- package/src/rules/maintainability/max-parameters.js +2 -17
- package/src/rules/maintainability/nested-complexity-hotspots.js +1 -55
- package/src/rules/maintainability/no-lonely-if.js +0 -15
- package/src/rules/maintainability/no-nested-ternary.js +0 -27
- package/src/rules/maintainability/no-unreadable-iife.js +1 -21
- package/src/types/index.js +0 -5
- package/CHANGELOG.md +0 -192
- package/src/rules/error-handling/error-message.d.ts +0 -20
- package/src/rules/error-handling/no-missing-error-context.d.ts +0 -26
- package/src/rules/error-handling/no-silent-errors.d.ts +0 -24
- package/src/rules/error-handling/no-unhandled-promise.d.ts +0 -41
- package/src/rules/maintainability/cognitive-complexity.d.ts +0 -28
- package/src/rules/maintainability/consistent-function-scoping.d.ts +0 -20
- package/src/rules/maintainability/identical-functions.d.ts +0 -33
- package/src/rules/maintainability/max-parameters.d.ts +0 -29
- package/src/rules/maintainability/nested-complexity-hotspots.d.ts +0 -31
- package/src/rules/maintainability/no-lonely-if.d.ts +0 -19
- package/src/rules/maintainability/no-nested-ternary.d.ts +0 -19
- package/src/rules/maintainability/no-unreadable-iife.d.ts +0 -24
|
@@ -1,20 +1,10 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.identicalFunctions = void 0;
|
|
9
4
|
exports.buildGenericName = buildGenericName;
|
|
10
5
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
11
6
|
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
12
7
|
const eslint_devkit_3 = require("@interlace/eslint-devkit");
|
|
13
|
-
/**
|
|
14
|
-
* Build the generic extracted-function name suggested for a duplication group.
|
|
15
|
-
* Shared by the unified-function template and the report data so the two can
|
|
16
|
-
* never drift apart.
|
|
17
|
-
*/
|
|
18
8
|
function buildGenericName(firstFunctionName) {
|
|
19
9
|
const baseName = firstFunctionName.replace(/^(handle|process|get|set|create|update|delete)/, '');
|
|
20
10
|
return `handle${baseName || 'Generic'}`;
|
|
@@ -28,7 +18,6 @@ exports.identicalFunctions = (0, eslint_devkit_2.createRule)({
|
|
|
28
18
|
description: 'Detects duplicate function implementations with DRY refactoring suggestions',
|
|
29
19
|
},
|
|
30
20
|
messages: {
|
|
31
|
-
// 🎯 Token optimization: 43% reduction (56→32 tokens) - DRY principle violation detected
|
|
32
21
|
identicalFunctions: (0, eslint_devkit_1.formatLLMMessage)({
|
|
33
22
|
icon: eslint_devkit_1.MessageIcons.DUPLICATION,
|
|
34
23
|
issueName: 'Code duplication',
|
|
@@ -99,46 +88,27 @@ exports.identicalFunctions = (0, eslint_devkit_2.createRule)({
|
|
|
99
88
|
const { minLines = 3, similarityThreshold = 0.9, ignoreTestFiles = true, } = context.options[0] || {};
|
|
100
89
|
const sourceCode = context.sourceCode;
|
|
101
90
|
const filename = context.filename;
|
|
102
|
-
// Skip test files if configured
|
|
103
91
|
if (ignoreTestFiles && /\.(test|spec)\.[jt]sx?$/.test(filename)) {
|
|
104
92
|
return {};
|
|
105
93
|
}
|
|
106
94
|
const functions = [];
|
|
107
|
-
/**
|
|
108
|
-
* Normalize function body for comparison
|
|
109
|
-
* Remove variable names, keep structure
|
|
110
|
-
*/
|
|
111
|
-
// oxlint-disable-next-line consistent-function-scoping
|
|
112
95
|
function normalizeBody(body) {
|
|
113
96
|
return (body
|
|
114
|
-
// Remove whitespace
|
|
115
97
|
.replace(/\s+/g, ' ')
|
|
116
|
-
// Normalize string quotes
|
|
117
98
|
.replace(/["'`]/g, '"')
|
|
118
|
-
// Normalize variable names to generic identifiers
|
|
119
99
|
.replace(/\b[a-z_$][a-zA-Z0-9_$]*\b/g, 'VAR')
|
|
120
|
-
// Remove comments
|
|
121
100
|
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
122
101
|
.replace(/\/\/.*/g, '')
|
|
123
102
|
.trim());
|
|
124
103
|
}
|
|
125
|
-
/**
|
|
126
|
-
* Calculate similarity between two normalized strings
|
|
127
|
-
* Using Levenshtein distance ratio
|
|
128
|
-
*/
|
|
129
104
|
function calculateSimilarity(str1, str2) {
|
|
130
105
|
if (str1 === str2)
|
|
131
106
|
return 1.0;
|
|
132
|
-
// str1 !== str2 here, so the longer string is never empty.
|
|
133
107
|
const longer = str1.length > str2.length ? str1 : str2;
|
|
134
108
|
const shorter = str1.length > str2.length ? str2 : str1;
|
|
135
109
|
const editDistance = levenshteinDistance(longer, shorter);
|
|
136
110
|
return (longer.length - editDistance) / longer.length;
|
|
137
111
|
}
|
|
138
|
-
/**
|
|
139
|
-
* Levenshtein distance algorithm
|
|
140
|
-
*/
|
|
141
|
-
// oxlint-disable-next-line consistent-function-scoping
|
|
142
112
|
function levenshteinDistance(str1, str2) {
|
|
143
113
|
const matrix = [];
|
|
144
114
|
for (let i = 0; i <= str2.length; i++) {
|
|
@@ -159,9 +129,6 @@ exports.identicalFunctions = (0, eslint_devkit_2.createRule)({
|
|
|
159
129
|
}
|
|
160
130
|
return matrix[str2.length][str1.length];
|
|
161
131
|
}
|
|
162
|
-
/**
|
|
163
|
-
* Find groups of similar functions
|
|
164
|
-
*/
|
|
165
132
|
function findDuplicationGroups() {
|
|
166
133
|
const groups = [];
|
|
167
134
|
const processed = new Set();
|
|
@@ -196,10 +163,6 @@ exports.identicalFunctions = (0, eslint_devkit_2.createRule)({
|
|
|
196
163
|
}
|
|
197
164
|
return groups;
|
|
198
165
|
}
|
|
199
|
-
/**
|
|
200
|
-
* Suggest refactoring approach
|
|
201
|
-
*/
|
|
202
|
-
// oxlint-disable-next-line consistent-function-scoping
|
|
203
166
|
function suggestRefactoringApproach(group) {
|
|
204
167
|
const funcNames = group.functions.map((f) => f.name);
|
|
205
168
|
const hasRolePattern = funcNames.some((name) => /user|admin|guest|customer/i.test(name));
|
|
@@ -224,9 +187,6 @@ exports.identicalFunctions = (0, eslint_devkit_2.createRule)({
|
|
|
224
187
|
complexity: 'simple',
|
|
225
188
|
};
|
|
226
189
|
}
|
|
227
|
-
/**
|
|
228
|
-
* Store function information
|
|
229
|
-
*/
|
|
230
190
|
function storeFunctionInfo(node) {
|
|
231
191
|
const body = node.body ? sourceCode.getText(node.body) : '';
|
|
232
192
|
const lines = body.split('\n').length;
|
|
@@ -246,9 +206,6 @@ exports.identicalFunctions = (0, eslint_devkit_2.createRule)({
|
|
|
246
206
|
params,
|
|
247
207
|
});
|
|
248
208
|
}
|
|
249
|
-
/**
|
|
250
|
-
* Report duplications after analyzing all functions
|
|
251
|
-
*/
|
|
252
209
|
function reportDuplications() {
|
|
253
210
|
const groups = findDuplicationGroups();
|
|
254
211
|
groups.forEach((group) => {
|
|
@@ -1,17 +1,9 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.maxParameters = void 0;
|
|
9
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
10
5
|
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
11
6
|
const eslint_devkit_3 = require("@interlace/eslint-devkit");
|
|
12
|
-
/**
|
|
13
|
-
* Count function parameters
|
|
14
|
-
*/
|
|
15
7
|
function countParameters(node) {
|
|
16
8
|
return node.params.length;
|
|
17
9
|
}
|
|
@@ -87,20 +79,13 @@ exports.maxParameters = (0, eslint_devkit_2.createRule)({
|
|
|
87
79
|
},
|
|
88
80
|
],
|
|
89
81
|
create(context, [options = {}]) {
|
|
90
|
-
const { max = 4, ignoreConstructors = false,
|
|
91
|
-
// ignoreOverriddenMethods = false, // Not used
|
|
92
|
-
} = options || {};
|
|
93
|
-
/**
|
|
94
|
-
* Check function parameters
|
|
95
|
-
*/
|
|
82
|
+
const { max = 4, ignoreConstructors = false, } = options || {};
|
|
96
83
|
function checkFunction(node) {
|
|
97
|
-
// Check if it's a constructor
|
|
98
84
|
if (ignoreConstructors) {
|
|
99
85
|
if (node.type === 'FunctionDeclaration' &&
|
|
100
86
|
node.id &&
|
|
101
87
|
node.id.name &&
|
|
102
88
|
/^[A-Z]/.test(node.id.name)) {
|
|
103
|
-
// Likely a constructor
|
|
104
89
|
return;
|
|
105
90
|
}
|
|
106
91
|
}
|
|
@@ -122,7 +107,7 @@ exports.maxParameters = (0, eslint_devkit_2.createRule)({
|
|
|
122
107
|
suggest: [
|
|
123
108
|
{
|
|
124
109
|
messageId: 'useObjectParameter',
|
|
125
|
-
fix: () => null,
|
|
110
|
+
fix: () => null,
|
|
126
111
|
},
|
|
127
112
|
{
|
|
128
113
|
messageId: 'extractToClass',
|
|
@@ -1,52 +1,8 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.nestedComplexityHotspots = void 0;
|
|
9
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
10
5
|
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
11
|
-
/**
|
|
12
|
-
* Calculate nesting depth for a node
|
|
13
|
-
* Note: Currently unused, keeping for future implementation
|
|
14
|
-
*/
|
|
15
|
-
/*
|
|
16
|
-
function calculateNestingDepth(
|
|
17
|
-
node: TSESTree.Node,
|
|
18
|
-
sourceCode: TSESLint.SourceCode
|
|
19
|
-
): number {
|
|
20
|
-
let depth = 0;
|
|
21
|
-
let current: TSESTree.Node | null = node;
|
|
22
|
-
const maxDepth = 20;
|
|
23
|
-
|
|
24
|
-
// Start from the node itself and traverse up
|
|
25
|
-
while (current && depth < maxDepth) {
|
|
26
|
-
const parent = (current as any).parent;
|
|
27
|
-
|
|
28
|
-
if (!parent) break;
|
|
29
|
-
|
|
30
|
-
// Count nested control structures
|
|
31
|
-
if (
|
|
32
|
-
parent.type === 'IfStatement' ||
|
|
33
|
-
parent.type === 'ForStatement' ||
|
|
34
|
-
parent.type === 'ForInStatement' ||
|
|
35
|
-
parent.type === 'ForOfStatement' ||
|
|
36
|
-
parent.type === 'WhileStatement' ||
|
|
37
|
-
parent.type === 'DoWhileStatement' ||
|
|
38
|
-
parent.type === 'SwitchStatement' ||
|
|
39
|
-
parent.type === 'TryStatement'
|
|
40
|
-
) {
|
|
41
|
-
depth++;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
current = parent as TSESTree.Node;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return depth;
|
|
48
|
-
}
|
|
49
|
-
*/
|
|
50
6
|
exports.nestedComplexityHotspots = (0, eslint_devkit_2.createRule)({
|
|
51
7
|
name: 'nested-complexity-hotspots',
|
|
52
8
|
meta: {
|
|
@@ -123,22 +79,15 @@ exports.nestedComplexityHotspots = (0, eslint_devkit_2.createRule)({
|
|
|
123
79
|
],
|
|
124
80
|
create(context, [options = {}]) {
|
|
125
81
|
const { maxDepth = 4, countConditionals = true, countLoops = true, } = options || {};
|
|
126
|
-
// const sourceCode = context.sourceCode; // Not used
|
|
127
|
-
/**
|
|
128
|
-
* Check control structures
|
|
129
|
-
*/
|
|
130
82
|
function checkControlStructure(node) {
|
|
131
|
-
// Count how many control structures are nested above this node
|
|
132
83
|
let depth = 0;
|
|
133
84
|
let current = node;
|
|
134
85
|
const maxDepthCheck = 20;
|
|
135
|
-
// Start from the node itself and traverse up
|
|
136
86
|
while (current && depth < maxDepthCheck) {
|
|
137
87
|
const parent = current
|
|
138
88
|
.parent;
|
|
139
89
|
if (!parent)
|
|
140
90
|
break;
|
|
141
|
-
// Count nested control structures above this node
|
|
142
91
|
if (parent.type === 'IfStatement' ||
|
|
143
92
|
parent.type === 'ForStatement' ||
|
|
144
93
|
parent.type === 'ForInStatement' ||
|
|
@@ -151,15 +100,12 @@ exports.nestedComplexityHotspots = (0, eslint_devkit_2.createRule)({
|
|
|
151
100
|
}
|
|
152
101
|
current = parent;
|
|
153
102
|
}
|
|
154
|
-
// depth now represents how many control structures are nested above this node
|
|
155
|
-
// For 5 nested ifs, the innermost if will have depth = 4 (4 ifs above it)
|
|
156
|
-
// So we check if depth >= maxDepth (not >)
|
|
157
103
|
if (depth >= maxDepth) {
|
|
158
104
|
context.report({
|
|
159
105
|
node,
|
|
160
106
|
messageId: 'nestedComplexity',
|
|
161
107
|
data: {
|
|
162
|
-
depth: String(depth + 1),
|
|
108
|
+
depth: String(depth + 1),
|
|
163
109
|
max: String(maxDepth),
|
|
164
110
|
},
|
|
165
111
|
suggest: [
|
|
@@ -1,9 +1,4 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.noLonelyIf = void 0;
|
|
9
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
@@ -47,10 +42,7 @@ exports.noLonelyIf = (0, eslint_devkit_1.createRule)({
|
|
|
47
42
|
const { allow = [] } = options || {};
|
|
48
43
|
const allowedContexts = new Set(allow);
|
|
49
44
|
function isInAllowedContext() {
|
|
50
|
-
// Check if we're in an allowed context
|
|
51
|
-
// This is a simple implementation - could be extended for more complex cases
|
|
52
45
|
for (const allowedContext of allowedContexts) {
|
|
53
|
-
// For now, just check if the context string appears anywhere in the source
|
|
54
46
|
const sourceCode = context.sourceCode;
|
|
55
47
|
const sourceText = sourceCode.getText();
|
|
56
48
|
if (sourceText.includes(allowedContext)) {
|
|
@@ -59,18 +51,13 @@ exports.noLonelyIf = (0, eslint_devkit_1.createRule)({
|
|
|
59
51
|
}
|
|
60
52
|
return false;
|
|
61
53
|
}
|
|
62
|
-
// oxlint-disable-next-line consistent-function-scoping
|
|
63
54
|
function isLonelyIf(node) {
|
|
64
|
-
// Check if this if statement is inside an else block (not a proper else if)
|
|
65
55
|
const parent = node.parent;
|
|
66
|
-
// If parent is a BlockStatement, check if that block is an else block
|
|
67
56
|
if (parent?.type === 'BlockStatement') {
|
|
68
57
|
const grandParent = parent.parent;
|
|
69
|
-
// Check if the block is the alternate (else) of an if statement
|
|
70
58
|
return (grandParent?.type === 'IfStatement' &&
|
|
71
59
|
grandParent.alternate === parent);
|
|
72
60
|
}
|
|
73
|
-
// If not in a block, it's not a lonely if (it's either a proper else if or top-level)
|
|
74
61
|
return false;
|
|
75
62
|
}
|
|
76
63
|
return {
|
|
@@ -88,7 +75,6 @@ exports.noLonelyIf = (0, eslint_devkit_1.createRule)({
|
|
|
88
75
|
messageId: 'noLonelyIf',
|
|
89
76
|
fix(fixer) {
|
|
90
77
|
const sourceCode = context.sourceCode;
|
|
91
|
-
// Find the else keyword
|
|
92
78
|
let elseToken = null;
|
|
93
79
|
const tokens = sourceCode.getTokensBefore(node, 10);
|
|
94
80
|
for (let i = tokens.length - 1; i >= 0; i--) {
|
|
@@ -98,7 +84,6 @@ exports.noLonelyIf = (0, eslint_devkit_1.createRule)({
|
|
|
98
84
|
}
|
|
99
85
|
}
|
|
100
86
|
if (elseToken) {
|
|
101
|
-
// Remove 'else' and replace 'if' with 'else if'
|
|
102
87
|
const ifToken = sourceCode.getTokenAfter(elseToken);
|
|
103
88
|
if (ifToken && ifToken.value === 'if') {
|
|
104
89
|
return [
|
|
@@ -1,9 +1,4 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.noNestedTernary = void 0;
|
|
9
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
@@ -45,37 +40,25 @@ exports.noNestedTernary = (0, eslint_devkit_1.createRule)({
|
|
|
45
40
|
create(context) {
|
|
46
41
|
const [options] = context.options;
|
|
47
42
|
const { allow = [] } = options || {};
|
|
48
|
-
/**
|
|
49
|
-
* Check if node is in an allowed context based on the allow option.
|
|
50
|
-
* Supported contexts:
|
|
51
|
-
* - 'jsx': Allow nested ternaries in JSX expressions
|
|
52
|
-
* - 'variable': Allow nested ternaries in variable declarations
|
|
53
|
-
* - 'return': Allow nested ternaries in return statements
|
|
54
|
-
* - 'argument': Allow nested ternaries in function arguments
|
|
55
|
-
*/
|
|
56
43
|
function isInAllowedContext(node) {
|
|
57
44
|
if (allow.length === 0) {
|
|
58
45
|
return false;
|
|
59
46
|
}
|
|
60
47
|
let current = node.parent;
|
|
61
48
|
while (current) {
|
|
62
|
-
// Check for JSX context
|
|
63
49
|
if (allow.includes('jsx') &&
|
|
64
50
|
(current.type === 'JSXExpressionContainer' ||
|
|
65
51
|
current.type === 'JSXElement' ||
|
|
66
52
|
current.type === 'JSXFragment')) {
|
|
67
53
|
return true;
|
|
68
54
|
}
|
|
69
|
-
// Check for variable declaration context
|
|
70
55
|
if (allow.includes('variable') &&
|
|
71
56
|
current.type === 'VariableDeclarator') {
|
|
72
57
|
return true;
|
|
73
58
|
}
|
|
74
|
-
// Check for return statement context
|
|
75
59
|
if (allow.includes('return') && current.type === 'ReturnStatement') {
|
|
76
60
|
return true;
|
|
77
61
|
}
|
|
78
|
-
// Check for function argument context
|
|
79
62
|
if (allow.includes('argument') && current.type === 'CallExpression') {
|
|
80
63
|
return true;
|
|
81
64
|
}
|
|
@@ -84,12 +67,10 @@ exports.noNestedTernary = (0, eslint_devkit_1.createRule)({
|
|
|
84
67
|
return false;
|
|
85
68
|
}
|
|
86
69
|
function hasNestedTernary(node) {
|
|
87
|
-
// Check if the consequent or alternate contains another ternary
|
|
88
70
|
function containsTernary(expr) {
|
|
89
71
|
if (expr.type === 'ConditionalExpression') {
|
|
90
72
|
return true;
|
|
91
73
|
}
|
|
92
|
-
// For other expression types, check their child expressions
|
|
93
74
|
switch (expr.type) {
|
|
94
75
|
case 'ArrayExpression':
|
|
95
76
|
return expr.elements.some((element) => element &&
|
|
@@ -115,21 +96,17 @@ exports.noNestedTernary = (0, eslint_devkit_1.createRule)({
|
|
|
115
96
|
return containsTernary(expr.argument);
|
|
116
97
|
case 'AssignmentExpression':
|
|
117
98
|
return containsTernary(expr.right);
|
|
118
|
-
// For literals and identifiers, no nested expressions
|
|
119
99
|
case 'Literal':
|
|
120
100
|
case 'Identifier':
|
|
121
101
|
case 'ThisExpression':
|
|
122
102
|
case 'Super':
|
|
123
103
|
case 'MetaProperty':
|
|
124
104
|
return false;
|
|
125
|
-
// For template literals, check expressions
|
|
126
105
|
case 'TemplateLiteral':
|
|
127
106
|
return expr.expressions.some((exp) => containsTernary(exp));
|
|
128
|
-
// For tagged templates, check tag and expressions
|
|
129
107
|
case 'TaggedTemplateExpression':
|
|
130
108
|
return (containsTernary(expr.tag) ||
|
|
131
109
|
expr.quasi.expressions.some((exp) => containsTernary(exp)));
|
|
132
|
-
// Default: assume no nested expressions for unknown types
|
|
133
110
|
default:
|
|
134
111
|
return false;
|
|
135
112
|
}
|
|
@@ -150,10 +127,6 @@ exports.noNestedTernary = (0, eslint_devkit_1.createRule)({
|
|
|
150
127
|
{
|
|
151
128
|
messageId: 'noNestedTernary',
|
|
152
129
|
fix() {
|
|
153
|
-
// This is a complex fix that would require:
|
|
154
|
-
// 1. Extracting the nested ternary to a variable
|
|
155
|
-
// 2. Replacing the nested part
|
|
156
|
-
// For now, just provide the suggestion
|
|
157
130
|
return null;
|
|
158
131
|
},
|
|
159
132
|
},
|
|
@@ -1,9 +1,4 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.noUnreadableIife = void 0;
|
|
9
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
@@ -78,16 +73,11 @@ exports.noUnreadableIife = (0, eslint_devkit_1.createRule)({
|
|
|
78
73
|
create(context) {
|
|
79
74
|
const [options] = context.options;
|
|
80
75
|
const { maxStatements = 3, maxDepth = 2, allowReturningIIFE = true } = options || {};
|
|
81
|
-
// oxlint-disable-next-line consistent-function-scoping
|
|
82
76
|
function getIifeFunctionNode(node) {
|
|
83
|
-
// Check if this is a call expression with a function expression or arrow function
|
|
84
|
-
// Note: In typescript-eslint AST, parentheses don't create a separate node type,
|
|
85
|
-
// so (function(){})() will have callee.type === 'FunctionExpression' directly
|
|
86
77
|
if (node.callee.type === 'FunctionExpression' ||
|
|
87
78
|
node.callee.type === 'ArrowFunctionExpression') {
|
|
88
79
|
return node.callee;
|
|
89
80
|
}
|
|
90
|
-
// Check for unary operator IIFEs like (!function(){})(), (+function(){})(), (void function(){})()
|
|
91
81
|
if (node.callee.type === 'UnaryExpression' &&
|
|
92
82
|
(node.callee.argument.type === 'FunctionExpression' ||
|
|
93
83
|
node.callee.argument.type === 'ArrowFunctionExpression')) {
|
|
@@ -95,22 +85,18 @@ exports.noUnreadableIife = (0, eslint_devkit_1.createRule)({
|
|
|
95
85
|
}
|
|
96
86
|
return null;
|
|
97
87
|
}
|
|
98
|
-
// oxlint-disable-next-line consistent-function-scoping
|
|
99
88
|
function countStatements(body) {
|
|
100
89
|
if (body.type === 'BlockStatement') {
|
|
101
90
|
return body.body.length;
|
|
102
91
|
}
|
|
103
|
-
return 1;
|
|
92
|
+
return 1;
|
|
104
93
|
}
|
|
105
94
|
function calculateDepth(node) {
|
|
106
|
-
// AST-based nesting depth calculation — no regex needed
|
|
107
95
|
const CONTROL_FLOW_TYPES = new Set([
|
|
108
96
|
'IfStatement', 'ForStatement', 'ForInStatement', 'ForOfStatement',
|
|
109
97
|
'WhileStatement', 'DoWhileStatement', 'SwitchStatement', 'TryStatement',
|
|
110
98
|
]);
|
|
111
|
-
// Non-AST keys that should never be traversed
|
|
112
99
|
const SKIP_KEYS = new Set(['parent', 'loc', 'range', 'tokens', 'comments', 'leadingComments', 'trailingComments']);
|
|
113
|
-
// oxlint-disable-next-line consistent-function-scoping
|
|
114
100
|
function isASTNode(value) {
|
|
115
101
|
return !!value && typeof value === 'object' && 'type' in value && 'loc' in value;
|
|
116
102
|
}
|
|
@@ -138,11 +124,9 @@ exports.noUnreadableIife = (0, eslint_devkit_1.createRule)({
|
|
|
138
124
|
if (body.type !== 'BlockStatement') {
|
|
139
125
|
return false;
|
|
140
126
|
}
|
|
141
|
-
// Check for multiple statements
|
|
142
127
|
if (body.body.length > maxStatements) {
|
|
143
128
|
return true;
|
|
144
129
|
}
|
|
145
|
-
// Check for complex constructs
|
|
146
130
|
for (const statement of body.body) {
|
|
147
131
|
if (statement.type === 'IfStatement' ||
|
|
148
132
|
statement.type === 'ForStatement' ||
|
|
@@ -151,7 +135,6 @@ exports.noUnreadableIife = (0, eslint_devkit_1.createRule)({
|
|
|
151
135
|
statement.type === 'TryStatement') {
|
|
152
136
|
return true;
|
|
153
137
|
}
|
|
154
|
-
// Check for nested function declarations
|
|
155
138
|
if (statement.type === 'FunctionDeclaration' ||
|
|
156
139
|
statement.type === 'VariableDeclaration' &&
|
|
157
140
|
statement.declarations.some((decl) => decl.init?.type === 'FunctionExpression' ||
|
|
@@ -168,11 +151,9 @@ exports.noUnreadableIife = (0, eslint_devkit_1.createRule)({
|
|
|
168
151
|
const hasComplex = hasComplexLogic(body);
|
|
169
152
|
const returnsValue = body.type === 'BlockStatement' &&
|
|
170
153
|
body.body.some((stmt) => stmt.type === 'ReturnStatement');
|
|
171
|
-
// Skip IIFEs that return values if allowed
|
|
172
154
|
if (allowReturningIIFE && returnsValue && !hasComplex) {
|
|
173
155
|
return;
|
|
174
156
|
}
|
|
175
|
-
// Check various conditions for unreadable IIFE
|
|
176
157
|
const issues = [];
|
|
177
158
|
if (statementCount > maxStatements) {
|
|
178
159
|
issues.push(`too many statements (${statementCount} > ${maxStatements})`);
|
|
@@ -200,7 +181,6 @@ exports.noUnreadableIife = (0, eslint_devkit_1.createRule)({
|
|
|
200
181
|
{
|
|
201
182
|
messageId: 'suggestNamedFunction',
|
|
202
183
|
fix(fixer) {
|
|
203
|
-
// Complex fix - would need to generate a function name and hoist it
|
|
204
184
|
return fixer.insertTextBefore(node, '// TODO: Extract complex IIFE to named function\n');
|
|
205
185
|
},
|
|
206
186
|
},
|
package/src/types/index.js
CHANGED