Browse Source

Merge pull request #1199 from rollup/gh-1194

[WIP] [BREAKING] Better warnings
gh-786
Rich Harris 8 years ago
committed by GitHub
parent
commit
a53555d213
  1. 36
      bin/src/runRollup.js
  2. 1
      package.json
  3. 4
      rollup.config.cli.js
  4. 42
      src/Bundle.js
  5. 31
      src/Module.js
  6. 4
      src/ast/Node.js
  7. 10
      src/ast/nodes/CallExpression.js
  8. 9
      src/ast/nodes/ExportDefaultDeclaration.js
  9. 8
      src/ast/nodes/MemberExpression.js
  10. 12
      src/ast/nodes/ThisExpression.js
  11. 6
      src/ast/nodes/shared/disallowIllegalReassignment.js
  12. 6
      src/ast/scopes/ModuleScope.js
  13. 2
      src/finalisers/iife.js
  14. 8
      src/finalisers/shared/getGlobalNameMaker.js
  15. 5
      src/finalisers/shared/warnOnBuiltins.js
  16. 2
      src/finalisers/umd.js
  17. 8
      src/utils/collapseSourcemaps.js
  18. 9
      src/utils/defaults.js
  19. 38
      src/utils/getCodeFrame.js
  20. 6
      src/utils/getExportMode.js
  21. 20
      src/utils/getLocation.js
  22. 11
      test/function/assign-namespace-to-var/_config.js
  23. 2
      test/function/cannot-call-external-namespace/_config.js
  24. 2
      test/function/cannot-call-internal-namespace/_config.js
  25. 12
      test/function/custom-path-resolver-async/_config.js
  26. 12
      test/function/custom-path-resolver-sync/_config.js
  27. 12
      test/function/does-not-hang-on-missing-module/_config.js
  28. 13
      test/function/double-named-export-from/_config.js
  29. 4
      test/function/duplicate-import-fails/_config.js
  30. 4
      test/function/duplicate-import-specifier-fails/_config.js
  31. 29
      test/function/empty-exports/_config.js
  32. 11
      test/function/module-tree/_config.js
  33. 24
      test/function/namespace-missing-export/_config.js
  34. 4
      test/function/namespace-reassign-import-fails/_config.js
  35. 4
      test/function/namespace-update-import-fails/_config.js
  36. 4
      test/function/reassign-import-fails/_config.js
  37. 4
      test/function/reassign-import-not-at-top-level-fails/_config.js
  38. 22
      test/function/unused-import/_config.js
  39. 4
      test/function/update-expression-of-import-fails/_config.js
  40. 24
      test/function/warn-on-ambiguous-function-export/_config.js
  41. 14
      test/function/warn-on-auto-named-default-exports/_config.js
  42. 13
      test/function/warn-on-empty-bundle/_config.js
  43. 28
      test/function/warn-on-eval/_config.js
  44. 9
      test/function/warn-on-namespace-conflict/_config.js
  45. 0
      test/function/warn-on-namespace-conflict/bar.js
  46. 0
      test/function/warn-on-namespace-conflict/deep.js
  47. 0
      test/function/warn-on-namespace-conflict/foo.js
  48. 0
      test/function/warn-on-namespace-conflict/main.js
  49. 24
      test/function/warn-on-top-level-this/_config.js
  50. 23
      test/function/warn-on-unused-missing-imports/_config.js
  51. 22
      test/sourcemaps/transform-without-sourcemap/_config.js
  52. 57
      test/test.js

36
bin/src/runRollup.js

