Skip to content

Commit

Permalink
Renaming
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed Mar 13, 2022
1 parent b9a463f commit cfa3cf4
Show file tree
Hide file tree
Showing 15 changed files with 189 additions and 185 deletions.
16 changes: 8 additions & 8 deletions src/config/normalize/lib/wild_wild_path/get.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,26 @@ import { iterate } from './iterate/main.js'
// Retrieve all properties in `target` matching a query string.
// Unlike `get|has()` it also return missing entries, letting consumers filter
// them or not.
export const list = function (target, queryOrPaths) {
return [...iterate(target, queryOrPaths)]
export const list = function (target, query) {
return [...iterate(target, query)]
}

// Retrieve a single property's value in `target` matching a query string.
// Wildcards can be used, but only the first value is returned.
export const get = function (target, queryOrPaths) {
const entry = find(target, queryOrPaths)
export const get = function (target, query) {
const entry = find(target, query)
return entry === undefined ? undefined : entry.value
}

// Check if a property is not missing according to a query
export const has = function (target, queryOrPaths) {
return find(target, queryOrPaths) !== undefined
export const has = function (target, query) {
return find(target, query) !== undefined
}

// Find the first non-missing entry
const find = function (target, queryOrPaths) {
const find = function (target, query) {
// eslint-disable-next-line fp/no-loops
for (const { value, missing } of iterate(target, queryOrPaths)) {
for (const { value, missing } of iterate(target, query)) {
// eslint-disable-next-line max-depth
if (!missing) {
return { value }
Expand Down
12 changes: 6 additions & 6 deletions src/config/normalize/lib/wild_wild_path/iterate/duplicate.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ const isNotDuplicate = function (entryA, index, entries) {
}

const isDuplicate = function (
{ simplePath: simplePathA, path: pathA },
{ simplePath: simplePathB, path: pathB },
{ path: pathA, queryArray: queryArrayA },
{ path: pathB, queryArray: queryArrayB },
) {
return (
fastEqualsSimple(simplePathA, simplePathB) &&
pathA.length === pathB.length &&
pathA.every(
fastEqualsSimple(pathA, pathB) &&
queryArrayA.length === queryArrayB.length &&
queryArrayA.every(
(tokenA, index) =>
index < simplePathA.length || isSameToken(tokenA, pathB[index]),
index < pathA.length || isSameToken(tokenA, queryArrayB[index]),
)
)
}
8 changes: 4 additions & 4 deletions src/config/normalize/lib/wild_wild_path/iterate/expand.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { getObjectTokenType } from '../tokens/main.js'
// However, iteration is guaranteed to return child entries before parent ones.
// - This is useful for recursive logic which must often be applied in a
// specific parent-child order
export const expandPath = function ({ path, value, simplePath }, index) {
const token = path[index]
export const expandToken = function ({ queryArray, value, path }, index) {
const token = queryArray[index]
const {
tokenType,
missing: missingParent,
Expand All @@ -15,9 +15,9 @@ export const expandPath = function ({ path, value, simplePath }, index) {
const childEntries = tokenType.iterate(valueA, token)
return childEntries.map(
({ value: childValue, prop, missing: missingEntry }) => ({
path,
queryArray,
value: childValue,
simplePath: [...simplePath, prop],
path: [...path, prop],
missing: missingParent || missingEntry,
}),
)
Expand Down
30 changes: 14 additions & 16 deletions src/config/normalize/lib/wild_wild_path/iterate/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,19 @@ import { parse } from '../parsing/parse.js'
import { serialize } from '../parsing/serialize.js'

import { removeDuplicates } from './duplicate.js'
import { expandPath } from './expand.js'
import { expandToken } from './expand.js'

// Iterate over all values (and their associated path) matching a specific
// query for on specific target value.
// Uses an iterator:
// - To allow consumers to return only the first matching entry quickly
// - To keep memory consumption low even on big queries
export const iterate = function* (
target,
queryOrPaths,
{ childFirst = false } = {},
) {
const paths = parse(queryOrPaths)
const entries = paths.map((path) => ({
path,
export const iterate = function* (target, query, { childFirst = false } = {}) {
const queryArrays = parse(query)
const entries = queryArrays.map((queryArray) => ({
queryArray,
value: target,
simplePath: [],
path: [],
missing: false,
}))
yield* iterateLevel(entries, childFirst, 0)
Expand All @@ -41,10 +37,12 @@ const iterateLevel = function* (entries, childFirst, index) {
}

const getParentEntries = function (entries, index) {
return entries.filter(({ path }) => path.length === index).map(normalizeEntry)
return entries
.filter(({ queryArray }) => queryArray.length === index)
.map(normalizeEntry)
}

const normalizeEntry = function ({ value, simplePath: path, missing }) {
const normalizeEntry = function ({ value, path, missing }) {
const query = serialize(path)
return { value, path, query, missing }
}
Expand All @@ -61,8 +59,8 @@ const iterateChildEntries = function* (
}

const childEntries = entries
.filter(({ path }) => path.length !== index)
.flatMap((entry) => expandPath(entry, index))
.filter(({ queryArray }) => queryArray.length !== index)
.flatMap((entry) => expandToken(entry, index))

if (childEntries.length === 0) {
return
Expand All @@ -87,6 +85,6 @@ const iterateChildren = function* (childEntries, childFirst, index) {
}
}

const getLastProp = function ({ simplePath }) {
return simplePath[simplePath.length - 1]
const getLastProp = function ({ path }) {
return path[path.length - 1]
}
59 changes: 33 additions & 26 deletions src/config/normalize/lib/wild_wild_path/parsing/compare.js
Original file line number Diff line number Diff line change
@@ -1,57 +1,64 @@
import { getObjectTokenType } from '../tokens/main.js'

import { validateSimplePath } from './normalize.js'
import { validatePath } from './normalize.js'
import { parse } from './parse.js'

// Check if two queries are equal.
// Works with:
// - Normalization, e.g. `:` === `0:`
// - Unions, e.g. `a b` === `b a`
// - Duplicates, e.g. `a a` === `a`
export const equals = function (queryOrPathsA, queryOrPathsB) {
const pathsA = parse(queryOrPathsA)
const pathsB = parse(queryOrPathsB)
export const equals = function (queryA, queryB) {
const queryArraysA = parse(queryA)
const queryArraysB = parse(queryB)
return (
pathsA.length === pathsB.length &&
pathsA.every((pathA) => hasSamePath(pathsB, pathA)) &&
pathsB.every((pathB) => hasSamePath(pathsA, pathB))
queryArraysA.length === queryArraysB.length &&
queryArraysA.every((queryArrayA) =>
hasSameQueryArray(queryArraysB, queryArrayA),
) &&
queryArraysB.every((queryArrayB) =>
hasSameQueryArray(queryArraysA, queryArrayB),
)
)
}

const hasSamePath = function (paths, pathA) {
return paths.some((pathB) => isSamePath(pathA, pathB))
const hasSameQueryArray = function (queryArrays, queryArrayA) {
return queryArrays.some((queryArrayB) =>
isSameQueryArray(queryArrayA, queryArrayB),
)
}

const isSamePath = function (pathA, pathB) {
const isSameQueryArray = function (queryArrayA, queryArrayB) {
return (
pathA.length === pathB.length &&
pathA.every((tokenA, index) => isSameToken(tokenA, pathB[index]))
queryArrayA.length === queryArrayB.length &&
queryArrayA.every((tokenA, index) =>
isSameToken(tokenA, queryArrayB[index]),
)
)
}

// Check if two simple paths are equal
export const equalsSimple = function (simplePathA, simplePathB) {
validateSimplePath(simplePathA)
validateSimplePath(simplePathB)
return fastEqualsSimple(simplePathA, simplePathB)
// Check if two paths are equal
export const equalsSimple = function (pathA, pathB) {
validatePath(pathA)
validatePath(pathB)
return fastEqualsSimple(pathA, pathB)
}

// Same as `equalsSimple()` but without validation
export const fastEqualsSimple = function (simplePathA, simplePathB) {
export const fastEqualsSimple = function (pathA, pathB) {
return (
simplePathA.length === simplePathB.length &&
simplePathA.every((prop, index) => isSameProp(simplePathB[index], prop))
pathA.length === pathB.length &&
pathA.every((prop, index) => isSameProp(pathB[index], prop))
)
}

// Check if a simple path is a parent to another
export const parent = function (parentSimplePath, childSimplePath) {
// Check if a path is a parent to another
export const parent = function (parentPath, childPath) {
return (
childSimplePath.length > parentSimplePath.length &&
childSimplePath.every(
childPath.length > parentPath.length &&
childPath.every(
(childToken, index) =>
index >= parentSimplePath.length ||
isSameProp(childToken, parentSimplePath[index]),
index >= parentPath.length || isSameProp(childToken, parentPath[index]),
)
)
}
Expand Down
69 changes: 37 additions & 32 deletions src/config/normalize/lib/wild_wild_path/parsing/normalize.js
Original file line number Diff line number Diff line change
@@ -1,65 +1,67 @@
import { inspect } from 'util'

import { getObjectTokenType, getSObjectTokenType } from '../tokens/main.js'
import { getObjectTokenType, getPathObjectTokenType } from '../tokens/main.js'

// Most methods accept both query and path syntaxes.
// Most methods accept both query and array syntaxes.
// This checks which one is used.
export const isQueryString = function (queryOrPaths) {
return typeof queryOrPaths === 'string'
export const isQueryString = function (query) {
return typeof query === 'string'
}

// Simple paths are a subset of paths which uses:
// Paths are a subset of query arrays which use:
// - No unions
// - Only prop tokens, and array tokens (positive only)
// Those are the ones exposed in output, as opposed to normal paths which are
// Those are the ones exposed in output, as opposed to query arrays which are
// exposed in input.
export const validateSimplePath = function (simplePath) {
if (!Array.isArray(simplePath)) {
throwPathError(simplePath, 'It must be an array.')
export const validatePath = function (path) {
if (!Array.isArray(path)) {
throwQueryArraysError(path, 'It must be an array.')
}

simplePath.forEach((prop) => {
validateProp(prop, simplePath)
path.forEach((prop) => {
validateProp(prop, path)
})
}

const validateProp = function (prop, simplePath) {
if (getSObjectTokenType(prop) === undefined) {
const validateProp = function (prop, path) {
if (getPathObjectTokenType(prop) === undefined) {
throwTokenError(
simplePath,
path,
prop,
'It must be a property name (string) or an array index (positive integer).',
)
}
}

// Normalize paths of tokens
export const normalizePaths = function (paths) {
validatePaths(paths)
const pathsA = paths.every(Array.isArray) ? paths : [paths]
return pathsA.map(normalizePath)
// Normalize query arrays
export const normalizeQueryArrays = function (queryArrays) {
validateQueryArrays(queryArrays)
const queryArraysA = queryArrays.every(Array.isArray)
? queryArrays
: [queryArrays]
return queryArraysA.map(normalizeQueryArray)
}

const validatePaths = function (paths) {
if (!Array.isArray(paths)) {
throwPathError(paths, 'It must be an array.')
const validateQueryArrays = function (queryArrays) {
if (!Array.isArray(queryArrays)) {
throwQueryArraysError(queryArrays, 'It must be an array.')
}
}

const normalizePath = function (path) {
return path.map((token) => normalizeToken(token, path))
const normalizeQueryArray = function (queryArray) {
return queryArray.map((token) => normalizeToken(token, queryArray))
}

const normalizeToken = function (token, path) {
const normalizeToken = function (token, queryArray) {
const tokenType = getObjectTokenType(token)
validateToken(tokenType, token, path)
validateToken(tokenType, token, queryArray)
return tokenType.normalize(token)
}

const validateToken = function (tokenType, token, path) {
const validateToken = function (tokenType, token, queryArray) {
if (tokenType === undefined) {
throwTokenError(
path,
queryArray,
token,
`It must be one of the following:
- a property name string
Expand All @@ -71,10 +73,13 @@ const validateToken = function (tokenType, token, path) {
}
}

const throwPathError = function (path, message) {
throw new Error(`Invalid path: ${inspect(path)}\n${message}`)
const throwQueryArraysError = function (queryArray, message) {
throw new Error(`Invalid query: ${inspect(queryArray)}\n${message}`)
}

const throwTokenError = function (path, token, message) {
throwPathError(path, `Invalid token: ${inspect(token)}\n${message}`)
const throwTokenError = function (queryArray, token, message) {
throwQueryArraysError(
queryArray,
`Invalid token: ${inspect(token)}\n${message}`,
)
}
Loading

0 comments on commit cfa3cf4

Please sign in to comment.