@webpieces/dev-config 0.0.0-dev → 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.
Files changed (86) hide show
  1. package/architecture/executors/generate/executor.d.ts +17 -0
  2. package/architecture/executors/generate/executor.js +67 -0
  3. package/architecture/executors/generate/executor.js.map +1 -0
  4. package/architecture/executors/generate/executor.ts +83 -0
  5. package/architecture/executors/generate/schema.json +14 -0
  6. package/architecture/executors/validate-architecture-unchanged/executor.d.ts +17 -0
  7. package/architecture/executors/validate-architecture-unchanged/executor.js +65 -0
  8. package/architecture/executors/validate-architecture-unchanged/executor.js.map +1 -0
  9. package/architecture/executors/validate-architecture-unchanged/executor.ts +81 -0
  10. package/architecture/executors/validate-architecture-unchanged/schema.json +14 -0
  11. package/architecture/executors/validate-no-cycles/executor.d.ts +16 -0
  12. package/architecture/executors/validate-no-cycles/executor.js +48 -0
  13. package/architecture/executors/validate-no-cycles/executor.js.map +1 -0
  14. package/architecture/executors/validate-no-cycles/executor.ts +60 -0
  15. package/architecture/executors/validate-no-cycles/schema.json +8 -0
  16. package/architecture/executors/validate-no-skiplevel-deps/executor.d.ts +19 -0
  17. package/architecture/executors/validate-no-skiplevel-deps/executor.js +227 -0
  18. package/architecture/executors/validate-no-skiplevel-deps/executor.js.map +1 -0
  19. package/architecture/executors/validate-no-skiplevel-deps/executor.ts +267 -0
  20. package/architecture/executors/validate-no-skiplevel-deps/schema.json +8 -0
  21. package/architecture/executors/visualize/executor.d.ts +17 -0
  22. package/architecture/executors/visualize/executor.js +49 -0
  23. package/architecture/executors/visualize/executor.js.map +1 -0
  24. package/architecture/executors/visualize/executor.ts +63 -0
  25. package/architecture/executors/visualize/schema.json +14 -0
  26. package/architecture/index.d.ts +19 -0
  27. package/architecture/index.js +23 -0
  28. package/architecture/index.js.map +1 -0
  29. package/architecture/index.ts +20 -0
  30. package/architecture/lib/graph-comparator.d.ts +39 -0
  31. package/architecture/lib/graph-comparator.js +100 -0
  32. package/architecture/lib/graph-comparator.js.map +1 -0
  33. package/architecture/lib/graph-comparator.ts +141 -0
  34. package/architecture/lib/graph-generator.d.ts +19 -0
  35. package/architecture/lib/graph-generator.js +88 -0
  36. package/architecture/lib/graph-generator.js.map +1 -0
  37. package/architecture/lib/graph-generator.ts +102 -0
  38. package/architecture/lib/graph-loader.d.ts +31 -0
  39. package/architecture/lib/graph-loader.js +70 -0
  40. package/architecture/lib/graph-loader.js.map +1 -0
  41. package/architecture/lib/graph-loader.ts +82 -0
  42. package/architecture/lib/graph-sorter.d.ts +37 -0
  43. package/architecture/lib/graph-sorter.js +110 -0
  44. package/architecture/lib/graph-sorter.js.map +1 -0
  45. package/architecture/lib/graph-sorter.ts +137 -0
  46. package/architecture/lib/graph-visualizer.d.ts +29 -0
  47. package/architecture/lib/graph-visualizer.js +209 -0
  48. package/architecture/lib/graph-visualizer.js.map +1 -0
  49. package/architecture/lib/graph-visualizer.ts +222 -0
  50. package/architecture/lib/package-validator.d.ts +38 -0
  51. package/architecture/lib/package-validator.js +105 -0
  52. package/architecture/lib/package-validator.js.map +1 -0
  53. package/architecture/lib/package-validator.ts +144 -0
  54. package/config/eslint/base.mjs +6 -0
  55. package/eslint-plugin/__tests__/catch-error-pattern.test.ts +0 -1
  56. package/eslint-plugin/__tests__/max-file-lines.test.ts +29 -17
  57. package/eslint-plugin/__tests__/max-method-lines.test.ts +27 -15
  58. package/eslint-plugin/__tests__/no-unmanaged-exceptions.test.ts +359 -0
  59. package/eslint-plugin/index.d.ts +9 -0
  60. package/eslint-plugin/index.js +11 -0
  61. package/eslint-plugin/index.js.map +1 -1
  62. package/eslint-plugin/index.ts +11 -0
  63. package/eslint-plugin/rules/enforce-architecture.d.ts +15 -0
  64. package/eslint-plugin/rules/enforce-architecture.js +406 -0
  65. package/eslint-plugin/rules/enforce-architecture.js.map +1 -0
  66. package/eslint-plugin/rules/enforce-architecture.ts +469 -0
  67. package/eslint-plugin/rules/max-file-lines.js +11 -11
  68. package/eslint-plugin/rules/max-file-lines.js.map +1 -1
  69. package/eslint-plugin/rules/max-file-lines.ts +11 -11
  70. package/eslint-plugin/rules/max-method-lines.js +71 -88
  71. package/eslint-plugin/rules/max-method-lines.js.map +1 -1
  72. package/eslint-plugin/rules/max-method-lines.ts +85 -102
  73. package/eslint-plugin/rules/no-unmanaged-exceptions.d.ts +22 -0
  74. package/eslint-plugin/rules/no-unmanaged-exceptions.js +605 -0
  75. package/eslint-plugin/rules/no-unmanaged-exceptions.js.map +1 -0
  76. package/eslint-plugin/rules/no-unmanaged-exceptions.ts +621 -0
  77. package/executors.json +29 -0
  78. package/package.json +13 -7
  79. package/plugins/circular-deps/index.d.ts +8 -0
  80. package/plugins/circular-deps/index.js +14 -0
  81. package/plugins/circular-deps/index.js.map +1 -0
  82. package/plugins/circular-deps/index.ts +9 -0
  83. package/plugins/circular-deps/plugin.d.ts +32 -0
  84. package/plugins/circular-deps/plugin.js +73 -0
  85. package/plugins/circular-deps/plugin.js.map +1 -0
  86. package/plugins/circular-deps/plugin.ts +83 -0
