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. 14
      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 {
constructor ({ id, source, ast, bundle }) {
this.source = source;
@ -103,6 +115,7 @@ export default class Module {
}
// 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 );
};
@ -288,7 +301,7 @@ export default class Module {
// bind the name to the other module's reference.
this.allExportsFrom.forEach( module => {
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 ) );
}
});

29
src/Scope.js

@ -27,9 +27,23 @@ function isntReference ( id ) {
return !( id instanceof Reference );
}
// Prefix the argument with '_'.
function underscorePrefix ( x ) {
return '_' + x;
// Returns a function that will prefix its argument with '_'
// and append a number if called with the same argument more than once.
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
@ -51,7 +65,7 @@ export default class Scope {
// Deconflict all names within the scope,
// using the given renaming function.
// If no function is supplied, `underscorePrefix` is used.
deconflict ( rename = underscorePrefix ) {
deconflict ( rename = underscorePrefix() ) {
const names = this.used;
this.ids.filter( ref => ref instanceof Reference ).forEach( ref => {
@ -67,6 +81,7 @@ export default class Scope {
});
this.ids.filter( isntReference ).forEach( id => {
// TODO: can this be removed?
if ( typeof id === 'string' ) {
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.
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.

56
src/Statement.js

@ -25,12 +25,19 @@ function isFunctionDeclaration ( node, parent ) {
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 ) {
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 {
@ -161,10 +168,10 @@ export default class Statement {
// /update expressions) need to be captured
let writeDepth = 0;
// Used to track
let topName;
let currentMemberExpression = null;
let namespace = null;
// Used to track and optimize lookups through namespaces.
let localName; // The local name of the top-most imported namespace.
let topNode = null; // The top-node of the member expression.
let namespace = null; // An instance of `Module`.
if ( !this.isImportDeclaration ) {
walk( this.node, {
@ -184,13 +191,21 @@ export default class Statement {
if ( node._scope ) scope = scope.parent;
// Optimize namespace lookups, which manifest as MemberExpressions.
if ( node.type === 'MemberExpression' && ( !currentMemberExpression || node.object === currentMemberExpression ) ) {
currentMemberExpression = node;
if ( node.type === 'MemberExpression' && ( !topNode || node.object === topNode ) ) {
// 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 ) {
topName = node.object.name;
const id = this.module.locals.lookup( topName );
localName = node.object.name;
// 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;
namespace = id;
@ -225,16 +240,16 @@ export default class Statement {
namespace.mark();
namespace = null;
currentMemberExpression = null;
topNode = null;
return;
}
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).
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.
@ -244,18 +259,18 @@ export default class Statement {
}
// FIXME: do this better
// If we depend on this name...
if ( this.dependsOn[ topName ] ) {
// If an earlier stage detected that we depend on this name...
if ( this.dependsOn[ localName ] ) {
// ... decrement the count...
if ( !--this.dependsOn[ topName ] ) {
if ( !--this.dependsOn[ localName ] ) {
// ... 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;
currentMemberExpression = null;
topNode = null;
return;
}
@ -486,7 +501,8 @@ export default class Statement {
topLevel = false;
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 => {
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 b = 2;
var internal = 42;
exports.a = a;
exports.b = b;
exports['default'] = internal;
});

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

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

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

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

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

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

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

@ -6,10 +6,8 @@
const a = 1;
const b = 2;
var internal = 42;
exports.a = a;
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();

14
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 external = real.virtual(),
@ -104,17 +104,11 @@ describe( 'Scope', function () {
locals.bind( 'Comp', external.reference( 'Component' ) );
assert.throws( function () {
exports.bind( 'default', locals.reference( 'Foo' ) );
try {
real.deconflict();
assert.ok( false, 'Scope.deconflict should throw with "Foo" undefined' );
} catch ( ignore ) {
// as expected
}
}, 'Cannot reference undefined identifier "Foo"' );
locals.define( 'Foo' );
real.deconflict();
exports.bind( 'default', locals.reference( 'Foo' ) );
});
});

Loading…
Cancel
Save