Skip to content

Commit

Permalink
Validate keywords
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed May 29, 2022
1 parent 4136c7a commit e0813af
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 9 deletions.
5 changes: 5 additions & 0 deletions src/config/normalize/lib/keywords/normalize/name.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { inspect } from 'util'

import isPlainObj from 'is-plain-obj'

import { CORE_PROPS_SET } from '../../rule.js'
import { BUILTIN_KEYWORDS } from '../list/main.js'

// Validate `keyword.name` and `keyword.aliases[*]`
Expand Down Expand Up @@ -34,6 +35,10 @@ export const validateNotBuiltin = function ({ name }) {
if (BUILTIN_NAMES.has(name)) {
throw new TypeError('must not be a builtin keyword.')
}

if (CORE_PROPS_SET.has(name)) {
throw new TypeError('must not be a core property.')
}
}

const getBuiltinNames = function () {
Expand Down
4 changes: 2 additions & 2 deletions src/config/normalize/lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import { logWarnings } from './warn.js'
// - Makes it clear to users what the order is
// TODO: abstract this function to its own library
export const normalizeInputs = async function (inputs, rules, opts) {
const { soft, all, keywords } = normalizeOpts(opts)
const rulesA = normalizeRules(rules, all)
const { soft, all, keywords, ruleProps } = normalizeOpts(opts)
const rulesA = normalizeRules(rules, all, ruleProps)

try {
const { inputs: inputsA, warnings } = await pReduce(
Expand Down
9 changes: 6 additions & 3 deletions src/config/normalize/lib/normalize.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import { normalizeQuery } from 'wild-wild-parser'

import { wrapError } from '../../../error/wrap.js'

import { validateRuleProps } from './rule.js'

// Validate and normalize rules.
// All methods and properties that use queries can use either the string or the
// path syntax.
export const normalizeRules = function (rules, all) {
export const normalizeRules = function (rules, all, ruleProps) {
validateRules(rules)
return rules.map((rule) => normalizeRule(rule, all))
return rules.map((rule) => normalizeRule(rule, all, ruleProps))
}

const validateRules = function (rules) {
Expand All @@ -19,11 +21,12 @@ const validateRules = function (rules) {
}
}

const normalizeRule = function (rule, all) {
const normalizeRule = function (rule, all, ruleProps) {
if (!isPlainObj(rule)) {
throw new TypeError(`Rule must be a plain object: ${inspect(rule)}`)
}

validateRuleProps(rule, ruleProps, 'Rule')
const ruleA = all === undefined ? rule : { ...all, ...rule }
return { ...ruleA, name: normalizeName(ruleA) }
}
Expand Down
12 changes: 8 additions & 4 deletions src/config/normalize/lib/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import filterObj from 'filter-obj'
import isPlainObj from 'is-plain-obj'

import { normalizeKeywords } from './keywords/normalize/main.js'
import { getRuleProps, validateRuleProps } from './rule.js'

// Normalize `options`
export const normalizeOpts = function (options = {}) {
Expand All @@ -13,9 +14,10 @@ export const normalizeOpts = function (options = {}) {

const { soft = false, all, keywords } = options
validateSoft(soft)
const allA = normalizeAll(all)
const keywordsA = normalizeKeywords(keywords)
return { soft, all: allA, keywords: keywordsA }
const ruleProps = getRuleProps(keywordsA)
const allA = normalizeAll(all, ruleProps)
return { soft, all: allA, keywords: keywordsA, ruleProps }
}

const validateSoft = function (soft) {
Expand All @@ -24,7 +26,7 @@ const validateSoft = function (soft) {
}
}

const normalizeAll = function (all) {
const normalizeAll = function (all, ruleProps) {
if (all === undefined) {
return
}
Expand All @@ -33,7 +35,9 @@ const normalizeAll = function (all) {
throw new TypeError(`Option "all" must be a plain object: ${inspect(all)}`)
}

return filterObj(all, isDefined)
const allA = filterObj(all, isDefined)
validateRuleProps(allA, ruleProps, 'Option "all"')
return allA
}

const isDefined = function (key, value) {
Expand Down
44 changes: 44 additions & 0 deletions src/config/normalize/lib/rule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { inspect } from 'util'

// Retrieve the list of possible rule properties
export const getRuleProps = function (keywords) {
return new Set([...CORE_PROPS, ...keywords.map(getKeywordName)])
}

const getKeywordName = function ({ name }) {
return name
}

// Rule properties that are not keywords
const CORE_PROPS = ['name']
export const CORE_PROPS_SET = new Set(CORE_PROPS)

// Validate that a `definitions` object has only allowed properties
export const validateRuleProps = function (definitions, ruleProps, message) {
// `definitions` is a plain object, i.e. does not have inherited properties
// eslint-disable-next-line fp/no-loops, guard-for-in
for (const ruleProp in definitions) {
validateRuleProp({ ruleProp, ruleProps, message, definitions })
}
}

const validateRuleProp = function ({
ruleProp,
ruleProps,
message,
definitions,
}) {
if (ruleProps.has(ruleProp)) {
return
}

// eslint-disable-next-line fp/no-mutating-methods
const rulePropsA = [...ruleProps].sort().join(', ')
throw new Error(
`${message}'s "${ruleProp}" property must be valid: ${inspect(definitions)}
It must be one of the following values instead:
${rulePropsA}
Did you misspell "${ruleProp}"?
If "${ruleProp}" is not misspelled, its keyword must be passed to the "keywords" option.`,
)
}

0 comments on commit e0813af

Please sign in to comment.