@webpieces/dev-config 0.0.0-dev → 0.2.17

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.
@@ -1,257 +0,0 @@
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: any) {
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
- // Walk up directory tree to find workspace root
117
- while (dir !== path.dirname(dir)) {
118
- const pkgPath = path.join(dir, 'package.json');
119
- if (fs.existsSync(pkgPath)) {
120
- try {
121
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
122
- if (pkg.workspaces || pkg.name === 'webpieces-ts') {
123
- return dir;
124
- }
125
- }
126
- catch (err) {
127
- //const error = toError(err);
128
- // Continue searching if JSON parse fails
129
- }
130
- }
131
- dir = path.dirname(dir);
132
- }
133
- return process.cwd(); // Fallback
134
- }
135
- function ensureDocFile(docPath, content) {
136
- try {
137
- fs.mkdirSync(path.dirname(docPath), { recursive: true });
138
- fs.writeFileSync(docPath, content, 'utf-8');
139
- return true;
140
- }
141
- catch (err) {
142
- //const error = toError(err);
143
- // Graceful degradation: log warning but don't break lint
144
- console.warn(`[webpieces] Could not create doc file: ${docPath}`, err);
145
- return false;
146
- }
147
- }
148
- function ensureMethodDoc(context) {
149
- if (methodDocCreated)
150
- return; // Performance: only create once per lint run
151
- const workspaceRoot = getWorkspaceRoot(context);
152
- const docPath = path.join(workspaceRoot, 'tmp', 'webpieces', 'webpieces.methods.md');
153
- if (ensureDocFile(docPath, METHOD_DOC_CONTENT)) {
154
- methodDocCreated = true;
155
- }
156
- }
157
- const rule = {
158
- meta: {
159
- type: 'suggestion',
160
- docs: {
161
- description: 'Enforce maximum method length',
162
- category: 'Best Practices',
163
- recommended: false,
164
- url: 'https://github.com/deanhiller/webpieces-ts',
165
- },
166
- messages: {
167
- tooLong: 'AI Agent: READ tmp/webpieces/webpieces.methods.md for fix instructions. Method "{{name}}" has {{actual}} lines (max: {{max}})',
168
- },
169
- fixable: undefined,
170
- schema: [
171
- {
172
- type: 'object',
173
- properties: {
174
- max: {
175
- type: 'integer',
176
- minimum: 1,
177
- },
178
- },
179
- additionalProperties: false,
180
- },
181
- ],
182
- },
183
- create(context) {
184
- 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
- }
248
- return {
249
- FunctionDeclaration: checkFunction,
250
- FunctionExpression: checkFunction,
251
- ArrowFunctionExpression: checkFunction,
252
- MethodDefinition: checkMethod,
253
- };
254
- },
255
- };
256
- module.exports = rule;
257
- //# sourceMappingURL=max-method-lines.js.map
@@ -1 +0,0 @@
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,304 +0,0 @@
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
- const METHOD_DOC_CONTENT = `# AI Agent Instructions: Method Too Long
40
-
41
- **READ THIS FILE to fix methods that are too long**
42
-
43
- ## Core Principle
44
- Every method should read like a TABLE OF CONTENTS of a book.
45
- - Each method call is a "chapter"
46
- - When you dive into a method, you find another table of contents
47
- - Keeping methods under 70 lines is achievable with proper extraction
48
-
49
- ## Command: Extract Code into Named Methods
50
-
51
- ### Pattern 1: Extract Loop Bodies
52
- \`\`\`typescript
53
- // ❌ BAD: 50 lines embedded in loop
54
- for (const order of orders) {
55
- // 20 lines of validation logic
56
- // 15 lines of processing logic
57
- // 10 lines of notification logic
58
- }
59
-
60
- // ✅ GOOD: Extracted to named methods
61
- for (const order of orders) {
62
- validateOrder(order);
63
- processOrderItems(order);
64
- sendNotifications(order);
65
- }
66
- \`\`\`
67
-
68
- ### Pattern 2: Try-Catch Wrapper for Exception Handling
69
- \`\`\`typescript
70
- // ✅ GOOD: Separates success path from error handling
71
- async function handleRequest(req: Request): Promise<Response> {
72
- try {
73
- return await executeRequest(req);
74
- } catch (err: any) {
75
- const error = toError(err);
76
- return createErrorResponse(error);
77
- }
78
- }
79
- \`\`\`
80
-
81
- ### Pattern 3: Sequential Method Calls (Table of Contents)
82
- \`\`\`typescript
83
- // ✅ GOOD: Self-documenting steps
84
- function processOrder(order: Order): void {
85
- validateOrderData(order);
86
- calculateTotals(order);
87
- applyDiscounts(order);
88
- processPayment(order);
89
- updateInventory(order);
90
- sendConfirmation(order);
91
- }
92
- \`\`\`
93
-
94
- ### Pattern 4: Separate Data Object Creation
95
- \`\`\`typescript
96
- // ❌ BAD: 15 lines of inline object creation
97
- doSomething({ field1: ..., field2: ..., field3: ..., /* 15 more fields */ });
98
-
99
- // ✅ GOOD: Extract to factory method
100
- const request = createRequestObject(data);
101
- doSomething(request);
102
- \`\`\`
103
-
104
- ### Pattern 5: Extract Inline Logic to Named Functions
105
- \`\`\`typescript
106
- // ❌ BAD: Complex inline logic
107
- if (user.role === 'admin' && user.permissions.includes('write') && !user.suspended) {
108
- // 30 lines of admin logic
109
- }
110
-
111
- // ✅ GOOD: Extract to named methods
112
- if (isAdminWithWriteAccess(user)) {
113
- performAdminOperation(user);
114
- }
115
- \`\`\`
116
-
117
- ## AI Agent Action Steps
118
-
119
- 1. **IDENTIFY** the long method in the error message
120
- 2. **READ** the method to understand its logical sections
121
- 3. **EXTRACT** logical units into separate methods with descriptive names
122
- 4. **REPLACE** inline code with method calls
123
- 5. **VERIFY** each extracted method is <70 lines
124
- 6. **TEST** that functionality remains unchanged
125
-
126
- ## 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()\`
133
-
134
- 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
- `;
136
-
137
- // Module-level flag to prevent redundant file creation
138
- let methodDocCreated = false;
139
-
140
- function getWorkspaceRoot(context: Rule.RuleContext): string {
141
- const filename = context.filename || context.getFilename();
142
- let dir = path.dirname(filename);
143
-
144
- // Walk up directory tree to find workspace root
145
- while (dir !== path.dirname(dir)) {
146
- const pkgPath = path.join(dir, 'package.json');
147
- if (fs.existsSync(pkgPath)) {
148
- try {
149
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
150
- if (pkg.workspaces || pkg.name === 'webpieces-ts') {
151
- return dir;
152
- }
153
- } catch (err: any) {
154
- //const error = toError(err);
155
- // Continue searching if JSON parse fails
156
- }
157
- }
158
- dir = path.dirname(dir);
159
- }
160
- return process.cwd(); // Fallback
161
- }
162
-
163
- function ensureDocFile(docPath: string, content: string): boolean {
164
- try {
165
- fs.mkdirSync(path.dirname(docPath), { recursive: true });
166
- fs.writeFileSync(docPath, content, 'utf-8');
167
- return true;
168
- } catch (err: any) {
169
- //const error = toError(err);
170
- // Graceful degradation: log warning but don't break lint
171
- console.warn(`[webpieces] Could not create doc file: ${docPath}`, err);
172
- return false;
173
- }
174
- }
175
-
176
- function ensureMethodDoc(context: Rule.RuleContext): void {
177
- if (methodDocCreated) return; // Performance: only create once per lint run
178
-
179
- const workspaceRoot = getWorkspaceRoot(context);
180
- const docPath = path.join(workspaceRoot, 'tmp', 'webpieces', 'webpieces.methods.md');
181
-
182
- if (ensureDocFile(docPath, METHOD_DOC_CONTENT)) {
183
- methodDocCreated = true;
184
- }
185
- }
186
-
187
- const rule: Rule.RuleModule = {
188
- meta: {
189
- type: 'suggestion',
190
- docs: {
191
- description: 'Enforce maximum method length',
192
- category: 'Best Practices',
193
- recommended: false,
194
- url: 'https://github.com/deanhiller/webpieces-ts',
195
- },
196
- messages: {
197
- tooLong:
198
- 'AI Agent: READ tmp/webpieces/webpieces.methods.md for fix instructions. Method "{{name}}" has {{actual}} lines (max: {{max}})',
199
- },
200
- fixable: undefined,
201
- schema: [
202
- {
203
- type: 'object',
204
- properties: {
205
- max: {
206
- type: 'integer',
207
- minimum: 1,
208
- },
209
- },
210
- additionalProperties: false,
211
- },
212
- ],
213
- },
214
-
215
- create(context: Rule.RuleContext): Rule.RuleListener {
216
- 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
- }
294
-
295
- return {
296
- FunctionDeclaration: checkFunction,
297
- FunctionExpression: checkFunction,
298
- ArrowFunctionExpression: checkFunction,
299
- MethodDefinition: checkMethod,
300
- };
301
- },
302
- };
303
-
304
- export = rule;