diff --git a/bin/src/logging.js b/bin/src/logging.js index 33b84afdf2d..16dd8b532f7 100644 --- a/bin/src/logging.js +++ b/bin/src/logging.js @@ -8,40 +8,33 @@ const errorSymbol = process.stderr.isTTY ? `🚨 ` : `Error: `; // log to stderr to keep `rollup main.js > bundle.js` from breaking export const stderr = console.error.bind( console ); // eslint-disable-line no-console -export function handleWarning ( warning ) { - stderr( `${warnSymbol}${chalk.bold( warning.message )}` ); +function log ( object, symbol ) { + const message = object.plugin ? `(${object.plugin} plugin) ${object.message}` : object.message; + + stderr( `${symbol}${chalk.bold( message )}` ); - if ( warning.url ) { - stderr( chalk.cyan( warning.url ) ); + if ( object.url ) { + stderr( chalk.cyan( object.url ) ); } - if ( warning.loc ) { - stderr( `${relativeId( warning.loc.file )} (${warning.loc.line}:${warning.loc.column})` ); + if ( object.loc ) { + stderr( `${relativeId( object.loc.file )} (${object.loc.line}:${object.loc.column})` ); + } else if ( object.id ) { + stderr( relativeId( object.id ) ); } - if ( warning.frame ) { - stderr( chalk.dim( warning.frame ) ); + if ( object.frame ) { + stderr( chalk.dim( object.frame ) ); } stderr( '' ); } -export function handleError ( err, recover ) { - stderr( `${errorSymbol}${chalk.bold( err.message )}` ); - - if ( err.url ) { - stderr( chalk.cyan( err.url ) ); - } - - if ( err.loc ) { - stderr( `${relativeId( err.loc.file )} (${err.loc.line}:${err.loc.column})` ); - } - - if ( err.frame ) { - stderr( chalk.dim( err.frame ) ); - } - - stderr( '' ); +export function handleWarning ( warning ) { + log( warning, warnSymbol ); +} +export function handleError ( err, recover ) { + log( err, errorSymbol ); if ( !recover ) process.exit( 1 ); } diff --git a/src/Bundle.js b/src/Bundle.js index 879c525e127..babb887cb0f 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -293,7 +293,7 @@ export default class Bundle { return this.cachedModules.get( id ); } - return transform( source, id, this.plugins ); + return transform( this, source, id, this.plugins ); }) .then( source => { const { code, originalCode, originalSourceMap, ast, sourceMapChain, resolvedIds } = source; @@ -619,11 +619,13 @@ export default class Bundle { warn ( warning ) { warning.toString = () => { - if ( warning.loc ) { - return `${relativeId( warning.loc.file )} (${warning.loc.line}:${warning.loc.column}) ${warning.message}`; - } + let str = ''; + + if ( warning.plugin ) str += `(${warning.plugin} plugin) `; + if ( warning.loc ) str += `${relativeId( warning.loc.file )} (${warning.loc.line}:${warning.loc.column}) `; + str += warning.message; - return warning.message; + return str; }; this.onwarn( warning ); diff --git a/src/utils/transform.js b/src/utils/transform.js index 7e90a748bc4..6581882f732 100644 --- a/src/utils/transform.js +++ b/src/utils/transform.js @@ -1,8 +1,9 @@ import { decode } from 'sourcemap-codec'; +import { locate } from 'locate-character'; import error from './error.js'; -import relativeId from './relativeId.js'; +import getCodeFrame from './getCodeFrame.js'; -export default function transform ( source, id, plugins ) { +export default function transform ( bundle, source, id, plugins ) { const sourceMapChain = []; const originalSourceMap = typeof source.map === 'string' ? JSON.parse( source.map ) : source.map; @@ -13,52 +14,88 @@ export default function transform ( source, id, plugins ) { const originalCode = source.code; let ast = source.ast; - let errored = false; - return plugins.reduce( ( promise, plugin ) => { - return promise.then( previous => { - if ( !plugin.transform ) return previous; + let promise = Promise.resolve( source.code ); - return Promise.resolve( plugin.transform( previous, id ) ).then( result => { - if ( result == null ) return previous; + plugins.forEach( plugin => { + if ( !plugin.transform ) return; - if ( typeof result === 'string' ) { - result = { - code: result, - ast: null, - map: null - }; + promise = promise.then( previous => { + function augment ( object, pos, code ) { + if ( typeof object === 'string' ) { + object = { message: object }; } - // `result.map` can only be a string if `result` isn't - else if ( typeof result.map === 'string' ) { - result.map = JSON.parse( result.map ); + + if ( !object.code ) object.code = code; + + if ( pos !== undefined ) { + object.pos = pos; + const { line, column } = locate( previous, pos, { offsetLine: 1 }); + object.loc = { file: id, line, column }; + object.frame = getCodeFrame( previous, line, column ); } - if ( result.map && typeof result.map.mappings === 'string' ) { - result.map.mappings = decode( result.map.mappings ); + return object; + } + + let err; + + const context = { + warn: ( warning, pos ) => { + warning = augment( warning, pos, 'PLUGIN_WARNING' ); + warning.plugin = plugin.name; + warning.id = id; + bundle.warn( warning ); + }, + + error ( e, pos ) { + err = augment( e, pos, 'PLUGIN_ERROR' ); } + }; + + let transformed; - sourceMapChain.push( result.map || { missing: true, plugin: plugin.name }); // lil' bit hacky but it works - ast = result.ast; + try { + transformed = plugin.transform.call( context, previous, id ); + } catch ( err ) { + context.error( err ); + } - return result.code; - }); - }).catch( err => { - // TODO this all seems a bit hacky - if ( errored ) throw err; - errored = true; + return Promise.resolve( transformed ) + .then( result => { + if ( err ) throw err; - err.plugin = plugin.name; - throw err; + if ( result == null ) return previous; + + if ( typeof result === 'string' ) { + result = { + code: result, + ast: null, + map: null + }; + } + + // `result.map` can only be a string if `result` isn't + else if ( typeof result.map === 'string' ) { + result.map = JSON.parse( result.map ); + } + + if ( result.map && typeof result.map.mappings === 'string' ) { + result.map.mappings = decode( result.map.mappings ); + } + + sourceMapChain.push( result.map || { missing: true, plugin: plugin.name }); // lil' bit hacky but it works + ast = result.ast; + + return result.code; + }) + .catch( err => { + err.plugin = plugin.name; + err.id = id; + error( err ); + }); }); - }, Promise.resolve( source.code ) ) - .catch( err => { - error({ - code: 'BAD_TRANSFORMER', - message: `Error transforming ${relativeId( id )}${err.plugin ? ` with '${err.plugin}' plugin` : ''}: ${err.message}`, - plugin: err.plugin, - id - }); - }) - .then( code => ({ code, originalCode, originalSourceMap, ast, sourceMapChain }) ); + }); + + return promise.then( code => ({ code, originalCode, originalSourceMap, ast, sourceMapChain }) ); } diff --git a/test/function/throws-only-first-transform-bundle/_config.js b/test/function/plugin-error-only-first-transform-bundle/_config.js similarity index 100% rename from test/function/throws-only-first-transform-bundle/_config.js rename to test/function/plugin-error-only-first-transform-bundle/_config.js diff --git a/test/function/throws-only-first-transform-bundle/main.js b/test/function/plugin-error-only-first-transform-bundle/main.js similarity index 100% rename from test/function/throws-only-first-transform-bundle/main.js rename to test/function/plugin-error-only-first-transform-bundle/main.js diff --git a/test/function/throws-only-first-transform/_config.js b/test/function/plugin-error-only-first-transform/_config.js similarity index 80% rename from test/function/throws-only-first-transform/_config.js rename to test/function/plugin-error-only-first-transform/_config.js index 4bfd127fa78..34f1e650e5f 100644 --- a/test/function/throws-only-first-transform/_config.js +++ b/test/function/plugin-error-only-first-transform/_config.js @@ -20,8 +20,8 @@ module.exports = { ] }, error: { - code: 'BAD_TRANSFORMER', - message: `Error transforming main.js with 'plugin1' plugin: Something happened 1`, + code: 'PLUGIN_ERROR', + message: `Something happened 1`, plugin: 'plugin1', id: path.resolve( __dirname, 'main.js' ) } diff --git a/test/function/throws-only-first-transform/main.js b/test/function/plugin-error-only-first-transform/main.js similarity index 100% rename from test/function/throws-only-first-transform/main.js rename to test/function/plugin-error-only-first-transform/main.js diff --git a/test/function/plugin-error/_config.js b/test/function/plugin-error/_config.js new file mode 100644 index 00000000000..ca424e175ce --- /dev/null +++ b/test/function/plugin-error/_config.js @@ -0,0 +1,29 @@ +const path = require( 'path' ); + +module.exports = { + description: 'plugin transform hooks can use `this.error({...}, char)` (#1140)', + options: { + plugins: [{ + name: 'test', + transform ( code, id ) { + this.error( 'nope', 22 ); + } + }] + }, + error: { + code: 'PLUGIN_ERROR', + plugin: 'test', + message: 'nope', + id: path.resolve( __dirname, 'main.js' ), + pos: 22, + loc: { + file: path.resolve( __dirname, 'main.js' ), + line: 1, + column: 22 + }, + frame: ` + 1: assert.equal( 21 * 2, TK ); + ^ + ` + } +}; diff --git a/test/function/plugin-error/main.js b/test/function/plugin-error/main.js new file mode 100644 index 00000000000..04164eed5dc --- /dev/null +++ b/test/function/plugin-error/main.js @@ -0,0 +1 @@ +assert.equal( 21 * 2, TK ); diff --git a/test/function/plugin-warn/_config.js b/test/function/plugin-warn/_config.js new file mode 100644 index 00000000000..78ee2539c71 --- /dev/null +++ b/test/function/plugin-warn/_config.js @@ -0,0 +1,39 @@ +const path = require( 'path' ); + +module.exports = { + description: 'plugin transform hooks can use `this.warn({...}, char)` (#1140)', + options: { + plugins: [{ + name: 'test', + transform ( code, id ) { + this.warn({ message: 'foo' }); + this.warn( 'bar', 22 ); + return 'assert.equal( 21 * 2, 42 );'; + } + }] + }, + warnings: [ + { + code: 'PLUGIN_WARNING', + id: path.resolve( __dirname, 'main.js' ), + plugin: 'test', + message: 'foo' + }, + { + code: 'PLUGIN_WARNING', + id: path.resolve( __dirname, 'main.js' ), + plugin: 'test', + message: 'bar', + pos: 22, + loc: { + file: path.resolve( __dirname, 'main.js' ), + line: 1, + column: 22 + }, + frame: ` + 1: assert.equal( 21 * 2, TK ); + ^ + ` + } + ] +}; diff --git a/test/function/plugin-warn/main.js b/test/function/plugin-warn/main.js new file mode 100644 index 00000000000..04164eed5dc --- /dev/null +++ b/test/function/plugin-warn/main.js @@ -0,0 +1 @@ +assert.equal( 21 * 2, TK ); diff --git a/test/function/report-transform-error-file/_config.js b/test/function/report-transform-error-file/_config.js deleted file mode 100644 index 56dc1bc6706..00000000000 --- a/test/function/report-transform-error-file/_config.js +++ /dev/null @@ -1,22 +0,0 @@ -var path = require( 'path' ); -var assert = require( 'assert' ); - -module.exports = { - description: 'reports which file caused a transform error', - options: { - plugins: [{ - name: 'bad-plugin', - transform: function ( code, id ) { - if ( /foo/.test( id ) ) { - throw new Error( 'nope' ); - } - } - }] - }, - error: { - code: 'BAD_TRANSFORMER', - message: `Error transforming foo.js with 'bad-plugin' plugin: nope`, - plugin: 'bad-plugin', - id: path.resolve( __dirname, 'foo.js' ) - } -}; diff --git a/test/function/report-transform-error-file/foo.js b/test/function/report-transform-error-file/foo.js deleted file mode 100644 index a2db33b7038..00000000000 --- a/test/function/report-transform-error-file/foo.js +++ /dev/null @@ -1,3 +0,0 @@ -export default function () { - console.log( 'foo' ); -} diff --git a/test/function/report-transform-error-file/main.js b/test/function/report-transform-error-file/main.js deleted file mode 100644 index e1fb5c4a3e2..00000000000 --- a/test/function/report-transform-error-file/main.js +++ /dev/null @@ -1,3 +0,0 @@ -import foo from './foo.js'; - -foo();