Skip to content

Commit

Permalink
Validate plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed Jan 30, 2022
1 parent 6642302 commit 4c903e0
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 16 deletions.
44 changes: 40 additions & 4 deletions src/config/normalize/lib/definitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const applyDefinition = async function (
path = false,
cwd = '.',
glob = false,
required = false,
validate,
transform,
},
Expand All @@ -26,10 +27,17 @@ export const applyDefinition = async function (

const valueA = await addDefaultValue(value, defaultValue, opts)
const valueB = await computeValue(valueA, compute, opts)
const valueC = await resolvePath(valueB, { path, cwd, glob, opts })
await validateValue(valueC, validate, opts)
const valueD = await transformValue(valueC, transform, opts)
return valueD
const valueC = await applyValidateTransform({
value: valueB,
path,
cwd,
glob,
required,
validate,
transform,
opts,
})
return valueC
}

// Apply `pick(value, opts)` which omits the current value if `false` is
Expand Down Expand Up @@ -58,6 +66,34 @@ const computeValue = async function (value, compute, opts) {
return compute === undefined ? value : await callUserFunc(compute, opts)
}

const applyValidateTransform = async function ({
value,
path,
cwd,
glob,
required,
validate,
transform,
opts,
}) {
const valueA = await resolvePath(value, { path, cwd, glob, opts })
await validateRequired(valueA, required, opts)
await validateValue(valueA, validate, opts)
const valueB = await transformValue(valueA, transform, opts)
return valueB
}

// Apply `required(value, opts)` which throws if `true` and value is `undefined`
const validateRequired = async function (value, required, opts) {
if (value === undefined && (await callValueFunc(required, value, opts))) {
await callValidateFunc(throwRequired, value, opts)
}
}

const throwRequired = function () {
throw new Error('must be defined.')
}

