@webpieces/dev-config 0.2.70 → 0.2.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import { ExecutorContext } from '@nx/devkit';
2
+ import { ReturnTypeMode } from '../validate-return-types/executor';
2
3
  export type ValidationMode = 'STRICT' | 'NORMAL' | 'OFF';
3
4
  export interface ValidateCodeOptions {
4
5
  mode?: ValidationMode;
@@ -6,6 +7,7 @@ export interface ValidateCodeOptions {
6
7
  strictNewMethodMaxLines?: number;
7
8
  modifiedMethodsMaxLines?: number;
8
9
  modifiedFilesMaxLines?: number;
10
+ requireReturnTypeMode?: ReturnTypeMode;
9
11
  }
10
12
  export interface ExecutorResult {
11
13
  success: boolean;
@@ -5,12 +5,14 @@ const tslib_1 = require("tslib");
5
5
  const executor_1 = tslib_1.__importDefault(require("../validate-new-methods/executor"));
6
6
  const executor_2 = tslib_1.__importDefault(require("../validate-modified-methods/executor"));
7
7
  const executor_3 = tslib_1.__importDefault(require("../validate-modified-files/executor"));
8
+ const executor_4 = tslib_1.__importDefault(require("../validate-return-types/executor"));
8
9
  async function runExecutor(options, context) {
9
10
  const mode = options.mode ?? 'NORMAL';
10
11
  if (mode === 'OFF') {
11
12
  console.log('\n⏭️ Skipping all code validations (validationMode: OFF)\n');
12
13
  return { success: true };
13
14
  }
15
+ const returnTypeMode = options.requireReturnTypeMode ?? 'OFF';
14
16
  console.log('\n📏 Running Code Validations\n');
15
17
  console.log(` Validation mode: ${mode}${mode === 'STRICT' ? ' (disable comments ignored for modified code)' : ''}`);
16
18
  console.log(` New methods max: ${options.newMethodsMaxLines ?? 30} lines (soft limit)`);
@@ -19,12 +21,17 @@ async function runExecutor(options, context) {
19
21
  }
20
22
  console.log(` Modified methods max: ${options.modifiedMethodsMaxLines ?? 80} lines`);
21
23
  console.log(` Modified files max: ${options.modifiedFilesMaxLines ?? 900} lines`);
24
+ console.log(` Require return types: ${returnTypeMode}`);
22
25
  console.log('');
23
26
  // Run all three validators sequentially to avoid interleaved output
24
27
  const newMethodsResult = await (0, executor_1.default)({ max: options.newMethodsMaxLines ?? 30, strictMax: options.strictNewMethodMaxLines, mode }, context);
25
28
  const modifiedMethodsResult = await (0, executor_2.default)({ max: options.modifiedMethodsMaxLines ?? 80, mode }, context);
26
29
  const modifiedFilesResult = await (0, executor_3.default)({ max: options.modifiedFilesMaxLines ?? 900, mode }, context);
27
- const allSuccess = newMethodsResult.success && modifiedMethodsResult.success && modifiedFilesResult.success;
30
+ const returnTypesResult = await (0, executor_4.default)({ mode: returnTypeMode }, context);
31
+ const allSuccess = newMethodsResult.success &&
32
+ modifiedMethodsResult.success &&
33
+ modifiedFilesResult.success &&
34
+ returnTypesResult.success;
28
35
  if (allSuccess) {
29
36
  console.log('\n✅ All code validations passed\n');
30
37
  }
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/dev-config/architecture/executors/validate-code/executor.ts"],"names":[],"mappings":";;AAmBA,8BA8CC;;AAhED,wFAAqE;AACrE,6FAA+E;AAC/E,2FAA2E;AAgB5D,KAAK,UAAU,WAAW,CACrC,OAA4B,EAC5B,OAAwB;IAExB,MAAM,IAAI,GAAmB,OAAO,CAAC,IAAI,IAAI,QAAQ,CAAC;IAEtD,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,6DAA6D,CAAC,CAAC;QAC3E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;IAC/C,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,GAAG,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,+CAA+C,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACtH,OAAO,CAAC,GAAG,CAAC,uBAAuB,OAAO,CAAC,kBAAkB,IAAI,EAAE,qBAAqB,CAAC,CAAC;IAC1F,IAAI,OAAO,CAAC,uBAAuB,EAAE,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,uBAAuB,OAAO,CAAC,uBAAuB,gCAAgC,CAAC,CAAC;IACxG,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,4BAA4B,OAAO,CAAC,uBAAuB,IAAI,EAAE,QAAQ,CAAC,CAAC;IACvF,OAAO,CAAC,GAAG,CAAC,0BAA0B,OAAO,CAAC,qBAAqB,IAAI,GAAG,QAAQ,CAAC,CAAC;IACpF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,oEAAoE;IACpE,MAAM,gBAAgB,GAAG,MAAM,IAAA,kBAAqB,EAChD,EAAE,GAAG,EAAE,OAAO,CAAC,kBAAkB,IAAI,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,uBAAuB,EAAE,IAAI,EAAE,EAC3F,OAAO,CACV,CAAC;IAEF,MAAM,qBAAqB,GAAG,MAAM,IAAA,kBAA0B,EAC1D,EAAE,GAAG,EAAE,OAAO,CAAC,uBAAuB,IAAI,EAAE,EAAE,IAAI,EAAE,EACpD,OAAO,CACV,CAAC;IAEF,MAAM,mBAAmB,GAAG,MAAM,IAAA,kBAAwB,EACtD,EAAE,GAAG,EAAE,OAAO,CAAC,qBAAqB,IAAI,GAAG,EAAE,IAAI,EAAE,EACnD,OAAO,CACV,CAAC;IAEF,MAAM,UAAU,GAAG,gBAAgB,CAAC,OAAO,IAAI,qBAAqB,CAAC,OAAO,IAAI,mBAAmB,CAAC,OAAO,CAAC;IAE5G,IAAI,UAAU,EAAE,CAAC;QACb,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;IACrD,CAAC;SAAM,CAAC;QACJ,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;IACtD,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;AACnC,CAAC","sourcesContent":["import { ExecutorContext } from '@nx/devkit';\nimport runNewMethodsExecutor from '../validate-new-methods/executor';\nimport runModifiedMethodsExecutor from '../validate-modified-methods/executor';\nimport runModifiedFilesExecutor from '../validate-modified-files/executor';\n\nexport type ValidationMode = 'STRICT' | 'NORMAL' | 'OFF';\n\nexport interface ValidateCodeOptions {\n mode?: ValidationMode;\n newMethodsMaxLines?: number;\n strictNewMethodMaxLines?: number;\n modifiedMethodsMaxLines?: number;\n modifiedFilesMaxLines?: number;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nexport default async function runExecutor(\n options: ValidateCodeOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const mode: ValidationMode = options.mode ?? 'NORMAL';\n\n if (mode === 'OFF') {\n console.log('\\n⏭️ Skipping all code validations (validationMode: OFF)\\n');\n return { success: true };\n }\n\n console.log('\\n📏 Running Code Validations\\n');\n console.log(` Validation mode: ${mode}${mode === 'STRICT' ? ' (disable comments ignored for modified code)' : ''}`);\n console.log(` New methods max: ${options.newMethodsMaxLines ?? 30} lines (soft limit)`);\n if (options.strictNewMethodMaxLines) {\n console.log(` New methods max: ${options.strictNewMethodMaxLines} lines (hard limit, no escape)`);\n }\n console.log(` Modified methods max: ${options.modifiedMethodsMaxLines ?? 80} lines`);\n console.log(` Modified files max: ${options.modifiedFilesMaxLines ?? 900} lines`);\n console.log('');\n\n // Run all three validators sequentially to avoid interleaved output\n const newMethodsResult = await runNewMethodsExecutor(\n { max: options.newMethodsMaxLines ?? 30, strictMax: options.strictNewMethodMaxLines, mode },\n context\n );\n\n const modifiedMethodsResult = await runModifiedMethodsExecutor(\n { max: options.modifiedMethodsMaxLines ?? 80, mode },\n context\n );\n\n const modifiedFilesResult = await runModifiedFilesExecutor(\n { max: options.modifiedFilesMaxLines ?? 900, mode },\n context\n );\n\n const allSuccess = newMethodsResult.success && modifiedMethodsResult.success && modifiedFilesResult.success;\n\n if (allSuccess) {\n console.log('\\n✅ All code validations passed\\n');\n } else {\n console.log('\\n❌ Some code validations failed\\n');\n }\n\n return { success: allSuccess };\n}\n"]}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/dev-config/architecture/executors/validate-code/executor.ts"],"names":[],"mappings":";;AAqBA,8BAuDC;;AA3ED,wFAAqE;AACrE,6FAA+E;AAC/E,2FAA2E;AAC3E,yFAA2F;AAiB5E,KAAK,UAAU,WAAW,CACrC,OAA4B,EAC5B,OAAwB;IAExB,MAAM,IAAI,GAAmB,OAAO,CAAC,IAAI,IAAI,QAAQ,CAAC;IAEtD,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,6DAA6D,CAAC,CAAC;QAC3E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IAED,MAAM,cAAc,GAAmB,OAAO,CAAC,qBAAqB,IAAI,KAAK,CAAC;IAE9E,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;IAC/C,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,GAAG,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,+CAA+C,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACtH,OAAO,CAAC,GAAG,CAAC,uBAAuB,OAAO,CAAC,kBAAkB,IAAI,EAAE,qBAAqB,CAAC,CAAC;IAC1F,IAAI,OAAO,CAAC,uBAAuB,EAAE,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,uBAAuB,OAAO,CAAC,uBAAuB,gCAAgC,CAAC,CAAC;IACxG,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,4BAA4B,OAAO,CAAC,uBAAuB,IAAI,EAAE,QAAQ,CAAC,CAAC;IACvF,OAAO,CAAC,GAAG,CAAC,0BAA0B,OAAO,CAAC,qBAAqB,IAAI,GAAG,QAAQ,CAAC,CAAC;IACpF,OAAO,CAAC,GAAG,CAAC,4BAA4B,cAAc,EAAE,CAAC,CAAC;IAC1D,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,oEAAoE;IACpE,MAAM,gBAAgB,GAAG,MAAM,IAAA,kBAAqB,EAChD,EAAE,GAAG,EAAE,OAAO,CAAC,kBAAkB,IAAI,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,uBAAuB,EAAE,IAAI,EAAE,EAC3F,OAAO,CACV,CAAC;IAEF,MAAM,qBAAqB,GAAG,MAAM,IAAA,kBAA0B,EAC1D,EAAE,GAAG,EAAE,OAAO,CAAC,uBAAuB,IAAI,EAAE,EAAE,IAAI,EAAE,EACpD,OAAO,CACV,CAAC;IAEF,MAAM,mBAAmB,GAAG,MAAM,IAAA,kBAAwB,EACtD,EAAE,GAAG,EAAE,OAAO,CAAC,qBAAqB,IAAI,GAAG,EAAE,IAAI,EAAE,EACnD,OAAO,CACV,CAAC;IAEF,MAAM,iBAAiB,GAAG,MAAM,IAAA,kBAAsB,EAAC,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,OAAO,CAAC,CAAC;IAE1F,MAAM,UAAU,GACZ,gBAAgB,CAAC,OAAO;QACxB,qBAAqB,CAAC,OAAO;QAC7B,mBAAmB,CAAC,OAAO;QAC3B,iBAAiB,CAAC,OAAO,CAAC;IAE9B,IAAI,UAAU,EAAE,CAAC;QACb,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;IACrD,CAAC;SAAM,CAAC;QACJ,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;IACtD,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;AACnC,CAAC","sourcesContent":["import { ExecutorContext } from '@nx/devkit';\nimport runNewMethodsExecutor from '../validate-new-methods/executor';\nimport runModifiedMethodsExecutor from '../validate-modified-methods/executor';\nimport runModifiedFilesExecutor from '../validate-modified-files/executor';\nimport runReturnTypesExecutor, { ReturnTypeMode } from '../validate-return-types/executor';\n\nexport type ValidationMode = 'STRICT' | 'NORMAL' | 'OFF';\n\nexport interface ValidateCodeOptions {\n mode?: ValidationMode;\n newMethodsMaxLines?: number;\n strictNewMethodMaxLines?: number;\n modifiedMethodsMaxLines?: number;\n modifiedFilesMaxLines?: number;\n requireReturnTypeMode?: ReturnTypeMode;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nexport default async function runExecutor(\n options: ValidateCodeOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const mode: ValidationMode = options.mode ?? 'NORMAL';\n\n if (mode === 'OFF') {\n console.log('\\n⏭️ Skipping all code validations (validationMode: OFF)\\n');\n return { success: true };\n }\n\n const returnTypeMode: ReturnTypeMode = options.requireReturnTypeMode ?? 'OFF';\n\n console.log('\\n📏 Running Code Validations\\n');\n console.log(` Validation mode: ${mode}${mode === 'STRICT' ? ' (disable comments ignored for modified code)' : ''}`);\n console.log(` New methods max: ${options.newMethodsMaxLines ?? 30} lines (soft limit)`);\n if (options.strictNewMethodMaxLines) {\n console.log(` New methods max: ${options.strictNewMethodMaxLines} lines (hard limit, no escape)`);\n }\n console.log(` Modified methods max: ${options.modifiedMethodsMaxLines ?? 80} lines`);\n console.log(` Modified files max: ${options.modifiedFilesMaxLines ?? 900} lines`);\n console.log(` Require return types: ${returnTypeMode}`);\n console.log('');\n\n // Run all three validators sequentially to avoid interleaved output\n const newMethodsResult = await runNewMethodsExecutor(\n { max: options.newMethodsMaxLines ?? 30, strictMax: options.strictNewMethodMaxLines, mode },\n context\n );\n\n const modifiedMethodsResult = await runModifiedMethodsExecutor(\n { max: options.modifiedMethodsMaxLines ?? 80, mode },\n context\n );\n\n const modifiedFilesResult = await runModifiedFilesExecutor(\n { max: options.modifiedFilesMaxLines ?? 900, mode },\n context\n );\n\n const returnTypesResult = await runReturnTypesExecutor({ mode: returnTypeMode }, context);\n\n const allSuccess =\n newMethodsResult.success &&\n modifiedMethodsResult.success &&\n modifiedFilesResult.success &&\n returnTypesResult.success;\n\n if (allSuccess) {\n console.log('\\n✅ All code validations passed\\n');\n } else {\n console.log('\\n❌ Some code validations failed\\n');\n }\n\n return { success: allSuccess };\n}\n"]}
@@ -2,6 +2,7 @@ import { ExecutorContext } from '@nx/devkit';
2
2
  import runNewMethodsExecutor from '../validate-new-methods/executor';
3
3
  import runModifiedMethodsExecutor from '../validate-modified-methods/executor';
4
4
  import runModifiedFilesExecutor from '../validate-modified-files/executor';
5
+ import runReturnTypesExecutor, { ReturnTypeMode } from '../validate-return-types/executor';
5
6
 
6
7
  export type ValidationMode = 'STRICT' | 'NORMAL' | 'OFF';
7
8
 
@@ -11,6 +12,7 @@ export interface ValidateCodeOptions {
11
12
  strictNewMethodMaxLines?: number;
12
13
  modifiedMethodsMaxLines?: number;
13
14
  modifiedFilesMaxLines?: number;
15
+ requireReturnTypeMode?: ReturnTypeMode;
14
16
  }
15
17
 
16
18
  export interface ExecutorResult {
@@ -28,6 +30,8 @@ export default async function runExecutor(
28
30
  return { success: true };
29
31
  }
30
32
 
33
+ const returnTypeMode: ReturnTypeMode = options.requireReturnTypeMode ?? 'OFF';
34
+
31
35
  console.log('\n📏 Running Code Validations\n');
32
36
  console.log(` Validation mode: ${mode}${mode === 'STRICT' ? ' (disable comments ignored for modified code)' : ''}`);
33
37
  console.log(` New methods max: ${options.newMethodsMaxLines ?? 30} lines (soft limit)`);
@@ -36,6 +40,7 @@ export default async function runExecutor(
36
40
  }
37
41
  console.log(` Modified methods max: ${options.modifiedMethodsMaxLines ?? 80} lines`);
38
42
  console.log(` Modified files max: ${options.modifiedFilesMaxLines ?? 900} lines`);
43
+ console.log(` Require return types: ${returnTypeMode}`);
39
44
  console.log('');
40
45
 
41
46
  // Run all three validators sequentially to avoid interleaved output
@@ -54,7 +59,13 @@ export default async function runExecutor(
54
59
  context
55
60
  );
56
61
 
57
- const allSuccess = newMethodsResult.success && modifiedMethodsResult.success && modifiedFilesResult.success;
62
+ const returnTypesResult = await runReturnTypesExecutor({ mode: returnTypeMode }, context);
63
+
64
+ const allSuccess =
65
+ newMethodsResult.success &&
66
+ modifiedMethodsResult.success &&
67
+ modifiedFilesResult.success &&
68
+ returnTypesResult.success;
58
69
 
59
70
  if (allSuccess) {
60
71
  console.log('\n✅ All code validations passed\n');
@@ -28,6 +28,12 @@
28
28
  "type": "number",
29
29
  "description": "Maximum lines for MODIFIED files",
30
30
  "default": 900
31
+ },
32
+ "requireReturnTypeMode": {
33
+ "type": "string",
34
+ "enum": ["OFF", "MODIFIED_NEW", "MODIFIED", "ALL"],
35
+ "description": "OFF: skip return type validation. MODIFIED_NEW: only validate new methods. MODIFIED: validate all methods in modified files. ALL: validate all methods in all files.",
36
+ "default": "OFF"
31
37
  }
32
38
  },
33
39
  "required": []
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Validate Return Types Executor
3
+ *
4
+ * Validates that methods have explicit return type annotations for better code readability.
5
+ * Instead of relying on TypeScript's type inference, explicit return types make code clearer:
6
+ *
7
+ * BAD: method() { return new MyClass(); }
8
+ * GOOD: method(): MyClass { return new MyClass(); }
9
+ * GOOD: async method(): Promise<MyType> { ... }
10
+ *
11
+ * Modes:
12
+ * - OFF: Skip validation entirely
13
+ * - MODIFIED_NEW: Only validate new methods (detected via git diff)
14
+ * - MODIFIED: Validate all methods in modified files
15
+ * - ALL: Validate all methods in all TypeScript files
16
+ *
17
+ * Escape hatch: Add webpieces-disable require-return-type comment with justification
18
+ */
19
+ import type { ExecutorContext } from '@nx/devkit';
20
+ export type ReturnTypeMode = 'OFF' | 'MODIFIED_NEW' | 'MODIFIED' | 'ALL';
21
+ export interface ValidateReturnTypesOptions {
22
+ mode?: ReturnTypeMode;
23
+ }
24
+ export interface ExecutorResult {
25
+ success: boolean;
26
+ }
27
+ export default function runExecutor(options: ValidateReturnTypesOptions, context: ExecutorContext): Promise<ExecutorResult>;
@@ -0,0 +1,376 @@
1
+ "use strict";
2
+ /**
3
+ * Validate Return Types Executor
4
+ *
5
+ * Validates that methods have explicit return type annotations for better code readability.
6
+ * Instead of relying on TypeScript's type inference, explicit return types make code clearer:
7
+ *
8
+ * BAD: method() { return new MyClass(); }
9
+ * GOOD: method(): MyClass { return new MyClass(); }
10
+ * GOOD: async method(): Promise<MyType> { ... }
11
+ *
12
+ * Modes:
13
+ * - OFF: Skip validation entirely
14
+ * - MODIFIED_NEW: Only validate new methods (detected via git diff)
15
+ * - MODIFIED: Validate all methods in modified files
16
+ * - ALL: Validate all methods in all TypeScript files
17
+ *
18
+ * Escape hatch: Add webpieces-disable require-return-type comment with justification
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.default = runExecutor;
22
+ const tslib_1 = require("tslib");
23
+ const child_process_1 = require("child_process");
24
+ const fs = tslib_1.__importStar(require("fs"));
25
+ const path = tslib_1.__importStar(require("path"));
26
+ const ts = tslib_1.__importStar(require("typescript"));
27
+ /**
28
+ * Get changed TypeScript files between base and head (or working tree if head not specified).
29
+ */
30
+ // webpieces-disable max-lines-new-methods -- Git command handling with untracked files requires multiple code paths
31
+ function getChangedTypeScriptFiles(workspaceRoot, base, head) {
32
+ try {
33
+ const diffTarget = head ? `${base} ${head}` : base;
34
+ const output = (0, child_process_1.execSync)(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {
35
+ cwd: workspaceRoot,
36
+ encoding: 'utf-8',
37
+ });
38
+ const changedFiles = output
39
+ .trim()
40
+ .split('\n')
41
+ .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
42
+ if (!head) {
43
+ try {
44
+ const untrackedOutput = (0, child_process_1.execSync)(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {
45
+ cwd: workspaceRoot,
46
+ encoding: 'utf-8',
47
+ });
48
+ const untrackedFiles = untrackedOutput
49
+ .trim()
50
+ .split('\n')
51
+ .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
52
+ const allFiles = new Set([...changedFiles, ...untrackedFiles]);
53
+ return Array.from(allFiles);
54
+ }
55
+ catch {
56
+ return changedFiles;
57
+ }
58
+ }
59
+ return changedFiles;
60
+ }
61
+ catch {
62
+ return [];
63
+ }
64
+ }
65
+ /**
66
+ * Get all TypeScript files in the workspace (excluding node_modules, dist, tests).
67
+ */
68
+ function getAllTypeScriptFiles(workspaceRoot) {
69
+ try {
70
+ const output = (0, child_process_1.execSync)(`find packages apps -type f \\( -name "*.ts" -o -name "*.tsx" \\) | grep -v node_modules | grep -v dist | grep -v ".spec.ts" | grep -v ".test.ts"`, {
71
+ cwd: workspaceRoot,
72
+ encoding: 'utf-8',
73
+ });
74
+ return output
75
+ .trim()
76
+ .split('\n')
77
+ .filter((f) => f);
78
+ }
79
+ catch {
80
+ return [];
81
+ }
82
+ }
83
+ /**
84
+ * Get the diff content for a specific file.
85
+ */
86
+ function getFileDiff(workspaceRoot, file, base, head) {
87
+ try {
88
+ const diffTarget = head ? `${base} ${head}` : base;
89
+ const diff = (0, child_process_1.execSync)(`git diff ${diffTarget} -- "${file}"`, {
90
+ cwd: workspaceRoot,
91
+ encoding: 'utf-8',
92
+ });
93
+ if (!diff && !head) {
94
+ const fullPath = path.join(workspaceRoot, file);
95
+ if (fs.existsSync(fullPath)) {
96
+ const isUntracked = (0, child_process_1.execSync)(`git ls-files --others --exclude-standard "${file}"`, {
97
+ cwd: workspaceRoot,
98
+ encoding: 'utf-8',
99
+ }).trim();
100
+ if (isUntracked) {
101
+ const content = fs.readFileSync(fullPath, 'utf-8');
102
+ const lines = content.split('\n');
103
+ return lines.map((line) => `+${line}`).join('\n');
104
+ }
105
+ }
106
+ }
107
+ return diff;
108
+ }
109
+ catch {
110
+ return '';
111
+ }
112
+ }
113
+ /**
114
+ * Parse diff to find newly added method signatures.
115
+ */
116
+ function findNewMethodSignaturesInDiff(diffContent) {
117
+ const newMethods = new Set();
118
+ const lines = diffContent.split('\n');
119
+ const patterns = [
120
+ /^\+\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/,
121
+ /^\+\s*(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s*)?\(/,
122
+ /^\+\s*(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?function/,
123
+ /^\+\s*(?:(?:public|private|protected)\s+)?(?:static\s+)?(?:async\s+)?(\w+)\s*\(/,
124
+ ];
125
+ for (const line of lines) {
126
+ if (line.startsWith('+') && !line.startsWith('+++')) {
127
+ for (const pattern of patterns) {
128
+ const match = line.match(pattern);
129
+ if (match) {
130
+ const methodName = match[1];
131
+ if (methodName && !['if', 'for', 'while', 'switch', 'catch', 'constructor'].includes(methodName)) {
132
+ newMethods.add(methodName);
133
+ }
134
+ break;
135
+ }
136
+ }
137
+ }
138
+ }
139
+ return newMethods;
140
+ }
141
+ /**
142
+ * Check if a line contains a webpieces-disable comment for return type.
143
+ */
144
+ function hasDisableComment(lines, lineNumber) {
145
+ const startCheck = Math.max(0, lineNumber - 5);
146
+ for (let i = lineNumber - 2; i >= startCheck; i--) {
147
+ const line = lines[i]?.trim() ?? '';
148
+ if (line.startsWith('function ') || line.startsWith('class ') || line.endsWith('}')) {
149
+ break;
150
+ }
151
+ if (line.includes('webpieces-disable') && line.includes('require-return-type')) {
152
+ return true;
153
+ }
154
+ }
155
+ return false;
156
+ }
157
+ /**
158
+ * Check if a method has an explicit return type annotation.
159
+ */
160
+ function hasExplicitReturnType(node) {
161
+ return node.type !== undefined;
162
+ }
163
+ /**
164
+ * Parse a TypeScript file and find methods with their return type status.
165
+ */
166
+ // webpieces-disable max-lines-new-methods -- AST traversal requires inline visitor function
167
+ function findMethodsInFile(filePath, workspaceRoot) {
168
+ const fullPath = path.join(workspaceRoot, filePath);
169
+ if (!fs.existsSync(fullPath))
170
+ return [];
171
+ const content = fs.readFileSync(fullPath, 'utf-8');
172
+ const fileLines = content.split('\n');
173
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
174
+ const methods = [];
175
+ // webpieces-disable max-lines-new-methods -- AST visitor pattern requires handling multiple node types
176
+ function visit(node) {
177
+ let methodName;
178
+ let startLine;
179
+ let hasReturnType = false;
180
+ if (ts.isMethodDeclaration(node) && node.name) {
181
+ methodName = node.name.getText(sourceFile);
182
+ const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
183
+ startLine = start.line + 1;
184
+ hasReturnType = hasExplicitReturnType(node);
185
+ }
186
+ else if (ts.isFunctionDeclaration(node) && node.name) {
187
+ methodName = node.name.getText(sourceFile);
188
+ const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
189
+ startLine = start.line + 1;
190
+ hasReturnType = hasExplicitReturnType(node);
191
+ }
192
+ else if (ts.isArrowFunction(node)) {
193
+ if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
194
+ methodName = node.parent.name.getText(sourceFile);
195
+ const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
196
+ startLine = start.line + 1;
197
+ hasReturnType = hasExplicitReturnType(node);
198
+ }
199
+ }
200
+ if (methodName && startLine !== undefined) {
201
+ methods.push({
202
+ name: methodName,
203
+ line: startLine,
204
+ hasReturnType,
205
+ hasDisableComment: hasDisableComment(fileLines, startLine),
206
+ });
207
+ }
208
+ ts.forEachChild(node, visit);
209
+ }
210
+ visit(sourceFile);
211
+ return methods;
212
+ }
213
+ /**
214
+ * Find methods without explicit return types based on mode.
215
+ */
216
+ // webpieces-disable max-lines-new-methods -- File iteration with diff parsing and method matching
217
+ function findViolationsForModifiedNew(workspaceRoot, changedFiles, base, head) {
218
+ const violations = [];
219
+ for (const file of changedFiles) {
220
+ const diff = getFileDiff(workspaceRoot, file, base, head);
221
+ const newMethodNames = findNewMethodSignaturesInDiff(diff);
222
+ if (newMethodNames.size === 0)
223
+ continue;
224
+ const methods = findMethodsInFile(file, workspaceRoot);
225
+ for (const method of methods) {
226
+ if (!newMethodNames.has(method.name))
227
+ continue;
228
+ if (method.hasReturnType)
229
+ continue;
230
+ if (method.hasDisableComment)
231
+ continue;
232
+ violations.push({
233
+ file,
234
+ methodName: method.name,
235
+ line: method.line,
236
+ });
237
+ }
238
+ }
239
+ return violations;
240
+ }
241
+ /**
242
+ * Find all methods without explicit return types in modified files.
243
+ */
244
+ function findViolationsForModified(workspaceRoot, changedFiles) {
245
+ const violations = [];
246
+ for (const file of changedFiles) {
247
+ const methods = findMethodsInFile(file, workspaceRoot);
248
+ for (const method of methods) {
249
+ if (method.hasReturnType)
250
+ continue;
251
+ if (method.hasDisableComment)
252
+ continue;
253
+ violations.push({
254
+ file,
255
+ methodName: method.name,
256
+ line: method.line,
257
+ });
258
+ }
259
+ }
260
+ return violations;
261
+ }
262
+ /**
263
+ * Find all methods without explicit return types in all files.
264
+ */
265
+ function findViolationsForAll(workspaceRoot) {
266
+ const allFiles = getAllTypeScriptFiles(workspaceRoot);
267
+ return findViolationsForModified(workspaceRoot, allFiles);
268
+ }
269
+ /**
270
+ * Auto-detect the base branch by finding the merge-base with origin/main.
271
+ */
272
+ function detectBase(workspaceRoot) {
273
+ try {
274
+ const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD origin/main', {
275
+ cwd: workspaceRoot,
276
+ encoding: 'utf-8',
277
+ stdio: ['pipe', 'pipe', 'pipe'],
278
+ }).trim();
279
+ if (mergeBase) {
280
+ return mergeBase;
281
+ }
282
+ }
283
+ catch {
284
+ try {
285
+ const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD main', {
286
+ cwd: workspaceRoot,
287
+ encoding: 'utf-8',
288
+ stdio: ['pipe', 'pipe', 'pipe'],
289
+ }).trim();
290
+ if (mergeBase) {
291
+ return mergeBase;
292
+ }
293
+ }
294
+ catch {
295
+ // Ignore
296
+ }
297
+ }
298
+ return null;
299
+ }
300
+ /**
301
+ * Report violations to console.
302
+ */
303
+ function reportViolations(violations, mode) {
304
+ console.error('');
305
+ console.error('❌ Methods missing explicit return types!');
306
+ console.error('');
307
+ console.error('📚 Explicit return types improve code readability:');
308
+ console.error('');
309
+ console.error(' BAD: method() { return new MyClass(); }');
310
+ console.error(' GOOD: method(): MyClass { return new MyClass(); }');
311
+ console.error(' GOOD: async method(): Promise<MyType> { ... }');
312
+ console.error('');
313
+ for (const v of violations) {
314
+ console.error(` ❌ ${v.file}:${v.line}`);
315
+ console.error(` Method: ${v.methodName} - missing return type annotation`);
316
+ }
317
+ console.error('');
318
+ console.error(' To fix: Add explicit return type after the parameter list');
319
+ console.error('');
320
+ console.error(' Escape hatch (use sparingly):');
321
+ console.error(' // webpieces-disable require-return-type -- [your reason]');
322
+ console.error('');
323
+ console.error(` Current mode: ${mode}`);
324
+ console.error('');
325
+ }
326
+ async function runExecutor(options, context) {
327
+ const workspaceRoot = context.root;
328
+ const mode = options.mode ?? 'MODIFIED_NEW';
329
+ if (mode === 'OFF') {
330
+ console.log('\n⏭️ Skipping return type validation (mode: OFF)');
331
+ console.log('');
332
+ return { success: true };
333
+ }
334
+ console.log('\n📏 Validating Return Types\n');
335
+ console.log(` Mode: ${mode}`);
336
+ let violations = [];
337
+ if (mode === 'ALL') {
338
+ console.log(' Scope: All TypeScript files');
339
+ console.log('');
340
+ violations = findViolationsForAll(workspaceRoot);
341
+ }
342
+ else {
343
+ let base = process.env['NX_BASE'];
344
+ const head = process.env['NX_HEAD'];
345
+ if (!base) {
346
+ base = detectBase(workspaceRoot) ?? undefined;
347
+ if (!base) {
348
+ console.log('\n⏭️ Skipping return type validation (could not detect base branch)');
349
+ console.log('');
350
+ return { success: true };
351
+ }
352
+ }
353
+ console.log(` Base: ${base}`);
354
+ console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);
355
+ console.log('');
356
+ const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base, head);
357
+ if (changedFiles.length === 0) {
358
+ console.log('✅ No TypeScript files changed');
359
+ return { success: true };
360
+ }
361
+ console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);
362
+ if (mode === 'MODIFIED_NEW') {
363
+ violations = findViolationsForModifiedNew(workspaceRoot, changedFiles, base, head);
364
+ }
365
+ else if (mode === 'MODIFIED') {
366
+ violations = findViolationsForModified(workspaceRoot, changedFiles);
367
+ }
368
+ }
369
+ if (violations.length === 0) {
370
+ console.log('✅ All methods have explicit return types');
371
+ return { success: true };
372
+ }
373
+ reportViolations(violations, mode);
374
+ return { success: false };
375
+ }
376
+ //# sourceMappingURL=executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/dev-config/architecture/executors/validate-return-types/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Return Types Executor\n *\n * Validates that methods have explicit return type annotations for better code readability.\n * Instead of relying on TypeScript's type inference, explicit return types make code clearer:\n *\n * BAD: method() { return new MyClass(); }\n * GOOD: method(): MyClass { return new MyClass(); }\n * GOOD: async method(): Promise<MyType> { ... }\n *\n * Modes:\n * - OFF: Skip validation entirely\n * - MODIFIED_NEW: Only validate new methods (detected via git diff)\n * - MODIFIED: Validate all methods in modified files\n * - ALL: Validate all methods in all TypeScript files\n *\n * Escape hatch: Add webpieces-disable require-return-type 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 type ReturnTypeMode = 'OFF' | 'MODIFIED_NEW' | 'MODIFIED' | 'ALL';\n\nexport interface ValidateReturnTypesOptions {\n mode?: ReturnTypeMode;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\ninterface MethodViolation {\n file: string;\n methodName: string;\n line: number;\n}\n\n/**\n * Get changed TypeScript files between base and head (or working tree if head not specified).\n */\n// webpieces-disable max-lines-new-methods -- Git command handling with untracked files requires multiple code paths\nfunction getChangedTypeScriptFiles(workspaceRoot: string, base: string, head?: string): string[] {\n try {\n const diffTarget = head ? `${base} ${head}` : base;\n const output = execSync(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n const changedFiles = output\n .trim()\n .split('\\n')\n .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));\n\n if (!head) {\n try {\n const untrackedOutput = execSync(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n const untrackedFiles = untrackedOutput\n .trim()\n .split('\\n')\n .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));\n const allFiles = new Set([...changedFiles, ...untrackedFiles]);\n return Array.from(allFiles);\n } catch {\n return changedFiles;\n }\n }\n\n return changedFiles;\n } catch {\n return [];\n }\n}\n\n/**\n * Get all TypeScript files in the workspace (excluding node_modules, dist, tests).\n */\nfunction getAllTypeScriptFiles(workspaceRoot: string): string[] {\n try {\n const output = execSync(\n `find packages apps -type f \\\\( -name \"*.ts\" -o -name \"*.tsx\" \\\\) | grep -v node_modules | grep -v dist | grep -v \".spec.ts\" | grep -v \".test.ts\"`,\n {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n }\n );\n return output\n .trim()\n .split('\\n')\n .filter((f) => f);\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 const diffTarget = head ? `${base} ${head}` : base;\n const diff = execSync(`git diff ${diffTarget} -- \"${file}\"`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n\n if (!diff && !head) {\n const fullPath = path.join(workspaceRoot, file);\n if (fs.existsSync(fullPath)) {\n const isUntracked = execSync(`git ls-files --others --exclude-standard \"${file}\"`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n }).trim();\n\n if (isUntracked) {\n const content = fs.readFileSync(fullPath, 'utf-8');\n const lines = content.split('\\n');\n return lines.map((line) => `+${line}`).join('\\n');\n }\n }\n }\n\n return diff;\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 const patterns = [\n /^\\+\\s*(?:export\\s+)?(?:async\\s+)?function\\s+(\\w+)\\s*\\(/,\n /^\\+\\s*(?:export\\s+)?(?:const|let)\\s+(\\w+)\\s*=\\s*(?:async\\s*)?\\(/,\n /^\\+\\s*(?:export\\s+)?(?:const|let)\\s+(\\w+)\\s*=\\s*(?:async\\s+)?function/,\n /^\\+\\s*(?:(?:public|private|protected)\\s+)?(?:static\\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 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 return type.\n */\nfunction hasDisableComment(lines: string[], lineNumber: number): boolean {\n const startCheck = Math.max(0, lineNumber - 5);\n for (let i = lineNumber - 2; i >= startCheck; i--) {\n const line = lines[i]?.trim() ?? '';\n if (line.startsWith('function ') || line.startsWith('class ') || line.endsWith('}')) {\n break;\n }\n if (line.includes('webpieces-disable') && line.includes('require-return-type')) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Check if a method has an explicit return type annotation.\n */\nfunction hasExplicitReturnType(node: ts.MethodDeclaration | ts.FunctionDeclaration | ts.ArrowFunction): boolean {\n return node.type !== undefined;\n}\n\ninterface MethodInfo {\n name: string;\n line: number;\n hasReturnType: boolean;\n hasDisableComment: boolean;\n}\n\n/**\n * Parse a TypeScript file and find methods with their return type status.\n */\n// webpieces-disable max-lines-new-methods -- AST traversal requires inline visitor function\nfunction findMethodsInFile(filePath: string, workspaceRoot: string): MethodInfo[] {\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: MethodInfo[] = [];\n\n // webpieces-disable max-lines-new-methods -- AST visitor pattern requires handling multiple node types\n function visit(node: ts.Node): void {\n let methodName: string | undefined;\n let startLine: number | undefined;\n let hasReturnType = false;\n\n if (ts.isMethodDeclaration(node) && node.name) {\n methodName = node.name.getText(sourceFile);\n const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n startLine = start.line + 1;\n hasReturnType = hasExplicitReturnType(node);\n } else if (ts.isFunctionDeclaration(node) && node.name) {\n methodName = node.name.getText(sourceFile);\n const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n startLine = start.line + 1;\n hasReturnType = hasExplicitReturnType(node);\n } else if (ts.isArrowFunction(node)) {\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 startLine = start.line + 1;\n hasReturnType = hasExplicitReturnType(node);\n }\n }\n\n if (methodName && startLine !== undefined) {\n methods.push({\n name: methodName,\n line: startLine,\n hasReturnType,\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 methods without explicit return types based on mode.\n */\n// webpieces-disable max-lines-new-methods -- File iteration with diff parsing and method matching\nfunction findViolationsForModifiedNew(\n workspaceRoot: string,\n changedFiles: string[],\n base: string,\n head?: string\n): MethodViolation[] {\n const violations: MethodViolation[] = [];\n\n for (const file of changedFiles) {\n const diff = getFileDiff(workspaceRoot, file, base, head);\n const newMethodNames = findNewMethodSignaturesInDiff(diff);\n\n if (newMethodNames.size === 0) continue;\n\n const methods = findMethodsInFile(file, workspaceRoot);\n\n for (const method of methods) {\n if (!newMethodNames.has(method.name)) continue;\n if (method.hasReturnType) continue;\n if (method.hasDisableComment) continue;\n\n violations.push({\n file,\n methodName: method.name,\n line: method.line,\n });\n }\n }\n\n return violations;\n}\n\n/**\n * Find all methods without explicit return types in modified files.\n */\nfunction findViolationsForModified(workspaceRoot: string, changedFiles: string[]): MethodViolation[] {\n const violations: MethodViolation[] = [];\n\n for (const file of changedFiles) {\n const methods = findMethodsInFile(file, workspaceRoot);\n\n for (const method of methods) {\n if (method.hasReturnType) continue;\n if (method.hasDisableComment) continue;\n\n violations.push({\n file,\n methodName: method.name,\n line: method.line,\n });\n }\n }\n\n return violations;\n}\n\n/**\n * Find all methods without explicit return types in all files.\n */\nfunction findViolationsForAll(workspaceRoot: string): MethodViolation[] {\n const allFiles = getAllTypeScriptFiles(workspaceRoot);\n return findViolationsForModified(workspaceRoot, allFiles);\n}\n\n/**\n * Auto-detect the base branch by finding the merge-base with origin/main.\n */\nfunction detectBase(workspaceRoot: string): string | null {\n try {\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 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\n }\n }\n return null;\n}\n\n/**\n * Report violations to console.\n */\nfunction reportViolations(violations: MethodViolation[], mode: ReturnTypeMode): void {\n console.error('');\n console.error('❌ Methods missing explicit return types!');\n console.error('');\n console.error('📚 Explicit return types improve code readability:');\n console.error('');\n console.error(' BAD: method() { return new MyClass(); }');\n console.error(' GOOD: method(): MyClass { return new MyClass(); }');\n console.error(' GOOD: async method(): Promise<MyType> { ... }');\n console.error('');\n\n for (const v of violations) {\n console.error(` ❌ ${v.file}:${v.line}`);\n console.error(` Method: ${v.methodName} - missing return type annotation`);\n }\n console.error('');\n\n console.error(' To fix: Add explicit return type after the parameter list');\n console.error('');\n console.error(' Escape hatch (use sparingly):');\n console.error(' // webpieces-disable require-return-type -- [your reason]');\n console.error('');\n console.error(` Current mode: ${mode}`);\n console.error('');\n}\n\nexport default async function runExecutor(\n options: ValidateReturnTypesOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n const mode: ReturnTypeMode = options.mode ?? 'MODIFIED_NEW';\n\n if (mode === 'OFF') {\n console.log('\\n⏭️ Skipping return type validation (mode: OFF)');\n console.log('');\n return { success: true };\n }\n\n console.log('\\n📏 Validating Return Types\\n');\n console.log(` Mode: ${mode}`);\n\n let violations: MethodViolation[] = [];\n\n if (mode === 'ALL') {\n console.log(' Scope: All TypeScript files');\n console.log('');\n violations = findViolationsForAll(workspaceRoot);\n } else {\n let base = process.env['NX_BASE'];\n const head = process.env['NX_HEAD'];\n\n if (!base) {\n base = detectBase(workspaceRoot) ?? undefined;\n\n if (!base) {\n console.log('\\n⏭️ Skipping return type validation (could not detect base branch)');\n console.log('');\n return { success: true };\n }\n }\n\n console.log(` Base: ${base}`);\n console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);\n console.log('');\n\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 if (mode === 'MODIFIED_NEW') {\n violations = findViolationsForModifiedNew(workspaceRoot, changedFiles, base, head);\n } else if (mode === 'MODIFIED') {\n violations = findViolationsForModified(workspaceRoot, changedFiles);\n }\n }\n\n if (violations.length === 0) {\n console.log('✅ All methods have explicit return types');\n return { success: true };\n }\n\n reportViolations(violations, mode);\n\n return { success: false };\n}\n"]}
@@ -0,0 +1,446 @@
1
+ /**
2
+ * Validate Return Types Executor
3
+ *
4
+ * Validates that methods have explicit return type annotations for better code readability.
5
+ * Instead of relying on TypeScript's type inference, explicit return types make code clearer:
6
+ *
7
+ * BAD: method() { return new MyClass(); }
8
+ * GOOD: method(): MyClass { return new MyClass(); }
9
+ * GOOD: async method(): Promise<MyType> { ... }
10
+ *
11
+ * Modes:
12
+ * - OFF: Skip validation entirely
13
+ * - MODIFIED_NEW: Only validate new methods (detected via git diff)
14
+ * - MODIFIED: Validate all methods in modified files
15
+ * - ALL: Validate all methods in all TypeScript files
16
+ *
17
+ * Escape hatch: Add webpieces-disable require-return-type comment with justification
18
+ */
19
+
20
+ import type { ExecutorContext } from '@nx/devkit';
21
+ import { execSync } from 'child_process';
22
+ import * as fs from 'fs';
23
+ import * as path from 'path';
24
+ import * as ts from 'typescript';
25
+
26
+ export type ReturnTypeMode = 'OFF' | 'MODIFIED_NEW' | 'MODIFIED' | 'ALL';
27
+
28
+ export interface ValidateReturnTypesOptions {
29
+ mode?: ReturnTypeMode;
30
+ }
31
+
32
+ export interface ExecutorResult {
33
+ success: boolean;
34
+ }
35
+
36
+ interface MethodViolation {
37
+ file: string;
38
+ methodName: string;
39
+ line: number;
40
+ }
41
+
42
+ /**
43
+ * Get changed TypeScript files between base and head (or working tree if head not specified).
44
+ */
45
+ // webpieces-disable max-lines-new-methods -- Git command handling with untracked files requires multiple code paths
46
+ function getChangedTypeScriptFiles(workspaceRoot: string, base: string, head?: string): string[] {
47
+ try {
48
+ const diffTarget = head ? `${base} ${head}` : base;
49
+ const output = execSync(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {
50
+ cwd: workspaceRoot,
51
+ encoding: 'utf-8',
52
+ });
53
+ const changedFiles = output
54
+ .trim()
55
+ .split('\n')
56
+ .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
57
+
58
+ if (!head) {
59
+ try {
60
+ const untrackedOutput = execSync(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {
61
+ cwd: workspaceRoot,
62
+ encoding: 'utf-8',
63
+ });
64
+ const untrackedFiles = untrackedOutput
65
+ .trim()
66
+ .split('\n')
67
+ .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
68
+ const allFiles = new Set([...changedFiles, ...untrackedFiles]);
69
+ return Array.from(allFiles);
70
+ } catch {
71
+ return changedFiles;
72
+ }
73
+ }
74
+
75
+ return changedFiles;
76
+ } catch {
77
+ return [];
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Get all TypeScript files in the workspace (excluding node_modules, dist, tests).
83
+ */
84
+ function getAllTypeScriptFiles(workspaceRoot: string): string[] {
85
+ try {
86
+ const output = execSync(
87
+ `find packages apps -type f \\( -name "*.ts" -o -name "*.tsx" \\) | grep -v node_modules | grep -v dist | grep -v ".spec.ts" | grep -v ".test.ts"`,
88
+ {
89
+ cwd: workspaceRoot,
90
+ encoding: 'utf-8',
91
+ }
92
+ );
93
+ return output
94
+ .trim()
95
+ .split('\n')
96
+ .filter((f) => f);
97
+ } catch {
98
+ return [];
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Get the diff content for a specific file.
104
+ */
105
+ function getFileDiff(workspaceRoot: string, file: string, base: string, head?: string): string {
106
+ try {
107
+ const diffTarget = head ? `${base} ${head}` : base;
108
+ const diff = execSync(`git diff ${diffTarget} -- "${file}"`, {
109
+ cwd: workspaceRoot,
110
+ encoding: 'utf-8',
111
+ });
112
+
113
+ if (!diff && !head) {
114
+ const fullPath = path.join(workspaceRoot, file);
115
+ if (fs.existsSync(fullPath)) {
116
+ const isUntracked = execSync(`git ls-files --others --exclude-standard "${file}"`, {
117
+ cwd: workspaceRoot,
118
+ encoding: 'utf-8',
119
+ }).trim();
120
+
121
+ if (isUntracked) {
122
+ const content = fs.readFileSync(fullPath, 'utf-8');
123
+ const lines = content.split('\n');
124
+ return lines.map((line) => `+${line}`).join('\n');
125
+ }
126
+ }
127
+ }
128
+
129
+ return diff;
130
+ } catch {
131
+ return '';
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Parse diff to find newly added method signatures.
137
+ */
138
+ function findNewMethodSignaturesInDiff(diffContent: string): Set<string> {
139
+ const newMethods = new Set<string>();
140
+ const lines = diffContent.split('\n');
141
+
142
+ const patterns = [
143
+ /^\+\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/,
144
+ /^\+\s*(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s*)?\(/,
145
+ /^\+\s*(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?function/,
146
+ /^\+\s*(?:(?:public|private|protected)\s+)?(?:static\s+)?(?:async\s+)?(\w+)\s*\(/,
147
+ ];
148
+
149
+ for (const line of lines) {
150
+ if (line.startsWith('+') && !line.startsWith('+++')) {
151
+ for (const pattern of patterns) {
152
+ const match = line.match(pattern);
153
+ if (match) {
154
+ const methodName = match[1];
155
+ if (methodName && !['if', 'for', 'while', 'switch', 'catch', 'constructor'].includes(methodName)) {
156
+ newMethods.add(methodName);
157
+ }
158
+ break;
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ return newMethods;
165
+ }
166
+
167
+ /**
168
+ * Check if a line contains a webpieces-disable comment for return type.
169
+ */
170
+ function hasDisableComment(lines: string[], lineNumber: number): boolean {
171
+ const startCheck = Math.max(0, lineNumber - 5);
172
+ for (let i = lineNumber - 2; i >= startCheck; i--) {
173
+ const line = lines[i]?.trim() ?? '';
174
+ if (line.startsWith('function ') || line.startsWith('class ') || line.endsWith('}')) {
175
+ break;
176
+ }
177
+ if (line.includes('webpieces-disable') && line.includes('require-return-type')) {
178
+ return true;
179
+ }
180
+ }
181
+ return false;
182
+ }
183
+
184
+ /**
185
+ * Check if a method has an explicit return type annotation.
186
+ */
187
+ function hasExplicitReturnType(node: ts.MethodDeclaration | ts.FunctionDeclaration | ts.ArrowFunction): boolean {
188
+ return node.type !== undefined;
189
+ }
190
+
191
+ interface MethodInfo {
192
+ name: string;
193
+ line: number;
194
+ hasReturnType: boolean;
195
+ hasDisableComment: boolean;
196
+ }
197
+
198
+ /**
199
+ * Parse a TypeScript file and find methods with their return type status.
200
+ */
201
+ // webpieces-disable max-lines-new-methods -- AST traversal requires inline visitor function
202
+ function findMethodsInFile(filePath: string, workspaceRoot: string): MethodInfo[] {
203
+ const fullPath = path.join(workspaceRoot, filePath);
204
+ if (!fs.existsSync(fullPath)) return [];
205
+
206
+ const content = fs.readFileSync(fullPath, 'utf-8');
207
+ const fileLines = content.split('\n');
208
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
209
+
210
+ const methods: MethodInfo[] = [];
211
+
212
+ // webpieces-disable max-lines-new-methods -- AST visitor pattern requires handling multiple node types
213
+ function visit(node: ts.Node): void {
214
+ let methodName: string | undefined;
215
+ let startLine: number | undefined;
216
+ let hasReturnType = false;
217
+
218
+ if (ts.isMethodDeclaration(node) && node.name) {
219
+ methodName = node.name.getText(sourceFile);
220
+ const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
221
+ startLine = start.line + 1;
222
+ hasReturnType = hasExplicitReturnType(node);
223
+ } else if (ts.isFunctionDeclaration(node) && node.name) {
224
+ methodName = node.name.getText(sourceFile);
225
+ const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
226
+ startLine = start.line + 1;
227
+ hasReturnType = hasExplicitReturnType(node);
228
+ } else if (ts.isArrowFunction(node)) {
229
+ if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
230
+ methodName = node.parent.name.getText(sourceFile);
231
+ const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
232
+ startLine = start.line + 1;
233
+ hasReturnType = hasExplicitReturnType(node);
234
+ }
235
+ }
236
+
237
+ if (methodName && startLine !== undefined) {
238
+ methods.push({
239
+ name: methodName,
240
+ line: startLine,
241
+ hasReturnType,
242
+ hasDisableComment: hasDisableComment(fileLines, startLine),
243
+ });
244
+ }
245
+
246
+ ts.forEachChild(node, visit);
247
+ }
248
+
249
+ visit(sourceFile);
250
+ return methods;
251
+ }
252
+
253
+ /**
254
+ * Find methods without explicit return types based on mode.
255
+ */
256
+ // webpieces-disable max-lines-new-methods -- File iteration with diff parsing and method matching
257
+ function findViolationsForModifiedNew(
258
+ workspaceRoot: string,
259
+ changedFiles: string[],
260
+ base: string,
261
+ head?: string
262
+ ): MethodViolation[] {
263
+ const violations: MethodViolation[] = [];
264
+
265
+ for (const file of changedFiles) {
266
+ const diff = getFileDiff(workspaceRoot, file, base, head);
267
+ const newMethodNames = findNewMethodSignaturesInDiff(diff);
268
+
269
+ if (newMethodNames.size === 0) continue;
270
+
271
+ const methods = findMethodsInFile(file, workspaceRoot);
272
+
273
+ for (const method of methods) {
274
+ if (!newMethodNames.has(method.name)) continue;
275
+ if (method.hasReturnType) continue;
276
+ if (method.hasDisableComment) continue;
277
+
278
+ violations.push({
279
+ file,
280
+ methodName: method.name,
281
+ line: method.line,
282
+ });
283
+ }
284
+ }
285
+
286
+ return violations;
287
+ }
288
+
289
+ /**
290
+ * Find all methods without explicit return types in modified files.
291
+ */
292
+ function findViolationsForModified(workspaceRoot: string, changedFiles: string[]): MethodViolation[] {
293
+ const violations: MethodViolation[] = [];
294
+
295
+ for (const file of changedFiles) {
296
+ const methods = findMethodsInFile(file, workspaceRoot);
297
+
298
+ for (const method of methods) {
299
+ if (method.hasReturnType) continue;
300
+ if (method.hasDisableComment) continue;
301
+
302
+ violations.push({
303
+ file,
304
+ methodName: method.name,
305
+ line: method.line,
306
+ });
307
+ }
308
+ }
309
+
310
+ return violations;
311
+ }
312
+
313
+ /**
314
+ * Find all methods without explicit return types in all files.
315
+ */
316
+ function findViolationsForAll(workspaceRoot: string): MethodViolation[] {
317
+ const allFiles = getAllTypeScriptFiles(workspaceRoot);
318
+ return findViolationsForModified(workspaceRoot, allFiles);
319
+ }
320
+
321
+ /**
322
+ * Auto-detect the base branch by finding the merge-base with origin/main.
323
+ */
324
+ function detectBase(workspaceRoot: string): string | null {
325
+ try {
326
+ const mergeBase = execSync('git merge-base HEAD origin/main', {
327
+ cwd: workspaceRoot,
328
+ encoding: 'utf-8',
329
+ stdio: ['pipe', 'pipe', 'pipe'],
330
+ }).trim();
331
+
332
+ if (mergeBase) {
333
+ return mergeBase;
334
+ }
335
+ } catch {
336
+ try {
337
+ const mergeBase = execSync('git merge-base HEAD main', {
338
+ cwd: workspaceRoot,
339
+ encoding: 'utf-8',
340
+ stdio: ['pipe', 'pipe', 'pipe'],
341
+ }).trim();
342
+
343
+ if (mergeBase) {
344
+ return mergeBase;
345
+ }
346
+ } catch {
347
+ // Ignore
348
+ }
349
+ }
350
+ return null;
351
+ }
352
+
353
+ /**
354
+ * Report violations to console.
355
+ */
356
+ function reportViolations(violations: MethodViolation[], mode: ReturnTypeMode): void {
357
+ console.error('');
358
+ console.error('❌ Methods missing explicit return types!');
359
+ console.error('');
360
+ console.error('📚 Explicit return types improve code readability:');
361
+ console.error('');
362
+ console.error(' BAD: method() { return new MyClass(); }');
363
+ console.error(' GOOD: method(): MyClass { return new MyClass(); }');
364
+ console.error(' GOOD: async method(): Promise<MyType> { ... }');
365
+ console.error('');
366
+
367
+ for (const v of violations) {
368
+ console.error(` ❌ ${v.file}:${v.line}`);
369
+ console.error(` Method: ${v.methodName} - missing return type annotation`);
370
+ }
371
+ console.error('');
372
+
373
+ console.error(' To fix: Add explicit return type after the parameter list');
374
+ console.error('');
375
+ console.error(' Escape hatch (use sparingly):');
376
+ console.error(' // webpieces-disable require-return-type -- [your reason]');
377
+ console.error('');
378
+ console.error(` Current mode: ${mode}`);
379
+ console.error('');
380
+ }
381
+
382
+ export default async function runExecutor(
383
+ options: ValidateReturnTypesOptions,
384
+ context: ExecutorContext
385
+ ): Promise<ExecutorResult> {
386
+ const workspaceRoot = context.root;
387
+ const mode: ReturnTypeMode = options.mode ?? 'MODIFIED_NEW';
388
+
389
+ if (mode === 'OFF') {
390
+ console.log('\n⏭️ Skipping return type validation (mode: OFF)');
391
+ console.log('');
392
+ return { success: true };
393
+ }
394
+
395
+ console.log('\n📏 Validating Return Types\n');
396
+ console.log(` Mode: ${mode}`);
397
+
398
+ let violations: MethodViolation[] = [];
399
+
400
+ if (mode === 'ALL') {
401
+ console.log(' Scope: All TypeScript files');
402
+ console.log('');
403
+ violations = findViolationsForAll(workspaceRoot);
404
+ } else {
405
+ let base = process.env['NX_BASE'];
406
+ const head = process.env['NX_HEAD'];
407
+
408
+ if (!base) {
409
+ base = detectBase(workspaceRoot) ?? undefined;
410
+
411
+ if (!base) {
412
+ console.log('\n⏭️ Skipping return type validation (could not detect base branch)');
413
+ console.log('');
414
+ return { success: true };
415
+ }
416
+ }
417
+
418
+ console.log(` Base: ${base}`);
419
+ console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);
420
+ console.log('');
421
+
422
+ const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base, head);
423
+
424
+ if (changedFiles.length === 0) {
425
+ console.log('✅ No TypeScript files changed');
426
+ return { success: true };
427
+ }
428
+
429
+ console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);
430
+
431
+ if (mode === 'MODIFIED_NEW') {
432
+ violations = findViolationsForModifiedNew(workspaceRoot, changedFiles, base, head);
433
+ } else if (mode === 'MODIFIED') {
434
+ violations = findViolationsForModified(workspaceRoot, changedFiles);
435
+ }
436
+ }
437
+
438
+ if (violations.length === 0) {
439
+ console.log('✅ All methods have explicit return types');
440
+ return { success: true };
441
+ }
442
+
443
+ reportViolations(violations, mode);
444
+
445
+ return { success: false };
446
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "title": "Validate Return Types Executor",
4
+ "description": "Validates that methods have explicit return type annotations for better code readability.",
5
+ "type": "object",
6
+ "properties": {
7
+ "mode": {
8
+ "type": "string",
9
+ "enum": ["OFF", "MODIFIED_NEW", "MODIFIED", "ALL"],
10
+ "description": "OFF: skip validation. MODIFIED_NEW: only validate new methods. MODIFIED: validate all methods in modified files. ALL: validate all methods in all files.",
11
+ "default": "MODIFIED_NEW"
12
+ }
13
+ },
14
+ "required": []
15
+ }
package/executors.json CHANGED
@@ -64,6 +64,11 @@
64
64
  "implementation": "./architecture/executors/validate-code/executor",
65
65
  "schema": "./architecture/executors/validate-code/schema.json",
66
66
  "description": "Combined validation for new methods, modified methods, and file sizes"
67
+ },
68
+ "validate-return-types": {
69
+ "implementation": "./architecture/executors/validate-return-types/executor",
70
+ "schema": "./architecture/executors/validate-return-types/schema.json",
71
+ "description": "Validate methods have explicit return type annotations"
67
72
  }
68
73
  }
69
74
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/dev-config",
3
- "version": "0.2.70",
3
+ "version": "0.2.71",
4
4
  "description": "Development configuration, scripts, and patterns for WebPieces projects",
5
5
  "type": "commonjs",
6
6
  "bin": {