@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.
Files changed (87) 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__/max-file-lines.test.ts +207 -0
  56. package/eslint-plugin/__tests__/max-method-lines.test.ts +258 -0
  57. package/eslint-plugin/__tests__/no-unmanaged-exceptions.test.ts +359 -0
  58. package/eslint-plugin/index.d.ts +11 -0
  59. package/eslint-plugin/index.js +15 -0
  60. package/eslint-plugin/index.js.map +1 -1
  61. package/eslint-plugin/index.ts +15 -0
  62. package/eslint-plugin/rules/enforce-architecture.d.ts +15 -0
  63. package/eslint-plugin/rules/enforce-architecture.js +406 -0
  64. package/eslint-plugin/rules/enforce-architecture.js.map +1 -0
  65. package/eslint-plugin/rules/enforce-architecture.ts +469 -0
  66. package/eslint-plugin/rules/max-file-lines.d.ts +12 -0
  67. package/eslint-plugin/rules/max-file-lines.js +257 -0
  68. package/eslint-plugin/rules/max-file-lines.js.map +1 -0
  69. package/eslint-plugin/rules/max-file-lines.ts +272 -0
  70. package/eslint-plugin/rules/max-method-lines.d.ts +12 -0
  71. package/eslint-plugin/rules/max-method-lines.js +240 -0
  72. package/eslint-plugin/rules/max-method-lines.js.map +1 -0
  73. package/eslint-plugin/rules/max-method-lines.ts +287 -0
  74. package/eslint-plugin/rules/no-unmanaged-exceptions.d.ts +22 -0
  75. package/eslint-plugin/rules/no-unmanaged-exceptions.js +605 -0
  76. package/eslint-plugin/rules/no-unmanaged-exceptions.js.map +1 -0
  77. package/eslint-plugin/rules/no-unmanaged-exceptions.ts +621 -0
  78. package/executors.json +29 -0
  79. package/package.json +13 -3
  80. package/plugins/circular-deps/index.d.ts +8 -0
  81. package/plugins/circular-deps/index.js +14 -0
  82. package/plugins/circular-deps/index.js.map +1 -0
  83. package/plugins/circular-deps/index.ts +9 -0
  84. package/plugins/circular-deps/plugin.d.ts +32 -0
  85. package/plugins/circular-deps/plugin.js +73 -0
  86. package/plugins/circular-deps/plugin.js.map +1 -0
  87. 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":";AAAA;;;;;;;;GAQG;;AAGH,+CAAyB;AACzB,mDAA6B;AA+B7B,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgG1B,CAAC;AAEF,uDAAuD;AACvD,IAAI,gBAAgB,GAAG,KAAK,CAAC;AAE7B,SAAS,gBAAgB,CAAC,OAAyB;IAC/C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAC3D,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAEjC,OAAO,GAAG,KAAK,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QAC/C,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACzB,IAAI,CAAC;gBACD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;gBAC1D,IAAI,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;oBAChD,OAAO,GAAG,CAAC;gBACf,CAAC;YACL,CAAC;YAAC,OAAO,GAAQ,EAAE,CAAC;gBAChB,6BAA6B;gBAC7B,KAAK,GAAG,CAAC;YACb,CAAC;QACL,CAAC;QACD,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC;AACzB,CAAC;AAED,SAAS,aAAa,CAAC,OAAe,EAAE,OAAe;IACnD,IAAI,CAAC;QACD,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACzD,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QAC5C,OAAO,IAAI,CAAC;IAChB,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAChB,6BAA6B;QAC7B,oCAAoC;QACpC,OAAO,CAAC,IAAI,CAAC,0CAA0C,OAAO,EAAE,EAAE,GAAG,CAAC,CAAC;QACvE,OAAO,KAAK,CAAC;IACjB,CAAC;AACL,CAAC;AAED,SAAS,eAAe,CAAC,OAAyB;IAC9C,MAAM,aAAa,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,EAAE,WAAW,EAAE,sBAAsB,CAAC,CAAC;IAErF,wDAAwD;IACxD,IAAI,gBAAgB,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO;IAEvD,IAAI,aAAa,CAAC,OAAO,EAAE,kBAAkB,CAAC,EAAE,CAAC;QAC7C,gBAAgB,GAAG,IAAI,CAAC;IAC5B,CAAC;AACL,CAAC;AAED,SAAS,eAAe,CAAC,QAAsB;IAC3C,IAAI,QAAQ,CAAC,IAAI,KAAK,qBAAqB,IAAI,QAAQ,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC;QAC/D,OAAO,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC;IAC5B,CAAC;IACD,IAAI,QAAQ,CAAC,IAAI,KAAK,oBAAoB,IAAI,QAAQ,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC;QAC9D,OAAO,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC;IAC5B,CAAC;IACD,OAAO,WAAW,CAAC;AACvB,CAAC;AAED,SAAS,aAAa,CAAC,GAAmB,EAAE,IAAS,EAAE,IAAY,EAAE,SAAiB;IAClF,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC;QACf,IAAI;QACJ,SAAS,EAAE,SAAS;QACpB,IAAI,EAAE;YACF,IAAI;YACJ,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC;YACzB,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;SAC5B;KACJ,CAAC,CAAC;AACP,CAAC;AAED,SAAS,iBAAiB,CAAC,GAAmB,EAAE,IAAS;IACrD,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC7B,MAAM,QAAQ,GAAG,IAAoB,CAAC;IAEtC,sDAAsD;IACtD,IAAI,QAAQ,CAAC,IAAI,KAAK,oBAAoB,IAAI,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,KAAK,kBAAkB,EAAE,CAAC;QAC5F,OAAO;IACX,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI;QAAE,OAAO;IAE5C,MAAM,IAAI,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC;IAEtE,IAAI,SAAS,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC3B,aAAa,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;IAClD,CAAC;AACL,CAAC;AAED,SAAS,eAAe,CAAC,GAAmB,EAAE,IAAS;IACnD,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAE7B,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK;QAAE,OAAO;IAErC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,WAAW,CAAC;IAC3C,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC;IAE9D,IAAI,SAAS,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC3B,aAAa,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;IAC9C,CAAC;AACL,CAAC;AAED,MAAM,IAAI,GAAoB;IAC1B,IAAI,EAAE;QACF,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE;YACF,WAAW,EAAE,+BAA+B;YAC5C,QAAQ,EAAE,gBAAgB;YAC1B,WAAW,EAAE,KAAK;YAClB,GAAG,EAAE,4CAA4C;SACpD;QACD,QAAQ,EAAE;YACN,OAAO,EACH,+HAA+H;SACtI;QACD,OAAO,EAAE,SAAS;QAClB,MAAM,EAAE;YACJ;gBACI,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE;oBACR,GAAG,EAAE;wBACD,IAAI,EAAE,SAAS;wBACf,OAAO,EAAE,CAAC;qBACb;iBACJ;gBACD,oBAAoB,EAAE,KAAK;aAC9B;SACJ;KACJ;IAED,MAAM,CAAC,OAAyB;QAC5B,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAmC,CAAC;QACrE,MAAM,GAAG,GAAmB,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,EAAE,CAAC;QAEtE,OAAO;YACH,mBAAmB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC;YAC3D,kBAAkB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC;YAC1D,uBAAuB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC;YAC/D,gBAAgB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,eAAe,CAAC,GAAG,EAAE,IAAI,CAAC;SACzD,CAAC;IACN,CAAC;CACJ,CAAC;AAEF,iBAAS,IAAI,CAAC","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;