Browse Source

Merge branch 'master' into gh-986

gh-1187
Rich-Harris 8 years ago
parent
commit
a9bcf45a62
  1. 27
      CHANGELOG.md
  2. 65
      bin/src/handleError.js
  3. 47
      bin/src/logging.js
  4. 206
      bin/src/runRollup.js
  5. 15
      package.json
  6. 3
      rollup.config.cli.js
  7. 92
      src/Bundle.js
  8. 114
      src/Module.js
  9. 4
      src/ast/Node.js
  10. 17
      src/ast/clone.js
  11. 18
      src/ast/nodes/CallExpression.js
  12. 11
      src/ast/nodes/ExportDefaultDeclaration.js
  13. 5
      src/ast/nodes/IfStatement.js
  14. 19
      src/ast/nodes/LogicalExpression.js
  15. 8
      src/ast/nodes/MemberExpression.js
  16. 12
      src/ast/nodes/ThisExpression.js
  17. 2
      src/ast/nodes/index.js
  18. 23
      src/ast/nodes/shared/disallowIllegalReassignment.js
  19. 6
      src/ast/scopes/ModuleScope.js
  20. 2
      src/finalisers/amd.js
  21. 12
      src/finalisers/iife.js
  22. 8
      src/finalisers/shared/getGlobalNameMaker.js
  23. 42
      src/finalisers/shared/warnOnBuiltins.js
  24. 11
      src/finalisers/umd.js
  25. 26
      src/rollup.js
  26. 13
      src/utils/collapseSourcemaps.js
  27. 18
      src/utils/defaults.js
  28. 41
      src/utils/getCodeFrame.js
  29. 17
      src/utils/getExportMode.js
  30. 20
      src/utils/getLocation.js
  31. 21
      src/utils/object.js
  32. 25
      src/utils/transform.js
  33. 9
      src/utils/transformBundle.js
  34. 2
      test/cli/sourcemap-newline/_config.js
  35. 2
      test/form/skips-dead-branches-i/_config.js
  36. 3
      test/form/skips-dead-branches-j/_config.js
  37. 7
      test/form/skips-dead-branches-j/_expected/amd.js
  38. 5
      test/form/skips-dead-branches-j/_expected/cjs.js
  39. 3
      test/form/skips-dead-branches-j/_expected/es.js
  40. 8
      test/form/skips-dead-branches-j/_expected/iife.js
  41. 11
      test/form/skips-dead-branches-j/_expected/umd.js
  42. 5
      test/form/skips-dead-branches-j/main.js
  43. 11
      test/function/assign-namespace-to-var/_config.js
  44. 19
      test/function/cannot-call-external-namespace/_config.js
  45. 19
      test/function/cannot-call-internal-namespace/_config.js
  46. 16
      test/function/cannot-import-self/_config.js
  47. 5
      test/function/check-resolve-for-entry/_config.js
  48. 1
      test/function/custom-external-resolver-async/_config.js
  49. 13
      test/function/custom-path-resolver-async/_config.js
  50. 10
      test/function/custom-path-resolver-plural-b/_config.js
  51. 12
      test/function/custom-path-resolver-sync/_config.js
  52. 19
      test/function/default-not-reexported/_config.js
  53. 12
      test/function/does-not-hang-on-missing-module/_config.js
  54. 16
      test/function/double-default-export/_config.js
  55. 13
      test/function/double-named-export-from/_config.js
  56. 17
      test/function/double-named-export/_config.js
  57. 17
      test/function/double-named-reexport/_config.js
  58. 20
      test/function/duplicate-import-fails/_config.js
  59. 18
      test/function/duplicate-import-specifier-fails/_config.js
  60. 29
      test/function/empty-exports/_config.js
  61. 8
      test/function/export-default-no-space/_config.js
  62. 1
      test/function/export-default-no-space/main.js
  63. 19
      test/function/export-not-at-top-level-fails/_config.js
  64. 5
      test/function/export-type-mismatch-b/_config.js
  65. 5
      test/function/export-type-mismatch-c/_config.js
  66. 5
      test/function/export-type-mismatch/_config.js
  67. 19
      test/function/import-not-at-top-level-fails/_config.js
  68. 2
      test/function/import-not-at-top-level-fails/main.js
  69. 19
      test/function/import-of-unexported-fails/_config.js
  70. 6
      test/function/load-returns-string-or-null/_config.js
  71. 11
      test/function/module-tree/_config.js
  72. 24
      test/function/namespace-missing-export/_config.js
  73. 19
      test/function/namespace-reassign-import-fails/_config.js
  74. 19
      test/function/namespace-update-import-fails/_config.js
  75. 5
      test/function/no-relative-external/_config.js
  76. 5
      test/function/paths-are-case-sensitive/_config.js
  77. 19
      test/function/reassign-import-fails/_config.js
  78. 20
      test/function/reassign-import-not-at-top-level-fails/_config.js
  79. 17
      test/function/reexport-missing-error/_config.js
  80. 9
      test/function/report-transform-error-file/_config.js
  81. 8
      test/function/reports-syntax-error-locations/_config.js
  82. 1
      test/function/reports-syntax-error-locations/main.js
  83. 7
      test/function/throws-not-found-module/_config.js
  84. 13
      test/function/throws-only-first-transform-bundle/_config.js
  85. 21
      test/function/throws-only-first-transform/_config.js
  86. 22
      test/function/unused-import/_config.js
  87. 19
      test/function/update-expression-of-import-fails/_config.js
  88. 9
      test/function/vars-with-init-in-dead-branch/_config.js
  89. 4
      test/function/vars-with-init-in-dead-branch/main.js
  90. 24
      test/function/warn-on-ambiguous-function-export/_config.js
  91. 14
      test/function/warn-on-auto-named-default-exports/_config.js
  92. 13
      test/function/warn-on-empty-bundle/_config.js
  93. 28
      test/function/warn-on-eval/_config.js
  94. 9
      test/function/warn-on-namespace-conflict/_config.js
  95. 0
      test/function/warn-on-namespace-conflict/bar.js
  96. 0
      test/function/warn-on-namespace-conflict/deep.js
  97. 0
      test/function/warn-on-namespace-conflict/foo.js
  98. 0
      test/function/warn-on-namespace-conflict/main.js
  99. 24
      test/function/warn-on-top-level-this/_config.js
  100. 23
      test/function/warn-on-unused-missing-imports/_config.js

27
CHANGELOG.md

