@webpieces/dev-config 0.2.17 → 0.2.21
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/architecture/executors/generate/executor.d.ts +17 -0
- package/architecture/executors/generate/executor.js +67 -0
- package/architecture/executors/generate/executor.js.map +1 -0
- package/architecture/executors/generate/executor.ts +83 -0
- package/architecture/executors/generate/schema.json +14 -0
- package/architecture/executors/validate-architecture-unchanged/executor.d.ts +17 -0
- package/architecture/executors/validate-architecture-unchanged/executor.js +65 -0
- package/architecture/executors/validate-architecture-unchanged/executor.js.map +1 -0
- package/architecture/executors/validate-architecture-unchanged/executor.ts +81 -0
- package/architecture/executors/validate-architecture-unchanged/schema.json +14 -0
- package/architecture/executors/validate-no-cycles/executor.d.ts +16 -0
- package/architecture/executors/validate-no-cycles/executor.js +48 -0
- package/architecture/executors/validate-no-cycles/executor.js.map +1 -0
- package/architecture/executors/validate-no-cycles/executor.ts +60 -0
- package/architecture/executors/validate-no-cycles/schema.json +8 -0
- package/architecture/executors/validate-no-skiplevel-deps/executor.d.ts +19 -0
- package/architecture/executors/validate-no-skiplevel-deps/executor.js +227 -0
- package/architecture/executors/validate-no-skiplevel-deps/executor.js.map +1 -0
- package/architecture/executors/validate-no-skiplevel-deps/executor.ts +267 -0
- package/architecture/executors/validate-no-skiplevel-deps/schema.json +8 -0
- package/architecture/executors/visualize/executor.d.ts +17 -0
- package/architecture/executors/visualize/executor.js +49 -0
- package/architecture/executors/visualize/executor.js.map +1 -0
- package/architecture/executors/visualize/executor.ts +63 -0
- package/architecture/executors/visualize/schema.json +14 -0
- package/architecture/index.d.ts +19 -0
- package/architecture/index.js +23 -0
- package/architecture/index.js.map +1 -0
- package/architecture/index.ts +20 -0
- package/architecture/lib/graph-comparator.d.ts +39 -0
- package/architecture/lib/graph-comparator.js +100 -0
- package/architecture/lib/graph-comparator.js.map +1 -0
- package/architecture/lib/graph-comparator.ts +141 -0
- package/architecture/lib/graph-generator.d.ts +19 -0
- package/architecture/lib/graph-generator.js +88 -0
- package/architecture/lib/graph-generator.js.map +1 -0
- package/architecture/lib/graph-generator.ts +102 -0
- package/architecture/lib/graph-loader.d.ts +31 -0
- package/architecture/lib/graph-loader.js +70 -0
- package/architecture/lib/graph-loader.js.map +1 -0
- package/architecture/lib/graph-loader.ts +82 -0
- package/architecture/lib/graph-sorter.d.ts +37 -0
- package/architecture/lib/graph-sorter.js +110 -0
- package/architecture/lib/graph-sorter.js.map +1 -0
- package/architecture/lib/graph-sorter.ts +137 -0
- package/architecture/lib/graph-visualizer.d.ts +29 -0
- package/architecture/lib/graph-visualizer.js +209 -0
- package/architecture/lib/graph-visualizer.js.map +1 -0
- package/architecture/lib/graph-visualizer.ts +222 -0
- package/architecture/lib/package-validator.d.ts +38 -0
- package/architecture/lib/package-validator.js +105 -0
- package/architecture/lib/package-validator.js.map +1 -0
- package/architecture/lib/package-validator.ts +144 -0
- package/config/eslint/base.mjs +6 -0
- package/eslint-plugin/__tests__/max-file-lines.test.ts +207 -0
- package/eslint-plugin/__tests__/max-method-lines.test.ts +258 -0
- package/eslint-plugin/__tests__/no-unmanaged-exceptions.test.ts +359 -0
- package/eslint-plugin/index.d.ts +11 -0
- package/eslint-plugin/index.js +15 -0
- package/eslint-plugin/index.js.map +1 -1
- package/eslint-plugin/index.ts +15 -0
- package/eslint-plugin/rules/enforce-architecture.d.ts +15 -0
- package/eslint-plugin/rules/enforce-architecture.js +406 -0
- package/eslint-plugin/rules/enforce-architecture.js.map +1 -0
- package/eslint-plugin/rules/enforce-architecture.ts +469 -0
- package/eslint-plugin/rules/max-file-lines.d.ts +12 -0
- package/eslint-plugin/rules/max-file-lines.js +257 -0
- package/eslint-plugin/rules/max-file-lines.js.map +1 -0
- package/eslint-plugin/rules/max-file-lines.ts +272 -0
- package/eslint-plugin/rules/max-method-lines.d.ts +12 -0
- package/eslint-plugin/rules/max-method-lines.js +240 -0
- package/eslint-plugin/rules/max-method-lines.js.map +1 -0
- package/eslint-plugin/rules/max-method-lines.ts +287 -0
- package/eslint-plugin/rules/no-unmanaged-exceptions.d.ts +22 -0
- package/eslint-plugin/rules/no-unmanaged-exceptions.js +605 -0
- package/eslint-plugin/rules/no-unmanaged-exceptions.js.map +1 -0
- package/eslint-plugin/rules/no-unmanaged-exceptions.ts +621 -0
- package/executors.json +29 -0
- package/package.json +13 -3
- package/plugins/circular-deps/index.d.ts +8 -0
- package/plugins/circular-deps/index.js +14 -0
- package/plugins/circular-deps/index.js.map +1 -0
- package/plugins/circular-deps/index.ts +9 -0
- package/plugins/circular-deps/plugin.d.ts +32 -0
- package/plugins/circular-deps/plugin.js +73 -0
- package/plugins/circular-deps/plugin.js.map +1 -0
- package/plugins/circular-deps/plugin.ts +83 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ESLint rule to enforce maximum method length
|
|
4
|
+
*
|
|
5
|
+
* Enforces a configurable maximum line count for methods, functions, and arrow functions.
|
|
6
|
+
* Default: 70 lines
|
|
7
|
+
*
|
|
8
|
+
* Configuration:
|
|
9
|
+
* '@webpieces/max-method-lines': ['error', { max: 70 }]
|
|
10
|
+
*/
|
|
11
|
+
const tslib_1 = require("tslib");
|
|
12
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
13
|
+
const path = tslib_1.__importStar(require("path"));
|
|
14
|
+
const METHOD_DOC_CONTENT = `# AI Agent Instructions: Method Too Long
|
|
15
|
+
|
|
16
|
+
**READ THIS FILE to fix methods that are too long**
|
|
17
|
+
|
|
18
|
+
## Core Principle
|
|
19
|
+
Every method should read like a TABLE OF CONTENTS of a book.
|
|
20
|
+
- Each method call is a "chapter"
|
|
21
|
+
- When you dive into a method, you find another table of contents
|
|
22
|
+
- Keeping methods under 70 lines is achievable with proper extraction
|
|
23
|
+
|
|
24
|
+
## Command: Extract Code into Named Methods
|
|
25
|
+
|
|
26
|
+
### Pattern 1: Extract Loop Bodies
|
|
27
|
+
\`\`\`typescript
|
|
28
|
+
// BAD: 50 lines embedded in loop
|
|
29
|
+
for (const order of orders) {
|
|
30
|
+
// 20 lines of validation logic
|
|
31
|
+
// 15 lines of processing logic
|
|
32
|
+
// 10 lines of notification logic
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// GOOD: Extracted to named methods
|
|
36
|
+
for (const order of orders) {
|
|
37
|
+
validateOrder(order);
|
|
38
|
+
processOrderItems(order);
|
|
39
|
+
sendNotifications(order);
|
|
40
|
+
}
|
|
41
|
+
\`\`\`
|
|
42
|
+
|
|
43
|
+
### Pattern 2: Try-Catch Wrapper for Exception Handling
|
|
44
|
+
\`\`\`typescript
|
|
45
|
+
// GOOD: Separates success path from error handling
|
|
46
|
+
async function handleRequest(req: Request): Promise<Response> {
|
|
47
|
+
try {
|
|
48
|
+
return await executeRequest(req);
|
|
49
|
+
} catch (err: unknown) {
|
|
50
|
+
const error = toError(err);
|
|
51
|
+
return createErrorResponse(error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
\`\`\`
|
|
55
|
+
|
|
56
|
+
### Pattern 3: Sequential Method Calls (Table of Contents)
|
|
57
|
+
\`\`\`typescript
|
|
58
|
+
// GOOD: Self-documenting steps
|
|
59
|
+
function processOrder(order: Order): void {
|
|
60
|
+
validateOrderData(order);
|
|
61
|
+
calculateTotals(order);
|
|
62
|
+
applyDiscounts(order);
|
|
63
|
+
processPayment(order);
|
|
64
|
+
updateInventory(order);
|
|
65
|
+
sendConfirmation(order);
|
|
66
|
+
}
|
|
67
|
+
\`\`\`
|
|
68
|
+
|
|
69
|
+
### Pattern 4: Separate Data Object Creation
|
|
70
|
+
\`\`\`typescript
|
|
71
|
+
// BAD: 15 lines of inline object creation
|
|
72
|
+
doSomething({ field1: ..., field2: ..., field3: ..., /* 15 more fields */ });
|
|
73
|
+
|
|
74
|
+
// GOOD: Extract to factory method
|
|
75
|
+
const request = createRequestObject(data);
|
|
76
|
+
doSomething(request);
|
|
77
|
+
\`\`\`
|
|
78
|
+
|
|
79
|
+
### Pattern 5: Extract Inline Logic to Named Functions
|
|
80
|
+
\`\`\`typescript
|
|
81
|
+
// BAD: Complex inline logic
|
|
82
|
+
if (user.role === 'admin' && user.permissions.includes('write') && !user.suspended) {
|
|
83
|
+
// 30 lines of admin logic
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// GOOD: Extract to named methods
|
|
87
|
+
if (isAdminWithWriteAccess(user)) {
|
|
88
|
+
performAdminOperation(user);
|
|
89
|
+
}
|
|
90
|
+
\`\`\`
|
|
91
|
+
|
|
92
|
+
## AI Agent Action Steps
|
|
93
|
+
|
|
94
|
+
1. **IDENTIFY** the long method in the error message
|
|
95
|
+
2. **READ** the method to understand its logical sections
|
|
96
|
+
3. **EXTRACT** logical units into separate methods with descriptive names
|
|
97
|
+
4. **REPLACE** inline code with method calls
|
|
98
|
+
5. **VERIFY** each extracted method is <70 lines
|
|
99
|
+
6. **TEST** that functionality remains unchanged
|
|
100
|
+
|
|
101
|
+
## Examples of "Logical Units" to Extract
|
|
102
|
+
- Validation logic -> \`validateX()\`
|
|
103
|
+
- Data transformation -> \`transformXToY()\`
|
|
104
|
+
- API calls -> \`fetchXFromApi()\`
|
|
105
|
+
- Object creation -> \`createX()\`
|
|
106
|
+
- Loop bodies -> \`processItem()\`
|
|
107
|
+
- Error handling -> \`handleXError()\`
|
|
108
|
+
|
|
109
|
+
Remember: Methods should read like a table of contents. Each line should be a "chapter title" (method call) that describes what happens, not how it happens.
|
|
110
|
+
`;
|
|
111
|
+
// Module-level flag to prevent redundant file creation
|
|
112
|
+
let methodDocCreated = false;
|
|
113
|
+
function getWorkspaceRoot(context) {
|
|
114
|
+
const filename = context.filename || context.getFilename();
|
|
115
|
+
let dir = path.dirname(filename);
|
|
116
|
+
while (dir !== path.dirname(dir)) {
|
|
117
|
+
const pkgPath = path.join(dir, 'package.json');
|
|
118
|
+
if (fs.existsSync(pkgPath)) {
|
|
119
|
+
try {
|
|
120
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
121
|
+
if (pkg.workspaces || pkg.name === 'webpieces-ts') {
|
|
122
|
+
return dir;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
//const error = toError(err);
|
|
127
|
+
void err;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
dir = path.dirname(dir);
|
|
131
|
+
}
|
|
132
|
+
return process.cwd();
|
|
133
|
+
}
|
|
134
|
+
function ensureDocFile(docPath, content) {
|
|
135
|
+
try {
|
|
136
|
+
fs.mkdirSync(path.dirname(docPath), { recursive: true });
|
|
137
|
+
fs.writeFileSync(docPath, content, 'utf-8');
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
//const error = toError(err);
|
|
142
|
+
// err is used in console.warn below
|
|
143
|
+
console.warn(`[webpieces] Could not create doc file: ${docPath}`, err);
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function ensureMethodDoc(context) {
|
|
148
|
+
const workspaceRoot = getWorkspaceRoot(context);
|
|
149
|
+
const docPath = path.join(workspaceRoot, 'tmp', 'webpieces', 'webpieces.methods.md');
|
|
150
|
+
// Check if file exists AND flag is true - if both, skip
|
|
151
|
+
if (methodDocCreated && fs.existsSync(docPath))
|
|
152
|
+
return;
|
|
153
|
+
if (ensureDocFile(docPath, METHOD_DOC_CONTENT)) {
|
|
154
|
+
methodDocCreated = true;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function getFunctionName(funcNode) {
|
|
158
|
+
if (funcNode.type === 'FunctionDeclaration' && funcNode.id?.name) {
|
|
159
|
+
return funcNode.id.name;
|
|
160
|
+
}
|
|
161
|
+
if (funcNode.type === 'FunctionExpression' && funcNode.id?.name) {
|
|
162
|
+
return funcNode.id.name;
|
|
163
|
+
}
|
|
164
|
+
return 'anonymous';
|
|
165
|
+
}
|
|
166
|
+
function reportTooLong(ctx, node, name, lineCount) {
|
|
167
|
+
ctx.context.report({
|
|
168
|
+
node,
|
|
169
|
+
messageId: 'tooLong',
|
|
170
|
+
data: {
|
|
171
|
+
name,
|
|
172
|
+
actual: String(lineCount),
|
|
173
|
+
max: String(ctx.maxLines),
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function checkFunctionNode(ctx, node) {
|
|
178
|
+
ensureMethodDoc(ctx.context);
|
|
179
|
+
const funcNode = node;
|
|
180
|
+
// Skip function expressions inside method definitions
|
|
181
|
+
if (funcNode.type === 'FunctionExpression' && funcNode['parent']?.type === 'MethodDefinition') {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (!funcNode.loc || !funcNode.body)
|
|
185
|
+
return;
|
|
186
|
+
const name = getFunctionName(funcNode);
|
|
187
|
+
const lineCount = funcNode.loc.end.line - funcNode.loc.start.line + 1;
|
|
188
|
+
if (lineCount > ctx.maxLines) {
|
|
189
|
+
reportTooLong(ctx, funcNode, name, lineCount);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function checkMethodNode(ctx, node) {
|
|
193
|
+
ensureMethodDoc(ctx.context);
|
|
194
|
+
if (!node.loc || !node.value)
|
|
195
|
+
return;
|
|
196
|
+
const name = node.key?.name || 'anonymous';
|
|
197
|
+
const lineCount = node.loc.end.line - node.loc.start.line + 1;
|
|
198
|
+
if (lineCount > ctx.maxLines) {
|
|
199
|
+
reportTooLong(ctx, node, name, lineCount);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const rule = {
|
|
203
|
+
meta: {
|
|
204
|
+
type: 'suggestion',
|
|
205
|
+
docs: {
|
|
206
|
+
description: 'Enforce maximum method length',
|
|
207
|
+
category: 'Best Practices',
|
|
208
|
+
recommended: false,
|
|
209
|
+
url: 'https://github.com/deanhiller/webpieces-ts',
|
|
210
|
+
},
|
|
211
|
+
messages: {
|
|
212
|
+
tooLong: 'AI Agent: READ tmp/webpieces/webpieces.methods.md for fix instructions. Method "{{name}}" has {{actual}} lines (max: {{max}})',
|
|
213
|
+
},
|
|
214
|
+
fixable: undefined,
|
|
215
|
+
schema: [
|
|
216
|
+
{
|
|
217
|
+
type: 'object',
|
|
218
|
+
properties: {
|
|
219
|
+
max: {
|
|
220
|
+
type: 'integer',
|
|
221
|
+
minimum: 1,
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
additionalProperties: false,
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
},
|
|
228
|
+
create(context) {
|
|
229
|
+
const options = context.options[0];
|
|
230
|
+
const ctx = { context, maxLines: options?.max ?? 70 };
|
|
231
|
+
return {
|
|
232
|
+
FunctionDeclaration: (node) => checkFunctionNode(ctx, node),
|
|
233
|
+
FunctionExpression: (node) => checkFunctionNode(ctx, node),
|
|
234
|
+
ArrowFunctionExpression: (node) => checkFunctionNode(ctx, node),
|
|
235
|
+
MethodDefinition: (node) => checkMethodNode(ctx, node),
|
|
236
|
+
};
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
module.exports = rule;
|
|
240
|
+
//# sourceMappingURL=max-method-lines.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"max-method-lines.js","sourceRoot":"","sources":["../../../../../../packages/tooling/dev-config/eslint-plugin/rules/max-method-lines.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ESLint rule to enforce maximum method length\n *\n * Enforces a configurable maximum line count for methods, functions, and arrow functions.\n * Default: 70 lines\n *\n * Configuration:\n * '@webpieces/max-method-lines': ['error', { max: 70 }]\n */\n\nimport type { Rule } from 'eslint';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\ninterface MethodLinesOptions {\n max: number;\n}\n\ninterface FunctionNode {\n type:\n | 'FunctionDeclaration'\n | 'FunctionExpression'\n | 'ArrowFunctionExpression'\n | 'MethodDefinition';\n body?: any;\n loc?: {\n start: { line: number };\n end: { line: number };\n };\n key?: {\n name?: string;\n };\n id?: {\n name?: string;\n };\n [key: string]: any;\n}\n\ninterface CheckerContext {\n context: Rule.RuleContext;\n maxLines: number;\n}\n\nconst METHOD_DOC_CONTENT = `# AI Agent Instructions: Method Too Long\n\n**READ THIS FILE to fix methods that are too long**\n\n## Core Principle\nEvery method should read like a TABLE OF CONTENTS of a book.\n- Each method call is a \"chapter\"\n- When you dive into a method, you find another table of contents\n- Keeping methods under 70 lines is achievable with proper extraction\n\n## Command: Extract Code into Named Methods\n\n### Pattern 1: Extract Loop Bodies\n\\`\\`\\`typescript\n// BAD: 50 lines embedded in loop\nfor (const order of orders) {\n // 20 lines of validation logic\n // 15 lines of processing logic\n // 10 lines of notification logic\n}\n\n// GOOD: Extracted to named methods\nfor (const order of orders) {\n validateOrder(order);\n processOrderItems(order);\n sendNotifications(order);\n}\n\\`\\`\\`\n\n### Pattern 2: Try-Catch Wrapper for Exception Handling\n\\`\\`\\`typescript\n// GOOD: Separates success path from error handling\nasync function handleRequest(req: Request): Promise<Response> {\n try {\n return await executeRequest(req);\n } catch (err: unknown) {\n const error = toError(err);\n return createErrorResponse(error);\n }\n}\n\\`\\`\\`\n\n### Pattern 3: Sequential Method Calls (Table of Contents)\n\\`\\`\\`typescript\n// GOOD: Self-documenting steps\nfunction processOrder(order: Order): void {\n validateOrderData(order);\n calculateTotals(order);\n applyDiscounts(order);\n processPayment(order);\n updateInventory(order);\n sendConfirmation(order);\n}\n\\`\\`\\`\n\n### Pattern 4: Separate Data Object Creation\n\\`\\`\\`typescript\n// BAD: 15 lines of inline object creation\ndoSomething({ field1: ..., field2: ..., field3: ..., /* 15 more fields */ });\n\n// GOOD: Extract to factory method\nconst request = createRequestObject(data);\ndoSomething(request);\n\\`\\`\\`\n\n### Pattern 5: Extract Inline Logic to Named Functions\n\\`\\`\\`typescript\n// BAD: Complex inline logic\nif (user.role === 'admin' && user.permissions.includes('write') && !user.suspended) {\n // 30 lines of admin logic\n}\n\n// GOOD: Extract to named methods\nif (isAdminWithWriteAccess(user)) {\n performAdminOperation(user);\n}\n\\`\\`\\`\n\n## AI Agent Action Steps\n\n1. **IDENTIFY** the long method in the error message\n2. **READ** the method to understand its logical sections\n3. **EXTRACT** logical units into separate methods with descriptive names\n4. **REPLACE** inline code with method calls\n5. **VERIFY** each extracted method is <70 lines\n6. **TEST** that functionality remains unchanged\n\n## Examples of \"Logical Units\" to Extract\n- Validation logic -> \\`validateX()\\`\n- Data transformation -> \\`transformXToY()\\`\n- API calls -> \\`fetchXFromApi()\\`\n- Object creation -> \\`createX()\\`\n- Loop bodies -> \\`processItem()\\`\n- Error handling -> \\`handleXError()\\`\n\nRemember: Methods should read like a table of contents. Each line should be a \"chapter title\" (method call) that describes what happens, not how it happens.\n`;\n\n// Module-level flag to prevent redundant file creation\nlet methodDocCreated = false;\n\nfunction getWorkspaceRoot(context: Rule.RuleContext): string {\n const filename = context.filename || context.getFilename();\n let dir = path.dirname(filename);\n\n while (dir !== path.dirname(dir)) {\n const pkgPath = path.join(dir, 'package.json');\n if (fs.existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));\n if (pkg.workspaces || pkg.name === 'webpieces-ts') {\n return dir;\n }\n } catch (err: any) {\n //const error = toError(err);\n void err;\n }\n }\n dir = path.dirname(dir);\n }\n return process.cwd();\n}\n\nfunction ensureDocFile(docPath: string, content: string): boolean {\n try {\n fs.mkdirSync(path.dirname(docPath), { recursive: true });\n fs.writeFileSync(docPath, content, 'utf-8');\n return true;\n } catch (err: any) {\n //const error = toError(err);\n // err is used in console.warn below\n console.warn(`[webpieces] Could not create doc file: ${docPath}`, err);\n return false;\n }\n}\n\nfunction ensureMethodDoc(context: Rule.RuleContext): void {\n const workspaceRoot = getWorkspaceRoot(context);\n const docPath = path.join(workspaceRoot, 'tmp', 'webpieces', 'webpieces.methods.md');\n\n // Check if file exists AND flag is true - if both, skip\n if (methodDocCreated && fs.existsSync(docPath)) return;\n\n if (ensureDocFile(docPath, METHOD_DOC_CONTENT)) {\n methodDocCreated = true;\n }\n}\n\nfunction getFunctionName(funcNode: FunctionNode): string {\n if (funcNode.type === 'FunctionDeclaration' && funcNode.id?.name) {\n return funcNode.id.name;\n }\n if (funcNode.type === 'FunctionExpression' && funcNode.id?.name) {\n return funcNode.id.name;\n }\n return 'anonymous';\n}\n\nfunction reportTooLong(ctx: CheckerContext, node: any, name: string, lineCount: number): void {\n ctx.context.report({\n node,\n messageId: 'tooLong',\n data: {\n name,\n actual: String(lineCount),\n max: String(ctx.maxLines),\n },\n });\n}\n\nfunction checkFunctionNode(ctx: CheckerContext, node: any): void {\n ensureMethodDoc(ctx.context);\n const funcNode = node as FunctionNode;\n\n // Skip function expressions inside method definitions\n if (funcNode.type === 'FunctionExpression' && funcNode['parent']?.type === 'MethodDefinition') {\n return;\n }\n\n if (!funcNode.loc || !funcNode.body) return;\n\n const name = getFunctionName(funcNode);\n const lineCount = funcNode.loc.end.line - funcNode.loc.start.line + 1;\n\n if (lineCount > ctx.maxLines) {\n reportTooLong(ctx, funcNode, name, lineCount);\n }\n}\n\nfunction checkMethodNode(ctx: CheckerContext, node: any): void {\n ensureMethodDoc(ctx.context);\n\n if (!node.loc || !node.value) return;\n\n const name = node.key?.name || 'anonymous';\n const lineCount = node.loc.end.line - node.loc.start.line + 1;\n\n if (lineCount > ctx.maxLines) {\n reportTooLong(ctx, node, name, lineCount);\n }\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Enforce maximum method length',\n category: 'Best Practices',\n recommended: false,\n url: 'https://github.com/deanhiller/webpieces-ts',\n },\n messages: {\n tooLong:\n 'AI Agent: READ tmp/webpieces/webpieces.methods.md for fix instructions. Method \"{{name}}\" has {{actual}} lines (max: {{max}})',\n },\n fixable: undefined,\n schema: [\n {\n type: 'object',\n properties: {\n max: {\n type: 'integer',\n minimum: 1,\n },\n },\n additionalProperties: false,\n },\n ],\n },\n\n create(context: Rule.RuleContext): Rule.RuleListener {\n const options = context.options[0] as MethodLinesOptions | undefined;\n const ctx: CheckerContext = { context, maxLines: options?.max ?? 70 };\n\n return {\n FunctionDeclaration: (node) => checkFunctionNode(ctx, node),\n FunctionExpression: (node) => checkFunctionNode(ctx, node),\n ArrowFunctionExpression: (node) => checkFunctionNode(ctx, node),\n MethodDefinition: (node) => checkMethodNode(ctx, node),\n };\n },\n};\n\nexport = rule;\n"]}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint rule to enforce maximum method length
|
|
3
|
+
*
|
|
4
|
+
* Enforces a configurable maximum line count for methods, functions, and arrow functions.
|
|
5
|
+
* Default: 70 lines
|
|
6
|
+
*
|
|
7
|
+
* Configuration:
|
|
8
|
+
* '@webpieces/max-method-lines': ['error', { max: 70 }]
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Rule } from 'eslint';
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
|
|
15
|
+
interface MethodLinesOptions {
|
|
16
|
+
max: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface FunctionNode {
|
|
20
|
+
type:
|
|
21
|
+
| 'FunctionDeclaration'
|
|
22
|
+
| 'FunctionExpression'
|
|
23
|
+
| 'ArrowFunctionExpression'
|
|
24
|
+
| 'MethodDefinition';
|
|
25
|
+
body?: any;
|
|
26
|
+
loc?: {
|
|
27
|
+
start: { line: number };
|
|
28
|
+
end: { line: number };
|
|
29
|
+
};
|
|
30
|
+
key?: {
|
|
31
|
+
name?: string;
|
|
32
|
+
};
|
|
33
|
+
id?: {
|
|
34
|
+
name?: string;
|
|
35
|
+
};
|
|
36
|
+
[key: string]: any;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface CheckerContext {
|
|
40
|
+
context: Rule.RuleContext;
|
|
41
|
+
maxLines: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const METHOD_DOC_CONTENT = `# AI Agent Instructions: Method Too Long
|
|
45
|
+
|
|
46
|
+
**READ THIS FILE to fix methods that are too long**
|
|
47
|
+
|
|
48
|
+
## Core Principle
|
|
49
|
+
Every method should read like a TABLE OF CONTENTS of a book.
|
|
50
|
+
- Each method call is a "chapter"
|
|
51
|
+
- When you dive into a method, you find another table of contents
|
|
52
|
+
- Keeping methods under 70 lines is achievable with proper extraction
|
|
53
|
+
|
|
54
|
+
## Command: Extract Code into Named Methods
|
|
55
|
+
|
|
56
|
+
### Pattern 1: Extract Loop Bodies
|
|
57
|
+
\`\`\`typescript
|
|
58
|
+
// BAD: 50 lines embedded in loop
|
|
59
|
+
for (const order of orders) {
|
|
60
|
+
// 20 lines of validation logic
|
|
61
|
+
// 15 lines of processing logic
|
|
62
|
+
// 10 lines of notification logic
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// GOOD: Extracted to named methods
|
|
66
|
+
for (const order of orders) {
|
|
67
|
+
validateOrder(order);
|
|
68
|
+
processOrderItems(order);
|
|
69
|
+
sendNotifications(order);
|
|
70
|
+
}
|
|
71
|
+
\`\`\`
|
|
72
|
+
|
|
73
|
+
### Pattern 2: Try-Catch Wrapper for Exception Handling
|
|
74
|
+
\`\`\`typescript
|
|
75
|
+
// GOOD: Separates success path from error handling
|
|
76
|
+
async function handleRequest(req: Request): Promise<Response> {
|
|
77
|
+
try {
|
|
78
|
+
return await executeRequest(req);
|
|
79
|
+
} catch (err: unknown) {
|
|
80
|
+
const error = toError(err);
|
|
81
|
+
return createErrorResponse(error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
\`\`\`
|
|
85
|
+
|
|
86
|
+
### Pattern 3: Sequential Method Calls (Table of Contents)
|
|
87
|
+
\`\`\`typescript
|
|
88
|
+
// GOOD: Self-documenting steps
|
|
89
|
+
function processOrder(order: Order): void {
|
|
90
|
+
validateOrderData(order);
|
|
91
|
+
calculateTotals(order);
|
|
92
|
+
applyDiscounts(order);
|
|
93
|
+
processPayment(order);
|
|
94
|
+
updateInventory(order);
|
|
95
|
+
sendConfirmation(order);
|
|
96
|
+
}
|
|
97
|
+
\`\`\`
|
|
98
|
+
|
|
99
|
+
### Pattern 4: Separate Data Object Creation
|
|
100
|
+
\`\`\`typescript
|
|
101
|
+
// BAD: 15 lines of inline object creation
|
|
102
|
+
doSomething({ field1: ..., field2: ..., field3: ..., /* 15 more fields */ });
|
|
103
|
+
|
|
104
|
+
// GOOD: Extract to factory method
|
|
105
|
+
const request = createRequestObject(data);
|
|
106
|
+
doSomething(request);
|
|
107
|
+
\`\`\`
|
|
108
|
+
|
|
109
|
+
### Pattern 5: Extract Inline Logic to Named Functions
|
|
110
|
+
\`\`\`typescript
|
|
111
|
+
// BAD: Complex inline logic
|
|
112
|
+
if (user.role === 'admin' && user.permissions.includes('write') && !user.suspended) {
|
|
113
|
+
// 30 lines of admin logic
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// GOOD: Extract to named methods
|
|
117
|
+
if (isAdminWithWriteAccess(user)) {
|
|
118
|
+
performAdminOperation(user);
|
|
119
|
+
}
|
|
120
|
+
\`\`\`
|
|
121
|
+
|
|
122
|
+
## AI Agent Action Steps
|
|
123
|
+
|
|
124
|
+
1. **IDENTIFY** the long method in the error message
|
|
125
|
+
2. **READ** the method to understand its logical sections
|
|
126
|
+
3. **EXTRACT** logical units into separate methods with descriptive names
|
|
127
|
+
4. **REPLACE** inline code with method calls
|
|
128
|
+
5. **VERIFY** each extracted method is <70 lines
|
|
129
|
+
6. **TEST** that functionality remains unchanged
|
|
130
|
+
|
|
131
|
+
## Examples of "Logical Units" to Extract
|
|
132
|
+
- Validation logic -> \`validateX()\`
|
|
133
|
+
- Data transformation -> \`transformXToY()\`
|
|
134
|
+
- API calls -> \`fetchXFromApi()\`
|
|
135
|
+
- Object creation -> \`createX()\`
|
|
136
|
+
- Loop bodies -> \`processItem()\`
|
|
137
|
+
- Error handling -> \`handleXError()\`
|
|
138
|
+
|
|
139
|
+
Remember: Methods should read like a table of contents. Each line should be a "chapter title" (method call) that describes what happens, not how it happens.
|
|
140
|
+
`;
|
|
141
|
+
|
|
142
|
+
// Module-level flag to prevent redundant file creation
|
|
143
|
+
let methodDocCreated = false;
|
|
144
|
+
|
|
145
|
+
function getWorkspaceRoot(context: Rule.RuleContext): string {
|
|
146
|
+
const filename = context.filename || context.getFilename();
|
|
147
|
+
let dir = path.dirname(filename);
|
|
148
|
+
|
|
149
|
+
while (dir !== path.dirname(dir)) {
|
|
150
|
+
const pkgPath = path.join(dir, 'package.json');
|
|
151
|
+
if (fs.existsSync(pkgPath)) {
|
|
152
|
+
try {
|
|
153
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
154
|
+
if (pkg.workspaces || pkg.name === 'webpieces-ts') {
|
|
155
|
+
return dir;
|
|
156
|
+
}
|
|
157
|
+
} catch (err: any) {
|
|
158
|
+
//const error = toError(err);
|
|
159
|
+
void err;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
dir = path.dirname(dir);
|
|
163
|
+
}
|
|
164
|
+
return process.cwd();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function ensureDocFile(docPath: string, content: string): boolean {
|
|
168
|
+
try {
|
|
169
|
+
fs.mkdirSync(path.dirname(docPath), { recursive: true });
|
|
170
|
+
fs.writeFileSync(docPath, content, 'utf-8');
|
|
171
|
+
return true;
|
|
172
|
+
} catch (err: any) {
|
|
173
|
+
//const error = toError(err);
|
|
174
|
+
// err is used in console.warn below
|
|
175
|
+
console.warn(`[webpieces] Could not create doc file: ${docPath}`, err);
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function ensureMethodDoc(context: Rule.RuleContext): void {
|
|
181
|
+
const workspaceRoot = getWorkspaceRoot(context);
|
|
182
|
+
const docPath = path.join(workspaceRoot, 'tmp', 'webpieces', 'webpieces.methods.md');
|
|
183
|
+
|
|
184
|
+
// Check if file exists AND flag is true - if both, skip
|
|
185
|
+
if (methodDocCreated && fs.existsSync(docPath)) return;
|
|
186
|
+
|
|
187
|
+
if (ensureDocFile(docPath, METHOD_DOC_CONTENT)) {
|
|
188
|
+
methodDocCreated = true;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function getFunctionName(funcNode: FunctionNode): string {
|
|
193
|
+
if (funcNode.type === 'FunctionDeclaration' && funcNode.id?.name) {
|
|
194
|
+
return funcNode.id.name;
|
|
195
|
+
}
|
|
196
|
+
if (funcNode.type === 'FunctionExpression' && funcNode.id?.name) {
|
|
197
|
+
return funcNode.id.name;
|
|
198
|
+
}
|
|
199
|
+
return 'anonymous';
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function reportTooLong(ctx: CheckerContext, node: any, name: string, lineCount: number): void {
|
|
203
|
+
ctx.context.report({
|
|
204
|
+
node,
|
|
205
|
+
messageId: 'tooLong',
|
|
206
|
+
data: {
|
|
207
|
+
name,
|
|
208
|
+
actual: String(lineCount),
|
|
209
|
+
max: String(ctx.maxLines),
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function checkFunctionNode(ctx: CheckerContext, node: any): void {
|
|
215
|
+
ensureMethodDoc(ctx.context);
|
|
216
|
+
const funcNode = node as FunctionNode;
|
|
217
|
+
|
|
218
|
+
// Skip function expressions inside method definitions
|
|
219
|
+
if (funcNode.type === 'FunctionExpression' && funcNode['parent']?.type === 'MethodDefinition') {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!funcNode.loc || !funcNode.body) return;
|
|
224
|
+
|
|
225
|
+
const name = getFunctionName(funcNode);
|
|
226
|
+
const lineCount = funcNode.loc.end.line - funcNode.loc.start.line + 1;
|
|
227
|
+
|
|
228
|
+
if (lineCount > ctx.maxLines) {
|
|
229
|
+
reportTooLong(ctx, funcNode, name, lineCount);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function checkMethodNode(ctx: CheckerContext, node: any): void {
|
|
234
|
+
ensureMethodDoc(ctx.context);
|
|
235
|
+
|
|
236
|
+
if (!node.loc || !node.value) return;
|
|
237
|
+
|
|
238
|
+
const name = node.key?.name || 'anonymous';
|
|
239
|
+
const lineCount = node.loc.end.line - node.loc.start.line + 1;
|
|
240
|
+
|
|
241
|
+
if (lineCount > ctx.maxLines) {
|
|
242
|
+
reportTooLong(ctx, node, name, lineCount);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const rule: Rule.RuleModule = {
|
|
247
|
+
meta: {
|
|
248
|
+
type: 'suggestion',
|
|
249
|
+
docs: {
|
|
250
|
+
description: 'Enforce maximum method length',
|
|
251
|
+
category: 'Best Practices',
|
|
252
|
+
recommended: false,
|
|
253
|
+
url: 'https://github.com/deanhiller/webpieces-ts',
|
|
254
|
+
},
|
|
255
|
+
messages: {
|
|
256
|
+
tooLong:
|
|
257
|
+
'AI Agent: READ tmp/webpieces/webpieces.methods.md for fix instructions. Method "{{name}}" has {{actual}} lines (max: {{max}})',
|
|
258
|
+
},
|
|
259
|
+
fixable: undefined,
|
|
260
|
+
schema: [
|
|
261
|
+
{
|
|
262
|
+
type: 'object',
|
|
263
|
+
properties: {
|
|
264
|
+
max: {
|
|
265
|
+
type: 'integer',
|
|
266
|
+
minimum: 1,
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
additionalProperties: false,
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
create(context: Rule.RuleContext): Rule.RuleListener {
|
|
275
|
+
const options = context.options[0] as MethodLinesOptions | undefined;
|
|
276
|
+
const ctx: CheckerContext = { context, maxLines: options?.max ?? 70 };
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
FunctionDeclaration: (node) => checkFunctionNode(ctx, node),
|
|
280
|
+
FunctionExpression: (node) => checkFunctionNode(ctx, node),
|
|
281
|
+
ArrowFunctionExpression: (node) => checkFunctionNode(ctx, node),
|
|
282
|
+
MethodDefinition: (node) => checkMethodNode(ctx, node),
|
|
283
|
+
};
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
export = rule;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint rule to discourage try-catch blocks outside test files
|
|
3
|
+
*
|
|
4
|
+
* Works alongside catch-error-pattern rule:
|
|
5
|
+
* - catch-error-pattern: Enforces HOW to handle exceptions (with toError())
|
|
6
|
+
* - no-unmanaged-exceptions: Enforces WHERE try-catch is allowed (tests only by default)
|
|
7
|
+
*
|
|
8
|
+
* Philosophy: Exceptions should bubble to global error handlers where they are logged
|
|
9
|
+
* with traceId and stored for debugging via /debugLocal and /debugCloud endpoints.
|
|
10
|
+
* Local try-catch blocks break this architecture and create blind spots in production.
|
|
11
|
+
*
|
|
12
|
+
* Auto-allowed in:
|
|
13
|
+
* - Test files (.test.ts, .spec.ts, __tests__/)
|
|
14
|
+
*
|
|
15
|
+
* Requires eslint-disable comment in:
|
|
16
|
+
* - Retry loops with exponential backoff
|
|
17
|
+
* - Batch processing where partial failure is expected
|
|
18
|
+
* - Resource cleanup (with approval)
|
|
19
|
+
*/
|
|
20
|
+
import type { Rule } from 'eslint';
|
|
21
|
+
declare const rule: Rule.RuleModule;
|
|
22
|
+
export = rule;
|