@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.
@@ -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
- * Only runs when NX_BASE environment variable is set (affected mode).
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
- * Only runs when NX_BASE environment variable is set (affected mode).
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 an eslint-disable comment with justification:
115
+ **Escape hatch**: Add a webpieces-disable comment with justification:
113
116
 
114
117
  \`\`\`typescript
115
- // eslint-disable-next-line @webpieces/max-method-lines -- Complex state machine, splitting reduces clarity
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 eslint-disable with clear justification
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 head
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, head) {
153
+ function getChangedTypeScriptFiles(workspaceRoot, base) {
149
154
  try {
150
- const output = (0, child_process_1.execSync)(`git diff --name-only ${base}...${head} -- '*.ts' '*.tsx'`, {
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, head) {
174
+ function getFileDiff(workspaceRoot, file, base) {
167
175
  try {
168
- return (0, child_process_1.execSync)(`git diff ${base}...${head} -- "${file}"`, {
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( 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/,
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 from different capture groups
200
- const methodName = match[2] || match[1];
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, head, maxLines) {
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, head);
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
- const base = process.env['NX_BASE'];
294
- const head = process.env['NX_HEAD'] || 'HEAD';
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
- 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 };
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(` Head: ${head}`);
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, head);
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, head, maxLines);
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
- const mdPath = writeTmpInstructions(workspaceRoot);
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. ~50% of the time, you can refactor');
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
- * Only runs when NX_BASE environment variable is set (affected mode).
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 an eslint-disable comment with justification:
131
+ **Escape hatch**: Add a webpieces-disable comment with justification:
129
132
 
130
133
  \`\`\`typescript
131
- // eslint-disable-next-line @webpieces/max-method-lines -- Complex state machine, splitting reduces clarity
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 eslint-disable with clear justification
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 head
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, head: string): string[] {
173
+ function getChangedTypeScriptFiles(workspaceRoot: string, base: string): string[] {
169
174
  try {
170
- const output = execSync(`git diff --name-only ${base}...${head} -- '*.ts' '*.tsx'`, {
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, head: string): string {
194
+ function getFileDiff(workspaceRoot: string, file: string, base: string): string {
187
195
  try {
188
- return execSync(`git diff ${base}...${head} -- "${file}"`, {
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( or methodName(
207
- /^\+\s*(async\s+)?(\w+)\s*\(/,
208
- // function methodName(
209
- /^\+\s*(async\s+)?function\s+(\w+)\s*\(/,
210
- // const/let methodName = (async)? (
211
- /^\+\s*(?:const|let)\s+(\w+)\s*=\s*(?:async\s*)?\(/,
212
- // const/let methodName = (async)? function
213
- /^\+\s*(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?function/,
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 from different capture groups
222
- const methodName = match[2] || match[1];
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, head);
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
- const base = process.env['NX_BASE'];
341
- const head = process.env['NX_HEAD'] || 'HEAD';
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
- console.log('\n⏭️ Skipping new method validation (not in affected mode)');
345
- console.log(' To run: nx affected --target=validate-new-methods --base=origin/main');
346
- console.log('');
347
- return { success: true };
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(` Head: ${head}`);
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, head);
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, head, maxLines);
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
- const mdPath = writeTmpInstructions(workspaceRoot);
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. ~50% of the time, you can refactor');
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/dev-config",
3
- "version": "0.2.45",
3
+ "version": "0.2.46",
4
4
  "description": "Development configuration, scripts, and patterns for WebPieces projects",
5
5
  "type": "commonjs",
6
6
  "bin": {