Skip to content

Commit

Permalink
Add keyword async methods
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed May 29, 2022
1 parent e01d45c commit aee497d
Show file tree
Hide file tree
Showing 11 changed files with 138 additions and 27 deletions.
22 changes: 14 additions & 8 deletions src/config/normalize/lib/call/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@ import { handleError } from './error.js'
// - This is because `test()` is called before definition function, which might
// return `undefined`, i.e. might skip the keyword
// - Inputs must be validated in `main()` instead
export const callTest = async function ({ test, input, info, keyword }) {
export const callTest = async function ({
test,
testSync,
input,
info,
keyword,
}) {
return await callFunc({
func: test,
input,
info,
hasInput: true,
keyword,
sync: false,
sync: testSync,
errorType: 'keyword',
bugType: 'keyword',
})
Expand Down Expand Up @@ -51,19 +57,19 @@ export const callDefinition = async function ({
// Other exceptions are considered keyword bugs.
export const callNormalize = async function ({
normalize,
normalizeSync,
definition,
info,
keyword,
exampleDefinition,
}) {
const func = () => normalize(definition)
return await callFunc({
func,
func: () => normalize(definition),
info,
keyword,
definition,
exampleDefinition,
sync: false,
sync: normalizeSync,
hasInput: false,
errorType: 'definition',
bugType: 'keyword',
Expand All @@ -75,6 +81,7 @@ export const callNormalize = async function ({
// Other exceptions are considered keyword bugs.
export const callMain = async function ({
main,
mainSync,
normalizedDefinition,
definition,
input,
Expand All @@ -83,16 +90,15 @@ export const callMain = async function ({
test,
keyword,
}) {
const func = main.bind(undefined, normalizedDefinition)
return await callFunc({
func,
func: main.bind(undefined, normalizedDefinition),
input,
info,
hasInput,
test,
keyword,
definition,
sync: false,
sync: mainSync,
errorType: 'input',
bugType: 'keyword',
})
Expand Down
2 changes: 2 additions & 0 deletions src/config/normalize/lib/keywords/definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const callDefinitionFunc = async function ({
export const normalizeDefinition = async function ({
definition,
normalize,
normalizeSync,
info,
keyword,
exampleDefinition,
Expand All @@ -58,6 +59,7 @@ export const normalizeDefinition = async function ({
? definition
: await callNormalize({
normalize,
normalizeSync,
definition,
info,
keyword,
Expand Down
4 changes: 2 additions & 2 deletions src/config/normalize/lib/keywords/list/cwd.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { stat } from 'fs/promises'
import { resolve } from 'path'
import { cwd as getCwd } from 'process'

const normalize = async function (definition) {
const normalizeAsync = async function (definition) {
await validateCwd(definition)
return resolve(definition)
}
Expand Down Expand Up @@ -39,6 +39,6 @@ export default {
name: 'cwd',
undefinedInput: true,
exampleDefinition: getCwd(),
normalize,
normalizeAsync,
main,
}
4 changes: 2 additions & 2 deletions src/config/normalize/lib/keywords/list/glob.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { isNotJunk } from 'junk'

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

const main = async function (definition, input, { cwd }) {
const mainAsync = async function (definition, input, { cwd }) {
if (!definition) {
return
}
Expand Down Expand Up @@ -37,5 +37,5 @@ export default {
hasInput: true,
exampleDefinition: true,
normalize: normalizeBoolean,
main,
mainAsync,
}
4 changes: 2 additions & 2 deletions src/config/normalize/lib/keywords/list/path/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { fileExists, validateExists } from './exist.js'
import { normalize, EXAMPLE_KEYWORDS } from './normalize.js'
import { validateType } from './type.js'

const main = async function (keywords, input, { cwd }) {
const mainAsync = async function (keywords, input, { cwd }) {
validateDefinedString(input)
const inputA = resolve(cwd, input)
await validateFile(inputA, keywords)
Expand Down Expand Up @@ -38,5 +38,5 @@ export default {
hasInput: true,
exampleDefinition: EXAMPLE_KEYWORDS,
normalize,
main,
mainAsync,
}
6 changes: 6 additions & 0 deletions src/config/normalize/lib/keywords/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,15 @@ const applyKeyword = async function ({
name: keyword,
aliases,
test,
testSync,
hasInput = false,
undefinedInput = false,
undefinedDefinition = false,
exampleDefinition,
normalize,
normalizeSync,
main,
mainSync,
},
state,
state: { input, info },
Expand All @@ -104,6 +107,7 @@ const applyKeyword = async function ({
input,
undefinedInput,
test,
testSync,
info,
keyword,
})
Expand All @@ -129,12 +133,14 @@ const applyKeyword = async function ({
const normalizedDefinition = await normalizeDefinition({
definition: definitionA,
normalize,
normalizeSync,
info,
keyword,
exampleDefinition,
})
const returnValue = await callMain({
main,
mainSync,
normalizedDefinition,
definition: definitionA,
input,
Expand Down
73 changes: 73 additions & 0 deletions src/config/normalize/lib/keywords/normalize/async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { DefinitionError } from '../../error.js'

// When the top mode is sync, async methods in keywords are forbidden.
// When the top mode is async, both sync|async methods in keywords are allowed,
// but async are preferred for performance reasons.
// Keywords can define the same method both sync|async to allow using it in
// sync mode, while still getting performance benefits in async mode.
export const normalizeAsyncMethods = function (keyword, sync) {
return METHOD_NAMES.reduce(
(keywordA, methodName) => normalizeAsyncMethod(sync, methodName, keywordA),
keyword,
)
}

// Keyword methods which can be either sync or async
const METHOD_NAMES = ['test', 'normalize', 'main']

// Normalize `test[Async]()` (and so on) into only `test()` together with a
// `testSync` boolean property
const normalizeAsyncMethod = function (
sync,
methodName,
{
[methodName]: syncMethod,
[`${methodName}${ASYNC_SUFFIX}`]: asyncMethod,
...keyword
},
) {
if (syncMethod === undefined && asyncMethod === undefined) {
return keyword
}

const syncProp = `${methodName}${SYNC_SUFFIX}`
return sync
? requireSyncMethod({ methodName, syncMethod, keyword, syncProp })
: preferAsyncMethod({
methodName,
syncMethod,
asyncMethod,
keyword,
syncProp,
})
}

const ASYNC_SUFFIX = 'Async'
const SYNC_SUFFIX = 'Sync'

const requireSyncMethod = function ({
methodName,
syncMethod,
keyword,
syncProp,
}) {
if (syncMethod === undefined) {
throw new DefinitionError(
`The "async: true" option must be used because they keyword "${keyword.name}" is async.`,
)
}

return { ...keyword, [methodName]: syncMethod, [syncProp]: true }
}

const preferAsyncMethod = function ({
methodName,
syncMethod,
asyncMethod,
keyword,
syncProp,
}) {
return asyncMethod === undefined
? { ...keyword, [methodName]: syncMethod, [syncProp]: true }
: { ...keyword, [methodName]: asyncMethod, [syncProp]: false }
}
20 changes: 13 additions & 7 deletions src/config/normalize/lib/keywords/normalize/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import moize from 'moize'
import { DefinitionError } from '../../error.js'
import { BUILTIN_KEYWORDS } from '../list/main.js'

import { normalizeAsyncMethods } from './async.js'
import { validateKeywords } from './validate.js'

// Normalize and validate `options.keywords`
export const normalizeKeywords = function (keywords) {
return addCustomKeywords(keywords).map(normalizeKeyword)
export const normalizeKeywords = function (keywords, sync) {
return addCustomKeywords(keywords).map((keyword) =>
normalizeKeyword(keyword, sync),
)
}

const addCustomKeywords = function (keywords) {
Expand All @@ -27,11 +30,12 @@ const addCustomKeywords = function (keywords) {
return [...BUILTIN_KEYWORDS, ...keywords]
}

const normalizeKeyword = function (keyword) {
const normalizeKeyword = function (keyword, sync) {
const keywordA = normalizeAsyncMethods(keyword, sync)
return {
...DEFAULT_VALUES,
...keyword,
normalize: memoizeNormalize(keyword.normalize),
...keywordA,
normalize: memoizeNormalize(keywordA),
}
}

Expand All @@ -43,8 +47,10 @@ const DEFAULT_VALUES = {

// `keyword.normalize()` must be a pure function, because it is memoized for
// performance reasons.
const memoizeNormalize = function (normalize) {
return normalize === undefined ? normalize : moize(normalize, MOIZE_OPTS)
const memoizeNormalize = function ({ normalize, normalizeSync }) {
return normalize === undefined
? normalize
: moize(normalize, { ...MOIZE_OPTS, isPromise: !normalizeSync })
}

const MOIZE_OPTS = { isSerialized: true, maxSize: 1 }
9 changes: 8 additions & 1 deletion src/config/normalize/lib/keywords/normalize/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,14 @@ const validateFunctions = function (keyword) {
})
}

const FUNCTION_PROPS = ['test', 'normalize', 'main']
const FUNCTION_PROPS = [
'test',
'testAsync',
'normalize',
'normalizeAsync',
'main',
'mainAsync',
]

const validateBooleans = function (keyword) {
BOOLEAN_PROPS.forEach((propName) => {
Expand Down
16 changes: 13 additions & 3 deletions src/config/normalize/lib/keywords/skip.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ export const shouldSkipKeyword = async function ({
input,
undefinedInput,
test,
testSync,
info,
keyword,
}) {
return (
definition === undefined ||
(!undefinedInput && input === undefined) ||
(await hasSkippedTest({ test, input, info, keyword }))
(await hasSkippedTest({ test, testSync, input, info, keyword }))
)
}

Expand All @@ -35,8 +36,17 @@ export const shouldSkipKeyword = async function ({
// specific value (e.g. `undefined` for `required|default` keywords), i.e.
// need to check the input during `test()` but should not pass it during
// `main()` not the definition function
const hasSkippedTest = async function ({ test, input, info, keyword }) {
return test !== undefined && !(await callTest({ test, input, info, keyword }))
const hasSkippedTest = async function ({
test,
testSync,
input,
info,
keyword,
}) {
return (
test !== undefined &&
!(await callTest({ test, testSync, input, info, keyword }))
)
}

// Function definitions returning `undefined` are skipped, unless
Expand Down
5 changes: 3 additions & 2 deletions src/config/normalize/lib/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ export const normalizeOpts = function (options = {}) {
}

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

const validateSoft = function (soft) {
Expand Down

0 comments on commit aee497d

Please sign in to comment.