invar-tools 1.17.2__py3-none-any.whl → 1.17.3__py3-none-any.whl
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.
- invar/node_tools/.gitignore +10 -6
- invar/node_tools/eslint-plugin/rules/__tests__/behavior.test.js +1321 -0
- invar/node_tools/eslint-plugin/rules/__tests__/behavior.test.js.map +1 -0
- invar/node_tools/eslint-plugin/rules/__tests__/e2e-scenarios.test.js +414 -0
- invar/node_tools/eslint-plugin/rules/__tests__/e2e-scenarios.test.js.map +1 -0
- invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/function-lengths.js +142 -0
- invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/function-lengths.js.map +1 -0
- invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/has-io-imports.js +15 -0
- invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/has-io-imports.js.map +1 -0
- invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/valid-small.js +27 -0
- invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/valid-small.js.map +1 -0
- invar/node_tools/eslint-plugin/rules/__tests__/fixtures/exported-functions.js +43 -0
- invar/node_tools/eslint-plugin/rules/__tests__/fixtures/exported-functions.js.map +1 -0
- invar/node_tools/eslint-plugin/rules/__tests__/fixtures/shell/with-io.js +27 -0
- invar/node_tools/eslint-plugin/rules/__tests__/fixtures/shell/with-io.js.map +1 -0
- invar/node_tools/eslint-plugin/rules/__tests__/fixtures/tests/large.test.js +260 -0
- invar/node_tools/eslint-plugin/rules/__tests__/fixtures/tests/large.test.js.map +1 -0
- invar/node_tools/eslint-plugin/rules/max-file-lines.js +105 -0
- invar/node_tools/eslint-plugin/rules/max-file-lines.js.map +1 -0
- invar/node_tools/eslint-plugin/rules/max-function-lines.js +133 -0
- invar/node_tools/eslint-plugin/rules/max-function-lines.js.map +1 -0
- invar/node_tools/eslint-plugin/rules/no-any-in-schema.js +39 -0
- invar/node_tools/eslint-plugin/rules/no-any-in-schema.js.map +1 -0
- invar/node_tools/eslint-plugin/rules/no-empty-schema.js +69 -0
- invar/node_tools/eslint-plugin/rules/no-empty-schema.js.map +1 -0
- invar/node_tools/eslint-plugin/rules/no-impure-calls-in-core.js +52 -0
- invar/node_tools/eslint-plugin/rules/no-impure-calls-in-core.js.map +1 -0
- invar/node_tools/eslint-plugin/rules/no-io-in-core.js +99 -0
- invar/node_tools/eslint-plugin/rules/no-io-in-core.js.map +1 -0
- invar/node_tools/eslint-plugin/rules/no-pure-logic-in-shell.js +197 -0
- invar/node_tools/eslint-plugin/rules/no-pure-logic-in-shell.js.map +1 -0
- invar/node_tools/eslint-plugin/rules/no-redundant-type-schema.js +99 -0
- invar/node_tools/eslint-plugin/rules/no-redundant-type-schema.js.map +1 -0
- invar/node_tools/eslint-plugin/rules/no-runtime-imports.js +66 -0
- invar/node_tools/eslint-plugin/rules/no-runtime-imports.js.map +1 -0
- invar/node_tools/eslint-plugin/rules/require-complete-validation.js +104 -0
- invar/node_tools/eslint-plugin/rules/require-complete-validation.js.map +1 -0
- invar/node_tools/eslint-plugin/rules/require-jsdoc-example.js +81 -0
- invar/node_tools/eslint-plugin/rules/require-jsdoc-example.js.map +1 -0
- invar/node_tools/eslint-plugin/rules/require-schema-validation.js +308 -0
- invar/node_tools/eslint-plugin/rules/require-schema-validation.js.map +1 -0
- invar/node_tools/eslint-plugin/rules/shell-complexity.js +273 -0
- invar/node_tools/eslint-plugin/rules/shell-complexity.js.map +1 -0
- invar/node_tools/eslint-plugin/rules/shell-result-type.js +138 -0
- invar/node_tools/eslint-plugin/rules/shell-result-type.js.map +1 -0
- invar/node_tools/eslint-plugin/rules/thin-entry-points.js +174 -0
- invar/node_tools/eslint-plugin/rules/thin-entry-points.js.map +1 -0
- invar/node_tools/eslint-plugin/utils/layer-detection.js +91 -0
- invar/node_tools/eslint-plugin/utils/layer-detection.js.map +1 -0
- invar/node_tools/eslint-plugin/utils/math-example.js +31 -0
- invar/node_tools/eslint-plugin/utils/math-example.js.map +1 -0
- {invar_tools-1.17.2.dist-info → invar_tools-1.17.3.dist-info}/METADATA +1 -1
- {invar_tools-1.17.2.dist-info → invar_tools-1.17.3.dist-info}/RECORD +58 -8
- {invar_tools-1.17.2.dist-info → invar_tools-1.17.3.dist-info}/WHEEL +0 -0
- {invar_tools-1.17.2.dist-info → invar_tools-1.17.3.dist-info}/entry_points.txt +0 -0
- {invar_tools-1.17.2.dist-info → invar_tools-1.17.3.dist-info}/licenses/LICENSE +0 -0
- {invar_tools-1.17.2.dist-info → invar_tools-1.17.3.dist-info}/licenses/LICENSE-GPL +0 -0
- {invar_tools-1.17.2.dist-info → invar_tools-1.17.3.dist-info}/licenses/NOTICE +0 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: shell-complexity
|
|
3
|
+
*
|
|
4
|
+
* Warn when Shell functions are too complex and should be split.
|
|
5
|
+
* Shell functions should orchestrate I/O, not contain complex business logic.
|
|
6
|
+
*
|
|
7
|
+
* Detects:
|
|
8
|
+
* - High cyclomatic complexity (many branches)
|
|
9
|
+
* - Too many statements (>20 lines of logic)
|
|
10
|
+
* - Multiple nested control structures
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_MAX_STATEMENTS = 20;
|
|
13
|
+
const DEFAULT_MAX_COMPLEXITY = 10;
|
|
14
|
+
export const shellComplexity = {
|
|
15
|
+
meta: {
|
|
16
|
+
type: 'suggestion',
|
|
17
|
+
docs: {
|
|
18
|
+
description: 'Warn when Shell functions are too complex (should extract logic to Core)',
|
|
19
|
+
recommended: false,
|
|
20
|
+
},
|
|
21
|
+
schema: [
|
|
22
|
+
{
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: {
|
|
25
|
+
maxStatements: {
|
|
26
|
+
type: 'number',
|
|
27
|
+
default: DEFAULT_MAX_STATEMENTS,
|
|
28
|
+
},
|
|
29
|
+
maxComplexity: {
|
|
30
|
+
type: 'number',
|
|
31
|
+
default: DEFAULT_MAX_COMPLEXITY,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
additionalProperties: false,
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
messages: {
|
|
38
|
+
tooManyStatements: 'Shell function "{{name}}" has {{count}} statements (max {{max}}). Consider extracting pure logic to Core.',
|
|
39
|
+
tooComplex: 'Shell function "{{name}}" has complexity {{complexity}} (max {{max}}). Consider splitting into smaller functions.',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
create(context) {
|
|
43
|
+
const filename = context.filename || context.getFilename();
|
|
44
|
+
// Only check files in shell/ directories
|
|
45
|
+
const isShell = /[/\\]shell[/\\]/.test(filename);
|
|
46
|
+
if (!isShell) {
|
|
47
|
+
return {}; // Skip non-shell files
|
|
48
|
+
}
|
|
49
|
+
const options = (context.options[0] || {});
|
|
50
|
+
const maxStatements = options.maxStatements || DEFAULT_MAX_STATEMENTS;
|
|
51
|
+
const maxComplexity = options.maxComplexity || DEFAULT_MAX_COMPLEXITY;
|
|
52
|
+
/**
|
|
53
|
+
* Count statements in function body
|
|
54
|
+
*/
|
|
55
|
+
function countStatements(node) {
|
|
56
|
+
if (!node.body || node.body.type !== 'BlockStatement') {
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
let count = 0;
|
|
60
|
+
const MAX_DEPTH = 10; // Reduced from 50 for better performance
|
|
61
|
+
function visit(n, depth = 0) {
|
|
62
|
+
if (depth > MAX_DEPTH)
|
|
63
|
+
return; // Depth limit
|
|
64
|
+
// Count different statement types
|
|
65
|
+
if (n.type === 'ExpressionStatement' ||
|
|
66
|
+
n.type === 'VariableDeclaration' ||
|
|
67
|
+
n.type === 'ReturnStatement' ||
|
|
68
|
+
n.type === 'IfStatement' ||
|
|
69
|
+
n.type === 'ForStatement' ||
|
|
70
|
+
n.type === 'WhileStatement' ||
|
|
71
|
+
n.type === 'DoWhileStatement' ||
|
|
72
|
+
n.type === 'SwitchStatement' ||
|
|
73
|
+
n.type === 'TryStatement' ||
|
|
74
|
+
n.type === 'ThrowStatement') {
|
|
75
|
+
count++;
|
|
76
|
+
}
|
|
77
|
+
// Performance: Skip non-statement nodes
|
|
78
|
+
if (n.type === 'Literal' ||
|
|
79
|
+
n.type === 'Identifier' ||
|
|
80
|
+
n.type === 'ThisExpression') {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// Only visit relevant keys
|
|
84
|
+
const relevantKeys = ['body', 'consequent', 'alternate', 'cases', 'block', 'finalizer'];
|
|
85
|
+
for (const key of relevantKeys) {
|
|
86
|
+
const value = n[key];
|
|
87
|
+
if (!value)
|
|
88
|
+
continue;
|
|
89
|
+
if (typeof value === 'object') {
|
|
90
|
+
if (Array.isArray(value)) {
|
|
91
|
+
for (const item of value) {
|
|
92
|
+
if (item && typeof item === 'object' && 'type' in item) {
|
|
93
|
+
visit(item, depth + 1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else if ('type' in value) {
|
|
98
|
+
visit(value, depth + 1);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
visit(node.body);
|
|
104
|
+
return count;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Calculate cyclomatic complexity
|
|
108
|
+
* Complexity increases with each decision point: if, for, while, case, &&, ||, ?:
|
|
109
|
+
*/
|
|
110
|
+
function calculateComplexity(node) {
|
|
111
|
+
if (!node.body) {
|
|
112
|
+
return 1; // Base complexity
|
|
113
|
+
}
|
|
114
|
+
let complexity = 1; // Start at 1
|
|
115
|
+
const MAX_DEPTH = 10; // Reduced from 50 for better performance
|
|
116
|
+
function visit(n, depth = 0) {
|
|
117
|
+
if (depth > MAX_DEPTH)
|
|
118
|
+
return; // Depth limit
|
|
119
|
+
// Decision points that increase complexity
|
|
120
|
+
if (n.type === 'IfStatement' ||
|
|
121
|
+
n.type === 'ForStatement' ||
|
|
122
|
+
n.type === 'ForInStatement' ||
|
|
123
|
+
n.type === 'ForOfStatement' ||
|
|
124
|
+
n.type === 'WhileStatement' ||
|
|
125
|
+
n.type === 'DoWhileStatement' ||
|
|
126
|
+
n.type === 'ConditionalExpression' || // ternary ? :
|
|
127
|
+
n.type === 'CatchClause') {
|
|
128
|
+
complexity++;
|
|
129
|
+
}
|
|
130
|
+
// SwitchCase: only count non-default cases
|
|
131
|
+
if (n.type === 'SwitchCase') {
|
|
132
|
+
const caseNode = n;
|
|
133
|
+
if (caseNode.test !== null) {
|
|
134
|
+
complexity++;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Logical operators (&&, ||, ??) add complexity
|
|
138
|
+
if (n.type === 'LogicalExpression') {
|
|
139
|
+
const logicalNode = n;
|
|
140
|
+
if (logicalNode.operator === '&&' || logicalNode.operator === '||' || logicalNode.operator === '??') {
|
|
141
|
+
complexity++;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Performance: Skip leaf nodes
|
|
145
|
+
if (n.type === 'Literal' ||
|
|
146
|
+
n.type === 'Identifier' ||
|
|
147
|
+
n.type === 'ThisExpression') {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// Only visit relevant keys
|
|
151
|
+
const relevantKeys = ['body', 'test', 'consequent', 'alternate', 'left', 'right', 'argument', 'cases'];
|
|
152
|
+
for (const key of relevantKeys) {
|
|
153
|
+
const value = n[key];
|
|
154
|
+
if (!value)
|
|
155
|
+
continue;
|
|
156
|
+
if (typeof value === 'object') {
|
|
157
|
+
if (Array.isArray(value)) {
|
|
158
|
+
for (const item of value) {
|
|
159
|
+
if (item && typeof item === 'object' && 'type' in item) {
|
|
160
|
+
visit(item, depth + 1);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else if ('type' in value) {
|
|
165
|
+
visit(value, depth + 1);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
visit(node.body);
|
|
171
|
+
return complexity;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Check if function has @shell_complexity marker comment (DX-22 Fix-or-Explain)
|
|
175
|
+
*
|
|
176
|
+
* Looks for `// @shell_complexity: <reason>` in the 4 lines before the function.
|
|
177
|
+
* This allows developers to explicitly justify complexity that cannot be refactored.
|
|
178
|
+
*/
|
|
179
|
+
function hasComplexityMarker(node) {
|
|
180
|
+
const sourceCode = context.getSourceCode();
|
|
181
|
+
const functionStart = node.loc?.start.line;
|
|
182
|
+
if (!functionStart) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
// Check 4 lines before function (matching Python implementation)
|
|
186
|
+
const startLine = Math.max(1, functionStart - 4);
|
|
187
|
+
const endLine = functionStart;
|
|
188
|
+
for (let line = startLine; line < endLine; line++) {
|
|
189
|
+
const text = sourceCode.lines[line - 1]; // lines array is 0-indexed
|
|
190
|
+
if (text && /\/\/\s*@shell_complexity\s*:/.test(text)) {
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Get function name with improved extraction from parent context
|
|
198
|
+
*/
|
|
199
|
+
function getFunctionName(node) {
|
|
200
|
+
// 1. FunctionDeclaration - use direct id
|
|
201
|
+
if (node.type === 'FunctionDeclaration' && node.id) {
|
|
202
|
+
return node.id.name;
|
|
203
|
+
}
|
|
204
|
+
// 2. FunctionExpression - try id first, then parent
|
|
205
|
+
if (node.type === 'FunctionExpression' && node.id) {
|
|
206
|
+
return node.id.name;
|
|
207
|
+
}
|
|
208
|
+
// 3. For unnamed FunctionExpression or ArrowFunctionExpression,
|
|
209
|
+
// try to get name from parent VariableDeclarator
|
|
210
|
+
try {
|
|
211
|
+
const ancestors = context.getAncestors();
|
|
212
|
+
// Look for parent VariableDeclarator
|
|
213
|
+
for (let i = ancestors.length - 1; i >= 0; i--) {
|
|
214
|
+
const ancestor = ancestors[i];
|
|
215
|
+
if (ancestor.type === 'VariableDeclarator') {
|
|
216
|
+
const varDecl = ancestor;
|
|
217
|
+
if (varDecl.id && varDecl.id.type === 'Identifier' && varDecl.id.name) {
|
|
218
|
+
return varDecl.id.name;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
// If ancestor lookup fails, fall through to 'anonymous'
|
|
225
|
+
}
|
|
226
|
+
return 'anonymous';
|
|
227
|
+
}
|
|
228
|
+
function checkFunction(node) {
|
|
229
|
+
const functionName = getFunctionName(node);
|
|
230
|
+
// Skip anonymous or very short helper functions
|
|
231
|
+
if (functionName === 'anonymous' || functionName.length < 3) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// DX-22: Skip if marked with @shell_complexity (Fix-or-Explain mechanism)
|
|
235
|
+
if (hasComplexityMarker(node)) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
// Check statement count
|
|
239
|
+
const statementCount = countStatements(node);
|
|
240
|
+
if (statementCount > maxStatements) {
|
|
241
|
+
context.report({
|
|
242
|
+
node: node,
|
|
243
|
+
messageId: 'tooManyStatements',
|
|
244
|
+
data: {
|
|
245
|
+
name: functionName,
|
|
246
|
+
count: String(statementCount),
|
|
247
|
+
max: String(maxStatements),
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
// Check cyclomatic complexity
|
|
252
|
+
const complexity = calculateComplexity(node);
|
|
253
|
+
if (complexity > maxComplexity) {
|
|
254
|
+
context.report({
|
|
255
|
+
node: node,
|
|
256
|
+
messageId: 'tooComplex',
|
|
257
|
+
data: {
|
|
258
|
+
name: functionName,
|
|
259
|
+
complexity: String(complexity),
|
|
260
|
+
max: String(maxComplexity),
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
FunctionDeclaration: checkFunction,
|
|
267
|
+
FunctionExpression: checkFunction,
|
|
268
|
+
ArrowFunctionExpression: checkFunction,
|
|
269
|
+
};
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
export default shellComplexity;
|
|
273
|
+
//# sourceMappingURL=shell-complexity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-complexity.js","sourceRoot":"","sources":["../../src/rules/shell-complexity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: shell-result-type
|
|
3
|
+
*
|
|
4
|
+
* Shell functions must return Result<T, E> type.
|
|
5
|
+
* This enforces explicit error handling in the Shell layer.
|
|
6
|
+
*/
|
|
7
|
+
const RESULT_TYPE_PATTERNS = [
|
|
8
|
+
/^Result</,
|
|
9
|
+
/^ResultAsync</,
|
|
10
|
+
/^Ok</,
|
|
11
|
+
/^Err</,
|
|
12
|
+
/^Either</,
|
|
13
|
+
/^Left</,
|
|
14
|
+
/^Right</,
|
|
15
|
+
];
|
|
16
|
+
function isResultType(typeAnnotation) {
|
|
17
|
+
return RESULT_TYPE_PATTERNS.some(pattern => pattern.test(typeAnnotation));
|
|
18
|
+
}
|
|
19
|
+
function isInShellDirectory(filename) {
|
|
20
|
+
return filename.includes('/shell/') || filename.includes('\\shell\\');
|
|
21
|
+
}
|
|
22
|
+
function isExported(node) {
|
|
23
|
+
const parent = node.parent;
|
|
24
|
+
if (!parent)
|
|
25
|
+
return false;
|
|
26
|
+
if (parent.type === 'ExportNamedDeclaration')
|
|
27
|
+
return true;
|
|
28
|
+
if (parent.type === 'ExportDefaultDeclaration')
|
|
29
|
+
return true;
|
|
30
|
+
// Check for module.exports assignment
|
|
31
|
+
if (parent.type === 'VariableDeclarator') {
|
|
32
|
+
const grandparent = parent.parent;
|
|
33
|
+
if (grandparent?.type === 'VariableDeclaration') {
|
|
34
|
+
const greatGrandparent = grandparent.parent;
|
|
35
|
+
if (greatGrandparent?.type === 'ExportNamedDeclaration')
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
export const shellResultType = {
|
|
42
|
+
meta: {
|
|
43
|
+
type: 'suggestion',
|
|
44
|
+
docs: {
|
|
45
|
+
description: 'Shell functions must return Result<T, E> type',
|
|
46
|
+
recommended: true,
|
|
47
|
+
},
|
|
48
|
+
hasSuggestions: true,
|
|
49
|
+
schema: [
|
|
50
|
+
{
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties: {
|
|
53
|
+
checkPrivate: {
|
|
54
|
+
type: 'boolean',
|
|
55
|
+
default: false,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
additionalProperties: false,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
messages: {
|
|
62
|
+
missingResultType: 'Shell function "{{name}}" should return Result<T, E> type for explicit error handling',
|
|
63
|
+
wrapWithResult: 'Wrap return type with Result<{{returnType}}, Error>',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
create(context) {
|
|
67
|
+
const filename = context.filename || context.getFilename();
|
|
68
|
+
const sourceCode = context.sourceCode || context.getSourceCode();
|
|
69
|
+
const options = context.options[0] || {};
|
|
70
|
+
const checkPrivate = options.checkPrivate || false;
|
|
71
|
+
if (!isInShellDirectory(filename)) {
|
|
72
|
+
return {};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Get the text of a return type annotation from source code.
|
|
76
|
+
* Strips the leading ": " to return just the type (e.g., "Result<T, E>").
|
|
77
|
+
*/
|
|
78
|
+
function getReturnTypeText(node) {
|
|
79
|
+
const typedNode = node;
|
|
80
|
+
if (!typedNode.returnType)
|
|
81
|
+
return null;
|
|
82
|
+
const text = sourceCode.getText(typedNode.returnType);
|
|
83
|
+
// Strip leading ": " from type annotation (e.g., ": Result<T, E>" -> "Result<T, E>")
|
|
84
|
+
return text.replace(/^:\s*/, '');
|
|
85
|
+
}
|
|
86
|
+
function checkFunction(node, name, returnType) {
|
|
87
|
+
// Skip anonymous functions
|
|
88
|
+
if (!name)
|
|
89
|
+
return;
|
|
90
|
+
// Skip private functions unless configured
|
|
91
|
+
if (!checkPrivate && name.startsWith('_'))
|
|
92
|
+
return;
|
|
93
|
+
// Skip non-exported functions
|
|
94
|
+
if (!isExported(node))
|
|
95
|
+
return;
|
|
96
|
+
// Check return type
|
|
97
|
+
if (!returnType || !isResultType(returnType)) {
|
|
98
|
+
const suggestedReturnType = returnType || 'void';
|
|
99
|
+
const typedNode = node;
|
|
100
|
+
context.report({
|
|
101
|
+
node,
|
|
102
|
+
messageId: 'missingResultType',
|
|
103
|
+
data: { name },
|
|
104
|
+
suggest: typedNode.returnType ? [
|
|
105
|
+
{
|
|
106
|
+
messageId: 'wrapWithResult',
|
|
107
|
+
data: { returnType: suggestedReturnType },
|
|
108
|
+
fix(fixer) {
|
|
109
|
+
if (!typedNode.returnType)
|
|
110
|
+
return null;
|
|
111
|
+
const newType = `Result<${suggestedReturnType}, Error>`;
|
|
112
|
+
return fixer.replaceText(typedNode.returnType, `: ${newType}`);
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
] : [],
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
FunctionDeclaration(node) {
|
|
121
|
+
const name = node.id?.name || null;
|
|
122
|
+
const returnType = getReturnTypeText(node);
|
|
123
|
+
checkFunction(node, name, returnType);
|
|
124
|
+
},
|
|
125
|
+
ArrowFunctionExpression(node) {
|
|
126
|
+
// Get name from parent variable declaration
|
|
127
|
+
const parent = node.parent;
|
|
128
|
+
const name = parent?.type === 'VariableDeclarator' && parent.id?.name
|
|
129
|
+
? parent.id.name
|
|
130
|
+
: null;
|
|
131
|
+
const returnType = getReturnTypeText(node);
|
|
132
|
+
checkFunction(node, name, returnType);
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
export default shellResultType;
|
|
138
|
+
//# sourceMappingURL=shell-result-type.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-result-type.js","sourceRoot":"","sources":["../../src/rules/shell-result-type.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: thin-entry-points
|
|
3
|
+
*
|
|
4
|
+
* Detect entry point files that contain substantial logic.
|
|
5
|
+
* Entry points should be thin - just importing and delegating to Core/Shell.
|
|
6
|
+
*
|
|
7
|
+
* Detects:
|
|
8
|
+
* - index.ts, main.ts, cli.ts with >10 non-import statements
|
|
9
|
+
* - Complex logic in entry points (functions, classes, etc.)
|
|
10
|
+
* - Entry points should export/re-export, not implement
|
|
11
|
+
*/
|
|
12
|
+
// Entry point file patterns
|
|
13
|
+
const ENTRY_POINT_PATTERNS = [
|
|
14
|
+
/index\.(ts|js|tsx|jsx)$/,
|
|
15
|
+
/main\.(ts|js|tsx|jsx)$/,
|
|
16
|
+
/cli\.(ts|js|tsx|jsx)$/,
|
|
17
|
+
/app\.(ts|js|tsx|jsx)$/,
|
|
18
|
+
/server\.(ts|js|tsx|jsx)$/,
|
|
19
|
+
];
|
|
20
|
+
const DEFAULT_MAX_STATEMENTS = 10;
|
|
21
|
+
export const thinEntryPoints = {
|
|
22
|
+
meta: {
|
|
23
|
+
type: 'suggestion',
|
|
24
|
+
docs: {
|
|
25
|
+
description: 'Warn when entry point files contain substantial logic (should delegate to Core/Shell)',
|
|
26
|
+
recommended: false,
|
|
27
|
+
},
|
|
28
|
+
schema: [
|
|
29
|
+
{
|
|
30
|
+
type: 'object',
|
|
31
|
+
properties: {
|
|
32
|
+
maxStatements: {
|
|
33
|
+
type: 'number',
|
|
34
|
+
default: DEFAULT_MAX_STATEMENTS,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
additionalProperties: false,
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
messages: {
|
|
41
|
+
tooMuchLogic: 'Entry point file "{{filename}}" has {{count}} non-import statements (max {{max}}). Entry points should be thin - delegate to Core/Shell.',
|
|
42
|
+
hasComplexLogic: 'Entry point file "{{filename}}" contains {{type}}. Entry points should only import/export, not implement logic.',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
create(context) {
|
|
46
|
+
const filename = context.filename || context.getFilename();
|
|
47
|
+
// Check if this is an entry point file
|
|
48
|
+
const isEntryPoint = ENTRY_POINT_PATTERNS.some(pattern => pattern.test(filename));
|
|
49
|
+
if (!isEntryPoint) {
|
|
50
|
+
return {}; // Skip non-entry-point files
|
|
51
|
+
}
|
|
52
|
+
const options = (context.options[0] || {});
|
|
53
|
+
const maxStatements = options.maxStatements || DEFAULT_MAX_STATEMENTS;
|
|
54
|
+
/**
|
|
55
|
+
* Check if statement is an import/export WITHOUT a declaration
|
|
56
|
+
* Export with declarations like "export function foo()" should be treated as declarations
|
|
57
|
+
*/
|
|
58
|
+
function isImportOrExport(stmt) {
|
|
59
|
+
if (stmt.type === 'ImportDeclaration' || stmt.type === 'ExportAllDeclaration') {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
// ExportNamedDeclaration and ExportDefaultDeclaration can have declarations
|
|
63
|
+
if (stmt.type === 'ExportNamedDeclaration' || stmt.type === 'ExportDefaultDeclaration') {
|
|
64
|
+
const exportStmt = stmt;
|
|
65
|
+
// If there's a declaration, treat it as a regular statement (not just an export)
|
|
66
|
+
return exportStmt.declaration === null;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check if statement is simple (type-only, interface, or simple variable declaration)
|
|
72
|
+
*/
|
|
73
|
+
function isSimpleStatement(stmt) {
|
|
74
|
+
// Type aliases and interfaces are OK (TypeScript-specific node types)
|
|
75
|
+
const stmtType = stmt.type;
|
|
76
|
+
if (stmtType === 'TSTypeAliasDeclaration' || stmtType === 'TSInterfaceDeclaration') {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
// Simple variable declarations without complex initializers
|
|
80
|
+
if (stmt.type === 'VariableDeclaration') {
|
|
81
|
+
const varDecl = stmt;
|
|
82
|
+
// Only allow simple assignments, not complex expressions
|
|
83
|
+
for (const decl of varDecl.declarations) {
|
|
84
|
+
if (decl.init && decl.init.type !== 'Literal' && decl.init.type !== 'Identifier') {
|
|
85
|
+
return false; // Complex initializer
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Check for complex logic (functions, classes, etc.)
|
|
94
|
+
*/
|
|
95
|
+
function hasComplexLogic(stmt) {
|
|
96
|
+
if (stmt.type === 'FunctionDeclaration') {
|
|
97
|
+
return { has: true, type: 'function definition' };
|
|
98
|
+
}
|
|
99
|
+
if (stmt.type === 'ClassDeclaration') {
|
|
100
|
+
return { has: true, type: 'class definition' };
|
|
101
|
+
}
|
|
102
|
+
if (stmt.type === 'IfStatement' || stmt.type === 'ForStatement' || stmt.type === 'WhileStatement') {
|
|
103
|
+
return { has: true, type: 'control flow statement' };
|
|
104
|
+
}
|
|
105
|
+
if (stmt.type === 'TryStatement') {
|
|
106
|
+
return { has: true, type: 'try-catch block' };
|
|
107
|
+
}
|
|
108
|
+
// Check for complex variable declarations with function/class expressions
|
|
109
|
+
if (stmt.type === 'VariableDeclaration') {
|
|
110
|
+
const varDecl = stmt;
|
|
111
|
+
for (const decl of varDecl.declarations) {
|
|
112
|
+
if (decl.init) {
|
|
113
|
+
if (decl.init.type === 'FunctionExpression' ||
|
|
114
|
+
decl.init.type === 'ArrowFunctionExpression' ||
|
|
115
|
+
decl.init.type === 'ClassExpression') {
|
|
116
|
+
return { has: true, type: 'function/class expression' };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return { has: false, type: '' };
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
Program(node) {
|
|
125
|
+
const program = node;
|
|
126
|
+
const statements = program.body;
|
|
127
|
+
// Count non-import/export statements
|
|
128
|
+
let nonImportExportCount = 0;
|
|
129
|
+
const complexLogicItems = [];
|
|
130
|
+
for (const stmt of statements) {
|
|
131
|
+
if (!isImportOrExport(stmt) && !isSimpleStatement(stmt)) {
|
|
132
|
+
nonImportExportCount++;
|
|
133
|
+
// Check for complex logic
|
|
134
|
+
const complexCheck = hasComplexLogic(stmt);
|
|
135
|
+
if (complexCheck.has) {
|
|
136
|
+
const stmtNode = stmt;
|
|
137
|
+
complexLogicItems.push({
|
|
138
|
+
type: complexCheck.type,
|
|
139
|
+
line: stmtNode.loc?.start.line || 0,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Report complex logic violations (higher priority)
|
|
145
|
+
if (complexLogicItems.length > 0) {
|
|
146
|
+
for (const item of complexLogicItems) {
|
|
147
|
+
context.report({
|
|
148
|
+
node: node,
|
|
149
|
+
messageId: 'hasComplexLogic',
|
|
150
|
+
data: {
|
|
151
|
+
filename: filename.replace(/\\/g, '/').split('/').pop() || filename,
|
|
152
|
+
type: item.type,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Report statement count violation
|
|
158
|
+
if (nonImportExportCount > maxStatements) {
|
|
159
|
+
context.report({
|
|
160
|
+
node: node,
|
|
161
|
+
messageId: 'tooMuchLogic',
|
|
162
|
+
data: {
|
|
163
|
+
filename: filename.replace(/\\/g, '/').split('/').pop() || filename,
|
|
164
|
+
count: String(nonImportExportCount),
|
|
165
|
+
max: String(maxStatements),
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
export default thinEntryPoints;
|
|
174
|
+
//# sourceMappingURL=thin-entry-points.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thin-entry-points.js","sourceRoot":"","sources":["../../src/rules/thin-entry-points.ts"],"names":[],"mappings":"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"}
|