@ -1,7 +1,9 @@
import { realpathSync } from 'fs';
import * as rollup from 'rollup';
import relative from 'require-relative';
import * as chalk from 'chalk';
import handleError from './handleError';
import relativeId from '../../src/utils/relativeId.js';
import SOURCEMAPPING_URL from './sourceMappingUrl.js';
import { install as installSourcemapSupport } from 'source-map-support';
@ -60,9 +62,8 @@ export default function runRollup ( command ) {
rollup.rollup({
entry: config,
onwarn: message => {
// TODO use warning codes instead of this hackery
if ( /treating it as an external dependency/.test( message ) ) return;
stderr( message );
if ( message.code === 'UNRESOLVED_IMPORT' ) return;
stderr( message.toString() );
}
}).then( bundle => {
const { code } = bundle.generate({
@ -121,7 +122,7 @@ function execute ( options, command ) {
const optionsExternal = options.external;
if ( command.globals ) {
let globals = Object.create( null );
const globals = Object.create( null );
command.globals.split( ',' ).forEach( str => {
const names = str.split( ':' );
@ -144,7 +145,32 @@ function execute ( options, command ) {
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 );
stderr( `⚠️ ${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( '' );
};
}
options.external = external;

1
package.json

@ -53,6 +53,7 @@
"eslint-plugin-import": "^2.2.0",
"is-reference": "^1.0.0",
"istanbul": "^0.4.3",
"locate-character": "^2.0.0",
"magic-string": "^0.15.2",
"minimist": "^1.2.0",
"mocha": "^3.0.0",

4
rollup.config.cli.js

@ -15,7 +15,9 @@ export default {
buble(),
commonjs({
include: 'node_modules/**',
namedExports: { chalk: [ 'red', 'cyan', 'grey' ] }
namedExports: {
chalk: [ 'yellow', 'red', 'cyan', 'grey', 'dim', 'bold' ]
}
}),
nodeResolve({
main: true

42
src/Bundle.js

@ -188,7 +188,10 @@ export default class Bundle {
`'${unused[0]}' is` :
`${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();
@ -309,7 +312,10 @@ export default class Bundle {
keys( exportAllModule.exportsAll ).forEach( name => {
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 ];
});
@ -333,7 +339,11 @@ export default class Bundle {
if ( !resolvedId && !isExternal ) {
if ( isRelative( source ) ) throw new Error( `Could not resolve '${source}' from ${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;
}
@ -380,7 +390,11 @@ export default class Bundle {
render ( options = {} ) {
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';
}
@ -404,7 +418,10 @@ export default class Bundle {
});
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' );
@ -470,7 +487,7 @@ export default class Bundle {
if ( typeof map.mappings === 'string' ) {
map.mappings = decode( map.mappings );
}
map = collapseSourcemaps( file, map, usedModules, bundleSourcemapChain, this.onwarn );
map = collapseSourcemaps( this, file, map, usedModules, bundleSourcemapChain );
} else {
map = magicString.generateMap({ file, includeContent: true });
}
@ -535,6 +552,7 @@ export default class Bundle {
for ( i += 1; i < ordered.length; i += 1 ) {
const b = ordered[i];
// TODO reinstate this! it no longer works
if ( stronglyDependsOn[ a.id ][ b.id ] ) {
// 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
@ -566,4 +584,16 @@ export default class Bundle {
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 );
}
}

31
src/Module.js

@ -1,10 +1,11 @@
import { timeStart, timeEnd } from './utils/flushTime.js';
import { parse } from 'acorn/src/index.js';
import MagicString from 'magic-string';
import { locate } from 'locate-character';
import { timeStart, timeEnd } from './utils/flushTime.js';
import { assign, blank, deepClone, keys } from './utils/object.js';
import { basename, extname } from './utils/path.js';
import getLocation from './utils/getLocation.js';
import makeLegalIdentifier from './utils/makeLegalIdentifier.js';
import getCodeFrame from './utils/getCodeFrame.js';
import { SOURCEMAPPING_URL_RE } from './utils/sourceMappingURL.js';
import error from './utils/error.js';
import relativeId from './utils/relativeId.js';
@ -179,7 +180,12 @@ export default class Module {
this.exports[ exportedName ] = { localName };
});
} 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 );
}
}
}
@ -195,7 +201,7 @@ export default class Module {
if ( this.imports[ localName ] ) {
const err = new Error( `Duplicated import '${localName}'` );
err.file = this.id;
err.loc = getLocation( this.code, specifier.start );
err.loc = locate( this.code, specifier.start, { offsetLine: 1 });
throw err;
}
@ -361,7 +367,7 @@ export default class Module {
error({
message: `'${importDeclaration.name}' is not exported by ${relativeId( otherModule.id )} (imported by ${relativeId( this.id )}). For help fixing this error see https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module`,
file: this.id,
loc: getLocation( this.code, importDeclaration.specifier.start )
loc: locate( this.code, importDeclaration.specifier.start, { offsetLine: 1 })
});
}
@ -381,7 +387,7 @@ export default class Module {
error({
message: `'${reexportDeclaration.localName}' is not exported by '${reexportDeclaration.module.id}' (imported by '${this.id}')`,
file: this.id,
loc: getLocation( this.code, reexportDeclaration.start )
loc: locate( this.code, reexportDeclaration.start, { offsetLine: 1 })
});
}
@ -405,4 +411,17 @@ export default class Module {
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 getLocation from '../utils/getLocation.js';
export default class Node {
bind ( scope ) {
@ -74,7 +74,7 @@ export default class Node {
locate () {
// 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.toString = () => JSON.stringify( location );

10
src/ast/nodes/CallExpression.js

@ -1,4 +1,4 @@
import getLocation from '../../utils/getLocation.js';
import { locate } from 'locate-character';
import error from '../../utils/error.js';
import Node from '../Node.js';
import isProgramLevel from '../utils/isProgramLevel.js';
@ -14,12 +14,16 @@ export default class CallExpression extends Node {
message: `Cannot call a namespace ('${this.callee.name}')`,
file: this.module.id,
pos: this.start,
loc: getLocation( this.module.code, this.start )
loc: locate( this.module.code, this.start, { offsetLine: 1 })
});
}
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 );
}
}

9
src/ast/nodes/ExportDefaultDeclaration.js

@ -1,6 +1,4 @@
import Node from '../Node.js';
import getLocation from '../../utils/getLocation.js';
import relativeId from '../../utils/relativeId.js';
const functionOrClassDeclaration = /^(?:Function|Class)Declaration/;
@ -74,8 +72,11 @@ export default class ExportDefaultDeclaration extends Node {
const newlineSeparated = /\n/.test( code.original.slice( start, end ) );
if ( newlineSeparated ) {
const { line, column } = getLocation( this.module.code, this.declaration.start );
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` );
this.module.warn({
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 );
}
}
}

8
src/ast/nodes/MemberExpression.js

@ -1,5 +1,4 @@
import isReference from 'is-reference';
import getLocation from '../../utils/getLocation.js';
import relativeId from '../../utils/relativeId.js';
import Node from '../Node.js';
import { UNKNOWN } from '../values.js';
@ -34,8 +33,11 @@ export default class MemberExpression extends Node {
declaration = declaration.module.traceExport( part.name );
if ( !declaration ) {
const { line, column } = getLocation( this.module.code, this.start );
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` );
this.module.warn({
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';
return;
}

12
src/ast/nodes/ThisExpression.js

@ -1,8 +1,4 @@
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 {
initialise ( scope ) {
@ -11,9 +7,11 @@ export default class ThisExpression extends Node {
if ( lexicalBoundary.isModuleScope ) {
this.alias = this.module.context;
if ( this.alias === 'undefined' ) {
const { line, column } = getLocation( this.module.code, this.start );
const detail = `${relativeId( this.module.id )} (${line}:${column + 1})`; // use one-based column number convention
this.module.bundle.onwarn( `${detail} ${warning}` );
this.module.warn({
code: 'THIS_IS_UNDEFINED',
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 );
}
}
}

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

@ -1,4 +1,4 @@
import getLocation from '../../../utils/getLocation.js';
import { locate } from 'locate-character';
import error from '../../../utils/error.js';
// TODO tidy this up a bit (e.g. they can both use node.module.imports)
@ -10,7 +10,7 @@ export default function disallowIllegalReassignment ( scope, node ) {
message: `Illegal reassignment to import '${node.object.name}'`,
file: node.module.id,
pos: node.start,
loc: getLocation( node.module.code, node.start )
loc: locate( node.module.code, node.start, { offsetLine: 1 })
});
}
}
@ -21,7 +21,7 @@ export default function disallowIllegalReassignment ( scope, node ) {
message: `Illegal reassignment to import '${node.name}'`,
file: node.module.id,
pos: node.start,
loc: getLocation( node.module.code, node.start )
loc: locate( node.module.code, node.start, { offsetLine: 1 })
});
}
}

6
src/ast/scopes/ModuleScope.js

@ -1,4 +1,5 @@
import { forOwn } from '../../utils/object.js';
import relativeId from '../../utils/relativeId.js';
import Scope from './Scope.js';
export default class ModuleScope extends Scope {
@ -26,7 +27,10 @@ export default class ModuleScope extends Scope {
if ( specifier.name !== '*' ) {
const declaration = specifier.module.traceExport( specifier.name );
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;
}

2
src/finalisers/iife.js

@ -25,7 +25,7 @@ function setupNamespace ( keypath ) {
}
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 isNamespaced = name && ~name.indexOf( '.' );

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 ];
return function ( module ) {
const name = fn( module.id );
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;
};
}

5
src/finalisers/shared/warnOnBuiltins.js

@ -35,5 +35,8 @@ export default function warnOnBuiltins ( bundle ) {
`module ('${externalBuiltins[0]}')` :
`modules (${externalBuiltins.slice( 0, -1 ).map( name => `'${name}'` ).join( ', ' )} and '${externalBuiltins.pop()}')`;
bundle.onwarn( `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` );
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`
});
}

2
src/finalisers/umd.js

@ -33,7 +33,7 @@ export default function umd ( bundle, magicString, { exportMode, indentString, i
warnOnBuiltins( bundle );
const globalNameMaker = getGlobalNameMaker( options.globals || blank(), bundle.onwarn );
const globalNameMaker = getGlobalNameMaker( options.globals || blank(), bundle );
const amdDeps = bundle.externalModules.map( quotePath );
const cjsDeps = bundle.externalModules.map( req );

8
src/utils/collapseSourcemaps.js

@ -98,7 +98,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 => {
let sourceMapChain = module.sourceMapChain;
@ -127,7 +127,11 @@ export default function collapseSourcemaps ( file, map, modules, bundleSourcemap
sourceMapChain.forEach( map => {
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 = {
names: [],

9
src/utils/defaults.js

@ -45,9 +45,10 @@ export function resolveId ( importee, importer ) {
export function makeOnwarn () {
const warned = blank();
return msg => {
if ( msg in warned ) return;
console.error( msg ); //eslint-disable-line no-console
warned[ msg ] = true;
return warning => {
const str = warning.toString();
if ( str in warned ) return;
console.error( str ); //eslint-disable-line no-console
warned[ str ] = true;
};
}

38
src/utils/getCodeFrame.js

@ -0,0 +1,38 @@
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 );
const frameEnd = Math.min( line + 2, lines.length );
const digits = String( frameEnd + 1 ).length;
lines = lines.slice( frameStart, frameEnd );
while ( !/\S/.test( lines[ lines.length - 1 ] ) ) lines.pop();
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' );
}

6
src/utils/getExportMode.js

@ -24,7 +24,11 @@ export default function getExportMode ( bundle, {exports: exportMode, moduleName
exportMode = 'default';
} else {
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';
}

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

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

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

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

@ -7,6 +7,6 @@ module.exports = {
assert.equal( err.message, 'Cannot call a namespace (\'foo\')' );
assert.equal( err.file.replace( /\//g, path.sep ), path.resolve( __dirname, 'main.js' ) );
assert.equal( err.pos, 28 );
assert.deepEqual( err.loc, { line: 2, column: 0 });
assert.deepEqual( err.loc, { character: 28, line: 2, column: 0 });
}
};

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

@ -7,6 +7,6 @@ module.exports = {
assert.equal( err.message, 'Cannot call a namespace (\'foo\')' );
assert.equal( err.file.replace( /\//g, path.sep ), path.resolve( __dirname, 'main.js' ) );
assert.equal( err.pos, 33 );
assert.deepEqual( err.loc, { line: 2, column: 0 });
assert.deepEqual( err.loc, { character: 33, line: 2, column: 0 });
}
};

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

@ -21,11 +21,13 @@ module.exports = {
}
}]
},
warnings: function ( 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`
]);
},
warnings: [
{
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 ) {
assert.strictEqual( exports.path, require( 'path' ) );
}

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

@ -13,11 +13,13 @@ module.exports = {
}
}]
},
warnings: function ( 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`
]);
},
warnings: [
{
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 ) {
assert.strictEqual( exports.path, require( 'path' ) );
}

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

@ -2,11 +2,13 @@ var assert = require( 'assert' );
module.exports = {
description: 'does not hang on missing module (#53)',
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`
]);
},
warnings: [
{
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 ) {
assert.equal( "Cannot find module 'unlessYouCreatedThisFileForSomeReason'", error.message );
}

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')}.`
]);
}
};

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

@ -4,8 +4,8 @@ var assert = require( 'assert' );
module.exports = {
description: 'disallows duplicate imports',
error: function ( err ) {
assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) );
assert.deepEqual( err.loc, { line: 2, column: 9 });
assert.equal( path.normalize( err.file ), path.resolve( __dirname, 'main.js' ) );
assert.deepEqual( err.loc, { character: 36, line: 2, column: 9 });
assert.ok( /Duplicated import/.test( err.message ) );
}
};

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

@ -4,8 +4,8 @@ var assert = require( 'assert' );
module.exports = {
description: 'disallows duplicate import specifiers',
error: function ( err ) {
assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) );
assert.deepEqual( err.loc, { line: 1, column: 12 });
assert.equal( path.normalize( err.file ), path.resolve( __dirname, 'main.js' ) );
assert.deepEqual( err.loc, { character: 12, line: 1, column: 12 });
assert.ok( /Duplicated import/.test( err.message ) );
}
};

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

@ -1,12 +1,23 @@
const assert = require( 'assert' );
const path = require( 'path' );
module.exports = {
description: 'warns on export {}, but does not fail',
warnings: warnings => {
assert.deepEqual( warnings, [
`Module ${path.resolve( __dirname, 'main.js' )} has an empty export declaration`,
'Generated an empty bundle'
]);
}
warnings: [
{
code: 'EMPTY_EXPORT',
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'
}
]
};

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

@ -34,9 +34,10 @@ module.exports = {
}
]);
},
warnings: warnings => {
assert.deepEqual( warnings, [
'Generated an empty bundle'
]);
}
warnings: [
{
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 = {
options: {
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` );
warnings: [
{
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`
}
}
]
};

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

@ -4,8 +4,8 @@ var assert = require( 'assert' );
module.exports = {
description: 'disallows reassignments to namespace exports',
error: function ( err ) {
assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) );
assert.deepEqual( err.loc, { line: 3, column: 0 });
assert.equal( path.normalize( err.file ), path.resolve( __dirname, 'main.js' ) );
assert.deepEqual( err.loc, { character: 31, line: 3, column: 0 });
assert.ok( /Illegal reassignment/.test( err.message ) );
}
};

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

@ -4,8 +4,8 @@ var assert = require( 'assert' );
module.exports = {
description: 'disallows updates to namespace exports',
error: function ( err ) {
assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) );
assert.deepEqual( err.loc, { line: 3, column: 0 });
assert.equal( path.normalize( err.file ), path.resolve( __dirname, 'main.js' ) );
assert.deepEqual( err.loc, { character: 31, line: 3, column: 0 });
assert.ok( /Illegal reassignment/.test( err.message ) );
}
};

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

@ -5,8 +5,8 @@ module.exports = {
description: 'disallows assignments to imported bindings',
error: function ( err ) {
assert.ok( /Illegal reassignment/.test( err.message ) );
assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) );
assert.deepEqual( err.loc, { line: 8, column: 0 });
assert.equal( path.normalize( err.file ), path.resolve( __dirname, 'main.js' ) );
assert.deepEqual( err.loc, { character: 113, line: 8, column: 0 });
}
};

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

