@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.
- package/architecture/executors/validate-code/executor.d.ts +2 -0
- package/architecture/executors/validate-code/executor.js +8 -1
- package/architecture/executors/validate-code/executor.js.map +1 -1
- package/architecture/executors/validate-code/executor.ts +12 -1
- package/architecture/executors/validate-code/schema.json +6 -0
- package/architecture/executors/validate-return-types/executor.d.ts +27 -0
- package/architecture/executors/validate-return-types/executor.js +376 -0
- package/architecture/executors/validate-return-types/executor.js.map +1 -0
- package/architecture/executors/validate-return-types/executor.ts +446 -0
- package/architecture/executors/validate-return-types/schema.json +15 -0
- package/executors.json +5 -0
- package/package.json +1 -1
|
@@ -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
|
|
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":";;
|
|
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
|
|
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
|
}
|