@serejke/eslint-plugin-no-new-error
v1.2.0
Published
ESLint rule to enforce BaseError usage with static messages
Maintainers
Readme
@serejke/eslint-plugin-no-new-error
ESLint plugin that enforces the use of BaseError subclasses with static messages instead of the native Error constructor.
Why?
This plugin enforces a strict error handling pattern that ensures:
- JSON Serializability: All errors can be serialized/deserialized (critical for Temporal.io workflows)
- Consistent Error Messages: Static messages are greppable and predictable
- Rich Context: Dynamic data is stored in typed context objects
- Type Safety: Error context is strongly typed
Installation
npm install --save-dev @serejke/eslint-plugin-no-new-error
# or
pnpm add -D @serejke/eslint-plugin-no-new-error
# or
yarn add -D @serejke/eslint-plugin-no-new-errorUsage
Add the plugin to your eslint.config.mjs (ESLint 9+ flat config):
import noNewError from '@serejke/eslint-plugin-no-new-error';
export default [
{
files: ['src/**/*.ts'],
plugins: {
'no-new-error': noNewError,
},
rules: {
'no-new-error/no-new-error': 'error',
},
},
];Rule Details
❌ Incorrect
// Using new Error()
throw new Error('Something went wrong');
throw new Error(`User ${id} not found`);
throw new Error('Failed: ' + reason);
// Dynamic messages in BaseError
throw new BaseError(`User ${userId} not found`);
throw new BaseError('Token: ' + tokenAddress);
const msg = 'Dynamic message';
throw new BaseError(msg);✅ Correct
// Define error with static message and typed context
export type UserNotFoundErrorContext = { userId: string };
export class UserNotFoundError extends BaseError<UserNotFoundErrorContext> {
constructor(context: UserNotFoundErrorContext, cause?: Error) {
super('User not found', { context, cause });
}
}
// Usage
throw new UserNotFoundError({ userId: '123' });// Error without context
export class NetworkError extends BaseError {
constructor(cause?: Error) {
super('Network request failed', { cause });
}
}
throw new NetworkError();Configuration
The rule accepts an options object:
{
baseErrorImportPath?: string; // Default: '@serejke/ts-ninja-error'
allowInTests?: boolean; // Default: true
testFilePatterns?: string[]; // Default: ['**/*.test.ts', '**/*.spec.ts', '**/__tests__/**/*.ts']
allowedErrorClasses?: string[]; // Default: []
}Example with options
export default [
{
files: ['src/**/*.ts'],
plugins: {
'no-new-error': noNewError,
},
rules: {
'no-new-error/no-new-error': [
'error',
{
baseErrorImportPath: '@/lib/errors/base-error',
allowInTests: true,
testFilePatterns: ['tests/**/*.ts', '**/*.test.ts'],
allowedErrorClasses: ['CustomAllowedError'],
},
],
},
},
];Allow in test files
By default, new Error() is allowed in test files:
// ✅ Allowed in test files
test('handles errors', () => {
const mockError = new Error('Test error');
expect(() => fn(mockError)).toThrow();
});To disable this behavior:
rules: {
'no-new-error/no-new-error': ['error', { allowInTests: false }]
}Pattern Explained
BaseError Class
The pattern assumes you have a BaseError class similar to:
import { JSONObject } from '@serejke/ts-ninja-plain-object';
export class BaseError<Context extends JSONObject = JSONObject> extends Error {
public readonly context?: Context;
public readonly originalCause: Error | undefined;
constructor(message: string, options: { cause?: Error; context?: Context } = {}) {
super(message);
this.originalCause = options.cause;
this.context = options.context;
this.name = this.constructor.name;
}
}Creating Custom Errors
- Define a type for your error context:
export type TransactionFailedErrorContext = {
transactionId: string;
reason: string;
amount?: number;
};- Create an error class that extends BaseError:
export class TransactionFailedError extends BaseError<TransactionFailedErrorContext> {
constructor(context: TransactionFailedErrorContext, cause?: Error) {
super('Transaction failed', { context, cause });
}
}- Use it with rich, typed context:
throw new TransactionFailedError({
transactionId: 'tx_123',
reason: 'Insufficient funds',
amount: 100,
});Benefits
- Static Messages: Easy to search and track
- Typed Context: IDE autocomplete and type checking
- Serializable: Works with Temporal.io and other serialization requirements
- Cause Chain: Preserve original errors
- Structured Data: JSON-compatible context
License
MIT
Author
Sergei Patrikeev [email protected]