@ -4,8 +4,8 @@ var assert = require( 'assert' );
module.exports = {
description: 'disallows assignments to imported bindings not at the top level',
error: function ( err ) {
assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) );
assert.deepEqual( err.loc, { line: 7, column: 2 });
assert.equal( path.normalize( err.file ), path.resolve( __dirname, 'main.js' ) );
assert.deepEqual( err.loc, { character: 95, line: 7, column: 2 });
assert.ok( /Illegal reassignment/.test( err.message ) );
}
};

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

@ -2,11 +2,19 @@ const assert = require( 'assert' );
module.exports = {
description: 'warns on unused imports ([#595])',
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`,
`'unused', 'notused' and 'neverused' are imported from external module 'external' but never used`,
`Generated an empty bundle`
]);
}
warnings: [
{
code: 'UNRESOLVED_IMPORT',
message: `'external' 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`
},
{
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`
}
]
};

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

@ -4,8 +4,8 @@ var assert = require( 'assert' );
module.exports = {
description: 'disallows updates to imported bindings',
error: function ( err ) {
assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) );
assert.deepEqual( err.loc, { line: 3, column: 0 });
assert.equal( path.normalize( err.file ), path.resolve( __dirname, 'main.js' ) );
assert.deepEqual( err.loc, { character: 28, line: 3, column: 0 });
assert.ok( /Illegal reassignment/.test( err.message ) );
}
};

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

