@webpieces/code-rules 0.0.1 → 0.2.114
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/package.json +4 -3
- package/src/cli.d.ts +1 -0
- package/src/cli.js +19 -0
- package/src/cli.js.map +1 -0
- package/src/diff-utils.d.ts +24 -0
- package/src/{diff-utils.ts → diff-utils.js} +30 -38
- package/src/diff-utils.js.map +1 -0
- package/src/from-shared-config.d.ts +28 -0
- package/src/from-shared-config.js +119 -0
- package/src/from-shared-config.js.map +1 -0
- package/src/index.js +33 -0
- package/src/index.js.map +1 -0
- package/src/validate-catch-error-pattern.d.ts +47 -0
- package/src/{validate-catch-error-pattern.ts → validate-catch-error-pattern.js} +74 -195
- package/src/validate-catch-error-pattern.js.map +1 -0
- package/src/validate-code.d.ts +98 -0
- package/src/{validate-code.ts → validate-code.js} +65 -259
- package/src/validate-code.js.map +1 -0
- package/src/validate-dtos.d.ts +41 -0
- package/src/{validate-dtos.ts → validate-dtos.js} +88 -215
- package/src/validate-dtos.js.map +1 -0
- package/src/validate-modified-files.d.ts +24 -0
- package/src/{validate-modified-files.ts → validate-modified-files.js} +46 -115
- package/src/validate-modified-files.js.map +1 -0
- package/src/validate-modified-methods.d.ts +30 -0
- package/src/{validate-modified-methods.ts → validate-modified-methods.js} +94 -196
- package/src/validate-modified-methods.js.map +1 -0
- package/src/validate-new-methods.d.ts +27 -0
- package/src/{validate-new-methods.ts → validate-new-methods.js} +63 -133
- package/src/validate-new-methods.js.map +1 -0
- package/src/validate-no-any-unknown.d.ts +41 -0
- package/src/{validate-no-any-unknown.ts → validate-no-any-unknown.js} +69 -146
- package/src/validate-no-any-unknown.js.map +1 -0
- package/src/validate-no-destructure.d.ts +51 -0
- package/src/{validate-no-destructure.ts → validate-no-destructure.js} +80 -166
- package/src/validate-no-destructure.js.map +1 -0
- package/src/validate-no-direct-api-resolver.d.ts +46 -0
- package/src/{validate-no-direct-api-resolver.ts → validate-no-direct-api-resolver.js} +112 -211
- package/src/validate-no-direct-api-resolver.js.map +1 -0
- package/src/validate-no-implicit-any.d.ts +36 -0
- package/src/{validate-no-implicit-any.ts → validate-no-implicit-any.js} +94 -141
- package/src/validate-no-implicit-any.js.map +1 -0
- package/src/validate-no-inline-types.d.ts +90 -0
- package/src/{validate-no-inline-types.ts → validate-no-inline-types.js} +93 -198
- package/src/validate-no-inline-types.js.map +1 -0
- package/src/validate-no-unmanaged-exceptions.d.ts +43 -0
- package/src/{validate-no-unmanaged-exceptions.ts → validate-no-unmanaged-exceptions.js} +71 -140
- package/src/validate-no-unmanaged-exceptions.js.map +1 -0
- package/src/validate-prisma-converters.d.ts +59 -0
- package/src/{validate-prisma-converters.ts → validate-prisma-converters.js} +120 -307
- package/src/validate-prisma-converters.js.map +1 -0
- package/src/validate-return-types.d.ts +28 -0
- package/src/{validate-return-types.ts → validate-return-types.js} +84 -168
- package/src/validate-return-types.js.map +1 -0
- package/LICENSE +0 -373
- package/jest.config.ts +0 -20
- package/project.json +0 -22
- package/src/cli.ts +0 -17
- package/src/from-shared-config.ts +0 -118
- package/tsconfig.json +0 -22
- package/tsconfig.lib.json +0 -10
- package/tsconfig.spec.json +0 -14
- /package/src/{index.ts → index.d.ts} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Validate Return Types Executor
|
|
3
4
|
*
|
|
@@ -16,39 +17,22 @@
|
|
|
16
17
|
*
|
|
17
18
|
* Escape hatch: Add webpieces-disable require-return-type comment with justification
|
|
18
19
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
export interface ValidateReturnTypesOptions {
|
|
28
|
-
mode?: ReturnTypeMode;
|
|
29
|
-
disableAllowed?: boolean;
|
|
30
|
-
ignoreModifiedUntilEpoch?: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface ExecutorResult {
|
|
34
|
-
success: boolean;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface MethodViolation {
|
|
38
|
-
file: string;
|
|
39
|
-
methodName: string;
|
|
40
|
-
line: number;
|
|
41
|
-
}
|
|
42
|
-
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.default = runValidator;
|
|
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"));
|
|
43
27
|
/**
|
|
44
28
|
* Get changed TypeScript files between base and head (or working tree if head not specified).
|
|
45
29
|
*/
|
|
46
30
|
// webpieces-disable max-lines-new-methods -- Git command handling with untracked files requires multiple code paths
|
|
47
|
-
function getChangedTypeScriptFiles(workspaceRoot
|
|
31
|
+
function getChangedTypeScriptFiles(workspaceRoot, base, head) {
|
|
48
32
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
49
33
|
try {
|
|
50
34
|
const diffTarget = head ? `${base} ${head}` : base;
|
|
51
|
-
const output = execSync(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {
|
|
35
|
+
const output = (0, child_process_1.execSync)(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {
|
|
52
36
|
cwd: workspaceRoot,
|
|
53
37
|
encoding: 'utf-8',
|
|
54
38
|
});
|
|
@@ -56,11 +40,10 @@ function getChangedTypeScriptFiles(workspaceRoot: string, base: string, head?: s
|
|
|
56
40
|
.trim()
|
|
57
41
|
.split('\n')
|
|
58
42
|
.filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
|
|
59
|
-
|
|
60
43
|
if (!head) {
|
|
61
44
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
62
45
|
try {
|
|
63
|
-
const untrackedOutput = execSync(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {
|
|
46
|
+
const untrackedOutput = (0, child_process_1.execSync)(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {
|
|
64
47
|
cwd: workspaceRoot,
|
|
65
48
|
encoding: 'utf-8',
|
|
66
49
|
});
|
|
@@ -70,39 +53,37 @@ function getChangedTypeScriptFiles(workspaceRoot: string, base: string, head?: s
|
|
|
70
53
|
.filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
|
|
71
54
|
const allFiles = new Set([...changedFiles, ...untrackedFiles]);
|
|
72
55
|
return Array.from(allFiles);
|
|
73
|
-
}
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
74
58
|
//const error = toError(err);
|
|
75
59
|
return changedFiles;
|
|
76
60
|
}
|
|
77
61
|
}
|
|
78
|
-
|
|
79
62
|
return changedFiles;
|
|
80
|
-
}
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
81
65
|
//const error = toError(err);
|
|
82
66
|
return [];
|
|
83
67
|
}
|
|
84
68
|
}
|
|
85
|
-
|
|
86
69
|
/**
|
|
87
70
|
* Get the diff content for a specific file.
|
|
88
71
|
*/
|
|
89
|
-
function getFileDiff(workspaceRoot
|
|
72
|
+
function getFileDiff(workspaceRoot, file, base, head) {
|
|
90
73
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
91
74
|
try {
|
|
92
75
|
const diffTarget = head ? `${base} ${head}` : base;
|
|
93
|
-
const diff = execSync(`git diff ${diffTarget} -- "${file}"`, {
|
|
76
|
+
const diff = (0, child_process_1.execSync)(`git diff ${diffTarget} -- "${file}"`, {
|
|
94
77
|
cwd: workspaceRoot,
|
|
95
78
|
encoding: 'utf-8',
|
|
96
79
|
});
|
|
97
|
-
|
|
98
80
|
if (!diff && !head) {
|
|
99
81
|
const fullPath = path.join(workspaceRoot, file);
|
|
100
82
|
if (fs.existsSync(fullPath)) {
|
|
101
|
-
const isUntracked = execSync(`git ls-files --others --exclude-standard "${file}"`, {
|
|
83
|
+
const isUntracked = (0, child_process_1.execSync)(`git ls-files --others --exclude-standard "${file}"`, {
|
|
102
84
|
cwd: workspaceRoot,
|
|
103
85
|
encoding: 'utf-8',
|
|
104
86
|
}).trim();
|
|
105
|
-
|
|
106
87
|
if (isUntracked) {
|
|
107
88
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
108
89
|
const lines = content.split('\n');
|
|
@@ -110,28 +91,25 @@ function getFileDiff(workspaceRoot: string, file: string, base: string, head?: s
|
|
|
110
91
|
}
|
|
111
92
|
}
|
|
112
93
|
}
|
|
113
|
-
|
|
114
94
|
return diff;
|
|
115
|
-
}
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
116
97
|
//const error = toError(err);
|
|
117
98
|
return '';
|
|
118
99
|
}
|
|
119
100
|
}
|
|
120
|
-
|
|
121
101
|
/**
|
|
122
102
|
* Parse diff to find newly added method signatures.
|
|
123
103
|
*/
|
|
124
|
-
function findNewMethodSignaturesInDiff(diffContent
|
|
125
|
-
const newMethods = new Set
|
|
104
|
+
function findNewMethodSignaturesInDiff(diffContent) {
|
|
105
|
+
const newMethods = new Set();
|
|
126
106
|
const lines = diffContent.split('\n');
|
|
127
|
-
|
|
128
107
|
const patterns = [
|
|
129
108
|
/^\+\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/,
|
|
130
109
|
/^\+\s*(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s*)?\(/,
|
|
131
110
|
/^\+\s*(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?function/,
|
|
132
111
|
/^\+\s*(?:(?:public|private|protected)\s+)?(?:static\s+)?(?:async\s+)?(\w+)\s*\(/,
|
|
133
112
|
];
|
|
134
|
-
|
|
135
113
|
for (const line of lines) {
|
|
136
114
|
if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
137
115
|
for (const pattern of patterns) {
|
|
@@ -146,14 +124,12 @@ function findNewMethodSignaturesInDiff(diffContent: string): Set<string> {
|
|
|
146
124
|
}
|
|
147
125
|
}
|
|
148
126
|
}
|
|
149
|
-
|
|
150
127
|
return newMethods;
|
|
151
128
|
}
|
|
152
|
-
|
|
153
129
|
/**
|
|
154
130
|
* Check if a line contains a webpieces-disable comment for return type.
|
|
155
131
|
*/
|
|
156
|
-
function hasDisableComment(lines
|
|
132
|
+
function hasDisableComment(lines, lineNumber) {
|
|
157
133
|
const startCheck = Math.max(0, lineNumber - 5);
|
|
158
134
|
for (let i = lineNumber - 2; i >= startCheck; i--) {
|
|
159
135
|
const line = lines[i]?.trim() ?? '';
|
|
@@ -166,43 +142,30 @@ function hasDisableComment(lines: string[], lineNumber: number): boolean {
|
|
|
166
142
|
}
|
|
167
143
|
return false;
|
|
168
144
|
}
|
|
169
|
-
|
|
170
145
|
/**
|
|
171
146
|
* Check if a method has an explicit return type annotation.
|
|
172
147
|
*/
|
|
173
|
-
function hasExplicitReturnType(node
|
|
148
|
+
function hasExplicitReturnType(node) {
|
|
174
149
|
return node.type !== undefined;
|
|
175
150
|
}
|
|
176
|
-
|
|
177
|
-
interface MethodInfo {
|
|
178
|
-
name: string;
|
|
179
|
-
line: number;
|
|
180
|
-
endLine: number;
|
|
181
|
-
hasReturnType: boolean;
|
|
182
|
-
hasDisableComment: boolean;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
151
|
/**
|
|
186
152
|
* Parse a TypeScript file and find methods with their return type status.
|
|
187
153
|
*/
|
|
188
154
|
// webpieces-disable max-lines-new-methods -- AST traversal requires inline visitor function
|
|
189
|
-
function findMethodsInFile(filePath
|
|
155
|
+
function findMethodsInFile(filePath, workspaceRoot) {
|
|
190
156
|
const fullPath = path.join(workspaceRoot, filePath);
|
|
191
|
-
if (!fs.existsSync(fullPath))
|
|
192
|
-
|
|
157
|
+
if (!fs.existsSync(fullPath))
|
|
158
|
+
return [];
|
|
193
159
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
194
160
|
const fileLines = content.split('\n');
|
|
195
161
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
196
|
-
|
|
197
|
-
const methods: MethodInfo[] = [];
|
|
198
|
-
|
|
162
|
+
const methods = [];
|
|
199
163
|
// webpieces-disable max-lines-new-methods -- AST visitor pattern requires handling multiple node types
|
|
200
|
-
function visit(node
|
|
201
|
-
let methodName
|
|
202
|
-
let startLine
|
|
203
|
-
let endLine
|
|
164
|
+
function visit(node) {
|
|
165
|
+
let methodName;
|
|
166
|
+
let startLine;
|
|
167
|
+
let endLine;
|
|
204
168
|
let hasReturnType = false;
|
|
205
|
-
|
|
206
169
|
if (ts.isMethodDeclaration(node) && node.name) {
|
|
207
170
|
methodName = node.name.getText(sourceFile);
|
|
208
171
|
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
@@ -210,14 +173,16 @@ function findMethodsInFile(filePath: string, workspaceRoot: string): MethodInfo[
|
|
|
210
173
|
startLine = start.line + 1;
|
|
211
174
|
endLine = end.line + 1;
|
|
212
175
|
hasReturnType = hasExplicitReturnType(node);
|
|
213
|
-
}
|
|
176
|
+
}
|
|
177
|
+
else if (ts.isFunctionDeclaration(node) && node.name) {
|
|
214
178
|
methodName = node.name.getText(sourceFile);
|
|
215
179
|
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
216
180
|
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
217
181
|
startLine = start.line + 1;
|
|
218
182
|
endLine = end.line + 1;
|
|
219
183
|
hasReturnType = hasExplicitReturnType(node);
|
|
220
|
-
}
|
|
184
|
+
}
|
|
185
|
+
else if (ts.isArrowFunction(node)) {
|
|
221
186
|
if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
|
|
222
187
|
methodName = node.parent.name.getText(sourceFile);
|
|
223
188
|
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
@@ -227,7 +192,6 @@ function findMethodsInFile(filePath: string, workspaceRoot: string): MethodInfo[
|
|
|
227
192
|
hasReturnType = hasExplicitReturnType(node);
|
|
228
193
|
}
|
|
229
194
|
}
|
|
230
|
-
|
|
231
195
|
if (methodName && startLine !== undefined && endLine !== undefined) {
|
|
232
196
|
methods.push({
|
|
233
197
|
name: methodName,
|
|
@@ -237,46 +201,41 @@ function findMethodsInFile(filePath: string, workspaceRoot: string): MethodInfo[
|
|
|
237
201
|
hasDisableComment: hasDisableComment(fileLines, startLine),
|
|
238
202
|
});
|
|
239
203
|
}
|
|
240
|
-
|
|
241
204
|
ts.forEachChild(node, visit);
|
|
242
205
|
}
|
|
243
|
-
|
|
244
206
|
visit(sourceFile);
|
|
245
207
|
return methods;
|
|
246
208
|
}
|
|
247
|
-
|
|
248
209
|
/**
|
|
249
210
|
* Parse diff to extract changed line numbers (both additions and modifications).
|
|
250
211
|
*/
|
|
251
|
-
function getChangedLineNumbers(diffContent
|
|
252
|
-
const changedLines = new Set
|
|
212
|
+
function getChangedLineNumbers(diffContent) {
|
|
213
|
+
const changedLines = new Set();
|
|
253
214
|
const lines = diffContent.split('\n');
|
|
254
215
|
let currentLine = 0;
|
|
255
|
-
|
|
256
216
|
for (const line of lines) {
|
|
257
217
|
const hunkMatch = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
|
258
218
|
if (hunkMatch) {
|
|
259
219
|
currentLine = parseInt(hunkMatch[1], 10);
|
|
260
220
|
continue;
|
|
261
221
|
}
|
|
262
|
-
|
|
263
222
|
if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
264
223
|
changedLines.add(currentLine);
|
|
265
224
|
currentLine++;
|
|
266
|
-
}
|
|
225
|
+
}
|
|
226
|
+
else if (line.startsWith('-') && !line.startsWith('---')) {
|
|
267
227
|
// Deletions don't increment line number
|
|
268
|
-
}
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
269
230
|
currentLine++;
|
|
270
231
|
}
|
|
271
232
|
}
|
|
272
|
-
|
|
273
233
|
return changedLines;
|
|
274
234
|
}
|
|
275
|
-
|
|
276
235
|
/**
|
|
277
236
|
* Check if a method has any changed lines within its range.
|
|
278
237
|
*/
|
|
279
|
-
function methodHasChanges(method
|
|
238
|
+
function methodHasChanges(method, changedLines) {
|
|
280
239
|
for (let line = method.line; line <= method.endLine; line++) {
|
|
281
240
|
if (changedLines.has(line)) {
|
|
282
241
|
return true;
|
|
@@ -284,33 +243,25 @@ function methodHasChanges(method: MethodInfo, changedLines: Set<number>): boolea
|
|
|
284
243
|
}
|
|
285
244
|
return false;
|
|
286
245
|
}
|
|
287
|
-
|
|
288
246
|
/**
|
|
289
247
|
* Find NEW methods without explicit return types (NEW_METHODS mode).
|
|
290
248
|
*/
|
|
291
249
|
// webpieces-disable max-lines-new-methods -- File iteration with diff parsing and method matching
|
|
292
|
-
function findViolationsForNewMethods(
|
|
293
|
-
|
|
294
|
-
changedFiles: string[],
|
|
295
|
-
base: string,
|
|
296
|
-
head: string | undefined,
|
|
297
|
-
disableAllowed: boolean
|
|
298
|
-
): MethodViolation[] {
|
|
299
|
-
const violations: MethodViolation[] = [];
|
|
300
|
-
|
|
250
|
+
function findViolationsForNewMethods(workspaceRoot, changedFiles, base, head, disableAllowed) {
|
|
251
|
+
const violations = [];
|
|
301
252
|
for (const file of changedFiles) {
|
|
302
253
|
const diff = getFileDiff(workspaceRoot, file, base, head);
|
|
303
254
|
const newMethodNames = findNewMethodSignaturesInDiff(diff);
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
255
|
+
if (newMethodNames.size === 0)
|
|
256
|
+
continue;
|
|
307
257
|
const methods = findMethodsInFile(file, workspaceRoot);
|
|
308
|
-
|
|
309
258
|
for (const method of methods) {
|
|
310
|
-
if (!newMethodNames.has(method.name))
|
|
311
|
-
|
|
312
|
-
if (
|
|
313
|
-
|
|
259
|
+
if (!newMethodNames.has(method.name))
|
|
260
|
+
continue;
|
|
261
|
+
if (method.hasReturnType)
|
|
262
|
+
continue;
|
|
263
|
+
if (disableAllowed && method.hasDisableComment)
|
|
264
|
+
continue;
|
|
314
265
|
violations.push({
|
|
315
266
|
file,
|
|
316
267
|
methodName: method.name,
|
|
@@ -318,39 +269,28 @@ function findViolationsForNewMethods(
|
|
|
318
269
|
});
|
|
319
270
|
}
|
|
320
271
|
}
|
|
321
|
-
|
|
322
272
|
return violations;
|
|
323
273
|
}
|
|
324
|
-
|
|
325
274
|
/**
|
|
326
275
|
* Find NEW methods AND methods with changes (NEW_AND_MODIFIED_METHODS mode).
|
|
327
276
|
*/
|
|
328
277
|
// webpieces-disable max-lines-new-methods -- Combines new method detection with change detection
|
|
329
|
-
function findViolationsForModifiedAndNewMethods(
|
|
330
|
-
|
|
331
|
-
changedFiles: string[],
|
|
332
|
-
base: string,
|
|
333
|
-
head: string | undefined,
|
|
334
|
-
disableAllowed: boolean
|
|
335
|
-
): MethodViolation[] {
|
|
336
|
-
const violations: MethodViolation[] = [];
|
|
337
|
-
|
|
278
|
+
function findViolationsForModifiedAndNewMethods(workspaceRoot, changedFiles, base, head, disableAllowed) {
|
|
279
|
+
const violations = [];
|
|
338
280
|
for (const file of changedFiles) {
|
|
339
281
|
const diff = getFileDiff(workspaceRoot, file, base, head);
|
|
340
282
|
const newMethodNames = findNewMethodSignaturesInDiff(diff);
|
|
341
283
|
const changedLines = getChangedLineNumbers(diff);
|
|
342
|
-
|
|
343
284
|
const methods = findMethodsInFile(file, workspaceRoot);
|
|
344
|
-
|
|
345
285
|
for (const method of methods) {
|
|
346
|
-
if (method.hasReturnType)
|
|
347
|
-
|
|
348
|
-
|
|
286
|
+
if (method.hasReturnType)
|
|
287
|
+
continue;
|
|
288
|
+
if (disableAllowed && method.hasDisableComment)
|
|
289
|
+
continue;
|
|
349
290
|
const isNewMethod = newMethodNames.has(method.name);
|
|
350
291
|
const isModifiedMethod = methodHasChanges(method, changedLines);
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
292
|
+
if (!isNewMethod && !isModifiedMethod)
|
|
293
|
+
continue;
|
|
354
294
|
violations.push({
|
|
355
295
|
file,
|
|
356
296
|
methodName: method.name,
|
|
@@ -358,23 +298,20 @@ function findViolationsForModifiedAndNewMethods(
|
|
|
358
298
|
});
|
|
359
299
|
}
|
|
360
300
|
}
|
|
361
|
-
|
|
362
301
|
return violations;
|
|
363
302
|
}
|
|
364
|
-
|
|
365
303
|
/**
|
|
366
304
|
* Find all methods without explicit return types in modified files (MODIFIED_FILES mode).
|
|
367
305
|
*/
|
|
368
|
-
function findViolationsForModifiedFiles(workspaceRoot
|
|
369
|
-
const violations
|
|
370
|
-
|
|
306
|
+
function findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed) {
|
|
307
|
+
const violations = [];
|
|
371
308
|
for (const file of changedFiles) {
|
|
372
309
|
const methods = findMethodsInFile(file, workspaceRoot);
|
|
373
|
-
|
|
374
310
|
for (const method of methods) {
|
|
375
|
-
if (method.hasReturnType)
|
|
376
|
-
|
|
377
|
-
|
|
311
|
+
if (method.hasReturnType)
|
|
312
|
+
continue;
|
|
313
|
+
if (disableAllowed && method.hasDisableComment)
|
|
314
|
+
continue;
|
|
378
315
|
violations.push({
|
|
379
316
|
file,
|
|
380
317
|
methodName: method.name,
|
|
@@ -382,50 +319,47 @@ function findViolationsForModifiedFiles(workspaceRoot: string, changedFiles: str
|
|
|
382
319
|
});
|
|
383
320
|
}
|
|
384
321
|
}
|
|
385
|
-
|
|
386
322
|
return violations;
|
|
387
323
|
}
|
|
388
|
-
|
|
389
324
|
/**
|
|
390
325
|
* Auto-detect the base branch by finding the merge-base with origin/main.
|
|
391
326
|
*/
|
|
392
|
-
function detectBase(workspaceRoot
|
|
327
|
+
function detectBase(workspaceRoot) {
|
|
393
328
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
394
329
|
try {
|
|
395
|
-
const mergeBase = execSync('git merge-base HEAD origin/main', {
|
|
330
|
+
const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD origin/main', {
|
|
396
331
|
cwd: workspaceRoot,
|
|
397
332
|
encoding: 'utf-8',
|
|
398
333
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
399
334
|
}).trim();
|
|
400
|
-
|
|
401
335
|
if (mergeBase) {
|
|
402
336
|
return mergeBase;
|
|
403
337
|
}
|
|
404
|
-
}
|
|
338
|
+
}
|
|
339
|
+
catch (err) {
|
|
405
340
|
//const error = toError(err);
|
|
406
341
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
407
342
|
try {
|
|
408
|
-
const mergeBase = execSync('git merge-base HEAD main', {
|
|
343
|
+
const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD main', {
|
|
409
344
|
cwd: workspaceRoot,
|
|
410
345
|
encoding: 'utf-8',
|
|
411
346
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
412
347
|
}).trim();
|
|
413
|
-
|
|
414
348
|
if (mergeBase) {
|
|
415
349
|
return mergeBase;
|
|
416
350
|
}
|
|
417
|
-
}
|
|
351
|
+
}
|
|
352
|
+
catch (err2) {
|
|
418
353
|
//const error2 = toError(err2);
|
|
419
354
|
// Ignore
|
|
420
355
|
}
|
|
421
356
|
}
|
|
422
357
|
return null;
|
|
423
358
|
}
|
|
424
|
-
|
|
425
359
|
/**
|
|
426
360
|
* Report violations to console.
|
|
427
361
|
*/
|
|
428
|
-
function reportViolations(violations
|
|
362
|
+
function reportViolations(violations, mode) {
|
|
429
363
|
console.error('');
|
|
430
364
|
console.error('❌ Methods missing explicit return types!');
|
|
431
365
|
console.error('');
|
|
@@ -435,13 +369,11 @@ function reportViolations(violations: MethodViolation[], mode: ReturnTypeMode):
|
|
|
435
369
|
console.error(' GOOD: method(): MyClass { return new MyClass(); }');
|
|
436
370
|
console.error(' GOOD: async method(): Promise<MyType> { ... }');
|
|
437
371
|
console.error('');
|
|
438
|
-
|
|
439
372
|
for (const v of violations) {
|
|
440
373
|
console.error(` ❌ ${v.file}:${v.line}`);
|
|
441
374
|
console.error(` Method: ${v.methodName} - missing return type annotation`);
|
|
442
375
|
}
|
|
443
376
|
console.error('');
|
|
444
|
-
|
|
445
377
|
console.error(' To fix: Add explicit return type after the parameter list');
|
|
446
378
|
console.error('');
|
|
447
379
|
console.error(' Escape hatch (use sparingly):');
|
|
@@ -450,12 +382,11 @@ function reportViolations(violations: MethodViolation[], mode: ReturnTypeMode):
|
|
|
450
382
|
console.error(` Current mode: ${mode}`);
|
|
451
383
|
console.error('');
|
|
452
384
|
}
|
|
453
|
-
|
|
454
385
|
/**
|
|
455
386
|
* Resolve mode considering ignoreModifiedUntilEpoch override.
|
|
456
387
|
* When active, downgrades to OFF. When expired, logs a warning.
|
|
457
388
|
*/
|
|
458
|
-
function resolveMode(normalMode
|
|
389
|
+
function resolveMode(normalMode, epoch) {
|
|
459
390
|
if (epoch === undefined || normalMode === 'OFF') {
|
|
460
391
|
return normalMode;
|
|
461
392
|
}
|
|
@@ -468,65 +399,50 @@ function resolveMode(normalMode: ReturnTypeMode, epoch: number | undefined): Ret
|
|
|
468
399
|
}
|
|
469
400
|
return normalMode;
|
|
470
401
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
options: ValidateReturnTypesOptions,
|
|
474
|
-
workspaceRoot: string
|
|
475
|
-
): Promise<ExecutorResult> {
|
|
476
|
-
const mode: ReturnTypeMode = resolveMode(options.mode ?? 'NEW_METHODS', options.ignoreModifiedUntilEpoch);
|
|
402
|
+
async function runValidator(options, workspaceRoot) {
|
|
403
|
+
const mode = resolveMode(options.mode ?? 'NEW_METHODS', options.ignoreModifiedUntilEpoch);
|
|
477
404
|
const disableAllowed = options.disableAllowed ?? true;
|
|
478
|
-
|
|
479
405
|
if (mode === 'OFF') {
|
|
480
406
|
console.log('\n⏭️ Skipping return type validation (mode: OFF)');
|
|
481
407
|
console.log('');
|
|
482
408
|
return { success: true };
|
|
483
409
|
}
|
|
484
|
-
|
|
485
410
|
console.log('\n📏 Validating Return Types\n');
|
|
486
411
|
console.log(` Mode: ${mode}`);
|
|
487
|
-
|
|
488
412
|
let base = process.env['NX_BASE'];
|
|
489
413
|
const head = process.env['NX_HEAD'];
|
|
490
|
-
|
|
491
414
|
if (!base) {
|
|
492
415
|
base = detectBase(workspaceRoot) ?? undefined;
|
|
493
|
-
|
|
494
416
|
if (!base) {
|
|
495
417
|
console.log('\n⏭️ Skipping return type validation (could not detect base branch)');
|
|
496
418
|
console.log('');
|
|
497
419
|
return { success: true };
|
|
498
420
|
}
|
|
499
421
|
}
|
|
500
|
-
|
|
501
422
|
console.log(` Base: ${base}`);
|
|
502
423
|
console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);
|
|
503
424
|
console.log('');
|
|
504
|
-
|
|
505
425
|
const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base, head);
|
|
506
|
-
|
|
507
426
|
if (changedFiles.length === 0) {
|
|
508
427
|
console.log('✅ No TypeScript files changed');
|
|
509
428
|
return { success: true };
|
|
510
429
|
}
|
|
511
|
-
|
|
512
430
|
console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);
|
|
513
|
-
|
|
514
|
-
let violations: MethodViolation[] = [];
|
|
515
|
-
|
|
431
|
+
let violations = [];
|
|
516
432
|
if (mode === 'NEW_METHODS') {
|
|
517
433
|
violations = findViolationsForNewMethods(workspaceRoot, changedFiles, base, head, disableAllowed);
|
|
518
|
-
}
|
|
434
|
+
}
|
|
435
|
+
else if (mode === 'NEW_AND_MODIFIED_METHODS') {
|
|
519
436
|
violations = findViolationsForModifiedAndNewMethods(workspaceRoot, changedFiles, base, head, disableAllowed);
|
|
520
|
-
}
|
|
437
|
+
}
|
|
438
|
+
else if (mode === 'MODIFIED_FILES') {
|
|
521
439
|
violations = findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed);
|
|
522
440
|
}
|
|
523
|
-
|
|
524
441
|
if (violations.length === 0) {
|
|
525
442
|
console.log('✅ All methods have explicit return types');
|
|
526
443
|
return { success: true };
|
|
527
444
|
}
|
|
528
|
-
|
|
529
445
|
reportViolations(violations, mode);
|
|
530
|
-
|
|
531
446
|
return { success: false };
|
|
532
447
|
}
|
|
448
|
+
//# sourceMappingURL=validate-return-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-return-types.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-return-types.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 * - NEW_METHODS: Only validate new methods (detected via git diff)\n * - NEW_AND_MODIFIED_METHODS: Validate new methods + methods with changes in their line range\n * - MODIFIED_FILES: Validate all methods in modified files\n *\n * Escape hatch: Add webpieces-disable require-return-type comment with justification\n */\n\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' | 'NEW_METHODS' | 'NEW_AND_MODIFIED_METHODS' | 'MODIFIED_FILES';\n\nexport interface ValidateReturnTypesOptions {\n mode?: ReturnTypeMode;\n disableAllowed?: boolean;\n ignoreModifiedUntilEpoch?: number;\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 // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\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 // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\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 (err: unknown) {\n //const error = toError(err);\n return changedFiles;\n }\n }\n\n return changedFiles;\n } catch (err: unknown) {\n //const error = toError(err);\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 // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\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 (err: unknown) {\n //const error = toError(err);\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 endLine: 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 endLine: 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 const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());\n startLine = start.line + 1;\n endLine = end.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 const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());\n startLine = start.line + 1;\n endLine = end.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 const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());\n startLine = start.line + 1;\n endLine = end.line + 1;\n hasReturnType = hasExplicitReturnType(node);\n }\n }\n\n if (methodName && startLine !== undefined && endLine !== undefined) {\n methods.push({\n name: methodName,\n line: startLine,\n endLine,\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 * Parse diff to extract changed line numbers (both additions and modifications).\n */\nfunction getChangedLineNumbers(diffContent: string): Set<number> {\n const changedLines = new Set<number>();\n const lines = diffContent.split('\\n');\n let currentLine = 0;\n\n for (const line of lines) {\n const hunkMatch = line.match(/^@@ -\\d+(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@/);\n if (hunkMatch) {\n currentLine = parseInt(hunkMatch[1], 10);\n continue;\n }\n\n if (line.startsWith('+') && !line.startsWith('+++')) {\n changedLines.add(currentLine);\n currentLine++;\n } else if (line.startsWith('-') && !line.startsWith('---')) {\n // Deletions don't increment line number\n } else {\n currentLine++;\n }\n }\n\n return changedLines;\n}\n\n/**\n * Check if a method has any changed lines within its range.\n */\nfunction methodHasChanges(method: MethodInfo, changedLines: Set<number>): boolean {\n for (let line = method.line; line <= method.endLine; line++) {\n if (changedLines.has(line)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Find NEW methods without explicit return types (NEW_METHODS mode).\n */\n// webpieces-disable max-lines-new-methods -- File iteration with diff parsing and method matching\nfunction findViolationsForNewMethods(\n workspaceRoot: string,\n changedFiles: string[],\n base: string,\n head: string | undefined,\n disableAllowed: boolean\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 (disableAllowed && 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 NEW methods AND methods with changes (NEW_AND_MODIFIED_METHODS mode).\n */\n// webpieces-disable max-lines-new-methods -- Combines new method detection with change detection\nfunction findViolationsForModifiedAndNewMethods(\n workspaceRoot: string,\n changedFiles: string[],\n base: string,\n head: string | undefined,\n disableAllowed: boolean\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 const changedLines = getChangedLineNumbers(diff);\n\n const methods = findMethodsInFile(file, workspaceRoot);\n\n for (const method of methods) {\n if (method.hasReturnType) continue;\n if (disableAllowed && method.hasDisableComment) continue;\n\n const isNewMethod = newMethodNames.has(method.name);\n const isModifiedMethod = methodHasChanges(method, changedLines);\n\n if (!isNewMethod && !isModifiedMethod) 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 (MODIFIED_FILES mode).\n */\nfunction findViolationsForModifiedFiles(workspaceRoot: string, changedFiles: string[], disableAllowed: boolean): 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 (disableAllowed && 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 * Auto-detect the base branch by finding the merge-base with origin/main.\n */\nfunction detectBase(workspaceRoot: string): string | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\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 (err: unknown) {\n //const error = toError(err);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\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 (err2: unknown) {\n //const error2 = toError(err2);\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\n/**\n * Resolve mode considering ignoreModifiedUntilEpoch override.\n * When active, downgrades to OFF. When expired, logs a warning.\n */\nfunction resolveMode(normalMode: ReturnTypeMode, epoch: number | undefined): ReturnTypeMode {\n if (epoch === undefined || normalMode === 'OFF') {\n return normalMode;\n }\n const nowSeconds = Date.now() / 1000;\n if (nowSeconds < epoch) {\n const expiresDate = new Date(epoch * 1000).toISOString().split('T')[0];\n console.log(`\\n⏭️ Skipping require-return-type validation (ignoreModifiedUntilEpoch active, expires: ${expiresDate})`);\n console.log('');\n return 'OFF';\n }\n return normalMode;\n}\n\nexport default async function runValidator(\n options: ValidateReturnTypesOptions,\n workspaceRoot: string\n): Promise<ExecutorResult> {\n const mode: ReturnTypeMode = resolveMode(options.mode ?? 'NEW_METHODS', options.ignoreModifiedUntilEpoch);\n const disableAllowed = options.disableAllowed ?? true;\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 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 let violations: MethodViolation[] = [];\n\n if (mode === 'NEW_METHODS') {\n violations = findViolationsForNewMethods(workspaceRoot, changedFiles, base, head, disableAllowed);\n } else if (mode === 'NEW_AND_MODIFIED_METHODS') {\n violations = findViolationsForModifiedAndNewMethods(workspaceRoot, changedFiles, base, head, disableAllowed);\n } else if (mode === 'MODIFIED_FILES') {\n violations = findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed);\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"]}
|