diff --git a/README.md b/README.md index f60bd82..636b55a 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,8 @@ Output:

after

``` +You can also return a promise from either function and it will work fine. + #### `validateNode(node)` Given a single reshape AST node, checks it for formatting errors. Example: diff --git a/lib/index.js b/lib/index.js index f69f441..e04957e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,30 +1,43 @@ +const when = require('when') const UtilError = require('./error') exports.modifyNodes = function modifyNodes (tree, criteria, transform) { - return tree.reduce((m, node) => { - // bottom-up recurse - if (node.type === 'tag' && node.content) { - node.content = modifyNodes(node.content, criteria, transform) - } - - // if it doesn't match the criteria, move on - if (!criteria(node)) { m.push(node); return m } + return when.reduce(tree, (m, node) => { + // this will resolve immediately unless the node has children and needs to + // recurse, in which case it will wait for the recursion to finish before + // resolving + let maybeRecurse = when.resolve() - // if it does, run the user transform - const output = transform(node) - - // push the output into the tree if it's a valid type - if (Array.isArray(output)) { - m = m.concat(output) - } else if (typeof output === 'object') { - m.push(output) - } else if (!output) { - // no node added - } else { - throw new UtilError('invalid replacement node', output) + // bottom-up recurse if there is a tag with contents + if (node.type === 'tag' && node.content) { + maybeRecurse = modifyNodes(node.content, criteria, transform) + .tap((content) => { node.content = content }) } - return m + // after the recurse has finished if applicable, test the node for the user + // criteria and modify if applicable + return maybeRecurse.then(() => { + // run the criteria function (can be a promise) + return when.resolve(criteria(node)).then((processNode) => { + // if it doesn't match the criteria, move on + if (!processNode) { m.push(node); return m } + + // if it does, run the user transform (can be a promise) + return when.resolve(transform(node)).then((output) => { + // push the output into the tree if it's a valid type + if (Array.isArray(output)) { + m.push(...output) + } else if (typeof output === 'object') { + m.push(output) + } else if (!output) { + // no node added + } else { + throw new UtilError('invalid replacement node', output) + } + return m + }) + }) + }) }, []) } diff --git a/package.json b/package.json index 5689ccd..5c340d4 100644 --- a/package.json +++ b/package.json @@ -27,5 +27,8 @@ "coveralls": "nyc report --reporter=text-lcov | coveralls", "pretest": "standard | snazzy", "test": "nyc ava" + }, + "dependencies": { + "when": "^3.7.7" } } diff --git a/test/index.js b/test/index.js index 0de86c2..ddd79f3 100644 --- a/test/index.js +++ b/test/index.js @@ -42,6 +42,23 @@ test('modifyNodes errors when invalid return node provided', (t) => { .catch((err) => t.truthy(err.toString() === 'Error: invalid replacement node\nFrom: plugin-util\n\nNode: "foo"')) }) +test('modifyNodes works with promises', (t) => { + function plugin (tree) { + return util.modifyNodes(tree, (n) => { + return new Promise((resolve) => resolve(n.name === 'p')) + }, (node) => { + return new Promise((resolve) => { + node.content[0].content = 'replaced!' + resolve(node) + }) + }) + } + + return reshape({ plugins: plugin }) + .process(readFileSync(path.join(fixtures, 'basic.html'), 'utf8')) + .then((res) => t.truthy(res.output() === '
\n

replaced!

\n
\n')) +}) + test('validateNode', (t) => { util.validateNode({ type: 'tag',