Skip to content
This repository has been archived by the owner on Nov 3, 2019. It is now read-only.

Commit

Permalink
fix(plugin-example): make the example plugin work with async code
Browse files Browse the repository at this point in the history
Execute the examples in child processes in order to be able to determine when they truly end.

fix #11
  • Loading branch information
zkochan committed Apr 1, 2016
1 parent 1231690 commit f9bf056
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 52 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ mos test --tap | tap-nyan
- [async-regex-replace](https://github.com/pmarkert/async-regex-replace): regex replacements using asynchronous callback functions
- [callsites](https://github.com/sindresorhus/callsites): Get callsites from the V8 stack trace API
- [chalk](https://github.com/chalk/chalk): Terminal string styling done right. Much color.
- [cross-spawn-async](https://github.com/IndigoUnited/node-cross-spawn-async): Cross platform child_process#spawn
- [github-url-to-object](https://github.com/zeke/github-url-to-object): Extract user, repo, and other interesting properties from GitHub URLs
- [glob](https://github.com/isaacs/node-glob): a little globber
- [meow](https://github.com/sindresorhus/meow): CLI app helper
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"async-regex-replace": "1.0.2",
"callsites": "1.0.0",
"chalk": "1.1.3",
"cross-spawn-async": "2.1.9",
"github-url-to-object": "2.2.1",
"glob": "7.0.3",
"meow": "^3.7.0",
Expand Down
43 changes: 43 additions & 0 deletions plugins/mos-plugin-example/lib/hook-console-log.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use strict'
module.exports = hookConsoleLog

const callsites = require('callsites')
const removeLastEOL = require('../../remove-last-eol')

const originalLog = console.log

function hookConsoleLog (filePath) {
console.log = function () {
const site = callsiteForFile(filePath)

originalLog({
message: getRealConsoleOutput.apply(null, arguments),
line: site.line,
})
}
}

function getRealConsoleOutput () {
const originalWrite = process.stdout.write

let message
process.stdout.write = msg => { message = msg }

originalLog.apply(console, arguments)

process.stdout.write = originalWrite

return removeLastEOL(message)
}

function callsiteForFile (fileName) {
const stack = trace()
return stack.find(callsite => callsite.file === fileName)
}

function trace () {
return callsites().map(callsite => ({
file: callsite.getFileName() || '?',
line: callsite.getLineNumber(),
}))
}
2 changes: 2 additions & 0 deletions plugins/mos-plugin-example/lib/hook-console-log.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
'use strict'
require('./hook-console-log')
96 changes: 44 additions & 52 deletions plugins/mos-plugin-example/lib/stdout-to-comments.js
Original file line number Diff line number Diff line change
@@ -1,72 +1,64 @@
'use strict'
module.exports = stdoutToComments

const removeLastEOL = require('../../remove-last-eol')
const fs = require('fs')
const callsites = require('callsites')
const spawn = require('cross-spawn-async')
const path = require('path')
const hookPath = path.resolve(__dirname, './hook-console-log')

function stdoutToComments (filePath) {
return new Promise((resolve, reject) => {
fs.readFile(filePath, 'utf8', (err, content) => {
if (err) return reject(err)

const originalLog = console.log
const outputs = []

console.log = function () {
const site = callsiteForFile(filePath)

outputs.push({
message: getRealConsoleOutput.apply(null, arguments),
line: site.line,
})
}

function getRealConsoleOutput () {
const originalWrite = process.stdout.write

let message
process.stdout.write = msg => { message = msg }

originalLog.apply(console, arguments)
const tmpFileName = filePath + Math.random() + '.js'
fs.writeFileSync(tmpFileName, addHook({
code: content,
filePath: tmpFileName,
}), 'utf8')

process.stdout.write = originalWrite
const outputs = []
let failed = false

return removeLastEOL(message)
}
const cp = spawn('node', [tmpFileName])
cp.stdout.setEncoding('utf8')
cp.stderr.setEncoding('utf8')
cp.stdout.on('data', data => {
try {
eval(`outputs.push(${data})`) // eslint-disable-line no-eval
} catch (err) {
failed = true
reject(err)
}
})
cp.stderr.on('data', data => console.error(data))
cp.on('close', code => {
fs.unlinkSync(tmpFileName)

try {
require(filePath)
} catch (err) {
throw err
} finally {
console.log = originalLog
}
if (failed) {
return
}

resolve(content.split('\n').reduce((contentLines, line, index) => {
contentLines.push(line)
resolve(content.split('\n').reduce((contentLines, line, index) => {
contentLines.push(line)

const lineNo = index + 1
while (outputs.length && outputs[0].line === lineNo) {
const matches = (contentLines[contentLines.length - 1] || '').match(/^(\s*)/)
const linePadding = matches && matches[0] || ''
contentLines.push(linePadding + '//> ' +
outputs.shift().message.replace(/\r?\n/g, '\n' + linePadding + '// '))
}
return contentLines
}, []).join('\n'))
const lineNo = index + 1
while (outputs.length && outputs[0].line === lineNo) {
const matches = (contentLines[contentLines.length - 1] || '').match(/^(\s*)/)
const linePadding = matches && matches[0] || ''
contentLines.push(linePadding + '//> ' +
outputs.shift().message.replace(/\r?\n/g, '\n' + linePadding + '// '))
}
return contentLines
}, []).join('\n'))
})
})
})
}

function callsiteForFile (fileName) {
const stack = trace()
return stack.find(callsite => callsite.file === fileName)
}

function trace () {
return callsites().map(callsite => ({
file: callsite.getFileName() || '?',
line: callsite.getLineNumber(),
}))
function addHook (opts) {
if (!opts.code.match(/['"]use strict['"]/)) {
return `require('${hookPath}')('${opts.filePath}');` + opts.code
}
return `'use strict';require('${hookPath}')('${opts.filePath}');` + opts.code
}
7 changes: 7 additions & 0 deletions plugins/mos-plugin-example/lib/stdout-to-comments.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ describe('stdoutToComments', () => {
)
})

it('should add the console output to the comments when the code executed asynchronously', () => {
return inlineStdoutToComments('setTimeout(() => console.log("Hello world!"), 0)')
.then(actual =>
expect(actual).to.eq('setTimeout(() => console.log("Hello world!"), 0)\n//> Hello world!')
)
})

it('should add the multiline console output to the comments', () => {
return inlineStdoutToComments('console.log("Hello world!\\nHello world!")')
.then(actual =>
Expand Down

0 comments on commit f9bf056

Please sign in to comment.