Browse Source

Made Scope.reference stricter. Helps to catch undefined/missing exports.

better-aggressive
Oskar Segersvärd 9 years ago
parent
commit
365f45c7f3
  1. 13
      src/Module.js
  2. 7
      src/Scope.js
  3. 10
      test/function/import-of-unexported-fails/_config.js
  4. 0
      test/function/import-of-unexported-fails/empty.js
  5. 3
      test/function/import-of-unexported-fails/main.js
  6. 14
      test/testScope.js

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

7
src/Scope.js

@ -81,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!` );
} }
@ -153,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.

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 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' ) );
assert.throws( function () {
exports.bind( 'default', locals.reference( 'Foo' ) ); exports.bind( 'default', locals.reference( 'Foo' ) );
}, 'Cannot reference undefined identifier "Foo"' );
try {
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