Skip to content

Commit

Permalink
handle custom childKey
Browse files Browse the repository at this point in the history
  • Loading branch information
doodlewind committed Jan 8, 2018
1 parent f30c043 commit d3d24fa
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 103 deletions.
117 changes: 15 additions & 102 deletions src/bumpover/index.js
Original file line number Diff line number Diff line change
@@ -1,103 +1,15 @@
import { deepEqual } from 'assert'
import { Rules, getRule } from '../rules'
import { Options } from '../options'

// Result items can be array, object or null.
// Flatten results to array of objects.
function sanitizeResults (maybeResults) {
const results = maybeResults
.map(result => Array.isArray(result) ? result : [result])
.reduce((a, b) => [...a, ...b], [])
.filter(result => !!result)
return results
}

// Validate node with possible struct provided in rules.
function validateNode (node, struct) {
if (!struct) return node
try { return struct(node) } catch (e) { throw e }
}

// Result can be array, object or null. Unify its shape to result struct.
function resolveResult (node, result, struct, childKey) {
if (!result) {
return { action: 'stop', newNode: null }
} else if (Array.isArray(result)) {
return {
action: 'next',
newNode: {
...validateNode(node, struct),
[childKey]: sanitizeResults(result)
}
}
} else {
// Provide default action.
const { action = 'next', node } = result
const newNode = validateNode(node, struct)
return { action, newNode }
}
}

function bumpChildren (node, rules, options, bumpFn, resolve, reject) {
const { childKey } = options
// Outlet for leaf node.
if (!node) {
resolve(null)
return
}

if (!Array.isArray(node[childKey])) {
resolve(node)
return
}

const children = node[childKey]
const childPromises = children.map(bumpFn)
const bumpAll = Promise.all(childPromises)
bumpAll.then(results => {
resolve({ ...node, [childKey]: sanitizeResults(results) })
}).catch(reject)
}

function bumpIgnoredNode (node, rule, options, bumpFn, resolve, reject) {
const { childKey } = options
// Resolve null if the ignored node is leaf.
if (!node || !node[childKey]) {
resolve(null)
return
}
// Resolve array of results.
const children = node[childKey]
const childPromises = children.map(bumpFn)
const bumpAll = Promise.all(childPromises)
bumpAll.then(results => {
resolve(sanitizeResults(results))
}).catch(reject)
}

function bumpRoot (node, options, bumpFn, resolve, reject) {
const { childKey, serializer, defaultValue } = options
if (!node) {
resolve(defaultValue)
return
}

if (!Array.isArray(node[childKey])) {
resolve(serializer(node) || defaultValue)
return
}

const children = node[childKey]
const childPromises = children.map(bumpFn)
const bumpChildren = Promise.all(childPromises)
bumpChildren.then(results => {
const output = serializer({
...node,
[childKey]: sanitizeResults(results)
})
resolve(output || defaultValue)
}).catch(reject)
}
import {
bumpChildren,
bumpIgnoredNode,
bumpRoot
} from './traverse'
import {
getChildKey,
resolveResult
} from './utils'

