Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ Output:
<p>after</p>
```

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:
Expand Down
55 changes: 34 additions & 21 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -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
})
})
})
}, [])
}

Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,8 @@
"coveralls": "nyc report --reporter=text-lcov | coveralls",
"pretest": "standard | snazzy",
"test": "nyc ava"
},
"dependencies": {
"when": "^3.7.7"
}
}
17 changes: 17 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() === '<div class="wow">\n <p>replaced!</p>\n</div>\n'))
})

test('validateNode', (t) => {
util.validateNode({
type: 'tag',
Expand Down