@webpieces/dev-config 0.2.38 → 0.2.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/architecture/executors/validate-architecture-unchanged/executor.js +166 -2
- package/architecture/executors/validate-architecture-unchanged/executor.js.map +1 -1
- package/architecture/executors/validate-architecture-unchanged/executor.ts +172 -2
- package/architecture/executors/validate-new-methods/executor.d.ts +22 -0
- package/architecture/executors/validate-new-methods/executor.js +351 -0
- package/architecture/executors/validate-new-methods/executor.js.map +1 -0
- package/architecture/executors/validate-new-methods/executor.ts +408 -0
- package/architecture/executors/validate-new-methods/schema.json +14 -0
- package/eslint-plugin/rules/enforce-architecture.js +12 -3
- package/eslint-plugin/rules/enforce-architecture.js.map +1 -1
- package/eslint-plugin/rules/enforce-architecture.ts +12 -3
- package/executors.json +5 -0
- package/package.json +1 -1
- package/plugin.js +43 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Validate New Methods Executor
|
|
4
|
+
*
|
|
5
|
+
* Validates that newly added methods don't exceed a maximum line count.
|
|
6
|
+
* Only runs when NX_BASE environment variable is set (affected mode).
|
|
7
|
+
*
|
|
8
|
+
* This validator encourages writing methods that read like a "table of contents"
|
|
9
|
+
* where each method call describes a larger piece of work.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* nx affected --target=validate-new-methods --base=origin/main
|
|
13
|
+
*
|
|
14
|
+
* Escape hatch: Add eslint-disable comment with justification
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.default = runExecutor;
|
|
18
|
+
const tslib_1 = require("tslib");
|
|
19
|
+
const child_process_1 = require("child_process");
|
|
20
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
21
|
+
const path = tslib_1.__importStar(require("path"));
|
|
22
|
+
const ts = tslib_1.__importStar(require("typescript"));
|
|
23
|
+
const TMP_DIR = 'tmp/webpieces';
|
|
24
|
+
const TMP_MD_FILE = 'webpieces.methodsize.md';
|
|
25
|
+
const METHODSIZE_DOC_CONTENT = `# Instructions: New Method Too Long
|
|
26
|
+
|
|
27
|
+
## The "Table of Contents" Principle
|
|
28
|
+
|
|
29
|
+
Good code reads like a book's table of contents:
|
|
30
|
+
- Chapter titles (method names) tell you WHAT happens
|
|
31
|
+
- Reading chapter titles gives you the full story
|
|
32
|
+
- You can dive into chapters (implementations) for details
|
|
33
|
+
|
|
34
|
+
## Why Limit New Methods to 20-30 Lines?
|
|
35
|
+
|
|
36
|
+
Methods under 20-30 lines are:
|
|
37
|
+
- Easy to review in a single screen
|
|
38
|
+
- Simple to understand without scrolling
|
|
39
|
+
- Quick for AI to analyze and suggest improvements
|
|
40
|
+
- More testable in isolation
|
|
41
|
+
- Self-documenting through well-named extracted methods
|
|
42
|
+
|
|
43
|
+
**~50% of the time**, you can stay under 20-30 lines in new code by extracting
|
|
44
|
+
logical units into well-named methods. This makes code more readable for both
|
|
45
|
+
AI and humans.
|
|
46
|
+
|
|
47
|
+
## How to Refactor
|
|
48
|
+
|
|
49
|
+
Instead of:
|
|
50
|
+
\`\`\`typescript
|
|
51
|
+
async processOrder(order: Order): Promise<Result> {
|
|
52
|
+
// 50 lines of validation, transformation, saving, notifications...
|
|
53
|
+
}
|
|
54
|
+
\`\`\`
|
|
55
|
+
|
|
56
|
+
Write:
|
|
57
|
+
\`\`\`typescript
|
|
58
|
+
async processOrder(order: Order): Promise<Result> {
|
|
59
|
+
const validated = this.validateOrder(order);
|
|
60
|
+
const transformed = this.applyBusinessRules(validated);
|
|
61
|
+
const saved = await this.saveToDatabase(transformed);
|
|
62
|
+
await this.notifyStakeholders(saved);
|
|
63
|
+
return this.buildResult(saved);
|
|
64
|
+
}
|
|
65
|
+
\`\`\`
|
|
66
|
+
|
|
67
|
+
Now the main method is a "table of contents" - each line tells part of the story!
|
|
68
|
+
|
|
69
|
+
## Patterns for Extraction
|
|
70
|
+
|
|
71
|
+
### Pattern 1: Extract Loop Bodies
|
|
72
|
+
\`\`\`typescript
|
|
73
|
+
// BEFORE
|
|
74
|
+
for (const item of items) {
|
|
75
|
+
// 20 lines of processing
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// AFTER
|
|
79
|
+
for (const item of items) {
|
|
80
|
+
this.processItem(item);
|
|
81
|
+
}
|
|
82
|
+
\`\`\`
|
|
83
|
+
|
|
84
|
+
### Pattern 2: Extract Conditional Blocks
|
|
85
|
+
\`\`\`typescript
|
|
86
|
+
// BEFORE
|
|
87
|
+
if (isAdmin(user)) {
|
|
88
|
+
// 15 lines of admin logic
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// AFTER
|
|
92
|
+
if (isAdmin(user)) {
|
|
93
|
+
this.handleAdminUser(user);
|
|
94
|
+
}
|
|
95
|
+
\`\`\`
|
|
96
|
+
|
|
97
|
+
### Pattern 3: Extract Data Transformations
|
|
98
|
+
\`\`\`typescript
|
|
99
|
+
// BEFORE
|
|
100
|
+
const result = {
|
|
101
|
+
// 10+ lines of object construction
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// AFTER
|
|
105
|
+
const result = this.buildResultObject(data);
|
|
106
|
+
\`\`\`
|
|
107
|
+
|
|
108
|
+
## If Refactoring Is Not Feasible
|
|
109
|
+
|
|
110
|
+
Sometimes methods genuinely need to be longer (complex algorithms, state machines, etc.).
|
|
111
|
+
|
|
112
|
+
**Escape hatch**: Add an eslint-disable comment with justification:
|
|
113
|
+
|
|
114
|
+
\`\`\`typescript
|
|
115
|
+
// eslint-disable-next-line @webpieces/max-method-lines -- Complex state machine, splitting reduces clarity
|
|
116
|
+
async complexStateMachine(): Promise<void> {
|
|
117
|
+
// ... longer method with justification
|
|
118
|
+
}
|
|
119
|
+
\`\`\`
|
|
120
|
+
|
|
121
|
+
## AI Agent Action Steps
|
|
122
|
+
|
|
123
|
+
1. **READ** the method to understand its logical sections
|
|
124
|
+
2. **IDENTIFY** logical units that can be extracted
|
|
125
|
+
3. **EXTRACT** into well-named private methods
|
|
126
|
+
4. **VERIFY** the main method now reads like a table of contents
|
|
127
|
+
5. **IF NOT FEASIBLE**: Add eslint-disable with clear justification
|
|
128
|
+
|
|
129
|
+
## Remember
|
|
130
|
+
|
|
131
|
+
- Every method you write today will be read many times tomorrow
|
|
132
|
+
- The best code explains itself through structure
|
|
133
|
+
- When in doubt, extract and name it
|
|
134
|
+
`;
|
|
135
|
+
/**
|
|
136
|
+
* Write the instructions documentation to tmp directory
|
|
137
|
+
*/
|
|
138
|
+
function writeTmpInstructions(workspaceRoot) {
|
|
139
|
+
const tmpDir = path.join(workspaceRoot, TMP_DIR);
|
|
140
|
+
const mdPath = path.join(tmpDir, TMP_MD_FILE);
|
|
141
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
142
|
+
fs.writeFileSync(mdPath, METHODSIZE_DOC_CONTENT);
|
|
143
|
+
return mdPath;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get changed TypeScript files between base and head
|
|
147
|
+
*/
|
|
148
|
+
function getChangedTypeScriptFiles(workspaceRoot, base, head) {
|
|
149
|
+
try {
|
|
150
|
+
const output = (0, child_process_1.execSync)(`git diff --name-only ${base}...${head} -- '*.ts' '*.tsx'`, {
|
|
151
|
+
cwd: workspaceRoot,
|
|
152
|
+
encoding: 'utf-8',
|
|
153
|
+
});
|
|
154
|
+
return output
|
|
155
|
+
.trim()
|
|
156
|
+
.split('\n')
|
|
157
|
+
.filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Get the diff content for a specific file
|
|
165
|
+
*/
|
|
166
|
+
function getFileDiff(workspaceRoot, file, base, head) {
|
|
167
|
+
try {
|
|
168
|
+
return (0, child_process_1.execSync)(`git diff ${base}...${head} -- "${file}"`, {
|
|
169
|
+
cwd: workspaceRoot,
|
|
170
|
+
encoding: 'utf-8',
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return '';
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Parse diff to find newly added method signatures
|
|
179
|
+
*/
|
|
180
|
+
function findNewMethodSignaturesInDiff(diffContent) {
|
|
181
|
+
const newMethods = new Set();
|
|
182
|
+
const lines = diffContent.split('\n');
|
|
183
|
+
// Patterns to match method definitions
|
|
184
|
+
const patterns = [
|
|
185
|
+
// async methodName( or methodName(
|
|
186
|
+
/^\+\s*(async\s+)?(\w+)\s*\(/,
|
|
187
|
+
// function methodName(
|
|
188
|
+
/^\+\s*(async\s+)?function\s+(\w+)\s*\(/,
|
|
189
|
+
// const/let methodName = (async)? (
|
|
190
|
+
/^\+\s*(?:const|let)\s+(\w+)\s*=\s*(?:async\s*)?\(/,
|
|
191
|
+
// const/let methodName = (async)? function
|
|
192
|
+
/^\+\s*(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?function/,
|
|
193
|
+
];
|
|
194
|
+
for (const line of lines) {
|
|
195
|
+
if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
196
|
+
for (const pattern of patterns) {
|
|
197
|
+
const match = line.match(pattern);
|
|
198
|
+
if (match) {
|
|
199
|
+
// Extract method name from different capture groups
|
|
200
|
+
const methodName = match[2] || match[1];
|
|
201
|
+
if (methodName && !['if', 'for', 'while', 'switch', 'catch', 'constructor'].includes(methodName)) {
|
|
202
|
+
newMethods.add(methodName);
|
|
203
|
+
}
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return newMethods;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Parse a TypeScript file and find methods with their line counts
|
|
213
|
+
*/
|
|
214
|
+
function findMethodsInFile(filePath, workspaceRoot) {
|
|
215
|
+
const fullPath = path.join(workspaceRoot, filePath);
|
|
216
|
+
if (!fs.existsSync(fullPath))
|
|
217
|
+
return [];
|
|
218
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
219
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
220
|
+
const methods = [];
|
|
221
|
+
function visit(node) {
|
|
222
|
+
let methodName;
|
|
223
|
+
let startLine;
|
|
224
|
+
let endLine;
|
|
225
|
+
if (ts.isMethodDeclaration(node) && node.name) {
|
|
226
|
+
methodName = node.name.getText(sourceFile);
|
|
227
|
+
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
228
|
+
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
229
|
+
startLine = start.line + 1;
|
|
230
|
+
endLine = end.line + 1;
|
|
231
|
+
}
|
|
232
|
+
else if (ts.isFunctionDeclaration(node) && node.name) {
|
|
233
|
+
methodName = node.name.getText(sourceFile);
|
|
234
|
+
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
235
|
+
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
236
|
+
startLine = start.line + 1;
|
|
237
|
+
endLine = end.line + 1;
|
|
238
|
+
}
|
|
239
|
+
else if (ts.isArrowFunction(node)) {
|
|
240
|
+
// Check if it's assigned to a variable
|
|
241
|
+
if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
|
|
242
|
+
methodName = node.parent.name.getText(sourceFile);
|
|
243
|
+
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
244
|
+
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
245
|
+
startLine = start.line + 1;
|
|
246
|
+
endLine = end.line + 1;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (methodName && startLine !== undefined && endLine !== undefined) {
|
|
250
|
+
methods.push({
|
|
251
|
+
name: methodName,
|
|
252
|
+
line: startLine,
|
|
253
|
+
lines: endLine - startLine + 1,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
ts.forEachChild(node, visit);
|
|
257
|
+
}
|
|
258
|
+
visit(sourceFile);
|
|
259
|
+
return methods;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Find new methods that exceed the line limit
|
|
263
|
+
*/
|
|
264
|
+
function findViolations(workspaceRoot, changedFiles, base, head, maxLines) {
|
|
265
|
+
const violations = [];
|
|
266
|
+
for (const file of changedFiles) {
|
|
267
|
+
// Get the diff to find which methods are NEW (not just modified)
|
|
268
|
+
const diff = getFileDiff(workspaceRoot, file, base, head);
|
|
269
|
+
const newMethodNames = findNewMethodSignaturesInDiff(diff);
|
|
270
|
+
if (newMethodNames.size === 0)
|
|
271
|
+
continue;
|
|
272
|
+
// Parse the current file to get method line counts
|
|
273
|
+
const methods = findMethodsInFile(file, workspaceRoot);
|
|
274
|
+
for (const method of methods) {
|
|
275
|
+
// Only check NEW methods
|
|
276
|
+
if (newMethodNames.has(method.name) && method.lines > maxLines) {
|
|
277
|
+
violations.push({
|
|
278
|
+
file,
|
|
279
|
+
methodName: method.name,
|
|
280
|
+
line: method.line,
|
|
281
|
+
lines: method.lines,
|
|
282
|
+
isNew: true,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return violations;
|
|
288
|
+
}
|
|
289
|
+
async function runExecutor(options, context) {
|
|
290
|
+
const workspaceRoot = context.root;
|
|
291
|
+
const maxLines = options.max ?? 30;
|
|
292
|
+
// Check if running in affected mode
|
|
293
|
+
const base = process.env['NX_BASE'];
|
|
294
|
+
const head = process.env['NX_HEAD'] || 'HEAD';
|
|
295
|
+
if (!base) {
|
|
296
|
+
console.log('\n⏭️ Skipping new method validation (not in affected mode)');
|
|
297
|
+
console.log(' To run: nx affected --target=validate-new-methods --base=origin/main');
|
|
298
|
+
console.log('');
|
|
299
|
+
return { success: true };
|
|
300
|
+
}
|
|
301
|
+
console.log('\n📏 Validating New Method Sizes\n');
|
|
302
|
+
console.log(` Base: ${base}`);
|
|
303
|
+
console.log(` Head: ${head}`);
|
|
304
|
+
console.log(` Max lines for new methods: ${maxLines}`);
|
|
305
|
+
console.log('');
|
|
306
|
+
try {
|
|
307
|
+
// Get changed TypeScript files
|
|
308
|
+
const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base, head);
|
|
309
|
+
if (changedFiles.length === 0) {
|
|
310
|
+
console.log('✅ No TypeScript files changed');
|
|
311
|
+
return { success: true };
|
|
312
|
+
}
|
|
313
|
+
console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);
|
|
314
|
+
// Find violations
|
|
315
|
+
const violations = findViolations(workspaceRoot, changedFiles, base, head, maxLines);
|
|
316
|
+
if (violations.length === 0) {
|
|
317
|
+
console.log('✅ All new methods are under ' + maxLines + ' lines');
|
|
318
|
+
return { success: true };
|
|
319
|
+
}
|
|
320
|
+
// Write instructions file
|
|
321
|
+
const mdPath = writeTmpInstructions(workspaceRoot);
|
|
322
|
+
// Report violations
|
|
323
|
+
console.error('');
|
|
324
|
+
console.error('❌ New methods exceed ' + maxLines + ' lines!');
|
|
325
|
+
console.error('');
|
|
326
|
+
console.error('📚 Methods should read like a "table of contents" - each method call');
|
|
327
|
+
console.error(' describes a larger piece of work. ~50% of the time, you can refactor');
|
|
328
|
+
console.error(' to stay under ' + maxLines + ' lines. If not feasible, use the escape hatch.');
|
|
329
|
+
console.error('');
|
|
330
|
+
for (const v of violations) {
|
|
331
|
+
console.error(` ❌ ${v.file}:${v.line}`);
|
|
332
|
+
console.error(` Method: ${v.methodName} (${v.lines} lines, max: ${maxLines})`);
|
|
333
|
+
console.error(` READ ${mdPath} to fix this error properly`);
|
|
334
|
+
console.error('');
|
|
335
|
+
}
|
|
336
|
+
console.error('💡 To fix:');
|
|
337
|
+
console.error(' 1. Refactor the method to read like a table of contents (preferred)');
|
|
338
|
+
console.error(' 2. OR add eslint-disable comment with justification:');
|
|
339
|
+
console.error(' // eslint-disable-next-line @webpieces/max-method-lines -- [reason]');
|
|
340
|
+
console.error('');
|
|
341
|
+
console.error(`⚠️ *** READ ${mdPath} for detailed guidance *** ⚠️`);
|
|
342
|
+
console.error('');
|
|
343
|
+
return { success: false };
|
|
344
|
+
}
|
|
345
|
+
catch (err) {
|
|
346
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
347
|
+
console.error('❌ New method validation failed:', error.message);
|
|
348
|
+
return { success: false };
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
//# sourceMappingURL=executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/dev-config/architecture/executors/validate-new-methods/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate New Methods Executor\n *\n * Validates that newly added methods don't exceed a maximum line count.\n * Only runs when NX_BASE environment variable is set (affected mode).\n *\n * This validator encourages writing methods that read like a \"table of contents\"\n * where each method call describes a larger piece of work.\n *\n * Usage:\n * nx affected --target=validate-new-methods --base=origin/main\n *\n * Escape hatch: Add eslint-disable comment with justification\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { execSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as ts from 'typescript';\n\nexport interface ValidateNewMethodsOptions {\n max?: number;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\ninterface MethodViolation {\n file: string;\n methodName: string;\n line: number;\n lines: number;\n isNew: boolean;\n}\n\nconst TMP_DIR = 'tmp/webpieces';\nconst TMP_MD_FILE = 'webpieces.methodsize.md';\n\nconst METHODSIZE_DOC_CONTENT = `# Instructions: New Method Too Long\n\n## The \"Table of Contents\" Principle\n\nGood code reads like a book's table of contents:\n- Chapter titles (method names) tell you WHAT happens\n- Reading chapter titles gives you the full story\n- You can dive into chapters (implementations) for details\n\n## Why Limit New Methods to 20-30 Lines?\n\nMethods under 20-30 lines are:\n- Easy to review in a single screen\n- Simple to understand without scrolling\n- Quick for AI to analyze and suggest improvements\n- More testable in isolation\n- Self-documenting through well-named extracted methods\n\n**~50% of the time**, you can stay under 20-30 lines in new code by extracting\nlogical units into well-named methods. This makes code more readable for both\nAI and humans.\n\n## How to Refactor\n\nInstead of:\n\\`\\`\\`typescript\nasync processOrder(order: Order): Promise<Result> {\n // 50 lines of validation, transformation, saving, notifications...\n}\n\\`\\`\\`\n\nWrite:\n\\`\\`\\`typescript\nasync processOrder(order: Order): Promise<Result> {\n const validated = this.validateOrder(order);\n const transformed = this.applyBusinessRules(validated);\n const saved = await this.saveToDatabase(transformed);\n await this.notifyStakeholders(saved);\n return this.buildResult(saved);\n}\n\\`\\`\\`\n\nNow the main method is a \"table of contents\" - each line tells part of the story!\n\n## Patterns for Extraction\n\n### Pattern 1: Extract Loop Bodies\n\\`\\`\\`typescript\n// BEFORE\nfor (const item of items) {\n // 20 lines of processing\n}\n\n// AFTER\nfor (const item of items) {\n this.processItem(item);\n}\n\\`\\`\\`\n\n### Pattern 2: Extract Conditional Blocks\n\\`\\`\\`typescript\n// BEFORE\nif (isAdmin(user)) {\n // 15 lines of admin logic\n}\n\n// AFTER\nif (isAdmin(user)) {\n this.handleAdminUser(user);\n}\n\\`\\`\\`\n\n### Pattern 3: Extract Data Transformations\n\\`\\`\\`typescript\n// BEFORE\nconst result = {\n // 10+ lines of object construction\n};\n\n// AFTER\nconst result = this.buildResultObject(data);\n\\`\\`\\`\n\n## If Refactoring Is Not Feasible\n\nSometimes methods genuinely need to be longer (complex algorithms, state machines, etc.).\n\n**Escape hatch**: Add an eslint-disable comment with justification:\n\n\\`\\`\\`typescript\n// eslint-disable-next-line @webpieces/max-method-lines -- Complex state machine, splitting reduces clarity\nasync complexStateMachine(): Promise<void> {\n // ... longer method with justification\n}\n\\`\\`\\`\n\n## AI Agent Action Steps\n\n1. **READ** the method to understand its logical sections\n2. **IDENTIFY** logical units that can be extracted\n3. **EXTRACT** into well-named private methods\n4. **VERIFY** the main method now reads like a table of contents\n5. **IF NOT FEASIBLE**: Add eslint-disable with clear justification\n\n## Remember\n\n- Every method you write today will be read many times tomorrow\n- The best code explains itself through structure\n- When in doubt, extract and name it\n`;\n\n/**\n * Write the instructions documentation to tmp directory\n */\nfunction writeTmpInstructions(workspaceRoot: string): string {\n const tmpDir = path.join(workspaceRoot, TMP_DIR);\n const mdPath = path.join(tmpDir, TMP_MD_FILE);\n\n fs.mkdirSync(tmpDir, { recursive: true });\n fs.writeFileSync(mdPath, METHODSIZE_DOC_CONTENT);\n\n return mdPath;\n}\n\n/**\n * Get changed TypeScript files between base and head\n */\nfunction getChangedTypeScriptFiles(workspaceRoot: string, base: string, head: string): string[] {\n try {\n const output = execSync(`git diff --name-only ${base}...${head} -- '*.ts' '*.tsx'`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n return output\n .trim()\n .split('\\n')\n .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));\n } catch {\n return [];\n }\n}\n\n/**\n * Get the diff content for a specific file\n */\nfunction getFileDiff(workspaceRoot: string, file: string, base: string, head: string): string {\n try {\n return execSync(`git diff ${base}...${head} -- \"${file}\"`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n } catch {\n return '';\n }\n}\n\n/**\n * Parse diff to find newly added method signatures\n */\nfunction findNewMethodSignaturesInDiff(diffContent: string): Set<string> {\n const newMethods = new Set<string>();\n const lines = diffContent.split('\\n');\n\n // Patterns to match method definitions\n const patterns = [\n // async methodName( or methodName(\n /^\\+\\s*(async\\s+)?(\\w+)\\s*\\(/,\n // function methodName(\n /^\\+\\s*(async\\s+)?function\\s+(\\w+)\\s*\\(/,\n // const/let methodName = (async)? (\n /^\\+\\s*(?:const|let)\\s+(\\w+)\\s*=\\s*(?:async\\s*)?\\(/,\n // const/let methodName = (async)? function\n /^\\+\\s*(?:const|let)\\s+(\\w+)\\s*=\\s*(?:async\\s+)?function/,\n ];\n\n for (const line of lines) {\n if (line.startsWith('+') && !line.startsWith('+++')) {\n for (const pattern of patterns) {\n const match = line.match(pattern);\n if (match) {\n // Extract method name from different capture groups\n const methodName = match[2] || match[1];\n if (methodName && !['if', 'for', 'while', 'switch', 'catch', 'constructor'].includes(methodName)) {\n newMethods.add(methodName);\n }\n break;\n }\n }\n }\n }\n\n return newMethods;\n}\n\n/**\n * Parse a TypeScript file and find methods with their line counts\n */\nfunction findMethodsInFile(\n filePath: string,\n workspaceRoot: string\n): Array<{ name: string; line: number; lines: number }> {\n const fullPath = path.join(workspaceRoot, filePath);\n if (!fs.existsSync(fullPath)) return [];\n\n const content = fs.readFileSync(fullPath, 'utf-8');\n const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);\n\n const methods: Array<{ name: string; line: number; lines: number }> = [];\n\n function visit(node: ts.Node): void {\n let methodName: string | undefined;\n let startLine: number | undefined;\n let endLine: number | undefined;\n\n if (ts.isMethodDeclaration(node) && node.name) {\n methodName = node.name.getText(sourceFile);\n const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());\n startLine = start.line + 1;\n endLine = end.line + 1;\n } else if (ts.isFunctionDeclaration(node) && node.name) {\n methodName = node.name.getText(sourceFile);\n const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());\n startLine = start.line + 1;\n endLine = end.line + 1;\n } else if (ts.isArrowFunction(node)) {\n // Check if it's assigned to a variable\n if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {\n methodName = node.parent.name.getText(sourceFile);\n const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());\n startLine = start.line + 1;\n endLine = end.line + 1;\n }\n }\n\n if (methodName && startLine !== undefined && endLine !== undefined) {\n methods.push({\n name: methodName,\n line: startLine,\n lines: endLine - startLine + 1,\n });\n }\n\n ts.forEachChild(node, visit);\n }\n\n visit(sourceFile);\n return methods;\n}\n\n/**\n * Find new methods that exceed the line limit\n */\nfunction findViolations(\n workspaceRoot: string,\n changedFiles: string[],\n base: string,\n head: string,\n maxLines: number\n): MethodViolation[] {\n const violations: MethodViolation[] = [];\n\n for (const file of changedFiles) {\n // Get the diff to find which methods are NEW (not just modified)\n const diff = getFileDiff(workspaceRoot, file, base, head);\n const newMethodNames = findNewMethodSignaturesInDiff(diff);\n\n if (newMethodNames.size === 0) continue;\n\n // Parse the current file to get method line counts\n const methods = findMethodsInFile(file, workspaceRoot);\n\n for (const method of methods) {\n // Only check NEW methods\n if (newMethodNames.has(method.name) && method.lines > maxLines) {\n violations.push({\n file,\n methodName: method.name,\n line: method.line,\n lines: method.lines,\n isNew: true,\n });\n }\n }\n }\n\n return violations;\n}\n\nexport default async function runExecutor(\n options: ValidateNewMethodsOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n const maxLines = options.max ?? 30;\n\n // Check if running in affected mode\n const base = process.env['NX_BASE'];\n const head = process.env['NX_HEAD'] || 'HEAD';\n\n if (!base) {\n console.log('\\n⏭️ Skipping new method validation (not in affected mode)');\n console.log(' To run: nx affected --target=validate-new-methods --base=origin/main');\n console.log('');\n return { success: true };\n }\n\n console.log('\\n📏 Validating New Method Sizes\\n');\n console.log(` Base: ${base}`);\n console.log(` Head: ${head}`);\n console.log(` Max lines for new methods: ${maxLines}`);\n console.log('');\n\n try {\n // Get changed TypeScript files\n const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base, head);\n\n if (changedFiles.length === 0) {\n console.log('✅ No TypeScript files changed');\n return { success: true };\n }\n\n console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);\n\n // Find violations\n const violations = findViolations(workspaceRoot, changedFiles, base, head, maxLines);\n\n if (violations.length === 0) {\n console.log('✅ All new methods are under ' + maxLines + ' lines');\n return { success: true };\n }\n\n // Write instructions file\n const mdPath = writeTmpInstructions(workspaceRoot);\n\n // Report violations\n console.error('');\n console.error('❌ New methods exceed ' + maxLines + ' lines!');\n console.error('');\n console.error('📚 Methods should read like a \"table of contents\" - each method call');\n console.error(' describes a larger piece of work. ~50% of the time, you can refactor');\n console.error(' to stay under ' + maxLines + ' lines. If not feasible, use the escape hatch.');\n console.error('');\n\n for (const v of violations) {\n console.error(` ❌ ${v.file}:${v.line}`);\n console.error(` Method: ${v.methodName} (${v.lines} lines, max: ${maxLines})`);\n console.error(` READ ${mdPath} to fix this error properly`);\n console.error('');\n }\n\n console.error('💡 To fix:');\n console.error(' 1. Refactor the method to read like a table of contents (preferred)');\n console.error(' 2. OR add eslint-disable comment with justification:');\n console.error(' // eslint-disable-next-line @webpieces/max-method-lines -- [reason]');\n console.error('');\n console.error(`⚠️ *** READ ${mdPath} for detailed guidance *** ⚠️`);\n console.error('');\n\n return { success: false };\n } catch (err: unknown) {\n const error = err instanceof Error ? err : new Error(String(err));\n console.error('❌ New method validation failed:', error.message);\n return { success: false };\n }\n}\n"]}
|