-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
181 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,54 @@ | ||
import { iterateQuery } from './query.js' | ||
import { iterateChildEntries } from './path_children.js' | ||
|
||
// `iterate()` logic when the query is path | ||
export const iteratePath = function* (target, pathArray, opts) { | ||
yield* iterateQuery(target, [pathArray], opts) | ||
const entries = getRootEntries(target, pathArray) | ||
yield* iterateLevel(entries, 0, opts) | ||
} | ||
|
||
const getRootEntries = function (target, pathArray) { | ||
return [{ queryArray: pathArray, value: target, path: [], missing: false }] | ||
} | ||
|
||
// The `roots` option can be used to only include the highest ancestors. | ||
// The `leaves` option can be used to only include the lowest descendants. | ||
// Neither option includes the values in-between. | ||
const iterateLevel = function* (entries, index, opts) { | ||
const parentEntry = getParentEntry(entries, index) | ||
|
||
if (shouldYieldParentFirst(parentEntry, opts)) { | ||
yield normalizeEntry(parentEntry, opts) | ||
} | ||
|
||
const hasChildren = yield* iterateChildEntries({ | ||
entries, | ||
parentEntry, | ||
index, | ||
opts, | ||
iterateLevel, | ||
}) | ||
|
||
if (shouldYieldParentLast(parentEntry, hasChildren, opts)) { | ||
yield normalizeEntry(parentEntry, opts) | ||
} | ||
} | ||
|
||
const getParentEntry = function (entries, index) { | ||
return entries.find(({ queryArray }) => queryArray.length === index) | ||
} | ||
|
||
const normalizeEntry = function ({ value, path, missing }, { entries }) { | ||
return entries ? { value, path, missing } : value | ||
} | ||
|
||
const shouldYieldParentFirst = function (parentEntry, { childFirst }) { | ||
return parentEntry !== undefined && !childFirst | ||
} | ||
|
||
const shouldYieldParentLast = function ( | ||
parentEntry, | ||
hasChildren, | ||
{ childFirst, leaves }, | ||
) { | ||
return parentEntry !== undefined && childFirst && !(leaves && hasChildren) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { expandTokens } from './path_expand.js' | ||
|
||
// Iterate over child entries | ||
export const iterateChildEntries = function* ({ | ||
entries, | ||
parentEntry, | ||
index, | ||
opts, | ||
iterateLevel, | ||
}) { | ||
if (!shouldIterateChildren(entries, parentEntry, opts)) { | ||
return false | ||
} | ||
|
||
// eslint-disable-next-line fp/no-let | ||
let hasChildren = false | ||
|
||
// eslint-disable-next-line fp/no-loops | ||
for (const childEntry of iterateChildren({ | ||
entries, | ||
index, | ||
opts, | ||
iterateLevel, | ||
})) { | ||
// eslint-disable-next-line fp/no-mutation | ||
hasChildren = true | ||
yield childEntry | ||
} | ||
|
||
return hasChildren | ||
} | ||
|
||
const shouldIterateChildren = function (entries, parentEntry, { roots }) { | ||
return parentEntry === undefined || (entries.length !== 1 && !roots) | ||
} | ||
|
||
const iterateChildren = function* ({ entries, index, opts, iterateLevel }) { | ||
const childEntries = expandTokens(entries, index, opts) | ||
|
||
if (childEntries.length === 0) { | ||
return | ||
} | ||
|
||
yield* iterateLevel(childEntries, index + 1, opts) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { handleMissingValue } from './path_missing.js' | ||
|
||
// Expand special tokens like *, **, regexps, slices into property names or | ||
// indices for a given value | ||
export const expandTokens = function (entries, index, opts) { | ||
return entries | ||
.filter(({ queryArray }) => queryArray.length !== index) | ||
.flatMap((entry) => expandToken(entry, index, opts)) | ||
} | ||
|
||
// Use the token to list entries against a target value. | ||
const expandToken = function ({ queryArray, value, path }, index, opts) { | ||
const token = queryArray[index] | ||
const missingReturn = handleMissingValue(value, token, opts.classes) | ||
const childEntriesA = iterateToken(token, missingReturn, opts) | ||
return childEntriesA | ||
.filter(isAllowedProp) | ||
.map(({ value: childValue, prop, missing: missingEntry }) => ({ | ||
queryArray, | ||
value: childValue, | ||
path: [...path, prop], | ||
missing: missingReturn.missing || missingEntry, | ||
})) | ||
} | ||
|
||
const isAllowedProp = function ({ prop }) { | ||
return !FORBIDDEN_PROPS.has(prop) | ||
} | ||
|
||
// Forbidden to avoid prototype pollution attacks | ||
const FORBIDDEN_PROPS = new Set(['__proto__', 'prototype', 'constructor']) | ||
|
||
const iterateToken = function ( | ||
token, | ||
{ missing: missingParent, value }, | ||
{ inherited, missing: includeMissing }, | ||
) { | ||
if (includeMissing) { | ||
return iterate(value, token, inherited) | ||
} | ||
|
||
if (missingParent) { | ||
return [] | ||
} | ||
|
||
const childEntries = iterate(value, token, inherited) | ||
return childEntries.filter(isNotMissing) | ||
} | ||
|
||
const iterate = function (value, token) { | ||
return [{ value: value[token], prop: token, missing: !(token in value) }] | ||
} | ||
|
||
const isNotMissing = function ({ missing }) { | ||
return !missing | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { getTokenType } from '../tokens/main.js' | ||
|
||
import { isWeakObject } from './object.js' | ||
|
||
export const handleMissingValue = function (value, token, classes) { | ||
const tokenType = getTokenType(token) | ||
const { isPresent, getDefaultValue } = MISSING_HANDLERS[tokenType.valueType] | ||
const missing = !isPresent(value, classes) | ||
const valueA = missing ? getDefaultValue() : value | ||
return { missing, value: valueA } | ||
} | ||
|
||
const getDefaultObject = function () { | ||
return {} | ||
} | ||
|
||
const getDefaultArray = function () { | ||
return [] | ||
} | ||
|
||
const MISSING_HANDLERS = { | ||
array: { | ||
isPresent: Array.isArray, | ||
getDefaultValue: getDefaultArray, | ||
}, | ||
weakObject: { | ||
isPresent: isWeakObject, | ||
getDefaultValue: getDefaultObject, | ||
}, | ||
} |