diff --git a/bin/src/handleError.js b/bin/src/handleError.js deleted file mode 100644 index 6882108..0000000 --- a/bin/src/handleError.js +++ /dev/null @@ -1,65 +0,0 @@ -import chalk from 'chalk'; - -function stderr ( msg ) { - console.error( msg ); // eslint-disable-line no-console -} - -const handlers = { - MISSING_CONFIG: () => { - stderr( chalk.red( 'Config file must export an options object. See https://github.com/rollup/rollup/wiki/Command-Line-Interface#using-a-config-file' ) ); - }, - - MISSING_EXTERNAL_CONFIG: err => { - stderr( chalk.red( `Could not resolve config file ${err.config}` ) ); - }, - - MISSING_INPUT_OPTION: () => { - stderr( chalk.red( 'You must specify an --input (-i) option' ) ); - }, - - MISSING_OUTPUT_OPTION: () => { - stderr( chalk.red( 'You must specify an --output (-o) option when creating a file with a sourcemap' ) ); - }, - - MISSING_NAME: () => { - stderr( chalk.red( 'You must supply a name for UMD exports (e.g. `--name myModule`)' ) ); - }, - - PARSE_ERROR: err => { - stderr( chalk.red( `Error parsing ${err.file}: ${err.message}` ) ); - }, - - ONE_AT_A_TIME: () => { - stderr( chalk.red( 'rollup can only bundle one file at a time' ) ); - }, - - DUPLICATE_IMPORT_OPTIONS: () => { - stderr( chalk.red( 'use --input, or pass input path as argument' ) ); - }, - - ROLLUP_WATCH_NOT_INSTALLED: () => { - stderr( chalk.red( 'rollup --watch depends on the rollup-watch package, which could not be found. Install it with ' ) + chalk.cyan( 'npm install -D rollup-watch' ) ); - }, - - WATCHER_MISSING_INPUT_OR_OUTPUT: () => { - stderr( chalk.red( 'must specify --input and --output when using rollup --watch' ) ); - } -}; - -export default function handleError ( err, recover ) { - const handler = handlers[ err && err.code ]; - - if ( handler ) { - handler( err ); - } else { - stderr( chalk.red( err.message || err ) ); - - if ( err.stack ) { - stderr( chalk.grey( err.stack ) ); - } - } - - stderr( `Type ${chalk.cyan( 'rollup --help' )} for help, or visit https://github.com/rollup/rollup/wiki` ); - - if ( !recover ) process.exit( 1 ); -} diff --git a/bin/src/logging.js b/bin/src/logging.js new file mode 100644 index 0000000..33b84af --- /dev/null +++ b/bin/src/logging.js @@ -0,0 +1,47 @@ +import chalk from 'chalk'; +import relativeId from '../../src/utils/relativeId.js'; + +if ( !process.stderr.isTTY ) chalk.enabled = false; +const warnSymbol = process.stderr.isTTY ? `⚠️ ` : `Warning: `; +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 )}` ); + + if ( warning.url ) { + stderr( chalk.cyan( warning.url ) ); + } + + if ( warning.loc ) { + stderr( `${relativeId( warning.loc.file )} (${warning.loc.line}:${warning.loc.column})` ); + } + + if ( warning.frame ) { + stderr( chalk.dim( warning.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( '' ); + + if ( !recover ) process.exit( 1 ); +} diff --git a/bin/src/runRollup.js b/bin/src/runRollup.js index 2629d4b..6b0130e 100644 --- a/bin/src/runRollup.js +++ b/bin/src/runRollup.js @@ -2,27 +2,26 @@ import { realpathSync } from 'fs'; import * as rollup from 'rollup'; import relative from 'require-relative'; import chalk from 'chalk'; -import handleError from './handleError'; -import relativeId from '../../src/utils/relativeId.js'; +import { handleWarning, handleError, stderr } from './logging.js'; import SOURCEMAPPING_URL from './sourceMappingUrl.js'; import { install as installSourcemapSupport } from 'source-map-support'; installSourcemapSupport(); -if ( !process.stderr.isTTY ) chalk.enabled = false; -const warnSymbol = process.stderr.isTTY ? `⚠️ ` : `Warning: `; - -// stderr to stderr to keep `rollup main.js > bundle.js` from breaking -const stderr = console.error.bind( console ); // eslint-disable-line no-console - export default function runRollup ( command ) { if ( command._.length > 1 ) { - handleError({ code: 'ONE_AT_A_TIME' }); + handleError({ + code: 'ONE_AT_A_TIME', + message: 'rollup can only bundle one file at a time' + }); } if ( command._.length === 1 ) { if ( command.input ) { - handleError({ code: 'DUPLICATE_IMPORT_OPTIONS' }); + handleError({ + code: 'DUPLICATE_IMPORT_OPTIONS', + message: 'use --input, or pass input path as argument' + }); } command.input = command._[0]; @@ -51,7 +50,10 @@ export default function runRollup ( command ) { config = relative.resolve( pkgName, process.cwd() ); } catch ( err ) { if ( err.code === 'MODULE_NOT_FOUND' ) { - handleError({ code: 'MISSING_EXTERNAL_CONFIG', config }); + handleError({ + code: 'MISSING_EXTERNAL_CONFIG', + message: `Could not resolve config file ${config}` + }); } throw err; @@ -64,37 +66,38 @@ export default function runRollup ( command ) { rollup.rollup({ entry: config, - onwarn: message => { - if ( message.code === 'UNRESOLVED_IMPORT' ) return; - stderr( message.toString() ); + onwarn: warning => { + if ( warning.code === 'UNRESOLVED_IMPORT' ) return; + handleWarning( warning ); } - }).then( bundle => { - const { code } = bundle.generate({ - format: 'cjs' - }); + }) + .then( bundle => { + const { code } = bundle.generate({ + format: 'cjs' + }); - // temporarily override require - const defaultLoader = require.extensions[ '.js' ]; - require.extensions[ '.js' ] = ( m, filename ) => { - if ( filename === config ) { - m._compile( code, filename ); - } else { - defaultLoader( m, filename ); - } - }; + // temporarily override require + const defaultLoader = require.extensions[ '.js' ]; + require.extensions[ '.js' ] = ( m, filename ) => { + if ( filename === config ) { + m._compile( code, filename ); + } else { + defaultLoader( m, filename ); + } + }; - try { const options = require( config ); if ( Object.keys( options ).length === 0 ) { - handleError({ code: 'MISSING_CONFIG' }); + handleError({ + code: 'MISSING_CONFIG', + message: 'Config file must export an options object', + url: 'https://github.com/rollup/rollup/wiki/Command-Line-Interface#using-a-config-file' + }); } execute( options, command ); require.extensions[ '.js' ] = defaultLoader; - } catch ( err ) { - handleError( err ); - } - }) - .catch( stderr ); + }) + .catch( handleError ); } else { execute( {}, command ); } @@ -157,21 +160,7 @@ function execute ( options, command ) { if ( seen.has( str ) ) return; seen.add( str ); - stderr( `${warnSymbol}${chalk.bold( warning.message )}` ); - - if ( warning.url ) { - stderr( chalk.cyan( warning.url ) ); - } - - if ( warning.loc ) { - stderr( `${relativeId( warning.loc.file )} (${warning.loc.line}:${warning.loc.column})` ); - } - - if ( warning.frame ) { - stderr( chalk.dim( warning.frame ) ); - } - - stderr( '' ); + handleWarning( warning ); }; } @@ -184,50 +173,52 @@ function execute ( options, command ) { } }); - try { - if ( command.watch ) { - if ( !options.entry || ( !options.dest && !options.targets ) ) { - handleError({ code: 'WATCHER_MISSING_INPUT_OR_OUTPUT' }); - } + if ( command.watch ) { + if ( !options.entry || ( !options.dest && !options.targets ) ) { + handleError({ + code: 'WATCHER_MISSING_INPUT_OR_OUTPUT', + message: 'must specify --input and --output when using rollup --watch' + }); + } - try { - const watch = relative( 'rollup-watch', process.cwd() ); - const watcher = watch( rollup, options ); + try { + const watch = relative( 'rollup-watch', process.cwd() ); + const watcher = watch( rollup, options ); - watcher.on( 'event', event => { - switch ( event.code ) { - case 'STARTING': // TODO this isn't emitted by newer versions of rollup-watch - stderr( 'checking rollup-watch version...' ); - break; + watcher.on( 'event', event => { + switch ( event.code ) { + case 'STARTING': // TODO this isn't emitted by newer versions of rollup-watch + stderr( 'checking rollup-watch version...' ); + break; - case 'BUILD_START': - stderr( 'bundling...' ); - break; + case 'BUILD_START': + stderr( 'bundling...' ); + break; - case 'BUILD_END': - stderr( 'bundled in ' + event.duration + 'ms. Watching for changes...' ); - break; + case 'BUILD_END': + stderr( 'bundled in ' + event.duration + 'ms. Watching for changes...' ); + break; - case 'ERROR': - handleError( event.error, true ); - break; + case 'ERROR': + handleError( event.error, true ); + break; - default: - stderr( 'unknown event', event ); - } - }); - } catch ( err ) { - if ( err.code === 'MODULE_NOT_FOUND' ) { - err.code = 'ROLLUP_WATCH_NOT_INSTALLED'; + default: + stderr( 'unknown event', event ); } - - handleError( err ); + }); + } catch ( err ) { + if ( err.code === 'MODULE_NOT_FOUND' ) { + handleError({ + code: 'ROLLUP_WATCH_NOT_INSTALLED', + message: 'rollup --watch depends on the rollup-watch package, which could not be found. Install it with npm install -D rollup-watch' + }); } - } else { - bundle( options ).catch( handleError ); + + handleError( err ); } - } catch ( err ) { - handleError( err ); + } else { + bundle( options ).catch( handleError ); } } @@ -244,34 +235,42 @@ function assign ( target, source ) { function bundle ( options ) { if ( !options.entry ) { - handleError({ code: 'MISSING_INPUT_OPTION' }); + handleError({ + code: 'MISSING_INPUT_OPTION', + message: 'You must specify an --input (-i) option' + }); } - return rollup.rollup( options ).then( bundle => { - if ( options.dest ) { - return bundle.write( options ); - } + return rollup.rollup( options ) + .then( bundle => { + if ( options.dest ) { + return bundle.write( options ); + } - if ( options.targets ) { - let result = null; + if ( options.targets ) { + let result = null; - options.targets.forEach( target => { - result = bundle.write( assign( clone( options ), target ) ); - }); + options.targets.forEach( target => { + result = bundle.write( assign( clone( options ), target ) ); + }); - return result; - } + return result; + } - if ( options.sourceMap && options.sourceMap !== 'inline' ) { - handleError({ code: 'MISSING_OUTPUT_OPTION' }); - } + if ( options.sourceMap && options.sourceMap !== 'inline' ) { + handleError({ + code: 'MISSING_OUTPUT_OPTION', + message: 'You must specify an --output (-o) option when creating a file with a sourcemap' + }); + } - let { code, map } = bundle.generate( options ); + let { code, map } = bundle.generate( options ); - if ( options.sourceMap === 'inline' ) { - code += `\n//# ${SOURCEMAPPING_URL}=${map.toUrl()}\n`; - } + if ( options.sourceMap === 'inline' ) { + code += `\n//# ${SOURCEMAPPING_URL}=${map.toUrl()}\n`; + } - process.stdout.write( code ); - }); + process.stdout.write( code ); + }) + .catch( handleError ); } diff --git a/src/Bundle.js b/src/Bundle.js index a532b84..0cfe895 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -17,6 +17,7 @@ import transformBundle from './utils/transformBundle.js'; import collapseSourcemaps from './utils/collapseSourcemaps.js'; import callIfFunction from './utils/callIfFunction.js'; import relativeId from './utils/relativeId.js'; +import error from './utils/error.js'; import { dirname, isRelative, isAbsolute, normalize, relative, resolve } from './utils/path.js'; import BundleScope from './ast/scopes/BundleScope.js'; @@ -106,7 +107,13 @@ export default class Bundle { // of the entry module's dependencies return this.resolveId( this.entry, undefined ) .then( id => { - if ( id == null ) throw new Error( `Could not resolve entry (${this.entry})` ); + if ( id == null ) { + error({ + code: 'UNRESOLVED_ENTRY', + message: `Could not resolve entry (${this.entry})` + }); + } + this.entryId = id; return this.fetchModule( id, undefined ); }) @@ -268,7 +275,11 @@ export default class Bundle { if ( typeof source === 'string' ) return source; if ( source && typeof source === 'object' && source.code ) return source; - throw new Error( `Error loading ${id}: load hook should return a string, a { code, map } object, or nothing/null` ); + // TODO report which plugin failed + error({ + code: 'BAD_LOADER', + message: `Error loading ${relativeId( id )}: plugin load hook should return a string, a { code, map } object, or nothing/null` + }); }) .then( source => { if ( typeof source === 'string' ) { @@ -337,7 +348,12 @@ export default class Bundle { let isExternal = this.isExternal( externalId ); if ( !resolvedId && !isExternal ) { - if ( isRelative( source ) ) throw new Error( `Could not resolve '${source}' from ${module.id}` ); + if ( isRelative( source ) ) { + error({ + code: 'UNRESOLVED_IMPORT', + message: `Could not resolve '${source}' from ${relativeId( module.id )}` + }); + } this.warn({ code: 'UNRESOLVED_IMPORT', @@ -367,7 +383,20 @@ export default class Bundle { }); } else { if ( resolvedId === module.id ) { - throw new Error( `A module cannot import itself (${resolvedId})` ); + // need to find the actual import declaration, so we can provide + // a useful error message. Bit hoop-jumpy but what can you do + const name = Object.keys( module.imports ) + .find( name => { + const declaration = module.imports[ name ]; + return declaration.source === source; + }); + + const declaration = module.imports[ name ].specifier.parent; + + module.error({ + code: 'CANNOT_IMPORT_SELF', + message: `A module cannot import itself` + }, declaration.start ); } module.resolvedIds[ source ] = resolvedId; @@ -445,7 +474,12 @@ export default class Bundle { const indentString = getIndentString( magicString, options ); const finalise = finalisers[ options.format ]; - if ( !finalise ) throw new Error( `You must specify an output type - valid options are ${keys( finalisers ).join( ', ' )}` ); + if ( !finalise ) { + error({ + code: 'INVALID_OPTION', + message: `You must specify an output type - valid options are ${keys( finalisers ).join( ', ' )}` + }); + } timeStart( 'render format' ); diff --git a/src/Module.js b/src/Module.js index f8e902d..ce59dee 100644 --- a/src/Module.js +++ b/src/Module.js @@ -15,25 +15,27 @@ import enhance from './ast/enhance.js'; import clone from './ast/clone.js'; import ModuleScope from './ast/scopes/ModuleScope.js'; -function tryParse ( code, comments, acornOptions, id ) { +function tryParse ( module, acornOptions ) { try { - return parse( code, assign({ + return parse( module.code, assign({ ecmaVersion: 8, sourceType: 'module', - onComment: ( block, text, start, end ) => comments.push({ block, text, start, end }), + onComment: ( block, text, start, end ) => module.comments.push({ block, text, start, end }), preserveParens: false }, acornOptions )); } catch ( err ) { - err.code = 'PARSE_ERROR'; - err.file = id; // see above - not necessarily true, but true enough - err.message += ` in ${id}`; - throw err; + module.error({ + code: 'PARSE_ERROR', + message: err.message.replace( / \(\d+:\d+\)$/, '' ) + }, err.pos ); } } export default class Module { constructor ({ id, code, originalCode, originalSourceMap, ast, sourceMapChain, resolvedIds, bundle }) { this.code = code; + this.id = id; + this.bundle = bundle; this.originalCode = originalCode; this.originalSourceMap = originalSourceMap; this.sourceMapChain = sourceMapChain; @@ -48,14 +50,12 @@ export default class Module { this.ast = clone( ast ); this.astClone = ast; } else { - this.ast = tryParse( code, this.comments, bundle.acornOptions, id ); // TODO what happens to comments if AST is provided? + this.ast = tryParse( this, bundle.acornOptions ); // TODO what happens to comments if AST is provided? this.astClone = clone( this.ast ); } timeEnd( 'ast' ); - this.bundle = bundle; - this.id = id; this.excludeFromSourcemap = /\0/.test( id ); this.context = bundle.getModuleContext( id ); @@ -121,7 +121,10 @@ export default class Module { const name = specifier.exported.name; if ( this.exports[ name ] || this.reexports[ name ] ) { - throw new Error( `A module cannot have multiple exports with the same name ('${name}')` ); + this.error({ + code: 'DUPLICATE_EXPORT', + message: `A module cannot have multiple exports with the same name ('${name}')` + }, specifier.start ); } this.reexports[ name ] = { @@ -141,8 +144,10 @@ export default class Module { const identifier = ( node.declaration.id && node.declaration.id.name ) || node.declaration.name; if ( this.exports.default ) { - // TODO indicate location - throw new Error( 'A module can only have one default export' ); + this.error({ + code: 'DUPLICATE_EXPORT', + message: `A module can only have one default export` + }, node.start ); } this.exports.default = { @@ -182,7 +187,10 @@ export default class Module { const exportedName = specifier.exported.name; if ( this.exports[ exportedName ] || this.reexports[ exportedName ] ) { - throw new Error( `A module cannot have multiple exports with the same name ('${exportedName}')` ); + this.error({ + code: 'DUPLICATE_EXPORT', + message: `A module cannot have multiple exports with the same name ('${exportedName}')` + }, specifier.start ); } this.exports[ exportedName ] = { localName }; @@ -207,10 +215,10 @@ export default class Module { const localName = specifier.local.name; if ( this.imports[ localName ] ) { - const err = new Error( `Duplicated import '${localName}'` ); - err.file = this.id; - err.loc = locate( this.code, specifier.start, { offsetLine: 1 }); - throw err; + this.error({ + code: 'DUPLICATE_IMPORT', + message: `Duplicated import '${localName}'` + }, specifier.start ); } const isDefault = specifier.type === 'ImportDefaultSpecifier'; @@ -282,6 +290,19 @@ export default class Module { // } } + error ( props, pos ) { + if ( pos !== undefined ) { + props.pos = pos; + + const { line, column } = locate( this.code, pos, { offsetLine: 1 }); // TODO trace sourcemaps + + props.loc = { file: this.id, line, column }; + props.frame = getCodeFrame( this.code, line, column ); + } + + error( props ); + } + findParent () { // TODO what does it mean if we're here? return null; @@ -372,11 +393,11 @@ export default class Module { const declaration = otherModule.traceExport( importDeclaration.name ); if ( !declaration ) { - error({ - message: `'${importDeclaration.name}' is not exported by ${relativeId( otherModule.id )} (imported by ${relativeId( this.id )}). For help fixing this error see https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module`, - file: this.id, - loc: locate( this.code, importDeclaration.specifier.start, { offsetLine: 1 }) - }); + this.error({ + code: 'MISSING_EXPORT', + message: `'${importDeclaration.name}' is not exported by ${relativeId( otherModule.id )}`, + url: `https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module` + }, importDeclaration.specifier.start ); } return declaration; @@ -392,11 +413,11 @@ export default class Module { const declaration = reexportDeclaration.module.traceExport( reexportDeclaration.localName ); if ( !declaration ) { - error({ - message: `'${reexportDeclaration.localName}' is not exported by '${reexportDeclaration.module.id}' (imported by '${this.id}')`, - file: this.id, - loc: locate( this.code, reexportDeclaration.start, { offsetLine: 1 }) - }); + this.error({ + code: 'MISSING_EXPORT', + message: `'${reexportDeclaration.localName}' is not exported by ${relativeId( reexportDeclaration.module.id )}`, + url: `https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module` + }, reexportDeclaration.start ); } return declaration; diff --git a/src/ast/nodes/CallExpression.js b/src/ast/nodes/CallExpression.js index 4880cb5..d743838 100644 --- a/src/ast/nodes/CallExpression.js +++ b/src/ast/nodes/CallExpression.js @@ -1,5 +1,3 @@ -import { locate } from 'locate-character'; -import error from '../../utils/error.js'; import Node from '../Node.js'; import isProgramLevel from '../utils/isProgramLevel.js'; import callHasEffects from './shared/callHasEffects.js'; @@ -10,12 +8,10 @@ export default class CallExpression extends Node { const declaration = scope.findDeclaration( this.callee.name ); if ( declaration.isNamespace ) { - error({ - message: `Cannot call a namespace ('${this.callee.name}')`, - file: this.module.id, - pos: this.start, - loc: locate( this.module.code, this.start, { offsetLine: 1 }) - }); + this.module.error({ + code: 'CANNOT_CALL_NAMESPACE', + message: `Cannot call a namespace ('${this.callee.name}')` + }, this.start ); } if ( this.callee.name === 'eval' && declaration.isGlobal ) { diff --git a/src/ast/nodes/shared/disallowIllegalReassignment.js b/src/ast/nodes/shared/disallowIllegalReassignment.js index 5d338af..17d054f 100644 --- a/src/ast/nodes/shared/disallowIllegalReassignment.js +++ b/src/ast/nodes/shared/disallowIllegalReassignment.js @@ -1,28 +1,21 @@ -import { locate } from 'locate-character'; -import error from '../../../utils/error.js'; - // TODO tidy this up a bit (e.g. they can both use node.module.imports) export default function disallowIllegalReassignment ( scope, node ) { if ( node.type === 'MemberExpression' && node.object.type === 'Identifier' ) { const declaration = scope.findDeclaration( node.object.name ); if ( declaration.isNamespace ) { - error({ - message: `Illegal reassignment to import '${node.object.name}'`, - file: node.module.id, - pos: node.start, - loc: locate( node.module.code, node.start, { offsetLine: 1 }) - }); + node.module.error({ + code: 'ILLEGAL_NAMESPACE_REASSIGNMENT', + message: `Illegal reassignment to import '${node.object.name}'` + }, node.start ); } } else if ( node.type === 'Identifier' ) { if ( node.module.imports[ node.name ] && !scope.contains( node.name ) ) { - error({ - message: `Illegal reassignment to import '${node.name}'`, - file: node.module.id, - pos: node.start, - loc: locate( node.module.code, node.start, { offsetLine: 1 }) - }); + node.module.error({ + code: 'ILLEGAL_REASSIGNMENT', + message: `Illegal reassignment to import '${node.name}'` + }, node.start ); } } } diff --git a/src/finalisers/iife.js b/src/finalisers/iife.js index 2d6121e..aad4491 100644 --- a/src/finalisers/iife.js +++ b/src/finalisers/iife.js @@ -1,5 +1,6 @@ import { blank } from '../utils/object.js'; import { getName } from '../utils/map-helpers.js'; +import error from '../utils/error.js'; import getInteropBlock from './shared/getInteropBlock.js'; import getExportBlock from './shared/getExportBlock.js'; import getGlobalNameMaker from './shared/getGlobalNameMaker.js'; @@ -36,7 +37,10 @@ export default function iife ( bundle, magicString, { exportMode, indentString, const args = bundle.externalModules.map( getName ); if ( exportMode !== 'none' && !name ) { - throw new Error( 'You must supply options.moduleName for IIFE bundles' ); + error({ + code: 'INVALID_OPTION', + message: `You must supply options.moduleName for IIFE bundles` + }); } if ( exportMode === 'named' ) { diff --git a/src/finalisers/umd.js b/src/finalisers/umd.js index 655c77a..b624307 100644 --- a/src/finalisers/umd.js +++ b/src/finalisers/umd.js @@ -1,5 +1,6 @@ import { blank } from '../utils/object.js'; import { getName, quotePath, req } from '../utils/map-helpers.js'; +import error from '../utils/error.js'; import getInteropBlock from './shared/getInteropBlock.js'; import getExportBlock from './shared/getExportBlock.js'; import getGlobalNameMaker from './shared/getGlobalNameMaker.js'; @@ -28,7 +29,10 @@ const wrapperOutro = '\n\n})));'; export default function umd ( bundle, magicString, { exportMode, indentString, intro, outro }, options ) { if ( exportMode !== 'none' && !options.moduleName ) { - throw new Error( 'You must supply options.moduleName for UMD bundles' ); + error({ + code: 'INVALID_OPTION', + message: 'You must supply options.moduleName for UMD bundles' + }); } warnOnBuiltins( bundle ); diff --git a/src/rollup.js b/src/rollup.js index c3e6379..a87fcf8 100644 --- a/src/rollup.js +++ b/src/rollup.js @@ -4,6 +4,7 @@ import { writeFile } from './utils/fs.js'; import { assign, keys } from './utils/object.js'; import { mapSequence } from './utils/promise.js'; import validateKeys from './utils/validateKeys.js'; +import error from './utils/error.js'; import { SOURCEMAPPING_URL } from './utils/sourceMappingURL.js'; import Bundle from './Bundle.js'; @@ -50,15 +51,15 @@ function checkOptions ( options ) { return new Error( 'The `transform`, `load`, `resolveId` and `resolveExternal` options are deprecated in favour of a unified plugin API. See https://github.com/rollup/rollup/wiki/Plugins for details' ); } - const error = validateKeys( keys(options), ALLOWED_KEYS ); - if ( error ) return error; + const err = validateKeys( keys(options), ALLOWED_KEYS ); + if ( err ) return err; return null; } export function rollup ( options ) { - const error = checkOptions ( options ); - if ( error ) return Promise.reject( error ); + const err = checkOptions ( options ); + if ( err ) return Promise.reject( err ); const bundle = new Bundle( options ); @@ -105,7 +106,10 @@ export function rollup ( options ) { generate, write: options => { if ( !options || !options.dest ) { - throw new Error( 'You must supply options.dest to bundle.write' ); + error({ + code: 'MISSING_OPTION', + message: 'You must supply options.dest to bundle.write' + }); } const dest = options.dest; diff --git a/src/utils/collapseSourcemaps.js b/src/utils/collapseSourcemaps.js index 122a148..bd65cf4 100644 --- a/src/utils/collapseSourcemaps.js +++ b/src/utils/collapseSourcemaps.js @@ -1,4 +1,5 @@ import { encode } from 'sourcemap-codec'; +import error from './error.js'; import { dirname, relative, resolve } from './path.js'; class Source { @@ -51,7 +52,9 @@ class Link { } else if ( sourcesContent[ sourceIndex ] == null ) { sourcesContent[ sourceIndex ] = traced.source.content; } else if ( traced.source.content != null && sourcesContent[ sourceIndex ] !== traced.source.content ) { - throw new Error( `Multiple conflicting contents for sourcemap source ${source.filename}` ); + error({ + message: `Multiple conflicting contents for sourcemap source ${source.filename}` + }); } segment[1] = sourceIndex; diff --git a/src/utils/defaults.js b/src/utils/defaults.js index c5dd054..d2757dd 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -1,6 +1,7 @@ import { lstatSync, readdirSync, readFileSync, realpathSync } from './fs.js'; // eslint-disable-line import { basename, dirname, isAbsolute, resolve } from './path.js'; import { blank } from './object.js'; +import error from './error.js'; export function load ( id ) { return readFileSync( id, 'utf-8' ); @@ -27,7 +28,13 @@ function addJsExtensionIfNecessary ( file ) { } export function resolveId ( importee, importer ) { - if ( typeof process === 'undefined' ) throw new Error( `It looks like you're using Rollup in a non-Node.js environment. This means you must supply a plugin with custom resolveId and load functions. See https://github.com/rollup/rollup/wiki/Plugins for more information` ); + if ( typeof process === 'undefined' ) { + error({ + code: 'MISSING_PROCESS', + message: `It looks like you're using Rollup in a non-Node.js environment. This means you must supply a plugin with custom resolveId and load functions`, + url: 'https://github.com/rollup/rollup/wiki/Plugins' + }); + } // absolute paths are left untouched if ( isAbsolute( importee ) ) return addJsExtensionIfNecessary( resolve( importee ) ); diff --git a/src/utils/getCodeFrame.js b/src/utils/getCodeFrame.js index 369829b..d5aa7a3 100644 --- a/src/utils/getCodeFrame.js +++ b/src/utils/getCodeFrame.js @@ -13,12 +13,15 @@ export default function getCodeFrame ( source, line, column ) { let lines = source.split( '\n' ); const frameStart = Math.max( 0, line - 3 ); - const frameEnd = Math.min( line + 2, lines.length ); - - const digits = String( frameEnd + 1 ).length; + let frameEnd = Math.min( line + 2, lines.length ); lines = lines.slice( frameStart, frameEnd ); - while ( !/\S/.test( lines[ lines.length - 1 ] ) ) lines.pop(); + while ( !/\S/.test( lines[ lines.length - 1 ] ) ) { + lines.pop(); + frameEnd -= 1; + } + + const digits = String( frameEnd ).length; return lines .map( ( str, i ) => { diff --git a/src/utils/getExportMode.js b/src/utils/getExportMode.js index d525550..d61b128 100644 --- a/src/utils/getExportMode.js +++ b/src/utils/getExportMode.js @@ -1,7 +1,11 @@ import { keys } from './object.js'; +import error from './error.js'; function badExports ( option, keys ) { - throw new Error( `'${option}' was specified for options.exports, but entry module has following exports: ${keys.join(', ')}` ); + error({ + code: 'INVALID_EXPORT_OPTION', + message: `'${option}' was specified for options.exports, but entry module has following exports: ${keys.join(', ')}` + }); } export default function getExportMode ( bundle, {exports: exportMode, moduleName, format} ) { @@ -35,7 +39,10 @@ export default function getExportMode ( bundle, {exports: exportMode, moduleName } if ( !/(?:default|named|none)/.test( exportMode ) ) { - throw new Error( `options.exports must be 'default', 'named', 'none', 'auto', or left unspecified (defaults to 'auto')` ); + error({ + code: 'INVALID_EXPORT_OPTION', + message: `options.exports must be 'default', 'named', 'none', 'auto', or left unspecified (defaults to 'auto')` + }); } return exportMode; diff --git a/src/utils/transform.js b/src/utils/transform.js index 2f0c167..7e90a74 100644 --- a/src/utils/transform.js +++ b/src/utils/transform.js @@ -1,4 +1,6 @@ import { decode } from 'sourcemap-codec'; +import error from './error.js'; +import relativeId from './relativeId.js'; export default function transform ( source, id, plugins ) { const sourceMapChain = []; @@ -11,6 +13,7 @@ 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 => { @@ -41,15 +44,21 @@ export default function transform ( source, id, plugins ) { return result.code; }); }).catch( err => { - if ( !err.rollupTransform ) { - err.rollupTransform = true; - err.id = id; - err.plugin = plugin.name; - err.message = `Error transforming ${id}${plugin.name ? ` with '${plugin.name}' plugin` : ''}: ${err.message}`; - } + // TODO this all seems a bit hacky + if ( errored ) throw err; + errored = true; + + err.plugin = plugin.name; throw err; }); }, Promise.resolve( source.code ) ) - - .then( code => ({ code, originalCode, originalSourceMap, ast, sourceMapChain }) ); + .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 }) ); } diff --git a/src/utils/transformBundle.js b/src/utils/transformBundle.js index 8569ad5..03eb966 100644 --- a/src/utils/transformBundle.js +++ b/src/utils/transformBundle.js @@ -1,4 +1,5 @@ import { decode } from 'sourcemap-codec'; +import error from './error.js'; export default function transformBundle ( code, plugins, sourceMapChain, options ) { return plugins.reduce( ( code, plugin ) => { @@ -9,9 +10,11 @@ export default function transformBundle ( code, plugins, sourceMapChain, options try { result = plugin.transformBundle( code, { format : options.format } ); } catch ( err ) { - err.plugin = plugin.name; - err.message = `Error transforming bundle${plugin.name ? ` with '${plugin.name}' plugin` : ''}: ${err.message}`; - throw err; + error({ + code: 'BAD_BUNDLE_TRANSFORMER', + message: `Error transforming bundle${plugin.name ? ` with '${plugin.name}' plugin` : ''}: ${err.message}`, + plugin: plugin.name + }); } if ( result == null ) return code; diff --git a/test/function/cannot-call-external-namespace/_config.js b/test/function/cannot-call-external-namespace/_config.js index 5ed02e2..a891b1f 100644 --- a/test/function/cannot-call-external-namespace/_config.js +++ b/test/function/cannot-call-external-namespace/_config.js @@ -3,10 +3,19 @@ var assert = require( 'assert' ); module.exports = { description: 'errors if code calls an external namespace', - error: function ( err ) { - assert.equal( err.message, 'Cannot call a namespace (\'foo\')' ); - assert.equal( err.file.replace( /\//g, path.sep ), path.resolve( __dirname, 'main.js' ) ); - assert.equal( err.pos, 28 ); - assert.deepEqual( err.loc, { character: 28, line: 2, column: 0 }); + error: { + code: 'CANNOT_CALL_NAMESPACE', + message: `Cannot call a namespace ('foo')`, + pos: 28, + loc: { + file: path.resolve( __dirname, 'main.js' ), + line: 2, + column: 0 + }, + frame: ` + 1: import * as foo from 'foo'; + 2: foo(); + ^ + ` } }; diff --git a/test/function/cannot-call-internal-namespace/_config.js b/test/function/cannot-call-internal-namespace/_config.js index d347008..6067234 100644 --- a/test/function/cannot-call-internal-namespace/_config.js +++ b/test/function/cannot-call-internal-namespace/_config.js @@ -3,10 +3,19 @@ var assert = require( 'assert' ); module.exports = { description: 'errors if code calls an internal namespace', - error: function ( err ) { - assert.equal( err.message, 'Cannot call a namespace (\'foo\')' ); - assert.equal( err.file.replace( /\//g, path.sep ), path.resolve( __dirname, 'main.js' ) ); - assert.equal( err.pos, 33 ); - assert.deepEqual( err.loc, { character: 33, line: 2, column: 0 }); + error: { + code: 'CANNOT_CALL_NAMESPACE', + message: `Cannot call a namespace ('foo')`, + pos: 33, + loc: { + file: path.resolve( __dirname, 'main.js' ), + line: 2, + column: 0 + }, + frame: ` + 1: import * as foo from './foo.js'; + 2: foo(); + ^ + ` } }; diff --git a/test/function/cannot-import-self/_config.js b/test/function/cannot-import-self/_config.js index 2413d03..f2896e9 100644 --- a/test/function/cannot-import-self/_config.js +++ b/test/function/cannot-import-self/_config.js @@ -1,8 +1,20 @@ +var path = require( 'path' ); var assert = require( 'assert' ); module.exports = { description: 'prevents a module importing itself', - error: function ( err ) { - assert.ok( /A module cannot import itself/.test( err.message ) ); + error: { + code: 'CANNOT_IMPORT_SELF', + message: `A module cannot import itself`, + pos: 0, + loc: { + file: path.resolve( __dirname, 'main.js' ), + line: 1, + column: 0 + }, + frame: ` + 1: import me from './main'; + ^ + ` } }; diff --git a/test/function/check-resolve-for-entry/_config.js b/test/function/check-resolve-for-entry/_config.js index a06632d..cd3da5f 100644 --- a/test/function/check-resolve-for-entry/_config.js +++ b/test/function/check-resolve-for-entry/_config.js @@ -5,7 +5,8 @@ module.exports = { options: { entry: '/not/a/path/that/actually/really/exists' }, - error: function ( err ) { - assert.ok( /Could not resolve entry/.test( err.message ) ); + error: { + code: 'UNRESOLVED_ENTRY', + message: 'Could not resolve entry (/not/a/path/that/actually/really/exists)' } }; diff --git a/test/function/custom-path-resolver-plural-b/_config.js b/test/function/custom-path-resolver-plural-b/_config.js index cb2da0c..7ca6fe6 100644 --- a/test/function/custom-path-resolver-plural-b/_config.js +++ b/test/function/custom-path-resolver-plural-b/_config.js @@ -5,21 +5,21 @@ module.exports = { options: { plugins: [ { - resolveId: function () { + resolveId () { throw new Error( 'nope' ); }, - load: function ( id ) { + load ( id ) { if ( id === 'main' ) return 'assert.ok( false );'; } }, { - resolveId: function ( importee, importer ) { + resolveId ( importee, importer ) { return 'main'; } } ] }, - error: function ( err ) { - assert.equal( err.message, 'nope' ); + error: { + message: 'nope' } }; diff --git a/test/function/default-not-reexported/_config.js b/test/function/default-not-reexported/_config.js index 4280924..3bd95b8 100644 --- a/test/function/default-not-reexported/_config.js +++ b/test/function/default-not-reexported/_config.js @@ -1,8 +1,23 @@ +const path = require( 'path' ); const assert = require( 'assert' ); module.exports = { description: 'default export is not re-exported with export *', - error ( error ) { - assert.equal( error.message, `'default' is not exported by foo.js (imported by main.js). For help fixing this error see https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module` ); + error: { + code: 'MISSING_EXPORT', + message: `'default' is not exported by foo.js`, + pos: 7, + loc: { + file: path.resolve( __dirname, 'main.js' ), + line: 1, + column: 7 + }, + frame: ` + 1: import def from './foo.js'; + ^ + 2: + 3: console.log( def ); + `, + url: `https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module` } }; diff --git a/test/function/double-default-export/_config.js b/test/function/double-default-export/_config.js index d5f5254..3a9f85f 100644 --- a/test/function/double-default-export/_config.js +++ b/test/function/double-default-export/_config.js @@ -3,7 +3,19 @@ const assert = require( 'assert' ); module.exports = { description: 'throws on double default exports', - error: err => { - assert.equal( err.message, `Duplicate export 'default' (2:7) in ${path.resolve(__dirname, 'foo.js')}` ); + error: { + code: 'PARSE_ERROR', + message: `Duplicate export 'default'`, + pos: 25, + loc: { + file: path.resolve( __dirname, 'foo.js' ), + line: 2, + column: 7 + }, + frame: ` + 1: export default 1; + 2: export default 2; + ^ + ` } }; diff --git a/test/function/double-named-export/_config.js b/test/function/double-named-export/_config.js index 169ce76..0982751 100644 --- a/test/function/double-named-export/_config.js +++ b/test/function/double-named-export/_config.js @@ -3,7 +3,20 @@ const assert = require( 'assert' ); module.exports = { description: 'throws on duplicate named exports', - error: err => { - assert.equal( err.message, `Duplicate export 'foo' (3:9) in ${path.resolve(__dirname, 'foo.js')}` ); + error: { + code: 'PARSE_ERROR', + message: `Duplicate export 'foo'`, + pos: 38, + loc: { + file: path.resolve( __dirname, 'foo.js' ), + line: 3, + column: 9 + }, + frame: ` + 1: var foo = 1; + 2: export { foo }; + 3: export { foo }; + ^ + ` } }; diff --git a/test/function/double-named-reexport/_config.js b/test/function/double-named-reexport/_config.js index 169ce76..1c7ff1c 100644 --- a/test/function/double-named-reexport/_config.js +++ b/test/function/double-named-reexport/_config.js @@ -3,7 +3,20 @@ const assert = require( 'assert' ); module.exports = { description: 'throws on duplicate named exports', - error: err => { - assert.equal( err.message, `Duplicate export 'foo' (3:9) in ${path.resolve(__dirname, 'foo.js')}` ); + error: { + code: 'PARSE_ERROR', + message: `Duplicate export 'foo'`, + pos: 38, + loc: { + file: path.resolve( __dirname, 'foo.js' ), + line: 3, + column: 9 + }, + frame: ` + 1: var foo = 1; + 2: export { foo }; + 3: export { foo } from './bar.js'; + ^ + ` } }; diff --git a/test/function/duplicate-import-fails/_config.js b/test/function/duplicate-import-fails/_config.js index 492ac2f..f47de60 100644 --- a/test/function/duplicate-import-fails/_config.js +++ b/test/function/duplicate-import-fails/_config.js @@ -3,10 +3,22 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows duplicate imports', - error: function ( err ) { - assert.equal( path.normalize( err.file ), path.resolve( __dirname, 'main.js' ) ); - assert.deepEqual( err.loc, { character: 36, line: 2, column: 9 }); - assert.ok( /Duplicated import/.test( err.message ) ); + error: { + code: 'DUPLICATE_IMPORT', + message: `Duplicated import 'a'`, + pos: 36, + loc: { + file: path.resolve( __dirname, 'main.js' ), + line: 2, + column: 9 + }, + frame: ` + 1: import { a } from './foo'; + 2: import { a } from './foo'; + ^ + 3: + 4: assert.equal(a, 1); + ` } }; diff --git a/test/function/duplicate-import-specifier-fails/_config.js b/test/function/duplicate-import-specifier-fails/_config.js index 58446af..35c88ce 100644 --- a/test/function/duplicate-import-specifier-fails/_config.js +++ b/test/function/duplicate-import-specifier-fails/_config.js @@ -3,10 +3,20 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows duplicate import specifiers', - error: function ( err ) { - assert.equal( path.normalize( err.file ), path.resolve( __dirname, 'main.js' ) ); - assert.deepEqual( err.loc, { character: 12, line: 1, column: 12 }); - assert.ok( /Duplicated import/.test( err.message ) ); + error: { + code: 'DUPLICATE_IMPORT', + message: `Duplicated import 'a'`, + pos: 12, + loc: { + file: path.resolve( __dirname, 'main.js' ), + line: 1, + column: 12 + }, + frame: ` + 1: import { a, a } from './foo'; + ^ + 2: assert.equal(a, 1); + ` } }; diff --git a/test/function/export-not-at-top-level-fails/_config.js b/test/function/export-not-at-top-level-fails/_config.js index 2dadeab..7fc1e3f 100644 --- a/test/function/export-not-at-top-level-fails/_config.js +++ b/test/function/export-not-at-top-level-fails/_config.js @@ -3,9 +3,20 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows non-top-level exports', - error: function ( err ) { - assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); - assert.deepEqual( err.loc, { line: 2, column: 2 }); - assert.ok( /may only appear at the top level/.test( err.message ) ); + error: { + code: 'PARSE_ERROR', + message: `'import' and 'export' may only appear at the top level`, + pos: 19, + loc: { + file: path.resolve( __dirname, 'main.js' ), + line: 2, + column: 2 + }, + frame: ` + 1: function foo() { + 2: export { foo }; + ^ + 3: } + ` } }; diff --git a/test/function/export-type-mismatch-b/_config.js b/test/function/export-type-mismatch-b/_config.js index 25a7220..e501330 100644 --- a/test/function/export-type-mismatch-b/_config.js +++ b/test/function/export-type-mismatch-b/_config.js @@ -5,7 +5,8 @@ module.exports = { bundleOptions: { exports: 'blah' }, - generateError: function ( err ) { - assert.ok( /options\.exports must be 'default', 'named', 'none', 'auto', or left unspecified/.test( err.message ) ); + generateError: { + code: 'INVALID_EXPORT_OPTION', + message: `options.exports must be 'default', 'named', 'none', 'auto', or left unspecified (defaults to 'auto')` } }; diff --git a/test/function/export-type-mismatch-c/_config.js b/test/function/export-type-mismatch-c/_config.js index 8df0d58..a1eca46 100644 --- a/test/function/export-type-mismatch-c/_config.js +++ b/test/function/export-type-mismatch-c/_config.js @@ -5,7 +5,8 @@ module.exports = { bundleOptions: { exports: 'none' }, - generateError: function ( err ) { - assert.ok( /'none' was specified for options\.exports/.test( err.message ) ); + generateError: { + code: 'INVALID_EXPORT_OPTION', + message: `'none' was specified for options.exports, but entry module has following exports: default` } }; diff --git a/test/function/export-type-mismatch/_config.js b/test/function/export-type-mismatch/_config.js index 5b321d6..1ff9fd5 100644 --- a/test/function/export-type-mismatch/_config.js +++ b/test/function/export-type-mismatch/_config.js @@ -5,7 +5,8 @@ module.exports = { bundleOptions: { exports: 'default' }, - generateError: function ( err ) { - assert.ok( /'default' was specified for options\.exports/.test( err.message ) ); + generateError: { + code: 'INVALID_EXPORT_OPTION', + message: `'default' was specified for options.exports, but entry module has following exports: foo` } }; diff --git a/test/function/import-not-at-top-level-fails/_config.js b/test/function/import-not-at-top-level-fails/_config.js index 4f873aa..4a68cc6 100644 --- a/test/function/import-not-at-top-level-fails/_config.js +++ b/test/function/import-not-at-top-level-fails/_config.js @@ -3,9 +3,20 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows non-top-level imports', - error: function ( err ) { - assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); - assert.deepEqual( err.loc, { line: 2, column: 2 }); - assert.ok( /may only appear at the top level/.test( err.message ) ); + error: { + code: 'PARSE_ERROR', + message: `'import' and 'export' may only appear at the top level`, + pos: 19, + loc: { + file: path.resolve( __dirname, 'main.js' ), + line: 2, + column: 2 + }, + frame: ` + 1: function foo() { + 2: import foo from './foo.js'; + ^ + 3: } + ` } }; diff --git a/test/function/import-not-at-top-level-fails/main.js b/test/function/import-not-at-top-level-fails/main.js index 1fa68e1..c88024f 100644 --- a/test/function/import-not-at-top-level-fails/main.js +++ b/test/function/import-not-at-top-level-fails/main.js @@ -1,3 +1,3 @@ function foo() { - import foo from './foo'; + import foo from './foo.js'; } diff --git a/test/function/import-of-unexported-fails/_config.js b/test/function/import-of-unexported-fails/_config.js index 10d11a6..3581bd9 100644 --- a/test/function/import-of-unexported-fails/_config.js +++ b/test/function/import-of-unexported-fails/_config.js @@ -1,8 +1,23 @@ +var path = require( 'path' ); var assert = require( 'assert' ); module.exports = { description: 'marking an imported, but unexported, identifier should throw', - error: function ( err ) { - assert.equal( err.message, `'default' is not exported by empty.js (imported by main.js). For help fixing this error see https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module` ); + error: { + code: 'MISSING_EXPORT', + message: `'default' is not exported by empty.js`, + pos: 7, + loc: { + file: path.resolve( __dirname, 'main.js' ), + line: 1, + column: 7 + }, + frame: ` + 1: import a from './empty.js'; + ^ + 2: + 3: a(); + `, + url: `https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module` } }; diff --git a/test/function/load-returns-string-or-null/_config.js b/test/function/load-returns-string-or-null/_config.js index ea80699..b49e3eb 100644 --- a/test/function/load-returns-string-or-null/_config.js +++ b/test/function/load-returns-string-or-null/_config.js @@ -4,12 +4,14 @@ module.exports = { description: 'throws error if load returns something wacky', options: { plugins: [{ + name: 'bad-plugin', load: function () { return 42; } }] }, - error: function ( err ) { - assert.ok( /load hook should return a string, a \{ code, map \} object, or nothing\/null/.test( err.message ) ); + error: { + code: 'BAD_LOADER', + message: `Error loading main.js: plugin load hook should return a string, a { code, map } object, or nothing/null` } }; diff --git a/test/function/namespace-reassign-import-fails/_config.js b/test/function/namespace-reassign-import-fails/_config.js index a76a36a..97335b1 100644 --- a/test/function/namespace-reassign-import-fails/_config.js +++ b/test/function/namespace-reassign-import-fails/_config.js @@ -3,10 +3,21 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows reassignments to namespace exports', - error: function ( err ) { - assert.equal( path.normalize( err.file ), path.resolve( __dirname, 'main.js' ) ); - assert.deepEqual( err.loc, { character: 31, line: 3, column: 0 }); - assert.ok( /Illegal reassignment/.test( err.message ) ); + error: { + code: 'ILLEGAL_NAMESPACE_REASSIGNMENT', + message: `Illegal reassignment to import 'exp'`, + pos: 31, + loc: { + file: path.resolve( __dirname, 'main.js' ), + line: 3, + column: 0 + }, + frame: ` + 1: import * as exp from './foo'; + 2: + 3: exp.foo = 2; + ^ + ` } }; diff --git a/test/function/namespace-update-import-fails/_config.js b/test/function/namespace-update-import-fails/_config.js index dc8e0f3..33ea524 100644 --- a/test/function/namespace-update-import-fails/_config.js +++ b/test/function/namespace-update-import-fails/_config.js @@ -3,10 +3,21 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows updates to namespace exports', - error: function ( err ) { - assert.equal( path.normalize( err.file ), path.resolve( __dirname, 'main.js' ) ); - assert.deepEqual( err.loc, { character: 31, line: 3, column: 0 }); - assert.ok( /Illegal reassignment/.test( err.message ) ); + error: { + code: 'ILLEGAL_NAMESPACE_REASSIGNMENT', + message: `Illegal reassignment to import 'exp'`, + pos: 31, + loc: { + file: path.resolve( __dirname, 'main.js' ), + line: 3, + column: 0 + }, + frame: ` + 1: import * as exp from './foo'; + 2: + 3: exp['foo']++; + ^ + ` } }; diff --git a/test/function/no-relative-external/_config.js b/test/function/no-relative-external/_config.js index b5c37e0..b87c9a1 100644 --- a/test/function/no-relative-external/_config.js +++ b/test/function/no-relative-external/_config.js @@ -2,7 +2,8 @@ var assert = require( 'assert' ); module.exports = { description: 'missing relative imports are an error, not a warning', - error: function ( err ) { - assert.ok( /Could not resolve '\.\/missing\.js' from/.test( err.message ) ); + error: { + code: 'UNRESOLVED_IMPORT', + message: `Could not resolve './missing.js' from main.js` } }; diff --git a/test/function/paths-are-case-sensitive/_config.js b/test/function/paths-are-case-sensitive/_config.js index 3f81b80..5b62cf7 100644 --- a/test/function/paths-are-case-sensitive/_config.js +++ b/test/function/paths-are-case-sensitive/_config.js @@ -2,7 +2,8 @@ var assert = require( 'assert' ); module.exports = { description: 'insists on correct casing for imports', - error: function ( err ) { - assert.ok( /Could not resolve/.test( err.message ) ); + error: { + code: 'UNRESOLVED_IMPORT', + message: `Could not resolve './foo.js' from main.js` } }; diff --git a/test/function/reassign-import-fails/_config.js b/test/function/reassign-import-fails/_config.js index 4854d02..922c41b 100644 --- a/test/function/reassign-import-fails/_config.js +++ b/test/function/reassign-import-fails/_config.js @@ -3,10 +3,21 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows assignments to imported bindings', - error: function ( err ) { - assert.ok( /Illegal reassignment/.test( err.message ) ); - assert.equal( path.normalize( err.file ), path.resolve( __dirname, 'main.js' ) ); - assert.deepEqual( err.loc, { character: 113, line: 8, column: 0 }); + error: { + code: 'ILLEGAL_REASSIGNMENT', + message: `Illegal reassignment to import 'x'`, + pos: 113, + loc: { + file: path.resolve( __dirname, 'main.js' ), + line: 8, + column: 0 + }, + frame: ` + 6: }); + 7: + 8: x = 10; + ^ + ` } }; diff --git a/test/function/reassign-import-not-at-top-level-fails/_config.js b/test/function/reassign-import-not-at-top-level-fails/_config.js index b4cba92..43b3a2c 100644 --- a/test/function/reassign-import-not-at-top-level-fails/_config.js +++ b/test/function/reassign-import-not-at-top-level-fails/_config.js @@ -3,10 +3,22 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows assignments to imported bindings not at the top level', - error: function ( err ) { - assert.equal( path.normalize( err.file ), path.resolve( __dirname, 'main.js' ) ); - assert.deepEqual( err.loc, { character: 95, line: 7, column: 2 }); - assert.ok( /Illegal reassignment/.test( err.message ) ); + error: { + code: 'ILLEGAL_REASSIGNMENT', + message: `Illegal reassignment to import 'x'`, + pos: 95, + loc: { + file: path.resolve( __dirname, 'main.js' ), + line: 7, + column: 2 + }, + frame: ` + 5: } + 6: export function bar () { + 7: x = 1; + ^ + 8: } + ` } }; diff --git a/test/function/reexport-missing-error/_config.js b/test/function/reexport-missing-error/_config.js index 6918683..66bcadc 100644 --- a/test/function/reexport-missing-error/_config.js +++ b/test/function/reexport-missing-error/_config.js @@ -1,8 +1,21 @@ +var path = require( 'path' ); var assert = require( 'assert' ); module.exports = { description: 'reexporting a missing identifier should print an error', - error: function ( error ) { - assert.ok( /^'foo' is not exported/.test( error.message ) ); + error: { + code: 'MISSING_EXPORT', + message: `'foo' is not exported by empty.js`, + pos: 9, + loc: { + file: path.resolve( __dirname, 'main.js' ), + line: 1, + column: 9 + }, + frame: ` + 1: export { foo as bar } from './empty.js'; + ^ + `, + url: 'https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module' } }; diff --git a/test/function/report-transform-error-file/_config.js b/test/function/report-transform-error-file/_config.js index c856974..56dc1bc 100644 --- a/test/function/report-transform-error-file/_config.js +++ b/test/function/report-transform-error-file/_config.js @@ -1,9 +1,11 @@ +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' ); @@ -11,7 +13,10 @@ module.exports = { } }] }, - error: function ( err ) { - assert.ok( ~err.message.indexOf( 'foo.js' ) ); + 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/reports-syntax-error-locations/_config.js b/test/function/reports-syntax-error-locations/_config.js deleted file mode 100644 index 2cb1085..0000000 --- a/test/function/reports-syntax-error-locations/_config.js +++ /dev/null @@ -1,8 +0,0 @@ -var assert = require( 'assert' ); - -module.exports = { - description: 'reports syntax error filename', - error: function ( err ) { - assert.ok( /in .+main\.js/.test( err.message ) ); - } -}; diff --git a/test/function/reports-syntax-error-locations/main.js b/test/function/reports-syntax-error-locations/main.js deleted file mode 100644 index d24584b..0000000 --- a/test/function/reports-syntax-error-locations/main.js +++ /dev/null @@ -1 +0,0 @@ -var 42 = answer; diff --git a/test/function/throws-not-found-module/_config.js b/test/function/throws-not-found-module/_config.js index 04963a3..8aee10d 100644 --- a/test/function/throws-not-found-module/_config.js +++ b/test/function/throws-not-found-module/_config.js @@ -3,7 +3,8 @@ var path = require( 'path' ); module.exports = { description: 'throws error if module is not found', - error: function ( err ) { - assert.equal( err.message, 'Could not resolve \'./mod\' from ' + path.resolve( __dirname, 'main.js' ) ); + error: { + code: 'UNRESOLVED_IMPORT', + message: `Could not resolve './mod' from main.js` } -}; \ No newline at end of file +}; diff --git a/test/function/throws-only-first-transform-bundle/_config.js b/test/function/throws-only-first-transform-bundle/_config.js index 9097f52..21804e7 100644 --- a/test/function/throws-only-first-transform-bundle/_config.js +++ b/test/function/throws-only-first-transform-bundle/_config.js @@ -7,19 +7,20 @@ module.exports = { { name: 'plugin1', transformBundle: function () { - throw Error('Something happend 1'); + throw Error( 'Something happened 1' ); } }, { name: 'plugin2', transformBundle: function () { - throw Error('Something happend 2'); + throw Error( 'Something happened 2' ); } } ] }, - generateError: function ( err ) { - assert.equal( err.plugin, 'plugin1' ); - assert.equal( err.message, 'Error transforming bundle with \'plugin1\' plugin: Something happend 1' ); + generateError: { + code: 'BAD_BUNDLE_TRANSFORMER', + plugin: 'plugin1', + message: `Error transforming bundle with 'plugin1' plugin: Something happened 1` } -}; \ No newline at end of file +}; diff --git a/test/function/throws-only-first-transform/_config.js b/test/function/throws-only-first-transform/_config.js index c97907e..4bfd127 100644 --- a/test/function/throws-only-first-transform/_config.js +++ b/test/function/throws-only-first-transform/_config.js @@ -7,23 +7,22 @@ module.exports = { plugins: [ { name: 'plugin1', - transform: function () { - throw Error('Something happend 1'); + transform () { + throw Error( 'Something happened 1' ); } }, { name: 'plugin2', - transform: function () { - throw Error('Something happend 2'); + transform () { + throw Error( 'Something happened 2' ); } } ] }, - error: function ( err ) { - var id = path.resolve( __dirname, 'main.js' ); - assert.equal( err.rollupTransform, true ); - assert.equal( err.id, id ); - assert.equal( err.plugin, 'plugin1' ); - assert.equal( err.message, 'Error transforming ' + id + ' with \'plugin1\' plugin: Something happend 1' ); + error: { + code: 'BAD_TRANSFORMER', + message: `Error transforming main.js with 'plugin1' plugin: Something happened 1`, + plugin: 'plugin1', + id: path.resolve( __dirname, 'main.js' ) } -}; \ No newline at end of file +}; diff --git a/test/function/update-expression-of-import-fails/_config.js b/test/function/update-expression-of-import-fails/_config.js index cf72241..5e813c5 100644 --- a/test/function/update-expression-of-import-fails/_config.js +++ b/test/function/update-expression-of-import-fails/_config.js @@ -3,10 +3,21 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows updates to imported bindings', - error: function ( err ) { - assert.equal( path.normalize( err.file ), path.resolve( __dirname, 'main.js' ) ); - assert.deepEqual( err.loc, { character: 28, line: 3, column: 0 }); - assert.ok( /Illegal reassignment/.test( err.message ) ); + error: { + code: 'ILLEGAL_REASSIGNMENT', + message: `Illegal reassignment to import 'a'`, + pos: 28, + loc: { + file: path.resolve( __dirname, 'main.js' ), + line: 3, + column: 0 + }, + frame: ` + 1: import { a } from './foo'; + 2: + 3: a++; + ^ + ` } }; diff --git a/test/test.js b/test/test.js index 78fead1..250b25a 100644 --- a/test/test.js +++ b/test/test.js @@ -79,6 +79,23 @@ function compareWarnings ( actual, expected ) { ); } +function compareError ( actual, expected ) { + delete actual.stack; + actual = Object.assign( {}, actual, { + message: actual.message + }); + + if ( actual.frame ) { + actual.frame = actual.frame.replace( /\s+$/gm, '' ); + } + + if ( expected.frame ) { + expected.frame = expected.frame.slice( 1 ).replace( /^\t+/gm, '' ).replace( /\s+$/gm, '' ).trim(); + } + + assert.deepEqual( actual, expected ); +} + describe( 'rollup', function () { this.timeout( 10000 ); @@ -259,7 +276,7 @@ describe( 'rollup', function () { } } catch ( err ) { if ( config.generateError ) { - config.generateError( err ); + compareError( err, config.generateError ); } else { unintendedError = err; } @@ -324,7 +341,7 @@ describe( 'rollup', function () { if ( unintendedError ) throw unintendedError; }, err => { if ( config.error ) { - config.error( err ); + compareError( err, config.error ); } else { throw err; }