Skip to content

Commit d82b2bf

Browse files
committed
feat: createNodeError
Signed-off-by: Lexus Drumgold <[email protected]>
1 parent de221cd commit d82b2bf

File tree

5 files changed

+156
-1
lines changed

5 files changed

+156
-1
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* @file Unit Tests - createNodeError
3+
* @module create-node-error/tests/unit/createNodeError
4+
*/
5+
6+
import { ErrorCode } from '#src/enums'
7+
import type { NodeError } from '#src/types'
8+
import { format } from 'node-inspect-extracted'
9+
import { resolve } from 'node:path'
10+
import testSubject from '../create-node-error'
11+
12+
describe('unit:createNodeError', () => {
13+
it('should return NodeErrorConstructor', () => {
14+
expect(testSubject(ErrorCode.ERR_INVALID_ARG_TYPE, TypeError, vi.fn()))
15+
.to.be.a('function')
16+
.with.property('name')
17+
.that.equals('NodeError')
18+
})
19+
20+
describe('NodeError', () => {
21+
it('should return NodeError instance', () => {
22+
// Arrange
23+
const code: ErrorCode = ErrorCode.ERR_UNSUPPORTED_DIR_IMPORT
24+
const s1: string = resolve('src/interfaces')
25+
const s2: string = resolve('src/index.ts')
26+
const message: string = `Directory import "%s" is not supported resolving ES modules imported from %s`
27+
28+
// Act
29+
const ERR_UNSUPPORTED_DIR_IMPORT = testSubject(code, Error, message)
30+
const err: NodeError = new ERR_UNSUPPORTED_DIR_IMPORT(s1, s2)
31+
const errstr: string = err.toString()
32+
33+
// Expect
34+
expect(err).to.have.property('code').equal(code)
35+
expect(err).to.have.property('message').equal(format(message, s1, s2))
36+
expect(err).to.have.property('name').equal('Error')
37+
expect(err).to.have.property('stack').startWith(errstr)
38+
expect(errstr).to.equal(`${err.name} [${code}]: ${err.message}`)
39+
})
40+
})
41+
})

src/create-node-error.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/**
2+
* @file createNodeError
3+
* @module create-node-error/createNodeError
4+
* @see https://github.com/nodejs/node/blob/v19.3.0/lib/internal/errors.js#L367-L404
5+
*/
6+
7+
import type { ErrorCode } from '#src/enums'
8+
import formatMessage from '#src/internal/format-message'
9+
import kIsNodeError from '#src/internal/k-is-node-error'
10+
import prepareStackTrace from '#src/internal/prepare-stack-trace'
11+
import type { MessageFn, NodeError, NodeErrorConstructor } from '#src/types'
12+
13+
/**
14+
* Creates a Node.js error constructor.
15+
*
16+
* @template B - Error base class type
17+
* @template M - Error message type
18+
* @template T - Error base type
19+
*
20+
* @constructs NodeError<T>
21+
*
22+
* @param {ErrorCode} code - Node.js error code
23+
* @param {B} Base - Error base class
24+
* @param {M} message - Error message or message function
25+
* @return {NodeErrorConstructor<B, M>} `NodeError` constructor
26+
*/
27+
function createNodeError<
28+
B extends ErrorConstructor = ErrorConstructor,
29+
M extends MessageFn | string = MessageFn | string,
30+
T extends B['prototype'] = B['prototype']
31+
>(code: ErrorCode, Base: B, message: M): NodeErrorConstructor<B, M, T> {
32+
/**
33+
* Creates a Node.js error.
34+
*
35+
* [1]: https://nodejs.org/api/util.html#utilformatformat-args
36+
*
37+
* @class
38+
* @implements {NodeError<T>}
39+
*
40+
* @param {any[] | Parameters<M>} args - `message` params if `message` is a
41+
* function; [`util.format`][1] arguments if `message` is a string
42+
* @return {NodeError<T>} Node.js error instance
43+
*/
44+
function NodeError(
45+
...args: M extends MessageFn ? Parameters<M> : any[]
46+
): NodeError<T> {
47+
/**
48+
* Node.js error instance.
49+
*
50+
* @const {NodeError<T>} error
51+
*/
52+
const error: NodeError<T> = new Base() as NodeError<T>
53+
54+
// define error symbol
55+
Object.defineProperty(error, kIsNodeError, {
56+
configurable: true,
57+
enumerable: false,
58+
value: true,
59+
writable: false
60+
})
61+
62+
// define instance properties and methods
63+
Object.defineProperties(error, {
64+
code: {
65+
configurable: true,
66+
enumerable: false,
67+
value: code,
68+
writable: true
69+
},
70+
message: {
71+
configurable: true,
72+
enumerable: false,
73+
value: formatMessage(code, message, args, error),
74+
writable: true
75+
},
76+
toString: {
77+
configurable: true,
78+
enumerable: false,
79+
/**
80+
* Returns a string representation of the error.
81+
*
82+
* @this {NodeError<T>}
83+
*
84+
* @return {string} String representation of error
85+
*/
86+
value(this: NodeError<T>): string {
87+
return `${this.name} [${this.code}]: ${this.message}`
88+
},
89+
writable: true
90+
}
91+
})
92+
93+
// add stack trace
94+
prepareStackTrace(error)
95+
96+
return error
97+
}
98+
99+
return NodeError as NodeErrorConstructor<B, M, T>
100+
}
101+
102+
export default createNodeError

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* @module create-node-error
44
*/
55

6+
export { default as createNodeError } from './create-node-error'
67
export { default as determineSpecificType } from './determine-specific-type'
78
export * from './enums'
89
export * from './types'

src/types/__tests__/node-error-constructor.spec-d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ describe('unit-d:types/NodeErrorConstructor', () => {
2121
expectTypeOf<TestSubject<B, M>>().constructorParameters.toEqualTypeOf<P>()
2222
})
2323

24+
it('should match [prototype: NodeError<T>]', () => {
25+
expectTypeOf<TestSubject>()
26+
.toHaveProperty('prototype')
27+
.toEqualTypeOf<NodeError>()
28+
expectTypeOf<TestSubject<SyntaxErrorConstructor>>()
29+
.toHaveProperty('prototype')
30+
.toEqualTypeOf<NodeError<SyntaxError>>()
31+
})
32+
2433
it('should return NodeError<T>', () => {
2534
expectTypeOf<TestSubject>().returns.toEqualTypeOf<NodeError>()
2635
expectTypeOf<TestSubject<TypeErrorConstructor>>().returns.toEqualTypeOf<

src/types/node-error-constructor.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type NodeError from './node-error'
1010
/**
1111
* Node.js error constructor type.
1212
*
13-
* @template B - Error constructor type
13+
* @template B - Error base class type
1414
* @template M - Error message type
1515
* @template T - Error base type
1616
*
@@ -21,6 +21,8 @@ type NodeErrorConstructor<
2121
M extends MessageFn | string = string,
2222
T extends B['prototype'] = B['prototype']
2323
> = Overwrite<B, B> & {
24+
readonly prototype: NodeError<T>
25+
2426
new (...args: M extends MessageFn ? Parameters<M> : any[]): NodeError<T>
2527
(...args: M extends MessageFn ? Parameters<M> : any[]): NodeError<T>
2628
}

0 commit comments

Comments
 (0)