@webpieces/dev-config 0.2.45 → 0.2.46
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-new-methods/executor.d.ts +4 -1
- package/architecture/executors/validate-new-methods/executor.js +113 -47
- package/architecture/executors/validate-new-methods/executor.js.map +1 -1
- package/architecture/executors/validate-new-methods/executor.ts +117 -50
- package/package.json +1 -1
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
* Validate New Methods Executor
|
|
3
3
|
*
|
|
4
4
|
* Validates that newly added methods don't exceed a maximum line count.
|
|
5
|
-
*
|
|
5
|
+
* Runs in affected mode when:
|
|
6
|
+
* 1. NX_BASE environment variable is set (via nx affected), OR
|
|
7
|
+
* 2. Auto-detects base by finding merge-base with origin/main
|
|
6
8
|
*
|
|
7
9
|
* This validator encourages writing methods that read like a "table of contents"
|
|
8
10
|
* where each method call describes a larger piece of work.
|
|
9
11
|
*
|
|
10
12
|
* Usage:
|
|
11
13
|
* nx affected --target=validate-new-methods --base=origin/main
|
|
14
|
+
* OR: runs automatically via build's architecture:validate-complete dependency
|
|
12
15
|
*
|
|
13
16
|
* Escape hatch: Add eslint-disable comment with justification
|
|
14
17
|
*/
|
|
@@ -3,13 +3,16 @@
|
|
|
3
3
|
* Validate New Methods Executor
|
|
4
4
|
*
|
|
5
5
|
* Validates that newly added methods don't exceed a maximum line count.
|
|
6
|
-
*
|
|
6
|
+
* Runs in affected mode when:
|
|
7
|
+
* 1. NX_BASE environment variable is set (via nx affected), OR
|
|
8
|
+
* 2. Auto-detects base by finding merge-base with origin/main
|
|
7
9
|
*
|
|
8
10
|
* This validator encourages writing methods that read like a "table of contents"
|
|
9
11
|
* where each method call describes a larger piece of work.
|
|
10
12
|
*
|
|
11
13
|
* Usage:
|
|
12
14
|
* nx affected --target=validate-new-methods --base=origin/main
|
|
15
|
+
* OR: runs automatically via build's architecture:validate-complete dependency
|
|
13
16
|
*
|
|
14
17
|
* Escape hatch: Add eslint-disable comment with justification
|
|
15
18
|
*/
|
|
@@ -109,10 +112,10 @@ const result = this.buildResultObject(data);
|
|
|
109
112
|
|
|
110
113
|
Sometimes methods genuinely need to be longer (complex algorithms, state machines, etc.).
|
|
111
114
|
|
|
112
|
-
**Escape hatch**: Add
|
|
115
|
+
**Escape hatch**: Add a webpieces-disable comment with justification:
|
|
113
116
|
|
|
114
117
|
\`\`\`typescript
|
|
115
|
-
//
|
|
118
|
+
// webpieces-disable max-method-lines -- Complex state machine, splitting reduces clarity
|
|
116
119
|
async complexStateMachine(): Promise<void> {
|
|
117
120
|
// ... longer method with justification
|
|
118
121
|
}
|
|
@@ -124,7 +127,7 @@ async complexStateMachine(): Promise<void> {
|
|
|
124
127
|
2. **IDENTIFY** logical units that can be extracted
|
|
125
128
|
3. **EXTRACT** into well-named private methods
|
|
126
129
|
4. **VERIFY** the main method now reads like a table of contents
|
|
127
|
-
5. **IF NOT FEASIBLE**: Add
|
|
130
|
+
5. **IF NOT FEASIBLE**: Add webpieces-disable comment with clear justification
|
|
128
131
|
|
|
129
132
|
## Remember
|
|
130
133
|
|
|
@@ -143,11 +146,14 @@ function writeTmpInstructions(workspaceRoot) {
|
|
|
143
146
|
return mdPath;
|
|
144
147
|
}
|
|
145
148
|
/**
|
|
146
|
-
* Get changed TypeScript files between base and
|
|
149
|
+
* Get changed TypeScript files between base and working tree.
|
|
150
|
+
* Uses `git diff base` (no three-dots) to match what `nx affected` does -
|
|
151
|
+
* this includes both committed and uncommitted changes in one diff.
|
|
147
152
|
*/
|
|
148
|
-
function getChangedTypeScriptFiles(workspaceRoot, base
|
|
153
|
+
function getChangedTypeScriptFiles(workspaceRoot, base) {
|
|
149
154
|
try {
|
|
150
|
-
|
|
155
|
+
// Use two-dot diff (base to working tree) - same as nx affected
|
|
156
|
+
const output = (0, child_process_1.execSync)(`git diff --name-only ${base} -- '*.ts' '*.tsx'`, {
|
|
151
157
|
cwd: workspaceRoot,
|
|
152
158
|
encoding: 'utf-8',
|
|
153
159
|
});
|
|
@@ -161,11 +167,14 @@ function getChangedTypeScriptFiles(workspaceRoot, base, head) {
|
|
|
161
167
|
}
|
|
162
168
|
}
|
|
163
169
|
/**
|
|
164
|
-
* Get the diff content for a specific file
|
|
170
|
+
* Get the diff content for a specific file between base and working tree.
|
|
171
|
+
* Uses `git diff base` (no three-dots) to match what `nx affected` does -
|
|
172
|
+
* this includes both committed and uncommitted changes in one diff.
|
|
165
173
|
*/
|
|
166
|
-
function getFileDiff(workspaceRoot, file, base
|
|
174
|
+
function getFileDiff(workspaceRoot, file, base) {
|
|
167
175
|
try {
|
|
168
|
-
|
|
176
|
+
// Use two-dot diff (base to working tree) - same as nx affected
|
|
177
|
+
return (0, child_process_1.execSync)(`git diff ${base} -- "${file}"`, {
|
|
169
178
|
cwd: workspaceRoot,
|
|
170
179
|
encoding: 'utf-8',
|
|
171
180
|
});
|
|
@@ -182,22 +191,22 @@ function findNewMethodSignaturesInDiff(diffContent) {
|
|
|
182
191
|
const lines = diffContent.split('\n');
|
|
183
192
|
// Patterns to match method definitions
|
|
184
193
|
const patterns = [
|
|
185
|
-
// async methodName(
|
|
186
|
-
/^\+\s*(async\s+)?(\w+)\s*\(/,
|
|
187
|
-
//
|
|
188
|
-
/^\+\s*(
|
|
189
|
-
// const/let methodName =
|
|
190
|
-
/^\+\s*(?:const|let)\s+(\w+)\s*=\s*(?:async\s
|
|
191
|
-
//
|
|
192
|
-
/^\+\s*(?:
|
|
194
|
+
// [export] [async] function methodName( - most explicit, check first
|
|
195
|
+
/^\+\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/,
|
|
196
|
+
// [export] const/let methodName = [async] (
|
|
197
|
+
/^\+\s*(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s*)?\(/,
|
|
198
|
+
// [export] const/let methodName = [async] function
|
|
199
|
+
/^\+\s*(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?function/,
|
|
200
|
+
// class method: [async] methodName( - but NOT constructor, if, for, while, etc.
|
|
201
|
+
/^\+\s*(?:async\s+)?(\w+)\s*\(/,
|
|
193
202
|
];
|
|
194
203
|
for (const line of lines) {
|
|
195
204
|
if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
196
205
|
for (const pattern of patterns) {
|
|
197
206
|
const match = line.match(pattern);
|
|
198
207
|
if (match) {
|
|
199
|
-
// Extract method name
|
|
200
|
-
const methodName = match[
|
|
208
|
+
// Extract method name - now always in capture group 1
|
|
209
|
+
const methodName = match[1];
|
|
201
210
|
if (methodName && !['if', 'for', 'while', 'switch', 'catch', 'constructor'].includes(methodName)) {
|
|
202
211
|
newMethods.add(methodName);
|
|
203
212
|
}
|
|
@@ -208,6 +217,25 @@ function findNewMethodSignaturesInDiff(diffContent) {
|
|
|
208
217
|
}
|
|
209
218
|
return newMethods;
|
|
210
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Check if a line contains a webpieces-disable comment for max-method-lines
|
|
222
|
+
*/
|
|
223
|
+
function hasDisableComment(lines, lineNumber) {
|
|
224
|
+
// Check the line before the method (lineNumber is 1-indexed, array is 0-indexed)
|
|
225
|
+
// We need to check a few lines before in case there's JSDoc or decorators
|
|
226
|
+
const startCheck = Math.max(0, lineNumber - 5);
|
|
227
|
+
for (let i = lineNumber - 2; i >= startCheck; i--) {
|
|
228
|
+
const line = lines[i]?.trim() ?? '';
|
|
229
|
+
// Stop if we hit another function/class/etc
|
|
230
|
+
if (line.startsWith('function ') || line.startsWith('class ') || line.endsWith('}')) {
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
if (line.includes('webpieces-disable') && line.includes('max-method-lines')) {
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
211
239
|
/**
|
|
212
240
|
* Parse a TypeScript file and find methods with their line counts
|
|
213
241
|
*/
|
|
@@ -216,6 +244,7 @@ function findMethodsInFile(filePath, workspaceRoot) {
|
|
|
216
244
|
if (!fs.existsSync(fullPath))
|
|
217
245
|
return [];
|
|
218
246
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
247
|
+
const fileLines = content.split('\n');
|
|
219
248
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
220
249
|
const methods = [];
|
|
221
250
|
function visit(node) {
|
|
@@ -251,6 +280,7 @@ function findMethodsInFile(filePath, workspaceRoot) {
|
|
|
251
280
|
name: methodName,
|
|
252
281
|
line: startLine,
|
|
253
282
|
lines: endLine - startLine + 1,
|
|
283
|
+
hasDisableComment: hasDisableComment(fileLines, startLine),
|
|
254
284
|
});
|
|
255
285
|
}
|
|
256
286
|
ts.forEachChild(node, visit);
|
|
@@ -261,19 +291,19 @@ function findMethodsInFile(filePath, workspaceRoot) {
|
|
|
261
291
|
/**
|
|
262
292
|
* Find new methods that exceed the line limit
|
|
263
293
|
*/
|
|
264
|
-
function findViolations(workspaceRoot, changedFiles, base,
|
|
294
|
+
function findViolations(workspaceRoot, changedFiles, base, maxLines) {
|
|
265
295
|
const violations = [];
|
|
266
296
|
for (const file of changedFiles) {
|
|
267
297
|
// Get the diff to find which methods are NEW (not just modified)
|
|
268
|
-
const diff = getFileDiff(workspaceRoot, file, base
|
|
298
|
+
const diff = getFileDiff(workspaceRoot, file, base);
|
|
269
299
|
const newMethodNames = findNewMethodSignaturesInDiff(diff);
|
|
270
300
|
if (newMethodNames.size === 0)
|
|
271
301
|
continue;
|
|
272
302
|
// Parse the current file to get method line counts
|
|
273
303
|
const methods = findMethodsInFile(file, workspaceRoot);
|
|
274
304
|
for (const method of methods) {
|
|
275
|
-
// Only check NEW methods
|
|
276
|
-
if (newMethodNames.has(method.name) && method.lines > maxLines) {
|
|
305
|
+
// Only check NEW methods that don't have webpieces-disable comment
|
|
306
|
+
if (newMethodNames.has(method.name) && method.lines > maxLines && !method.hasDisableComment) {
|
|
277
307
|
violations.push({
|
|
278
308
|
file,
|
|
279
309
|
methodName: method.name,
|
|
@@ -286,59 +316,95 @@ function findViolations(workspaceRoot, changedFiles, base, head, maxLines) {
|
|
|
286
316
|
}
|
|
287
317
|
return violations;
|
|
288
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* Auto-detect the base branch by finding the merge-base with origin/main.
|
|
321
|
+
* This allows the executor to run even when NX_BASE isn't set (e.g., via dependsOn).
|
|
322
|
+
*/
|
|
323
|
+
function detectBase(workspaceRoot) {
|
|
324
|
+
try {
|
|
325
|
+
// First, try to get merge-base with origin/main
|
|
326
|
+
const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD origin/main', {
|
|
327
|
+
cwd: workspaceRoot,
|
|
328
|
+
encoding: 'utf-8',
|
|
329
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
330
|
+
}).trim();
|
|
331
|
+
if (mergeBase) {
|
|
332
|
+
return mergeBase;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
// origin/main might not exist, try main
|
|
337
|
+
try {
|
|
338
|
+
const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD main', {
|
|
339
|
+
cwd: workspaceRoot,
|
|
340
|
+
encoding: 'utf-8',
|
|
341
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
342
|
+
}).trim();
|
|
343
|
+
if (mergeBase) {
|
|
344
|
+
return mergeBase;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
// Ignore - will return null
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
289
353
|
async function runExecutor(options, context) {
|
|
290
354
|
const workspaceRoot = context.root;
|
|
291
355
|
const maxLines = options.max ?? 30;
|
|
292
|
-
// Check if running in affected mode
|
|
293
|
-
|
|
294
|
-
|
|
356
|
+
// Check if running in affected mode via NX_BASE, or auto-detect
|
|
357
|
+
// We use NX_BASE as the base, and compare to WORKING TREE (not NX_HEAD)
|
|
358
|
+
// This matches what `nx affected` does - it compares base to working tree
|
|
359
|
+
let base = process.env['NX_BASE'];
|
|
295
360
|
if (!base) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
361
|
+
// Try to auto-detect base from git merge-base
|
|
362
|
+
base = detectBase(workspaceRoot) ?? undefined;
|
|
363
|
+
if (!base) {
|
|
364
|
+
console.log('\n⏭️ Skipping new method validation (could not detect base branch)');
|
|
365
|
+
console.log(' To run explicitly: nx affected --target=validate-new-methods --base=origin/main');
|
|
366
|
+
console.log('');
|
|
367
|
+
return { success: true };
|
|
368
|
+
}
|
|
369
|
+
console.log('\n📏 Validating New Method Sizes (auto-detected base)\n');
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
console.log('\n📏 Validating New Method Sizes\n');
|
|
300
373
|
}
|
|
301
|
-
console.log('\n📏 Validating New Method Sizes\n');
|
|
302
374
|
console.log(` Base: ${base}`);
|
|
303
|
-
console.log(`
|
|
375
|
+
console.log(` Comparing to: working tree (includes uncommitted changes)`);
|
|
304
376
|
console.log(` Max lines for new methods: ${maxLines}`);
|
|
305
377
|
console.log('');
|
|
306
378
|
try {
|
|
307
|
-
// Get changed TypeScript files
|
|
308
|
-
const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base
|
|
379
|
+
// Get changed TypeScript files (base to working tree, like nx affected)
|
|
380
|
+
const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base);
|
|
309
381
|
if (changedFiles.length === 0) {
|
|
310
382
|
console.log('✅ No TypeScript files changed');
|
|
311
383
|
return { success: true };
|
|
312
384
|
}
|
|
313
385
|
console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);
|
|
314
386
|
// Find violations
|
|
315
|
-
const violations = findViolations(workspaceRoot, changedFiles, base,
|
|
387
|
+
const violations = findViolations(workspaceRoot, changedFiles, base, maxLines);
|
|
316
388
|
if (violations.length === 0) {
|
|
317
389
|
console.log('✅ All new methods are under ' + maxLines + ' lines');
|
|
318
390
|
return { success: true };
|
|
319
391
|
}
|
|
320
392
|
// Write instructions file
|
|
321
|
-
|
|
393
|
+
writeTmpInstructions(workspaceRoot);
|
|
322
394
|
// Report violations
|
|
323
395
|
console.error('');
|
|
324
396
|
console.error('❌ New methods exceed ' + maxLines + ' lines!');
|
|
325
397
|
console.error('');
|
|
326
398
|
console.error('📚 Methods should read like a "table of contents" - each method call');
|
|
327
|
-
console.error(' describes a larger piece of work.
|
|
328
|
-
console.error(' to stay under ' + maxLines + ' lines. If not feasible, use the escape hatch.');
|
|
399
|
+
console.error(' describes a larger piece of work. You can refactor');
|
|
400
|
+
console.error(' to stay under ' + maxLines + ' lines 50% of the time. If not feasible, use the escape hatch.');
|
|
401
|
+
console.error('');
|
|
402
|
+
console.error('⚠️ *** READ tmp/webpieces/webpieces.methodsize.md for detailed guidance on how to fix this easily *** ⚠️');
|
|
329
403
|
console.error('');
|
|
330
404
|
for (const v of violations) {
|
|
331
405
|
console.error(` ❌ ${v.file}:${v.line}`);
|
|
332
406
|
console.error(` Method: ${v.methodName} (${v.lines} lines, max: ${maxLines})`);
|
|
333
|
-
console.error(` READ ${mdPath} to fix this error properly`);
|
|
334
|
-
console.error('');
|
|
335
407
|
}
|
|
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
408
|
console.error('');
|
|
343
409
|
return { success: false };
|
|
344
410
|
}
|
|
@@ -1 +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"]}
|
|
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 * Runs in affected mode when:\n * 1. NX_BASE environment variable is set (via nx affected), OR\n * 2. Auto-detects base by finding merge-base with origin/main\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 * OR: runs automatically via build's architecture:validate-complete dependency\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 a webpieces-disable comment with justification:\n\n\\`\\`\\`typescript\n// webpieces-disable 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 webpieces-disable comment 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 working tree.\n * Uses `git diff base` (no three-dots) to match what `nx affected` does -\n * this includes both committed and uncommitted changes in one diff.\n */\nfunction getChangedTypeScriptFiles(workspaceRoot: string, base: string): string[] {\n try {\n // Use two-dot diff (base to working tree) - same as nx affected\n const output = execSync(`git diff --name-only ${base} -- '*.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 between base and working tree.\n * Uses `git diff base` (no three-dots) to match what `nx affected` does -\n * this includes both committed and uncommitted changes in one diff.\n */\nfunction getFileDiff(workspaceRoot: string, file: string, base: string): string {\n try {\n // Use two-dot diff (base to working tree) - same as nx affected\n return execSync(`git diff ${base} -- \"${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 // [export] [async] function methodName( - most explicit, check first\n /^\\+\\s*(?:export\\s+)?(?:async\\s+)?function\\s+(\\w+)\\s*\\(/,\n // [export] const/let methodName = [async] (\n /^\\+\\s*(?:export\\s+)?(?:const|let)\\s+(\\w+)\\s*=\\s*(?:async\\s*)?\\(/,\n // [export] const/let methodName = [async] function\n /^\\+\\s*(?:export\\s+)?(?:const|let)\\s+(\\w+)\\s*=\\s*(?:async\\s+)?function/,\n // class method: [async] methodName( - but NOT constructor, if, for, while, etc.\n /^\\+\\s*(?:async\\s+)?(\\w+)\\s*\\(/,\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 - now always in capture group 1\n const methodName = 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 * Check if a line contains a webpieces-disable comment for max-method-lines\n */\nfunction hasDisableComment(lines: string[], lineNumber: number): boolean {\n // Check the line before the method (lineNumber is 1-indexed, array is 0-indexed)\n // We need to check a few lines before in case there's JSDoc or decorators\n const startCheck = Math.max(0, lineNumber - 5);\n for (let i = lineNumber - 2; i >= startCheck; i--) {\n const line = lines[i]?.trim() ?? '';\n // Stop if we hit another function/class/etc\n if (line.startsWith('function ') || line.startsWith('class ') || line.endsWith('}')) {\n break;\n }\n if (line.includes('webpieces-disable') && line.includes('max-method-lines')) {\n return true;\n }\n }\n return false;\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; hasDisableComment: boolean }> {\n const fullPath = path.join(workspaceRoot, filePath);\n if (!fs.existsSync(fullPath)) return [];\n\n const content = fs.readFileSync(fullPath, 'utf-8');\n const fileLines = content.split('\\n');\n const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);\n\n const methods: Array<{ name: string; line: number; lines: number; hasDisableComment: boolean }> = [];\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 hasDisableComment: hasDisableComment(fileLines, startLine),\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 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);\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 that don't have webpieces-disable comment\n if (newMethodNames.has(method.name) && method.lines > maxLines && !method.hasDisableComment) {\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\n/**\n * Auto-detect the base branch by finding the merge-base with origin/main.\n * This allows the executor to run even when NX_BASE isn't set (e.g., via dependsOn).\n */\nfunction detectBase(workspaceRoot: string): string | null {\n try {\n // First, try to get merge-base with origin/main\n const mergeBase = execSync('git merge-base HEAD origin/main', {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n if (mergeBase) {\n return mergeBase;\n }\n } catch {\n // origin/main might not exist, try main\n try {\n const mergeBase = execSync('git merge-base HEAD main', {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n if (mergeBase) {\n return mergeBase;\n }\n } catch {\n // Ignore - will return null\n }\n }\n return null;\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 via NX_BASE, or auto-detect\n // We use NX_BASE as the base, and compare to WORKING TREE (not NX_HEAD)\n // This matches what `nx affected` does - it compares base to working tree\n let base = process.env['NX_BASE'];\n\n if (!base) {\n // Try to auto-detect base from git merge-base\n base = detectBase(workspaceRoot) ?? undefined;\n\n if (!base) {\n console.log('\\n⏭️ Skipping new method validation (could not detect base branch)');\n console.log(' To run explicitly: 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 (auto-detected base)\\n');\n } else {\n console.log('\\n📏 Validating New Method Sizes\\n');\n }\n\n console.log(` Base: ${base}`);\n console.log(` Comparing to: working tree (includes uncommitted changes)`);\n console.log(` Max lines for new methods: ${maxLines}`);\n console.log('');\n\n try {\n // Get changed TypeScript files (base to working tree, like nx affected)\n const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base);\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, 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 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. You can refactor');\n console.error(' to stay under ' + maxLines + ' lines 50% of the time. If not feasible, use the escape hatch.');\n console.error('');\n console.error('⚠️ *** READ tmp/webpieces/webpieces.methodsize.md for detailed guidance on how to fix this easily *** ⚠️');\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 }\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"]}
|
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
* Validate New Methods Executor
|
|
3
3
|
*
|
|
4
4
|
* Validates that newly added methods don't exceed a maximum line count.
|
|
5
|
-
*
|
|
5
|
+
* Runs in affected mode when:
|
|
6
|
+
* 1. NX_BASE environment variable is set (via nx affected), OR
|
|
7
|
+
* 2. Auto-detects base by finding merge-base with origin/main
|
|
6
8
|
*
|
|
7
9
|
* This validator encourages writing methods that read like a "table of contents"
|
|
8
10
|
* where each method call describes a larger piece of work.
|
|
9
11
|
*
|
|
10
12
|
* Usage:
|
|
11
13
|
* nx affected --target=validate-new-methods --base=origin/main
|
|
14
|
+
* OR: runs automatically via build's architecture:validate-complete dependency
|
|
12
15
|
*
|
|
13
16
|
* Escape hatch: Add eslint-disable comment with justification
|
|
14
17
|
*/
|
|
@@ -125,10 +128,10 @@ const result = this.buildResultObject(data);
|
|
|
125
128
|
|
|
126
129
|
Sometimes methods genuinely need to be longer (complex algorithms, state machines, etc.).
|
|
127
130
|
|
|
128
|
-
**Escape hatch**: Add
|
|
131
|
+
**Escape hatch**: Add a webpieces-disable comment with justification:
|
|
129
132
|
|
|
130
133
|
\`\`\`typescript
|
|
131
|
-
//
|
|
134
|
+
// webpieces-disable max-method-lines -- Complex state machine, splitting reduces clarity
|
|
132
135
|
async complexStateMachine(): Promise<void> {
|
|
133
136
|
// ... longer method with justification
|
|
134
137
|
}
|
|
@@ -140,7 +143,7 @@ async complexStateMachine(): Promise<void> {
|
|
|
140
143
|
2. **IDENTIFY** logical units that can be extracted
|
|
141
144
|
3. **EXTRACT** into well-named private methods
|
|
142
145
|
4. **VERIFY** the main method now reads like a table of contents
|
|
143
|
-
5. **IF NOT FEASIBLE**: Add
|
|
146
|
+
5. **IF NOT FEASIBLE**: Add webpieces-disable comment with clear justification
|
|
144
147
|
|
|
145
148
|
## Remember
|
|
146
149
|
|
|
@@ -163,11 +166,14 @@ function writeTmpInstructions(workspaceRoot: string): string {
|
|
|
163
166
|
}
|
|
164
167
|
|
|
165
168
|
/**
|
|
166
|
-
* Get changed TypeScript files between base and
|
|
169
|
+
* Get changed TypeScript files between base and working tree.
|
|
170
|
+
* Uses `git diff base` (no three-dots) to match what `nx affected` does -
|
|
171
|
+
* this includes both committed and uncommitted changes in one diff.
|
|
167
172
|
*/
|
|
168
|
-
function getChangedTypeScriptFiles(workspaceRoot: string, base: string
|
|
173
|
+
function getChangedTypeScriptFiles(workspaceRoot: string, base: string): string[] {
|
|
169
174
|
try {
|
|
170
|
-
|
|
175
|
+
// Use two-dot diff (base to working tree) - same as nx affected
|
|
176
|
+
const output = execSync(`git diff --name-only ${base} -- '*.ts' '*.tsx'`, {
|
|
171
177
|
cwd: workspaceRoot,
|
|
172
178
|
encoding: 'utf-8',
|
|
173
179
|
});
|
|
@@ -181,11 +187,14 @@ function getChangedTypeScriptFiles(workspaceRoot: string, base: string, head: st
|
|
|
181
187
|
}
|
|
182
188
|
|
|
183
189
|
/**
|
|
184
|
-
* Get the diff content for a specific file
|
|
190
|
+
* Get the diff content for a specific file between base and working tree.
|
|
191
|
+
* Uses `git diff base` (no three-dots) to match what `nx affected` does -
|
|
192
|
+
* this includes both committed and uncommitted changes in one diff.
|
|
185
193
|
*/
|
|
186
|
-
function getFileDiff(workspaceRoot: string, file: string, base: string
|
|
194
|
+
function getFileDiff(workspaceRoot: string, file: string, base: string): string {
|
|
187
195
|
try {
|
|
188
|
-
|
|
196
|
+
// Use two-dot diff (base to working tree) - same as nx affected
|
|
197
|
+
return execSync(`git diff ${base} -- "${file}"`, {
|
|
189
198
|
cwd: workspaceRoot,
|
|
190
199
|
encoding: 'utf-8',
|
|
191
200
|
});
|
|
@@ -203,14 +212,14 @@ function findNewMethodSignaturesInDiff(diffContent: string): Set<string> {
|
|
|
203
212
|
|
|
204
213
|
// Patterns to match method definitions
|
|
205
214
|
const patterns = [
|
|
206
|
-
// async methodName(
|
|
207
|
-
/^\+\s*(async\s+)?(\w+)\s*\(/,
|
|
208
|
-
//
|
|
209
|
-
/^\+\s*(
|
|
210
|
-
// const/let methodName =
|
|
211
|
-
/^\+\s*(?:const|let)\s+(\w+)\s*=\s*(?:async\s
|
|
212
|
-
//
|
|
213
|
-
/^\+\s*(?:
|
|
215
|
+
// [export] [async] function methodName( - most explicit, check first
|
|
216
|
+
/^\+\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/,
|
|
217
|
+
// [export] const/let methodName = [async] (
|
|
218
|
+
/^\+\s*(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s*)?\(/,
|
|
219
|
+
// [export] const/let methodName = [async] function
|
|
220
|
+
/^\+\s*(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?function/,
|
|
221
|
+
// class method: [async] methodName( - but NOT constructor, if, for, while, etc.
|
|
222
|
+
/^\+\s*(?:async\s+)?(\w+)\s*\(/,
|
|
214
223
|
];
|
|
215
224
|
|
|
216
225
|
for (const line of lines) {
|
|
@@ -218,8 +227,8 @@ function findNewMethodSignaturesInDiff(diffContent: string): Set<string> {
|
|
|
218
227
|
for (const pattern of patterns) {
|
|
219
228
|
const match = line.match(pattern);
|
|
220
229
|
if (match) {
|
|
221
|
-
// Extract method name
|
|
222
|
-
const methodName = match[
|
|
230
|
+
// Extract method name - now always in capture group 1
|
|
231
|
+
const methodName = match[1];
|
|
223
232
|
if (methodName && !['if', 'for', 'while', 'switch', 'catch', 'constructor'].includes(methodName)) {
|
|
224
233
|
newMethods.add(methodName);
|
|
225
234
|
}
|
|
@@ -232,20 +241,41 @@ function findNewMethodSignaturesInDiff(diffContent: string): Set<string> {
|
|
|
232
241
|
return newMethods;
|
|
233
242
|
}
|
|
234
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Check if a line contains a webpieces-disable comment for max-method-lines
|
|
246
|
+
*/
|
|
247
|
+
function hasDisableComment(lines: string[], lineNumber: number): boolean {
|
|
248
|
+
// Check the line before the method (lineNumber is 1-indexed, array is 0-indexed)
|
|
249
|
+
// We need to check a few lines before in case there's JSDoc or decorators
|
|
250
|
+
const startCheck = Math.max(0, lineNumber - 5);
|
|
251
|
+
for (let i = lineNumber - 2; i >= startCheck; i--) {
|
|
252
|
+
const line = lines[i]?.trim() ?? '';
|
|
253
|
+
// Stop if we hit another function/class/etc
|
|
254
|
+
if (line.startsWith('function ') || line.startsWith('class ') || line.endsWith('}')) {
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
if (line.includes('webpieces-disable') && line.includes('max-method-lines')) {
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
|
|
235
264
|
/**
|
|
236
265
|
* Parse a TypeScript file and find methods with their line counts
|
|
237
266
|
*/
|
|
238
267
|
function findMethodsInFile(
|
|
239
268
|
filePath: string,
|
|
240
269
|
workspaceRoot: string
|
|
241
|
-
): Array<{ name: string; line: number; lines: number }> {
|
|
270
|
+
): Array<{ name: string; line: number; lines: number; hasDisableComment: boolean }> {
|
|
242
271
|
const fullPath = path.join(workspaceRoot, filePath);
|
|
243
272
|
if (!fs.existsSync(fullPath)) return [];
|
|
244
273
|
|
|
245
274
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
275
|
+
const fileLines = content.split('\n');
|
|
246
276
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
247
277
|
|
|
248
|
-
const methods: Array<{ name: string; line: number; lines: number }> = [];
|
|
278
|
+
const methods: Array<{ name: string; line: number; lines: number; hasDisableComment: boolean }> = [];
|
|
249
279
|
|
|
250
280
|
function visit(node: ts.Node): void {
|
|
251
281
|
let methodName: string | undefined;
|
|
@@ -280,6 +310,7 @@ function findMethodsInFile(
|
|
|
280
310
|
name: methodName,
|
|
281
311
|
line: startLine,
|
|
282
312
|
lines: endLine - startLine + 1,
|
|
313
|
+
hasDisableComment: hasDisableComment(fileLines, startLine),
|
|
283
314
|
});
|
|
284
315
|
}
|
|
285
316
|
|
|
@@ -297,14 +328,13 @@ function findViolations(
|
|
|
297
328
|
workspaceRoot: string,
|
|
298
329
|
changedFiles: string[],
|
|
299
330
|
base: string,
|
|
300
|
-
head: string,
|
|
301
331
|
maxLines: number
|
|
302
332
|
): MethodViolation[] {
|
|
303
333
|
const violations: MethodViolation[] = [];
|
|
304
334
|
|
|
305
335
|
for (const file of changedFiles) {
|
|
306
336
|
// Get the diff to find which methods are NEW (not just modified)
|
|
307
|
-
const diff = getFileDiff(workspaceRoot, file, base
|
|
337
|
+
const diff = getFileDiff(workspaceRoot, file, base);
|
|
308
338
|
const newMethodNames = findNewMethodSignaturesInDiff(diff);
|
|
309
339
|
|
|
310
340
|
if (newMethodNames.size === 0) continue;
|
|
@@ -313,8 +343,8 @@ function findViolations(
|
|
|
313
343
|
const methods = findMethodsInFile(file, workspaceRoot);
|
|
314
344
|
|
|
315
345
|
for (const method of methods) {
|
|
316
|
-
// Only check NEW methods
|
|
317
|
-
if (newMethodNames.has(method.name) && method.lines > maxLines) {
|
|
346
|
+
// Only check NEW methods that don't have webpieces-disable comment
|
|
347
|
+
if (newMethodNames.has(method.name) && method.lines > maxLines && !method.hasDisableComment) {
|
|
318
348
|
violations.push({
|
|
319
349
|
file,
|
|
320
350
|
methodName: method.name,
|
|
@@ -329,6 +359,41 @@ function findViolations(
|
|
|
329
359
|
return violations;
|
|
330
360
|
}
|
|
331
361
|
|
|
362
|
+
/**
|
|
363
|
+
* Auto-detect the base branch by finding the merge-base with origin/main.
|
|
364
|
+
* This allows the executor to run even when NX_BASE isn't set (e.g., via dependsOn).
|
|
365
|
+
*/
|
|
366
|
+
function detectBase(workspaceRoot: string): string | null {
|
|
367
|
+
try {
|
|
368
|
+
// First, try to get merge-base with origin/main
|
|
369
|
+
const mergeBase = execSync('git merge-base HEAD origin/main', {
|
|
370
|
+
cwd: workspaceRoot,
|
|
371
|
+
encoding: 'utf-8',
|
|
372
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
373
|
+
}).trim();
|
|
374
|
+
|
|
375
|
+
if (mergeBase) {
|
|
376
|
+
return mergeBase;
|
|
377
|
+
}
|
|
378
|
+
} catch {
|
|
379
|
+
// origin/main might not exist, try main
|
|
380
|
+
try {
|
|
381
|
+
const mergeBase = execSync('git merge-base HEAD main', {
|
|
382
|
+
cwd: workspaceRoot,
|
|
383
|
+
encoding: 'utf-8',
|
|
384
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
385
|
+
}).trim();
|
|
386
|
+
|
|
387
|
+
if (mergeBase) {
|
|
388
|
+
return mergeBase;
|
|
389
|
+
}
|
|
390
|
+
} catch {
|
|
391
|
+
// Ignore - will return null
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
|
|
332
397
|
export default async function runExecutor(
|
|
333
398
|
options: ValidateNewMethodsOptions,
|
|
334
399
|
context: ExecutorContext
|
|
@@ -336,26 +401,35 @@ export default async function runExecutor(
|
|
|
336
401
|
const workspaceRoot = context.root;
|
|
337
402
|
const maxLines = options.max ?? 30;
|
|
338
403
|
|
|
339
|
-
// Check if running in affected mode
|
|
340
|
-
|
|
341
|
-
|
|
404
|
+
// Check if running in affected mode via NX_BASE, or auto-detect
|
|
405
|
+
// We use NX_BASE as the base, and compare to WORKING TREE (not NX_HEAD)
|
|
406
|
+
// This matches what `nx affected` does - it compares base to working tree
|
|
407
|
+
let base = process.env['NX_BASE'];
|
|
342
408
|
|
|
343
409
|
if (!base) {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
410
|
+
// Try to auto-detect base from git merge-base
|
|
411
|
+
base = detectBase(workspaceRoot) ?? undefined;
|
|
412
|
+
|
|
413
|
+
if (!base) {
|
|
414
|
+
console.log('\n⏭️ Skipping new method validation (could not detect base branch)');
|
|
415
|
+
console.log(' To run explicitly: nx affected --target=validate-new-methods --base=origin/main');
|
|
416
|
+
console.log('');
|
|
417
|
+
return { success: true };
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
console.log('\n📏 Validating New Method Sizes (auto-detected base)\n');
|
|
421
|
+
} else {
|
|
422
|
+
console.log('\n📏 Validating New Method Sizes\n');
|
|
348
423
|
}
|
|
349
424
|
|
|
350
|
-
console.log('\n📏 Validating New Method Sizes\n');
|
|
351
425
|
console.log(` Base: ${base}`);
|
|
352
|
-
console.log(`
|
|
426
|
+
console.log(` Comparing to: working tree (includes uncommitted changes)`);
|
|
353
427
|
console.log(` Max lines for new methods: ${maxLines}`);
|
|
354
428
|
console.log('');
|
|
355
429
|
|
|
356
430
|
try {
|
|
357
|
-
// Get changed TypeScript files
|
|
358
|
-
const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base
|
|
431
|
+
// Get changed TypeScript files (base to working tree, like nx affected)
|
|
432
|
+
const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base);
|
|
359
433
|
|
|
360
434
|
if (changedFiles.length === 0) {
|
|
361
435
|
console.log('✅ No TypeScript files changed');
|
|
@@ -365,7 +439,7 @@ export default async function runExecutor(
|
|
|
365
439
|
console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);
|
|
366
440
|
|
|
367
441
|
// Find violations
|
|
368
|
-
const violations = findViolations(workspaceRoot, changedFiles, base,
|
|
442
|
+
const violations = findViolations(workspaceRoot, changedFiles, base, maxLines);
|
|
369
443
|
|
|
370
444
|
if (violations.length === 0) {
|
|
371
445
|
console.log('✅ All new methods are under ' + maxLines + ' lines');
|
|
@@ -373,30 +447,23 @@ export default async function runExecutor(
|
|
|
373
447
|
}
|
|
374
448
|
|
|
375
449
|
// Write instructions file
|
|
376
|
-
|
|
450
|
+
writeTmpInstructions(workspaceRoot);
|
|
377
451
|
|
|
378
452
|
// Report violations
|
|
379
453
|
console.error('');
|
|
380
454
|
console.error('❌ New methods exceed ' + maxLines + ' lines!');
|
|
381
455
|
console.error('');
|
|
382
456
|
console.error('📚 Methods should read like a "table of contents" - each method call');
|
|
383
|
-
console.error(' describes a larger piece of work.
|
|
384
|
-
console.error(' to stay under ' + maxLines + ' lines. If not feasible, use the escape hatch.');
|
|
457
|
+
console.error(' describes a larger piece of work. You can refactor');
|
|
458
|
+
console.error(' to stay under ' + maxLines + ' lines 50% of the time. If not feasible, use the escape hatch.');
|
|
459
|
+
console.error('');
|
|
460
|
+
console.error('⚠️ *** READ tmp/webpieces/webpieces.methodsize.md for detailed guidance on how to fix this easily *** ⚠️');
|
|
385
461
|
console.error('');
|
|
386
462
|
|
|
387
463
|
for (const v of violations) {
|
|
388
464
|
console.error(` ❌ ${v.file}:${v.line}`);
|
|
389
465
|
console.error(` Method: ${v.methodName} (${v.lines} lines, max: ${maxLines})`);
|
|
390
|
-
console.error(` READ ${mdPath} to fix this error properly`);
|
|
391
|
-
console.error('');
|
|
392
466
|
}
|
|
393
|
-
|
|
394
|
-
console.error('💡 To fix:');
|
|
395
|
-
console.error(' 1. Refactor the method to read like a table of contents (preferred)');
|
|
396
|
-
console.error(' 2. OR add eslint-disable comment with justification:');
|
|
397
|
-
console.error(' // eslint-disable-next-line @webpieces/max-method-lines -- [reason]');
|
|
398
|
-
console.error('');
|
|
399
|
-
console.error(`⚠️ *** READ ${mdPath} for detailed guidance *** ⚠️`);
|
|
400
467
|
console.error('');
|
|
401
468
|
|
|
402
469
|
return { success: false };
|