@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 Catch Error Pattern Executor
|
|
3
4
|
*
|
|
@@ -35,57 +36,31 @@
|
|
|
35
36
|
* // webpieces-disable catch-error-pattern -- [your justification]
|
|
36
37
|
* } catch (err: unknown) {
|
|
37
38
|
*/
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
export interface ValidateCatchErrorPatternOptions {
|
|
47
|
-
mode?: CatchErrorPatternMode;
|
|
48
|
-
disableAllowed?: boolean;
|
|
49
|
-
ignoreModifiedUntilEpoch?: number;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface ExecutorResult {
|
|
53
|
-
success: boolean;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
interface CatchViolation {
|
|
57
|
-
file: string;
|
|
58
|
-
line: number;
|
|
59
|
-
message: string;
|
|
60
|
-
context: string;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
interface CatchViolationInfo {
|
|
64
|
-
line: number;
|
|
65
|
-
message: string;
|
|
66
|
-
context: string;
|
|
67
|
-
hasDisableComment: boolean;
|
|
68
|
-
}
|
|
69
|
-
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.default = runValidator;
|
|
41
|
+
const tslib_1 = require("tslib");
|
|
42
|
+
const child_process_1 = require("child_process");
|
|
43
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
44
|
+
const path = tslib_1.__importStar(require("path"));
|
|
45
|
+
const ts = tslib_1.__importStar(require("typescript"));
|
|
70
46
|
/**
|
|
71
47
|
* Check if a file is a test file that should be skipped.
|
|
72
48
|
*/
|
|
73
|
-
function isTestFile(filePath
|
|
49
|
+
function isTestFile(filePath) {
|
|
74
50
|
return filePath.includes('.spec.ts') ||
|
|
75
51
|
filePath.includes('.test.ts') ||
|
|
76
52
|
filePath.includes('__tests__/');
|
|
77
53
|
}
|
|
78
|
-
|
|
79
54
|
/**
|
|
80
55
|
* Get changed TypeScript files between base and head.
|
|
81
56
|
* Excludes test files.
|
|
82
57
|
*/
|
|
83
58
|
// webpieces-disable max-lines-new-methods -- Git command handling with untracked files requires multiple code paths
|
|
84
|
-
function getChangedTypeScriptFiles(workspaceRoot
|
|
59
|
+
function getChangedTypeScriptFiles(workspaceRoot, base, head) {
|
|
85
60
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
86
61
|
try {
|
|
87
62
|
const diffTarget = head ? `${base} ${head}` : base;
|
|
88
|
-
const output = execSync(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {
|
|
63
|
+
const output = (0, child_process_1.execSync)(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {
|
|
89
64
|
cwd: workspaceRoot,
|
|
90
65
|
encoding: 'utf-8',
|
|
91
66
|
});
|
|
@@ -93,11 +68,10 @@ function getChangedTypeScriptFiles(workspaceRoot: string, base: string, head?: s
|
|
|
93
68
|
.trim()
|
|
94
69
|
.split('\n')
|
|
95
70
|
.filter((f) => f && !isTestFile(f));
|
|
96
|
-
|
|
97
71
|
if (!head) {
|
|
98
72
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
99
73
|
try {
|
|
100
|
-
const untrackedOutput = execSync(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {
|
|
74
|
+
const untrackedOutput = (0, child_process_1.execSync)(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {
|
|
101
75
|
cwd: workspaceRoot,
|
|
102
76
|
encoding: 'utf-8',
|
|
103
77
|
});
|
|
@@ -107,39 +81,37 @@ function getChangedTypeScriptFiles(workspaceRoot: string, base: string, head?: s
|
|
|
107
81
|
.filter((f) => f && !isTestFile(f));
|
|
108
82
|
const allFiles = new Set([...changedFiles, ...untrackedFiles]);
|
|
109
83
|
return Array.from(allFiles);
|
|
110
|
-
|
|
111
|
-
}
|
|
84
|
+
// webpieces-disable catch-error-pattern -- intentional swallow of git command failure
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
112
87
|
return changedFiles;
|
|
113
88
|
}
|
|
114
89
|
}
|
|
115
|
-
|
|
116
90
|
return changedFiles;
|
|
117
|
-
|
|
118
|
-
}
|
|
91
|
+
// webpieces-disable catch-error-pattern -- intentional swallow of git command failure
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
119
94
|
return [];
|
|
120
95
|
}
|
|
121
96
|
}
|
|
122
|
-
|
|
123
97
|
/**
|
|
124
98
|
* Get the diff content for a specific file.
|
|
125
99
|
*/
|
|
126
|
-
function getFileDiff(workspaceRoot
|
|
100
|
+
function getFileDiff(workspaceRoot, file, base, head) {
|
|
127
101
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
128
102
|
try {
|
|
129
103
|
const diffTarget = head ? `${base} ${head}` : base;
|
|
130
|
-
const diff = execSync(`git diff ${diffTarget} -- "${file}"`, {
|
|
104
|
+
const diff = (0, child_process_1.execSync)(`git diff ${diffTarget} -- "${file}"`, {
|
|
131
105
|
cwd: workspaceRoot,
|
|
132
106
|
encoding: 'utf-8',
|
|
133
107
|
});
|
|
134
|
-
|
|
135
108
|
if (!diff && !head) {
|
|
136
109
|
const fullPath = path.join(workspaceRoot, file);
|
|
137
110
|
if (fs.existsSync(fullPath)) {
|
|
138
|
-
const isUntracked = execSync(`git ls-files --others --exclude-standard "${file}"`, {
|
|
111
|
+
const isUntracked = (0, child_process_1.execSync)(`git ls-files --others --exclude-standard "${file}"`, {
|
|
139
112
|
cwd: workspaceRoot,
|
|
140
113
|
encoding: 'utf-8',
|
|
141
114
|
}).trim();
|
|
142
|
-
|
|
143
115
|
if (isUntracked) {
|
|
144
116
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
145
117
|
const lines = content.split('\n');
|
|
@@ -147,47 +119,44 @@ function getFileDiff(workspaceRoot: string, file: string, base: string, head?: s
|
|
|
147
119
|
}
|
|
148
120
|
}
|
|
149
121
|
}
|
|
150
|
-
|
|
151
122
|
return diff;
|
|
152
|
-
|
|
153
|
-
}
|
|
123
|
+
// webpieces-disable catch-error-pattern -- intentional swallow of git command failure
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
154
126
|
return '';
|
|
155
127
|
}
|
|
156
128
|
}
|
|
157
|
-
|
|
158
129
|
/**
|
|
159
130
|
* Parse diff to extract changed line numbers (additions only).
|
|
160
131
|
*/
|
|
161
|
-
function getChangedLineNumbers(diffContent
|
|
162
|
-
const changedLines = new Set
|
|
132
|
+
function getChangedLineNumbers(diffContent) {
|
|
133
|
+
const changedLines = new Set();
|
|
163
134
|
const lines = diffContent.split('\n');
|
|
164
135
|
let currentLine = 0;
|
|
165
|
-
|
|
166
136
|
for (const line of lines) {
|
|
167
137
|
const hunkMatch = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
|
168
138
|
if (hunkMatch) {
|
|
169
139
|
currentLine = parseInt(hunkMatch[1], 10);
|
|
170
140
|
continue;
|
|
171
141
|
}
|
|
172
|
-
|
|
173
142
|
if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
174
143
|
changedLines.add(currentLine);
|
|
175
144
|
currentLine++;
|
|
176
|
-
}
|
|
145
|
+
}
|
|
146
|
+
else if (line.startsWith('-') && !line.startsWith('---')) {
|
|
177
147
|
// Deletions don't increment line number
|
|
178
|
-
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
179
150
|
currentLine++;
|
|
180
151
|
}
|
|
181
152
|
}
|
|
182
|
-
|
|
183
153
|
return changedLines;
|
|
184
154
|
}
|
|
185
|
-
|
|
186
155
|
/**
|
|
187
156
|
* Check if a line contains a disable comment for catch-error-pattern.
|
|
188
157
|
* Recognizes both webpieces-disable and eslint-disable-next-line @webpieces/ formats.
|
|
189
158
|
*/
|
|
190
|
-
function hasDisableComment(lines
|
|
159
|
+
function hasDisableComment(lines, lineNumber) {
|
|
191
160
|
const startCheck = Math.max(0, lineNumber - 5);
|
|
192
161
|
for (let i = lineNumber - 2; i >= startCheck; i--) {
|
|
193
162
|
const line = lines[i]?.trim() ?? '';
|
|
@@ -203,56 +172,37 @@ function hasDisableComment(lines: string[], lineNumber: number): boolean {
|
|
|
203
172
|
}
|
|
204
173
|
return false;
|
|
205
174
|
}
|
|
206
|
-
|
|
207
175
|
/**
|
|
208
176
|
* Check if the catch block contains a disable comment for catch-error-pattern.
|
|
209
177
|
*/
|
|
210
|
-
function hasBlockLevelDisable(sourceText
|
|
178
|
+
function hasBlockLevelDisable(sourceText, blockStart, blockEnd) {
|
|
211
179
|
const blockText = sourceText.substring(blockStart, blockEnd);
|
|
212
180
|
return blockText.includes('webpieces-disable') && blockText.includes('catch-error-pattern') ||
|
|
213
181
|
blockText.includes('@webpieces/catch-error-pattern');
|
|
214
182
|
}
|
|
215
|
-
|
|
216
183
|
/**
|
|
217
184
|
* Check if the catch block body text contains the commented-out ignore pattern.
|
|
218
185
|
*/
|
|
219
|
-
function hasIgnoreComment(
|
|
220
|
-
sourceText: string,
|
|
221
|
-
blockStart: number,
|
|
222
|
-
blockEnd: number,
|
|
223
|
-
expectedVarName: string,
|
|
224
|
-
actualParamName: string,
|
|
225
|
-
): boolean {
|
|
186
|
+
function hasIgnoreComment(sourceText, blockStart, blockEnd, expectedVarName, actualParamName) {
|
|
226
187
|
const blockText = sourceText.substring(blockStart, blockEnd);
|
|
227
|
-
const ignorePattern = new RegExp(
|
|
228
|
-
`//\\s*const\\s+${expectedVarName}\\s*=\\s*toError\\(${actualParamName}\\)`,
|
|
229
|
-
);
|
|
188
|
+
const ignorePattern = new RegExp(`//\\s*const\\s+${expectedVarName}\\s*=\\s*toError\\(${actualParamName}\\)`);
|
|
230
189
|
return ignorePattern.test(blockText);
|
|
231
190
|
}
|
|
232
|
-
|
|
233
191
|
/**
|
|
234
192
|
* Validate a single CatchClause node.
|
|
235
193
|
*/
|
|
236
194
|
// webpieces-disable max-lines-new-methods -- AST validation with multiple check paths for param name, type, and toError
|
|
237
|
-
function validateCatchClause(
|
|
238
|
-
|
|
239
|
-
sourceFile: ts.SourceFile,
|
|
240
|
-
fileLines: string[],
|
|
241
|
-
depth: number,
|
|
242
|
-
disableAllowed: boolean,
|
|
243
|
-
): CatchViolationInfo[] {
|
|
244
|
-
const violations: CatchViolationInfo[] = [];
|
|
195
|
+
function validateCatchClause(node, sourceFile, fileLines, depth, disableAllowed) {
|
|
196
|
+
const violations = [];
|
|
245
197
|
const suffix = depth === 1 ? '' : String(depth);
|
|
246
198
|
const expectedParam = 'err' + suffix;
|
|
247
199
|
const expectedVar = 'error' + suffix;
|
|
248
|
-
|
|
249
200
|
const catchLine = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
250
201
|
const catchContext = fileLines[catchLine - 1]?.trim() ?? '';
|
|
251
202
|
const blockStart = node.block.getStart(sourceFile);
|
|
252
203
|
const blockEnd = node.block.getEnd();
|
|
253
204
|
const disabled = hasDisableComment(fileLines, catchLine) ||
|
|
254
205
|
hasBlockLevelDisable(sourceFile.text, blockStart, blockEnd);
|
|
255
|
-
|
|
256
206
|
const varDecl = node.variableDeclaration;
|
|
257
207
|
if (!varDecl) {
|
|
258
208
|
violations.push({
|
|
@@ -262,9 +212,7 @@ function validateCatchClause(
|
|
|
262
212
|
});
|
|
263
213
|
return violations;
|
|
264
214
|
}
|
|
265
|
-
|
|
266
215
|
const actualParam = ts.isIdentifier(varDecl.name) ? varDecl.name.text : expectedParam;
|
|
267
|
-
|
|
268
216
|
// Check parameter name
|
|
269
217
|
if (ts.isIdentifier(varDecl.name) && varDecl.name.text !== expectedParam) {
|
|
270
218
|
violations.push({
|
|
@@ -273,7 +221,6 @@ function validateCatchClause(
|
|
|
273
221
|
hasDisableComment: resolveDisable(disabled, disableAllowed),
|
|
274
222
|
});
|
|
275
223
|
}
|
|
276
|
-
|
|
277
224
|
// Check type annotation is : unknown
|
|
278
225
|
if (!varDecl.type || varDecl.type.kind !== ts.SyntaxKind.UnknownKeyword) {
|
|
279
226
|
violations.push({
|
|
@@ -282,12 +229,10 @@ function validateCatchClause(
|
|
|
282
229
|
hasDisableComment: resolveDisable(disabled, disableAllowed),
|
|
283
230
|
});
|
|
284
231
|
}
|
|
285
|
-
|
|
286
232
|
// Check for commented-out ignore pattern
|
|
287
233
|
if (hasIgnoreComment(sourceFile.text, blockStart, blockEnd, expectedVar, actualParam)) {
|
|
288
234
|
return violations;
|
|
289
235
|
}
|
|
290
|
-
|
|
291
236
|
// Check first statement is const error = toError(err)
|
|
292
237
|
if (node.block.statements.length === 0) {
|
|
293
238
|
violations.push({
|
|
@@ -297,40 +242,26 @@ function validateCatchClause(
|
|
|
297
242
|
});
|
|
298
243
|
return violations;
|
|
299
244
|
}
|
|
300
|
-
|
|
301
245
|
const firstStmt = node.block.statements[0];
|
|
302
246
|
const toErrorViolation = validateToErrorStatement(firstStmt, sourceFile, fileLines, expectedParam, expectedVar, actualParam, disabled, disableAllowed);
|
|
303
247
|
if (toErrorViolation) {
|
|
304
248
|
violations.push(toErrorViolation);
|
|
305
249
|
}
|
|
306
|
-
|
|
307
250
|
return violations;
|
|
308
251
|
}
|
|
309
|
-
|
|
310
|
-
function resolveDisable(disabled: boolean, disableAllowed: boolean): boolean {
|
|
252
|
+
function resolveDisable(disabled, disableAllowed) {
|
|
311
253
|
if (!disableAllowed && disabled) {
|
|
312
254
|
return false;
|
|
313
255
|
}
|
|
314
256
|
return disabled;
|
|
315
257
|
}
|
|
316
|
-
|
|
317
258
|
/**
|
|
318
259
|
* Validate that the first statement is `const error = toError(err);`
|
|
319
260
|
*/
|
|
320
261
|
// webpieces-disable max-lines-new-methods -- Deep AST check for variable declaration with toError call expression
|
|
321
|
-
function validateToErrorStatement(
|
|
322
|
-
stmt: ts.Statement,
|
|
323
|
-
sourceFile: ts.SourceFile,
|
|
324
|
-
fileLines: string[],
|
|
325
|
-
expectedParam: string,
|
|
326
|
-
expectedVar: string,
|
|
327
|
-
actualParam: string,
|
|
328
|
-
disabled: boolean,
|
|
329
|
-
disableAllowed: boolean,
|
|
330
|
-
): CatchViolationInfo | null {
|
|
262
|
+
function validateToErrorStatement(stmt, sourceFile, fileLines, expectedParam, expectedVar, actualParam, disabled, disableAllowed) {
|
|
331
263
|
const stmtLine = sourceFile.getLineAndCharacterOfPosition(stmt.getStart(sourceFile)).line + 1;
|
|
332
264
|
const stmtContext = fileLines[stmtLine - 1]?.trim() ?? '';
|
|
333
|
-
|
|
334
265
|
if (!ts.isVariableStatement(stmt)) {
|
|
335
266
|
return {
|
|
336
267
|
line: stmtLine,
|
|
@@ -339,7 +270,6 @@ function validateToErrorStatement(
|
|
|
339
270
|
hasDisableComment: resolveDisable(disabled, disableAllowed),
|
|
340
271
|
};
|
|
341
272
|
}
|
|
342
|
-
|
|
343
273
|
const declarations = stmt.declarationList.declarations;
|
|
344
274
|
if (declarations.length === 0) {
|
|
345
275
|
return {
|
|
@@ -349,9 +279,7 @@ function validateToErrorStatement(
|
|
|
349
279
|
hasDisableComment: resolveDisable(disabled, disableAllowed),
|
|
350
280
|
};
|
|
351
281
|
}
|
|
352
|
-
|
|
353
282
|
const decl = declarations[0];
|
|
354
|
-
|
|
355
283
|
// Check variable name
|
|
356
284
|
if (ts.isIdentifier(decl.name) && decl.name.text !== expectedVar) {
|
|
357
285
|
return {
|
|
@@ -361,7 +289,6 @@ function validateToErrorStatement(
|
|
|
361
289
|
hasDisableComment: resolveDisable(disabled, disableAllowed),
|
|
362
290
|
};
|
|
363
291
|
}
|
|
364
|
-
|
|
365
292
|
// Check initializer is toError(actualParam)
|
|
366
293
|
const init = decl.initializer;
|
|
367
294
|
if (!init || !ts.isCallExpression(init)) {
|
|
@@ -372,7 +299,6 @@ function validateToErrorStatement(
|
|
|
372
299
|
hasDisableComment: resolveDisable(disabled, disableAllowed),
|
|
373
300
|
};
|
|
374
301
|
}
|
|
375
|
-
|
|
376
302
|
if (!ts.isIdentifier(init.expression) || init.expression.text !== 'toError') {
|
|
377
303
|
return {
|
|
378
304
|
line: stmtLine,
|
|
@@ -381,7 +307,6 @@ function validateToErrorStatement(
|
|
|
381
307
|
hasDisableComment: resolveDisable(disabled, disableAllowed),
|
|
382
308
|
};
|
|
383
309
|
}
|
|
384
|
-
|
|
385
310
|
// Check argument
|
|
386
311
|
const args = init.arguments;
|
|
387
312
|
if (args.length !== 1 || !ts.isIdentifier(args[0]) || args[0].text !== actualParam) {
|
|
@@ -392,29 +317,21 @@ function validateToErrorStatement(
|
|
|
392
317
|
hasDisableComment: resolveDisable(disabled, disableAllowed),
|
|
393
318
|
};
|
|
394
319
|
}
|
|
395
|
-
|
|
396
320
|
return null;
|
|
397
321
|
}
|
|
398
|
-
|
|
399
322
|
/**
|
|
400
323
|
* Find all catch pattern violations in a file using AST.
|
|
401
324
|
*/
|
|
402
|
-
function findCatchViolationsInFile(
|
|
403
|
-
filePath: string,
|
|
404
|
-
workspaceRoot: string,
|
|
405
|
-
disableAllowed: boolean,
|
|
406
|
-
): CatchViolationInfo[] {
|
|
325
|
+
function findCatchViolationsInFile(filePath, workspaceRoot, disableAllowed) {
|
|
407
326
|
const fullPath = path.join(workspaceRoot, filePath);
|
|
408
|
-
if (!fs.existsSync(fullPath))
|
|
409
|
-
|
|
327
|
+
if (!fs.existsSync(fullPath))
|
|
328
|
+
return [];
|
|
410
329
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
411
330
|
const fileLines = content.split('\n');
|
|
412
331
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
413
|
-
|
|
414
|
-
const violations: CatchViolationInfo[] = [];
|
|
332
|
+
const violations = [];
|
|
415
333
|
let catchDepth = 0;
|
|
416
|
-
|
|
417
|
-
function visit(node: ts.Node): void {
|
|
334
|
+
function visit(node) {
|
|
418
335
|
if (ts.isCatchClause(node)) {
|
|
419
336
|
catchDepth++;
|
|
420
337
|
const clauseViolations = validateCatchClause(node, sourceFile, fileLines, catchDepth, disableAllowed);
|
|
@@ -425,105 +342,85 @@ function findCatchViolationsInFile(
|
|
|
425
342
|
}
|
|
426
343
|
ts.forEachChild(node, visit);
|
|
427
344
|
}
|
|
428
|
-
|
|
429
345
|
visit(sourceFile);
|
|
430
346
|
return violations;
|
|
431
347
|
}
|
|
432
|
-
|
|
433
348
|
/**
|
|
434
349
|
* MODIFIED_CODE mode: Flag violations on changed lines.
|
|
435
350
|
*/
|
|
436
|
-
function findViolationsForModifiedCode(
|
|
437
|
-
|
|
438
|
-
changedFiles: string[],
|
|
439
|
-
base: string,
|
|
440
|
-
head: string | undefined,
|
|
441
|
-
disableAllowed: boolean,
|
|
442
|
-
): CatchViolation[] {
|
|
443
|
-
const violations: CatchViolation[] = [];
|
|
444
|
-
|
|
351
|
+
function findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed) {
|
|
352
|
+
const violations = [];
|
|
445
353
|
for (const file of changedFiles) {
|
|
446
354
|
const diff = getFileDiff(workspaceRoot, file, base, head);
|
|
447
355
|
const changedLines = getChangedLineNumbers(diff);
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
356
|
+
if (changedLines.size === 0)
|
|
357
|
+
continue;
|
|
451
358
|
const allViolations = findCatchViolationsInFile(file, workspaceRoot, disableAllowed);
|
|
452
|
-
|
|
453
359
|
for (const v of allViolations) {
|
|
454
|
-
if (disableAllowed && v.hasDisableComment)
|
|
455
|
-
|
|
456
|
-
|
|
360
|
+
if (disableAllowed && v.hasDisableComment)
|
|
361
|
+
continue;
|
|
362
|
+
if (!changedLines.has(v.line))
|
|
363
|
+
continue;
|
|
457
364
|
violations.push({ file, line: v.line, message: v.message, context: v.context });
|
|
458
365
|
}
|
|
459
366
|
}
|
|
460
|
-
|
|
461
367
|
return violations;
|
|
462
368
|
}
|
|
463
|
-
|
|
464
369
|
/**
|
|
465
370
|
* MODIFIED_FILES mode: Flag ALL violations in modified files.
|
|
466
371
|
*/
|
|
467
|
-
function findViolationsForModifiedFiles(
|
|
468
|
-
|
|
469
|
-
changedFiles: string[],
|
|
470
|
-
disableAllowed: boolean,
|
|
471
|
-
): CatchViolation[] {
|
|
472
|
-
const violations: CatchViolation[] = [];
|
|
473
|
-
|
|
372
|
+
function findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed) {
|
|
373
|
+
const violations = [];
|
|
474
374
|
for (const file of changedFiles) {
|
|
475
375
|
const allViolations = findCatchViolationsInFile(file, workspaceRoot, disableAllowed);
|
|
476
|
-
|
|
477
376
|
for (const v of allViolations) {
|
|
478
|
-
if (disableAllowed && v.hasDisableComment)
|
|
377
|
+
if (disableAllowed && v.hasDisableComment)
|
|
378
|
+
continue;
|
|
479
379
|
violations.push({ file, line: v.line, message: v.message, context: v.context });
|
|
480
380
|
}
|
|
481
381
|
}
|
|
482
|
-
|
|
483
382
|
return violations;
|
|
484
383
|
}
|
|
485
|
-
|
|
486
384
|
/**
|
|
487
385
|
* Auto-detect the base branch by finding the merge-base with origin/main.
|
|
488
386
|
*/
|
|
489
|
-
function detectBase(workspaceRoot
|
|
387
|
+
function detectBase(workspaceRoot) {
|
|
490
388
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
491
389
|
try {
|
|
492
|
-
const mergeBase = execSync('git merge-base HEAD origin/main', {
|
|
390
|
+
const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD origin/main', {
|
|
493
391
|
cwd: workspaceRoot,
|
|
494
392
|
encoding: 'utf-8',
|
|
495
393
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
496
394
|
}).trim();
|
|
497
|
-
|
|
498
395
|
if (mergeBase) {
|
|
499
396
|
return mergeBase;
|
|
500
397
|
}
|
|
501
|
-
|
|
502
|
-
}
|
|
398
|
+
// webpieces-disable catch-error-pattern -- intentional swallow of git command failure
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
503
401
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
504
402
|
try {
|
|
505
|
-
const mergeBase = execSync('git merge-base HEAD main', {
|
|
403
|
+
const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD main', {
|
|
506
404
|
cwd: workspaceRoot,
|
|
507
405
|
encoding: 'utf-8',
|
|
508
406
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
509
407
|
}).trim();
|
|
510
|
-
|
|
511
408
|
if (mergeBase) {
|
|
512
409
|
return mergeBase;
|
|
513
410
|
}
|
|
514
|
-
|
|
515
|
-
}
|
|
411
|
+
// webpieces-disable catch-error-pattern -- intentional swallow of git command failure
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
516
414
|
// Ignore
|
|
517
415
|
}
|
|
518
416
|
}
|
|
519
417
|
return null;
|
|
520
418
|
}
|
|
521
|
-
|
|
522
419
|
/**
|
|
523
420
|
* Report violations to console.
|
|
524
421
|
*/
|
|
525
422
|
// webpieces-disable max-lines-new-methods -- Console output with pattern examples and escape hatch
|
|
526
|
-
function reportViolations(violations
|
|
423
|
+
function reportViolations(violations, mode, disableAllowed) {
|
|
527
424
|
console.error('');
|
|
528
425
|
console.error('\u274c Catch blocks must follow the standardized error handling pattern!');
|
|
529
426
|
console.error('');
|
|
@@ -541,18 +438,17 @@ function reportViolations(violations: CatchViolation[], mode: CatchErrorPatternM
|
|
|
541
438
|
console.error('');
|
|
542
439
|
console.error(' For nested catches: err2/error2, err3/error3, etc.');
|
|
543
440
|
console.error('');
|
|
544
|
-
|
|
545
441
|
for (const v of violations) {
|
|
546
442
|
console.error(` \u274c ${v.file}:${v.line}`);
|
|
547
443
|
console.error(` ${v.message}`);
|
|
548
444
|
console.error(` ${v.context}`);
|
|
549
445
|
}
|
|
550
446
|
console.error('');
|
|
551
|
-
|
|
552
447
|
if (disableAllowed) {
|
|
553
448
|
console.error(' Escape hatch (use sparingly):');
|
|
554
449
|
console.error(' // webpieces-disable catch-error-pattern -- [your reason]');
|
|
555
|
-
}
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
556
452
|
console.error(' Escape hatch: DISABLED (disableAllowed: false)');
|
|
557
453
|
console.error(' Disable comments are ignored. Fix the catch block directly.');
|
|
558
454
|
}
|
|
@@ -560,11 +456,10 @@ function reportViolations(violations: CatchViolation[], mode: CatchErrorPatternM
|
|
|
560
456
|
console.error(` Current mode: ${mode}`);
|
|
561
457
|
console.error('');
|
|
562
458
|
}
|
|
563
|
-
|
|
564
459
|
/**
|
|
565
460
|
* Resolve mode considering ignoreModifiedUntilEpoch override.
|
|
566
461
|
*/
|
|
567
|
-
function resolveMode(normalMode
|
|
462
|
+
function resolveMode(normalMode, epoch) {
|
|
568
463
|
if (epoch === undefined || normalMode === 'OFF') {
|
|
569
464
|
return normalMode;
|
|
570
465
|
}
|
|
@@ -577,63 +472,47 @@ function resolveMode(normalMode: CatchErrorPatternMode, epoch: number | undefine
|
|
|
577
472
|
}
|
|
578
473
|
return normalMode;
|
|
579
474
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
options: ValidateCatchErrorPatternOptions,
|
|
583
|
-
workspaceRoot: string
|
|
584
|
-
): Promise<ExecutorResult> {
|
|
585
|
-
const mode: CatchErrorPatternMode = resolveMode(options.mode ?? 'OFF', options.ignoreModifiedUntilEpoch);
|
|
475
|
+
async function runValidator(options, workspaceRoot) {
|
|
476
|
+
const mode = resolveMode(options.mode ?? 'OFF', options.ignoreModifiedUntilEpoch);
|
|
586
477
|
const disableAllowed = options.disableAllowed ?? true;
|
|
587
|
-
|
|
588
478
|
if (mode === 'OFF') {
|
|
589
479
|
console.log('\n\u23ed\ufe0f Skipping catch-error-pattern validation (mode: OFF)');
|
|
590
480
|
console.log('');
|
|
591
481
|
return { success: true };
|
|
592
482
|
}
|
|
593
|
-
|
|
594
483
|
console.log('\n\ud83d\udccf Validating Catch Error Pattern\n');
|
|
595
484
|
console.log(` Mode: ${mode}`);
|
|
596
|
-
|
|
597
485
|
let base = process.env['NX_BASE'];
|
|
598
486
|
const head = process.env['NX_HEAD'];
|
|
599
|
-
|
|
600
487
|
if (!base) {
|
|
601
488
|
base = detectBase(workspaceRoot) ?? undefined;
|
|
602
|
-
|
|
603
489
|
if (!base) {
|
|
604
490
|
console.log('\n\u23ed\ufe0f Skipping catch-error-pattern validation (could not detect base branch)');
|
|
605
491
|
console.log('');
|
|
606
492
|
return { success: true };
|
|
607
493
|
}
|
|
608
494
|
}
|
|
609
|
-
|
|
610
495
|
console.log(` Base: ${base}`);
|
|
611
496
|
console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);
|
|
612
497
|
console.log('');
|
|
613
|
-
|
|
614
498
|
const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base, head);
|
|
615
|
-
|
|
616
499
|
if (changedFiles.length === 0) {
|
|
617
500
|
console.log('\u2705 No TypeScript files changed');
|
|
618
501
|
return { success: true };
|
|
619
502
|
}
|
|
620
|
-
|
|
621
503
|
console.log(`\ud83d\udcc2 Checking ${changedFiles.length} changed file(s)...`);
|
|
622
|
-
|
|
623
|
-
let violations: CatchViolation[] = [];
|
|
624
|
-
|
|
504
|
+
let violations = [];
|
|
625
505
|
if (mode === 'MODIFIED_CODE') {
|
|
626
506
|
violations = findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed);
|
|
627
|
-
}
|
|
507
|
+
}
|
|
508
|
+
else if (mode === 'MODIFIED_FILES') {
|
|
628
509
|
violations = findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed);
|
|
629
510
|
}
|
|
630
|
-
|
|
631
511
|
if (violations.length === 0) {
|
|
632
512
|
console.log('\u2705 No catch error pattern violations found');
|
|
633
513
|
return { success: true };
|
|
634
514
|
}
|
|
635
|
-
|
|
636
515
|
reportViolations(violations, mode, disableAllowed);
|
|
637
|
-
|
|
638
516
|
return { success: false };
|
|
639
517
|
}
|
|
518
|
+
//# sourceMappingURL=validate-catch-error-pattern.js.map
|