Skip to content

Commit

Permalink
Improve error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed May 29, 2022
1 parent c443f3b commit 194b531
Show file tree
Hide file tree
Showing 13 changed files with 63 additions and 41 deletions.
3 changes: 1 addition & 2 deletions src/config/normalize/lib/keywords/list/glob.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { basename } from 'path'
import fastGlob from 'fast-glob'
import { isNotJunk } from 'junk'

import { validateDefinedString } from '../../type.js'
import { normalizeBoolean } from '../normalize/common.js'
import { validateDefinedString, normalizeBoolean } from '../normalize/common.js'

const main = async function (definition, input, { cwd }) {
if (!definition) {
Expand Down
2 changes: 1 addition & 1 deletion src/config/normalize/lib/keywords/list/path/main.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { resolve } from 'path'

import { validateDefinedString } from '../../../type.js'
import { validateDefinedString } from '../../normalize/common.js'

import { validateAccess } from './access.js'
import { fileExists, validateExists } from './exist.js'
Expand Down
18 changes: 18 additions & 0 deletions src/config/normalize/lib/keywords/normalize/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,21 @@ export const normalizePropertyPath = function (definition) {
throw wrapError(error, 'must be a valid path:')
}
}

// Validate input is a non-empty string
export const validateDefinedString = function (value) {
validateString(value)
validateNonEmptyString(value)
}

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

const validateNonEmptyString = function (value) {
if (typeof value === 'string' && value.trim() === '') {
throw new Error('must not be an empty string.')
}
}
3 changes: 2 additions & 1 deletion src/config/normalize/lib/keywords/normalize/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { inspect } from 'util'

import moize from 'moize'

import { DefinitionError } from '../../error.js'
import { BUILTIN_KEYWORDS } from '../list/main.js'

import { validateKeywords } from './validate.js'
Expand All @@ -17,7 +18,7 @@ const addCustomKeywords = function (keywords) {
}

if (!Array.isArray(keywords)) {
throw new TypeError(
throw new DefinitionError(
`Option "keywords" must be an array: ${inspect(keywords)}`,
)
}
Expand Down
13 changes: 7 additions & 6 deletions src/config/normalize/lib/keywords/normalize/name.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@ import { inspect } from 'util'

import isPlainObj from 'is-plain-obj'

import { KeywordError, DefinitionError } from '../../error.js'
import { CORE_PROPS_SET } from '../../rule.js'
import { BUILTIN_KEYWORDS } from '../list/main.js'

// Validate `keyword.name` and `keyword.aliases[*]`
export const validateName = function (name, parent, prefix) {
if (name === undefined) {
throw new TypeError(`${prefix} must be defined: ${inspect(parent)}`)
throw new KeywordError(`${prefix} must be defined: ${inspect(parent)}`)
}

if (!isValidName(name)) {
throw new TypeError(`${prefix} must be a non-empty string: ${name}`)
throw new KeywordError(`${prefix} must be a non-empty string: ${name}`)
}

if (!NAME_REGEXP.test(name)) {
throw new TypeError(
throw new KeywordError(
`${prefix} must only contain lowercase letters: "${name}"`,
)
}
Expand All @@ -33,11 +34,11 @@ const NAME_REGEXP = /^[a-z]+$/u
// Do not allow redefining builtin keywords
export const validateNotBuiltin = function ({ name }) {
if (BUILTIN_NAMES.has(name)) {
throw new TypeError('must not be a builtin keyword.')
throw new DefinitionError('must not be a builtin keyword.')
}

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

Expand All @@ -64,6 +65,6 @@ export const validateDuplicateKeyword = function (keywordA, indexA, keywords) {
)

if (isDuplicate) {
throw new TypeError('must not be passed twice.')
throw new DefinitionError('must not be passed twice.')
}
}
10 changes: 6 additions & 4 deletions src/config/normalize/lib/keywords/normalize/props.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { inspect } from 'util'

import { KeywordError } from '../../error.js'

import {
validateName,
validateNotBuiltin,
Expand Down Expand Up @@ -27,7 +29,7 @@ const REQUIRED_PROPS = ['main']

const validateKeywordProp = function (keyword, propName) {
if (keyword[propName] === undefined) {
throw new TypeError(`"${propName}" must be defined.`)
throw new KeywordError(`"${propName}" must be defined.`)
}
}

Expand All @@ -37,7 +39,7 @@ const validateAliases = function ({ aliases }) {
}

if (!Array.isArray(aliases)) {
throw new TypeError('"aliases" property must be an array.')
throw new KeywordError('"aliases" property must be an array.')
}

aliases.forEach((alias) => {
Expand Down Expand Up @@ -66,7 +68,7 @@ const validateOptionalProp = function (keyword, propName, typeName) {

// eslint-disable-next-line valid-typeof
if (value !== undefined && typeof value !== typeName) {
throw new TypeError(
throw new KeywordError(
`"${propName}" property must be a ${typeName}: ${inspect(value)}`,
)
}
Expand All @@ -79,7 +81,7 @@ const validateProps = function (keywords) {
const validateProp = function (propName) {
if (!VALID_PROPS.has(propName)) {
const propNames = [...VALID_PROPS].join(', ')
throw new TypeError(
throw new KeywordError(
`"${propName}" property must be one of the following instead:\n${propNames}`,
)
}
Expand Down
5 changes: 4 additions & 1 deletion src/config/normalize/lib/keywords/normalize/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { inspect } from 'util'
import isPlainObj from 'is-plain-obj'

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

import { validateName } from './name.js'
import { validateKeywordProps } from './props.js'
Expand All @@ -14,7 +15,9 @@ export const validateKeywords = function (keywords) {

const validateKeyword = function (keyword, index, keywords) {
if (!isPlainObj(keyword)) {
throw new TypeError(`Keyword must be a plain object: ${inspect(keyword)}`)
throw new DefinitionError(
`Keyword must be a plain object: ${inspect(keyword)}`,
)
}

validateName(keyword.name, keyword, 'Keyword "name" property')
Expand Down
3 changes: 2 additions & 1 deletion src/config/normalize/lib/keywords/path.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { normalizePath } from 'wild-wild-parser'

import { wrapError } from '../../../../error/wrap.js'
import { KeywordError } from '../error.js'
import { addMove } from '../move.js'

// Keywords can give a hint on where a value has moved by returning a `path`
Expand All @@ -23,6 +24,6 @@ const safeNormalizePath = function (path) {
try {
return normalizePath(path)
} catch (error) {
throw wrapError(error, 'The "path" is invalid:')
throw wrapError(error, 'The "path" is invalid:', KeywordError)
}
}
3 changes: 2 additions & 1 deletion src/config/normalize/lib/keywords/rename.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { serializePath, normalizePath } from 'wild-wild-parser'
import { remove } from 'wild-wild-path'

import { wrapError } from '../../../../error/wrap.js'
import { KeywordError } from '../error.js'
import { addMove } from '../move.js'

import { setInputs } from './set.js'
Expand Down Expand Up @@ -43,6 +44,6 @@ const safeNormalizePath = function (rename) {
try {
return normalizePath(rename)
} catch (error) {
throw wrapError(error, 'The "rename" path is invalid:')
throw wrapError(error, 'The "rename" path is invalid:', KeywordError)
}
}
11 changes: 7 additions & 4 deletions src/config/normalize/lib/normalize.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { normalizeQuery } from 'wild-wild-parser'

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

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

// Validate and normalize rules.
Expand All @@ -17,13 +18,13 @@ export const normalizeRules = function (rules, all, ruleProps) {

const validateRules = function (rules) {
if (!Array.isArray(rules)) {
throw new TypeError(`Rules must be an array: ${inspect(rules)}`)
throw new DefinitionError(`Rules must be an array: ${inspect(rules)}`)
}
}

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

validateRuleProps(rule, ruleProps, 'Rule')
Expand All @@ -33,12 +34,14 @@ const normalizeRule = function (rule, all, ruleProps) {

const normalizeName = function (rule) {
if (rule.name === undefined) {
throw new Error(`Rule must have a "name" property: ${inspect(rule)}`)
throw new DefinitionError(
`Rule must have a "name" property: ${inspect(rule)}`,
)
}

try {
return normalizeQuery(rule.name)
} catch (error) {
throw wrapError(error, 'Invalid "name":')
throw wrapError(error, 'Invalid "name":', DefinitionError)
}
}
13 changes: 10 additions & 3 deletions src/config/normalize/lib/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import { inspect } from 'util'
import filterObj from 'filter-obj'
import isPlainObj from 'is-plain-obj'

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

// Normalize `options`
export const normalizeOpts = function (options = {}) {
if (!isPlainObj(options)) {
throw new TypeError(`Options must be a plain object: ${inspect(options)}`)
throw new DefinitionError(
`Options must be a plain object: ${inspect(options)}`,
)
}

const { soft = false, all, keywords } = options
Expand All @@ -22,7 +25,9 @@ export const normalizeOpts = function (options = {}) {

const validateSoft = function (soft) {
if (typeof soft !== 'boolean') {
throw new TypeError(`Option "soft" must be a boolean: ${inspect(soft)}`)
throw new DefinitionError(
`Option "soft" must be a boolean: ${inspect(soft)}`,
)
}
}

Expand All @@ -32,7 +37,9 @@ const normalizeAll = function (all, ruleProps) {
}

if (!isPlainObj(all)) {
throw new TypeError(`Option "all" must be a plain object: ${inspect(all)}`)
throw new DefinitionError(
`Option "all" must be a plain object: ${inspect(all)}`,
)
}

const allA = filterObj(all, isDefined)
Expand Down
4 changes: 3 additions & 1 deletion src/config/normalize/lib/rule.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { inspect } from 'util'

import { DefinitionError } from './error.js'

// Retrieve the list of possible rule properties
export const getRuleProps = function (keywords) {
return new Set([...CORE_PROPS, ...keywords.map(getKeywordName)])
Expand Down Expand Up @@ -34,7 +36,7 @@ const validateRuleProp = function ({

// eslint-disable-next-line fp/no-mutating-methods
const rulePropsA = [...ruleProps].sort().join(', ')
throw new Error(
throw new DefinitionError(
`${message}'s "${ruleProp}" property must be valid: ${inspect(definitions)}
It must be one of the following values instead:
${rulePropsA}
Expand Down
16 changes: 0 additions & 16 deletions src/config/normalize/lib/type.js

This file was deleted.

0 comments on commit 194b531

Please sign in to comment.