Skip to content

Commit

Permalink
Refactor code-style
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Aug 22, 2023
1 parent 66f2c87 commit bf96d15
Show file tree
Hide file tree
Showing 2 changed files with 397 additions and 240 deletions.
303 changes: 179 additions & 124 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,166 +1,216 @@
/**
* @typedef {import('unified').Processor} Processor
* @typedef {import('unified').ProcessCallback} ProcessCallback
* @typedef {import('vfile').VFileValue} Value
* @typedef {((error?: Error) => void)} Callback
* @typedef {Omit<NodeJS.ReadableStream & NodeJS.WritableStream, 'read'|'setEncoding'|'pause'|'resume'|'isPaused'|'unpipe'|'unshift'|'wrap'>} MinimalDuplex
*/

/**
* @callback Callback
* Callback.
* @param {Error | undefined} [error]
* Error.
* @returns {undefined | void}
* Nothing.
*
* @typedef {Omit<NodeJS.ReadableStream & NodeJS.WritableStream, SymbolConstructor['asyncIterator'] | 'isPaused' | 'pause' | 'read' | 'resume' | 'setEncoding' | 'unpipe' | 'unshift' | 'wrap'>} MinimalDuplex
* Simple readable and writable stream.
*
* @typedef PipeOptions
* Configuration (optional).
* @property {boolean | null | undefined} [end=false]
* Whether to `end` if the other stream ends (default: `false`).
*
* @typedef {Buffer | string} Value
*/

import {EventEmitter} from 'node:events'