@@ -25,14 +25,14 @@ Every method should read like a TABLE OF CONTENTS of a book.
25
25
 
26
26
  ### Pattern 1: Extract Loop Bodies
27
27
  \`\`\`typescript
28
- // BAD: 50 lines embedded in loop
28
+ // BAD: 50 lines embedded in loop
29
29
  for (const order of orders) {
30
30
  // 20 lines of validation logic
31
31
  // 15 lines of processing logic
32
32
  // 10 lines of notification logic
33
33
  }
34
34
 
35
- // GOOD: Extracted to named methods
35
+ // GOOD: Extracted to named methods
36
36
  for (const order of orders) {
37
37
  validateOrder(order);
38
38
  processOrderItems(order);
@@ -42,11 +42,11 @@ for (const order of orders) {
42
42
 
43
43
  ### Pattern 2: Try-Catch Wrapper for Exception Handling
44
44
  \`\`\`typescript
45
- // GOOD: Separates success path from error handling
45
+ // GOOD: Separates success path from error handling
46
46
  async function handleRequest(req: Request): Promise<Response> {
47
47
  try {
48
48
  return await executeRequest(req);
49
- } catch (err: any) {
49
+ } catch (err: unknown) {
50
50
  const error = toError(err);
51
51
  return createErrorResponse(error);
52
52
  }
@@ -55,7 +55,7 @@ async function handleRequest(req: Request): Promise<Response> {
55
55
 
56
56
  ### Pattern 3: Sequential Method Calls (Table of Contents)
57
57
  \`\`\`typescript
58
- // GOOD: Self-documenting steps
58
+ // GOOD: Self-documenting steps
59
59
  function processOrder(order: Order): void {
60
60
  validateOrderData(order);
61
61
  calculateTotals(order);
@@ -68,22 +68,22 @@ function processOrder(order: Order): void {
68
68
 
69
69
  ### Pattern 4: Separate Data Object Creation
70
70
  \`\`\`typescript
71
- // BAD: 15 lines of inline object creation
71
+ // BAD: 15 lines of inline object creation
72
72
  doSomething({ field1: ..., field2: ..., field3: ..., /* 15 more fields */ });
73
73
 
74
- // GOOD: Extract to factory method
74
+ // GOOD: Extract to factory method
75
75
  const request = createRequestObject(data);
76
76
  doSomething(request);
77
77
  \`\`\`
78
78
 
79
79
  ### Pattern 5: Extract Inline Logic to Named Functions
80
80
  \`\`\`typescript
81
- // BAD: Complex inline logic
81
+ // BAD: Complex inline logic
82
82
  if (user.role === 'admin' && user.permissions.includes('write') && !user.suspended) {
83
83
  // 30 lines of admin logic
84
84
  }
85
85
 
86
- // GOOD: Extract to named methods
86
+ // GOOD: Extract to named methods
87
87
  if (isAdminWithWriteAccess(user)) {
88
88
  performAdminOperation(user);
89
89
  }
@@ -99,12 +99,12 @@ if (isAdminWithWriteAccess(user)) {
99
99
  6. **TEST** that functionality remains unchanged
100
100
 
101
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()\`
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
108
 
109
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
110
  `;
