Skip to content

Commit

Permalink
Allow using array of functions
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed Feb 27, 2022
1 parent 5bd9a47 commit 01bb8f5
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 46 deletions.
4 changes: 2 additions & 2 deletions src/config/normalize/lib/apply.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const applyRule = async function (
value: valueC,
name,
newNames,
warning,
warnings,
} = await validateAndModify({
value: valueB,
path,
Expand All @@ -45,7 +45,7 @@ export const applyRule = async function (
rename,
opts,
})
return { value: valueC, name, newNames, warning }
return { value: valueC, name, newNames, warnings }
}

// Apply `pick(value, opts)` which omits the current value if `false` is
Expand Down
6 changes: 3 additions & 3 deletions src/config/normalize/lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getOpts } from './opts.js'
import { list } from './prop_path/get.js'
import { set, remove } from './prop_path/set.js'
import { normalizeRule } from './rule.js'
import { addWarning, logWarnings } from './warn.js'
import { addWarnings, logWarnings } from './warn.js'

// Normalize configuration shape and do custom validation.
// An array of rule objects is passed.
Expand Down Expand Up @@ -76,11 +76,11 @@ const applyPropRule = async function (
value: newValue,
name: newName = name,
newNames = [],
warning,
warnings: newWarnings,
} = await applyRule(rule, value, opts)
const configA = setConfigValue({ config, name, newName, newValue })
const movesA = addMoves(moves, newNames, name)
const warningsA = addWarning(warnings, warning)
const warningsA = addWarnings(warnings, newWarnings)
return { config: configA, moves: movesA, warnings: warningsA }
}

Expand Down
14 changes: 9 additions & 5 deletions src/config/normalize/lib/modify.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { callValueFunc, callUserFunc, getValidateExampleError } from './call.js'
import { resolvePath } from './path.js'
import { has } from './prop_path/get.js'
import { transformValue } from './transform.js'
import { getWarning } from './warn.js'
import { getWarnings } from './warn.js'