/**
* Create a readable/writable stream that transforms with `processor`.
* Create a duplex (readable and writable) stream that transforms with
* `processor`.
*
* @param {Processor} processor
* unified processor.
* @returns {MinimalDuplex}
* Duplex stream.
*/
export function stream(processor) {
/** @type {string[]} */
/** @type {Array<string>} */
let chunks = []
/** @type {boolean|undefined} */
let ended
let ended = false

/** @type {MinimalDuplex} */
// @ts-expect-error: `addEventListener` is fine.
const emitter = new EventEmitter()
// @ts-expect-error: fine.
emitter.end = end
emitter.pipe = pipe
emitter.readable = true
emitter.writable = true
// @ts-expect-error: fine.
emitter.write = write

return emitter

/**
* Write a chunk into memory.
*
* @param [chunk]
* Chunk to write.
* @param [encoding]
* Encoding to understand `chunk` as when it’s a buffer.
* @param [callback]
* Callback called when written.
* @overload
* @param {Value | null | undefined} [chunk]
* Slice of markdown to parse (`string` or `Uint8Array`).
* @param {string | null | undefined} [encoding]
* Character encoding to understand `chunk` as when it’s a `Uint8Array`
* (`string`, default: `'utf8'`).
* @param {Callback | null | undefined} [callback]
* Function called when write was successful.
* @returns {boolean}
* Whether write was successful.
*
* @overload
* @param {Value | null | undefined} [chunk]
* Slice of markdown to parse (`string` or `Uint8Array`).
* @param {Callback | null | undefined} [callback]
* Function called when write was successful.
* @returns {boolean}
* Whether write was successful.
*
* @param {Value | null | undefined} [chunk]
* Slice of markdown to parse (`string` or `Uint8Array`).
* @param {Callback | string | null | undefined} [encoding]
* Character encoding to understand `chunk` as when it’s a `Uint8Array`
* (`string`, default: `'utf8'`).
* @param {Callback | null | undefined} [callback]
* Function called when write was successful.
* @returns {boolean}
* Whether write was successful.
*/
const write =
/**
* @type {(
* ((value?: Value, encoding?: string, callback?: Callback) => boolean) &
* ((value: Value, callback?: Callback) => boolean)
* )}
*/
(
/**
* @param {Value} [chunk]
* @param {string} [encoding]
* @param {Callback} [callback]
*/
function (chunk, encoding, callback) {
if (typeof encoding === 'function') {
callback = encoding
encoding = undefined
}
function write(chunk, encoding, callback) {
if (typeof encoding === 'function') {
callback = encoding
encoding = undefined
}

if (ended) {
throw new Error('Did not expect `write` after `end`')
}
if (ended) {
throw new Error('Did not expect `write` after `end`')
}

// To do: improve writnig, I think there’s a Node textencoder example somewhere.
// @ts-expect-error: `encoding` is fine on buffers.
chunks.push((chunk || '').toString(encoding || 'utf8'))
chunks.push(
typeof chunk === 'string'
? chunk
: chunk
? // @ts-expect-error: to do: uint8array.
chunk.toString(encoding || undefined)
: ''
)

if (callback) {
callback()
}
if (callback) {
callback()
}

// Signal succesful write.
return true
}
)
// Signal successful write.
return true
}

/**
* End the writing.
* Passes all arguments to a final `write`.
* Starts the process, which will trigger `error`, with a fatal error, if any;
* `data`, with the generated document in `string` form, if succesful.
* If messages are triggered during the process, those are triggerd as
* `warning`s.
*
* @param [chunk]
* Chunk to write.
* @param [encoding]
* Encoding to understand `chunk` as when it’s a buffer.
* @param [callback]
* Callback called when written.
* Passes all arguments as a final `write`.
*
* @overload
* @param {Value | null | undefined} [chunk]
* Slice of markdown to parse (`string` or `Uint8Array`).
* @param {string | null | undefined} [encoding]
* Character encoding to understand `chunk` as when it’s a `Uint8Array`
* (`string`, default: `'utf8'`).
* @param {Callback | null | undefined} [callback]
* Function called when write was successful.
* @returns {boolean}
* Whether write was successful.
*
* @overload
* @param {Value | null | undefined} [chunk]
* Slice of markdown to parse (`string` or `Uint8Array`).
* @param {Callback | null | undefined} [callback]
* Function called when write was successful.
* @returns {boolean}
* Whether write was successful.
*
* @overload
* @param {Callback | null | undefined} [callback]
* Function called when write was successful.
* @returns {boolean}
* Whether write was successful.
*
* @param {Callback | Value | null | undefined} [chunk]
* Slice of markdown to parse (`string` or `Uint8Array`).
* @param {Callback | string | null | undefined} [encoding]
* Character encoding to understand `chunk` as when it’s a `Uint8Array`
* (`string`, default: `'utf8'`).
* @param {Callback | null | undefined} [callback]
* Function called when write was successful.
* @returns {boolean}
* Whether write was successful.
*/
const end =
/**
* @type {(
* ((value?: Value, encoding?: string, callback?: Callback) => boolean) &
* ((value: Value, callback?: Callback) => boolean)
* )}
*/
(
/**
* @param {Value} [chunk]
* @param {string} [encoding]
* @param {Callback} [callback]
*/
function (chunk, encoding, callback) {
write(chunk, encoding, callback)
function end(chunk, encoding, callback) {
if (typeof chunk === 'function') {
encoding = chunk
chunk = undefined
}

processor.process(chunks.join(''), done)
if (typeof encoding === 'function') {
callback = encoding
encoding = undefined
}

emitter.emit('end')
ended = true
return true

/** @type {ProcessCallback} */
function done(error, file) {
const messages = file ? file.messages : []
let index = -1

// @ts-expect-error: clear memory.
chunks = undefined

// Trigger messages as warnings, except for fatal error.
while (++index < messages.length) {
/* istanbul ignore else - shouldn’t happen. */
if (messages[index] !== error) {
emitter.emit('warning', messages[index])
}
}

if (error || !file) {
// Don’t enter an infinite error throwing loop.
setTimeout(() => {
emitter.emit('error', error)
}, 4)
} else {
emitter.emit('data', file.value)
emitter.emit('end')
}
write(chunk, encoding, callback)

processor.process(chunks.join(''), done)

emitter.emit('end')
ended = true
return true

/** @type {ProcessCallback} */
function done(error, file) {
const messages = file ? file.messages : []
let index = -1

// @ts-expect-error: clear memory.
chunks = undefined

// Trigger messages as warnings, except for fatal error.
while (++index < messages.length) {
if (messages[index] !== error) {
emitter.emit('warning', messages[index])
}
}
)

/** @type {MinimalDuplex} */
// @ts-expect-error `addListener` is fine.
const emitter = Object.assign(new EventEmitter(), {
processor,
writable: true,
readable: true,
write,
end,
pipe
})

return emitter
if (error || !file) {
// Don’t enter an infinite error throwing loop.
setImmediate(function () {
emitter.emit('error', error)
})
} else {
emitter.emit('data', file.value)
emitter.emit('end')
}
}
}

/**
* Pipe the processor into a writable stream.
*
* Basically `Stream#pipe`, but inlined and simplified to keep the bundled
* size down.
* See: <https://github.com/nodejs/node/blob/43a5170/lib/internal/streams/legacy.js#L13>.
*
* @template {NodeJS.WritableStream} Stream
* Stream to write into.
* @param {Stream} dest
* @template {NodeJS.WritableStream} Destination
* Stream type.
* @param {Destination} dest
* Stream to write into.
* @param {{end?: boolean}} [options]
* @param {PipeOptions | null | undefined} [options]
* Configuration (optional).
* @returns {Stream}
* Stream to write into.
* @returns {Destination}
* Given stream.
*/
function pipe(dest, options) {
emitter.on('data', ondata)
Expand All @@ -185,7 +235,8 @@ export function stream(processor) {
/**
* End destination.
*
* @returns {void}
* @returns {undefined}
* Nothing.
*/
function onended() {
if (dest.end) {
Expand All @@ -198,7 +249,8 @@ export function stream(processor) {
*
* @param {Value} chunk
* Data to write.
* @returns {void}
* @returns {undefined}
* Nothing.
*/
function ondata(chunk) {
if (dest.writable) {
Expand All @@ -209,7 +261,8 @@ export function stream(processor) {
/**
* Clean listeners.
*
* @returns {void}
* @returns {undefined}
* Nothing.
*/
function cleanup() {
emitter.removeListener('data', ondata)
Expand All @@ -225,8 +278,10 @@ export function stream(processor) {
/**
* Close dangling pipes and handle unheard errors.
*
* @param {Error?} [error]
* @returns {void}
* @param {Error | undefined} [error]
* Error.
* @returns {undefined}
* Nothing.
*/
function onerror(error) {
cleanup()
Expand Down
Loading

0 comments on commit bf96d15

Please sign in to comment.