Browse Source

Merge branch 'master' of https://github.com/rollup/rollup

better-aggressive
Rich Harris 10 years ago
parent
commit
68f2e0a2c8
  1. 15
      src/Module.js
  2. 29
      src/Scope.js
  3. 56
      src/Statement.js
  4. 2
      test/form/export-all-from-internal/_expected/amd.js
  5. 2
      test/form/export-all-from-internal/_expected/cjs.js
  6. 2
      test/form/export-all-from-internal/_expected/es6.js
  7. 2
      test/form/export-all-from-internal/_expected/iife.js
  8. 2
      test/form/export-all-from-internal/_expected/umd.js
  9. 10
      test/function/import-of-unexported-fails/_config.js
  10. 0
      test/function/import-of-unexported-fails/empty.js
  11. 3
      test/function/import-of-unexported-fails/main.js
  12. 16
      test/testScope.js

15
src/Module.js

@ -50,6 +50,18 @@ class Id {
} }
} }
class LateBoundIdPlaceholder {
constructor ( module, name ) {
this.module = module;
this.name = name;
this.placeholder = true;
}
mark () {
throw new Error(`The imported name "${this.name}" is never exported by "${this.module.id}".`);
}
}
export default class Module { export default class Module {
constructor ({ id, source, ast, bundle }) { constructor ({ id, source, ast, bundle }) {
this.source = source; this.source = source;
@ -103,6 +115,7 @@ export default class Module {
} }
// throw new Error( `The name "${name}" is never exported (from ${this.id})!` ); // throw new Error( `The name "${name}" is never exported (from ${this.id})!` );
this.exports.define( name, new LateBoundIdPlaceholder( this, name ) );
return reference.call( this.exports, name ); return reference.call( this.exports, name );
}; };
@ -288,7 +301,7 @@ export default class Module {
// bind the name to the other module's reference. // bind the name to the other module's reference.
this.allExportsFrom.forEach( module => { this.allExportsFrom.forEach( module => {
module.exports.getNames().forEach( name => { module.exports.getNames().forEach( name => {
if ( !this.exports.defines( name ) ) { if ( name !== 'default' && !this.exports.defines( name ) ) {
this.exports.bind( name, module.exports.reference( name ) ); this.exports.bind( name, module.exports.reference( name ) );
} }
}); });

29
src/Scope.js

@ -27,9 +27,23 @@ function isntReference ( id ) {
return !( id instanceof Reference ); return !( id instanceof Reference );
} }
// Prefix the argument with '_'. // Returns a function that will prefix its argument with '_'
function underscorePrefix ( x ) { // and append a number if called with the same argument more than once.
return '_' + x; function underscorePrefix () {
function number ( x ) {
if ( !( x in map ) ) {
map[ x ] = 0;
return '';
}
return map[ x ]++;
}
var map = blank();
return function ( x ) {
return '_' + x + number( x );
}
} }
// ## Scope // ## Scope
@ -51,7 +65,7 @@ export default class Scope {
// Deconflict all names within the scope, // Deconflict all names within the scope,
// using the given renaming function. // using the given renaming function.
// If no function is supplied, `underscorePrefix` is used. // If no function is supplied, `underscorePrefix` is used.
deconflict ( rename = underscorePrefix ) { deconflict ( rename = underscorePrefix() ) {
const names = this.used; const names = this.used;
this.ids.filter( ref => ref instanceof Reference ).forEach( ref => { this.ids.filter( ref => ref instanceof Reference ).forEach( ref => {
@ -67,6 +81,7 @@ export default class Scope {
}); });
this.ids.filter( isntReference ).forEach( id => { this.ids.filter( isntReference ).forEach( id => {
// TODO: can this be removed?
if ( typeof id === 'string' ) { if ( typeof id === 'string' ) {
throw new Error( `Required name "${id}" is undefined!` ); throw new Error( `Required name "${id}" is undefined!` );
} }
@ -139,7 +154,11 @@ export default class Scope {
// Get a reference to the identifier `name` in this scope. // Get a reference to the identifier `name` in this scope.
reference ( name ) { reference ( name ) {
return new Reference( this, this.index( name ) ); if ( !( name in this.names ) ) {
throw new Error( `Cannot reference undefined identifier "${name}"` );
}
return new Reference( this, this.names[ name ] );
} }
// Return the used names of the scope. // Return the used names of the scope.

56
src/Statement.js

@ -25,12 +25,19 @@ function isFunctionDeclaration ( node, parent ) {
if ( node.type === 'FunctionExpression' && parent.type === 'VariableDeclarator' ) return true; if ( node.type === 'FunctionExpression' && parent.type === 'VariableDeclarator' ) return true;
} }
// Extract the property access from a MemberExpression.
function property ( node ) {
return node.name ? `.${node.name}` : `[${node.value}]`;
}
// Recursively traverse the chain of member expressions from `node`,
// returning the access, e.g. `foo.bar[17]`
function chainedMemberExpression ( node ) { function chainedMemberExpression ( node ) {
if ( node.object.type === 'MemberExpression' ) { if ( node.object.type === 'MemberExpression' ) {
return chainedMemberExpression( node.object ) + '.' + node.property.name; return chainedMemberExpression( node.object ) + property( node.property );
} }
return node.object.name + '.' + node.property.name; return node.object.name + property( node.property );
} }
export default class Statement { export default class Statement {
@ -161,10 +168,10 @@ export default class Statement {
// /update expressions) need to be captured // /update expressions) need to be captured
let writeDepth = 0; let writeDepth = 0;
// Used to track // Used to track and optimize lookups through namespaces.
let topName; let localName; // The local name of the top-most imported namespace.
let currentMemberExpression = null; let topNode = null; // The top-node of the member expression.
let namespace = null; let namespace = null; // An instance of `Module`.
if ( !this.isImportDeclaration ) { if ( !this.isImportDeclaration ) {
walk( this.node, { walk( this.node, {
@ -184,13 +191,21 @@ export default class Statement {
if ( node._scope ) scope = scope.parent; if ( node._scope ) scope = scope.parent;
// Optimize namespace lookups, which manifest as MemberExpressions. // Optimize namespace lookups, which manifest as MemberExpressions.
if ( node.type === 'MemberExpression' && ( !currentMemberExpression || node.object === currentMemberExpression ) ) { if ( node.type === 'MemberExpression' && ( !topNode || node.object === topNode ) ) {
currentMemberExpression = node; // Ignore anything that doesn't begin with an identifier.
if ( !topNode && node.object.type !== 'Identifier') return;
topNode = node;
// If we don't already have a namespace,
// we aren't currently exploring any chain of member expressions.
if ( !namespace ) { if ( !namespace ) {
topName = node.object.name; localName = node.object.name;
const id = this.module.locals.lookup( topName );
// At first, we don't have a namespace, so we'll try to look one up.
const id = this.module.locals.lookup( localName );
// It only counts if it exists, is a module, and isn't external.
if ( !id || !id.isModule || id.isExternal ) return; if ( !id || !id.isModule || id.isExternal ) return;
namespace = id; namespace = id;
@ -225,16 +240,16 @@ export default class Statement {
namespace.mark(); namespace.mark();
namespace = null; namespace = null;
currentMemberExpression = null; topNode = null;
return; return;
} }
const id = namespace.exports.lookup( name ); const id = namespace.exports.lookup( name );
// If the namespace doesn't define the given name, // If the namespace doesn't export the given name,
// we can throw an error (even for nested namespaces). // we can throw an error (even for nested namespaces).
if ( !id ) { if ( !id ) {
throw new Error( `Module doesn't define "${name}"!` ); throw new Error( `Module "${namespace.id}" doesn't export "${name}"!` );
} }
// We can't resolve deeper. Replace the member chain. // We can't resolve deeper. Replace the member chain.
@ -244,18 +259,18 @@ export default class Statement {
} }
// FIXME: do this better // FIXME: do this better
// If we depend on this name... // If an earlier stage detected that we depend on this name...
if ( this.dependsOn[ topName ] ) { if ( this.dependsOn[ localName ] ) {
// ... decrement the count... // ... decrement the count...
if ( !--this.dependsOn[ topName ] ) { if ( !--this.dependsOn[ localName ] ) {
// ... and remove it if the count is 0. // ... and remove it if the count is 0.
delete this.dependsOn[ topName ]; delete this.dependsOn[ localName ];
} }
} }
this.namespaceReplacements.push( [ node, id ] ); this.namespaceReplacements.push( [ topNode, id ] );
namespace = null; namespace = null;
currentMemberExpression = null; topNode = null;
return; return;
} }
@ -486,7 +501,8 @@ export default class Statement {
topLevel = false; topLevel = false;
let newNames = blank(); let newNames = blank();
let hasReplacements; // Consider a scope to have replacements if there are any namespaceReplacements.
let hasReplacements = statement.namespaceReplacements.length > 0;
keys( names ).forEach( name => { keys( names ).forEach( name => {
if ( !scope.declarations[ name ] ) { if ( !scope.declarations[ name ] ) {

2
test/form/export-all-from-internal/_expected/amd.js

@ -2,10 +2,8 @@ define(['exports'], function (exports) { 'use strict';
const a = 1; const a = 1;
const b = 2; const b = 2;
var internal = 42;
exports.a = a; exports.a = a;
exports.b = b; exports.b = b;
exports['default'] = internal;
}); });

2
test/form/export-all-from-internal/_expected/cjs.js

@ -2,8 +2,6 @@
const a = 1; const a = 1;
const b = 2; const b = 2;
var internal = 42;
exports.a = a; exports.a = a;
exports.b = b; exports.b = b;
exports['default'] = internal;

2
test/form/export-all-from-internal/_expected/es6.js

@ -1,6 +1,4 @@
const a = 1; const a = 1;
const b = 2; const b = 2;
var internal = 42;
export { a, b }; export { a, b };
export default internal;

2
test/form/export-all-from-internal/_expected/iife.js

@ -2,10 +2,8 @@
const a = 1; const a = 1;
const b = 2; const b = 2;
var internal = 42;
exports.a = a; exports.a = a;
exports.b = b; exports.b = b;
exports['default'] = internal;
})((this.exposedInternals = {})); })((this.exposedInternals = {}));

2
test/form/export-all-from-internal/_expected/umd.js

@ -6,10 +6,8 @@
const a = 1; const a = 1;
const b = 2; const b = 2;
var internal = 42;
exports.a = a; exports.a = a;
exports.b = b; exports.b = b;
exports['default'] = internal;
})); }));

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

@ -0,0 +1,10 @@
var assert = require( 'assert' );
module.exports = {
description: 'marking an imported, but unexported, identifier should throw',
error: function ( err ) {
assert.equal( err.message.slice( 0, 50 ), 'The imported name "default" is never exported by "' );
assert.equal( err.message.slice( -10 ), 'empty.js".' );
}
};

0
test/function/import-of-unexported-fails/empty.js

3
test/function/import-of-unexported-fails/main.js

@ -0,0 +1,3 @@
import a from './empty.js';
a();

16
test/testScope.js

@ -93,7 +93,7 @@ describe( 'Scope', function () {
}); });
}); });
it( 'dedupes-external-imports', function () { it( 'cannot reference undefined names', function () {
var real = new Scope(); var real = new Scope();
var external = real.virtual(), var external = real.virtual(),
@ -104,17 +104,11 @@ describe( 'Scope', function () {
locals.bind( 'Comp', external.reference( 'Component' ) ); locals.bind( 'Comp', external.reference( 'Component' ) );
exports.bind( 'default', locals.reference( 'Foo' ) ); assert.throws( function () {
exports.bind( 'default', locals.reference( 'Foo' ) );
try { }, 'Cannot reference undefined identifier "Foo"' );
real.deconflict();
assert.ok( false, 'Scope.deconflict should throw with "Foo" undefined' );
} catch ( ignore ) {
// as expected
}
locals.define( 'Foo' ); locals.define( 'Foo' );
exports.bind( 'default', locals.reference( 'Foo' ) );
real.deconflict();
}); });
}); });

Loading…
Cancel
Save