// Once the initial value has been computed, apply validation and transforms,
// unless the value is `undefined`.
Expand All @@ -24,15 +24,15 @@ export const validateAndModify = async function ({

const valueA = await resolvePath({ value, path, glob, opts })
await validateValue(valueA, validate, opts)
const warning = await getWarning(valueA, warn, opts)
const warnings = await getWarnings(valueA, warn, opts)
const { value: valueB, newName } = await transformValue(
valueA,
transform,
opts,
)
const name = await renameProp(valueB, rename, opts)
const newNames = [newName, name].filter(Boolean)
return { value: valueB, name, newNames, warning }
return { value: valueB, name, newNames, warnings }
}

// Apply `required(opts)` which throws if `true` and value is `undefined`
Expand All @@ -44,9 +44,13 @@ const validateRequired = async function (required, value, opts) {

// Apply `validate(value, opts)` which throws on validation errors
const validateValue = async function (value, validate, opts) {
if (validate !== undefined) {
await callValueFunc(validate, value, opts)
if (validate === undefined) {
return
}

await Promise.all(
validate.map((validateFunc) => callValueFunc(validateFunc, value, opts)),
)
}

// Apply `rename(value, opts)` which transforms the property's name.
Expand Down
17 changes: 14 additions & 3 deletions src/config/normalize/lib/rule.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export const normalizeRule = function (
rules,
) {
const exampleA = addDefaultExample(example, name, rules)
const validateA = normalizeOptionalArray(validate)
const warnA = normalizeOptionalArray(warn)
const transformA = normalizeOptionalArray(transform)
return {
name,
pick,
Expand All @@ -31,13 +34,21 @@ export const normalizeRule = function (
glob,
required,
example: exampleA,
validate,
warn,
transform,
validate: validateA,
warn: warnA,
transform: transformA,
rename,
}
}

// For convenience, some rule methods which are functions can be array of
// functions too.
// We do not do this on functions returning booleans since it would be ambiguous
// whether they should use a union or an intersection.
const normalizeOptionalArray = function (value) {
return value === undefined || Array.isArray(value) ? value : [value]
}

// `required` defaults to `false` except for array items.
// This validates against sparse arrays by default, since they are usually
// unwanted.
Expand Down
58 changes: 36 additions & 22 deletions src/config/normalize/lib/transform.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import isPlainObj from 'is-plain-obj'
import pReduce from 'p-reduce'

import { callValueFunc } from './call.js'

Expand All @@ -9,18 +10,37 @@ export const transformValue = async function (value, transform, opts) {
return { value }
}

const transformReturn = await callValueFunc(transform, value, opts)

const { name } = opts.funcOpts
const { value: valueA, newProps } = await pReduce(
transform,
(memo, transformFunc) => transformSingleValue(memo, transformFunc, opts),
{ value, newProps: [] },
)
const newName = getNewName(newProps, opts)
return { value: valueA, newName }
}

if (isTransformMove(transformReturn)) {
return getTransformMove(transformReturn, name)
}
const transformSingleValue = async function (
{ value, newProps },
transformFunc,
opts,
) {
const { value: valueA, newProp } = await getTransformedValue(
value,
transformFunc,
opts,
)
const newPropsA = newProp === undefined ? newProps : [newProp, ...newProps]
return { value: valueA, newProps: newPropsA }
}

const commonMoveReturn = applyCommonMoves(transformReturn, value, name)
return commonMoveReturn === undefined
? { value: transformReturn }
: commonMoveReturn
const getTransformedValue = async function (value, transformFunc, opts) {
const transformReturn = await callValueFunc(transformFunc, value, opts)
return isTransformMove(transformReturn)
? transformReturn
: {
value: transformReturn,
newProp: findCommonMove(transformReturn, value),
}
}

// `transform()` can return a `{ newProp, value }` object to indicate the
Expand All @@ -37,20 +57,10 @@ const isTransformMove = function (transformReturn) {
)
}

const getTransformMove = function ({ newProp, value }, name) {
return { newName: `${name}.${newProp}`, value }
}

// Automatically detect some common type of moves
const applyCommonMoves = function (newValue, oldValue, name) {
const findCommonMove = function (newValue, oldValue) {
const commonMove = COMMON_MOVES.find(({ test }) => test(newValue, oldValue))

if (commonMove === undefined) {
return
}

const newProp = commonMove.getNewProp(newValue)
return { newName: `${name}.${newProp}`, value: newValue }
return commonMove === undefined ? undefined : commonMove.getNewProp(newValue)
}

const COMMON_MOVES = [
Expand Down Expand Up @@ -81,3 +91,7 @@ const COMMON_MOVES = [
},
},
]

const getNewName = function (newProps, { funcOpts: { name } }) {
return newProps.length === 0 ? undefined : [name, ...newProps].join('.')
}
17 changes: 10 additions & 7 deletions src/config/normalize/lib/warn.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,27 @@ import { callValueFunc } from './call.js'
import { getPrefix } from './validate.js'

// Apply `warn(value, opts)` which can return a string to print as warning
export const getWarning = async function (value, warn, opts) {
export const getWarnings = async function (value, warn, opts) {
if (warn === undefined) {
return
}

const warningSuffix = await callValueFunc(warn, value, opts)
const warningSuffixes = await Promise.all(
warn.map((warnFunc) => callValueFunc(warnFunc, value, opts)),
)
const warningSuffixesA = warningSuffixes.filter(Boolean)

if (warningSuffix === undefined) {
if (warningSuffixesA.length === 0) {
return
}

const prefix = getPrefix(opts)
return `${prefix} ${warningSuffix}`
return warningSuffixesA.map((warningSuffix) => `${prefix} ${warningSuffix}`)
}

// When a new warning was returned, add it to the list
export const addWarning = function (warnings, warning) {
return warning === undefined ? warnings : [...warnings, warning]
// When new warnings are returned, add them to the list
export const addWarnings = function (warnings, newWarnings) {
return newWarnings === undefined ? warnings : [...warnings, ...newWarnings]
}

// Log all warnings at the end.
Expand Down
5 changes: 1 addition & 4 deletions src/config/normalize/rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,7 @@ const cwd = {
pick: amongCommands(['dev', 'remove', 'run', 'show']),
default: getCwd,
path: true,
async validate(value) {
await validateFileExists(value)
await validateDirectory(value)
},
validate: [validateFileExists, validateDirectory],
}

const delta = {
Expand Down

0 comments on commit 01bb8f5

Please sign in to comment.