@webpieces/dev-config 0.2.32 → 0.2.33
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.
|
@@ -100,6 +100,9 @@ function ensureDocFile(docPath: string, content: string): boolean {
|
|
|
100
100
|
/**
|
|
101
101
|
* Ensures the exception documentation markdown file exists
|
|
102
102
|
* Only creates file once per lint run using module-level flag
|
|
103
|
+
*
|
|
104
|
+
* Reads from the template file packaged with @webpieces/dev-config
|
|
105
|
+
* and copies it to tmp/webpieces/ for AI agents to read.
|
|
103
106
|
*/
|
|
104
107
|
function ensureExceptionDoc(context: Rule.RuleContext): void {
|
|
105
108
|
if (exceptionDocCreated) return;
|
|
@@ -107,479 +110,28 @@ function ensureExceptionDoc(context: Rule.RuleContext): void {
|
|
|
107
110
|
const workspaceRoot = getWorkspaceRoot(context);
|
|
108
111
|
const docPath = path.join(workspaceRoot, 'tmp', 'webpieces', 'webpieces.exceptions.md');
|
|
109
112
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Module-level flag to prevent redundant markdown file creation
|
|
116
|
-
let exceptionDocCreated = false;
|
|
117
|
-
|
|
118
|
-
// Comprehensive markdown documentation content
|
|
119
|
-
const EXCEPTION_DOC_CONTENT = `# AI Agent Instructions: Try-Catch Blocks Detected
|
|
120
|
-
|
|
121
|
-
**READ THIS FILE to understand why try-catch blocks are restricted and how to fix violations**
|
|
122
|
-
|
|
123
|
-
## Core Principle
|
|
124
|
-
|
|
125
|
-
**EXCEPTIONS MUST BUBBLE TO GLOBAL HANDLER WITH TRACEID FOR DEBUGGABILITY.**
|
|
126
|
-
|
|
127
|
-
The webpieces framework uses a global error handling architecture where:
|
|
128
|
-
- Every request gets a unique traceId stored in RequestContext
|
|
129
|
-
- All errors bubble to the global handler (WebpiecesMiddleware.globalErrorHandler)
|
|
130
|
-
- Error IDs enable lookup via \`/debugLocal/{id}\` and \`/debugCloud/{id}\` endpoints
|
|
131
|
-
- Local try-catch blocks break this pattern by losing error IDs and context
|
|
132
|
-
|
|
133
|
-
This is not a performance concern - it's an architecture decision for distributed tracing and debugging in production.
|
|
134
|
-
|
|
135
|
-
## Why This Rule Exists
|
|
136
|
-
|
|
137
|
-
### Problem 1: AI Over-Adds Try-Catch (Especially Frontend)
|
|
138
|
-
AI agents tend to add defensive try-catch blocks everywhere, which:
|
|
139
|
-
- Swallows errors and loses traceId
|
|
140
|
-
- Shows custom error messages without debugging context
|
|
141
|
-
- Makes production issues impossible to trace
|
|
142
|
-
- Creates "blind spots" where errors disappear
|
|
143
|
-
|
|
144
|
-
### Problem 2: Lost TraceId = Lost Debugging Capability
|
|
145
|
-
Without traceId in errors:
|
|
146
|
-
- \`/debugLocal/{id}\` endpoint cannot retrieve error details
|
|
147
|
-
- \`/debugCloud/{id}\` endpoint cannot correlate logs
|
|
148
|
-
- DevOps cannot trace request flow through distributed systems
|
|
149
|
-
- Users report "an error occurred" with no way to investigate
|
|
150
|
-
|
|
151
|
-
### Problem 3: Try-Catch-Rethrow Is Code Smell
|
|
152
|
-
\`\`\`typescript
|
|
153
|
-
// BAD: Why catch if you're just rethrowing?
|
|
154
|
-
try {
|
|
155
|
-
await operation();
|
|
156
|
-
} catch (err: any) {
|
|
157
|
-
const error = toError(err);
|
|
158
|
-
console.error('Failed:', error);
|
|
159
|
-
throw error; // Why catch at all???
|
|
160
|
-
}
|
|
161
|
-
\`\`\`
|
|
162
|
-
99% of the time, there's a better pattern (logging filter, global handler, etc.).
|
|
163
|
-
|
|
164
|
-
### Problem 4: Swallowing Exceptions = Lazy Programming
|
|
165
|
-
\`\`\`typescript
|
|
166
|
-
// BAD: "I don't want to deal with this error"
|
|
167
|
-
try {
|
|
168
|
-
await riskyOperation();
|
|
169
|
-
} catch (err: any) {
|
|
170
|
-
// Silence...
|
|
171
|
-
}
|
|
172
|
-
\`\`\`
|
|
173
|
-
This is the #1 shortcut developers take that creates production nightmares.
|
|
174
|
-
|
|
175
|
-
## Industry Best Practices (2025)
|
|
176
|
-
|
|
177
|
-
### Distributed Tracing: The Three Pillars
|
|
178
|
-
Modern observability requires correlation across:
|
|
179
|
-
1. **Traces** - Request flow through services
|
|
180
|
-
2. **Logs** - Contextual debugging information
|
|
181
|
-
3. **Metrics** - Aggregated system health
|
|
182
|
-
|
|
183
|
-
TraceId (also called correlation ID, request ID) ties these together.
|
|
184
|
-
|
|
185
|
-
### Research Findings
|
|
186
|
-
- **Performance**: Try-catch is an expensive operation in V8 engine (source: Node.js performance docs)
|
|
187
|
-
- **Error Handling**: Global handlers at highest level reduce blind spots by 40% (source: Google SRE practices)
|
|
188
|
-
- **Middleware Pattern**: Express/Koa middleware with async error boundaries is industry standard (source: Express.js error handling docs)
|
|
189
|
-
- **Only Catch What You Can Handle**: If you can't recover, let it bubble (source: "Effective Error Handling" - JavaScript design patterns)
|
|
190
|
-
|
|
191
|
-
### 2025 Trends
|
|
192
|
-
- Correlation IDs are standard in microservices (OpenTelemetry, Datadog, New Relic)
|
|
193
|
-
- Structured logging with context (Winston, Pino)
|
|
194
|
-
- Middleware-based error boundaries reduce boilerplate
|
|
195
|
-
- Frontend: React Error Boundaries, not scattered try-catch
|
|
196
|
-
|
|
197
|
-
## Command: Remove Try-Catch and Use Global Handler
|
|
198
|
-
|
|
199
|
-
## AI Agent Action Steps
|
|
200
|
-
|
|
201
|
-
1. **IDENTIFY** the try-catch block flagged in the error message
|
|
202
|
-
|
|
203
|
-
2. **ANALYZE** the purpose:
|
|
204
|
-
- Is it catching errors just to log them? → Remove (use LogApiFilter)
|
|
205
|
-
- Is it catching to show custom message? → Remove (use global handler)
|
|
206
|
-
- Is it catching to retry? → Requires approval (see Acceptable Patterns)
|
|
207
|
-
- Is it catching in a batch loop? → Requires approval (see Acceptable Patterns)
|
|
208
|
-
- Is it catching for cleanup? → Usually wrong pattern
|
|
209
|
-
|
|
210
|
-
3. **REMOVE** the try-catch block:
|
|
211
|
-
- Delete the \`try {\` and \`} catch (err: any) { ... }\` wrapper
|
|
212
|
-
- Let the code execute normally
|
|
213
|
-
- Errors will bubble to global handler automatically
|
|
214
|
-
|
|
215
|
-
4. **VERIFY** global handler exists:
|
|
216
|
-
- Check that WebpiecesMiddleware.globalErrorHandler is registered
|
|
217
|
-
- Check that ContextFilter is setting up RequestContext
|
|
218
|
-
- Check that traceId is being added to RequestContext
|
|
219
|
-
|
|
220
|
-
5. **ADD** traceId to RequestContext (if not already present):
|
|
221
|
-
- In ContextFilter or similar high-priority filter
|
|
222
|
-
- Use \`RequestContext.put('TRACE_ID', generateTraceId())\`
|
|
223
|
-
|
|
224
|
-
6. **TEST** error flow:
|
|
225
|
-
- Trigger an error in the code
|
|
226
|
-
- Verify error is logged with traceId
|
|
227
|
-
- Verify \`/debugLocal/{traceId}\` endpoint works
|
|
228
|
-
|
|
229
|
-
## Pattern 1: Global Error Handler (GOOD)
|
|
230
|
-
|
|
231
|
-
### Server-Side: WebpiecesMiddleware
|
|
232
|
-
|
|
233
|
-
\`\`\`typescript
|
|
234
|
-
// packages/http/http-server/src/WebpiecesMiddleware.ts
|
|
235
|
-
@provideSingleton()
|
|
236
|
-
@injectable()
|
|
237
|
-
export class WebpiecesMiddleware {
|
|
238
|
-
async globalErrorHandler(
|
|
239
|
-
req: Request,
|
|
240
|
-
res: Response,
|
|
241
|
-
next: NextFunction
|
|
242
|
-
): Promise<void> {
|
|
243
|
-
console.log('[GlobalErrorHandler] Request START:', req.method, req.path);
|
|
244
|
-
|
|
245
|
-
try {
|
|
246
|
-
// Await catches BOTH sync throws AND rejected promises
|
|
247
|
-
await next();
|
|
248
|
-
console.log('[GlobalErrorHandler] Request END (success)');
|
|
249
|
-
} catch (err: any) {
|
|
250
|
-
const error = toError(err);
|
|
251
|
-
const traceId = RequestContext.get<string>('TRACE_ID');
|
|
252
|
-
|
|
253
|
-
// Log with traceId for /debugLocal lookup
|
|
254
|
-
console.error('[GlobalErrorHandler] ERROR:', {
|
|
255
|
-
traceId,
|
|
256
|
-
message: error.message,
|
|
257
|
-
stack: error.stack,
|
|
258
|
-
path: req.path,
|
|
259
|
-
method: req.method,
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
// Store error for /debugLocal/{id} endpoint
|
|
263
|
-
ErrorStore.save(traceId, error);
|
|
264
|
-
|
|
265
|
-
if (!res.headersSent) {
|
|
266
|
-
res.status(500).send(\`
|
|
267
|
-
<!DOCTYPE html>
|
|
268
|
-
<html>
|
|
269
|
-
<head><title>Server Error</title></head>
|
|
270
|
-
<body>
|
|
271
|
-
<h1>Server Error</h1>
|
|
272
|
-
<p>An error occurred. Reference ID: \${traceId}</p>
|
|
273
|
-
<p>Contact support with this ID to investigate.</p>
|
|
274
|
-
</body>
|
|
275
|
-
</html>
|
|
276
|
-
\`);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
\`\`\`
|
|
282
|
-
|
|
283
|
-
### Adding TraceId: ContextFilter
|
|
284
|
-
|
|
285
|
-
\`\`\`typescript
|
|
286
|
-
// packages/http/http-server/src/filters/ContextFilter.ts
|
|
287
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
288
|
-
|
|
289
|
-
@provideSingleton()
|
|
290
|
-
@injectable()
|
|
291
|
-
export class ContextFilter extends Filter<MethodMeta, WpResponse<unknown>> {
|
|
292
|
-
async filter(
|
|
293
|
-
meta: MethodMeta,
|
|
294
|
-
nextFilter: Service<MethodMeta, WpResponse<unknown>>
|
|
295
|
-
): Promise<WpResponse<unknown>> {
|
|
296
|
-
return RequestContext.run(async () => {
|
|
297
|
-
// Generate unique traceId for this request
|
|
298
|
-
const traceId = uuidv4();
|
|
299
|
-
RequestContext.put('TRACE_ID', traceId);
|
|
300
|
-
RequestContext.put('METHOD_META', meta);
|
|
301
|
-
RequestContext.put('REQUEST_PATH', meta.path);
|
|
302
|
-
|
|
303
|
-
return await nextFilter.invoke(meta);
|
|
304
|
-
// RequestContext auto-cleared when done
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
\`\`\`
|
|
309
|
-
|
|
310
|
-
## Pattern 2: Debug Endpoints (GOOD)
|
|
311
|
-
|
|
312
|
-
\`\`\`typescript
|
|
313
|
-
// Example debug endpoint for local development
|
|
314
|
-
@provideSingleton()
|
|
315
|
-
@Controller()
|
|
316
|
-
export class DebugController implements DebugApi {
|
|
317
|
-
@Get()
|
|
318
|
-
@Path('/debugLocal/:id')
|
|
319
|
-
async getErrorById(@PathParam('id') id: string): Promise<DebugErrorResponse> {
|
|
320
|
-
const error = ErrorStore.get(id);
|
|
321
|
-
if (!error) {
|
|
322
|
-
throw new HttpNotFoundError(\`Error \${id} not found\`);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
return {
|
|
326
|
-
traceId: id,
|
|
327
|
-
message: error.message,
|
|
328
|
-
stack: error.stack,
|
|
329
|
-
timestamp: error.timestamp,
|
|
330
|
-
requestPath: error.requestPath,
|
|
331
|
-
requestMethod: error.requestMethod,
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// ErrorStore singleton (in-memory for local, Redis for production)
|
|
337
|
-
class ErrorStoreImpl {
|
|
338
|
-
private errors = new Map<string, ErrorRecord>();
|
|
339
|
-
|
|
340
|
-
save(traceId: string, error: Error): void {
|
|
341
|
-
this.errors.set(traceId, {
|
|
342
|
-
traceId,
|
|
343
|
-
message: error.message,
|
|
344
|
-
stack: error.stack,
|
|
345
|
-
timestamp: new Date(),
|
|
346
|
-
requestPath: RequestContext.get('REQUEST_PATH'),
|
|
347
|
-
requestMethod: RequestContext.get('HTTP_METHOD'),
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
get(traceId: string): ErrorRecord | undefined {
|
|
352
|
-
return this.errors.get(traceId);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
export const ErrorStore = new ErrorStoreImpl();
|
|
357
|
-
\`\`\`
|
|
358
|
-
|
|
359
|
-
## Examples
|
|
360
|
-
|
|
361
|
-
### BAD Example 1: Local Try-Catch That Swallows Error
|
|
362
|
-
|
|
363
|
-
\`\`\`typescript
|
|
364
|
-
// BAD: Error is swallowed, no traceId in logs
|
|
365
|
-
async function processOrder(order: Order): Promise<void> {
|
|
366
|
-
try {
|
|
367
|
-
await validateOrder(order);
|
|
368
|
-
await saveToDatabase(order);
|
|
369
|
-
} catch (err: any) {
|
|
370
|
-
// Error disappears into void - debugging nightmare!
|
|
371
|
-
console.log('Order processing failed');
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
\`\`\`
|
|
375
|
-
|
|
376
|
-
**Problem**: When this fails in production, you have:
|
|
377
|
-
- No traceId to look up the error
|
|
378
|
-
- No stack trace
|
|
379
|
-
- No request context
|
|
380
|
-
- No way to investigate
|
|
381
|
-
|
|
382
|
-
### BAD Example 2: Try-Catch With Custom Error (No TraceId)
|
|
383
|
-
|
|
384
|
-
\`\`\`typescript
|
|
385
|
-
// BAD: Shows custom message but loses traceId
|
|
386
|
-
async function fetchUserData(userId: string): Promise<User> {
|
|
387
|
-
try {
|
|
388
|
-
const response = await fetch(\`/api/users/\${userId}\`);
|
|
389
|
-
return await response.json();
|
|
390
|
-
} catch (err: any) {
|
|
391
|
-
const error = toError(err);
|
|
392
|
-
// Custom message without traceId
|
|
393
|
-
throw new Error(\`Failed to fetch user \${userId}: \${error.message}\`);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
\`\`\`
|
|
397
|
-
|
|
398
|
-
**Problem**:
|
|
399
|
-
- Original error context is lost
|
|
400
|
-
- No traceId attached to new error
|
|
401
|
-
- Global handler receives generic error, can't trace root cause
|
|
113
|
+
// Read from the template file packaged with the npm module
|
|
114
|
+
// Path: from eslint-plugin/rules/ -> ../../templates/
|
|
115
|
+
const templatePath = path.join(__dirname, '..', '..', 'templates', 'webpieces.exceptions.md');
|
|
402
116
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
\`\`\`typescript
|
|
406
|
-
// GOOD: Error bubbles to global handler with traceId
|
|
407
|
-
async function processOrder(order: Order): Promise<void> {
|
|
408
|
-
// No try-catch needed!
|
|
409
|
-
await validateOrder(order);
|
|
410
|
-
await saveToDatabase(order);
|
|
411
|
-
// If error occurs, it bubbles with traceId intact
|
|
412
|
-
}
|
|
413
|
-
\`\`\`
|
|
414
|
-
|
|
415
|
-
**Why GOOD**:
|
|
416
|
-
- Global handler catches error
|
|
417
|
-
- TraceId from RequestContext is preserved
|
|
418
|
-
- Full stack trace available
|
|
419
|
-
- \`/debugLocal/{traceId}\` endpoint works
|
|
420
|
-
|
|
421
|
-
### GOOD Example 2: Global Handler Logs With TraceId
|
|
422
|
-
|
|
423
|
-
\`\`\`typescript
|
|
424
|
-
// GOOD: Global handler has full context
|
|
425
|
-
// In WebpiecesMiddleware.globalErrorHandler (see Pattern 1 above)
|
|
426
|
-
catch (err: any) {
|
|
427
|
-
const error = toError(err);
|
|
428
|
-
const traceId = RequestContext.get<string>('TRACE_ID');
|
|
429
|
-
|
|
430
|
-
console.error('[GlobalErrorHandler] ERROR:', {
|
|
431
|
-
traceId, // Unique ID for this request
|
|
432
|
-
message: error.message,
|
|
433
|
-
stack: error.stack,
|
|
434
|
-
path: req.path, // Request context preserved
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
\`\`\`
|
|
438
|
-
|
|
439
|
-
**Why GOOD**:
|
|
440
|
-
- TraceId logged with every error
|
|
441
|
-
- Full request context available
|
|
442
|
-
- Error stored for \`/debugLocal/{id}\` lookup
|
|
443
|
-
- DevOps can trace distributed requests
|
|
444
|
-
|
|
445
|
-
### ACCEPTABLE Example 1: Retry Loop (With eslint-disable)
|
|
446
|
-
|
|
447
|
-
\`\`\`typescript
|
|
448
|
-
// ACCEPTABLE: Retry pattern requires try-catch
|
|
449
|
-
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- Retry loop with exponential backoff
|
|
450
|
-
async function callVendorApiWithRetry(request: VendorRequest): Promise<VendorResponse> {
|
|
451
|
-
const maxRetries = 3;
|
|
452
|
-
let lastError: Error | undefined;
|
|
453
|
-
|
|
454
|
-
for (let i = 0; i < maxRetries; i++) {
|
|
117
|
+
let content: string;
|
|
455
118
|
try {
|
|
456
|
-
|
|
457
|
-
} catch
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
console.warn(\`Retry \${i + 1}/\${maxRetries} failed:\`, error.message);
|
|
461
|
-
await sleep(1000 * Math.pow(2, i)); // Exponential backoff
|
|
119
|
+
content = fs.readFileSync(templatePath, 'utf-8');
|
|
120
|
+
} catch {
|
|
121
|
+
// Fallback message if template not found (shouldn't happen in published package)
|
|
122
|
+
content = `# Exception Documentation Not Found\n\nTemplate file not found at: ${templatePath}\n\nPlease ensure @webpieces/dev-config is properly installed.`;
|
|
462
123
|
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// After retries exhausted, throw with traceId
|
|
466
|
-
const traceId = RequestContext.get<string>('TRACE_ID');
|
|
467
|
-
throw new HttpVendorError(
|
|
468
|
-
\`Vendor API failed after \${maxRetries} retries. TraceId: \${traceId}\`,
|
|
469
|
-
lastError
|
|
470
|
-
);
|
|
471
|
-
}
|
|
472
|
-
\`\`\`
|
|
473
124
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
- Final error still includes traceId
|
|
477
|
-
- Error still bubbles to global handler
|
|
478
|
-
- Requires senior developer approval (enforced by PR review)
|
|
479
|
-
|
|
480
|
-
### ACCEPTABLE Example 2: Batching Pattern (With eslint-disable)
|
|
481
|
-
|
|
482
|
-
\`\`\`typescript
|
|
483
|
-
// ACCEPTABLE: Batching requires try-catch to continue processing
|
|
484
|
-
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- Batch processing continues on individual failures
|
|
485
|
-
async function processBatch(items: Item[]): Promise<BatchResult> {
|
|
486
|
-
const results: ItemResult[] = [];
|
|
487
|
-
const errors: ItemError[] = [];
|
|
488
|
-
const traceId = RequestContext.get<string>('TRACE_ID');
|
|
489
|
-
|
|
490
|
-
for (const item of items) {
|
|
491
|
-
try {
|
|
492
|
-
const result = await processItem(item);
|
|
493
|
-
results.push(result);
|
|
494
|
-
} catch (err: any) {
|
|
495
|
-
const error = toError(err);
|
|
496
|
-
// Log individual error with traceId
|
|
497
|
-
console.error(\`[Batch] Item \${item.id} failed (traceId: \${traceId}):\`, error);
|
|
498
|
-
errors.push({ itemId: item.id, error: error.message, traceId });
|
|
125
|
+
if (ensureDocFile(docPath, content)) {
|
|
126
|
+
exceptionDocCreated = true;
|
|
499
127
|
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
// Return both successes and failures
|
|
503
|
-
return {
|
|
504
|
-
traceId,
|
|
505
|
-
successCount: results.length,
|
|
506
|
-
failureCount: errors.length,
|
|
507
|
-
results,
|
|
508
|
-
errors,
|
|
509
|
-
};
|
|
510
128
|
}
|
|
511
|
-
\`\`\`
|
|
512
|
-
|
|
513
|
-
**Why ACCEPTABLE**:
|
|
514
|
-
- Legitimate use case: partial failure handling
|
|
515
|
-
- Each error logged with traceId
|
|
516
|
-
- Batch traceId included in response
|
|
517
|
-
- Requires senior developer approval (enforced by PR review)
|
|
518
|
-
|
|
519
|
-
### UNACCEPTABLE Example: Try-Catch-Rethrow
|
|
520
|
-
|
|
521
|
-
\`\`\`typescript
|
|
522
|
-
// UNACCEPTABLE: Pointless try-catch that just rethrows
|
|
523
|
-
async function saveUser(user: User): Promise<void> {
|
|
524
|
-
try {
|
|
525
|
-
await database.save(user);
|
|
526
|
-
} catch (err: any) {
|
|
527
|
-
const error = toError(err);
|
|
528
|
-
console.error('Save failed:', error);
|
|
529
|
-
throw error; // Why catch at all???
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
\`\`\`
|
|
533
|
-
|
|
534
|
-
**Why UNACCEPTABLE**:
|
|
535
|
-
- Adds no value - logging should be in LogApiFilter
|
|
536
|
-
- Global handler already logs errors
|
|
537
|
-
- Just adds noise and confusion
|
|
538
|
-
- Remove the try-catch entirely!
|
|
539
|
-
|
|
540
|
-
## When eslint-disable IS Acceptable
|
|
541
|
-
|
|
542
|
-
You may use \`// eslint-disable-next-line @webpieces/no-unmanaged-exceptions\` ONLY for:
|
|
543
129
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
3. **Resource cleanup** with explicit approval
|
|
547
|
-
|
|
548
|
-
All three require:
|
|
549
|
-
- Senior developer approval in PR review
|
|
550
|
-
- Comment explaining WHY try-catch is needed
|
|
551
|
-
- TraceId must still be logged/included in final error
|
|
552
|
-
|
|
553
|
-
## How to Request Approval
|
|
554
|
-
|
|
555
|
-
If you believe you have a legitimate use case for try-catch:
|
|
556
|
-
|
|
557
|
-
1. **Add a comment** explaining why it's needed:
|
|
558
|
-
\`\`\`typescript
|
|
559
|
-
// JUSTIFICATION: Vendor API requires retry loop with exponential backoff
|
|
560
|
-
// to handle rate limiting. Final error includes traceId for debugging.
|
|
561
|
-
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
562
|
-
\`\`\`
|
|
563
|
-
|
|
564
|
-
2. **Ensure traceId is preserved** in final error or logged
|
|
565
|
-
|
|
566
|
-
3. **Request PR review** from senior developer
|
|
567
|
-
|
|
568
|
-
4. **Be prepared to justify** - 99% of try-catch can be removed
|
|
569
|
-
|
|
570
|
-
## Summary
|
|
571
|
-
|
|
572
|
-
**The webpieces philosophy**: Errors should bubble to the global handler where they are logged with traceId and stored for debugging. Local try-catch blocks break this architecture and create blind spots in production.
|
|
573
|
-
|
|
574
|
-
**Key takeaways**:
|
|
575
|
-
- Global error handler with traceId = debuggable production issues
|
|
576
|
-
- Local try-catch = lost context and debugging nightmares
|
|
577
|
-
- 99% of try-catch blocks can be removed safely
|
|
578
|
-
- Only use try-catch for: retries, batching (with approval)
|
|
579
|
-
- TraceId enables \`/debugLocal/{id}\` and \`/debugCloud/{id}\` endpoints
|
|
130
|
+
// Module-level flag to prevent redundant markdown file creation
|
|
131
|
+
let exceptionDocCreated = false;
|
|
580
132
|
|
|
581
|
-
|
|
582
|
-
|
|
133
|
+
// NOTE: Documentation content moved to templates/webpieces.exceptions.md
|
|
134
|
+
// The ensureExceptionDoc function reads from that file at runtime.
|
|
583
135
|
|
|
584
136
|
const rule: Rule.RuleModule = {
|
|
585
137
|
meta: {
|
|
@@ -600,9 +152,9 @@ const rule: Rule.RuleModule = {
|
|
|
600
152
|
|
|
601
153
|
create(context: Rule.RuleContext): Rule.RuleListener {
|
|
602
154
|
return {
|
|
603
|
-
TryStatement(node:
|
|
155
|
+
TryStatement(node: unknown): void {
|
|
604
156
|
// Skip try..finally blocks (no catch handler, no exception handling)
|
|
605
|
-
if (!node.handler) {
|
|
157
|
+
if (!(node as { handler?: unknown }).handler) {
|
|
606
158
|
return;
|
|
607
159
|
}
|
|
608
160
|
|
|
@@ -615,7 +167,7 @@ const rule: Rule.RuleModule = {
|
|
|
615
167
|
// Has catch block outside test file - report violation
|
|
616
168
|
ensureExceptionDoc(context);
|
|
617
169
|
context.report({
|
|
618
|
-
node,
|
|
170
|
+
node: node as Rule.Node,
|
|
619
171
|
messageId: 'noUnmanagedExceptions',
|
|
620
172
|
});
|
|
621
173
|
},
|
|
@@ -624,3 +176,4 @@ const rule: Rule.RuleModule = {
|
|
|
624
176
|
};
|
|
625
177
|
|
|
626
178
|
export = rule;
|
|
179
|
+
|