react-hook-eslint 1.0.1

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/lib/tools.js ADDED
@@ -0,0 +1,394 @@
1
+ 'use strict'
2
+
3
+ /* eslint no-prototype-builtins: 0 */
4
+
5
+ const format = require('quick-format-unescaped')
6
+ const { mapHttpRequest, mapHttpResponse } = require('pino-std-serializers')
7
+ const SonicBoom = require('sonic-boom')
8
+ const onExit = require('on-exit-leak-free')
9
+ const {
10
+ lsCacheSym,
11
+ chindingsSym,
12
+ writeSym,
13
+ serializersSym,
14
+ formatOptsSym,
15
+ endSym,
16
+ stringifiersSym,
17
+ stringifySym,
18
+ stringifySafeSym,
19
+ wildcardFirstSym,
20
+ nestedKeySym,
21
+ formattersSym,
22
+ messageKeySym,
23
+ errorKeySym,
24
+ nestedKeyStrSym,
25
+ msgPrefixSym
26
+ } = require('./symbols')
27
+ const { isMainThread } = require('worker_threads')
28
+ const transport = require('./transport')
29
+
30
+ function noop () {
31
+ }
32
+
33
+ function genLog (level, hook) {
34
+ if (!hook) return LOG
35
+
36
+ return function hookWrappedLog (...args) {
37
+ hook.call(this, args, LOG, level)
38
+ }
39
+
40
+ function LOG (o, ...n) {
41
+ if (typeof o === 'object') {
42
+ let msg = o
43
+ if (o !== null) {
44
+ if (o.method && o.headers && o.socket) {
45
+ o = mapHttpRequest(o)
46
+ } else if (typeof o.setHeader === 'function') {
47
+ o = mapHttpResponse(o)
48
+ }
49
+ }
50
+ let formatParams
51
+ if (msg === null && n.length === 0) {
52
+ formatParams = [null]
53
+ } else {
54
+ msg = n.shift()
55
+ formatParams = n
56
+ }
57
+ // We do not use a coercive check for `msg` as it is
58
+ // measurably slower than the explicit checks.
59
+ if (typeof this[msgPrefixSym] === 'string' && msg !== undefined && msg !== null) {
60
+ msg = this[msgPrefixSym] + msg
61
+ }
62
+ this[writeSym](o, format(msg, formatParams, this[formatOptsSym]), level)
63
+ } else {
64
+ let msg = o === undefined ? n.shift() : o
65
+
66
+ // We do not use a coercive check for `msg` as it is
67
+ // measurably slower than the explicit checks.
68
+ if (typeof this[msgPrefixSym] === 'string' && msg !== undefined && msg !== null) {
69
+ msg = this[msgPrefixSym] + msg
70
+ }
71
+ this[writeSym](null, format(msg, n, this[formatOptsSym]), level)
72
+ }
73
+ }
74
+ }
75
+
76
+ // magically escape strings for json
77
+ // relying on their charCodeAt
78
+ // everything below 32 needs JSON.stringify()
79
+ // 34 and 92 happens all the time, so we
80
+ // have a fast case for them
81
+ function asString (str) {
82
+ let result = ''
83
+ let last = 0
84
+ let found = false
85
+ let point = 255
86
+ const l = str.length
87
+ if (l > 100) {
88
+ return JSON.stringify(str)
89
+ }
90
+ for (var i = 0; i < l && point >= 32; i++) {
91
+ point = str.charCodeAt(i)
92
+ if (point === 34 || point === 92) {
93
+ result += str.slice(last, i) + '\\'
94
+ last = i
95
+ found = true
96
+ }
97
+ }
98
+ if (!found) {
99
+ result = str
100
+ } else {
101
+ result += str.slice(last)
102
+ }
103
+ return point < 32 ? JSON.stringify(str) : '"' + result + '"'
104
+ }
105
+
106
+ function asJson (obj, msg, num, time) {
107
+ const stringify = this[stringifySym]
108
+ const stringifySafe = this[stringifySafeSym]
109
+ const stringifiers = this[stringifiersSym]
110
+ const end = this[endSym]
111
+ const chindings = this[chindingsSym]
112
+ const serializers = this[serializersSym]
113
+ const formatters = this[formattersSym]
114
+ const messageKey = this[messageKeySym]
115
+ const errorKey = this[errorKeySym]
116
+ let data = this[lsCacheSym][num] + time
117
+
118
+ // we need the child bindings added to the output first so instance logged
119
+ // objects can take precedence when JSON.parse-ing the resulting log line
120
+ data = data + chindings
121
+
122
+ let value
123
+ if (formatters.log) {
124
+ obj = formatters.log(obj)
125
+ }
126
+ const wildcardStringifier = stringifiers[wildcardFirstSym]
127
+ let propStr = ''
128
+ for (const key in obj) {
129
+ value = obj[key]
130
+ if (Object.prototype.hasOwnProperty.call(obj, key) && value !== undefined) {
131
+ if (serializers[key]) {
132
+ value = serializers[key](value)
133
+ } else if (key === errorKey && serializers.err) {
134
+ value = serializers.err(value)
135
+ }
136
+
137
+ const stringifier = stringifiers[key] || wildcardStringifier
138
+
139
+ switch (typeof value) {
140
+ case 'undefined':
141
+ case 'function':
142
+ continue
143
+ case 'number':
144
+ /* eslint no-fallthrough: "off" */
145
+ if (Number.isFinite(value) === false) {
146
+ value = null
147
+ }
148
+ // this case explicitly falls through to the next one
149
+ case 'boolean':
150
+ if (stringifier) value = stringifier(value)
151
+ break
152
+ case 'string':
153
+ value = (stringifier || asString)(value)
154
+ break
155
+ default:
156
+ value = (stringifier || stringify)(value, stringifySafe)
157
+ }
158
+ if (value === undefined) continue
159
+ const strKey = asString(key)
160
+ propStr += ',' + strKey + ':' + value
161
+ }
162
+ }
163
+
164
+ let msgStr = ''
165
+ if (msg !== undefined) {
166
+ value = serializers[messageKey] ? serializers[messageKey](msg) : msg
167
+ const stringifier = stringifiers[messageKey] || wildcardStringifier
168
+
169
+ switch (typeof value) {
170
+ case 'function':
171
+ break
172
+ case 'number':
173
+ /* eslint no-fallthrough: "off" */
174
+ if (Number.isFinite(value) === false) {
175
+ value = null
176
+ }
177
+ // this case explicitly falls through to the next one
178
+ case 'boolean':
179
+ if (stringifier) value = stringifier(value)
180
+ msgStr = ',"' + messageKey + '":' + value
181
+ break
182
+ case 'string':
183
+ value = (stringifier || asString)(value)
184
+ msgStr = ',"' + messageKey + '":' + value
185
+ break
186
+ default:
187
+ value = (stringifier || stringify)(value, stringifySafe)
188
+ msgStr = ',"' + messageKey + '":' + value
189
+ }
190
+ }
191
+
192
+ if (this[nestedKeySym] && propStr) {
193
+ // place all the obj properties under the specified key
194
+ // the nested key is already formatted from the constructor
195
+ return data + this[nestedKeyStrSym] + propStr.slice(1) + '}' + msgStr + end
196
+ } else {
197
+ return data + propStr + msgStr + end
198
+ }
199
+ }
200
+
201
+ function asChindings (instance, bindings) {
202
+ let value
203
+ let data = instance[chindingsSym]
204
+ const stringify = instance[stringifySym]
205
+ const stringifySafe = instance[stringifySafeSym]
206
+ const stringifiers = instance[stringifiersSym]
207
+ const wildcardStringifier = stringifiers[wildcardFirstSym]
208
+ const serializers = instance[serializersSym]
209
+ const formatter = instance[formattersSym].bindings
210
+ bindings = formatter(bindings)
211
+
212
+ for (const key in bindings) {
213
+ value = bindings[key]
214
+ const valid = key !== 'level' &&
215
+ key !== 'serializers' &&
216
+ key !== 'formatters' &&
217
+ key !== 'customLevels' &&
218
+ bindings.hasOwnProperty(key) &&
219
+ value !== undefined
220
+ if (valid === true) {
221
+ value = serializers[key] ? serializers[key](value) : value
222
+ value = (stringifiers[key] || wildcardStringifier || stringify)(value, stringifySafe)
223
+ if (value === undefined) continue
224
+ data += ',"' + key + '":' + value
225
+ }
226
+ }
227
+ return data
228
+ }
229
+
230
+ function hasBeenTampered (stream) {
231
+ return stream.write !== stream.constructor.prototype.write
232
+ }
233
+
234
+ const hasNodeCodeCoverage = process.env.NODE_V8_COVERAGE || process.env.V8_COVERAGE
235
+
236
+ function buildSafeSonicBoom (opts) {
237
+ const stream = new SonicBoom(opts)
238
+ stream.on('error', filterBrokenPipe)
239
+ // If we are sync: false, we must flush on exit
240
+ // We must disable this if there is node code coverage due to
241
+ // https://github.com/nodejs/node/issues/49344#issuecomment-1741776308.
242
+ if (!hasNodeCodeCoverage && !opts.sync && isMainThread) {
243
+ onExit.register(stream, autoEnd)
244
+
245
+ stream.on('close', function () {
246
+ onExit.unregister(stream)
247
+ })
248
+ }
249
+ return stream
250
+
251
+ function filterBrokenPipe (err) {
252
+ // Impossible to replicate across all operating systems
253
+ /* istanbul ignore next */
254
+ if (err.code === 'EPIPE') {
255
+ // If we get EPIPE, we should stop logging here
256
+ // however we have no control to the consumer of
257
+ // SonicBoom, so we just overwrite the write method
258
+ stream.write = noop
259
+ stream.end = noop
260
+ stream.flushSync = noop
261
+ stream.destroy = noop
262
+ return
263
+ }
264
+ stream.removeListener('error', filterBrokenPipe)
265
+ stream.emit('error', err)
266
+ }
267
+ }
268
+
269
+ function autoEnd (stream, eventName) {
270
+ // This check is needed only on some platforms
271
+ /* istanbul ignore next */
272
+ if (stream.destroyed) {
273
+ return
274
+ }
275
+
276
+ if (eventName === 'beforeExit') {
277
+ // We still have an event loop, let's use it
278
+ stream.flush()
279
+ stream.on('drain', function () {
280
+ stream.end()
281
+ })
282
+ } else {
283
+ // For some reason istanbul is not detecting this, but it's there
284
+ /* istanbul ignore next */
285
+ // We do not have an event loop, so flush synchronously
286
+ stream.flushSync()
287
+ }
288
+ }
289
+
290
+ function createArgsNormalizer (defaultOptions) {
291
+ return function normalizeArgs (instance, caller, opts = {}, stream) {
292
+ // support stream as a string
293
+ if (typeof opts === 'string') {
294
+ stream = buildSafeSonicBoom({ dest: opts })
295
+ opts = {}
296
+ } else if (typeof stream === 'string') {
297
+ if (opts && opts.transport) {
298
+ throw Error('only one of option.transport or stream can be specified')
299
+ }
300
+ stream = buildSafeSonicBoom({ dest: stream })
301
+ } else if (opts instanceof SonicBoom || opts.writable || opts._writableState) {
302
+ stream = opts
303
+ opts = {}
304
+ } else if (opts.transport) {
305
+ if (opts.transport instanceof SonicBoom || opts.transport.writable || opts.transport._writableState) {
306
+ throw Error('option.transport do not allow stream, please pass to option directly. e.g. pino(transport)')
307
+ }
308
+ if (opts.transport.targets && opts.transport.targets.length && opts.formatters && typeof opts.formatters.level === 'function') {
309
+ throw Error('option.transport.targets do not allow custom level formatters')
310
+ }
311
+
312
+ let customLevels
313
+ if (opts.customLevels) {
314
+ customLevels = opts.useOnlyCustomLevels ? opts.customLevels : Object.assign({}, opts.levels, opts.customLevels)
315
+ }
316
+ stream = transport({ caller, ...opts.transport, levels: customLevels })
317
+ }
318
+ opts = Object.assign({}, defaultOptions, opts)
319
+ opts.serializers = Object.assign({}, defaultOptions.serializers, opts.serializers)
320
+ opts.formatters = Object.assign({}, defaultOptions.formatters, opts.formatters)
321
+
322
+ if (opts.prettyPrint) {
323
+ throw new Error('prettyPrint option is no longer supported, see the pino-pretty package (https://github.com/pinojs/pino-pretty)')
324
+ }
325
+
326
+ const { enabled, onChild } = opts
327
+ if (enabled === false) opts.level = 'silent'
328
+ if (!onChild) opts.onChild = noop
329
+ if (!stream) {
330
+ if (!hasBeenTampered(process.stdout)) {
331
+ // If process.stdout.fd is undefined, it means that we are running
332
+ // in a worker thread. Let's assume we are logging to file descriptor 1.
333
+ stream = buildSafeSonicBoom({ fd: process.stdout.fd || 1 })
334
+ } else {
335
+ stream = process.stdout
336
+ }
337
+ }
338
+ return { opts, stream }
339
+ }
340
+ }
341
+
342
+ function stringify (obj, stringifySafeFn) {
343
+ try {
344
+ return JSON.stringify(obj)
345
+ } catch (_) {
346
+ try {
347
+ const stringify = stringifySafeFn || this[stringifySafeSym]
348
+ return stringify(obj)
349
+ } catch (_) {
350
+ return '"[unable to serialize, circular reference is too complex to analyze]"'
351
+ }
352
+ }
353
+ }
354
+
355
+ function buildFormatters (level, bindings, log) {
356
+ return {
357
+ level,
358
+ bindings,
359
+ log
360
+ }
361
+ }
362
+
363
+ /**
364
+ * Convert a string integer file descriptor to a proper native integer
365
+ * file descriptor.
366
+ *
367
+ * @param {string} destination The file descriptor string to attempt to convert.
368
+ *
369
+ * @returns {Number}
370
+ */
371
+ function normalizeDestFileDescriptor (destination) {
372
+ const fd = Number(destination)
373
+ if (typeof destination === 'string' && Number.isFinite(fd)) {
374
+ return fd
375
+ }
376
+ // destination could be undefined if we are in a worker
377
+ if (destination === undefined) {
378
+ // This is stdout in UNIX systems
379
+ return 1
380
+ }
381
+ return destination
382
+ }
383
+
384
+ module.exports = {
385
+ noop,
386
+ buildSafeSonicBoom,
387
+ asChindings,
388
+ asJson,
389
+ genLog,
390
+ createArgsNormalizer,
391
+ stringify,
392
+ buildFormatters,
393
+ normalizeDestFileDescriptor
394
+ }
@@ -0,0 +1,56 @@
1
+ 'use strict'
2
+
3
+ const { realImport, realRequire } = require('real-require')
4
+
5
+ module.exports = loadTransportStreamBuilder
6
+
7
+ /**
8
+ * Loads & returns a function to build transport streams
9
+ * @param {string} target
10
+ * @returns {Promise<function(object): Promise<import('node:stream').Writable>>}
11
+ * @throws {Error} In case the target module does not export a function
12
+ */
13
+ async function loadTransportStreamBuilder (target) {
14
+ let fn
15
+ try {
16
+ const toLoad = target.startsWith('file://') ? target : 'file://' + target
17
+
18
+ if (toLoad.endsWith('.ts') || toLoad.endsWith('.cts')) {
19
+ // TODO: add support for the TSM modules loader ( https://github.com/lukeed/tsm ).
20
+ if (process[Symbol.for('ts-node.register.instance')]) {
21
+ realRequire('ts-node/register')
22
+ } else if (process.env && process.env.TS_NODE_DEV) {
23
+ realRequire('ts-node-dev')
24
+ }
25
+ // TODO: Support ES imports once tsc, tap & ts-node provide better compatibility guarantees.
26
+ fn = realRequire(decodeURIComponent(target))
27
+ } else {
28
+ fn = (await realImport(toLoad))
29
+ }
30
+ } catch (error) {
31
+ // See this PR for details: https://github.com/pinojs/thread-stream/pull/34
32
+ if ((error.code === 'ENOTDIR' || error.code === 'ERR_MODULE_NOT_FOUND')) {
33
+ fn = realRequire(target)
34
+ } else if (error.code === undefined || error.code === 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING') {
35
+ // When bundled with pkg, an undefined error is thrown when called with realImport
36
+ // When bundled with pkg and using node v20, an ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING error is thrown when called with realImport
37
+ // More info at: https://github.com/pinojs/thread-stream/issues/143
38
+ try {
39
+ fn = realRequire(decodeURIComponent(target))
40
+ } catch {
41
+ throw error
42
+ }
43
+ } else {
44
+ throw error
45
+ }
46
+ }
47
+
48
+ // Depending on how the default export is performed, and on how the code is
49
+ // transpiled, we may find cases of two nested "default" objects.
50
+ // See https://github.com/pinojs/pino/issues/1243#issuecomment-982774762
51
+ if (typeof fn === 'object') fn = fn.default
52
+ if (typeof fn === 'object') fn = fn.default
53
+ if (typeof fn !== 'function') throw Error('exported worker is not a function')
54
+
55
+ return fn
56
+ }
@@ -0,0 +1,167 @@
1
+ 'use strict'
2
+
3
+ const { createRequire } = require('module')
4
+ const getCallers = require('./caller')
5
+ const { join, isAbsolute, sep } = require('node:path')
6
+ const sleep = require('atomic-sleep')
7
+ const onExit = require('on-exit-leak-free')
8
+ const ThreadStream = require('thread-stream')
9
+
10
+ function setupOnExit (stream) {
11
+ // This is leak free, it does not leave event handlers
12
+ onExit.register(stream, autoEnd)
13
+ onExit.registerBeforeExit(stream, flush)
14
+
15
+ stream.on('close', function () {
16
+ onExit.unregister(stream)
17
+ })
18
+ }
19
+
20
+ function buildStream (filename, workerData, workerOpts, sync) {
21
+ const stream = new ThreadStream({
22
+ filename,
23
+ workerData,
24
+ workerOpts,
25
+ sync
26
+ })
27
+
28
+ stream.on('ready', onReady)
29
+ stream.on('close', function () {
30
+ process.removeListener('exit', onExit)
31
+ })
32
+
33
+ process.on('exit', onExit)
34
+
35
+ function onReady () {
36
+ process.removeListener('exit', onExit)
37
+ stream.unref()
38
+
39
+ if (workerOpts.autoEnd !== false) {
40
+ setupOnExit(stream)
41
+ }
42
+ }
43
+
44
+ function onExit () {
45
+ /* istanbul ignore next */
46
+ if (stream.closed) {
47
+ return
48
+ }
49
+ stream.flushSync()
50
+ // Apparently there is a very sporadic race condition
51
+ // that in certain OS would prevent the messages to be flushed
52
+ // because the thread might not have been created still.
53
+ // Unfortunately we need to sleep(100) in this case.
54
+ sleep(100)
55
+ stream.end()
56
+ }
57
+
58
+ return stream
59
+ }
60
+
61
+ function autoEnd (stream) {
62
+ stream.ref()
63
+ stream.flushSync()
64
+ stream.end()
65
+ stream.once('close', function () {
66
+ stream.unref()
67
+ })
68
+ }
69
+
70
+ function flush (stream) {
71
+ stream.flushSync()
72
+ }
73
+
74
+ function transport (fullOptions) {
75
+ const { pipeline, targets, levels, dedupe, worker = {}, caller = getCallers(), sync = false } = fullOptions
76
+
77
+ const options = {
78
+ ...fullOptions.options
79
+ }
80
+
81
+ // Backwards compatibility
82
+ const callers = typeof caller === 'string' ? [caller] : caller
83
+
84
+ // This will be eventually modified by bundlers
85
+ const bundlerOverrides = '__bundlerPathsOverrides' in globalThis ? globalThis.__bundlerPathsOverrides : {}
86
+
87
+ let target = fullOptions.target
88
+
89
+ if (target && targets) {
90
+ throw new Error('only one of target or targets can be specified')
91
+ }
92
+
93
+ if (targets) {
94
+ target = bundlerOverrides['pino-worker'] || join(__dirname, 'worker.js')
95
+ options.targets = targets.filter(dest => dest.target).map((dest) => {
96
+ return {
97
+ ...dest,
98
+ target: fixTarget(dest.target)
99
+ }
100
+ })
101
+ options.pipelines = targets.filter(dest => dest.pipeline).map((dest) => {
102
+ return dest.pipeline.map((t) => {
103
+ return {
104
+ ...t,
105
+ level: dest.level, // duplicate the pipeline `level` property defined in the upper level
106
+ target: fixTarget(t.target)
107
+ }
108
+ })
109
+ })
110
+ } else if (pipeline) {
111
+ target = bundlerOverrides['pino-worker'] || join(__dirname, 'worker.js')
112
+ options.pipelines = [pipeline.map((dest) => {
113
+ return {
114
+ ...dest,
115
+ target: fixTarget(dest.target)
116
+ }
117
+ })]
118
+ }
119
+
120
+ if (levels) {
121
+ options.levels = levels
122
+ }
123
+
124
+ if (dedupe) {
125
+ options.dedupe = dedupe
126
+ }
127
+
128
+ options.pinoWillSendConfig = true
129
+
130
+ return buildStream(fixTarget(target), options, worker, sync)
131
+
132
+ function fixTarget (origin) {
133
+ origin = bundlerOverrides[origin] || origin
134
+
135
+ if (isAbsolute(origin) || origin.indexOf('file://') === 0) {
136
+ return origin
137
+ }
138
+
139
+ if (origin === 'pino/file') {
140
+ return join(__dirname, '..', 'file.js')
141
+ }
142
+
143
+ let fixTarget
144
+
145
+ for (const filePath of callers) {
146
+ try {
147
+ const context = filePath === 'node:repl'
148
+ ? process.cwd() + sep
149
+ : filePath
150
+
151
+ fixTarget = createRequire(context).resolve(origin)
152
+ break
153
+ } catch (err) {
154
+ // Silent catch
155
+ continue
156
+ }
157
+ }
158
+
159
+ if (!fixTarget) {
160
+ throw new Error(`unable to determine transport target for "${origin}"`)
161
+ }
162
+
163
+ return fixTarget
164
+ }
165
+ }
166
+
167
+ module.exports = transport