@ -2,9 +2,23 @@ const assert = require( 'assert' );
module.exports = {
description: 'uses original name of default export function (#1011)',
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'
]);
}
warnings: [
{
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 = {
description: 'warns if default and named exports are used in auto mode',
warnings: function ( 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'
]);
}
warnings: [
{
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 = {
description: 'warns if empty bundle is generated (#444)',
warnings: warnings => {
assert.deepEqual( warnings, [
'Generated an empty bundle'
]);
}
warnings: [
{
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 = {
description: 'warns about use of eval',
options: {
onwarn: function ( message ) {
warned = true;
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 ) );
warnings: [
{
code: 'EVAL',
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 = {
description: 'warns on top-level this (#770)',
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`
]);
},
warnings: [
{
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 => {
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 = {
description: 'warns on missing (but unused) imports',
warnings: warnings => {
assert.deepEqual( warnings, [
`Non-existent export 'b' is imported from ${path.resolve(__dirname, 'foo.js')} by ${path.resolve(__dirname, 'main.js')}`
]);
}
warnings: [
{
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 );
`
}
]
};

22
test/sourcemaps/transform-without-sourcemap/_config.js

@ -1,10 +1,5 @@
const assert = require( 'assert' );
let warnings = [];
module.exports = {
description: 'preserves sourcemap chains when transforming',
before: () => warnings = [], // reset
options: {
plugins: [
{
@ -13,14 +8,13 @@ module.exports = {
return code;
}
}
],
onwarn ( msg ) {
warnings.push( msg );
}
]
},
test: () => {
assert.deepEqual( warnings, [
`Sourcemap is likely to be incorrect: a plugin ('fake 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`
]);
}
warnings: [
{
code: `SOURCEMAP_BROKEN`,
message: `Sourcemap is likely to be incorrect: a plugin ('fake 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`
}
]
};

57
test/test.js

@ -58,6 +58,27 @@ function loader ( modules ) {
};
}
function compareWarnings ( actual, expected ) {
assert.deepEqual(
actual.map( warning => {
const clone = Object.assign( {}, warning );
delete clone.toString;
if ( clone.frame ) {
clone.frame = clone.frame.replace( /\s+$/gm, '' );
}
return clone;
}),
expected.map( warning => {
if ( warning.frame ) {
warning.frame = warning.frame.slice( 1 ).replace( /^\t+/gm, '' ).replace( /\s+$/gm, '' ).trim();
}
return warning;
})
);
}
describe( 'rollup', function () {
this.timeout( 10000 );
@ -270,7 +291,11 @@ describe( 'rollup', function () {
}
if ( config.warnings ) {
config.warnings( warnings );
if ( Array.isArray( config.warnings ) ) {
compareWarnings( warnings, config.warnings );
} else {
config.warnings( warnings );
}
} else if ( warnings.length ) {
throw new Error( `Got unexpected warnings:\n${warnings.join('\n')}` );
}
@ -377,11 +402,18 @@ describe( 'rollup', function () {
const entry = path.resolve( SOURCEMAPS, dir, 'main.js' );
const dest = path.resolve( SOURCEMAPS, dir, '_actual/bundle' );
const options = extend( {}, config.options, { entry });
let warnings;
const options = extend( {}, config.options, {
entry,
onwarn: warning => warnings.push( warning )
});
PROFILES.forEach( profile => {
( config.skip ? it.skip : config.solo ? it.only : it )( 'generates ' + profile.format, () => {
process.chdir( SOURCEMAPS + '/' + dir );
warnings = [];
return rollup.rollup( options ).then( bundle => {
const options = extend( {}, {
format: profile.format,
@ -391,9 +423,16 @@ describe( 'rollup', function () {
bundle.write( options );
if ( config.before ) config.before();
const result = bundle.generate( options );
config.test( result.code, result.map );
if ( config.test ) {
const { code, map } = bundle.generate( options );
config.test( code, map );
}
if ( config.warnings ) {
compareWarnings( warnings, config.warnings );
} else if ( warnings.length ) {
throw new Error( `Unexpected warnings` );
}
});
});
});
@ -737,11 +776,9 @@ describe( 'rollup', function () {
moduleName: 'myBundle'
});
assert.deepEqual( warnings, [
`'util' is imported by entry, 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`,
`Creating a browser bundle that depends on Node.js built-in module ('util'). You might need to include https://www.npmjs.com/package/rollup-plugin-node-builtins`,
`No name was provided for external module 'util' in options.globals – guessing 'util'`
]);
const relevantWarnings = warnings.filter( warning => warning.code === 'MISSING_NODE_BUILTINS' );
assert.equal( relevantWarnings.length, 1 );
assert.equal( relevantWarnings[0].message, `Creating a browser bundle that depends on Node.js built-in module ('util'). You might need to include https://www.npmjs.com/package/rollup-plugin-node-builtins` );
});
});
});

Loading…
Cancel
Save