diff --git a/README.md b/README.md index 3210cf6..d45af2e 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/package.json b/package.json index a1903bd..bfd576d 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/plugins/mos-plugin-example/lib/hook-console-log.js b/plugins/mos-plugin-example/lib/hook-console-log.js new file mode 100644 index 0000000..0f4f944 --- /dev/null +++ b/plugins/mos-plugin-example/lib/hook-console-log.js @@ -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(), + })) +} diff --git a/plugins/mos-plugin-example/lib/hook-console-log.spec.js b/plugins/mos-plugin-example/lib/hook-console-log.spec.js new file mode 100644 index 0000000..52d6c44 --- /dev/null +++ b/plugins/mos-plugin-example/lib/hook-console-log.spec.js @@ -0,0 +1,2 @@ +'use strict' +require('./hook-console-log') diff --git a/plugins/mos-plugin-example/lib/stdout-to-comments.js b/plugins/mos-plugin-example/lib/stdout-to-comments.js index c71a09a..a9ff7e9 100644 --- a/plugins/mos-plugin-example/lib/stdout-to-comments.js +++ b/plugins/mos-plugin-example/lib/stdout-to-comments.js @@ -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 } diff --git a/plugins/mos-plugin-example/lib/stdout-to-comments.spec.js b/plugins/mos-plugin-example/lib/stdout-to-comments.spec.js index 8fcae8d..57fcc1b 100644 --- a/plugins/mos-plugin-example/lib/stdout-to-comments.spec.js +++ b/plugins/mos-plugin-example/lib/stdout-to-comments.spec.js @@ -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 =>