Skip to content

Commit

Permalink
Refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed May 29, 2022
1 parent 2215ef1 commit dc4fde5
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 93 deletions.
121 changes: 69 additions & 52 deletions src/config/normalize/lib/call.js
Original file line number Diff line number Diff line change
@@ -1,70 +1,92 @@
import { inspect } from 'util'

import { wrapError } from '../../../error/wrap.js'
import { maybeFunction } from '../../../utils/function.js'

import { addPrefix } from './prefix.js'
import { handleValidateError } from './validate.js'

export const callFunc = async function ({ func, input, info, hasInput, test }) {
if (hasInput) {
return await callInputFunc(func, input, info)
}

if (test === undefined) {
return await callNoInputFunc(func, info)
}

return await callConstraintFunc(func, info)
}

// Most rule methods follow the same patterns:
// - Called with `input` and `info`
// Call a function from `test()`, `main()` or a definition.
// They are all:
// - Optionally async
export const callInputFunc = async function (func, input, info) {
// - Called with same arguments
// - Error handled
export const callFunc = async function ({
func,
input,
info: { originalName },
info: { example, prefix, ...info },
hasInput,
test,
}) {
try {
return await callUserFunc(func.bind(undefined, input), info)
return await (hasInput ? func(input, info) : func(info))
} catch (error) {
const errorA = handleError(error, info)
const errorB = addCurrentValue(errorA, input)
throw addExampleValue(errorB, info)
throw handleError({
error,
input,
example,
prefix,
originalName,
hasInput,
test,
})
}
}

// Some methods are not called with any `input` but their logic requires knowing
// whether it is undefined
const callConstraintFunc = async function (func, info) {
try {
return await callUserFunc(func, info)
} catch (error) {
const errorA = handleError(error, info)
throw addExampleValue(errorA, info)
const handleError = function ({
error,
input,
example,
prefix,
originalName,
hasInput,
test,
}) {
const isValidation = isValidateError(error)
const errorA = addPrefix({ error, prefix, originalName, isValidation })

if (!isValidation) {
return errorA
}

// eslint-disable-next-line fp/no-mutation
errorA.validation = true
const errorB = addCurrentValue(errorA, input, hasInput)
const errorC = addExampleValue({ error: errorB, example, hasInput, test })
return errorC
}

// Some methods are not called with any input
const callNoInputFunc = async function (func, info) {
try {
return await callUserFunc(func, info)
} catch (error) {
throw handleError(error, info)
}
// Consumers can distinguish users errors from system bugs by checking
// the `error.validation` boolean property.
// User errors are distinguished by having error message starting with "must".
// We fail on the first error, as opposed to aggregating all errors
// - Otherwise, a failed property might be used by another property, which
// would also appear as failed, even if it has no issues
// We detect this using the error message instead of the error class because:
// - It is simpler for users
// - It works both on browsers and in Node.js
// - It ensures the error message looks good
const isValidateError = function (error) {
return error instanceof Error && error.message.startsWith('must')
}

const handleError = function (error, info) {
handleValidateError(error)
return addPrefix(error, info)
const addPrefix = function ({ error, prefix, originalName, isValidation }) {
const prefixA = getPrefix(prefix, originalName)
const prefixB = isValidation ? prefixA : `${prefixA}: `
return wrapError(error, prefixB)
}

// The `prefix` is the name of the type of property to show in error
// message and warnings such as "Option".
export const getPrefix = function (prefix, originalName) {
return `${prefix} "${originalName}"`
}

// Add the current input value as error suffix
const addCurrentValue = function (error, input) {
return error.validation
? wrapErrorValue(error, 'Current value', input)
: error
const addCurrentValue = function (error, input, hasInput) {
return hasInput ? wrapErrorValue(error, 'Current value', input) : error
}

const addExampleValue = function (error, { example }) {
return error.validation && example !== undefined
// Add the example as error suffix
const addExampleValue = function ({ error, example, hasInput, test }) {
return example !== undefined && (hasInput || test !== undefined)
? wrapErrorValue(error, 'Example value', example)
: error
}
Expand All @@ -78,8 +100,3 @@ const serializeValue = function (value) {
const separator = valueStr.includes('\n') ? '\n' : ' '
return `${separator}${valueStr}`
}

// eslint-disable-next-line no-unused-vars
const callUserFunc = async function (func, { example, prefix, ...info }) {
return await maybeFunction(func, info)
}
7 changes: 5 additions & 2 deletions src/config/normalize/lib/keywords/skip.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { callInputFunc } from '../call.js'
import { callFunc } from '../call.js'

// `undefined` definitions are always skipped because:
// - It allows any keyword to be disabled by setting `definition` to
Expand Down Expand Up @@ -35,7 +35,10 @@ export const shouldSkipKeyword = async function ({
// need to check the input during `test()` but should not pass it during
// `main()` nor the definition function
const hasSkippedTest = async function (test, input, info) {
return test !== undefined && !(await callInputFunc(test, input, info))
return (
test !== undefined &&
!(await callFunc({ func: test, input, info, hasInput: true, test }))
)
}

// Function definitions returning `undefined` are skipped, unless
Expand Down
13 changes: 0 additions & 13 deletions src/config/normalize/lib/prefix.js

This file was deleted.

19 changes: 0 additions & 19 deletions src/config/normalize/lib/validate.js

This file was deleted.

10 changes: 7 additions & 3 deletions src/config/normalize/lib/warn.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { getPrefix } from './prefix.js'
import { getPrefix } from './call.js'

// When a new warning is returned, add it to the list
export const addWarning = function ({ warning }, warnings, info) {
export const addWarning = function (
{ warning },
warnings,
{ prefix, originalName },
) {
return warning === undefined
? warnings
: [...warnings, `${getPrefix(info)} ${warning}`]
: [...warnings, `${getPrefix(prefix, originalName)} ${warning}`]
}

// Log all warnings at the end.
Expand Down
4 changes: 0 additions & 4 deletions src/utils/function.js

This file was deleted.

0 comments on commit dc4fde5

Please sign in to comment.