@ -1,5 +1,32 @@
# rollup changelog # rollup changelog
## 0.40.1
* Allow missing space between `export default` and declaration ([#1218](https://github.com/rollup/rollup/pull/1218))
## 0.40.0
* [BREAKING] Better, more consistent error logging ([#1212](https://github.com/rollup/rollup/pull/1212))
* Don't use colours and emojis for non-TTY stderr ([#1201](https://github.com/rollup/rollup/issues/1201))
## 0.39.2
* Prevent mutation of cached ASTs (fixes stack overflow with rollup-watch) ([#1205](https://github.com/rollup/rollup/pull/1205))
## 0.39.1
* Ignore `var` initialisers in dead branches ([#1198](https://github.com/rollup/rollup/issues/1198))
## 0.39.0
* [BREAKING] Warnings are objects, rather than strings ([#1194](https://github.com/rollup/rollup/issues/1194))
## 0.38.3
* More informative warning for implicit external dependencies ([#1051](https://github.com/rollup/rollup/issues/1051))
* Warn when creating browser bundle with external dependencies on Node built-ins ([#1051](https://github.com/rollup/rollup/issues/1051))
* Statically analyse LogicalExpression nodes, for better dead code removal ([#1061](https://github.com/rollup/rollup/issues/1061))
## 0.38.2 ## 0.38.2
* Preserve `var` declarations in dead branches ([#997](https://github.com/rollup/rollup/issues/997)) * Preserve `var` declarations in dead branches ([#997](https://github.com/rollup/rollup/issues/997))

65
bin/src/handleError.js

@ -1,65 +0,0 @@
import * as 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. You can install it globally (recommended) with ' ) + chalk.cyan( 'npm install -g 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 );
}

47
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 );
}

206
bin/src/runRollup.js

@ -1,23 +1,27 @@
import { realpathSync } from 'fs'; import { realpathSync } from 'fs';
import * as rollup from 'rollup'; import * as rollup from 'rollup';
import relative from 'require-relative'; import relative from 'require-relative';
import handleError from './handleError'; import chalk from 'chalk';
import { handleWarning, handleError, stderr } from './logging.js';
import SOURCEMAPPING_URL from './sourceMappingUrl.js'; import SOURCEMAPPING_URL from './sourceMappingUrl.js';
import { install as installSourcemapSupport } from 'source-map-support'; import { install as installSourcemapSupport } from 'source-map-support';
installSourcemapSupport(); installSourcemapSupport();
// 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 ) { export default function runRollup ( command ) {
if ( command._.length > 1 ) { 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._.length === 1 ) {
if ( command.input ) { 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]; command.input = command._[0];
@ -46,7 +50,10 @@ export default function runRollup ( command ) {
config = relative.resolve( pkgName, process.cwd() ); config = relative.resolve( pkgName, process.cwd() );
} catch ( err ) { } catch ( err ) {
if ( err.code === 'MODULE_NOT_FOUND' ) { 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; throw err;
@ -59,38 +66,38 @@ export default function runRollup ( command ) {
rollup.rollup({ rollup.rollup({
entry: config, entry: config,
onwarn: message => { onwarn: warning => {
// TODO use warning codes instead of this hackery if ( warning.code === 'UNRESOLVED_IMPORT' ) return;
if ( /treating it as an external dependency/.test( message ) ) return; handleWarning( warning );
stderr( message );
} }
}).then( bundle => { })
const { code } = bundle.generate({ .then( bundle => {
format: 'cjs' const { code } = bundle.generate({
}); format: 'cjs'
});
// temporarily override require // temporarily override require
const defaultLoader = require.extensions[ '.js' ]; const defaultLoader = require.extensions[ '.js' ];
require.extensions[ '.js' ] = ( m, filename ) => { require.extensions[ '.js' ] = ( m, filename ) => {
if ( filename === config ) { if ( filename === config ) {
m._compile( code, filename ); m._compile( code, filename );
} else { } else {
defaultLoader( m, filename ); defaultLoader( m, filename );
} }
}; };
try {
const options = require( config ); const options = require( config );
if ( Object.keys( options ).length === 0 ) { 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 ); execute( options, command );
require.extensions[ '.js' ] = defaultLoader; require.extensions[ '.js' ] = defaultLoader;
} catch ( err ) { })
handleError( err ); .catch( handleError );
}
})
.catch( stderr );
} else { } else {
execute( {}, command ); execute( {}, command );
} }
@ -121,7 +128,7 @@ function execute ( options, command ) {
const optionsExternal = options.external; const optionsExternal = options.external;
if ( command.globals ) { if ( command.globals ) {
let globals = Object.create( null ); const globals = Object.create( null );
command.globals.split( ',' ).forEach( str => { command.globals.split( ',' ).forEach( str => {
const names = str.split( ':' ); const names = str.split( ':' );
@ -144,7 +151,18 @@ function execute ( options, command ) {
external = ( optionsExternal || [] ).concat( commandExternal ); external = ( optionsExternal || [] ).concat( commandExternal );
} }
options.onwarn = options.onwarn || stderr; if ( !options.onwarn ) {
const seen = new Set();
options.onwarn = warning => {
const str = warning.toString();
if ( seen.has( str ) ) return;
seen.add( str );
handleWarning( warning );
};
}
options.external = external; options.external = external;
@ -155,50 +173,52 @@ function execute ( options, command ) {
} }
}); });
try { if ( command.watch ) {
if ( command.watch ) { if ( !options.entry || ( !options.dest && !options.targets ) ) {
if ( !options.entry || ( !options.dest && !options.targets ) ) { handleError({
handleError({ code: 'WATCHER_MISSING_INPUT_OR_OUTPUT' }); code: 'WATCHER_MISSING_INPUT_OR_OUTPUT',
} message: 'must specify --input and --output when using rollup --watch'
});
}
try { try {
const watch = relative( 'rollup-watch', process.cwd() ); const watch = relative( 'rollup-watch', process.cwd() );
const watcher = watch( rollup, options ); const watcher = watch( rollup, options );
watcher.on( 'event', event => { watcher.on( 'event', event => {
switch ( event.code ) { switch ( event.code ) {
case 'STARTING': case 'STARTING': // TODO this isn't emitted by newer versions of rollup-watch
stderr( 'checking rollup-watch version...' ); stderr( 'checking rollup-watch version...' );
break; break;
case 'BUILD_START': case 'BUILD_START':
stderr( 'bundling...' ); stderr( 'bundling...' );
break; break;
case 'BUILD_END': case 'BUILD_END':
stderr( 'bundled in ' + event.duration + 'ms. Watching for changes...' ); stderr( 'bundled in ' + event.duration + 'ms. Watching for changes...' );
break; break;
case 'ERROR': case 'ERROR':
handleError( event.error, true ); handleError( event.error, true );
break; break;
default: default:
stderr( 'unknown event', event ); stderr( 'unknown event', event );
}
});
} catch ( err ) {
if ( err.code === 'MODULE_NOT_FOUND' ) {
err.code = 'ROLLUP_WATCH_NOT_INSTALLED';
} }
});
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 ) { } else {
handleError( err ); bundle( options ).catch( handleError );
} }
} }
@ -215,34 +235,42 @@ function assign ( target, source ) {
function bundle ( options ) { function bundle ( options ) {
if ( !options.entry ) { 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 => { return rollup.rollup( options )
if ( options.dest ) { .then( bundle => {
return bundle.write( options ); if ( options.dest ) {
} return bundle.write( options );
}
if ( options.targets ) { if ( options.targets ) {
let result = null; let result = null;
options.targets.forEach( target => { options.targets.forEach( target => {
result = bundle.write( assign( clone( options ), target ) ); result = bundle.write( assign( clone( options ), target ) );
}); });
return result; return result;
} }
if ( options.sourceMap && options.sourceMap !== 'inline' ) { if ( options.sourceMap && options.sourceMap !== 'inline' ) {
handleError({ code: 'MISSING_OUTPUT_OPTION' }); 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' ) { if ( options.sourceMap === 'inline' ) {
code += `\n//# ${SOURCEMAPPING_URL}=${map.toUrl()}\n`; code += `\n//# ${SOURCEMAPPING_URL}=${map.toUrl()}\n`;
} }
process.stdout.write( code ); process.stdout.write( code );
}); })
.catch( handleError );
} }

15
package.json

@ -1,6 +1,6 @@
{ {
"name": "rollup", "name": "rollup",
"version": "0.38.2", "version": "0.40.1",
"description": "Next-generation ES6 module bundler", "description": "Next-generation ES6 module bundler",
"main": "dist/rollup.js", "main": "dist/rollup.js",
"module": "dist/rollup.es.js", "module": "dist/rollup.es.js",
@ -17,7 +17,7 @@
"posttest-coverage": "remap-istanbul -i coverage/coverage-final.json -o coverage/coverage-remapped.json -b dist && remap-istanbul -i coverage/coverage-final.json -o coverage/coverage-remapped.lcov -t lcovonly -b dist && remap-istanbul -i coverage/coverage-final.json -o coverage/coverage-remapped -t html -b dist", "posttest-coverage": "remap-istanbul -i coverage/coverage-final.json -o coverage/coverage-remapped.json -b dist && remap-istanbul -i coverage/coverage-final.json -o coverage/coverage-remapped.lcov -t lcovonly -b dist && remap-istanbul -i coverage/coverage-final.json -o coverage/coverage-remapped -t html -b dist",
"ci": "npm run test-coverage && codecov < coverage/coverage-remapped.lcov", "ci": "npm run test-coverage && codecov < coverage/coverage-remapped.lcov",
"build": "git rev-parse HEAD > .commithash && rollup -c", "build": "git rev-parse HEAD > .commithash && rollup -c",
"build:cli": "rollup -c rollup.config.cli.js", "build:cli": "rollup -c rollup.config.cli.js && chmod a+x bin/rollup",
"build:browser": "git rev-parse HEAD > .commithash && rollup -c rollup.config.browser.js", "build:browser": "git rev-parse HEAD > .commithash && rollup -c rollup.config.browser.js",
"watch": "rollup -c -w", "watch": "rollup -c -w",
"watch:browser": "rollup -c rollup.config.browser.js -w", "watch:browser": "rollup -c rollup.config.browser.js -w",
@ -49,23 +49,24 @@
"chalk": "^1.1.3", "chalk": "^1.1.3",
"codecov.io": "^0.1.6", "codecov.io": "^0.1.6",
"console-group": "^0.3.1", "console-group": "^0.3.1",
"eslint": "^2.13.1", "eslint": "^3.12.2",
"eslint-plugin-import": "^2.2.0", "eslint-plugin-import": "^2.2.0",
"is-reference": "^1.0.0", "is-reference": "^1.0.0",
"istanbul": "^0.4.3", "istanbul": "^0.4.3",
"locate-character": "^2.0.0",
"magic-string": "^0.15.2", "magic-string": "^0.15.2",
"minimist": "^1.2.0", "minimist": "^1.2.0",
"mocha": "^3.0.0", "mocha": "^3.0.0",
"remap-istanbul": "^0.6.4", "remap-istanbul": "^0.6.4",
"require-relative": "^0.8.7", "require-relative": "^0.8.7",
"rollup": "^0.34.0", "rollup": "^0.39.0",
"rollup-plugin-buble": "^0.12.1", "rollup-plugin-buble": "^0.13.0",
"rollup-plugin-commonjs": "^3.0.0", "rollup-plugin-commonjs": "^7.0.0",
"rollup-plugin-json": "^2.0.0", "rollup-plugin-json": "^2.0.0",
"rollup-plugin-node-resolve": "^2.0.0", "rollup-plugin-node-resolve": "^2.0.0",
"rollup-plugin-replace": "^1.1.0", "rollup-plugin-replace": "^1.1.0",
"rollup-plugin-string": "^2.0.0", "rollup-plugin-string": "^2.0.0",
"sander": "^0.5.1", "sander": "^0.6.0",
"source-map": "^0.5.6", "source-map": "^0.5.6",
"sourcemap-codec": "^1.3.0", "sourcemap-codec": "^1.3.0",
"uglify-js": "^2.6.2" "uglify-js": "^2.6.2"

3
rollup.config.cli.js

@ -14,8 +14,7 @@ export default {
json(), json(),
buble(), buble(),
commonjs({ commonjs({
include: 'node_modules/**', include: 'node_modules/**'
namedExports: { chalk: [ 'red', 'cyan', 'grey' ] }
}), }),
nodeResolve({ nodeResolve({
main: true main: true

92
src/Bundle.js

@ -17,6 +17,7 @@ import transformBundle from './utils/transformBundle.js';
import collapseSourcemaps from './utils/collapseSourcemaps.js'; import collapseSourcemaps from './utils/collapseSourcemaps.js';
import callIfFunction from './utils/callIfFunction.js'; import callIfFunction from './utils/callIfFunction.js';
import relativeId from './utils/relativeId.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 { dirname, isRelative, isAbsolute, normalize, relative, resolve } from './utils/path.js';
import BundleScope from './ast/scopes/BundleScope.js'; import BundleScope from './ast/scopes/BundleScope.js';
@ -106,7 +107,13 @@ export default class Bundle {
// of the entry module's dependencies // of the entry module's dependencies
return this.resolveId( this.entry, undefined ) return this.resolveId( this.entry, undefined )
.then( id => { .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; this.entryId = id;
return this.fetchModule( id, undefined ); return this.fetchModule( id, undefined );
}) })
@ -188,7 +195,10 @@ export default class Bundle {
`'${unused[0]}' is` : `'${unused[0]}' is` :
`${unused.slice( 0, -1 ).map( name => `'${name}'` ).join( ', ' )} and '${unused.pop()}' are`; `${unused.slice( 0, -1 ).map( name => `'${name}'` ).join( ', ' )} and '${unused.pop()}' are`;
this.onwarn( `${names} imported from external module '${module.id}' but never used` ); this.warn({
code: 'UNUSED_EXTERNAL_IMPORT',
message: `${names} imported from external module '${module.id}' but never used`
});
}); });
this.orderedModules = this.sort(); this.orderedModules = this.sort();
@ -265,7 +275,11 @@ export default class Bundle {
if ( typeof source === 'string' ) return source; if ( typeof source === 'string' ) return source;
if ( source && typeof source === 'object' && source.code ) 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 => { .then( source => {
if ( typeof source === 'string' ) { if ( typeof source === 'string' ) {
@ -309,7 +323,10 @@ export default class Bundle {
keys( exportAllModule.exportsAll ).forEach( name => { keys( exportAllModule.exportsAll ).forEach( name => {
if ( name in module.exportsAll ) { if ( name in module.exportsAll ) {
this.onwarn( `Conflicting namespaces: ${module.id} re-exports '${name}' from both ${module.exportsAll[ name ]} (will be ignored) and ${exportAllModule.exportsAll[ name ]}.` ); this.warn({
code: 'NAMESPACE_CONFLICT',
message: `Conflicting namespaces: ${relativeId( module.id )} re-exports '${name}' from both ${relativeId( module.exportsAll[ name ] )} (will be ignored) and ${relativeId( exportAllModule.exportsAll[ name ] )}`
});
} }
module.exportsAll[ name ] = exportAllModule.exportsAll[ name ]; module.exportsAll[ name ] = exportAllModule.exportsAll[ name ];
}); });
@ -331,9 +348,18 @@ export default class Bundle {
let isExternal = this.isExternal( externalId ); let isExternal = this.isExternal( externalId );
if ( !resolvedId && !isExternal ) { 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.onwarn( `'${source}' is imported by ${relativeId( module.id )}, but could not be resolved – treating it as an external dependency. For help see https://github.com/rollup/rollup/wiki/Troubleshooting#treating-module-as-external-dependency` ); this.warn({
code: 'UNRESOLVED_IMPORT',
message: `'${source}' is imported by ${relativeId( module.id )}, but could not be resolved – treating it as an external dependency`,
url: 'https://github.com/rollup/rollup/wiki/Troubleshooting#treating-module-as-external-dependency'
});
isExternal = true; isExternal = true;
} }
@ -357,7 +383,20 @@ export default class Bundle {
}); });
} else { } else {
if ( resolvedId === module.id ) { 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; module.resolvedIds[ source ] = resolvedId;
@ -380,12 +419,14 @@ export default class Bundle {
render ( options = {} ) { render ( options = {} ) {
if ( options.format === 'es6' ) { if ( options.format === 'es6' ) {
this.onwarn( 'The es6 format is deprecated – use `es` instead' ); this.warn({
code: 'DEPRECATED_ES6',
message: 'The es6 format is deprecated – use `es` instead'
});
options.format = 'es'; options.format = 'es';
} }
const format = options.format || 'es';
// Determine export mode - 'default', 'named', 'none' // Determine export mode - 'default', 'named', 'none'
const exportMode = getExportMode( this, options ); const exportMode = getExportMode( this, options );
@ -395,7 +436,7 @@ export default class Bundle {
timeStart( 'render modules' ); timeStart( 'render modules' );
this.orderedModules.forEach( module => { this.orderedModules.forEach( module => {
const source = module.render( format === 'es', this.legacy ); const source = module.render( options.format === 'es', this.legacy );
if ( source.toString().length ) { if ( source.toString().length ) {
magicString.addSource( source ); magicString.addSource( source );
@ -404,7 +445,10 @@ export default class Bundle {
}); });
if ( !magicString.toString().trim() && this.entryModule.getExports().length === 0 ) { if ( !magicString.toString().trim() && this.entryModule.getExports().length === 0 ) {
this.onwarn( 'Generated an empty bundle' ); this.warn({
code: 'EMPTY_BUNDLE',
message: 'Generated an empty bundle'
});
} }
timeEnd( 'render modules' ); timeEnd( 'render modules' );
@ -429,8 +473,13 @@ export default class Bundle {
const indentString = getIndentString( magicString, options ); const indentString = getIndentString( magicString, options );
const finalise = finalisers[ format ]; 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' ); timeStart( 'render format' );
@ -470,7 +519,7 @@ export default class Bundle {
if ( typeof map.mappings === 'string' ) { if ( typeof map.mappings === 'string' ) {
map.mappings = decode( map.mappings ); map.mappings = decode( map.mappings );
} }
map = collapseSourcemaps( file, map, usedModules, bundleSourcemapChain, this.onwarn ); map = collapseSourcemaps( this, file, map, usedModules, bundleSourcemapChain );
} else { } else {
map = magicString.generateMap({ file, includeContent: true }); map = magicString.generateMap({ file, includeContent: true });
} }
@ -535,6 +584,7 @@ export default class Bundle {
for ( i += 1; i < ordered.length; i += 1 ) { for ( i += 1; i < ordered.length; i += 1 ) {
const b = ordered[i]; const b = ordered[i];
// TODO reinstate this! it no longer works
if ( stronglyDependsOn[ a.id ][ b.id ] ) { if ( stronglyDependsOn[ a.id ][ b.id ] ) {
// somewhere, there is a module that imports b before a. Because // somewhere, there is a module that imports b before a. Because
// b imports a, a is placed before b. We need to find the module // b imports a, a is placed before b. We need to find the module
@ -566,4 +616,16 @@ export default class Bundle {
return ordered; return ordered;
} }
warn ( warning ) {
warning.toString = () => {
if ( warning.loc ) {
return `${relativeId( warning.loc.file )} (${warning.loc.line}:${warning.loc.column}) ${warning.message}`;
}
return warning.message;
};
this.onwarn( warning );
}
} }

114
src/Module.js

@ -1,37 +1,41 @@
import { timeStart, timeEnd } from './utils/flushTime.js';
import { parse } from 'acorn/src/index.js'; import { parse } from 'acorn/src/index.js';
import MagicString from 'magic-string'; import MagicString from 'magic-string';
import { assign, blank, deepClone, keys } from './utils/object.js'; import { locate } from 'locate-character';
import { timeStart, timeEnd } from './utils/flushTime.js';
import { assign, blank, keys } from './utils/object.js';
import { basename, extname } from './utils/path.js'; import { basename, extname } from './utils/path.js';
import getLocation from './utils/getLocation.js';
import makeLegalIdentifier from './utils/makeLegalIdentifier.js'; import makeLegalIdentifier from './utils/makeLegalIdentifier.js';
import getCodeFrame from './utils/getCodeFrame.js';
import { SOURCEMAPPING_URL_RE } from './utils/sourceMappingURL.js'; import { SOURCEMAPPING_URL_RE } from './utils/sourceMappingURL.js';
import error from './utils/error.js'; import error from './utils/error.js';
import relativeId from './utils/relativeId.js'; import relativeId from './utils/relativeId.js';
import { SyntheticNamespaceDeclaration } from './Declaration.js'; import { SyntheticNamespaceDeclaration } from './Declaration.js';
import extractNames from './ast/utils/extractNames.js'; import extractNames from './ast/utils/extractNames.js';
import enhance from './ast/enhance.js'; import enhance from './ast/enhance.js';
import clone from './ast/clone.js';
import ModuleScope from './ast/scopes/ModuleScope.js'; import ModuleScope from './ast/scopes/ModuleScope.js';
function tryParse ( code, comments, acornOptions, id ) { function tryParse ( module, acornOptions ) {
try { try {
return parse( code, assign({ return parse( module.code, assign({
ecmaVersion: 8, ecmaVersion: 8,
sourceType: 'module', 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 preserveParens: false
}, acornOptions )); }, acornOptions ));
} catch ( err ) { } catch ( err ) {
err.code = 'PARSE_ERROR'; module.error({
err.file = id; // see above - not necessarily true, but true enough code: 'PARSE_ERROR',
err.message += ` in ${id}`; message: err.message.replace( / \(\d+:\d+\)$/, '' )
throw err; }, err.pos );
} }
} }
export default class Module { export default class Module {
constructor ({ id, code, originalCode, originalSourceMap, ast, sourceMapChain, resolvedIds, bundle }) { constructor ({ id, code, originalCode, originalSourceMap, ast, sourceMapChain, resolvedIds, bundle }) {
this.code = code; this.code = code;
this.id = id;
this.bundle = bundle;
this.originalCode = originalCode; this.originalCode = originalCode;
this.originalSourceMap = originalSourceMap; this.originalSourceMap = originalSourceMap;
this.sourceMapChain = sourceMapChain; this.sourceMapChain = sourceMapChain;
@ -40,13 +44,18 @@ export default class Module {
timeStart( 'ast' ); timeStart( 'ast' );
this.ast = ast || tryParse( code, this.comments, bundle.acornOptions, id ); // TODO what happens to comments if AST is provided? if ( ast ) {
this.astClone = deepClone( this.ast ); // prevent mutating the provided AST, as it may be reused on
// subsequent incremental rebuilds
this.ast = clone( ast );
this.astClone = ast;
} else {
this.ast = tryParse( this, bundle.acornOptions ); // TODO what happens to comments if AST is provided?
this.astClone = clone( this.ast );
}
timeEnd( 'ast' ); timeEnd( 'ast' );
this.bundle = bundle;
this.id = id;
this.excludeFromSourcemap = /\0/.test( id ); this.excludeFromSourcemap = /\0/.test( id );
this.context = bundle.getModuleContext( id ); this.context = bundle.getModuleContext( id );
@ -112,7 +121,10 @@ export default class Module {
const name = specifier.exported.name; const name = specifier.exported.name;
if ( this.exports[ name ] || this.reexports[ 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 ] = { this.reexports[ name ] = {
@ -132,8 +144,10 @@ export default class Module {
const identifier = ( node.declaration.id && node.declaration.id.name ) || node.declaration.name; const identifier = ( node.declaration.id && node.declaration.id.name ) || node.declaration.name;
if ( this.exports.default ) { if ( this.exports.default ) {
// TODO indicate location this.error({
throw new Error( 'A module can only have one default export' ); code: 'DUPLICATE_EXPORT',
message: `A module can only have one default export`
}, node.start );
} }
this.exports.default = { this.exports.default = {
@ -173,13 +187,21 @@ export default class Module {
const exportedName = specifier.exported.name; const exportedName = specifier.exported.name;
if ( this.exports[ exportedName ] || this.reexports[ exportedName ] ) { 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 }; this.exports[ exportedName ] = { localName };
}); });
} else { } else {
this.bundle.onwarn( `Module ${this.id} has an empty export declaration` ); // TODO is this really necessary? `export {}` is valid JS, and
// might be used as a hint that this is indeed a module
this.warn({
code: 'EMPTY_EXPORT',
message: `Empty export declaration`
}, node.start );
} }
} }
} }
@ -193,10 +215,10 @@ export default class Module {
const localName = specifier.local.name; const localName = specifier.local.name;
if ( this.imports[ localName ] ) { if ( this.imports[ localName ] ) {
const err = new Error( `Duplicated import '${localName}'` ); this.error({
err.file = this.id; code: 'DUPLICATE_IMPORT',
err.loc = getLocation( this.code, specifier.start ); message: `Duplicated import '${localName}'`
throw err; }, specifier.start );
} }
const isDefault = specifier.type === 'ImportDefaultSpecifier'; const isDefault = specifier.type === 'ImportDefaultSpecifier';
@ -268,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 () { findParent () {
// TODO what does it mean if we're here? // TODO what does it mean if we're here?
return null; return null;
@ -358,11 +393,11 @@ export default class Module {
const declaration = otherModule.traceExport( importDeclaration.name ); const declaration = otherModule.traceExport( importDeclaration.name );
if ( !declaration ) { if ( !declaration ) {
error({ this.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`, code: 'MISSING_EXPORT',
file: this.id, message: `'${importDeclaration.name}' is not exported by ${relativeId( otherModule.id )}`,
loc: getLocation( this.code, importDeclaration.specifier.start ) url: `https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module`
}); }, importDeclaration.specifier.start );
} }
return declaration; return declaration;
@ -378,11 +413,11 @@ export default class Module {
const declaration = reexportDeclaration.module.traceExport( reexportDeclaration.localName ); const declaration = reexportDeclaration.module.traceExport( reexportDeclaration.localName );
if ( !declaration ) { if ( !declaration ) {
error({ this.error({
message: `'${reexportDeclaration.localName}' is not exported by '${reexportDeclaration.module.id}' (imported by '${this.id}')`, code: 'MISSING_EXPORT',
file: this.id, message: `'${reexportDeclaration.localName}' is not exported by ${relativeId( reexportDeclaration.module.id )}`,
loc: getLocation( this.code, reexportDeclaration.start ) url: `https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module`
}); }, reexportDeclaration.start );
} }
return declaration; return declaration;
@ -405,4 +440,17 @@ export default class Module {
if ( declaration ) return declaration; if ( declaration ) return declaration;
} }
} }
warn ( warning, pos ) {
if ( pos !== undefined ) {
warning.pos = pos;
const { line, column } = locate( this.code, pos, { offsetLine: 1 }); // TODO trace sourcemaps
warning.loc = { file: this.id, line, column };
warning.frame = getCodeFrame( this.code, line, column );
}
this.bundle.warn( warning );
}
} }

4
src/ast/Node.js

@ -1,5 +1,5 @@
import { locate } from 'locate-character';
import { UNKNOWN } from './values.js'; import { UNKNOWN } from './values.js';
import getLocation from '../utils/getLocation.js';
export default class Node { export default class Node {
bind ( scope ) { bind ( scope ) {
@ -74,7 +74,7 @@ export default class Node {
locate () { locate () {
// useful for debugging // useful for debugging
const location = getLocation( this.module.code, this.start ); const location = locate( this.module.code, this.start, { offsetLine: 1 });
location.file = this.module.id; location.file = this.module.id;
location.toString = () => JSON.stringify( location ); location.toString = () => JSON.stringify( location );

17
src/ast/clone.js

@ -0,0 +1,17 @@
export default function clone ( node ) {
if ( !node ) return node;
if ( typeof node !== 'object' ) return node;
if ( Array.isArray( node ) ) {
const cloned = new Array( node.length );
for ( let i = 0; i < node.length; i += 1 ) cloned[i] = clone( node[i] );
return cloned;
}
const cloned = {};
for ( const key in node ) {
cloned[ key ] = clone( node[ key ] );
}
return cloned;
}

18
src/ast/nodes/CallExpression.js

@ -1,5 +1,3 @@
import getLocation from '../../utils/getLocation.js';
import error from '../../utils/error.js';
import Node from '../Node.js'; import Node from '../Node.js';
import isProgramLevel from '../utils/isProgramLevel.js'; import isProgramLevel from '../utils/isProgramLevel.js';
import callHasEffects from './shared/callHasEffects.js'; import callHasEffects from './shared/callHasEffects.js';
@ -10,16 +8,18 @@ export default class CallExpression extends Node {
const declaration = scope.findDeclaration( this.callee.name ); const declaration = scope.findDeclaration( this.callee.name );
if ( declaration.isNamespace ) { if ( declaration.isNamespace ) {
error({ this.module.error({
message: `Cannot call a namespace ('${this.callee.name}')`, code: 'CANNOT_CALL_NAMESPACE',
file: this.module.id, message: `Cannot call a namespace ('${this.callee.name}')`
pos: this.start, }, this.start );
loc: getLocation( this.module.code, this.start )
});
} }
if ( this.callee.name === 'eval' && declaration.isGlobal ) { if ( this.callee.name === 'eval' && declaration.isGlobal ) {
this.module.bundle.onwarn( `Use of \`eval\` (in ${this.module.id}) is strongly discouraged, as it poses security risks and may cause issues with minification. See https://github.com/rollup/rollup/wiki/Troubleshooting#avoiding-eval for more details` ); this.module.warn({
code: 'EVAL',
message: `Use of eval is strongly discouraged, as it poses security risks and may cause issues with minification`,
url: 'https://github.com/rollup/rollup/wiki/Troubleshooting#avoiding-eval'
}, this.start );
} }
} }

11
src/ast/nodes/ExportDefaultDeclaration.js

@ -1,6 +1,4 @@
import Node from '../Node.js'; import Node from '../Node.js';
import getLocation from '../../utils/getLocation.js';
import relativeId from '../../utils/relativeId.js';
const functionOrClassDeclaration = /^(?:Function|Class)Declaration/; const functionOrClassDeclaration = /^(?:Function|Class)Declaration/;
@ -55,7 +53,7 @@ export default class ExportDefaultDeclaration extends Node {
let declaration_start; let declaration_start;
if ( this.declaration ) { if ( this.declaration ) {
const statementStr = code.original.slice( this.start, this.end ); const statementStr = code.original.slice( this.start, this.end );
declaration_start = this.start + statementStr.match(/^\s*export\s+default\s+/)[0].length; declaration_start = this.start + statementStr.match(/^\s*export\s+default\s*/)[0].length;
} }
if ( this.shouldInclude || this.declaration.activated ) { if ( this.shouldInclude || this.declaration.activated ) {
@ -74,8 +72,11 @@ export default class ExportDefaultDeclaration extends Node {
const newlineSeparated = /\n/.test( code.original.slice( start, end ) ); const newlineSeparated = /\n/.test( code.original.slice( start, end ) );
if ( newlineSeparated ) { if ( newlineSeparated ) {
const { line, column } = getLocation( this.module.code, this.declaration.start ); this.module.warn({
this.module.bundle.onwarn( `${relativeId( this.module.id )} (${line}:${column}) Ambiguous default export (is a call expression, but looks like a function declaration). See https://github.com/rollup/rollup/wiki/Troubleshooting#ambiguous-default-export` ); code: 'AMBIGUOUS_DEFAULT_EXPORT',
message: `Ambiguous default export (is a call expression, but looks like a function declaration)`,
url: 'https://github.com/rollup/rollup/wiki/Troubleshooting#ambiguous-default-export'
}, this.declaration.start );
} }
} }
} }

5
src/ast/nodes/IfStatement.js

@ -17,9 +17,10 @@ function handleVarDeclarations ( node, scope ) {
function visit ( node ) { function visit ( node ) {
if ( node.type === 'VariableDeclaration' && node.kind === 'var' ) { if ( node.type === 'VariableDeclaration' && node.kind === 'var' ) {
node.initialise( scope );
node.declarations.forEach( declarator => { node.declarations.forEach( declarator => {
declarator.init = null;
declarator.initialise( scope );
extractNames( declarator.id ).forEach( name => { extractNames( declarator.id ).forEach( name => {
if ( !~hoistedVars.indexOf( name ) ) hoistedVars.push( name ); if ( !~hoistedVars.indexOf( name ) ) hoistedVars.push( name );
}); });

19
src/ast/nodes/LogicalExpression.js

@ -0,0 +1,19 @@
import Node from '../Node.js';
import { UNKNOWN } from '../values.js';
const operators = {
'&&': ( left, right ) => left && right,
'||': ( left, right ) => left || right
};
export default class LogicalExpression extends Node {
getValue () {
const leftValue = this.left.getValue();
if ( leftValue === UNKNOWN ) return UNKNOWN;
const rightValue = this.right.getValue();
if ( rightValue === UNKNOWN ) return UNKNOWN;
return operators[ this.operator ]( leftValue, rightValue );
}
}

8
src/ast/nodes/MemberExpression.js

@ -1,5 +1,4 @@
import isReference from 'is-reference'; import isReference from 'is-reference';
import getLocation from '../../utils/getLocation.js';
import relativeId from '../../utils/relativeId.js'; import relativeId from '../../utils/relativeId.js';
import Node from '../Node.js'; import Node from '../Node.js';
import { UNKNOWN } from '../values.js'; import { UNKNOWN } from '../values.js';
@ -34,8 +33,11 @@ export default class MemberExpression extends Node {
declaration = declaration.module.traceExport( part.name ); declaration = declaration.module.traceExport( part.name );
if ( !declaration ) { if ( !declaration ) {
const { line, column } = getLocation( this.module.code, this.start ); this.module.warn({
this.module.bundle.onwarn( `${relativeId( this.module.id )} (${line}:${column}) '${part.name}' is not exported by '${relativeId( exporterId )}'. See https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module` ); code: 'MISSING_EXPORT',
message: `'${part.name}' is not exported by '${relativeId( exporterId )}'`,
url: `https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module`
}, part.start );
this.replacement = 'undefined'; this.replacement = 'undefined';
return; return;
} }

12
src/ast/nodes/ThisExpression.js

@ -1,8 +1,4 @@
import Node from '../Node.js'; import Node from '../Node.js';
import getLocation from '../../utils/getLocation.js';
import relativeId from '../../utils/relativeId.js';
const warning = `The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten. See https://github.com/rollup/rollup/wiki/Troubleshooting#this-is-undefined for more information`;
export default class ThisExpression extends Node { export default class ThisExpression extends Node {
initialise ( scope ) { initialise ( scope ) {
@ -11,9 +7,11 @@ export default class ThisExpression extends Node {
if ( lexicalBoundary.isModuleScope ) { if ( lexicalBoundary.isModuleScope ) {
this.alias = this.module.context; this.alias = this.module.context;
if ( this.alias === 'undefined' ) { if ( this.alias === 'undefined' ) {
const { line, column } = getLocation( this.module.code, this.start ); this.module.warn({
const detail = `${relativeId( this.module.id )} (${line}:${column + 1})`; // use one-based column number convention code: 'THIS_IS_UNDEFINED',
this.module.bundle.onwarn( `${detail} ${warning}` ); message: `The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten`,
url: `https://github.com/rollup/rollup/wiki/Troubleshooting#this-is-undefined`
}, this.start );
} }
} }
} }

2
src/ast/nodes/index.js

@ -21,6 +21,7 @@ import Identifier from './Identifier.js';
import IfStatement from './IfStatement.js'; import IfStatement from './IfStatement.js';
import ImportDeclaration from './ImportDeclaration.js'; import ImportDeclaration from './ImportDeclaration.js';
import Literal from './Literal.js'; import Literal from './Literal.js';
import LogicalExpression from './LogicalExpression.js';
import MemberExpression from './MemberExpression.js'; import MemberExpression from './MemberExpression.js';
import NewExpression from './NewExpression.js'; import NewExpression from './NewExpression.js';
import ObjectExpression from './ObjectExpression.js'; import ObjectExpression from './ObjectExpression.js';
@ -59,6 +60,7 @@ export default {
IfStatement, IfStatement,
ImportDeclaration, ImportDeclaration,
Literal, Literal,
LogicalExpression,
MemberExpression, MemberExpression,
NewExpression, NewExpression,
ObjectExpression, ObjectExpression,

23
src/ast/nodes/shared/disallowIllegalReassignment.js

@ -1,28 +1,21 @@
import getLocation from '../../../utils/getLocation.js';
import error from '../../../utils/error.js';
// TODO tidy this up a bit (e.g. they can both use node.module.imports) // TODO tidy this up a bit (e.g. they can both use node.module.imports)
export default function disallowIllegalReassignment ( scope, node ) { export default function disallowIllegalReassignment ( scope, node ) {
if ( node.type === 'MemberExpression' && node.object.type === 'Identifier' ) { if ( node.type === 'MemberExpression' && node.object.type === 'Identifier' ) {
const declaration = scope.findDeclaration( node.object.name ); const declaration = scope.findDeclaration( node.object.name );
if ( declaration.isNamespace ) { if ( declaration.isNamespace ) {
error({ node.module.error({
message: `Illegal reassignment to import '${node.object.name}'`, code: 'ILLEGAL_NAMESPACE_REASSIGNMENT',
file: node.module.id, message: `Illegal reassignment to import '${node.object.name}'`
pos: node.start, }, node.start );
loc: getLocation( node.module.code, node.start )
});
} }
} }
else if ( node.type === 'Identifier' ) { else if ( node.type === 'Identifier' ) {
if ( node.module.imports[ node.name ] && !scope.contains( node.name ) ) { if ( node.module.imports[ node.name ] && !scope.contains( node.name ) ) {
error({ node.module.error({
message: `Illegal reassignment to import '${node.name}'`, code: 'ILLEGAL_REASSIGNMENT',
file: node.module.id, message: `Illegal reassignment to import '${node.name}'`
pos: node.start, }, node.start );
loc: getLocation( node.module.code, node.start )
});
} }
} }
} }

6
src/ast/scopes/ModuleScope.js

@ -1,4 +1,5 @@
import { forOwn } from '../../utils/object.js'; import { forOwn } from '../../utils/object.js';
import relativeId from '../../utils/relativeId.js';
import Scope from './Scope.js'; import Scope from './Scope.js';
export default class ModuleScope extends Scope { export default class ModuleScope extends Scope {
@ -26,7 +27,10 @@ export default class ModuleScope extends Scope {
if ( specifier.name !== '*' ) { if ( specifier.name !== '*' ) {
const declaration = specifier.module.traceExport( specifier.name ); const declaration = specifier.module.traceExport( specifier.name );
if ( !declaration ) { if ( !declaration ) {
this.module.bundle.onwarn( `Non-existent export '${specifier.name}' is imported from ${specifier.module.id} by ${this.module.id}` ); this.module.warn({
code: 'NON_EXISTENT_EXPORT',
message: `Non-existent export '${specifier.name}' is imported from ${relativeId( specifier.module.id )}`
}, specifier.specifier.start );
return; return;
} }

2
src/finalisers/amd.js

@ -2,8 +2,10 @@ import { getName, quotePath } from '../utils/map-helpers.js';
import getInteropBlock from './shared/getInteropBlock.js'; import getInteropBlock from './shared/getInteropBlock.js';
import getExportBlock from './shared/getExportBlock.js'; import getExportBlock from './shared/getExportBlock.js';
import esModuleExport from './shared/esModuleExport.js'; import esModuleExport from './shared/esModuleExport.js';
import warnOnBuiltins from './shared/warnOnBuiltins.js';
export default function amd ( bundle, magicString, { exportMode, indentString, intro, outro }, options ) { export default function amd ( bundle, magicString, { exportMode, indentString, intro, outro }, options ) {
warnOnBuiltins( bundle );
const deps = bundle.externalModules.map( quotePath ); const deps = bundle.externalModules.map( quotePath );
const args = bundle.externalModules.map( getName ); const args = bundle.externalModules.map( getName );

12
src/finalisers/iife.js

@ -1,9 +1,11 @@
import { blank } from '../utils/object.js'; import { blank } from '../utils/object.js';
import { getName } from '../utils/map-helpers.js'; import { getName } from '../utils/map-helpers.js';
import error from '../utils/error.js';
import getInteropBlock from './shared/getInteropBlock.js'; import getInteropBlock from './shared/getInteropBlock.js';
import getExportBlock from './shared/getExportBlock.js'; import getExportBlock from './shared/getExportBlock.js';
import getGlobalNameMaker from './shared/getGlobalNameMaker.js'; import getGlobalNameMaker from './shared/getGlobalNameMaker.js';
import propertyStringFor from './shared/propertyStringFor'; import propertyStringFor from './shared/propertyStringFor';
import warnOnBuiltins from './shared/warnOnBuiltins.js';
// thisProp('foo.bar-baz.qux') === "this.foo['bar-baz'].qux" // thisProp('foo.bar-baz.qux') === "this.foo['bar-baz'].qux"
const thisProp = propertyStringFor('this'); const thisProp = propertyStringFor('this');
@ -24,17 +26,21 @@ function setupNamespace ( keypath ) {
} }
export default function iife ( bundle, magicString, { exportMode, indentString, intro, outro }, options ) { export default function iife ( bundle, magicString, { exportMode, indentString, intro, outro }, options ) {
const globalNameMaker = getGlobalNameMaker( options.globals || blank(), bundle.onwarn ); const globalNameMaker = getGlobalNameMaker( options.globals || blank(), bundle );
const name = options.moduleName; const name = options.moduleName;
const isNamespaced = name && ~name.indexOf( '.' ); const isNamespaced = name && ~name.indexOf( '.' );
const dependencies = bundle.externalModules.map( globalNameMaker ); warnOnBuiltins( bundle );
const dependencies = bundle.externalModules.map( globalNameMaker );
const args = bundle.externalModules.map( getName ); const args = bundle.externalModules.map( getName );
if ( exportMode !== 'none' && !name ) { 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' ) { if ( exportMode === 'named' ) {

8
src/finalisers/shared/getGlobalNameMaker.js

@ -1,11 +1,15 @@
export default function getGlobalNameMaker ( globals, onwarn ) { export default function getGlobalNameMaker ( globals, bundle ) {
const fn = typeof globals === 'function' ? globals : id => globals[ id ]; const fn = typeof globals === 'function' ? globals : id => globals[ id ];
return function ( module ) { return function ( module ) {
const name = fn( module.id ); const name = fn( module.id );
if ( name ) return name; if ( name ) return name;
onwarn( `No name was provided for external module '${module.id}' in options.globals – guessing '${module.name}'` ); bundle.warn({
code: 'MISSING_GLOBAL_NAME',
message: `No name was provided for external module '${module.id}' in options.globals – guessing '${module.name}'`
});
return module.name; return module.name;
}; };
} }

42
src/finalisers/shared/warnOnBuiltins.js

@ -0,0 +1,42 @@
const builtins = {
process: true,
events: true,
stream: true,
util: true,
path: true,
buffer: true,
querystring: true,
url: true,
string_decoder: true,
punycode: true,
http: true,
https: true,
os: true,
assert: true,
constants: true,
timers: true,
console: true,
vm: true,
zlib: true,
tty: true,
domain: true
};
// Creating a browser bundle that depends on Node.js built-in modules ('util'). You might need to include https://www.npmjs.com/package/rollup-plugin-node-builtins
export default function warnOnBuiltins ( bundle ) {
const externalBuiltins = bundle.externalModules
.filter( mod => mod.id in builtins )
.map( mod => mod.id );
if ( !externalBuiltins.length ) return;
const detail = externalBuiltins.length === 1 ?
`module ('${externalBuiltins[0]}')` :
`modules (${externalBuiltins.slice( 0, -1 ).map( name => `'${name}'` ).join( ', ' )} and '${externalBuiltins.pop()}')`;
bundle.warn({
code: 'MISSING_NODE_BUILTINS',
message: `Creating a browser bundle that depends on Node.js built-in ${detail}. You might need to include https://www.npmjs.com/package/rollup-plugin-node-builtins`
});
}

11
src/finalisers/umd.js

@ -1,10 +1,12 @@
import { blank } from '../utils/object.js'; import { blank } from '../utils/object.js';
import { getName, quotePath, req } from '../utils/map-helpers.js'; import { getName, quotePath, req } from '../utils/map-helpers.js';
import error from '../utils/error.js';
import getInteropBlock from './shared/getInteropBlock.js'; import getInteropBlock from './shared/getInteropBlock.js';
import getExportBlock from './shared/getExportBlock.js'; import getExportBlock from './shared/getExportBlock.js';
import getGlobalNameMaker from './shared/getGlobalNameMaker.js'; import getGlobalNameMaker from './shared/getGlobalNameMaker.js';
import esModuleExport from './shared/esModuleExport.js'; import esModuleExport from './shared/esModuleExport.js';
import propertyStringFor from './shared/propertyStringFor.js'; import propertyStringFor from './shared/propertyStringFor.js';
import warnOnBuiltins from './shared/warnOnBuiltins.js';
// globalProp('foo.bar-baz') === "global.foo['bar-baz']" // globalProp('foo.bar-baz') === "global.foo['bar-baz']"
const globalProp = propertyStringFor('global'); const globalProp = propertyStringFor('global');
@ -27,10 +29,15 @@ const wrapperOutro = '\n\n})));';
export default function umd ( bundle, magicString, { exportMode, indentString, intro, outro }, options ) { export default function umd ( bundle, magicString, { exportMode, indentString, intro, outro }, options ) {
if ( exportMode !== 'none' && !options.moduleName ) { 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'
});
} }
const globalNameMaker = getGlobalNameMaker( options.globals || blank(), bundle.onwarn ); warnOnBuiltins( bundle );
const globalNameMaker = getGlobalNameMaker( options.globals || blank(), bundle );
const amdDeps = bundle.externalModules.map( quotePath ); const amdDeps = bundle.externalModules.map( quotePath );
const cjsDeps = bundle.externalModules.map( req ); const cjsDeps = bundle.externalModules.map( req );

26
src/rollup.js

@ -4,6 +4,7 @@ import { writeFile } from './utils/fs.js';
import { assign, keys } from './utils/object.js'; import { assign, keys } from './utils/object.js';
import { mapSequence } from './utils/promise.js'; import { mapSequence } from './utils/promise.js';
import validateKeys from './utils/validateKeys.js'; import validateKeys from './utils/validateKeys.js';
import error from './utils/error.js';
import { SOURCEMAPPING_URL } from './utils/sourceMappingURL.js'; import { SOURCEMAPPING_URL } from './utils/sourceMappingURL.js';
import Bundle from './Bundle.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' ); 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 ); const err = validateKeys( keys(options), ALLOWED_KEYS );
if ( error ) return error; if ( err ) return err;
return null; return null;
} }
export function rollup ( options ) { export function rollup ( options ) {
const error = checkOptions ( options ); const err = checkOptions ( options );
if ( error ) return Promise.reject( error ); if ( err ) return Promise.reject( err );
const bundle = new Bundle( options ); const bundle = new Bundle( options );
@ -67,7 +68,17 @@ export function rollup ( options ) {
return bundle.build().then( () => { return bundle.build().then( () => {
timeEnd( '--BUILD--' ); timeEnd( '--BUILD--' );
function generate ( options ) { function generate ( options = {} ) {
if ( !options.format ) {
bundle.warn({
code: 'MISSING_FORMAT',
message: `No format option was supplied – defaulting to 'es'`,
url: `https://github.com/rollup/rollup/wiki/JavaScript-API#format`
});
options.format = 'es';
}
timeStart( '--GENERATE--' ); timeStart( '--GENERATE--' );
const rendered = bundle.render( options ); const rendered = bundle.render( options );
@ -95,7 +106,10 @@ export function rollup ( options ) {
generate, generate,
write: options => { write: options => {
if ( !options || !options.dest ) { 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; const dest = options.dest;

13
src/utils/collapseSourcemaps.js

@ -1,4 +1,5 @@
import { encode } from 'sourcemap-codec'; import { encode } from 'sourcemap-codec';
import error from './error.js';
import { dirname, relative, resolve } from './path.js'; import { dirname, relative, resolve } from './path.js';
class Source { class Source {
@ -51,7 +52,9 @@ class Link {
} else if ( sourcesContent[ sourceIndex ] == null ) { } else if ( sourcesContent[ sourceIndex ] == null ) {
sourcesContent[ sourceIndex ] = traced.source.content; sourcesContent[ sourceIndex ] = traced.source.content;
} else if ( traced.source.content != 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; segment[1] = sourceIndex;
@ -98,7 +101,7 @@ class Link {
} }
} }
export default function collapseSourcemaps ( file, map, modules, bundleSourcemapChain, onwarn ) { export default function collapseSourcemaps ( bundle, file, map, modules, bundleSourcemapChain ) {
const moduleSources = modules.filter( module => !module.excludeFromSourcemap ).map( module => { const moduleSources = modules.filter( module => !module.excludeFromSourcemap ).map( module => {
let sourceMapChain = module.sourceMapChain; let sourceMapChain = module.sourceMapChain;
@ -127,7 +130,11 @@ export default function collapseSourcemaps ( file, map, modules, bundleSourcemap
sourceMapChain.forEach( map => { sourceMapChain.forEach( map => {
if ( map.missing ) { if ( map.missing ) {
onwarn( `Sourcemap is likely to be incorrect: a plugin${map.plugin ? ` ('${map.plugin}')` : ``} was used to transform files, but didn't generate a sourcemap for the transformation. Consult https://github.com/rollup/rollup/wiki/Troubleshooting and the plugin documentation for more information` ); bundle.warn({
code: 'SOURCEMAP_BROKEN',
message: `Sourcemap is likely to be incorrect: a plugin${map.plugin ? ` ('${map.plugin}')` : ``} was used to transform files, but didn't generate a sourcemap for the transformation. Consult the plugin documentation for help`,
url: `https://github.com/rollup/rollup/wiki/Troubleshooting#sourcemap-is-likely-to-be-incorrect`
});
map = { map = {
names: [], names: [],

18
src/utils/defaults.js

@ -1,6 +1,7 @@
import { lstatSync, readdirSync, readFileSync, realpathSync } from './fs.js'; // eslint-disable-line import { lstatSync, readdirSync, readFileSync, realpathSync } from './fs.js'; // eslint-disable-line
import { basename, dirname, isAbsolute, resolve } from './path.js'; import { basename, dirname, isAbsolute, resolve } from './path.js';
import { blank } from './object.js'; import { blank } from './object.js';
import error from './error.js';
export function load ( id ) { export function load ( id ) {
return readFileSync( id, 'utf-8' ); return readFileSync( id, 'utf-8' );
@ -27,7 +28,13 @@ function addJsExtensionIfNecessary ( file ) {
} }
export function resolveId ( importee, importer ) { 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 // absolute paths are left untouched
if ( isAbsolute( importee ) ) return addJsExtensionIfNecessary( resolve( importee ) ); if ( isAbsolute( importee ) ) return addJsExtensionIfNecessary( resolve( importee ) );
@ -45,9 +52,10 @@ export function resolveId ( importee, importer ) {
export function makeOnwarn () { export function makeOnwarn () {
const warned = blank(); const warned = blank();
return msg => { return warning => {
if ( msg in warned ) return; const str = warning.toString();
console.error( msg ); //eslint-disable-line no-console if ( str in warned ) return;
warned[ msg ] = true; console.error( str ); //eslint-disable-line no-console
warned[ str ] = true;
}; };
} }

41
src/utils/getCodeFrame.js

@ -0,0 +1,41 @@
function spaces ( i ) {
let result = '';
while ( i-- ) result += ' ';
return result;
}
function tabsToSpaces ( str ) {
return str.replace( /^\t+/, match => match.split( '\t' ).join( ' ' ) );
}
export default function getCodeFrame ( source, line, column ) {
let lines = source.split( '\n' );
const frameStart = Math.max( 0, line - 3 );
let frameEnd = Math.min( line + 2, lines.length );
lines = lines.slice( frameStart, frameEnd );
while ( !/\S/.test( lines[ lines.length - 1 ] ) ) {
lines.pop();
frameEnd -= 1;
}
const digits = String( frameEnd ).length;
return lines
.map( ( str, i ) => {
const isErrorLine = frameStart + i + 1 === line;
let lineNum = String( i + frameStart + 1 );
while ( lineNum.length < digits ) lineNum = ` ${lineNum}`;
if ( isErrorLine ) {
const indicator = spaces( digits + 2 + tabsToSpaces( str.slice( 0, column ) ).length ) + '^';
return `${lineNum}: ${tabsToSpaces( str )}\n${indicator}`;
}
return `${lineNum}: ${tabsToSpaces( str )}`;
})
.join( '\n' );
}

17
src/utils/getExportMode.js

@ -1,7 +1,11 @@
import { keys } from './object.js'; import { keys } from './object.js';
import error from './error.js';
function badExports ( option, keys ) { 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} ) { export default function getExportMode ( bundle, {exports: exportMode, moduleName, format} ) {
@ -24,14 +28,21 @@ export default function getExportMode ( bundle, {exports: exportMode, moduleName
exportMode = 'default'; exportMode = 'default';
} else { } else {
if ( bundle.entryModule.exports.default && format !== 'es') { if ( bundle.entryModule.exports.default && format !== 'es') {
bundle.onwarn( `Using named and default exports together. Consumers of your bundle will have to use ${moduleName || 'bundle'}['default'] to access the default export, which may not be what you want. Use \`exports: 'named'\` to disable this warning. See https://github.com/rollup/rollup/wiki/JavaScript-API#exports for more information` ); bundle.warn({
code: 'MIXED_EXPORTS',
message: `Using named and default exports together. Consumers of your bundle will have to use ${moduleName || 'bundle'}['default'] to access the default export, which may not be what you want. Use \`exports: 'named'\` to disable this warning`,
url: `https://github.com/rollup/rollup/wiki/JavaScript-API#exports`
});
} }
exportMode = 'named'; exportMode = 'named';
} }
} }
if ( !/(?:default|named|none)/.test( exportMode ) ) { 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; return exportMode;

20
src/utils/getLocation.js

@ -1,20 +0,0 @@
export default function getLocation ( source, charIndex ) {
const lines = source.split( '\n' );
const len = lines.length;
let lineStart = 0;
let i;
for ( i = 0; i < len; i += 1 ) {
const line = lines[i];
const lineEnd = lineStart + line.length + 1; // +1 for newline
if ( lineEnd > charIndex ) {
return { line: i + 1, column: charIndex - lineStart };
}
lineStart = lineEnd;
}
throw new Error( 'Could not determine location of character' );
}

21
src/utils/object.js

@ -17,24 +17,3 @@ export function assign ( target, ...sources ) {
return target; return target;
} }
const isArray = Array.isArray;
// used for cloning ASTs. Not for use with cyclical structures!
export function deepClone ( obj ) {
if ( !obj ) return obj;
if ( typeof obj !== 'object' ) return obj;
if ( isArray( obj ) ) {
const clone = new Array( obj.length );
for ( let i = 0; i < obj.length; i += 1 ) clone[i] = deepClone( obj[i] );
return clone;
}
const clone = {};
for ( const key in obj ) {
clone[ key ] = deepClone( obj[ key ] );
}
return clone;
}

25
src/utils/transform.js

@ -1,4 +1,6 @@
import { decode } from 'sourcemap-codec'; import { decode } from 'sourcemap-codec';
import error from './error.js';
import relativeId from './relativeId.js';
export default function transform ( source, id, plugins ) { export default function transform ( source, id, plugins ) {
const sourceMapChain = []; const sourceMapChain = [];
@ -11,6 +13,7 @@ export default function transform ( source, id, plugins ) {
const originalCode = source.code; const originalCode = source.code;
let ast = source.ast; let ast = source.ast;
let errored = false;
return plugins.reduce( ( promise, plugin ) => { return plugins.reduce( ( promise, plugin ) => {
return promise.then( previous => { return promise.then( previous => {
@ -41,15 +44,21 @@ export default function transform ( source, id, plugins ) {
return result.code; return result.code;
}); });
}).catch( err => { }).catch( err => {
if ( !err.rollupTransform ) { // TODO this all seems a bit hacky
err.rollupTransform = true; if ( errored ) throw err;
err.id = id; errored = true;
err.plugin = plugin.name;
err.message = `Error transforming ${id}${plugin.name ? ` with '${plugin.name}' plugin` : ''}: ${err.message}`; err.plugin = plugin.name;
}
throw err; throw err;
}); });
}, Promise.resolve( source.code ) ) }, Promise.resolve( source.code ) )
.catch( err => {
.then( code => ({ code, originalCode, originalSourceMap, ast, sourceMapChain }) ); 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 }) );
} }

9
src/utils/transformBundle.js

@ -1,4 +1,5 @@
import { decode } from 'sourcemap-codec'; import { decode } from 'sourcemap-codec';
import error from './error.js';
export default function transformBundle ( code, plugins, sourceMapChain, options ) { export default function transformBundle ( code, plugins, sourceMapChain, options ) {
return plugins.reduce( ( code, plugin ) => { return plugins.reduce( ( code, plugin ) => {
@ -9,9 +10,11 @@ export default function transformBundle ( code, plugins, sourceMapChain, options
try { try {
result = plugin.transformBundle( code, { format : options.format } ); result = plugin.transformBundle( code, { format : options.format } );
} catch ( err ) { } catch ( err ) {
err.plugin = plugin.name; error({
err.message = `Error transforming bundle${plugin.name ? ` with '${plugin.name}' plugin` : ''}: ${err.message}`; code: 'BAD_BUNDLE_TRANSFORMER',
throw err; message: `Error transforming bundle${plugin.name ? ` with '${plugin.name}' plugin` : ''}: ${err.message}`,
plugin: plugin.name
});
} }
if ( result == null ) return code; if ( result == null ) return code;

2
test/cli/sourcemap-newline/_config.js

@ -2,7 +2,7 @@ const assert = require( 'assert' );
module.exports = { module.exports = {
description: 'adds a newline after the sourceMappingURL comment (#756)', description: 'adds a newline after the sourceMappingURL comment (#756)',
command: 'rollup -i main.js -m inline', command: 'rollup -i main.js -f es -m inline',
result: code => { result: code => {
assert.equal( code.slice( -1 ), '\n' ); assert.equal( code.slice( -1 ), '\n' );
} }

2
test/form/skips-dead-branches-i/_config.js

@ -1,3 +1,3 @@
module.exports = { module.exports = {
description: 'skips a dead branch (h)' description: 'skips a dead branch (i)'
}; };

3
test/form/skips-dead-branches-j/_config.js

@ -0,0 +1,3 @@
module.exports = {
description: 'skips a dead branch (j)'
};

7
test/form/skips-dead-branches-j/_expected/amd.js

@ -0,0 +1,7 @@
define(function () { 'use strict';
{
console.log( 'true' );
}
});

5
test/form/skips-dead-branches-j/_expected/cjs.js

@ -0,0 +1,5 @@
'use strict';
{
console.log( 'true' );
}

3
test/form/skips-dead-branches-j/_expected/es.js

@ -0,0 +1,3 @@
{
console.log( 'true' );
}

8
test/form/skips-dead-branches-j/_expected/iife.js

@ -0,0 +1,8 @@
(function () {
'use strict';
{
console.log( 'true' );
}
}());

11
test/form/skips-dead-branches-j/_expected/umd.js

@ -0,0 +1,11 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
typeof define === 'function' && define.amd ? define(factory) :
(factory());
}(this, (function () { 'use strict';
{
console.log( 'true' );
}
})));

5
test/form/skips-dead-branches-j/main.js

@ -0,0 +1,5 @@
if ( true && true ) {
console.log( 'true' );
} else {
console.log( 'false' );
}

11
test/function/assign-namespace-to-var/_config.js

@ -2,9 +2,10 @@ const assert = require( 'assert' );
module.exports = { module.exports = {
description: 'allows a namespace to be assigned to a variable', description: 'allows a namespace to be assigned to a variable',
warnings: warnings => { warnings: [
assert.deepEqual( warnings, [ {
'Generated an empty bundle' code: 'EMPTY_BUNDLE',
]); message: 'Generated an empty bundle'
} }
]
}; };

19
test/function/cannot-call-external-namespace/_config.js

@ -3,10 +3,19 @@ var assert = require( 'assert' );
module.exports = { module.exports = {
description: 'errors if code calls an external namespace', description: 'errors if code calls an external namespace',
error: function ( err ) { error: {
assert.equal( err.message, 'Cannot call a namespace (\'foo\')' ); code: 'CANNOT_CALL_NAMESPACE',
assert.equal( err.file.replace( /\//g, path.sep ), path.resolve( __dirname, 'main.js' ) ); message: `Cannot call a namespace ('foo')`,
assert.equal( err.pos, 28 ); pos: 28,
assert.deepEqual( err.loc, { line: 2, column: 0 }); loc: {
file: path.resolve( __dirname, 'main.js' ),
line: 2,
column: 0
},
frame: `
1: import * as foo from 'foo';
2: foo();
^
`
} }
}; };

19
test/function/cannot-call-internal-namespace/_config.js

@ -3,10 +3,19 @@ var assert = require( 'assert' );
module.exports = { module.exports = {
description: 'errors if code calls an internal namespace', description: 'errors if code calls an internal namespace',
error: function ( err ) { error: {
assert.equal( err.message, 'Cannot call a namespace (\'foo\')' ); code: 'CANNOT_CALL_NAMESPACE',
assert.equal( err.file.replace( /\//g, path.sep ), path.resolve( __dirname, 'main.js' ) ); message: `Cannot call a namespace ('foo')`,
assert.equal( err.pos, 33 ); pos: 33,
assert.deepEqual( err.loc, { line: 2, column: 0 }); loc: {
file: path.resolve( __dirname, 'main.js' ),
line: 2,
column: 0
},
frame: `
1: import * as foo from './foo.js';
2: foo();
^
`
} }
}; };

16
test/function/cannot-import-self/_config.js

@ -1,8 +1,20 @@
var path = require( 'path' );
var assert = require( 'assert' ); var assert = require( 'assert' );
module.exports = { module.exports = {
description: 'prevents a module importing itself', description: 'prevents a module importing itself',
error: function ( err ) { error: {
assert.ok( /A module cannot import itself/.test( err.message ) ); 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';
^
`
} }
}; };

5
test/function/check-resolve-for-entry/_config.js

@ -5,7 +5,8 @@ module.exports = {
options: { options: {
entry: '/not/a/path/that/actually/really/exists' entry: '/not/a/path/that/actually/really/exists'
}, },
error: function ( err ) { error: {
assert.ok( /Could not resolve entry/.test( err.message ) ); code: 'UNRESOLVED_ENTRY',
message: 'Could not resolve entry (/not/a/path/that/actually/really/exists)'
} }
}; };

1
test/function/custom-external-resolver-async/_config.js

@ -1,6 +1,5 @@
var path = require( 'path' ); var path = require( 'path' );
var assert = require( 'assert' ); var assert = require( 'assert' );
var Promise = require( 'sander' ).Promise;
module.exports = { module.exports = {
description: 'uses a custom external path resolver (asynchronous)', description: 'uses a custom external path resolver (asynchronous)',

13
test/function/custom-path-resolver-async/_config.js

@ -6,7 +6,6 @@ module.exports = {
options: { options: {
plugins: [{ plugins: [{
resolveId: function ( importee, importer ) { resolveId: function ( importee, importer ) {
var Promise = require( 'sander' ).Promise;
var resolved; var resolved;
if ( path.normalize(importee) === path.resolve( __dirname, 'main.js' ) ) return importee; if ( path.normalize(importee) === path.resolve( __dirname, 'main.js' ) ) return importee;
@ -21,11 +20,13 @@ module.exports = {
} }
}] }]
}, },
warnings: function ( warnings ) { warnings: [
assert.deepEqual( warnings, [ {
`'path' is imported by main.js, but could not be resolved – treating it as an external dependency. For help see https://github.com/rollup/rollup/wiki/Troubleshooting#treating-module-as-external-dependency` code: 'UNRESOLVED_IMPORT',
]); message: `'path' is imported by main.js, but could not be resolved – treating it as an external dependency`,
}, url: `https://github.com/rollup/rollup/wiki/Troubleshooting#treating-module-as-external-dependency`
}
],
exports: function ( exports ) { exports: function ( exports ) {
assert.strictEqual( exports.path, require( 'path' ) ); assert.strictEqual( exports.path, require( 'path' ) );
} }

10
test/function/custom-path-resolver-plural-b/_config.js

@ -5,21 +5,21 @@ module.exports = {
options: { options: {
plugins: [ plugins: [
{ {
resolveId: function () { resolveId () {
throw new Error( 'nope' ); throw new Error( 'nope' );
}, },
load: function ( id ) { load ( id ) {
if ( id === 'main' ) return 'assert.ok( false );'; if ( id === 'main' ) return 'assert.ok( false );';
} }
}, },
{ {
resolveId: function ( importee, importer ) { resolveId ( importee, importer ) {
return 'main'; return 'main';
} }
} }
] ]
}, },
error: function ( err ) { error: {
assert.equal( err.message, 'nope' ); message: 'nope'
} }
}; };

12
test/function/custom-path-resolver-sync/_config.js

@ -13,11 +13,13 @@ module.exports = {
} }
}] }]
}, },
warnings: function ( warnings ) { warnings: [
assert.deepEqual( warnings, [ {
`'path' is imported by main.js, but could not be resolved – treating it as an external dependency. For help see https://github.com/rollup/rollup/wiki/Troubleshooting#treating-module-as-external-dependency` code: 'UNRESOLVED_IMPORT',
]); message: `'path' is imported by main.js, but could not be resolved – treating it as an external dependency`,
}, url: `https://github.com/rollup/rollup/wiki/Troubleshooting#treating-module-as-external-dependency`
}
],
exports: function ( exports ) { exports: function ( exports ) {
assert.strictEqual( exports.path, require( 'path' ) ); assert.strictEqual( exports.path, require( 'path' ) );
} }

19
test/function/default-not-reexported/_config.js

@ -1,8 +1,23 @@
const path = require( 'path' );
const assert = require( 'assert' ); const assert = require( 'assert' );
module.exports = { module.exports = {
description: 'default export is not re-exported with export *', description: 'default export is not re-exported with export *',
error ( 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` ); 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`
} }
}; };

12
test/function/does-not-hang-on-missing-module/_config.js

@ -2,11 +2,13 @@ var assert = require( 'assert' );
module.exports = { module.exports = {
description: 'does not hang on missing module (#53)', description: 'does not hang on missing module (#53)',
warnings: warnings => { warnings: [
assert.deepEqual( warnings, [ {
`'unlessYouCreatedThisFileForSomeReason' is imported by main.js, but could not be resolved – treating it as an external dependency. For help see https://github.com/rollup/rollup/wiki/Troubleshooting#treating-module-as-external-dependency` code: 'UNRESOLVED_IMPORT',
]); message: `'unlessYouCreatedThisFileForSomeReason' is imported by main.js, but could not be resolved – treating it as an external dependency`,
}, url: `https://github.com/rollup/rollup/wiki/Troubleshooting#treating-module-as-external-dependency`
}
],
runtimeError: function ( error ) { runtimeError: function ( error ) {
assert.equal( "Cannot find module 'unlessYouCreatedThisFileForSomeReason'", error.message ); assert.equal( "Cannot find module 'unlessYouCreatedThisFileForSomeReason'", error.message );
} }

16
test/function/double-default-export/_config.js

@ -3,7 +3,19 @@ const assert = require( 'assert' );
module.exports = { module.exports = {
description: 'throws on double default exports', description: 'throws on double default exports',
error: err => { error: {
assert.equal( err.message, `Duplicate export 'default' (2:7) in ${path.resolve(__dirname, 'foo.js')}` ); 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;
^
`
} }
}; };

13
test/function/double-named-export-from/_config.js

@ -1,13 +0,0 @@
const { resolve } = require('path');
const assert = require( 'assert' );
const r = path => resolve( __dirname, path );
module.exports = {
description: 'throws on duplicate export * from',
warnings ( warnings ) {
assert.deepEqual( warnings, [
`Conflicting namespaces: ${r('main.js')} re-exports 'foo' from both ${r('foo.js')} (will be ignored) and ${r('deep.js')}.`
]);
}
};

17
test/function/double-named-export/_config.js

@ -3,7 +3,20 @@ const assert = require( 'assert' );
module.exports = { module.exports = {
description: 'throws on duplicate named exports', description: 'throws on duplicate named exports',
error: err => { error: {
assert.equal( err.message, `Duplicate export 'foo' (3:9) in ${path.resolve(__dirname, 'foo.js')}` ); 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 };
^
`
} }
}; };

17
test/function/double-named-reexport/_config.js

@ -3,7 +3,20 @@ const assert = require( 'assert' );
module.exports = { module.exports = {
description: 'throws on duplicate named exports', description: 'throws on duplicate named exports',
error: err => { error: {
assert.equal( err.message, `Duplicate export 'foo' (3:9) in ${path.resolve(__dirname, 'foo.js')}` ); 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';
^
`
} }
}; };

20
test/function/duplicate-import-fails/_config.js

@ -3,10 +3,22 @@ var assert = require( 'assert' );
module.exports = { module.exports = {
description: 'disallows duplicate imports', description: 'disallows duplicate imports',
error: function ( err ) { error: {
assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); code: 'DUPLICATE_IMPORT',
assert.deepEqual( err.loc, { line: 2, column: 9 }); message: `Duplicated import 'a'`,
assert.ok( /Duplicated import/.test( err.message ) ); 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);
`
} }
}; };

18
test/function/duplicate-import-specifier-fails/_config.js

@ -3,10 +3,20 @@ var assert = require( 'assert' );
module.exports = { module.exports = {
description: 'disallows duplicate import specifiers', description: 'disallows duplicate import specifiers',
error: function ( err ) { error: {
assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); code: 'DUPLICATE_IMPORT',
assert.deepEqual( err.loc, { line: 1, column: 12 }); message: `Duplicated import 'a'`,
assert.ok( /Duplicated import/.test( err.message ) ); 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);
`
} }
}; };

29
test/function/empty-exports/_config.js

@ -1,12 +1,23 @@
const assert = require( 'assert' );
const path = require( 'path' );
module.exports = { module.exports = {
description: 'warns on export {}, but does not fail', description: 'warns on export {}, but does not fail',
warnings: warnings => { warnings: [
assert.deepEqual( warnings, [ {
`Module ${path.resolve( __dirname, 'main.js' )} has an empty export declaration`, code: 'EMPTY_EXPORT',
'Generated an empty bundle' message: 'Empty export declaration',
]); pos: 0,
} loc: {
file: require( 'path' ).resolve( __dirname, 'main.js' ),
line: 1,
column: 0
},
frame: `
1: export {};
^
`
},
{
code: 'EMPTY_BUNDLE',
message: 'Generated an empty bundle'
}
]
}; };

8
test/function/export-default-no-space/_config.js

@ -0,0 +1,8 @@
const assert = require( 'assert' );
module.exports = {
description: 'handles default exports with no space before declaration',
exports: exports => {
assert.deepEqual( exports, {} );
}
};

1
test/function/export-default-no-space/main.js

@ -0,0 +1 @@
export default{};

19
test/function/export-not-at-top-level-fails/_config.js

@ -3,9 +3,20 @@ var assert = require( 'assert' );
module.exports = { module.exports = {
description: 'disallows non-top-level exports', description: 'disallows non-top-level exports',
error: function ( err ) { error: {
assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); code: 'PARSE_ERROR',
assert.deepEqual( err.loc, { line: 2, column: 2 }); message: `'import' and 'export' may only appear at the top level`,
assert.ok( /may only appear at the top level/.test( err.message ) ); pos: 19,
loc: {
file: path.resolve( __dirname, 'main.js' ),
line: 2,
column: 2
},
frame: `
1: function foo() {
2: export { foo };
^
3: }
`
} }
}; };

5
test/function/export-type-mismatch-b/_config.js

@ -5,7 +5,8 @@ module.exports = {
bundleOptions: { bundleOptions: {
exports: 'blah' exports: 'blah'
}, },
generateError: function ( err ) { generateError: {
assert.ok( /options\.exports must be 'default', 'named', 'none', 'auto', or left unspecified/.test( err.message ) ); code: 'INVALID_EXPORT_OPTION',
message: `options.exports must be 'default', 'named', 'none', 'auto', or left unspecified (defaults to 'auto')`
} }
}; };

5
test/function/export-type-mismatch-c/_config.js

@ -5,7 +5,8 @@ module.exports = {
bundleOptions: { bundleOptions: {
exports: 'none' exports: 'none'
}, },
generateError: function ( err ) { generateError: {
assert.ok( /'none' was specified for options\.exports/.test( err.message ) ); code: 'INVALID_EXPORT_OPTION',
message: `'none' was specified for options.exports, but entry module has following exports: default`
} }
}; };

5
test/function/export-type-mismatch/_config.js

@ -5,7 +5,8 @@ module.exports = {
bundleOptions: { bundleOptions: {
exports: 'default' exports: 'default'
}, },
generateError: function ( err ) { generateError: {
assert.ok( /'default' was specified for options\.exports/.test( err.message ) ); code: 'INVALID_EXPORT_OPTION',
message: `'default' was specified for options.exports, but entry module has following exports: foo`
} }
}; };

19
test/function/import-not-at-top-level-fails/_config.js

@ -3,9 +3,20 @@ var assert = require( 'assert' );
module.exports = { module.exports = {
description: 'disallows non-top-level imports', description: 'disallows non-top-level imports',
error: function ( err ) { error: {
assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); code: 'PARSE_ERROR',
assert.deepEqual( err.loc, { line: 2, column: 2 }); message: `'import' and 'export' may only appear at the top level`,
assert.ok( /may only appear at the top level/.test( err.message ) ); pos: 19,
loc: {
file: path.resolve( __dirname, 'main.js' ),
line: 2,
column: 2
},
frame: `
1: function foo() {
2: import foo from './foo.js';
^
3: }
`
} }
}; };

2
test/function/import-not-at-top-level-fails/main.js

@ -1,3 +1,3 @@
function foo() { function foo() {
import foo from './foo'; import foo from './foo.js';
} }

19
test/function/import-of-unexported-fails/_config.js

@ -1,8 +1,23 @@
var path = require( 'path' );
var assert = require( 'assert' ); var assert = require( 'assert' );
module.exports = { module.exports = {
description: 'marking an imported, but unexported, identifier should throw', description: 'marking an imported, but unexported, identifier should throw',
error: function ( err ) { error: {
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` ); 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`
} }
}; };

6
test/function/load-returns-string-or-null/_config.js

@ -4,12 +4,14 @@ module.exports = {
description: 'throws error if load returns something wacky', description: 'throws error if load returns something wacky',
options: { options: {
plugins: [{ plugins: [{
name: 'bad-plugin',
load: function () { load: function () {
return 42; return 42;
} }
}] }]
}, },
error: function ( err ) { error: {
assert.ok( /load hook should return a string, a \{ code, map \} object, or nothing\/null/.test( err.message ) ); code: 'BAD_LOADER',
message: `Error loading main.js: plugin load hook should return a string, a { code, map } object, or nothing/null`
} }
}; };

11
test/function/module-tree/_config.js

@ -34,9 +34,10 @@ module.exports = {
} }
]); ]);
}, },
warnings: warnings => { warnings: [
assert.deepEqual( warnings, [ {
'Generated an empty bundle' code: 'EMPTY_BUNDLE',
]); message: 'Generated an empty bundle'
} }
]
}; };

24
test/function/namespace-missing-export/_config.js

@ -1,9 +1,21 @@
var assert = require( 'assert' );
module.exports = { module.exports = {
options: { warnings: [
onwarn: function ( msg ) { {
assert.equal( msg, `main.js (3:21) 'foo' is not exported by 'empty.js'. See https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module` ); code: 'MISSING_EXPORT',
message: `'foo' is not exported by 'empty.js'`,
pos: 61,
loc: {
file: require( 'path' ).resolve( __dirname, 'main.js' ),
line: 3,
column: 25
},
frame: `
1: import * as mod from './empty.js';
2:
3: assert.equal( typeof mod.foo, 'undefined' );
^
`,
url: `https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module`
} }
} ]
}; };

19
test/function/namespace-reassign-import-fails/_config.js

@ -3,10 +3,21 @@ var assert = require( 'assert' );
module.exports = { module.exports = {
description: 'disallows reassignments to namespace exports', description: 'disallows reassignments to namespace exports',
error: function ( err ) { error: {
assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); code: 'ILLEGAL_NAMESPACE_REASSIGNMENT',
assert.deepEqual( err.loc, { line: 3, column: 0 }); message: `Illegal reassignment to import 'exp'`,
assert.ok( /Illegal reassignment/.test( err.message ) ); 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;
^
`
} }
}; };

19
test/function/namespace-update-import-fails/_config.js

@ -3,10 +3,21 @@ var assert = require( 'assert' );
module.exports = { module.exports = {
description: 'disallows updates to namespace exports', description: 'disallows updates to namespace exports',
error: function ( err ) { error: {
assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); code: 'ILLEGAL_NAMESPACE_REASSIGNMENT',
assert.deepEqual( err.loc, { line: 3, column: 0 }); message: `Illegal reassignment to import 'exp'`,
assert.ok( /Illegal reassignment/.test( err.message ) ); pos: 31,
loc: {
file: path.resolve( __dirname, 'main.js' ),
line: 3,
column: 0
},
frame: `
1: import * as exp from './foo';
2:
3: exp['foo']++;
^
`
} }
}; };

5
test/function/no-relative-external/_config.js

@ -2,7 +2,8 @@ var assert = require( 'assert' );
module.exports = { module.exports = {
description: 'missing relative imports are an error, not a warning', description: 'missing relative imports are an error, not a warning',
error: function ( err ) { error: {
assert.ok( /Could not resolve '\.\/missing\.js' from/.test( err.message ) ); code: 'UNRESOLVED_IMPORT',
message: `Could not resolve './missing.js' from main.js`
} }
}; };

5
test/function/paths-are-case-sensitive/_config.js

@ -2,7 +2,8 @@ var assert = require( 'assert' );
module.exports = { module.exports = {
description: 'insists on correct casing for imports', description: 'insists on correct casing for imports',
error: function ( err ) { error: {
assert.ok( /Could not resolve/.test( err.message ) ); code: 'UNRESOLVED_IMPORT',
message: `Could not resolve './foo.js' from main.js`
} }
}; };

19
test/function/reassign-import-fails/_config.js

@ -3,10 +3,21 @@ var assert = require( 'assert' );
module.exports = { module.exports = {
description: 'disallows assignments to imported bindings', description: 'disallows assignments to imported bindings',
error: function ( err ) { error: {
assert.ok( /Illegal reassignment/.test( err.message ) ); code: 'ILLEGAL_REASSIGNMENT',
assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); message: `Illegal reassignment to import 'x'`,
assert.deepEqual( err.loc, { line: 8, column: 0 }); pos: 113,
loc: {
file: path.resolve( __dirname, 'main.js' ),
line: 8,
column: 0
},
frame: `
6: });
7:
8: x = 10;
^
`
} }
}; };

20
test/function/reassign-import-not-at-top-level-fails/_config.js

@ -3,10 +3,22 @@ var assert = require( 'assert' );
module.exports = { module.exports = {
description: 'disallows assignments to imported bindings not at the top level', description: 'disallows assignments to imported bindings not at the top level',
error: function ( err ) { error: {
assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); code: 'ILLEGAL_REASSIGNMENT',
assert.deepEqual( err.loc, { line: 7, column: 2 }); message: `Illegal reassignment to import 'x'`,
assert.ok( /Illegal reassignment/.test( err.message ) ); pos: 95,
loc: {
file: path.resolve( __dirname, 'main.js' ),
line: 7,
column: 2
},
frame: `
5: }
6: export function bar () {
7: x = 1;
^
8: }
`
} }
}; };

17
test/function/reexport-missing-error/_config.js

@ -1,8 +1,21 @@
var path = require( 'path' );
var assert = require( 'assert' ); var assert = require( 'assert' );
module.exports = { module.exports = {
description: 'reexporting a missing identifier should print an error', description: 'reexporting a missing identifier should print an error',
error: function ( error ) { error: {
assert.ok( /^'foo' is not exported/.test( error.message ) ); 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'
} }
}; };

9
test/function/report-transform-error-file/_config.js

@ -1,9 +1,11 @@
var path = require( 'path' );
var assert = require( 'assert' ); var assert = require( 'assert' );
module.exports = { module.exports = {
description: 'reports which file caused a transform error', description: 'reports which file caused a transform error',
options: { options: {
plugins: [{ plugins: [{
name: 'bad-plugin',
transform: function ( code, id ) { transform: function ( code, id ) {
if ( /foo/.test( id ) ) { if ( /foo/.test( id ) ) {
throw new Error( 'nope' ); throw new Error( 'nope' );
@ -11,7 +13,10 @@ module.exports = {
} }
}] }]
}, },
error: function ( err ) { error: {
assert.ok( ~err.message.indexOf( 'foo.js' ) ); code: 'BAD_TRANSFORMER',
message: `Error transforming foo.js with 'bad-plugin' plugin: nope`,
plugin: 'bad-plugin',
id: path.resolve( __dirname, 'foo.js' )
} }
}; };

8
test/function/reports-syntax-error-locations/_config.js

@ -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 ) );
}
};

1
test/function/reports-syntax-error-locations/main.js

@ -1 +0,0 @@
var 42 = answer;

7
test/function/throws-not-found-module/_config.js

@ -3,7 +3,8 @@ var path = require( 'path' );
module.exports = { module.exports = {
description: 'throws error if module is not found', description: 'throws error if module is not found',
error: function ( err ) { error: {
assert.equal( err.message, 'Could not resolve \'./mod\' from ' + path.resolve( __dirname, 'main.js' ) ); code: 'UNRESOLVED_IMPORT',
message: `Could not resolve './mod' from main.js`
} }
}; };

13
test/function/throws-only-first-transform-bundle/_config.js

@ -7,19 +7,20 @@ module.exports = {
{ {
name: 'plugin1', name: 'plugin1',
transformBundle: function () { transformBundle: function () {
throw Error('Something happend 1'); throw Error( 'Something happened 1' );
} }
}, },
{ {
name: 'plugin2', name: 'plugin2',
transformBundle: function () { transformBundle: function () {
throw Error('Something happend 2'); throw Error( 'Something happened 2' );
} }
} }
] ]
}, },
generateError: function ( err ) { generateError: {
assert.equal( err.plugin, 'plugin1' ); code: 'BAD_BUNDLE_TRANSFORMER',
assert.equal( err.message, 'Error transforming bundle with \'plugin1\' plugin: Something happend 1' ); plugin: 'plugin1',
message: `Error transforming bundle with 'plugin1' plugin: Something happened 1`
} }
}; };

21
test/function/throws-only-first-transform/_config.js

@ -7,23 +7,22 @@ module.exports = {
plugins: [ plugins: [
{ {
name: 'plugin1', name: 'plugin1',
transform: function () { transform () {
throw Error('Something happend 1'); throw Error( 'Something happened 1' );
} }
}, },
{ {
name: 'plugin2', name: 'plugin2',
transform: function () { transform () {
throw Error('Something happend 2'); throw Error( 'Something happened 2' );
} }
} }
] ]
}, },
error: function ( err ) { error: {
var id = path.resolve( __dirname, 'main.js' ); code: 'BAD_TRANSFORMER',
assert.equal( err.rollupTransform, true ); message: `Error transforming main.js with 'plugin1' plugin: Something happened 1`,
assert.equal( err.id, id ); plugin: 'plugin1',
assert.equal( err.plugin, 'plugin1' ); id: path.resolve( __dirname, 'main.js' )
assert.equal( err.message, 'Error transforming ' + id + ' with \'plugin1\' plugin: Something happend 1' );
} }
}; };

22
test/function/unused-import/_config.js

@ -2,11 +2,19 @@ const assert = require( 'assert' );
module.exports = { module.exports = {
description: 'warns on unused imports ([#595])', description: 'warns on unused imports ([#595])',
warnings: warnings => { warnings: [
assert.deepEqual( warnings, [ {
`'external' is imported by main.js, but could not be resolved – treating it as an external dependency. For help see https://github.com/rollup/rollup/wiki/Troubleshooting#treating-module-as-external-dependency`, code: 'UNRESOLVED_IMPORT',
`'unused', 'notused' and 'neverused' are imported from external module 'external' but never used`, message: `'external' is imported by main.js, but could not be resolved – treating it as an external dependency`,
`Generated an empty bundle` url: `https://github.com/rollup/rollup/wiki/Troubleshooting#treating-module-as-external-dependency`
]); },
} {
code: 'UNUSED_EXTERNAL_IMPORT',
message: `'unused', 'notused' and 'neverused' are imported from external module 'external' but never used`
},
{
code: 'EMPTY_BUNDLE',
message: `Generated an empty bundle`
}
]
}; };

19
test/function/update-expression-of-import-fails/_config.js

@ -3,10 +3,21 @@ var assert = require( 'assert' );
module.exports = { module.exports = {
description: 'disallows updates to imported bindings', description: 'disallows updates to imported bindings',
error: function ( err ) { error: {
assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); code: 'ILLEGAL_REASSIGNMENT',
assert.deepEqual( err.loc, { line: 3, column: 0 }); message: `Illegal reassignment to import 'a'`,
assert.ok( /Illegal reassignment/.test( err.message ) ); pos: 28,
loc: {
file: path.resolve( __dirname, 'main.js' ),
line: 3,
column: 0
},
frame: `
1: import { a } from './foo';
2:
3: a++;
^
`
} }
}; };

9
test/function/vars-with-init-in-dead-branch/_config.js

@ -0,0 +1,9 @@
module.exports = {
description: 'handles vars with init in dead branch (#1198)',
warnings: [
{
code: 'EMPTY_BUNDLE',
message: 'Generated an empty bundle'
}
]
};

4
test/function/vars-with-init-in-dead-branch/main.js

@ -0,0 +1,4 @@
if ( false ) {
var foo = [];
var bar = foo.concat( 'x' );
}

24
test/function/warn-on-ambiguous-function-export/_config.js

@ -2,9 +2,23 @@ const assert = require( 'assert' );
module.exports = { module.exports = {
description: 'uses original name of default export function (#1011)', description: 'uses original name of default export function (#1011)',
warnings: warnings => { warnings: [
assert.deepEqual( warnings, [ {
'foo.js (1:15) Ambiguous default export (is a call expression, but looks like a function declaration). See https://github.com/rollup/rollup/wiki/Troubleshooting#ambiguous-default-export' code: 'AMBIGUOUS_DEFAULT_EXPORT',
]); message: `Ambiguous default export (is a call expression, but looks like a function declaration)`,
} pos: 15,
loc: {
file: require( 'path' ).resolve( __dirname, 'foo.js' ),
line: 1,
column: 15
},
frame: `
1: export default function foo ( a, b ) {
^
2: assert.equal( a, b );
3: return 3;
`,
url: `https://github.com/rollup/rollup/wiki/Troubleshooting#ambiguous-default-export`
}
]
}; };

14
test/function/warn-on-auto-named-default-exports/_config.js

@ -1,10 +1,10 @@
var assert = require( 'assert' );
module.exports = { module.exports = {
description: 'warns if default and named exports are used in auto mode', description: 'warns if default and named exports are used in auto mode',
warnings: function ( warnings ) { warnings: [
assert.deepEqual( warnings, [ {
'Using named and default exports together. Consumers of your bundle will have to use bundle[\'default\'] to access the default export, which may not be what you want. Use `exports: \'named\'` to disable this warning. See https://github.com/rollup/rollup/wiki/JavaScript-API#exports for more information' code: 'MIXED_EXPORTS',
]); message: `Using named and default exports together. Consumers of your bundle will have to use bundle['default'] to access the default export, which may not be what you want. Use \`exports: 'named'\` to disable this warning`,
} url: `https://github.com/rollup/rollup/wiki/JavaScript-API#exports`
}
]
}; };

13
test/function/warn-on-empty-bundle/_config.js

@ -1,10 +1,9 @@
const assert = require( 'assert' );
module.exports = { module.exports = {
description: 'warns if empty bundle is generated (#444)', description: 'warns if empty bundle is generated (#444)',
warnings: warnings => { warnings: [
assert.deepEqual( warnings, [ {
'Generated an empty bundle' code: 'EMPTY_BUNDLE',
]); message: 'Generated an empty bundle'
} }
]
}; };

28
test/function/warn-on-eval/_config.js

@ -1,16 +1,20 @@
var assert = require( 'assert' );
var warned = false;
module.exports = { module.exports = {
description: 'warns about use of eval', description: 'warns about use of eval',
options: { warnings: [
onwarn: function ( message ) { {
warned = true; code: 'EVAL',
assert.ok( /Use of `eval` \(in .+?main\.js\) is strongly discouraged, as it poses security risks and may cause issues with minification\. See https:\/\/github.com\/rollup\/rollup\/wiki\/Troubleshooting#avoiding-eval for more details/.test( message ) ); message: `Use of eval is strongly discouraged, as it poses security risks and may cause issues with minification`,
pos: 13,
loc: {
column: 13,
file: require( 'path' ).resolve( __dirname, 'main.js' ),
line: 1
},
frame: `
1: var result = eval( '1 + 1' );
^
`,
url: 'https://github.com/rollup/rollup/wiki/Troubleshooting#avoiding-eval'
} }
}, ]
exports: function () {
assert.ok( warned, 'did not warn' );
}
}; };

9
test/function/warn-on-namespace-conflict/_config.js

@ -0,0 +1,9 @@
module.exports = {
description: 'warns on duplicate export * from',
warnings: [
{
code: 'NAMESPACE_CONFLICT',
message: `Conflicting namespaces: main.js re-exports 'foo' from both foo.js (will be ignored) and deep.js`
}
]
};

0
test/function/double-named-export-from/bar.js → test/function/warn-on-namespace-conflict/bar.js

0
test/function/double-named-export-from/deep.js → test/function/warn-on-namespace-conflict/deep.js

0
test/function/double-named-export-from/foo.js → test/function/warn-on-namespace-conflict/foo.js

0
test/function/double-named-export-from/main.js → test/function/warn-on-namespace-conflict/main.js

24
test/function/warn-on-top-level-this/_config.js

@ -2,11 +2,25 @@ const assert = require( 'assert' );
module.exports = { module.exports = {
description: 'warns on top-level this (#770)', description: 'warns on top-level this (#770)',
warnings: warnings => { warnings: [
assert.deepEqual( warnings, [ {
`main.js (3:1) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten. See https://github.com/rollup/rollup/wiki/Troubleshooting#this-is-undefined for more information` code: 'THIS_IS_UNDEFINED',
]); message: `The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten`,
}, pos: 81,
loc: {
file: require( 'path' ).resolve( __dirname, 'main.js' ),
line: 3,
column: 0
},
frame: `
1: const someVariableJustToCheckOnCorrectLineNumber = true; // eslint-disable-line
2:
3: this.foo = 'bar';
^
`,
url: `https://github.com/rollup/rollup/wiki/Troubleshooting#this-is-undefined`
}
],
runtimeError: err => { runtimeError: err => {
assert.equal( err.message, `Cannot set property 'foo' of undefined` ); assert.equal( err.message, `Cannot set property 'foo' of undefined` );
} }

23
test/function/warn-on-unused-missing-imports/_config.js

@ -3,9 +3,22 @@ const assert = require( 'assert' );
module.exports = { module.exports = {
description: 'warns on missing (but unused) imports', description: 'warns on missing (but unused) imports',
warnings: warnings => { warnings: [
assert.deepEqual( warnings, [ {
`Non-existent export 'b' is imported from ${path.resolve(__dirname, 'foo.js')} by ${path.resolve(__dirname, 'main.js')}` code: 'NON_EXISTENT_EXPORT',
]); message: `Non-existent export 'b' is imported from foo.js`,
} pos: 12,
loc: {
file: path.resolve( __dirname, 'main.js' ),
line: 1,
column: 12
},
frame: `
1: import { a, b } from './foo.js';
^
2:
3: assert.equal( a, 42 );
`
}
]
}; };

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save