eslint-plugin-maintainability 3.0.13 → 3.0.15
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/README.md +1 -1
- package/package.json +3 -3
- package/src/index.js +1 -50
- package/src/oxlint.js +1 -3
- package/src/rules/error-handling/error-message.js +1 -131
- package/src/rules/error-handling/no-missing-error-context.js +1 -164
- package/src/rules/error-handling/no-silent-errors.js +1 -157
- package/src/rules/error-handling/no-unhandled-promise.js +1 -261
- package/src/rules/maintainability/cognitive-complexity.js +1 -341
- package/src/rules/maintainability/consistent-function-scoping.js +1 -229
- package/src/rules/maintainability/identical-functions.js +1 -259
- package/src/rules/maintainability/max-parameters.js +1 -129
- package/src/rules/maintainability/nested-complexity-hotspots.js +1 -137
- package/src/rules/maintainability/no-lonely-if.js +1 -107
- package/src/rules/maintainability/no-nested-ternary.js +1 -139
- package/src/rules/maintainability/no-unreadable-iife.js +1 -206
- package/src/types/index.js +1 -2
|
@@ -1,137 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.nestedComplexityHotspots = void 0;
|
|
4
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
5
|
-
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
6
|
-
exports.nestedComplexityHotspots = (0, eslint_devkit_2.createRule)({
|
|
7
|
-
name: 'nested-complexity-hotspots',
|
|
8
|
-
meta: {
|
|
9
|
-
type: 'suggestion',
|
|
10
|
-
docs: {
|
|
11
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/nested-complexity-hotspots.md',
|
|
12
|
-
description: 'Identifies nested control structures that harm readability',
|
|
13
|
-
},
|
|
14
|
-
messages: {
|
|
15
|
-
nestedComplexity: (0, eslint_devkit_1.formatLLMMessage)({
|
|
16
|
-
icon: eslint_devkit_1.MessageIcons.COMPLEXITY,
|
|
17
|
-
issueName: 'Nested complexity hotspot',
|
|
18
|
-
description: 'Nesting depth {{depth}} exceeds maximum {{max}}',
|
|
19
|
-
severity: 'MEDIUM',
|
|
20
|
-
fix: 'Use early returns, guard clauses, or extract methods',
|
|
21
|
-
documentationLink: 'https://en.wikipedia.org/wiki/Cyclomatic_complexity',
|
|
22
|
-
}),
|
|
23
|
-
useEarlyReturn: (0, eslint_devkit_1.formatLLMMessage)({
|
|
24
|
-
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
25
|
-
issueName: 'Use Early Return',
|
|
26
|
-
description: 'Use early return to reduce nesting',
|
|
27
|
-
severity: 'LOW',
|
|
28
|
-
fix: 'if (!condition) return; // Continue with main logic',
|
|
29
|
-
documentationLink: 'https://refactoring.guru/smells/long-method',
|
|
30
|
-
}),
|
|
31
|
-
useGuardClauses: (0, eslint_devkit_1.formatLLMMessage)({
|
|
32
|
-
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
33
|
-
issueName: 'Use Guard Clauses',
|
|
34
|
-
description: 'Use guard clauses for validation',
|
|
35
|
-
severity: 'LOW',
|
|
36
|
-
fix: 'if (!isValid(input)) throw new Error()',
|
|
37
|
-
documentationLink: 'https://refactoring.guru/replace-nested-conditional-with-guard-clauses',
|
|
38
|
-
}),
|
|
39
|
-
extractMethod: (0, eslint_devkit_1.formatLLMMessage)({
|
|
40
|
-
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
41
|
-
issueName: 'Extract Method',
|
|
42
|
-
description: 'Extract nested logic to separate method',
|
|
43
|
-
severity: 'LOW',
|
|
44
|
-
fix: 'Extract to private method',
|
|
45
|
-
documentationLink: 'https://refactoring.guru/extract-method',
|
|
46
|
-
}),
|
|
47
|
-
},
|
|
48
|
-
schema: [
|
|
49
|
-
{
|
|
50
|
-
type: 'object',
|
|
51
|
-
properties: {
|
|
52
|
-
maxDepth: {
|
|
53
|
-
type: 'number',
|
|
54
|
-
default: 4,
|
|
55
|
-
minimum: 1,
|
|
56
|
-
description: 'Maximum nesting depth',
|
|
57
|
-
},
|
|
58
|
-
countConditionals: {
|
|
59
|
-
type: 'boolean',
|
|
60
|
-
default: true,
|
|
61
|
-
description: 'Count nested conditionals',
|
|
62
|
-
},
|
|
63
|
-
countLoops: {
|
|
64
|
-
type: 'boolean',
|
|
65
|
-
default: true,
|
|
66
|
-
description: 'Count nested loops',
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
additionalProperties: false,
|
|
70
|
-
},
|
|
71
|
-
],
|
|
72
|
-
},
|
|
73
|
-
defaultOptions: [
|
|
74
|
-
{
|
|
75
|
-
maxDepth: 4,
|
|
76
|
-
countConditionals: true,
|
|
77
|
-
countLoops: true,
|
|
78
|
-
},
|
|
79
|
-
],
|
|
80
|
-
create(context, [options = {}]) {
|
|
81
|
-
const { maxDepth = 4, countConditionals = true, countLoops = true, } = options || {};
|
|
82
|
-
function checkControlStructure(node) {
|
|
83
|
-
let depth = 0;
|
|
84
|
-
let current = node;
|
|
85
|
-
const maxDepthCheck = 20;
|
|
86
|
-
while (current && depth < maxDepthCheck) {
|
|
87
|
-
const parent = current
|
|
88
|
-
.parent;
|
|
89
|
-
if (!parent)
|
|
90
|
-
break;
|
|
91
|
-
if (parent.type === 'IfStatement' ||
|
|
92
|
-
parent.type === 'ForStatement' ||
|
|
93
|
-
parent.type === 'ForInStatement' ||
|
|
94
|
-
parent.type === 'ForOfStatement' ||
|
|
95
|
-
parent.type === 'WhileStatement' ||
|
|
96
|
-
parent.type === 'DoWhileStatement' ||
|
|
97
|
-
parent.type === 'SwitchStatement' ||
|
|
98
|
-
parent.type === 'TryStatement') {
|
|
99
|
-
depth++;
|
|
100
|
-
}
|
|
101
|
-
current = parent;
|
|
102
|
-
}
|
|
103
|
-
if (depth >= maxDepth) {
|
|
104
|
-
context.report({
|
|
105
|
-
node,
|
|
106
|
-
messageId: 'nestedComplexity',
|
|
107
|
-
data: {
|
|
108
|
-
depth: String(depth + 1),
|
|
109
|
-
max: String(maxDepth),
|
|
110
|
-
},
|
|
111
|
-
suggest: [
|
|
112
|
-
{
|
|
113
|
-
messageId: 'useEarlyReturn',
|
|
114
|
-
fix: () => null,
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
messageId: 'useGuardClauses',
|
|
118
|
-
fix: () => null,
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
messageId: 'extractMethod',
|
|
122
|
-
fix: () => null,
|
|
123
|
-
},
|
|
124
|
-
],
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return {
|
|
129
|
-
IfStatement: countConditionals ? checkControlStructure : undefined,
|
|
130
|
-
ForStatement: countLoops ? checkControlStructure : undefined,
|
|
131
|
-
ForInStatement: countLoops ? checkControlStructure : undefined,
|
|
132
|
-
ForOfStatement: countLoops ? checkControlStructure : undefined,
|
|
133
|
-
WhileStatement: countLoops ? checkControlStructure : undefined,
|
|
134
|
-
SwitchStatement: countConditionals ? checkControlStructure : undefined,
|
|
135
|
-
};
|
|
136
|
-
},
|
|
137
|
-
});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.nestedComplexityHotspots=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");exports.nestedComplexityHotspots=(0,eslint_devkit_2.createRule)({name:"nested-complexity-hotspots",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/nested-complexity-hotspots.md",description:"Identifies nested control structures that harm readability"},hasSuggestions:true,messages:{nestedComplexity:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.COMPLEXITY,issueName:"Nested complexity hotspot",description:"Nesting depth {{depth}} exceeds maximum {{max}}",severity:"MEDIUM",fix:"Use early returns, guard clauses, or extract methods",documentationLink:"https://en.wikipedia.org/wiki/Cyclomatic_complexity"}),useEarlyReturn:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Early Return",description:"Use early return to reduce nesting",severity:"LOW",fix:"if (!condition) return; // Continue with main logic",documentationLink:"https://refactoring.guru/smells/long-method"}),useGuardClauses:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Guard Clauses",description:"Use guard clauses for validation",severity:"LOW",fix:"if (!isValid(input)) throw new Error()",documentationLink:"https://refactoring.guru/replace-nested-conditional-with-guard-clauses"}),extractMethod:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Extract Method",description:"Extract nested logic to separate method",severity:"LOW",fix:"Extract to private method",documentationLink:"https://refactoring.guru/extract-method"})},schema:[{type:"object",properties:{maxDepth:{type:"number",default:4,minimum:1,description:"Maximum nesting depth"},countConditionals:{type:"boolean",default:true,description:"Count nested conditionals"},countLoops:{type:"boolean",default:true,description:"Count nested loops"}},additionalProperties:false}]},defaultOptions:[{maxDepth:4,countConditionals:true,countLoops:true}],create(context,[options={}]){const{maxDepth=4,countConditionals=true,countLoops=true}=options||{};function checkControlStructure(node){let depth=0;let current=node;const maxDepthCheck=20;while(current&&depth<maxDepthCheck){const parent=current.parent;if(!parent)break;if(parent.type==="IfStatement"||parent.type==="ForStatement"||parent.type==="ForInStatement"||parent.type==="ForOfStatement"||parent.type==="WhileStatement"||parent.type==="DoWhileStatement"||parent.type==="SwitchStatement"||parent.type==="TryStatement"){depth++}current=parent}if(depth>=maxDepth){context.report({node,messageId:"nestedComplexity",data:{depth:String(depth+1),max:String(maxDepth)},suggest:[{messageId:"useEarlyReturn",fix:()=>null},{messageId:"useGuardClauses",fix:()=>null},{messageId:"extractMethod",fix:()=>null}]})}}return{IfStatement:countConditionals?checkControlStructure:void 0,ForStatement:countLoops?checkControlStructure:void 0,ForInStatement:countLoops?checkControlStructure:void 0,ForOfStatement:countLoops?checkControlStructure:void 0,WhileStatement:countLoops?checkControlStructure:void 0,SwitchStatement:countConditionals?checkControlStructure:void 0}}});
|
|
@@ -1,107 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.noLonelyIf = void 0;
|
|
4
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
5
|
-
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
6
|
-
exports.noLonelyIf = (0, eslint_devkit_1.createRule)({
|
|
7
|
-
name: 'no-lonely-if',
|
|
8
|
-
meta: {
|
|
9
|
-
type: 'suggestion',
|
|
10
|
-
docs: {
|
|
11
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/no-lonely-if.md',
|
|
12
|
-
description: 'Prevent lone if statements inside else blocks - use else if instead',
|
|
13
|
-
},
|
|
14
|
-
hasSuggestions: true,
|
|
15
|
-
messages: {
|
|
16
|
-
noLonelyIf: (0, eslint_devkit_2.formatLLMMessage)({
|
|
17
|
-
icon: eslint_devkit_2.MessageIcons.WARNING,
|
|
18
|
-
issueName: 'Lonely If',
|
|
19
|
-
description: 'Unexpected if statement inside else block',
|
|
20
|
-
severity: 'MEDIUM',
|
|
21
|
-
fix: 'Replace with else if for better readability',
|
|
22
|
-
documentationLink: 'https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-lonely-if.md',
|
|
23
|
-
}),
|
|
24
|
-
},
|
|
25
|
-
schema: [
|
|
26
|
-
{
|
|
27
|
-
type: 'object',
|
|
28
|
-
properties: {
|
|
29
|
-
allow: {
|
|
30
|
-
type: 'array',
|
|
31
|
-
items: { type: 'string' },
|
|
32
|
-
default: [],
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
additionalProperties: false,
|
|
36
|
-
},
|
|
37
|
-
],
|
|
38
|
-
},
|
|
39
|
-
defaultOptions: [{ allow: [] }],
|
|
40
|
-
create(context) {
|
|
41
|
-
const [options] = context.options;
|
|
42
|
-
const { allow = [] } = options || {};
|
|
43
|
-
const allowedContexts = new Set(allow);
|
|
44
|
-
function isInAllowedContext() {
|
|
45
|
-
for (const allowedContext of allowedContexts) {
|
|
46
|
-
const sourceCode = context.sourceCode;
|
|
47
|
-
const sourceText = sourceCode.getText();
|
|
48
|
-
if (sourceText.includes(allowedContext)) {
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
function isLonelyIf(node) {
|
|
55
|
-
const parent = node.parent;
|
|
56
|
-
if (parent?.type === 'BlockStatement') {
|
|
57
|
-
const grandParent = parent.parent;
|
|
58
|
-
return (grandParent?.type === 'IfStatement' &&
|
|
59
|
-
grandParent.alternate === parent);
|
|
60
|
-
}
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
return {
|
|
64
|
-
IfStatement(node) {
|
|
65
|
-
if (isLonelyIf(node) && !isInAllowedContext()) {
|
|
66
|
-
context.report({
|
|
67
|
-
node,
|
|
68
|
-
messageId: 'noLonelyIf',
|
|
69
|
-
data: {
|
|
70
|
-
current: 'if statement in else block',
|
|
71
|
-
fix: 'else if',
|
|
72
|
-
},
|
|
73
|
-
suggest: [
|
|
74
|
-
{
|
|
75
|
-
messageId: 'noLonelyIf',
|
|
76
|
-
fix(fixer) {
|
|
77
|
-
const sourceCode = context.sourceCode;
|
|
78
|
-
let elseToken = null;
|
|
79
|
-
const tokens = sourceCode.getTokensBefore(node, 10);
|
|
80
|
-
for (let i = tokens.length - 1; i >= 0; i--) {
|
|
81
|
-
if (tokens[i].value === 'else') {
|
|
82
|
-
elseToken = tokens[i];
|
|
83
|
-
break;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
if (elseToken) {
|
|
87
|
-
const ifToken = sourceCode.getTokenAfter(elseToken);
|
|
88
|
-
if (ifToken && ifToken.value === 'if') {
|
|
89
|
-
return [
|
|
90
|
-
fixer.removeRange([
|
|
91
|
-
elseToken.range[0],
|
|
92
|
-
ifToken.range[1],
|
|
93
|
-
]),
|
|
94
|
-
fixer.insertTextBefore(ifToken, 'else '),
|
|
95
|
-
];
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return null;
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
],
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
},
|
|
107
|
-
});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noLonelyIf=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");exports.noLonelyIf=(0,eslint_devkit_1.createRule)({name:"no-lonely-if",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/no-lonely-if.md",description:"Prevent lone if statements inside else blocks - use else if instead"},hasSuggestions:true,messages:{noLonelyIf:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.WARNING,issueName:"Lonely If",description:"Unexpected if statement inside else block",severity:"MEDIUM",fix:"Replace with else if for better readability",documentationLink:"https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-lonely-if.md"})},schema:[{type:"object",properties:{allow:{type:"array",items:{type:"string"},default:[]}},additionalProperties:false}]},defaultOptions:[{allow:[]}],create(context){const[options]=context.options;const{allow=[]}=options||{};const allowedContexts=new Set(allow);function isInAllowedContext(){for(const allowedContext of allowedContexts){const sourceCode=context.sourceCode;const sourceText=sourceCode.getText();if(sourceText.includes(allowedContext)){return true}}return false}function isLonelyIf(node){const parent=node.parent;if(parent?.type==="BlockStatement"){const grandParent=parent.parent;return grandParent?.type==="IfStatement"&&grandParent.alternate===parent}return false}return{IfStatement(node){if(isLonelyIf(node)&&!isInAllowedContext()){context.report({node,messageId:"noLonelyIf",data:{current:"if statement in else block",fix:"else if"},suggest:[{messageId:"noLonelyIf",fix(fixer){const sourceCode=context.sourceCode;let elseToken=null;const tokens=sourceCode.getTokensBefore(node,10);for(let i=tokens.length-1;i>=0;i--){if(tokens[i].value==="else"){elseToken=tokens[i];break}}if(elseToken){const ifToken=sourceCode.getTokenAfter(elseToken);if(ifToken&&ifToken.value==="if"){return[fixer.removeRange([elseToken.range[0],ifToken.range[1]]),fixer.insertTextBefore(ifToken,"else ")]}}return null}}]})}}}}});
|
|
@@ -1,139 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.noNestedTernary = void 0;
|
|
4
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
5
|
-
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
6
|
-
exports.noNestedTernary = (0, eslint_devkit_1.createRule)({
|
|
7
|
-
name: 'no-nested-ternary',
|
|
8
|
-
meta: {
|
|
9
|
-
type: 'suggestion',
|
|
10
|
-
docs: {
|
|
11
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/no-nested-ternary.md',
|
|
12
|
-
description: 'Prevent nested ternary expressions for better readability',
|
|
13
|
-
},
|
|
14
|
-
hasSuggestions: true,
|
|
15
|
-
messages: {
|
|
16
|
-
noNestedTernary: (0, eslint_devkit_2.formatLLMMessage)({
|
|
17
|
-
icon: eslint_devkit_2.MessageIcons.WARNING,
|
|
18
|
-
issueName: 'Nested Ternary',
|
|
19
|
-
description: 'Avoid nested ternary expressions',
|
|
20
|
-
severity: 'MEDIUM',
|
|
21
|
-
fix: 'Extract to helper variable or use if-else',
|
|
22
|
-
documentationLink: 'https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-nested-ternary.md',
|
|
23
|
-
}),
|
|
24
|
-
},
|
|
25
|
-
schema: [
|
|
26
|
-
{
|
|
27
|
-
type: 'object',
|
|
28
|
-
properties: {
|
|
29
|
-
allow: {
|
|
30
|
-
type: 'array',
|
|
31
|
-
items: { type: 'string' },
|
|
32
|
-
default: [],
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
additionalProperties: false,
|
|
36
|
-
},
|
|
37
|
-
],
|
|
38
|
-
},
|
|
39
|
-
defaultOptions: [{ allow: [] }],
|
|
40
|
-
create(context) {
|
|
41
|
-
const [options] = context.options;
|
|
42
|
-
const { allow = [] } = options || {};
|
|
43
|
-
function isInAllowedContext(node) {
|
|
44
|
-
if (allow.length === 0) {
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
let current = node.parent;
|
|
48
|
-
while (current) {
|
|
49
|
-
if (allow.includes('jsx') &&
|
|
50
|
-
(current.type === 'JSXExpressionContainer' ||
|
|
51
|
-
current.type === 'JSXElement' ||
|
|
52
|
-
current.type === 'JSXFragment')) {
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
if (allow.includes('variable') &&
|
|
56
|
-
current.type === 'VariableDeclarator') {
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
|
-
if (allow.includes('return') && current.type === 'ReturnStatement') {
|
|
60
|
-
return true;
|
|
61
|
-
}
|
|
62
|
-
if (allow.includes('argument') && current.type === 'CallExpression') {
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
current = current.parent;
|
|
66
|
-
}
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
function hasNestedTernary(node) {
|
|
70
|
-
function containsTernary(expr) {
|
|
71
|
-
if (expr.type === 'ConditionalExpression') {
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
switch (expr.type) {
|
|
75
|
-
case 'ArrayExpression':
|
|
76
|
-
return expr.elements.some((element) => element &&
|
|
77
|
-
element.type !== 'SpreadElement' &&
|
|
78
|
-
containsTernary(element));
|
|
79
|
-
case 'ObjectExpression':
|
|
80
|
-
return expr.properties.some((prop) => prop.type === 'Property' &&
|
|
81
|
-
prop.value &&
|
|
82
|
-
containsTernary(prop.value));
|
|
83
|
-
case 'CallExpression':
|
|
84
|
-
return (expr.arguments.some((arg) => arg.type !== 'SpreadElement' && containsTernary(arg)) ||
|
|
85
|
-
(expr.callee.type !== 'Super' && containsTernary(expr.callee)));
|
|
86
|
-
case 'MemberExpression':
|
|
87
|
-
return (containsTernary(expr.object) ||
|
|
88
|
-
(expr.property.type !== 'Identifier' &&
|
|
89
|
-
expr.property.type !== 'PrivateIdentifier' &&
|
|
90
|
-
containsTernary(expr.property)));
|
|
91
|
-
case 'BinaryExpression':
|
|
92
|
-
case 'LogicalExpression':
|
|
93
|
-
return containsTernary(expr.left) || containsTernary(expr.right);
|
|
94
|
-
case 'UnaryExpression':
|
|
95
|
-
case 'UpdateExpression':
|
|
96
|
-
return containsTernary(expr.argument);
|
|
97
|
-
case 'AssignmentExpression':
|
|
98
|
-
return containsTernary(expr.right);
|
|
99
|
-
case 'Literal':
|
|
100
|
-
case 'Identifier':
|
|
101
|
-
case 'ThisExpression':
|
|
102
|
-
case 'Super':
|
|
103
|
-
case 'MetaProperty':
|
|
104
|
-
return false;
|
|
105
|
-
case 'TemplateLiteral':
|
|
106
|
-
return expr.expressions.some((exp) => containsTernary(exp));
|
|
107
|
-
case 'TaggedTemplateExpression':
|
|
108
|
-
return (containsTernary(expr.tag) ||
|
|
109
|
-
expr.quasi.expressions.some((exp) => containsTernary(exp)));
|
|
110
|
-
default:
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return (containsTernary(node.consequent) || containsTernary(node.alternate));
|
|
115
|
-
}
|
|
116
|
-
return {
|
|
117
|
-
ConditionalExpression(node) {
|
|
118
|
-
if (hasNestedTernary(node) && !isInAllowedContext(node)) {
|
|
119
|
-
context.report({
|
|
120
|
-
node,
|
|
121
|
-
messageId: 'noNestedTernary',
|
|
122
|
-
data: {
|
|
123
|
-
current: 'nested ternary expression',
|
|
124
|
-
fix: 'extract or use if-else',
|
|
125
|
-
},
|
|
126
|
-
suggest: [
|
|
127
|
-
{
|
|
128
|
-
messageId: 'noNestedTernary',
|
|
129
|
-
fix() {
|
|
130
|
-
return null;
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
],
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
},
|
|
137
|
-
};
|
|
138
|
-
},
|
|
139
|
-
});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noNestedTernary=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");exports.noNestedTernary=(0,eslint_devkit_1.createRule)({name:"no-nested-ternary",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/no-nested-ternary.md",description:"Prevent nested ternary expressions for better readability"},hasSuggestions:true,messages:{noNestedTernary:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.WARNING,issueName:"Nested Ternary",description:"Avoid nested ternary expressions",severity:"MEDIUM",fix:"Extract to helper variable or use if-else",documentationLink:"https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-nested-ternary.md"})},schema:[{type:"object",properties:{allow:{type:"array",items:{type:"string"},default:[]}},additionalProperties:false}]},defaultOptions:[{allow:[]}],create(context){const[options]=context.options;const{allow=[]}=options||{};function isInAllowedContext(node){if(allow.length===0){return false}let current=node.parent;while(current){if(allow.includes("jsx")&&(current.type==="JSXExpressionContainer"||current.type==="JSXElement"||current.type==="JSXFragment")){return true}if(allow.includes("variable")&¤t.type==="VariableDeclarator"){return true}if(allow.includes("return")&¤t.type==="ReturnStatement"){return true}if(allow.includes("argument")&¤t.type==="CallExpression"){return true}current=current.parent}return false}function hasNestedTernary(node){function containsTernary(expr){if(expr.type==="ConditionalExpression"){return true}switch(expr.type){case"ArrayExpression":return expr.elements.some(element=>element&&element.type!=="SpreadElement"&&containsTernary(element));case"ObjectExpression":return expr.properties.some(prop=>prop.type==="Property"&&prop.value&&containsTernary(prop.value));case"CallExpression":return expr.arguments.some(arg=>arg.type!=="SpreadElement"&&containsTernary(arg))||expr.callee.type!=="Super"&&containsTernary(expr.callee);case"MemberExpression":return containsTernary(expr.object)||expr.property.type!=="Identifier"&&expr.property.type!=="PrivateIdentifier"&&containsTernary(expr.property);case"BinaryExpression":case"LogicalExpression":return containsTernary(expr.left)||containsTernary(expr.right);case"UnaryExpression":case"UpdateExpression":return containsTernary(expr.argument);case"AssignmentExpression":return containsTernary(expr.right);case"Literal":case"Identifier":case"ThisExpression":case"Super":case"MetaProperty":return false;case"TemplateLiteral":return expr.expressions.some(exp=>containsTernary(exp));case"TaggedTemplateExpression":return containsTernary(expr.tag)||expr.quasi.expressions.some(exp=>containsTernary(exp));default:return false}}return containsTernary(node.consequent)||containsTernary(node.alternate)}return{ConditionalExpression(node){if(hasNestedTernary(node)&&!isInAllowedContext(node)){context.report({node,messageId:"noNestedTernary",data:{current:"nested ternary expression",fix:"extract or use if-else"},suggest:[{messageId:"noNestedTernary",fix(){return null}}]})}}}}});
|
|
@@ -1,206 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.noUnreadableIife = void 0;
|
|
4
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
5
|
-
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
6
|
-
exports.noUnreadableIife = (0, eslint_devkit_1.createRule)({
|
|
7
|
-
name: 'no-unreadable-iife',
|
|
8
|
-
meta: {
|
|
9
|
-
type: 'suggestion',
|
|
10
|
-
docs: {
|
|
11
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/no-unreadable-iife.md',
|
|
12
|
-
description: 'Prevent unreadable Immediately Invoked Function Expressions that harm code clarity',
|
|
13
|
-
},
|
|
14
|
-
hasSuggestions: true,
|
|
15
|
-
messages: {
|
|
16
|
-
unreadableIIFE: (0, eslint_devkit_2.formatLLMMessage)({
|
|
17
|
-
icon: eslint_devkit_2.MessageIcons.WARNING,
|
|
18
|
-
issueName: 'Unreadable IIFE',
|
|
19
|
-
description: 'Complex IIFE reduces code readability',
|
|
20
|
-
severity: 'MEDIUM',
|
|
21
|
-
fix: 'Extract to named function or simplify the IIFE structure',
|
|
22
|
-
documentationLink: 'https://developer.mozilla.org/en-US/docs/Glossary/IIFE',
|
|
23
|
-
}),
|
|
24
|
-
suggestNamedFunction: (0, eslint_devkit_2.formatLLMMessage)({
|
|
25
|
-
icon: eslint_devkit_2.MessageIcons.INFO,
|
|
26
|
-
issueName: 'Extract Function',
|
|
27
|
-
description: 'Extract complex logic to named function',
|
|
28
|
-
severity: 'LOW',
|
|
29
|
-
fix: 'function processData() { /* logic */ } processData();',
|
|
30
|
-
documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions',
|
|
31
|
-
}),
|
|
32
|
-
suggestBlockScope: (0, eslint_devkit_2.formatLLMMessage)({
|
|
33
|
-
icon: eslint_devkit_2.MessageIcons.INFO,
|
|
34
|
-
issueName: 'Use Block Scope',
|
|
35
|
-
description: 'Use block scope instead of IIFE',
|
|
36
|
-
severity: 'LOW',
|
|
37
|
-
fix: '{ const isolated = value; /* logic */ }',
|
|
38
|
-
documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block',
|
|
39
|
-
}),
|
|
40
|
-
complexIIFE: (0, eslint_devkit_2.formatLLMMessage)({
|
|
41
|
-
icon: eslint_devkit_2.MessageIcons.INFO,
|
|
42
|
-
issueName: 'Simplify IIFE',
|
|
43
|
-
description: 'Complex IIFE detected',
|
|
44
|
-
severity: 'LOW',
|
|
45
|
-
fix: 'Simplify or extract to separate function',
|
|
46
|
-
documentationLink: 'https://developer.mozilla.org/en-US/docs/Glossary/IIFE',
|
|
47
|
-
}),
|
|
48
|
-
},
|
|
49
|
-
schema: [
|
|
50
|
-
{
|
|
51
|
-
type: 'object',
|
|
52
|
-
properties: {
|
|
53
|
-
maxStatements: {
|
|
54
|
-
type: 'number',
|
|
55
|
-
minimum: 1,
|
|
56
|
-
default: 3,
|
|
57
|
-
},
|
|
58
|
-
maxDepth: {
|
|
59
|
-
type: 'number',
|
|
60
|
-
minimum: 1,
|
|
61
|
-
default: 2,
|
|
62
|
-
},
|
|
63
|
-
allowReturningIIFE: {
|
|
64
|
-
type: 'boolean',
|
|
65
|
-
default: true,
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
additionalProperties: false,
|
|
69
|
-
},
|
|
70
|
-
],
|
|
71
|
-
},
|
|
72
|
-
defaultOptions: [{ maxStatements: 3, maxDepth: 2, allowReturningIIFE: true }],
|
|
73
|
-
create(context) {
|
|
74
|
-
const [options] = context.options;
|
|
75
|
-
const { maxStatements = 3, maxDepth = 2, allowReturningIIFE = true } = options || {};
|
|
76
|
-
function getIifeFunctionNode(node) {
|
|
77
|
-
if (node.callee.type === 'FunctionExpression' ||
|
|
78
|
-
node.callee.type === 'ArrowFunctionExpression') {
|
|
79
|
-
return node.callee;
|
|
80
|
-
}
|
|
81
|
-
if (node.callee.type === 'UnaryExpression' &&
|
|
82
|
-
(node.callee.argument.type === 'FunctionExpression' ||
|
|
83
|
-
node.callee.argument.type === 'ArrowFunctionExpression')) {
|
|
84
|
-
return node.callee.argument;
|
|
85
|
-
}
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
function countStatements(body) {
|
|
89
|
-
if (body.type === 'BlockStatement') {
|
|
90
|
-
return body.body.length;
|
|
91
|
-
}
|
|
92
|
-
return 1;
|
|
93
|
-
}
|
|
94
|
-
function calculateDepth(node) {
|
|
95
|
-
const CONTROL_FLOW_TYPES = new Set([
|
|
96
|
-
'IfStatement', 'ForStatement', 'ForInStatement', 'ForOfStatement',
|
|
97
|
-
'WhileStatement', 'DoWhileStatement', 'SwitchStatement', 'TryStatement',
|
|
98
|
-
]);
|
|
99
|
-
const SKIP_KEYS = new Set(['parent', 'loc', 'range', 'tokens', 'comments', 'leadingComments', 'trailingComments']);
|
|
100
|
-
function isASTNode(value) {
|
|
101
|
-
return !!value && typeof value === 'object' && 'type' in value && 'loc' in value;
|
|
102
|
-
}
|
|
103
|
-
let maxNestingDepth = 0;
|
|
104
|
-
function walk(current, depth) {
|
|
105
|
-
const isControlFlow = CONTROL_FLOW_TYPES.has(current.type);
|
|
106
|
-
const newDepth = isControlFlow ? depth + 1 : depth;
|
|
107
|
-
maxNestingDepth = Math.max(maxNestingDepth, newDepth);
|
|
108
|
-
Object.entries(current).forEach(([key, value]) => {
|
|
109
|
-
if (SKIP_KEYS.has(key))
|
|
110
|
-
return;
|
|
111
|
-
if (Array.isArray(value)) {
|
|
112
|
-
value.forEach(item => { if (isASTNode(item))
|
|
113
|
-
walk(item, newDepth); });
|
|
114
|
-
}
|
|
115
|
-
else if (isASTNode(value)) {
|
|
116
|
-
walk(value, newDepth);
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
walk(node, 0);
|
|
121
|
-
return maxNestingDepth;
|
|
122
|
-
}
|
|
123
|
-
function hasComplexLogic(body) {
|
|
124
|
-
if (body.type !== 'BlockStatement') {
|
|
125
|
-
return false;
|
|
126
|
-
}
|
|
127
|
-
if (body.body.length > maxStatements) {
|
|
128
|
-
return true;
|
|
129
|
-
}
|
|
130
|
-
for (const statement of body.body) {
|
|
131
|
-
if (statement.type === 'IfStatement' ||
|
|
132
|
-
statement.type === 'ForStatement' ||
|
|
133
|
-
statement.type === 'WhileStatement' ||
|
|
134
|
-
statement.type === 'SwitchStatement' ||
|
|
135
|
-
statement.type === 'TryStatement') {
|
|
136
|
-
return true;
|
|
137
|
-
}
|
|
138
|
-
if (statement.type === 'FunctionDeclaration' ||
|
|
139
|
-
statement.type === 'VariableDeclaration' &&
|
|
140
|
-
statement.declarations.some((decl) => decl.init?.type === 'FunctionExpression' ||
|
|
141
|
-
decl.init?.type === 'ArrowFunctionExpression')) {
|
|
142
|
-
return true;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
function analyzeIIFE(node, functionNode) {
|
|
148
|
-
const body = functionNode.body;
|
|
149
|
-
const statementCount = countStatements(body);
|
|
150
|
-
const nestingDepth = calculateDepth(body);
|
|
151
|
-
const hasComplex = hasComplexLogic(body);
|
|
152
|
-
const returnsValue = body.type === 'BlockStatement' &&
|
|
153
|
-
body.body.some((stmt) => stmt.type === 'ReturnStatement');
|
|
154
|
-
if (allowReturningIIFE && returnsValue && !hasComplex) {
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
const issues = [];
|
|
158
|
-
if (statementCount > maxStatements) {
|
|
159
|
-
issues.push(`too many statements (${statementCount} > ${maxStatements})`);
|
|
160
|
-
}
|
|
161
|
-
if (nestingDepth > maxDepth) {
|
|
162
|
-
issues.push(`too deeply nested (${nestingDepth} > ${maxDepth})`);
|
|
163
|
-
}
|
|
164
|
-
if (hasComplex) {
|
|
165
|
-
issues.push('contains complex control flow');
|
|
166
|
-
}
|
|
167
|
-
if (functionNode.params.length > 2) {
|
|
168
|
-
issues.push('too many parameters');
|
|
169
|
-
}
|
|
170
|
-
if (issues.length > 0) {
|
|
171
|
-
context.report({
|
|
172
|
-
node,
|
|
173
|
-
messageId: 'unreadableIIFE',
|
|
174
|
-
data: {
|
|
175
|
-
issues: issues.join(', '),
|
|
176
|
-
statementCount,
|
|
177
|
-
nestingDepth,
|
|
178
|
-
suggestion: hasComplex ? 'extract to named function' : 'simplify or use block scope',
|
|
179
|
-
},
|
|
180
|
-
suggest: [
|
|
181
|
-
{
|
|
182
|
-
messageId: 'suggestNamedFunction',
|
|
183
|
-
fix(fixer) {
|
|
184
|
-
return fixer.insertTextBefore(node, '// TODO: Extract complex IIFE to named function\n');
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
{
|
|
188
|
-
messageId: 'suggestBlockScope',
|
|
189
|
-
fix(fixer) {
|
|
190
|
-
return fixer.insertTextBefore(node, '// TODO: Consider using block scope { const x = ...; }\n');
|
|
191
|
-
},
|
|
192
|
-
},
|
|
193
|
-
],
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
return {
|
|
198
|
-
CallExpression(node) {
|
|
199
|
-
const functionNode = getIifeFunctionNode(node);
|
|
200
|
-
if (functionNode) {
|
|
201
|
-
analyzeIIFE(node, functionNode);
|
|
202
|
-
}
|
|
203
|
-
},
|
|
204
|
-
};
|
|
205
|
-
},
|
|
206
|
-
});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noUnreadableIife=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");exports.noUnreadableIife=(0,eslint_devkit_1.createRule)({name:"no-unreadable-iife",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/no-unreadable-iife.md",description:"Prevent unreadable Immediately Invoked Function Expressions that harm code clarity"},hasSuggestions:true,messages:{unreadableIIFE:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.WARNING,issueName:"Unreadable IIFE",description:"Complex IIFE reduces code readability",severity:"MEDIUM",fix:"Extract to named function or simplify the IIFE structure",documentationLink:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"}),suggestNamedFunction:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.INFO,issueName:"Extract Function",description:"Extract complex logic to named function",severity:"LOW",fix:"function processData() { /* logic */ } processData();",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions"}),suggestBlockScope:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.INFO,issueName:"Use Block Scope",description:"Use block scope instead of IIFE",severity:"LOW",fix:"{ const isolated = value; /* logic */ }",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block"}),complexIIFE:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.INFO,issueName:"Simplify IIFE",description:"Complex IIFE detected",severity:"LOW",fix:"Simplify or extract to separate function",documentationLink:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"})},schema:[{type:"object",properties:{maxStatements:{type:"number",minimum:1,default:3},maxDepth:{type:"number",minimum:1,default:2},allowReturningIIFE:{type:"boolean",default:true}},additionalProperties:false}]},defaultOptions:[{maxStatements:3,maxDepth:2,allowReturningIIFE:true}],create(context){const[options]=context.options;const{maxStatements=3,maxDepth=2,allowReturningIIFE=true}=options||{};function getIifeFunctionNode(node){if(node.callee.type==="FunctionExpression"||node.callee.type==="ArrowFunctionExpression"){return node.callee}if(node.callee.type==="UnaryExpression"&&(node.callee.argument.type==="FunctionExpression"||node.callee.argument.type==="ArrowFunctionExpression")){return node.callee.argument}return null}function countStatements(body){if(body.type==="BlockStatement"){return body.body.length}return 1}function calculateDepth(node){const CONTROL_FLOW_TYPES=new Set(["IfStatement","ForStatement","ForInStatement","ForOfStatement","WhileStatement","DoWhileStatement","SwitchStatement","TryStatement"]);const SKIP_KEYS=new Set(["parent","loc","range","tokens","comments","leadingComments","trailingComments"]);function isASTNode(value){return!!value&&typeof value==="object"&&"type"in value&&"loc"in value}let maxNestingDepth=0;function walk(current,depth){const isControlFlow=CONTROL_FLOW_TYPES.has(current.type);const newDepth=isControlFlow?depth+1:depth;maxNestingDepth=Math.max(maxNestingDepth,newDepth);Object.entries(current).forEach(([key,value])=>{if(SKIP_KEYS.has(key))return;if(Array.isArray(value)){value.forEach(item=>{if(isASTNode(item))walk(item,newDepth)})}else if(isASTNode(value)){walk(value,newDepth)}})}walk(node,0);return maxNestingDepth}function hasComplexLogic(body){if(body.type!=="BlockStatement"){return false}if(body.body.length>maxStatements){return true}for(const statement of body.body){if(statement.type==="IfStatement"||statement.type==="ForStatement"||statement.type==="WhileStatement"||statement.type==="SwitchStatement"||statement.type==="TryStatement"){return true}if(statement.type==="FunctionDeclaration"||statement.type==="VariableDeclaration"&&statement.declarations.some(decl=>decl.init?.type==="FunctionExpression"||decl.init?.type==="ArrowFunctionExpression")){return true}}return false}function analyzeIIFE(node,functionNode){const body=functionNode.body;const statementCount=countStatements(body);const nestingDepth=calculateDepth(body);const hasComplex=hasComplexLogic(body);const returnsValue=body.type==="BlockStatement"&&body.body.some(stmt=>stmt.type==="ReturnStatement");if(allowReturningIIFE&&returnsValue&&!hasComplex){return}const issues=[];if(statementCount>maxStatements){issues.push(`too many statements (${statementCount} > ${maxStatements})`)}if(nestingDepth>maxDepth){issues.push(`too deeply nested (${nestingDepth} > ${maxDepth})`)}if(hasComplex){issues.push("contains complex control flow")}if(functionNode.params.length>2){issues.push("too many parameters")}if(issues.length>0){context.report({node,messageId:"unreadableIIFE",data:{issues:issues.join(", "),statementCount,nestingDepth,suggestion:hasComplex?"extract to named function":"simplify or use block scope"},suggest:[{messageId:"suggestNamedFunction",fix(fixer){return fixer.insertTextBefore(node,"// TODO: Extract complex IIFE to named function\n")}},{messageId:"suggestBlockScope",fix(fixer){return fixer.insertTextBefore(node,"// TODO: Consider using block scope { const x = ...; }\n")}}]})}}return{CallExpression(node){const functionNode=getIifeFunctionNode(node);if(functionNode){analyzeIIFE(node,functionNode)}}}}});
|
package/src/types/index.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});
|