@@ -113,7 +113,6 @@ let methodDocCreated = false;
113
113
  function getWorkspaceRoot(context) {
114
114
  const filename = context.filename || context.getFilename();
115
115
  let dir = path.dirname(filename);
116
- // Walk up directory tree to find workspace root
117
116
  while (dir !== path.dirname(dir)) {
118
117
  const pkgPath = path.join(dir, 'package.json');
119
118
  if (fs.existsSync(pkgPath)) {
@@ -125,12 +124,12 @@ function getWorkspaceRoot(context) {
125
124
  }
126
125
  catch (err) {
127
126
  //const error = toError(err);
128
- // Continue searching if JSON parse fails
127
+ void err;
129
128
  }
130
129
  }
131
130
  dir = path.dirname(dir);
132
131
  }
133
- return process.cwd(); // Fallback
132
+ return process.cwd();
134
133
  }
135
134
  function ensureDocFile(docPath, content) {
136
135
  try {
@@ -140,20 +139,66 @@ function ensureDocFile(docPath, content) {
140
139
  }
141
140
  catch (err) {
142
141
  //const error = toError(err);
143
- // Graceful degradation: log warning but don't break lint
142
+ // err is used in console.warn below
144
143
  console.warn(`[webpieces] Could not create doc file: ${docPath}`, err);
145
144
  return false;
146
145
  }
147
146
  }
148
147
  function ensureMethodDoc(context) {
149
- if (methodDocCreated)
150
- return; // Performance: only create once per lint run
151
148
  const workspaceRoot = getWorkspaceRoot(context);
152
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
153
  if (ensureDocFile(docPath, METHOD_DOC_CONTENT)) {
154
154
  methodDocCreated = true;
155
155
  }
156
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
+ }
157
202
  const rule = {
158
203
  meta: {
159
204
  type: 'suggestion',
@@ -182,74 +227,12 @@ const rule = {
182
227
  },
183
228
  create(context) {
184
229
  const options = context.options[0];
185
- const maxLines = options?.max ?? 70;
186
- function checkFunction(node) {
187
- ensureMethodDoc(context);
188
- const funcNode = node;
189
- // Skip if this is a function expression that's part of a method definition
190
- // (method definitions will be handled by checkMethod)
191
- if (funcNode.type === 'FunctionExpression' &&
192
- funcNode['parent']?.type === 'MethodDefinition') {
193
- return;
194
- }
195
- // Skip if no location info or no body
196
- if (!funcNode.loc || !funcNode.body) {
197
- return;
198
- }
199
- // Get function name
200
- let name = 'anonymous';
201
- if (funcNode.type === 'FunctionDeclaration' && funcNode.id?.name) {
202
- name = funcNode.id.name;
203
- }
204
- else if (funcNode.type === 'FunctionExpression' && funcNode.id?.name) {
205
- name = funcNode.id.name;
206
- }
207
- // Calculate line count
208
- const startLine = funcNode.loc.start.line;
209
- const endLine = funcNode.loc.end.line;
210
- const lineCount = endLine - startLine + 1;
211
- if (lineCount > maxLines) {
212
- context.report({
213
- node: funcNode,
214
- messageId: 'tooLong',
215
- data: {
216
- name,
217
- actual: String(lineCount),
218
- max: String(maxLines),
219
- },
220
- });
221
- }
222
- }
223
- function checkMethod(node) {
224
- ensureMethodDoc(context);
225
- const methodNode = node;
226
- // Skip if no location info
227
- if (!methodNode.loc || !methodNode.value) {
228
- return;
229
- }
230
- // Get method name from key
231
- const name = methodNode.key?.name || 'anonymous';
232
- // Calculate line count for the method (including the method definition)
233
- const startLine = methodNode.loc.start.line;
234
- const endLine = methodNode.loc.end.line;
235
- const lineCount = endLine - startLine + 1;
236
- if (lineCount > maxLines) {
237
- context.report({
238
- node: methodNode,
239
- messageId: 'tooLong',
240
- data: {
241
- name,
242
- actual: String(lineCount),
243
- max: String(maxLines),
244
- },
245
- });
246
- }
247
- }
230
+ const ctx = { context, maxLines: options?.max ?? 70 };
248
231
  return {
249
- FunctionDeclaration: checkFunction,
250
- FunctionExpression: checkFunction,
251
- ArrowFunctionExpression: checkFunction,
252
- MethodDefinition: checkMethod,
232
+ FunctionDeclaration: (node) => checkFunctionNode(ctx, node),
233
+ FunctionExpression: (node) => checkFunctionNode(ctx, node),
234
+ ArrowFunctionExpression: (node) => checkFunctionNode(ctx, node),
235
+ MethodDefinition: (node) => checkMethodNode(ctx, node),
253
236
  };
254
237
  },
255
238
  };
@@ -1 +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\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: any) {\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 // Walk up directory tree to find workspace root\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 // Continue searching if JSON parse fails\n }\n }\n dir = path.dirname(dir);\n }\n return process.cwd(); // Fallback\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 // Graceful degradation: log warning but don't break lint\n console.warn(`[webpieces] Could not create doc file: ${docPath}`, err);\n return false;\n }\n}\n\nfunction ensureMethodDoc(context: Rule.RuleContext): void {\n if (methodDocCreated) return; // Performance: only create once per lint run\n\n const workspaceRoot = getWorkspaceRoot(context);\n const docPath = path.join(workspaceRoot, 'tmp', 'webpieces', 'webpieces.methods.md');\n\n if (ensureDocFile(docPath, METHOD_DOC_CONTENT)) {\n methodDocCreated = true;\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 maxLines = options?.max ?? 70;\n\n function checkFunction(node: any): void {\n ensureMethodDoc(context);\n\n const funcNode = node as FunctionNode;\n\n // Skip if this is a function expression that's part of a method definition\n // (method definitions will be handled by checkMethod)\n if (\n funcNode.type === 'FunctionExpression' &&\n funcNode['parent']?.type === 'MethodDefinition'\n ) {\n return;\n }\n\n // Skip if no location info or no body\n if (!funcNode.loc || !funcNode.body) {\n return;\n }\n\n // Get function name\n let name = 'anonymous';\n if (funcNode.type === 'FunctionDeclaration' && funcNode.id?.name) {\n name = funcNode.id.name;\n } else if (funcNode.type === 'FunctionExpression' && funcNode.id?.name) {\n name = funcNode.id.name;\n }\n\n // Calculate line count\n const startLine = funcNode.loc.start.line;\n const endLine = funcNode.loc.end.line;\n const lineCount = endLine - startLine + 1;\n\n if (lineCount > maxLines) {\n context.report({\n node: funcNode as any,\n messageId: 'tooLong',\n data: {\n name,\n actual: String(lineCount),\n max: String(maxLines),\n },\n });\n }\n }\n\n function checkMethod(node: any): void {\n ensureMethodDoc(context);\n\n const methodNode = node;\n\n // Skip if no location info\n if (!methodNode.loc || !methodNode.value) {\n return;\n }\n\n // Get method name from key\n const name = methodNode.key?.name || 'anonymous';\n\n // Calculate line count for the method (including the method definition)\n const startLine = methodNode.loc.start.line;\n const endLine = methodNode.loc.end.line;\n const lineCount = endLine - startLine + 1;\n\n if (lineCount > maxLines) {\n context.report({\n node: methodNode as any,\n messageId: 'tooLong',\n data: {\n name,\n actual: String(lineCount),\n max: String(maxLines),\n },\n });\n }\n }\n\n return {\n FunctionDeclaration: checkFunction,\n FunctionExpression: checkFunction,\n ArrowFunctionExpression: checkFunction,\n MethodDefinition: checkMethod,\n };\n },\n};\n\nexport = rule;\n"]}
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"]}
@@ -36,6 +36,11 @@ interface FunctionNode {
36
36
  [key: string]: any;
37
37
  }
38
38
 
39
+ interface CheckerContext {
40
+ context: Rule.RuleContext;
41
+ maxLines: number;
42
+ }
43
+
39
44
  const METHOD_DOC_CONTENT = `# AI Agent Instructions: Method Too Long
40
45
 
41
46
  **READ THIS FILE to fix methods that are too long**
@@ -50,14 +55,14 @@ Every method should read like a TABLE OF CONTENTS of a book.
50
55
 
51
56
  ### Pattern 1: Extract Loop Bodies
52
57
  \`\`\`typescript
53
- // BAD: 50 lines embedded in loop
58
+ // BAD: 50 lines embedded in loop
54
59
  for (const order of orders) {
55
60
  // 20 lines of validation logic
56
61
  // 15 lines of processing logic
57
62
  // 10 lines of notification logic
58
63
  }
59
64
 
60
- // GOOD: Extracted to named methods
65
+ // GOOD: Extracted to named methods
61
66
  for (const order of orders) {
62
67
  validateOrder(order);
63
68
  processOrderItems(order);
@@ -67,11 +72,11 @@ for (const order of orders) {
67
72
 
68
73
  ### Pattern 2: Try-Catch Wrapper for Exception Handling
69
74
  \`\`\`typescript
70
- // GOOD: Separates success path from error handling
75
+ // GOOD: Separates success path from error handling
71
76
  async function handleRequest(req: Request): Promise<Response> {
72
77
  try {
73
78
  return await executeRequest(req);
74
- } catch (err: any) {
79
+ } catch (err: unknown) {
75
80
  const error = toError(err);
76
81
  return createErrorResponse(error);
77
82
  }
@@ -80,7 +85,7 @@ async function handleRequest(req: Request): Promise<Response> {
80
85
 
81
86
  ### Pattern 3: Sequential Method Calls (Table of Contents)
82
87
  \`\`\`typescript
83
- // GOOD: Self-documenting steps
88
+ // GOOD: Self-documenting steps
84
89
  function processOrder(order: Order): void {
85
90
  validateOrderData(order);
86
91
  calculateTotals(order);
@@ -93,22 +98,22 @@ function processOrder(order: Order): void {
93
98
 
94
99
  ### Pattern 4: Separate Data Object Creation
95
100
  \`\`\`typescript
96
- // BAD: 15 lines of inline object creation
101
+ // BAD: 15 lines of inline object creation
97
102
  doSomething({ field1: ..., field2: ..., field3: ..., /* 15 more fields */ });
98
103
 
99
- // GOOD: Extract to factory method
104
+ // GOOD: Extract to factory method
100
105
  const request = createRequestObject(data);
101
106
  doSomething(request);
102
107
  \`\`\`
103
108
 
104
109
  ### Pattern 5: Extract Inline Logic to Named Functions
105
110
  \`\`\`typescript
106
- // BAD: Complex inline logic
111
+ // BAD: Complex inline logic
107
112
  if (user.role === 'admin' && user.permissions.includes('write') && !user.suspended) {
108
113
  // 30 lines of admin logic
109
114
  }
110
115
 
111
- // GOOD: Extract to named methods
116
+ // GOOD: Extract to named methods
112
117
  if (isAdminWithWriteAccess(user)) {
113
118
  performAdminOperation(user);
114
119
  }
@@ -124,12 +129,12 @@ if (isAdminWithWriteAccess(user)) {
124
129
  6. **TEST** that functionality remains unchanged
125
130
 
126
131
  ## Examples of "Logical Units" to Extract
127
- - Validation logic \`validateX()\`
128
- - Data transformation \`transformXToY()\`
129
- - API calls \`fetchXFromApi()\`
130
- - Object creation \`createX()\`
131
- - Loop bodies \`processItem()\`
132
- - Error handling \`handleXError()\`
132
+ - Validation logic -> \`validateX()\`
133
+ - Data transformation -> \`transformXToY()\`
134
+ - API calls -> \`fetchXFromApi()\`
135
+ - Object creation -> \`createX()\`
136
+ - Loop bodies -> \`processItem()\`
137
+ - Error handling -> \`handleXError()\`
133
138
 
134
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.
135
140
  `;
@@ -141,7 +146,6 @@ function getWorkspaceRoot(context: Rule.RuleContext): string {
141
146
  const filename = context.filename || context.getFilename();
142
147
  let dir = path.dirname(filename);
143
148
 
144
- // Walk up directory tree to find workspace root
145
149
  while (dir !== path.dirname(dir)) {
146
150
  const pkgPath = path.join(dir, 'package.json');
147
151
  if (fs.existsSync(pkgPath)) {
@@ -152,12 +156,12 @@ function getWorkspaceRoot(context: Rule.RuleContext): string {
152
156
  }
153
157
  } catch (err: any) {
154
158
  //const error = toError(err);
155
- // Continue searching if JSON parse fails
159
+ void err;
156
160
  }
157
161
  }
158
162
  dir = path.dirname(dir);
159
163
  }
160
- return process.cwd(); // Fallback
164
+ return process.cwd();
161
165
  }
162
166
 
163
167
  function ensureDocFile(docPath: string, content: string): boolean {
@@ -167,23 +171,78 @@ function ensureDocFile(docPath: string, content: string): boolean {
167
171
  return true;
168
172
  } catch (err: any) {
169
173
  //const error = toError(err);
170
- // Graceful degradation: log warning but don't break lint
174
+ // err is used in console.warn below
171
175
  console.warn(`[webpieces] Could not create doc file: ${docPath}`, err);
172
176
  return false;
173
177
  }
174
178
  }
175
179
 
176
180
  function ensureMethodDoc(context: Rule.RuleContext): void {
177
- if (methodDocCreated) return; // Performance: only create once per lint run
178
-
179
181
  const workspaceRoot = getWorkspaceRoot(context);
180
182
  const docPath = path.join(workspaceRoot, 'tmp', 'webpieces', 'webpieces.methods.md');
181
183
 
184
+ // Check if file exists AND flag is true - if both, skip
185
+ if (methodDocCreated && fs.existsSync(docPath)) return;
186
+
182
187
  if (ensureDocFile(docPath, METHOD_DOC_CONTENT)) {
183
188
  methodDocCreated = true;
184
189
  }
185
190
  }
186
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
+
187
246
  const rule: Rule.RuleModule = {
188
247
  meta: {
189
248
  type: 'suggestion',
@@ -214,89 +273,13 @@ const rule: Rule.RuleModule = {
214
273
 
215
274
  create(context: Rule.RuleContext): Rule.RuleListener {
216
275
  const options = context.options[0] as MethodLinesOptions | undefined;
217
- const maxLines = options?.max ?? 70;
218
-
219
- function checkFunction(node: any): void {
220
- ensureMethodDoc(context);
221
-
222
- const funcNode = node as FunctionNode;
223
-
224
- // Skip if this is a function expression that's part of a method definition
225
- // (method definitions will be handled by checkMethod)
226
- if (
227
- funcNode.type === 'FunctionExpression' &&
228
- funcNode['parent']?.type === 'MethodDefinition'
229
- ) {
230
- return;
231
- }
232
-
233
- // Skip if no location info or no body
234
- if (!funcNode.loc || !funcNode.body) {
235
- return;
236
- }
237
-
238
- // Get function name
239
- let name = 'anonymous';
240
- if (funcNode.type === 'FunctionDeclaration' && funcNode.id?.name) {
241
- name = funcNode.id.name;
242
- } else if (funcNode.type === 'FunctionExpression' && funcNode.id?.name) {
243
- name = funcNode.id.name;
244
- }
245
-
246
- // Calculate line count
247
- const startLine = funcNode.loc.start.line;
248
- const endLine = funcNode.loc.end.line;
249
- const lineCount = endLine - startLine + 1;
250
-
251
- if (lineCount > maxLines) {
252
- context.report({
253
- node: funcNode as any,
254
- messageId: 'tooLong',
255
- data: {
256
- name,
257
- actual: String(lineCount),
258
- max: String(maxLines),
259
- },
260
- });
261
- }
262
- }
263
-
264
- function checkMethod(node: any): void {
265
- ensureMethodDoc(context);
266
-
267
- const methodNode = node;
268
-
269
- // Skip if no location info
270
- if (!methodNode.loc || !methodNode.value) {
271
- return;
272
- }
273
-
274
- // Get method name from key
275
- const name = methodNode.key?.name || 'anonymous';
276
-
277
- // Calculate line count for the method (including the method definition)
278
- const startLine = methodNode.loc.start.line;
279
- const endLine = methodNode.loc.end.line;
280
- const lineCount = endLine - startLine + 1;
281
-
282
- if (lineCount > maxLines) {
283
- context.report({
284
- node: methodNode as any,
285
- messageId: 'tooLong',
286
- data: {
287
- name,
288
- actual: String(lineCount),
289
- max: String(maxLines),
290
- },
291
- });
292
- }
293
- }
276
+ const ctx: CheckerContext = { context, maxLines: options?.max ?? 70 };
294
277
 
295
278
  return {
296
- FunctionDeclaration: checkFunction,
297
- FunctionExpression: checkFunction,
298
- ArrowFunctionExpression: checkFunction,
299
- MethodDefinition: checkMethod,
279
+ FunctionDeclaration: (node) => checkFunctionNode(ctx, node),
280
+ FunctionExpression: (node) => checkFunctionNode(ctx, node),
281
+ ArrowFunctionExpression: (node) => checkFunctionNode(ctx, node),
282
+ MethodDefinition: (node) => checkMethodNode(ctx, node),
300
283
  };
301
284
  },
302
285
  };
@@ -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;