Skip to content

Commit 9494c4c

Browse files
committed
feat(internal): formatMessage
Signed-off-by: Lexus Drumgold <[email protected]>
1 parent b3f705a commit 9494c4c

File tree

5 files changed

+163
-3
lines changed

5 files changed

+163
-3
lines changed

.cspell.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,17 @@
3131
"patches/",
3232
"yarn.lock"
3333
],
34-
"ignoreRegExpList": ["/from\\s+(['\"]).*\\1/"],
34+
"ignoreRegExpList": [
35+
"/@flex-development\\/.*/",
36+
"/from\\s+(['\"]).*\\1/",
37+
"import\\(.*\\)"
38+
],
3539
"ignoreWords": [],
3640
"language": "en-US",
3741
"patterns": [],
3842
"readonly": true,
3943
"useGitignore": true,
4044
"usePnP": false,
4145
"version": "0.2",
42-
"words": ["fldv", "create-node-error"]
46+
"words": ["Odfijos", "fldv"]
4347
}

.eslintrc.base.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,7 @@ const config = {
10321032
'promise/prefer-await-to-callbacks': 0,
10331033
'promise/valid-params': 0,
10341034
'unicorn/consistent-destructuring': 0,
1035+
'unicorn/error-message': 0,
10351036
'unicorn/explicit-length-check': 0,
10361037
'unicorn/no-array-for-each': 0,
10371038
'unicorn/prefer-at': 0,

.eslintrc.cjs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,15 @@
1111
const config = {
1212
root: true,
1313
extends: ['./.eslintrc.base.cjs'],
14-
overrides: [...require('./.eslintrc.base.cjs').overrides]
14+
overrides: [
15+
...require('./.eslintrc.base.cjs').overrides,
16+
{
17+
files: ['src/internal/format-message.ts'],
18+
rules: {
19+
'unicorn/error-message': 0
20+
}
21+
}
22+
]
1523
}
1624

1725
module.exports = config
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* @file Unit Tests - formatMessage
3+
* @module create-error-node/internal/tests/unit/formatMessage
4+
*/
5+
6+
import { ErrorCode } from '#src/enums'
7+
import type { MessageFn } from '#src/types'
8+
import { format } from 'node-inspect-extracted'
9+
import { resolve } from 'node:path'
10+
import { pathToFileURL } from 'node:url'
11+
import testSubject from '../format-message'
12+
13+
describe('unit:internal/formatMessage', () => {
14+
it('should return formatted messsage if msg is MessageFn', () => {
15+
// Arrange
16+
const code: ErrorCode = ErrorCode.ERR_MODULE_NOT_FOUND
17+
const path: string = resolve('src/utils.ts')
18+
const base: string = resolve('src/index.ts')
19+
const msg: MessageFn<[string, string, string?]> = (
20+
path: string,
21+
base: string,
22+
type: string = 'module'
23+
) => `Cannot find ${type} '${path}' imported from ${base}`
24+
25+
// Act
26+
const result = testSubject(code, msg, [path, base], new TypeError())
27+
28+
// Expect
29+
expect(result).to.equal(msg(path, base))
30+
})
31+
32+
it('should return formatted messsage if msg is string', () => {
33+
// Arrange
34+
const code: ErrorCode = ErrorCode.ERR_IMPORT_ASSERTION_TYPE_MISSING
35+
const msg: string = 'Module "%s" needs an import assertion of type "%s"'
36+
const s1: string = pathToFileURL(resolve('./package.json')).href
37+
const s2: string = 'json'
38+
39+
// Act
40+
const result = testSubject(code, msg, [s1, s2], new TypeError())
41+
42+
// Expect
43+
expect(result).to.equal(format(msg, s1, s2))
44+
})
45+
46+
it('should throw if args length is invalid', () => {
47+
// Arrange
48+
const cases: Parameters<typeof testSubject>[] = [
49+
[
50+
ErrorCode.ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED,
51+
'Import assertion type "%s" is unsupported'
52+
],
53+
[
54+
ErrorCode.ERR_UNKNOWN_FILE_EXTENSION,
55+
(ext: string, path: string) => {
56+
return `Unknown file extension "${ext}" for ${path}`
57+
}
58+
]
59+
]
60+
61+
// Act + Expect
62+
cases.forEach(([code, msg, args = [], self]) => {
63+
const fn: typeof testSubject = testSubject.bind({}, code, msg, args, self)
64+
const pattern: string = `^${code}; The arguments length \\(${args.length}\\) provided to \`(?:msg|util\\.format)\` does not match the required length \\(\\d\\)\\.$`
65+
66+
expect(fn).to.throw(new RegExp(pattern))
67+
})
68+
})
69+
})

src/internal/format-message.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
* @file Internal - formatMessage
3+
* @module create-node-error/internal/formatMessage
4+
* @see https://github.com/nodejs/node/blob/v19.3.0/lib/internal/errors.js#L440-L467
5+
*/
6+
7+
import type { ErrorCode } from '#src/enums'
8+
import type { MessageFn } from '#src/types'
9+
import { format } from 'node-inspect-extracted'
10+
11+
/**
12+
* Formats an error message.
13+
*
14+
* @see https://nodejs.org/api/util.html#utilformatformat-args
15+
*
16+
* @param {ErrorCode} code - Node.js error code
17+
* @param {MessageFn | string} msg - Error message or message function
18+
* @param {any[]} [args=[]] - `util.format` arguments if `msg` is a string, or
19+
* `msg` parameters if `msg` is a function
20+
* @param {Error} [self=new Error()] - Error used as `this` argument in `msg`
21+
* @return {string} Formatted error message
22+
* @throws {Error} If `args` length is invalid
23+
*/
24+
function formatMessage(
25+
code: ErrorCode,
26+
msg: MessageFn | string,
27+
args: any[] = [],
28+
self: Error = new Error()
29+
): string {
30+
/**
31+
* Invalid {@linkcode args} length error message.
32+
*
33+
* @var {string} error
34+
*/
35+
let error: string = `${code};` + ' '
36+
37+
/**
38+
* Expected message length.
39+
*
40+
* @var {number} length
41+
*/
42+
let length: number = 0
43+
44+
// try returning error message from function
45+
if (typeof msg === 'function') {
46+
// set expected message length to total number of function parameters
47+
length = msg.length
48+
49+
// ensure expected message length. default parameters do not count
50+
if (!(length <= args.length)) {
51+
error += `The arguments length (${args.length}) provided to \`msg\` does not match the required length (${length}).`
52+
throw new Error(error)
53+
}
54+
55+
// return result from message function
56+
return Reflect.apply(msg, self, args)
57+
}
58+
59+
/**
60+
* {@linkcode format} specifiers regex.
61+
*
62+
* @const {RegExp} regex
63+
*/
64+
const regex: RegExp = /%[Odfijos]/g
65+
66+
// get expected message length
67+
while (regex.exec(msg) !== null) length++
68+
69+
// ensure expected message length
70+
if (length !== args.length) {
71+
error += `The arguments length (${args.length}) provided to \`util.format\` does not match the required length (${length}).`
72+
throw new Error(error)
73+
}
74+
75+
return Reflect.apply(format, null, [msg, ...args])
76+
}
77+
78+
export default formatMessage

0 commit comments

Comments
 (0)