Browse Source

Added tests for Scope, stricter .eslintrc, and contributors.

gh-109
Oskar Segersvärd 9 years ago
parent
commit
9f6fdd2e13
  1. 1
      .babelrc
  2. 6
      .eslintrc
  3. 4
      package.json
  4. 78
      src/Scope.js
  5. 74
      test/testScope.js

1
.babelrc

@ -5,6 +5,7 @@
"es6.classes", "es6.classes",
"es6.constants", "es6.constants",
"es6.destructuring", "es6.destructuring",
"es6.modules",
"es6.parameters", "es6.parameters",
"es6.properties.shorthand", "es6.properties.shorthand",
"es6.spread", "es6.spread",

6
.eslintrc

@ -1,15 +1,19 @@
{ {
"rules": { "rules": {
"indent": [ 2, "tab", { "SwitchCase": 1}], "indent": [ 2, "tab", { "SwitchCase": 1 } ],
"quotes": [ 2, "single" ], "quotes": [ 2, "single" ],
"linebreak-style": [ 2, "unix" ], "linebreak-style": [ 2, "unix" ],
"semi": [ 2, "always" ], "semi": [ 2, "always" ],
"space-after-keywords": [ 2, "always" ],
"space-before-blocks": [ 2, "always" ],
"space-before-function-paren": [ 2, "always" ],
"no-mixed-spaces-and-tabs": [ 2, "smart-tabs" ], "no-mixed-spaces-and-tabs": [ 2, "smart-tabs" ],
"no-cond-assign": [ 0 ] "no-cond-assign": [ 0 ]
}, },
"env": { "env": {
"es6": true, "es6": true,
"browser": true, "browser": true,
"mocha": true,
"node": true "node": true
}, },
"extends": "eslint:recommended", "extends": "eslint:recommended",

4
package.json

@ -26,12 +26,16 @@
"optimizer" "optimizer"
], ],
"author": "Rich Harris", "author": "Rich Harris",
"contributors": [
"Oskar Segersvärd <victorystick@gmail.com>"
],
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {
"url": "https://github.com/rich-harris/rollup/issues" "url": "https://github.com/rich-harris/rollup/issues"
}, },
"homepage": "https://github.com/rich-harris/rollup", "homepage": "https://github.com/rich-harris/rollup",
"devDependencies": { "devDependencies": {
"babel": "^5.8.21",
"babel-core": "^5.5.8", "babel-core": "^5.5.8",
"console-group": "^0.1.2", "console-group": "^0.1.2",
"eslint": "^1.1.0", "eslint": "^1.1.0",

78
src/Scope.js

@ -1,43 +1,54 @@
import { blank } from './utils/object'; import { blank, keys } from './utils/object';
class Identifier { // A minimal `Identifier` implementation. Anything that has an `originalName`,
constructor ( name ) { // and a mutable `name` property can be used as an `Identifier`.
this.originalName = this.name = name; function Identifier ( name ) {
} this.originalName = this.name = name;
} }
class Reference { // A reference to an `Identifier`.
constructor ( scope, index ) { function Reference ( scope, index ) {
this.scope = scope; this.scope = scope;
this.index = index; this.index = index;
} }
deref () { // Dereferences a `Reference`.
return this.scope.ids[ this.index ]; function dereference ( ref ) {
} return ref.scope.ids[ ref.index ];
} }
function isntReference ( id ) { function isntReference ( id ) {
return !( id instanceof Reference ); return !( id instanceof Reference );
} }
// Prefix the argument with _. // Prefix the argument with '_'.
function underscorePrefix ( x ) { function underscorePrefix ( x ) {
return '_' + x; return '_' + x;
} }
// A Scope is a mapping from name to identifiers.
export default class Scope { export default class Scope {
constructor () { constructor () {
this.ids = []; this.ids = [];
this.names = {}; this.names = blank();
this.used = blank();
} }
// Binds the `name` to the given reference `ref`.
bind ( name, ref ) { bind ( name, ref ) {
if ( isntReference( ref ) ) {
throw new TypeError( `` );
}
this.ids[ this.index( name ) ] = ref; this.ids[ this.index( name ) ] = ref;
} }
// Deconflict all names within the scope,
// using the given renaming function.
// If no function is supplied, the name is simply prefixed with '_'.
deconflict ( rename = underscorePrefix ) { deconflict ( rename = underscorePrefix ) {
const names = blank(); const names = this.used;
this.ids.filter( isntReference ).forEach( id => { this.ids.filter( isntReference ).forEach( id => {
let name = id.name; let name = id.name;
@ -45,34 +56,61 @@ export default class Scope {
while ( name in names && names[ name ] !== id ) { while ( name in names && names[ name ] !== id ) {
name = rename( name ); name = rename( name );
} }
names[ name ] = id;
id.name = name; id.name = name;
}); });
} }
define ( name, id ) { // Defines `name` in the scope. `name` must be a `string` or an `Identifier`.
this.ids[ this.index( name ) ] = id || new Identifier( name ); define ( name ) {
if ( typeof name === 'string' ) {
this.ids[ this.index( name ) ] = new Identifier( name );
} else {
this.ids[ this.index( name.name ) ] = name;
}
} }
// !! private, don't use !!
//
// Lookup the `ids` index of `name`.
index ( name ) { index ( name ) {
if ( !( name in this.names ) ) { if ( !( name in this.names ) ) {
return this.names[ name ] = 1 + this.ids.push(); // The `undefined` value of this push is a placeholder
// that should be overwritten by the caller.
return this.names[ name ] = this.ids.push();
} }
return this.names[ name ]; return this.names[ name ];
} }
// Lookup the identifier referred to by `name`.
lookup ( name ) { lookup ( name ) {
let id = this.ids[ this.names[ name ] ]; let id = this.ids[ this.names[ name ] ];
while ( id instanceof Reference ) { while ( id instanceof Reference ) {
id = id.deref(); id = dereference( id );
} }
return id; return id;
} }
// Get a reference to the identifier `name` in this scope.
reference ( name ) { reference ( name ) {
return new Reference( this, this.names[ name ] ); return new Reference( this, this.names[ name ] );
} }
// Return the names currently in use in the scope.
// Names aren't considered used until they're deconflicted.
usedNames () {
return keys( this.used ).sort();
}
// Create and return a virtual scope, bound to
// the actual scope of this Scope.
virtual () {
const scope = new Scope();
scope.ids = this.ids;
return scope;
}
} }

74
test/testScope.js

@ -0,0 +1,74 @@
require('babel/register');
var assert = require( 'assert' );
var Scope = require( '../src/Scope' );
describe( 'Scope', function () {
it( 'can define and bind names', function () {
const scope = new Scope();
// If I define 'a'...
scope.define( 'a' );
// ... and bind 'b' to a reference to 'a'...
scope.bind( 'b', scope.reference( 'a' ) );
// ... lookups for 'a' and 'b' should both
// resolve to the same identifier.
assert.equal( scope.lookup( 'b' ), scope.lookup( 'a' ) );
});
describe( 'virtual scope:', function () {
var real, a, b;
beforeEach(function () {
real = new Scope();
a = real.virtual();
b = real.virtual();
});
it( 'is created within another scope', function () {
// The actual ids are the same.
assert.equal( real.ids, a.ids );
assert.equal( real.ids, b.ids );
});
it( 'lookups different identifiers', function () {
// If I define 'a' in both scopes...
a.define( 'a' );
b.define( 'a' );
// ... the name 'a' should lookup different identifiers.
assert.notEqual( a.lookup( 'a' ), b.lookup( 'b' ) );
});
it( 'can deconflict names', function () {
a.define( 'a' );
b.define( 'a' );
// Deconflicting the actual scope should make all identifiers unique.
real.deconflict();
assert.deepEqual( real.usedNames(), [ '_a', 'a' ] );
});
it( 'deconflicts with a custom function, if provided', function () {
for (var i = 0; i < 26; i++) {
// Create 26 scopes, all of which define 'a'.
real.virtual().define( 'a' );
}
// Custom deconfliction function which ignores the current name.
var num = 10;
real.deconflict( function () {
return (num++).toString(36);
});
assert.deepEqual( real.usedNames(), 'abcdefghijklmnopqrstuvwxyz'.split('') );
// Deconflicting twice has no additional effect.
real.deconflict();
assert.deepEqual( real.usedNames(), 'abcdefghijklmnopqrstuvwxyz'.split('') );
});
});
});
Loading…
Cancel
Save