// Apply `validate(value, opts)` which throws on validation errors
const validateValue = async function (value, validate, opts) {
if (value !== undefined && validate !== undefined) {
Expand Down
6 changes: 6 additions & 0 deletions src/config/normalize/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ const isJson = function (value) {
}
}

export const validateFunction = function (value) {
if (typeof value !== 'function') {
throw new TypeError('must be a function.')
}
}

export const validateFileExists = async function (value) {
if (!(await pathExists(value))) {
throw new Error('must be an existing file.')
Expand Down
2 changes: 2 additions & 0 deletions src/config/plugin/add.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const getPluginsByType = async function (
builtins,
topProps,
isCombinationDimension,
mainDefinitions,
},
config,
) {
Expand All @@ -52,6 +53,7 @@ const getPluginsByType = async function (
modulePrefix,
builtins,
isCombinationDimension,
mainDefinitions,
})
const pluginsA = addPluginsConfig({ plugins, config, configProp, topProps })
return [varName, pluginsA]
Expand Down
24 changes: 22 additions & 2 deletions src/config/plugin/load.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createRequire } from 'module'
import { PluginError, UserError } from '../../error/main.js'
import { wrapError } from '../../error/wrap.js'
import { PLUGINS_IMPORT_BASE } from '../normalize/cwd.js'
import { normalizeConfig } from '../normalize/main.js'

import { getModuleId } from './id.js'

Expand All @@ -13,10 +14,18 @@ export const loadPlugins = async function ({
modulePrefix,
builtins,
isCombinationDimension,
mainDefinitions,
}) {
return await Promise.all(
ids.map((id) =>
loadPlugin({ id, type, modulePrefix, builtins, isCombinationDimension }),
loadPlugin({
id,
type,
modulePrefix,
builtins,
isCombinationDimension,
mainDefinitions,
}),
),
)
}
Expand All @@ -27,10 +36,13 @@ const loadPlugin = async function ({
modulePrefix,
builtins,
isCombinationDimension,
mainDefinitions,
}) {
const moduleId = getModuleId(id, type, isCombinationDimension)
const plugin = await importPlugin({ moduleId, type, modulePrefix, builtins })
return { ...plugin, id }
const pluginA = { ...plugin }
const pluginB = await normalizePlugin(pluginA, mainDefinitions)
return { ...pluginB, id }
}

// Builtin modules are lazy loaded for performance reasons
Expand Down Expand Up @@ -82,3 +94,11 @@ This Node module was not found, please ensure it is installed.\n\n`,
)
}
}

// Validate a plugin has the correct shape and normalize it
const normalizePlugin = async function (plugin, mainDefinitions) {
return await normalizeConfig(plugin, mainDefinitions, {
context: {},
ErrorType: PluginError,
})
}
43 changes: 42 additions & 1 deletion src/config/plugin/types.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { DEFAULT_REPORTER_OUTPUT } from '../../report/contents/output.js'
import { getReportMethods } from '../../report/formats/list.js'
import {
BUILTIN_REPORTERS,
DEFAULT_REPORTERS,
} from '../../report/reporters/main.js'
import { BUILTIN_RUNNERS, DEFAULT_RUNNERS } from '../../runners/main.js'
import { normalizeOptionalArray } from '../normalize/transform.js'
import { validateEmptyArray } from '../normalize/validate.js'
import {
validateBoolean,
validateDefinedString,
validateObject,
validateEmptyArray,
validateFunction,
} from '../normalize/validate.js'

// All plugin types
export const PLUGIN_TYPES = {
Expand Down Expand Up @@ -34,6 +42,13 @@ export const PLUGIN_TYPES = {
selectPropDefinition: {
validate: validateEmptyArray,
},
mainDefinitions: [
{
name: 'launch',
required: true,
validate: validateFunction,
},
],
},
reporter: {
type: 'reporter',
Expand All @@ -60,6 +75,32 @@ export const PLUGIN_TYPES = {
return config.force ? [] : normalizeOptionalArray(value)
},
},
mainDefinitions: [
...getReportMethods().map((name) => ({
name,
validate: validateFunction,
})),
{
name: 'capabilities',
default: {},
validate: validateObject,
},
{
name: 'capabilities.debugStats',
default: false,
validate: validateBoolean,
},
{
name: 'capabilities.history',
default: false,
validate: validateBoolean,
},
{
name: 'defaultOutput',
default: DEFAULT_REPORTER_OUTPUT,
validate: validateDefinedString,
},
],
},
}

Expand Down
6 changes: 1 addition & 5 deletions src/report/config/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,12 @@ const addOutput = function (reporter) {
// `reporter.defaultOutput` is meant for reporters to define the default format
// and filename
const getOutput = function ({
defaultOutput = DEFAULT_OUTPUT,
defaultOutput,
config: { output = defaultOutput },
}) {
return output
}

const DEFAULT_OUTPUT = 'stdout'

// `reporter.tty` is `true` when output is interactive terminal
const getTty = function (output) {
return output === 'stdout' && isTtyOutput()
Expand All @@ -57,13 +55,11 @@ const shouldUseReporter = function ({ tty }, command) {
const addDefaultReporterConfig = function ({
tty,
config: { quiet = !tty, showDiff = tty, colors = tty, ...config },
capabilities: { debugStats = false, history = false } = {},
...reporter
}) {
return {
...reporter,
tty,
config: { ...config, quiet, showDiff, colors },
capabilities: { debugStats, history },
}
}
1 change: 1 addition & 0 deletions src/report/contents/output.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const isOutputPath = function (output) {
}

const OUTPUT_SPECIAL_VALUES = new Set(['stdout', 'external'])
export const DEFAULT_REPORTER_OUTPUT = 'stdout'

// Print result to file or to terminal based on the `output` configuration
// property.
Expand Down
9 changes: 9 additions & 0 deletions src/report/formats/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,12 @@ const getFormatPair = function (format) {
}

export const FORMATS = getFormats()

// Return all possible `report*()` methods
export const getReportMethods = function () {
return [...new Set(FORMATS_ARRAY.flatMap(getFormatReportMethods))]
}

const getFormatReportMethods = function ({ methods }) {
return methods
}
2 changes: 0 additions & 2 deletions src/runners/cli/main.js
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
export { launch } from './launch/main.js'

export const id = 'cli'
2 changes: 0 additions & 2 deletions src/runners/node/main.js
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
export { launch } from './launch/main.js'

export const id = 'node'

0 comments on commit 4c903e0

Please sign in to comment.