export class Bumpover {
constructor (rules = [], options = {}) {
Expand Down Expand Up @@ -126,7 +38,7 @@ export class Bumpover {
if (!rule) {
const { ignoreUnknown, onUnmatch } = options
if (ignoreUnknown) {
bumpIgnoredNode(node, rule, options, bumpNode, resolve, reject)
bumpIgnoredNode(node, options, bumpNode, resolve, reject)
return
} else {
onUnmatch(node)
Expand All @@ -137,7 +49,7 @@ export class Bumpover {
}

rule.update(node).then(result => {
const { childKey } = options
const childKey = getChildKey(node, rules, options)
const { action, newNode } = resolveResult(
node, result, rule.struct, childKey
)
Expand Down Expand Up @@ -170,16 +82,17 @@ export class Bumpover {
if (ignoreUnknown) resolve(defaultValue)
else {
this.options.onUnmatch(rootNode)
bumpRoot(rootNode, options, bumpNode, resolve, reject)
bumpRoot(rootNode, [], options, bumpNode, resolve, reject)
}
} else {
rule.update(rootNode).then(result => {
const { childKey, serializer, defaultValue } = options
const { serializer, defaultValue } = options
const childKey = getChildKey(rootNode, rules, options)
const { action, newNode } = resolveResult(
rootNode, result, rule.struct, childKey
)
if (action === 'next') {
bumpRoot(newNode, options, bumpNode, resolve, reject)
bumpRoot(newNode, rules, options, bumpNode, resolve, reject)
} else if (action === 'stop') {
resolve(serializer(newNode) || defaultValue)
} else reject(new Error(`Unknown action:\n${action}`))
Expand Down
70 changes: 70 additions & 0 deletions src/bumpover/traverse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import {
getChildKey,
sanitizeResults
} from './utils'

// Recursively bump node.
export function bumpChildren (node, rules, options, bumpFn, resolve, reject) {
const childKey = getChildKey(node, rules, options)
// Outlet for leaf node.
if (!node) {
resolve(null)
return
}

if (!Array.isArray(node[childKey])) {
resolve(node)
return
}

const children = node[childKey]
const childPromises = children.map(bumpFn)
const bumpAll = Promise.all(childPromises)
bumpAll.then(results => {
resolve({ ...node, [childKey]: sanitizeResults(results) })
}).catch(reject)
}

// Recursively bump node. Since ignored nodes doesn't have their `rules`,
// we don't pass in `rules` here, other args remains the same.
export function bumpIgnoredNode (node, options, bumpFn, resolve, reject) {
const childKey = getChildKey(node, [], options)
// Resolve null if the ignored node is leaf.
if (!node || !node[childKey]) {
resolve(null)
return
}
// Resolve array of results.
const children = node[childKey]
const childPromises = children.map(bumpFn)
const bumpAll = Promise.all(childPromises)
bumpAll.then(results => {
resolve(sanitizeResults(results))
}).catch(reject)
}

// Recursively bump root node.
export function bumpRoot (node, rules, options, bumpFn, resolve, reject) {
const { serializer, defaultValue } = options
const childKey = getChildKey(node, rules, options)
if (!node) {
resolve(defaultValue)
return
}

if (!Array.isArray(node[childKey])) {
resolve(serializer(node) || defaultValue)
return
}

const children = node[childKey]
const childPromises = children.map(bumpFn)
const bumpChildren = Promise.all(childPromises)
bumpChildren.then(results => {
const output = serializer({
...node,
[childKey]: sanitizeResults(results)
})
resolve(output || defaultValue)
}).catch(reject)
}
44 changes: 44 additions & 0 deletions src/bumpover/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { getRule } from '../rules'

// Validate node with possible struct provided in rules.
function validateNode (node, struct) {
if (!struct) return node
try { return struct(node) } catch (e) { throw e }
}

// Result items can be array, object or null.
// Flatten results to array of objects.
export function sanitizeResults (maybeResults) {
const results = maybeResults
.map(result => Array.isArray(result) ? result : [result])
.reduce((a, b) => [...a, ...b], [])
.filter(result => !!result)
return results
}

// Get child key by rules and options
export function getChildKey (node, rules, options) {
const rule = getRule(node, rules)
if (rule && rule.childKey) return rule.childKey
else return options.childKey
}

// Result can be array, object or null. Unify its shape to result struct.
export function resolveResult (node, result, struct, childKey) {
if (!result) {
return { action: 'stop', newNode: null }
} else if (Array.isArray(result)) {
return {
action: 'next',
newNode: {
...validateNode(node, struct),
[childKey]: sanitizeResults(result)
}
}
} else {
// Provide default action.
const { action = 'next', node } = result
const newNode = validateNode(node, struct)
return { action, newNode }
}
}
3 changes: 2 additions & 1 deletion src/rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { struct } from 'superstruct'
const Rule = struct({
match: 'function',
update: 'function',
struct: 'function?'
struct: 'function?',
childKey: 'string?'
})

export const Rules = struct([Rule])
Expand Down
64 changes: 64 additions & 0 deletions test/bumpover/rule.js
Original file line number Diff line number Diff line change
Expand Up @@ -377,3 +377,67 @@ test('invalid action on node', async t => {

await t.throws(bumper.bump(input))
})

test('custom child key', async t => {
const input = {
name: 'div',
props: {},
children: [
{
name: 'span',
props: {},
foo: [
{ name: 'span', props: {}, children: [] },
{ name: 'small', props: {}, children: [] },
{ name: 'span', props: {}, children: [] }
]
}
]
}

const expected = {
name: 'div',
props: {},
children: [
{
name: 'span',
props: {
fontSize: '16px'
},
foo: [
{
name: 'span',
props: {
fontSize: '16px'
},
children: []
},
{ name: 'small', props: {}, children: [] },
{
name: 'span',
props: {
fontSize: '16px'
},
children: []
}
]
}
]
}

const rules = [
{
match: ({ name }) => name === 'span',
update: (node) => new Promise((resolve, reject) => {
resolve({
node: { ...node, props: { fontSize: '16px' } }
})
}),
childKey: 'foo'
}
]

const bumper = new Bumpover(rules)

return bumper.bump(input).then(actual => t.deepEqual(actual, expected))
})

0 comments on commit d3d24fa

Please sign in to comment.