[Pkg-javascript-commits] [node-magic-string] 01/04: New upstream version 0.18.0

Julien Puydt julien.puydt at laposte.net
Fri Dec 2 15:34:28 UTC 2016


This is an automated email from the git hooks/post-receive script.

jpuydt-guest pushed a commit to branch master
in repository node-magic-string.

commit 68f4435bb06cf3f0afc565c357b735cf02fc4bbc
Author: Julien Puydt <julien.puydt at laposte.net>
Date:   Fri Dec 2 10:06:55 2016 +0100

    New upstream version 0.18.0
---
 .eslintrc                    |   10 +-
 CHANGELOG.md                 |    9 +
 README.md                    |   20 +-
 package.json                 |   37 +-
 rollup.config.js             |    2 +-
 src/Bundle.js                |   44 +-
 src/Chunk.js                 |   12 +-
 src/MagicString.js           |  151 ++--
 src/utils/encodeMappings.js  |    6 +-
 src/utils/getLocator.js      |    4 +-
 src/utils/getRelativePath.js |    4 +-
 test/.eslintrc               |    6 +
 test/MagicString.Bundle.js   |  574 ++++++++++++++
 test/MagicString.js          | 1127 ++++++++++++++++++++++++++++
 test/index.js                | 1684 ------------------------------------------
 test/mocha.opts              |    1 +
 16 files changed, 1907 insertions(+), 1784 deletions(-)

diff --git a/.eslintrc b/.eslintrc
index dfefff3..7ed2294 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -2,20 +2,24 @@
     "rules": {
         "indent": [ 2, "tab", { "SwitchCase": 1 }],
         "quotes": [ 2, "single" ],
-        "linebreak-style": [ 2, "unix" ],
         "semi": [ 2, "always" ],
+        "keyword-spacing": [ 2, { "before": true, "after": true } ],
+        "space-before-blocks": [ 2, "always" ],
+        "space-before-function-paren": [ 2, "always" ],
         "no-mixed-spaces-and-tabs": [ 2, "smart-tabs" ],
         "object-shorthand":  [2, "always" ],
         "no-const-assign": 2,
+        "no-unused-vars": 2,
         "no-class-assign": 2,
         "no-this-before-super": 2,
         "no-var": 2,
         "quote-props": [ 2, "as-needed" ],
         "one-var": [ 2, "never" ],
         "prefer-arrow-callback": 2,
+        "prefer-const": 2,
         "arrow-spacing": 2,
-
-        "no-cond-assign": 0
+        "no-cond-assign": 0,
+        "no-constant-condition": 0
     },
     "env": {
         "es6": true,
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 20fa5b0..fa2153f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
 # magic-string changelog
 
+## 0.18.0
+
+* Optimisation – remove empty chunks following `overwrite` or `remove` ([#113](https://github.com/Rich-Harris/magic-string/pull/113))
+
+## 0.17.0
+
+* Add `appendLeft`, `appendRight`, `prependLeft`, `prependRight` methods ([#109](https://github.com/Rich-Harris/magic-string/issues/109))
+* `insertLeft` and `insertRight` are deprecated in favour of `appendLeft` and `prependRight` respectively
+
 ## 0.16.0
 
 * Include inserts in range for `overwrite` and `remove` operations ([#89](https://github.com/Rich-Harris/magic-string/pull/89))
diff --git a/README.md b/README.md
index b971bd4..1314c72 100644
--- a/README.md
+++ b/README.md
@@ -90,6 +90,14 @@ Adds the specified character index (with respect to the original string) to sour
 
 Appends the specified content to the end of the string. Returns `this`.
 
+### s.appendLeft( index, content )
+
+Appends the specified `content` at the `index` in the original string. If a range *ending* with `index` is subsequently moved, the insert will be moved with it. Returns `this`. See also `s.prependLeft(...)`.
+
+### s.appendRight( index, content )
+
+Appends the specified `content` at the `index` in the original string. If a range *starting* with `index` is subsequently moved, the insert will be moved with it. Returns `this`. See also `s.prependRight(...)`.
+
 ### s.clone()
 
 Does what you'd expect.
@@ -120,11 +128,11 @@ The `options` argument can have an `exclude` property, which is an array of `[st
 
 ### s.insertLeft( index, content )
 
-Inserts the specified `content` at the `index` in the original string. If a range *ending* with `index` is subsequently moved, the insert will be moved with it. Returns `this`.
+**DEPRECATED** since 0.17 – use `s.appendLeft(...)` instead
 
 ### s.insertRight( index, content )
 
-Inserts the specified `content` at the `index` in the original string. If a range *starting* with `index` is subsequently moved, the insert will be moved with it. Returns `this`.
+**DEPRECATED** since 0.17 – use `s.prependRight(...)` instead
 
 ### s.locate( index )
 
@@ -146,6 +154,14 @@ Replaces the characters from `start` to `end` with `content`. The same restricti
 
 Prepends the string with the specified content. Returns `this`.
 
+### s.prependLeft ( index, content )
+
+Same as `s.appendLeft(...)`, except that the inserted content will go *before* any previous appends or prepends at `index`
+
+### s.prependRight ( index, content )
+
+Same as `s.appendRight(...)`, except that the inserted content will go *before* any previous appends or prepends at `index`
+
 ### s.remove( start, end )
 
 Removes the characters from `start` to `end` (of the original string, **not** the generated string). Removing the same content twice, or making removals that partially overlap, will cause an error. Returns `this`.
diff --git a/package.json b/package.json
index 3c6f944..486e862 100644
--- a/package.json
+++ b/package.json
@@ -2,29 +2,30 @@
   "name": "magic-string",
   "description": "Modify strings, generate sourcemaps",
   "author": "Rich Harris",
-  "version": "0.16.0",
+  "version": "0.18.0",
   "repository": "https://github.com/rich-harris/magic-string",
   "main": "dist/magic-string.cjs.js",
-  "module": "dist/magic-string.es6.js",
-  "jsnext:main": "dist/magic-string.es6.js",
+  "module": "dist/magic-string.es.js",
+  "jsnext:main": "dist/magic-string.es.js",
   "license": "MIT",
   "dependencies": {
     "vlq": "^0.2.1"
   },
   "devDependencies": {
+    "buble": "^0.14.0",
     "codecov.io": "^0.1.6",
-    "console-group": "^0.2.1",
-    "eslint": "^2.11.1",
-    "istanbul": "^0.4.3",
-    "mocha": "^3.0.1",
+    "console-group": "^0.3.2",
+    "eslint": "^3.7.1",
+    "istanbul": "^0.4.5",
+    "mocha": "^3.1.0",
     "remap-istanbul": "^0.6.4",
     "resolve": "^1.1.7",
-    "rollup": "^0.34.5",
-    "rollup-plugin-buble": "^0.12.1",
+    "rollup": "^0.36.1",
+    "rollup-plugin-buble": "^0.14.0",
     "rollup-plugin-node-resolve": "^2.0.0",
-    "rollup-plugin-replace": "^1.1.0",
+    "rollup-plugin-replace": "^1.1.1",
     "source-map": "^0.5.6",
-    "source-map-support": "^0.4.0"
+    "source-map-support": "^0.4.3"
   },
   "keywords": [
     "string",
@@ -37,19 +38,19 @@
     "test": "mocha",
     "pretest": "npm run build:cjs",
     "pretest-coverage": "npm run build:cjs",
-    "test-coverage": "rm -rf coverage/* && istanbul cover --report json node_modules/.bin/_mocha -- -u exports -R spec test/index.js",
+    "test-coverage": "rm -rf coverage/* && istanbul cover --report json node_modules/.bin/_mocha -- -u exports -R spec test/*.js",
     "posttest-coverage": "remap-istanbul -i coverage/coverage-final.json -o coverage/coverage-remapped.json -b dist && remap-istanbul -i coverage/coverage-final.json -o coverage/coverage-remapped.lcov -t lcovonly -b dist && remap-istanbul -i coverage/coverage-final.json -o coverage/coverage-remapped -t html -b dist",
     "ci": "npm run test-coverage && codecov < coverage/coverage-remapped.lcov",
     "build:cjs": "rollup -c",
-    "build:es6": "rollup -c --environment ES",
+    "build:es": "rollup -c --environment ES",
     "build:umd": "rollup -c --environment DEPS",
-    "build": " npm run build:cjs && npm run build:es6 && npm run build:umd",
-    "prepublish": "rm -rf dist && npm test && npm run build:es6 && npm run build:umd",
-    "lint": "eslint src",
+    "build": " npm run build:cjs && npm run build:es && npm run build:umd",
+    "prepublish": "rm -rf dist && npm test && npm run build:es && npm run build:umd",
+    "lint": "eslint src test",
     "watch:cjs": "rollup -w -c",
-    "watch:es6": "rollup -w -c --environment ES",
+    "watch:es": "rollup -w -c --environment ES",
     "watch:umd": "rollup -w -c --environment DEPS",
-    "watch": "npm run watch:es6"
+    "watch": "npm run watch:es"
   },
   "files": [
     "src/*",
diff --git a/rollup.config.js b/rollup.config.js
index ef0aa20..512a4d9 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -3,7 +3,7 @@ import nodeResolve from 'rollup-plugin-node-resolve';
 import replace from 'rollup-plugin-replace';
 
 var external = process.env.DEPS ? null : [ 'vlq' ];
-var format = process.env.DEPS ? 'umd' : process.env.ES ? 'es6' : 'cjs';
+var format = process.env.DEPS ? 'umd' : process.env.ES ? 'es' : 'cjs';
 
 export default {
 	entry: process.env.ES ? 'src/index.js' : 'src/index-legacy.js',
diff --git a/src/Bundle.js b/src/Bundle.js
index 7814ea8..0f3786a 100644
--- a/src/Bundle.js
+++ b/src/Bundle.js
@@ -79,19 +79,31 @@ Bundle.prototype = {
 		return bundle;
 	},
 
-	generateMap ( options ) {
-		options = options || {};
-
-		let offsets = {};
-
-		let names = [];
+	generateMap ( options = {} ) {
+		const names = [];
 		this.sources.forEach( source => {
 			Object.keys( source.content.storedNames ).forEach( name => {
 				if ( !~names.indexOf( name ) ) names.push( name );
 			});
 		});
 
-		const encoded = (
+		return new SourceMap({
+			file: ( options.file ? options.file.split( /[\/\\]/ ).pop() : null ),
+			sources: this.uniqueSources.map( source => {
+				return options.file ? getRelativePath( options.file, source.filename ) : source.filename;
+			}),
+			sourcesContent: this.uniqueSources.map( source => {
+				return options.includeContent ? source.content : null;
+			}),
+			names,
+			mappings: this.getMappings( options, names )
+		});
+	},
+
+	getMappings ( options, names ) {
+		const offsets = {};
+
+		return (
 			getSemis( this.intro ) +
 			this.sources.map( ( source, i ) => {
 				const prefix = ( i > 0 ) ? ( getSemis( source.separator ) || ',' ) : '';
@@ -102,28 +114,16 @@ Bundle.prototype = {
 					mappings = getSemis( source.content.toString() );
 				} else {
 					const sourceIndex = this.uniqueSourceIndexByFilename[ source.filename ];
-					mappings = source.content.getMappings( options.hires, sourceIndex, offsets, names );
+					mappings = source.content.getMappings( options, sourceIndex, offsets, names );
 				}
 
 				return prefix + mappings;
 			}).join( '' )
 		);
-
-		return new SourceMap({
-			file: ( options.file ? options.file.split( /[\/\\]/ ).pop() : null ),
-			sources: this.uniqueSources.map( source => {
-				return options.file ? getRelativePath( options.file, source.filename ) : source.filename;
-			}),
-			sourcesContent: this.uniqueSources.map( source => {
-				return options.includeContent ? source.content : null;
-			}),
-			names,
-			mappings: encoded
-		});
 	},
 
 	getIndentString () {
-		let indentStringCounts = {};
+		const indentStringCounts = {};
 
 		this.sources.forEach( source => {
 			const indentStr = source.content.indentStr;
@@ -178,7 +178,7 @@ Bundle.prototype = {
 	toString () {
 		const body = this.sources.map( ( source, i ) => {
 			const separator = source.separator !== undefined ? source.separator : this.separator;
-			let str = ( i > 0 ? separator : '' ) + source.content.toString();
+			const str = ( i > 0 ? separator : '' ) + source.content.toString();
 
 			return str;
 		}).join( '' );
diff --git a/src/Chunk.js b/src/Chunk.js
index dfe0152..d83e036 100644
--- a/src/Chunk.js
+++ b/src/Chunk.js
@@ -18,10 +18,14 @@ export default function Chunk ( start, end, content ) {
 }
 
 Chunk.prototype = {
-	append ( content ) {
+	appendLeft ( content ) {
 		this.outro += content;
 	},
 
+	appendRight ( content ) {
+		this.intro = this.intro + content;
+	},
+
 	clone () {
 		const chunk = new Chunk( this.start, this.end, this.original );
 
@@ -65,7 +69,11 @@ Chunk.prototype = {
 		return this;
 	},
 
-	prepend ( content ) {
+	prependLeft ( content ) {
+		this.outro = content + this.outro;
+	},
+
+	prependRight ( content ) {
 		this.intro = content + this.intro;
 	},
 
diff --git a/src/MagicString.js b/src/MagicString.js
index f1756a0..7fc93d1 100644
--- a/src/MagicString.js
+++ b/src/MagicString.js
@@ -7,6 +7,11 @@ import isObject from './utils/isObject.js';
 import getLocator from './utils/getLocator.js';
 import Stats from './utils/Stats.js';
 
+const warned = {
+	insertLeft: false,
+	insertRight: false
+};
+
 export default function MagicString ( string, options = {} ) {
 	const chunk = new Chunk( 0, string.length, string );
 
@@ -46,8 +51,46 @@ MagicString.prototype = {
 		return this;
 	},
 
+	appendLeft ( index, content ) {
+		if ( typeof content !== 'string' ) throw new TypeError( 'inserted content must be a string' );
+
+		if ( DEBUG ) this.stats.time( 'insertLeft' );
+
+		this._split( index );
+
+		const chunk = this.byEnd[ index ];
+
+		if ( chunk ) {
+			chunk.appendLeft( content );
+		} else {
+			this.intro += content;
+		}
+
+		if ( DEBUG ) this.stats.timeEnd( 'insertLeft' );
+		return this;
+	},
+
+	appendRight ( index, content ) {
+		if ( typeof content !== 'string' ) throw new TypeError( 'inserted content must be a string' );
+
+		if ( DEBUG ) this.stats.time( 'insertLeft' );
+
+		this._split( index );
+
+		const chunk = this.byStart[ index ];
+
+		if ( chunk ) {
+			chunk.appendRight( content );
+		} else {
+			this.outro += content;
+		}
+
+		if ( DEBUG ) this.stats.timeEnd( 'insertLeft' );
+		return this;
+	},
+
 	clone () {
-		let cloned = new MagicString( this.original, { filename: this.filename });
+		const cloned = new MagicString( this.original, { filename: this.filename });
 
 		let originalChunk = this.firstChunk;
 		let clonedChunk = cloned.firstChunk = cloned.lastSearchedChunk = originalChunk.clone();
@@ -95,7 +138,7 @@ MagicString.prototype = {
 			sources: [ options.source ? getRelativePath( options.file || '', options.source ) : null ],
 			sourcesContent: options.includeContent ? [ this.original ] : [ null ],
 			names,
-			mappings: this.getMappings( options.hires, 0, {}, names )
+			mappings: this.getMappings( options, 0, {}, names )
 		});
 		if ( DEBUG ) this.stats.timeEnd( 'generateMap' );
 
@@ -106,8 +149,8 @@ MagicString.prototype = {
 		return this.indentStr === null ? '\t' : this.indentStr;
 	},
 
-	getMappings ( hires, sourceIndex, offsets, names ) {
-		return encodeMappings( this.original, this.intro, this.outro, this.firstChunk, hires, this.sourcemapLocations, sourceIndex, offsets, names );
+	getMappings ( options, sourceIndex, offsets, names ) {
+		return encodeMappings( this.original, this.intro, this.outro, this.firstChunk, options.hires, this.sourcemapLocations, sourceIndex, offsets, names );
 	},
 
 	indent ( indentStr, options ) {
@@ -125,10 +168,10 @@ MagicString.prototype = {
 		options = options || {};
 
 		// Process exclusion ranges
-		let isExcluded = {};
+		const isExcluded = {};
 
 		if ( options.exclude ) {
-			let exclusions = typeof options.exclude[0] === 'number' ? [ options.exclude ] : options.exclude;
+			const exclusions = typeof options.exclude[0] === 'number' ? [ options.exclude ] : options.exclude;
 			exclusions.forEach( exclusion => {
 				for ( let i = exclusion[0]; i < exclusion[1]; i += 1 ) {
 					isExcluded[i] = true;
@@ -173,10 +216,10 @@ MagicString.prototype = {
 							shouldIndentNextCharacter = false;
 
 							if ( charIndex === chunk.start ) {
-								chunk.prepend( indentStr );
+								chunk.prependRight( indentStr );
 							} else {
 								const rhs = chunk.split( charIndex );
-								rhs.prepend( indentStr );
+								rhs.prependRight( indentStr );
 
 								this.byStart[ charIndex ] = rhs;
 								this.byEnd[ charIndex ] = chunk;
@@ -204,41 +247,21 @@ MagicString.prototype = {
 	},
 
 	insertLeft ( index, content ) {
-		if ( typeof content !== 'string' ) throw new TypeError( 'inserted content must be a string' );
-
-		if ( DEBUG ) this.stats.time( 'insertLeft' );
-
-		this._split( index );
-
-		const chunk = this.byEnd[ index ];
-
-		if ( chunk ) {
-			chunk.append( content );
-		} else {
-			this.intro += content;
+		if ( !warned.insertLeft ) {
+			console.warn( 'magicString.insertLeft(...) is deprecated. Use magicString.appendLeft(...) instead' ); // eslint-disable-line no-console
+			warned.insertLeft = true;
 		}
 
-		if ( DEBUG ) this.stats.timeEnd( 'insertLeft' );
-		return this;
+		return this.appendLeft( index, content );
 	},
 
 	insertRight ( index, content ) {
-		if ( typeof content !== 'string' ) throw new TypeError( 'inserted content must be a string' );
-
-		if ( DEBUG ) this.stats.time( 'insertRight' );
-
-		this._split( index );
-
-		const chunk = this.byStart[ index ];
-
-		if ( chunk ) {
-			chunk.prepend( content );
-		} else {
-			this.outro += content;
+		if ( !warned.insertRight ) {
+			console.warn( 'magicString.insertRight(...) is deprecated. Use magicString.prependRight(...) instead' ); // eslint-disable-line no-console
+			warned.insertRight = true;
 		}
 
-		if ( DEBUG ) this.stats.timeEnd( 'insertRight' );
-		return this;
+		return this.prependRight( index, content );
 	},
 
 	move ( start, end, index ) {
@@ -307,15 +330,15 @@ MagicString.prototype = {
 		if ( first ) {
 			first.edit( content, storeName );
 
-			if ( first !== last ) {
-				let chunk = first.next;
-				while ( chunk !== last ) {
-					chunk.edit( '', false );
-					chunk = chunk.next;
-				}
-
-				chunk.edit( '', false );
+			if ( last ) {
+				first.next = last.next;
+			} else {
+				first.next = null;
+				this.lastChunk = first;
 			}
+
+			first.original = this.original.slice( start, end );
+			first.end = end;
 		}
 
 		else {
@@ -338,6 +361,44 @@ MagicString.prototype = {
 		return this;
 	},
 
+	prependLeft ( index, content ) {
+		if ( typeof content !== 'string' ) throw new TypeError( 'inserted content must be a string' );
+
+		if ( DEBUG ) this.stats.time( 'insertRight' );
+
+		this._split( index );
+
+		const chunk = this.byEnd[ index ];
+
+		if ( chunk ) {
+			chunk.prependLeft( content );
+		} else {
+			this.intro = content + this.intro;
+		}
+
+		if ( DEBUG ) this.stats.timeEnd( 'insertRight' );
+		return this;
+	},
+
+	prependRight ( index, content ) {
+		if ( typeof content !== 'string' ) throw new TypeError( 'inserted content must be a string' );
+
+		if ( DEBUG ) this.stats.time( 'insertRight' );
+
+		this._split( index );
+
+		const chunk = this.byStart[ index ];
+
+		if ( chunk ) {
+			chunk.prependRight( content );
+		} else {
+			this.outro = content + this.outro;
+		}
+
+		if ( DEBUG ) this.stats.timeEnd( 'insertRight' );
+		return this;
+	},
+
 	remove ( start, end ) {
 		while ( start < 0 ) start += this.original.length;
 		while ( end < 0 ) end += this.original.length;
@@ -370,7 +431,7 @@ MagicString.prototype = {
 
 		if ( chunk && chunk.edited && chunk.start !== start ) throw new Error(`Cannot use replaced character ${start} as slice start anchor.`);
 
-		let startChunk = chunk;
+		const startChunk = chunk;
 		while ( chunk ) {
 			if ( chunk.intro && ( startChunk !== chunk || chunk.start === start ) ) {
 				result += chunk.intro;
diff --git a/src/utils/encodeMappings.js b/src/utils/encodeMappings.js
index b96efca..de210e0 100644
--- a/src/utils/encodeMappings.js
+++ b/src/utils/encodeMappings.js
@@ -5,7 +5,7 @@ import getLocator from './getLocator.js';
 const nonWhitespace = /\S/;
 
 export default function encodeMappings ( original, intro, outro, chunk, hires, sourcemapLocations, sourceIndex, offsets, names ) {
-	let rawLines = [];
+	const rawLines = [];
 
 	let generatedCodeLine = intro.split( '\n' ).length - 1;
 	let rawSegments = rawLines[ generatedCodeLine ] = [];
@@ -83,7 +83,7 @@ export default function encodeMappings ( original, intro, outro, chunk, hires, s
 	let hasContent = false;
 
 	while ( chunk ) {
-		let loc = locate( chunk.start );
+		const loc = locate( chunk.start );
 
 		if ( chunk.intro.length ) {
 			addEdit( chunk.intro, '', loc, -1, hasContent );
@@ -114,7 +114,7 @@ export default function encodeMappings ( original, intro, outro, chunk, hires, s
 		let generatedCodeColumn = 0;
 
 		return segments.map( segment => {
-			let arr = [
+			const arr = [
 				segment.generatedCodeColumn - generatedCodeColumn,
 				segment.sourceIndex - offsets.sourceIndex,
 				segment.sourceCodeLine - offsets.sourceCodeLine,
diff --git a/src/utils/getLocator.js b/src/utils/getLocator.js
index d2ea142..3043f12 100644
--- a/src/utils/getLocator.js
+++ b/src/utils/getLocator.js
@@ -1,8 +1,8 @@
 export default function getLocator ( source ) {
-	let originalLines = source.split( '\n' );
+	const originalLines = source.split( '\n' );
 
 	let start = 0;
-	let lineRanges = originalLines.map( ( line, i ) => {
+	const lineRanges = originalLines.map( ( line, i ) => {
 		const end = start + line.length + 1;
 		const range = { start, end, line: i };
 
diff --git a/src/utils/getRelativePath.js b/src/utils/getRelativePath.js
index a05cb19..bf8e35d 100644
--- a/src/utils/getRelativePath.js
+++ b/src/utils/getRelativePath.js
@@ -1,6 +1,6 @@
 export default function getRelativePath ( from, to ) {
-	let fromParts = from.split( /[\/\\]/ );
-	let toParts = to.split( /[\/\\]/ );
+	const fromParts = from.split( /[\/\\]/ );
+	const toParts = to.split( /[\/\\]/ );
 
 	fromParts.pop(); // get dirname
 
diff --git a/test/.eslintrc b/test/.eslintrc
new file mode 100644
index 0000000..434fc13
--- /dev/null
+++ b/test/.eslintrc
@@ -0,0 +1,6 @@
+{
+  "env": {
+    "node": true,
+    "mocha": true
+  }
+}
\ No newline at end of file
diff --git a/test/MagicString.Bundle.js b/test/MagicString.Bundle.js
new file mode 100644
index 0000000..9033450
--- /dev/null
+++ b/test/MagicString.Bundle.js
@@ -0,0 +1,574 @@
+const assert = require( 'assert' );
+const SourceMapConsumer = require( 'source-map' ).SourceMapConsumer;
+const MagicString = require( '../' );
+
+require( 'source-map-support' ).install();
+require( 'console-group' ).install();
+
+describe( 'MagicString.Bundle', () => {
+	describe( 'addSource', () => {
+		it( 'should return this', () => {
+			const b = new MagicString.Bundle();
+			const source = new MagicString( 'abcdefghijkl' );
+
+			assert.strictEqual( b.addSource({ content: source }), b );
+		});
+
+		it( 'should accept MagicString instance as a single argument', () => {
+			const b = new MagicString.Bundle();
+			const array = [];
+			const source = new MagicString( 'abcdefghijkl', {
+				filename: 'foo.js',
+				indentExclusionRanges: array
+			});
+
+			b.addSource( source );
+			assert.strictEqual( b.sources[0].content, source );
+			assert.strictEqual( b.sources[0].filename, 'foo.js' );
+			assert.strictEqual( b.sources[0].indentExclusionRanges, array );
+		});
+
+		it( 'respects MagicString init options with { content: source }', () => {
+			const b = new MagicString.Bundle();
+			const array = [];
+			const source = new MagicString( 'abcdefghijkl', {
+				filename: 'foo.js',
+				indentExclusionRanges: array
+			});
+
+			b.addSource({ content: source });
+			assert.strictEqual( b.sources[0].content, source );
+			assert.strictEqual( b.sources[0].filename, 'foo.js' );
+			assert.strictEqual( b.sources[0].indentExclusionRanges, array );
+		});
+	});
+
+	describe( 'append', () => {
+		it( 'should append content', () => {
+			const b = new MagicString.Bundle();
+
+			b.addSource({ content: new MagicString( '*' ) });
+
+			b.append( '123' ).append( '456' );
+			assert.equal( b.toString(), '*123456' );
+		});
+
+		it( 'should append content before subsequent sources', () => {
+			const b = new MagicString.Bundle();
+
+			b.addSource( new MagicString( '*' ) );
+
+			b.append( '123' ).addSource( new MagicString( '-' ) ).append( '456' );
+			assert.equal( b.toString(), '*123\n-456' );
+		});
+
+		it( 'should return this', () => {
+			const b = new MagicString.Bundle();
+			assert.strictEqual( b.append( 'x' ), b );
+		});
+	});
+
+	describe( 'clone', () => {
+		it( 'should clone a bundle', () => {
+			const s1 = new MagicString( 'abcdef' );
+			const s2 = new MagicString( 'ghijkl' );
+			const b = new MagicString.Bundle()
+				.addSource({ content: s1 })
+				.addSource({ content: s2 })
+				.prepend( '>>>' )
+				.append( '<<<' );
+			const clone = b.clone();
+
+			assert.equal( clone.toString(), '>>>abcdef\nghijkl<<<' );
+
+			s1.overwrite( 2, 4, 'XX' );
+			assert.equal( b.toString(), '>>>abXXef\nghijkl<<<' );
+			assert.equal( clone.toString(), '>>>abcdef\nghijkl<<<' );
+		});
+	});
+
+	describe( 'generateMap', () => {
+		it( 'should generate a sourcemap', () => {
+			const b = new MagicString.Bundle()
+				.addSource({
+					filename: 'foo.js',
+					content: new MagicString( 'var answer = 42;' )
+				})
+				.addSource({
+					filename: 'bar.js',
+					content: new MagicString( 'console.log( answer );' )
+				});
+
+
+			const map = b.generateMap({
+				file: 'bundle.js',
+				includeContent: true,
+				hires: true
+			});
+
+			assert.equal( map.version, 3 );
+			assert.equal( map.file, 'bundle.js' );
+			assert.deepEqual( map.sources, [ 'foo.js', 'bar.js' ]);
+			assert.deepEqual( map.sourcesContent, [ 'var answer = 42;', 'console.log( answer );' ]);
+
+			assert.equal( map.toString(), '{"version":3,"file":"bundle.js","sources":["foo.js","bar.js"],"sourcesContent":["var answer = 42;","console.log( answer );"],"names":[],"mappings":"AAAA,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;ACAf,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC"}' );
+			assert.equal( map.toUrl(), 'data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVuZGxlLmpzIiwic291cmNlcyI6WyJmb28uanMiLCJiYXIuanMiXSwic291cmNlc0NvbnRlbnQiOlsidmFyIGFuc3dlciA9IDQyOyIsImNvbnNvbGUubG9nKCBhbnN3ZXIgKTsiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUNBZixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE [...]
+
+			const smc = new SourceMapConsumer( map );
+			let loc;
+
+			loc = smc.originalPositionFor({ line: 1, column: 0 });
+			assert.equal( loc.line, 1 );
+			assert.equal( loc.column, 0 );
+			assert.equal( loc.source, 'foo.js' );
+
+			loc = smc.originalPositionFor({ line: 1, column: 1 });
+			assert.equal( loc.line, 1 );
+			assert.equal( loc.column, 1 );
+			assert.equal( loc.source, 'foo.js' );
+
+			loc = smc.originalPositionFor({ line: 2, column: 0 });
+			assert.equal( loc.line, 1 );
+			assert.equal( loc.column, 0 );
+			assert.equal( loc.source, 'bar.js' );
+
+			loc = smc.originalPositionFor({ line: 2, column: 1 });
+			assert.equal( loc.line, 1 );
+			assert.equal( loc.column, 1 );
+			assert.equal( loc.source, 'bar.js' );
+		});
+
+		it( 'should handle Windows-style paths', () => {
+			const b = new MagicString.Bundle()
+				.addSource({
+					filename: 'path\\to\\foo.js',
+					content: new MagicString( 'var answer = 42;' )
+				})
+				.addSource({
+					filename: 'path\\to\\bar.js',
+					content: new MagicString( 'console.log( answer );' )
+				});
+
+			const map = b.generateMap({
+				file: 'bundle.js',
+				includeContent: true,
+				hires: true
+			});
+
+			assert.equal( map.version, 3 );
+			assert.equal( map.file, 'bundle.js' );
+			assert.deepEqual( map.sources, [ 'path/to/foo.js', 'path/to/bar.js' ]);
+			assert.deepEqual( map.sourcesContent, [ 'var answer = 42;', 'console.log( answer );' ]);
+
+			assert.equal( map.toString(), '{"version":3,"file":"bundle.js","sources":["path/to/foo.js","path/to/bar.js"],"sourcesContent":["var answer = 42;","console.log( answer );"],"names":[],"mappings":"AAAA,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;ACAf,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC"}' );
+
+			const smc = new SourceMapConsumer( map );
+			let loc;
+
+			loc = smc.originalPositionFor({ line: 1, column: 0 });
+			assert.equal( loc.line, 1 );
+			assert.equal( loc.column, 0 );
+			assert.equal( loc.source, 'path/to/foo.js' );
+
+			loc = smc.originalPositionFor({ line: 1, column: 1 });
+			assert.equal( loc.line, 1 );
+			assert.equal( loc.column, 1 );
+			assert.equal( loc.source, 'path/to/foo.js' );
+
+			loc = smc.originalPositionFor({ line: 2, column: 0 });
+			assert.equal( loc.line, 1 );
+			assert.equal( loc.column, 0 );
+			assert.equal( loc.source, 'path/to/bar.js' );
+
+			loc = smc.originalPositionFor({ line: 2, column: 1 });
+			assert.equal( loc.line, 1 );
+			assert.equal( loc.column, 1 );
+			assert.equal( loc.source, 'path/to/bar.js' );
+		});
+
+		it( 'should handle edge case with intro content', () => {
+			const b = new MagicString.Bundle()
+				.addSource({
+					filename: 'foo.js',
+					content: new MagicString( 'var answer = 42;' )
+				})
+				.addSource({
+					filename: 'bar.js',
+					content: new MagicString( '\nconsole.log( answer );' )
+				})
+				.indent().prepend( '(function () {\n' ).append( '\n}());' );
+
+			const map = b.generateMap({
+				file: 'bundle.js',
+				includeContent: true,
+				hires: true
+			});
+
+			const smc = new SourceMapConsumer( map );
+			let loc;
+
+			loc = smc.originalPositionFor({ line: 2, column: 1 });
+			assert.equal( loc.line, 1 );
+			assert.equal( loc.column, 0 );
+			assert.equal( loc.source, 'foo.js' );
+
+			loc = smc.originalPositionFor({ line: 2, column: 2 });
+			assert.equal( loc.line, 1 );
+			assert.equal( loc.column, 1 );
+			assert.equal( loc.source, 'foo.js' );
+
+			loc = smc.originalPositionFor({ line: 4, column: 1 });
+			assert.equal( loc.line, 2 );
+			assert.equal( loc.column, 0 );
+			assert.equal( loc.source, 'bar.js' );
+
+			loc = smc.originalPositionFor({ line: 4, column: 2 });
+			assert.equal( loc.line, 2 );
+			assert.equal( loc.column, 1 );
+			assert.equal( loc.source, 'bar.js' );
+		});
+
+		it( 'should allow missing file option when generating map', () => {
+			new MagicString.Bundle()
+				.addSource({
+					filename: 'foo.js',
+					content: new MagicString( 'var answer = 42;' )
+				})
+				.generateMap({
+					includeContent: true,
+					hires: true
+				});
+		});
+
+		it( 'should handle repeated sources', () => {
+			const b = new MagicString.Bundle();
+
+			const foo = new MagicString( 'var one = 1;\nvar three = 3;', {
+				filename: 'foo.js'
+			});
+
+			const bar = new MagicString( 'var two = 2;\nvar four = 4;', {
+				filename: 'bar.js'
+			});
+
+			b.addSource( foo.snip( 0, 12 ) );
+			b.addSource( bar.snip( 0, 12 ) );
+			b.addSource( foo.snip( 13, 27 ) );
+			b.addSource( bar.snip( 13, 26 ) );
+
+			const code = b.toString();
+			assert.equal( code, 'var one = 1;\nvar two = 2;\nvar three = 3;\nvar four = 4;' );
+
+			const map = b.generateMap({
+				includeContent: true,
+				hires: true
+			});
+
+			assert.equal( map.sources.length, 2 );
+			assert.equal( map.sourcesContent.length, 2 );
+
+			const smc = new SourceMapConsumer( map );
+			let loc;
+
+			loc = smc.originalPositionFor({ line: 1, column: 0 });
+			assert.equal( loc.line, 1 );
+			assert.equal( loc.column, 0 );
+			assert.equal( loc.source, 'foo.js' );
+
+			loc = smc.originalPositionFor({ line: 2, column: 0 });
+			assert.equal( loc.line, 1 );
+			assert.equal( loc.column, 0 );
+			assert.equal( loc.source, 'bar.js' );
+
+			loc = smc.originalPositionFor({ line: 3, column: 0 });
+			assert.equal( loc.line, 2 );
+			assert.equal( loc.column, 0 );
+			assert.equal( loc.source, 'foo.js' );
+
+			loc = smc.originalPositionFor({ line: 4, column: 0 });
+			assert.equal( loc.line, 2 );
+			assert.equal( loc.column, 0 );
+			assert.equal( loc.source, 'bar.js' );
+		});
+
+		it( 'should recover original names', () => {
+			const b = new MagicString.Bundle();
+
+			const one = new MagicString( 'function one () {}', { filename: 'one.js' });
+			const two = new MagicString( 'function two () {}', { filename: 'two.js' });
+
+			one.overwrite( 9, 12, 'three', true );
+			two.overwrite( 9, 12, 'four', true );
+
+			b.addSource( one );
+			b.addSource( two );
+
+			const map = b.generateMap({
+				file: 'output.js',
+				source: 'input.js',
+				includeContent: true
+			});
+
+			const smc = new SourceMapConsumer( map );
+			let loc;
+
+			loc = smc.originalPositionFor({ line: 1, column: 9 });
+			assert.equal( loc.name, 'one' );
+
+			loc = smc.originalPositionFor({ line: 2, column: 9 });
+			assert.equal( loc.name, 'two' );
+		});
+
+		it( 'should exclude sources without filename from sourcemap', () => {
+			const b = new MagicString.Bundle();
+
+			const one = new MagicString( 'function one () {}', { filename: 'one.js' });
+			const two = new MagicString( 'function two () {}', { filename: null });
+			const three = new MagicString( 'function three () {}', { filename: 'three.js' });
+
+			b.addSource( one );
+			b.addSource( two );
+			b.addSource( three );
+
+			const map = b.generateMap({
+				file: 'output.js',
+				source: 'input.js',
+				includeContent: true
+			});
+
+			const smc = new SourceMapConsumer( map );
+			let loc;
+
+			loc = smc.originalPositionFor({ line: 1, column: 9 });
+			assert.equal( loc.source, 'one.js' );
+
+			loc = smc.originalPositionFor({ line: 2, column: 9 });
+			assert.equal( loc.source, null );
+
+			loc = smc.originalPositionFor({ line: 3, column: 9 });
+			assert.equal( loc.source, 'three.js' );
+		});
+
+		it( 'handles prepended content', () => {
+			const b = new MagicString.Bundle();
+
+			const one = new MagicString( 'function one () {}', { filename: 'one.js' });
+			const two = new MagicString( 'function two () {}', { filename: 'two.js' });
+			two.prepend( 'function oneAndAHalf() {}\n' );
+
+			b.addSource( one );
+			b.addSource( two );
+
+			const map = b.generateMap({
+				file: 'output.js',
+				source: 'input.js',
+				includeContent: true
+			});
+
+			const smc = new SourceMapConsumer( map );
+			let loc;
+
+			loc = smc.originalPositionFor({ line: 1, column: 9 });
+			assert.equal( loc.source, 'one.js' );
+
+			loc = smc.originalPositionFor({ line: 3, column: 9 });
+			assert.equal( loc.source, 'two.js' );
+		});
+
+		it( 'handles appended content', () => {
+			const b = new MagicString.Bundle();
+
+			const one = new MagicString( 'function one () {}', { filename: 'one.js' });
+			one.append( '\nfunction oneAndAHalf() {}' );
+			const two = new MagicString( 'function two () {}', { filename: 'two.js' });
+
+			b.addSource( one );
+			b.addSource( two );
+
+			const map = b.generateMap({
+				file: 'output.js',
+				source: 'input.js',
+				includeContent: true
+			});
+
+			const smc = new SourceMapConsumer( map );
+			let loc;
+
+			loc = smc.originalPositionFor({ line: 1, column: 9 });
+			assert.equal( loc.source, 'one.js' );
+
+			loc = smc.originalPositionFor({ line: 3, column: 9 });
+			assert.equal( loc.source, 'two.js' );
+		});
+	});
+
+	describe( 'indent', () => {
+		it( 'should indent a bundle', () => {
+			const b = new MagicString.Bundle();
+
+			b.addSource({ content: new MagicString( 'abcdef' ) });
+			b.addSource({ content: new MagicString( 'ghijkl' ) });
+
+			b.indent().prepend( '>>>\n' ).append( '\n<<<' );
+			assert.equal( b.toString(), '>>>\n\tabcdef\n\tghijkl\n<<<' );
+		});
+
+		it( 'should ignore non-indented sources when guessing indentation', () => {
+			const b = new MagicString.Bundle();
+
+			b.addSource({ content: new MagicString( 'abcdef' ) });
+			b.addSource({ content: new MagicString( 'ghijkl' ) });
+			b.addSource({ content: new MagicString( '  mnopqr' ) });
+
+			b.indent();
+			assert.equal( b.toString(), '  abcdef\n  ghijkl\n    mnopqr' );
+		});
+
+		it( 'should respect indent exclusion ranges', () => {
+			const b = new MagicString.Bundle();
+
+			b.addSource({
+				content: new MagicString( 'abc\ndef\nghi\njkl' ),
+				indentExclusionRanges: [ 7, 15 ]
+			});
+
+			b.indent( '  ' );
+			assert.equal( b.toString(), '  abc\n  def\nghi\njkl' );
+
+			b.indent( '>>' );
+			assert.equal( b.toString(), '>>  abc\n>>  def\nghi\njkl' );
+		});
+
+		it( 'does not indent sources with no preceding newline, i.e. append()', () => {
+			const b = new MagicString.Bundle();
+
+			b.addSource( new MagicString( 'abcdef' ) );
+			b.addSource( new MagicString( 'ghijkl' ) );
+
+			b.prepend( '>>>' ).append( '<<<' ).indent();
+			assert.equal( b.toString(), '\t>>>abcdef\n\tghijkl<<<' );
+		});
+
+		it( 'should noop with an empty string', () => {
+			const b = new MagicString.Bundle();
+
+			b.addSource( new MagicString( 'abcdef' ) );
+			b.addSource( new MagicString( 'ghijkl' ) );
+
+			b.indent( '' );
+			assert.equal( b.toString(), 'abcdef\nghijkl' );
+		});
+
+		it( 'indents prepended content', () => {
+			const b = new MagicString.Bundle();
+			b.prepend( 'a\nb' ).indent();
+
+			assert.equal( b.toString(), '\ta\n\tb' );
+		});
+
+		it( 'indents content immediately following intro with trailing newline', () => {
+			const b = new MagicString.Bundle({ separator: '\n\n' });
+
+			const s = new MagicString( '2' );
+			b.addSource({ content: s });
+
+			b.prepend( '1\n' );
+
+			assert.equal( b.indent().toString(), '\t1\n\t2' );
+		});
+
+		it( 'should return this', () => {
+			const b = new MagicString.Bundle();
+			assert.strictEqual( b.indent(), b );
+		});
+
+		it( 'should return this on noop', () => {
+			const b = new MagicString.Bundle();
+			assert.strictEqual( b.indent( '' ), b );
+		});
+	});
+
+	describe( 'prepend', () => {
+		it( 'should append content', () => {
+			const b = new MagicString.Bundle();
+
+			b.addSource({ content: new MagicString( '*' ) });
+
+			b.prepend( '123' ).prepend( '456' );
+			assert.equal( b.toString(), '456123*' );
+		});
+
+		it( 'should return this', () => {
+			const b = new MagicString.Bundle();
+			assert.strictEqual( b.prepend( 'x' ), b );
+		});
+	});
+
+	describe( 'trim', () => {
+		it( 'should trim bundle', () => {
+			const b = new MagicString.Bundle();
+
+			b.addSource({
+				content: new MagicString( '   abcdef   ' )
+			});
+
+			b.addSource({
+				content: new MagicString( '   ghijkl   ' )
+			});
+
+			b.trim();
+			assert.equal( b.toString(), 'abcdef   \n   ghijkl' );
+		});
+
+		it( 'should handle funky edge cases', () => {
+			const b = new MagicString.Bundle();
+
+			b.addSource({
+				content: new MagicString( '   abcdef   ' )
+			});
+
+			b.addSource({
+				content: new MagicString( '   x   ' )
+			});
+
+			b.prepend( '\n>>>\n' ).append( '   ' );
+
+			b.trim();
+			assert.equal( b.toString(), '>>>\n   abcdef   \n   x' );
+		});
+
+		it( 'should return this', () => {
+			const b = new MagicString.Bundle();
+			assert.strictEqual( b.trim(), b );
+		});
+	});
+
+	describe( 'toString', () => {
+		it( 'should separate with a newline by default', () => {
+			const b = new MagicString.Bundle();
+
+			b.addSource( new MagicString( 'abc' ) );
+			b.addSource( new MagicString( 'def' ) );
+
+			assert.strictEqual( b.toString(), 'abc\ndef' );
+		});
+
+		it( 'should accept separator option', () => {
+			const b = new MagicString.Bundle({ separator: '==' });
+
+			b.addSource( new MagicString( 'abc' ) );
+			b.addSource( new MagicString( 'def' ) );
+
+			assert.strictEqual( b.toString(), 'abc==def' );
+		});
+
+		it( 'should accept empty string separator option', () => {
+			const b = new MagicString.Bundle({ separator: '' });
+
+			b.addSource( new MagicString( 'abc' ) );
+			b.addSource( new MagicString( 'def' ) );
+
+			assert.strictEqual( b.toString(), 'abcdef' );
+		});
+	});
+});
diff --git a/test/MagicString.js b/test/MagicString.js
new file mode 100644
index 0000000..d34a269
--- /dev/null
+++ b/test/MagicString.js
@@ -0,0 +1,1127 @@
+const assert = require( 'assert' );
+const SourceMapConsumer = require( 'source-map' ).SourceMapConsumer;
+const MagicString = require( '../' );
+
+require( 'source-map-support' ).install();
+require( 'console-group' ).install();
+
+describe( 'MagicString', () => {
+	describe( 'options', () => {
+		it( 'stores source file information', () => {
+			const s = new MagicString( 'abc', {
+				filename: 'foo.js'
+			});
+
+			assert.equal( s.filename, 'foo.js' );
+		});
+	});
+
+	describe( 'append', () => {
+		it( 'should append content', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+
+			s.append( 'xyz' );
+			assert.equal( s.toString(), 'abcdefghijklxyz' );
+
+			s.append( 'xyz' );
+			assert.equal( s.toString(), 'abcdefghijklxyzxyz' );
+		});
+
+		it( 'should return this', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			assert.strictEqual( s.append( 'xyz' ), s );
+		});
+
+		it( 'should throw when given non-string content', () => {
+			const s = new MagicString( '' );
+			assert.throws( () => s.append( [] ), TypeError );
+		});
+	});
+
+	describe( '(ap|pre)pend(Left|Right)', () => {
+		it( 'preserves intended order', () => {
+			const s = new MagicString( '0123456789' );
+
+			s.insertLeft( 5, 'A' );
+			s.insertRight( 5, 'a' );
+			s.insertRight( 5, 'b' );
+			s.insertLeft( 5, 'B' );
+			s.insertLeft( 5, 'C' );
+			s.insertRight( 5, 'c' );
+
+			assert.equal( s.toString(), '01234ABCcba56789' );
+			assert.equal( s.slice(0, 5) , '01234ABC' );
+			assert.equal( s.slice(5), 'cba56789' );
+
+			s.prependLeft( 5, '<' );
+			s.prependLeft( 5, '{' );
+			assert.equal( s.toString(), '01234{<ABCcba56789' );
+
+			s.appendRight( 5, '>' );
+			s.appendRight( 5, '}' );
+			assert.equal( s.toString(), '01234{<ABCcba>}56789' );
+
+			s.appendLeft(5, '(');   // appendLeft is a synonym for insertLeft
+			s.appendLeft(5, '[');   // appendLeft is a synonym for insertLeft
+			assert.equal( s.toString(), '01234{<ABC([cba>}56789' );
+
+			s.prependRight(5, ')'); // prependRight is a synonym for insertRight
+			s.prependRight(5, ']'); // prependRight is a synonym for insertRight
+			assert.equal( s.toString(), '01234{<ABC([])cba>}56789' );
+
+			assert.equal( s.slice(0, 5), '01234{<ABC([' );
+			assert.equal( s.slice(5), '])cba>}56789' );
+		});
+
+		it( 'preserves intended order at beginning of string', () => {
+			const s = new MagicString( 'x' );
+
+			s.appendLeft( 0, '1' );
+			s.prependLeft( 0, '2' );
+			s.appendLeft( 0, '3' );
+			s.prependLeft( 0, '4' );
+
+			assert.equal( s.toString(), '4213x' );
+		});
+
+		it( 'preserves intended order at end of string', () => {
+			const s = new MagicString( 'x' );
+
+			s.appendRight( 1, '1' );
+			s.prependRight( 1, '2' );
+			s.appendRight( 1, '3' );
+			s.prependRight( 1, '4' );
+
+			assert.equal( s.toString(), 'x4213' );
+		});
+	});
+
+	describe( 'appendLeft', () => {
+		it( 'should return this', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			assert.strictEqual( s.appendLeft( 0, 'a' ), s );
+		});
+	});
+
+	describe( 'appendRight', () => {
+		it( 'should return this', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			assert.strictEqual( s.appendRight( 0, 'a' ), s );
+		});
+	});
+
+	describe( 'clone', () => {
+		it( 'should clone a magic string', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+
+			s.overwrite( 3, 9, 'XYZ' );
+			const c = s.clone();
+
+			assert.notEqual( s, c );
+			assert.equal( c.toString(), 'abcXYZjkl' );
+		});
+
+		it( 'should clone filename info', () => {
+			const s = new MagicString( 'abcdefghijkl', { filename: 'foo.js' });
+			const c = s.clone();
+
+			assert.equal( c.filename, 'foo.js' );
+		});
+
+		it( 'should clone indentExclusionRanges', () => {
+			const array = [ 3, 6 ];
+			const source = new MagicString( 'abcdefghijkl', {
+				filename: 'foo.js',
+				indentExclusionRanges: array
+			});
+
+			const clone = source.clone();
+
+			assert.notStrictEqual( source.indentExclusionRanges, clone.indentExclusionRanges );
+			assert.deepEqual( source.indentExclusionRanges, clone.indentExclusionRanges );
+		});
+
+		it( 'should clone sourcemapLocations', () => {
+			const source = new MagicString( 'abcdefghijkl', {
+				filename: 'foo.js'
+			});
+
+			source.addSourcemapLocation( 3 );
+
+			const clone = source.clone();
+
+			assert.notStrictEqual( source.sourcemapLocations, clone.sourcemapLocations );
+			assert.deepEqual( source.sourcemapLocations, clone.sourcemapLocations );
+		});
+	});
+
+	describe( 'generateMap', () => {
+		it( 'should generate a sourcemap', () => {
+			const s = new MagicString( 'abcdefghijkl' ).remove( 3, 9 );
+
+			const map = s.generateMap({
+				file: 'output.md',
+				source: 'input.md',
+				includeContent: true,
+				hires: true
+			});
+
+			assert.equal( map.version, 3 );
+			assert.equal( map.file, 'output.md' );
+			assert.deepEqual( map.sources, [ 'input.md' ]);
+			assert.deepEqual( map.sourcesContent, [ 'abcdefghijkl' ]);
+			assert.equal( map.mappings, 'AAAA,CAAC,CAAC,CAAC,AAAM,CAAC,CAAC' );
+
+			assert.equal( map.toString(), '{"version":3,"file":"output.md","sources":["input.md"],"sourcesContent":["abcdefghijkl"],"names":[],"mappings":"AAAA,CAAC,CAAC,CAAC,AAAM,CAAC,CAAC"}' );
+			assert.equal( map.toUrl(), 'data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3V0cHV0Lm1kIiwic291cmNlcyI6WyJpbnB1dC5tZCJdLCJzb3VyY2VzQ29udGVudCI6WyJhYmNkZWZnaGlqa2wiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsQ0FBQyxDQUFDLENBQUMsQUFBTSxDQUFDLENBQUMifQ==' );
+
+			const smc = new SourceMapConsumer( map );
+			let loc;
+
+			loc = smc.originalPositionFor({ line: 1, column: 0 });
+			assert.equal( loc.line, 1 );
+			assert.equal( loc.column, 0 );
+
+			loc = smc.originalPositionFor({ line: 1, column: 1 });
+			assert.equal( loc.line, 1 );
+			assert.equal( loc.column, 1 );
+
+			loc = smc.originalPositionFor({ line: 1, column: 4 });
+			assert.equal( loc.line, 1 );
+			assert.equal( loc.column, 10 );
+		});
+
+		it( 'should generate a correct sourcemap for indented content', () => {
+			const s = new MagicString( 'var answer = 42;\nconsole.log("the answer is %s", answer);' );
+
+			s.prepend( '\'use strict\';\n\n' );
+			s.indent( '\t' ).prepend( '(function () {\n' ).append( '\n}).call(global);' );
+
+			const map = s.generateMap({
+				source: 'input.md',
+				includeContent: true,
+				hires: true
+			});
+
+			const smc = new SourceMapConsumer( map );
+
+			const originLoc = smc.originalPositionFor({ line: 5, column: 1 });
+			assert.equal( originLoc.line, 2 );
+			assert.equal( originLoc.column, 0 );
+		});
+
+		it( 'should generate a sourcemap using specified locations', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+
+			s.addSourcemapLocation( 0 );
+			s.addSourcemapLocation( 3 );
+			s.addSourcemapLocation( 10 );
+
+			s.remove( 6, 9 );
+			const map = s.generateMap({
+				file: 'output.md',
+				source: 'input.md',
+				includeContent: true
+			});
+
+			assert.equal( map.version, 3 );
+			assert.equal( map.file, 'output.md' );
+			assert.deepEqual( map.sources, [ 'input.md' ]);
+			assert.deepEqual( map.sourcesContent, [ 'abcdefghijkl' ]);
+
+			assert.equal( map.toString(), '{"version":3,"file":"output.md","sources":["input.md"],"sourcesContent":["abcdefghijkl"],"names":[],"mappings":"AAAA,GAAG,GAAG,AAAG,CAAC"}' );
+			assert.equal( map.toUrl(), 'data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3V0cHV0Lm1kIiwic291cmNlcyI6WyJpbnB1dC5tZCJdLCJzb3VyY2VzQ29udGVudCI6WyJhYmNkZWZnaGlqa2wiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsR0FBRyxHQUFHLEFBQUcsQ0FBQyJ9' );
+
+			const smc = new SourceMapConsumer( map );
+			let loc;
+
+			loc = smc.originalPositionFor({ line: 1, column: 0 });
+			assert.equal( loc.line, 1 );
+			assert.equal( loc.column, 0 );
+
+			loc = smc.originalPositionFor({ line: 1, column: 3 });
+			assert.equal( loc.line, 1 );
+			assert.equal( loc.column, 3 );
+
+			loc = smc.originalPositionFor({ line: 1, column: 7 });
+			assert.equal( loc.line, 1 );
+			assert.equal( loc.column, 10 );
+		});
+
+		it( 'should correctly map inserted content', () => {
+			const s = new MagicString( 'function Foo () {}' );
+
+			s.overwrite( 9, 12, 'Bar' );
+
+			const map = s.generateMap({
+				file: 'output.js',
+				source: 'input.js',
+				includeContent: true
+			});
+
+			const smc = new SourceMapConsumer( map );
+
+			const loc = smc.originalPositionFor({ line: 1, column: 9 });
+			assert.equal( loc.line, 1 );
+			assert.equal( loc.column, 9 );
+		});
+
+		it( 'should yield consistent results between insertLeft and insertRight', () => {
+			const s1 = new MagicString( 'abcdefghijkl' );
+			s1.insertLeft( 6, 'X' );
+
+			const s2 = new MagicString( 'abcdefghijkl' );
+			s2.insertRight( 6, 'X' );
+
+			const m1 = s1.generateMap({ file: 'output', source: 'input', includeContent: true });
+			const m2 = s2.generateMap({ file: 'output', source: 'input', includeContent: true });
+
+			assert.deepEqual( m1, m2 );
+		});
+
+		it( 'should recover original names', () => {
+			const s = new MagicString( 'function Foo () {}' );
+
+			s.overwrite( 9, 12, 'Bar', true );
+
+			const map = s.generateMap({
+				file: 'output.js',
+				source: 'input.js',
+				includeContent: true
+			});
+
+			const smc = new SourceMapConsumer( map );
+
+			const loc = smc.originalPositionFor({ line: 1, column: 9 });
+			assert.equal( loc.name, 'Foo' );
+		});
+
+		it( 'should generate one segment per replacement', () => {
+			const s = new MagicString( 'var answer = 42' );
+			s.overwrite( 4, 10, 'number', true );
+
+			const map = s.generateMap({
+				file: 'output.js',
+				source: 'input.js',
+				includeContent: true
+			});
+
+			const smc = new SourceMapConsumer( map );
+
+			let numMappings = 0;
+			smc.eachMapping( () => numMappings += 1 );
+
+			assert.equal( numMappings, 3 ); // one at 0, one at the edit, one afterwards
+		});
+
+		it( 'should generate a sourcemap that correctly locates moved content', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			s.move( 3, 6, 9 );
+
+			const result = s.toString();
+			const map = s.generateMap({
+				file: 'output.js',
+				source: 'input.js',
+				includeContent: true,
+				hires: true
+			});
+
+			const smc = new SourceMapConsumer( map );
+
+			'abcdefghijkl'.split( '' ).forEach( ( letter, i ) => {
+				const column = result.indexOf( letter );
+				const loc = smc.originalPositionFor({ line: 1, column });
+
+				assert.equal( loc.line, 1 );
+				assert.equal( loc.column, i );
+			});
+		});
+
+		it( 'generates a map with trimmed content (#53)', () => {
+			const s1 = new MagicString( 'abcdefghijkl ' ).trim();
+			const map1 = s1.generateMap({
+				file: 'output',
+				source: 'input',
+				includeContent: true,
+				hires: true
+			});
+
+			const smc1 = new SourceMapConsumer( map1 );
+			const loc1 = smc1.originalPositionFor({ line: 1, column: 11 });
+
+			assert.equal( loc1.column, 11 );
+
+			const s2 = new MagicString( ' abcdefghijkl' ).trim();
+			const map2 = s2.generateMap({
+				file: 'output',
+				source: 'input',
+				includeContent: true,
+				hires: true
+			});
+
+			const smc2 = new SourceMapConsumer( map2 );
+			const loc2 = smc2.originalPositionFor({ line: 1, column: 1 });
+
+			assert.equal( loc2.column, 2 );
+		});
+
+		it( 'skips empty segments at the start', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			s.remove( 0, 3 ).remove( 3, 6 );
+
+			const map = s.generateMap();
+			const smc = new SourceMapConsumer( map );
+			const loc = smc.originalPositionFor({ line: 1, column: 6 });
+
+			assert.equal( loc.column, 6 );
+		});
+
+		it( 'skips indentation at the start', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			s.indent( '    ' );
+
+			const map = s.generateMap();
+			assert.equal( map.mappings, 'IAAA' );
+		});
+	});
+
+	describe( 'getIndentString', () => {
+		it( 'should guess the indent string', () => {
+			const s = new MagicString( 'abc\n  def\nghi' );
+			assert.equal( s.getIndentString(), '  ' );
+		});
+
+		it( 'should return a tab if no lines are indented', () => {
+			const s = new MagicString( 'abc\ndef\nghi' );
+			assert.equal( s.getIndentString(), '\t' );
+		});
+	});
+
+	describe( 'indent', () => {
+		it( 'should indent content with a single tab character by default', () => {
+			const s = new MagicString( 'abc\ndef\nghi\njkl' );
+
+			s.indent();
+			assert.equal( s.toString(), '\tabc\n\tdef\n\tghi\n\tjkl' );
+
+			s.indent();
+			assert.equal( s.toString(), '\t\tabc\n\t\tdef\n\t\tghi\n\t\tjkl' );
+		});
+
+		it( 'should indent content, using existing indentation as a guide', () => {
+			const s = new MagicString( 'abc\n  def\n    ghi\n  jkl' );
+
+			s.indent();
+			assert.equal( s.toString(), '  abc\n    def\n      ghi\n    jkl' );
+
+			s.indent();
+			assert.equal( s.toString(), '    abc\n      def\n        ghi\n      jkl' );
+		});
+
+		it( 'should disregard single-space indentation when auto-indenting', () => {
+			const s = new MagicString( 'abc\n/**\n *comment\n */' );
+
+			s.indent();
+			assert.equal( s.toString(), '\tabc\n\t/**\n\t *comment\n\t */' );
+		});
+
+		it( 'should indent content using the supplied indent string', () => {
+			const s = new MagicString( 'abc\ndef\nghi\njkl' );
+
+			s.indent( '  ');
+			assert.equal( s.toString(), '  abc\n  def\n  ghi\n  jkl' );
+
+			s.indent( '>>' );
+			assert.equal( s.toString(), '>>  abc\n>>  def\n>>  ghi\n>>  jkl' );
+		});
+
+		it( 'should indent content using the empty string if specified (i.e. noop)', () => {
+			const s = new MagicString( 'abc\ndef\nghi\njkl' );
+
+			s.indent( '');
+			assert.equal( s.toString(), 'abc\ndef\nghi\njkl' );
+		});
+
+		it( 'should prevent excluded characters from being indented', () => {
+			const s = new MagicString( 'abc\ndef\nghi\njkl' );
+
+			s.indent( '  ', { exclude: [ 7, 15 ] });
+			assert.equal( s.toString(), '  abc\n  def\nghi\njkl' );
+
+			s.indent( '>>', { exclude: [ 7, 15 ] });
+			assert.equal( s.toString(), '>>  abc\n>>  def\nghi\njkl' );
+		});
+
+		it( 'should not add characters to empty lines', () => {
+			const s = new MagicString( '\n\nabc\ndef\n\nghi\njkl' );
+
+			s.indent();
+			assert.equal( s.toString(), '\n\n\tabc\n\tdef\n\n\tghi\n\tjkl' );
+
+			s.indent();
+			assert.equal( s.toString(), '\n\n\t\tabc\n\t\tdef\n\n\t\tghi\n\t\tjkl' );
+		});
+
+		it( 'should not add characters to empty lines, even on Windows', () => {
+			const s = new MagicString( '\r\n\r\nabc\r\ndef\r\n\r\nghi\r\njkl' );
+
+			s.indent();
+			assert.equal( s.toString(), '\r\n\r\n\tabc\r\n\tdef\r\n\r\n\tghi\r\n\tjkl' );
+
+			s.indent();
+			assert.equal( s.toString(), '\r\n\r\n\t\tabc\r\n\t\tdef\r\n\r\n\t\tghi\r\n\t\tjkl' );
+		});
+
+		it( 'should indent content with removals', () => {
+			const s = new MagicString( '/* remove this line */\nvar foo = 1;' );
+
+			s.remove( 0, 23 );
+			s.indent();
+
+			assert.equal( s.toString(), '\tvar foo = 1;' );
+		});
+
+		it( 'should not indent patches in the middle of a line', () => {
+			const s = new MagicString( 'class Foo extends Bar {}' );
+
+			s.overwrite( 18, 21, 'Baz' );
+			assert.equal( s.toString(), 'class Foo extends Baz {}' );
+
+			s.indent();
+			assert.equal( s.toString(), '\tclass Foo extends Baz {}' );
+		});
+
+		it( 'should return this', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			assert.strictEqual( s.indent(), s );
+		});
+
+		it( 'should return this on noop', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			assert.strictEqual( s.indent( '' ), s );
+		});
+	});
+
+	describe( 'insert', () => {
+		it( 'is deprecated', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			assert.throws( () => s.insert( 6, 'X' ), /deprecated/ );
+		});
+
+		// TODO move this into insertRight and insertLeft tests
+
+		// it( 'should insert characters in the correct location', () => {
+		// 	const s = new MagicString( 'abcdefghijkl' );
+		//
+		// 	s.insert( 0, '>>>' );
+		// 	s.insert( 6, '***' );
+		// 	s.insert( 12, '<<<' );
+		//
+		// 	assert.equal( s.toString(), '>>>abcdef***ghijkl<<<' );
+		// });
+		//
+		// it( 'should return this', () => {
+		// 	const s = new MagicString( 'abcdefghijkl' );
+		// 	assert.strictEqual( s.insert( 0, 'a' ), s );
+		// });
+		//
+		// it( 'should insert repeatedly at the same position correctly', () => {
+		// 	const s = new MagicString( 'ab' );
+		// 	assert.equal( s.insert(1, '1').toString(), 'a1b' );
+		// 	assert.equal( s.insert(1, '2').toString(), 'a12b' );
+		// });
+		//
+		// it( 'should insert repeatedly at the beginning correctly', () => {
+		// 	const s = new MagicString( 'ab' );
+		// 	assert.equal( s.insert(0, '1').toString(), '1ab' );
+		// 	assert.equal( s.insert(0, '2').toString(), '12ab' );
+		// });
+		//
+		// it( 'should throw when given non-string content', () => {
+		// 	const s = new MagicString( '' );
+		// 	assert.throws(
+		// 		function () { s.insert( 0, [] ); },
+		// 		TypeError
+		// 	);
+		// });
+		//
+		// it( 'should allow inserting after removed range', () => {
+		// 	const s = new MagicString( 'abcd' );
+		// 	s.remove( 1, 2 );
+		// 	s.insert( 2, 'z' );
+		// 	assert.equal( s.toString(), 'azcd' );
+		// });
+	});
+
+	describe( 'move', () => {
+		it( 'moves content from the start', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			s.move( 0, 3, 6 );
+
+			assert.equal( s.toString(), 'defabcghijkl' );
+		});
+
+		it( 'moves content to the start', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			s.move( 3, 6, 0 );
+
+			assert.equal( s.toString(), 'defabcghijkl' );
+		});
+
+		it( 'moves content from the end', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			s.move( 9, 12, 6 );
+
+			assert.equal( s.toString(), 'abcdefjklghi' );
+		});
+
+		it( 'moves content to the end', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			s.move( 6, 9, 12 );
+
+			assert.equal( s.toString(), 'abcdefjklghi' );
+		});
+
+		it( 'ignores redundant move', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			s.insertRight( 9, 'X' );
+			s.move( 9, 12, 6 );
+			s.insertLeft( 12, 'Y' );
+			s.move( 6, 9, 12 ); // this is redundant – [6,9] is already after [9,12]
+
+			assert.equal( s.toString(), 'abcdefXjklYghi' );
+		});
+
+		it( 'moves content to the middle', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			s.move( 3, 6, 9 );
+
+			assert.equal( s.toString(), 'abcghidefjkl' );
+		});
+
+		it( 'handles multiple moves of the same snippet', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+
+			s.move( 0, 3, 6 );
+			assert.equal( s.toString(), 'defabcghijkl' );
+
+			s.move( 0, 3, 9 );
+			assert.equal( s.toString(), 'defghiabcjkl' );
+		});
+
+		it( 'handles moves of adjacent snippets', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+
+			s.move( 0, 2, 6 );
+			assert.equal( s.toString(), 'cdefabghijkl' );
+
+			s.move( 2, 4, 6 );
+			assert.equal( s.toString(), 'efabcdghijkl' );
+		});
+
+		it( 'handles moves to same index', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			s.move( 0, 2, 6 ).move( 3, 5, 6 );
+
+			assert.equal( s.toString(), 'cfabdeghijkl' );
+		});
+
+		it( 'refuses to move a selection to inside itself', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+
+			assert.throws( () => s.move( 3, 6, 3 ), /Cannot move a selection inside itself/ );
+
+			assert.throws( () => s.move( 3, 6, 4 ), /Cannot move a selection inside itself/ );
+
+			assert.throws( () => s.move( 3, 6, 6 ), /Cannot move a selection inside itself/ );
+		});
+
+		it( 'allows edits of moved content', () => {
+			const s1 = new MagicString( 'abcdefghijkl' );
+
+			s1.move( 3, 6, 9 );
+			s1.overwrite( 3, 6, 'DEF' );
+
+			assert.equal( s1.toString(), 'abcghiDEFjkl' );
+
+			const s2 = new MagicString( 'abcdefghijkl' );
+
+			s2.move( 3, 6, 9 );
+			s2.overwrite( 4, 5, 'E' );
+
+			assert.equal( s2.toString(), 'abcghidEfjkl' );
+		});
+
+		// it( 'move follows inserts', () => {
+		// 	const s = new MagicString( 'abcdefghijkl' );
+		//
+		// 	s.insertLeft( 3, 'X' ).move( 6, 9, 3 );
+		// 	assert.equal( s.toString(), 'abcXghidefjkl' );
+		// });
+		//
+		// it( 'inserts follow move', () => {
+		// 	const s = new MagicString( 'abcdefghijkl' );
+		//
+		// 	s.insert( 3, 'X' ).move( 6, 9, 3 ).insert( 3, 'Y' );
+		// 	assert.equal( s.toString(), 'abcXghiYdefjkl' );
+		// });
+		//
+		// it( 'discards inserts at end of move by default', () => {
+		// 	const s = new MagicString( 'abcdefghijkl' );
+		//
+		// 	s.insert( 6, 'X' ).move( 3, 6, 9 );
+		// 	assert.equal( s.toString(), 'abcXghidefjkl' );
+		// });
+
+		it( 'moves content inserted at end of range', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+
+			s.insertLeft( 6, 'X' ).move( 3, 6, 9 );
+			assert.equal( s.toString(), 'abcghidefXjkl' );
+		});
+
+		it( 'returns this', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			assert.strictEqual( s.move( 3, 6, 9 ), s );
+		});
+	});
+
+	describe( 'overwrite', () => {
+		it( 'should replace characters', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+
+			s.overwrite( 5, 8, 'FGH' );
+			assert.equal( s.toString(), 'abcdeFGHijkl' );
+		});
+
+		it( 'should throw an error if overlapping replacements are attempted', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+
+			s.overwrite( 7, 11, 'xx' );
+
+			assert.throws( () => s.overwrite( 8, 12, 'yy' ), /Cannot split a chunk that has already been edited/ );
+
+			assert.equal( s.toString(), 'abcdefgxxl' );
+
+			s.overwrite( 6, 12, 'yes' );
+			assert.equal( s.toString(), 'abcdefyes' );
+		});
+
+		it( 'should allow contiguous but non-overlapping replacements', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+
+			s.overwrite( 3, 6, 'DEF' );
+			assert.equal( s.toString(), 'abcDEFghijkl' );
+
+			s.overwrite( 6, 9, 'GHI' );
+			assert.equal( s.toString(), 'abcDEFGHIjkl' );
+
+			s.overwrite( 0, 3, 'ABC' );
+			assert.equal( s.toString(), 'ABCDEFGHIjkl' );
+
+			s.overwrite( 9, 12, 'JKL' );
+			assert.equal( s.toString(), 'ABCDEFGHIJKL' );
+		});
+
+		it( 'does not replace zero-length inserts at overwrite start location', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+
+			s.remove( 0, 6 );
+			s.insertLeft( 6, 'DEF' );
+			s.overwrite( 6, 9, 'GHI' );
+			assert.equal( s.toString(), 'DEFGHIjkl' );
+		});
+
+		it( 'replaces zero-length inserts inside overwrite', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+
+			s.insertLeft( 6, 'XXX' );
+			s.overwrite( 3, 9, 'DEFGHI' );
+			assert.equal( s.toString(), 'abcDEFGHIjkl' );
+		});
+
+		it( 'replaces non-zero-length inserts inside overwrite', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+
+			s.overwrite( 3, 4, 'XXX' );
+			s.overwrite( 3, 5, 'DE' );
+			assert.equal( s.toString(), 'abcDEfghijkl' );
+
+			s.overwrite( 7, 8, 'YYY' );
+			s.overwrite( 6, 8, 'GH' );
+			assert.equal( s.toString(), 'abcDEfGHijkl' );
+		});
+
+		it( 'should return this', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			assert.strictEqual( s.overwrite( 3, 4, 'D' ), s );
+		});
+
+		it( 'should disallow overwriting zero-length ranges', () => {
+			const s = new MagicString( 'x' );
+			assert.throws( () => s.overwrite( 0, 0, 'anything' ), /Cannot overwrite a zero-length range – use insertLeft or insertRight instead/ );
+		});
+
+		it( 'should throw when given non-string content', () => {
+			const s = new MagicString( '' );
+			assert.throws( () => s.overwrite( 0, 1, [] ), TypeError );
+		});
+
+		it ( 'replaces interior inserts', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+
+			s.insertLeft( 1, '&' );
+			s.insertRight( 1, '^' );
+			s.insertLeft( 3, '!' );
+			s.insertRight( 3, '?' );
+			s.overwrite( 1, 3, '...' );
+			assert.equal( s.toString(), 'a&...?defghijkl' );
+		});
+	});
+
+	describe( 'prepend', () => {
+		it( 'should prepend content', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+
+			s.prepend( 'xyz' );
+			assert.equal( s.toString(), 'xyzabcdefghijkl' );
+
+			s.prepend( '123' );
+			assert.equal( s.toString(), '123xyzabcdefghijkl' );
+		});
+
+		it( 'should return this', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			assert.strictEqual( s.prepend( 'xyz' ), s );
+		});
+	});
+
+	describe( 'prependLeft', () => {
+		it( 'should return this', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			assert.strictEqual( s.prependLeft( 0, 'a' ), s );
+		});
+	});
+
+	describe( 'prependRight', () => {
+		it( 'should return this', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			assert.strictEqual( s.prependRight( 0, 'a' ), s );
+		});
+	});
+
+	describe( 'remove', () => {
+		it( 'should remove characters from the original string', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+
+			s.remove( 1, 5 );
+			assert.equal( s.toString(), 'afghijkl' );
+
+			s.remove( 9, 12 );
+			assert.equal( s.toString(), 'afghi' );
+		});
+
+		it( 'should remove from the start', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+
+			s.remove( 0, 6 );
+			assert.equal( s.toString(), 'ghijkl' );
+		});
+
+		it( 'should remove from the end', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+
+			s.remove( 6, 12 );
+			assert.equal( s.toString(), 'abcdef' );
+		});
+
+		it( 'should treat zero-length removals as a no-op', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+
+			s.remove( 0, 0 ).remove( 6, 6 ).remove( 9, -3 );
+			assert.equal( s.toString(), 'abcdefghijkl' );
+		});
+
+		it( 'should remove overlapping ranges', () => {
+			const s1 = new MagicString( 'abcdefghijkl' );
+
+			s1.remove( 3, 7 ).remove( 5, 9 );
+			assert.equal( s1.toString(), 'abcjkl' );
+
+			const s2 = new MagicString( 'abcdefghijkl' );
+
+			s2.remove( 3, 7 ).remove( 4, 6 );
+			assert.equal( s2.toString(), 'abchijkl' );
+		});
+
+		it( 'should remove overlapping ranges, redux', () => {
+			const s = new MagicString( 'abccde' );
+
+			s.remove( 2, 3 ); // c
+			s.remove( 1, 3 ); // bc
+			assert.equal( s.toString(), 'acde' );
+		});
+
+		it( 'should remove modified ranges', () => {
+			const s = new MagicString( 'abcdefghi' );
+
+			s.overwrite( 3, 6, 'DEF' );
+			s.remove( 2, 7 ); // cDEFg
+			assert.equal( s.slice( 1, 8 ), 'bh' );
+			assert.equal( s.toString(), 'abhi' );
+		});
+
+		it( 'should not remove content inserted after the end of removed range', () => {
+			const s = new MagicString( 'ab.c;' );
+
+			s.insertRight( 0, '(' );
+			s.insertRight( 4, ')' );
+			s.remove( 2, 4 );
+			assert.equal( s.toString(), '(ab);' );
+		});
+
+		it( 'should remove interior inserts', () => {
+			const s = new MagicString( 'abc;' );
+
+			s.insertLeft( 1, '[' );
+			s.insertRight( 1, '(' );
+			s.insertLeft( 2, ')' );
+			s.insertRight( 2, ']' );
+			s.remove( 1, 2 );
+			assert.equal( s.toString(), 'a[]c;' );
+		});
+
+		it( 'should provide a useful error when illegal removals are attempted', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+
+			s.overwrite( 5, 7, 'XX' );
+
+			assert.throws( () => s.remove( 4, 6 ), /Cannot split a chunk that has already been edited/ );
+		});
+
+		it( 'should return this', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			assert.strictEqual( s.remove( 3, 4 ), s );
+		});
+	});
+
+	describe( 'slice', () => {
+		it( 'should return the generated content between the specified original characters', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+
+			assert.equal( s.slice( 3, 9 ), 'defghi' );
+			s.overwrite( 4, 8, 'XX' );
+			assert.equal( s.slice( 3, 9 ), 'dXXi' );
+			s.overwrite( 2, 10, 'ZZ' );
+			assert.equal( s.slice( 1, 11 ), 'bZZk' );
+			assert.equal( s.slice( 2, 10 ), 'ZZ' );
+
+			assert.throws( () => s.slice( 3, 9 ));
+		});
+
+		it( 'defaults `end` to the original string length', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			assert.equal( s.slice( 3 ), 'defghijkl' );
+		});
+
+		it( 'allows negative numbers as arguments', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			assert.equal( s.slice( -3 ), 'jkl' );
+			assert.equal( s.slice( 0, -3 ), 'abcdefghi' );
+		});
+
+		it( 'includes inserted characters, respecting insertion direction', () => {
+			const s = new MagicString( 'abefij' );
+
+			s.insertRight( 2, 'cd' );
+			s.insertLeft( 4, 'gh' );
+
+			assert.equal( s.slice(), 'abcdefghij' );
+			assert.equal( s.slice( 1, 5 ), 'bcdefghi' );
+			assert.equal( s.slice( 2, 4 ), 'cdefgh' );
+			assert.equal( s.slice( 3, 4 ), 'fgh' );
+			assert.equal( s.slice( 0, 2 ), 'ab' );
+			assert.equal( s.slice( 0, 3 ), 'abcde' );
+			assert.equal( s.slice( 4, 6 ), 'ij' );
+			assert.equal( s.slice( 3, 6 ), 'fghij' );
+		});
+
+		it( 'supports characters moved outward', () => {
+			const s = new MagicString( 'abcdEFghIJklmn' );
+
+			s.move( 4, 6, 2 );
+			s.move( 8, 10, 12 );
+			assert.equal( s.toString(), 'abEFcdghklIJmn' );
+
+			assert.equal( s.slice( 1, -1 ), 'bEFcdghklIJm' );
+			assert.equal( s.slice( 2, -2 ), 'cdghkl' );
+			assert.equal( s.slice( 3, -3 ), 'dghk' );
+			assert.equal( s.slice( 4, -4 ), 'EFcdghklIJ' );
+			assert.equal( s.slice( 5, -5 ), 'FcdghklI' );
+			assert.equal( s.slice( 6, -6 ), 'gh' );
+		});
+
+		it( 'supports characters moved inward', () => {
+			const s = new MagicString( 'abCDefghijKLmn' );
+
+			s.move( 2, 4, 6 );
+			s.move( 10, 12, 8 );
+			assert.equal( s.toString(), 'abefCDghKLijmn' );
+
+			assert.equal( s.slice( 1, -1 ), 'befCDghKLijm' );
+			assert.equal( s.slice( 2, -2 ), 'CDghKL' );
+			assert.equal( s.slice( 3, -3 ), 'DghK' );
+			assert.equal( s.slice( 4, -4 ), 'efCDghKLij' );
+			assert.equal( s.slice( 5, -5 ), 'fCDghKLi' );
+			assert.equal( s.slice( 6, -6 ), 'gh' );
+		});
+
+		it( 'supports characters moved opposing', () => {
+			const s = new MagicString( 'abCDefghIJkl' );
+
+			s.move( 2, 4, 8 );
+			s.move( 8, 10, 4 );
+			assert.equal( s.toString(), 'abIJefghCDkl' );
+
+			assert.equal( s.slice( 1, -1 ), 'bIJefghCDk' );
+			assert.equal( s.slice( 2, -2 ), '' );
+			assert.equal( s.slice( 3, -3 ), '' );
+			assert.equal( s.slice( -3, 3 ), 'JefghC' );
+			assert.equal( s.slice( 4, -4 ), 'efgh' );
+			assert.equal( s.slice( 0, 3 ), 'abIJefghC' );
+			assert.equal( s.slice( 3 ), 'Dkl' );
+			assert.equal( s.slice( 0, -3 ), 'abI' );
+			assert.equal( s.slice( -3 ), 'JefghCDkl' );
+		});
+
+		it( 'errors if replaced characters are used as slice anchors', () => {
+			const s = new MagicString( 'abcdef' );
+			s.overwrite( 2, 4, 'CD' );
+
+			assert.throws( () => s.slice( 2, 3 ), /slice end anchor/ );
+
+			assert.throws( () => s.slice( 3, 4 ), /slice start anchor/ );
+
+			assert.throws( () => s.slice( 3, 5 ), /slice start anchor/ );
+
+			assert.equal( s.slice( 1, 5 ), 'bCDe' );
+		});
+
+		it( 'does not error if slice is after removed characters', () => {
+			const s = new MagicString( 'abcdef' );
+
+			s.remove( 0, 2 );
+
+			assert.equal( s.slice( 2, 4 ), 'cd' );
+		});
+	});
+
+	describe( 'snip', () => {
+		it( 'should return a clone with content outside `start` and `end` removed', () => {
+			const s = new MagicString( 'abcdefghijkl', {
+				filename: 'foo.js'
+			});
+
+			s.overwrite( 6, 9, 'GHI' );
+
+			const snippet = s.snip( 3, 9 );
+			assert.equal( snippet.toString(), 'defGHI' );
+			assert.equal( snippet.filename, 'foo.js' );
+		});
+
+		it( 'should snip from the start', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			const snippet = s.snip( 0, 6 );
+
+			assert.equal( snippet.toString(), 'abcdef' );
+		});
+
+		it( 'should snip from the end', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			const snippet = s.snip( 6, 12 );
+
+			assert.equal( snippet.toString(), 'ghijkl' );
+		});
+
+		it( 'should respect original indices', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+			const snippet = s.snip( 3, 9 );
+
+			snippet.overwrite( 6, 9, 'GHI' );
+			assert.equal( snippet.toString(), 'defGHI' );
+		});
+	});
+
+	describe( 'trim', () => {
+		it( 'should trim original content', () => {
+			assert.equal( new MagicString( '   abcdefghijkl   ' ).trim().toString(), 'abcdefghijkl' );
+			assert.equal( new MagicString( '   abcdefghijkl' ).trim().toString(), 'abcdefghijkl' );
+			assert.equal( new MagicString( 'abcdefghijkl   ' ).trim().toString(), 'abcdefghijkl' );
+		});
+
+		it( 'should trim replaced content', () => {
+			const s = new MagicString( 'abcdefghijkl' );
+
+			s.overwrite( 0, 3, '   ' ).overwrite( 9, 12, '   ' ).trim();
+			assert.equal( s.toString(), 'defghi' );
+		});
+
+		it( 'should trim original content before replaced content', () => {
+			const s = new MagicString( 'abc   def' );
+
+			s.remove( 6, 9 );
+			assert.equal( s.toString(), 'abc   ' );
+
+			s.trim();
+			assert.equal( s.toString(), 'abc' );
+		});
+
+		it( 'should trim original content after replaced content', () => {
+			const s = new MagicString( 'abc   def' );
+
+			s.remove( 0, 3 );
+			assert.equal( s.toString(), '   def' );
+
+			s.trim();
+			assert.equal( s.toString(), 'def' );
+		});
+
+		it( 'should trim original content before and after replaced content', () => {
+			const s = new MagicString( 'abc   def   ghi' );
+
+			s.remove( 0, 3 );
+			s.remove( 12, 15 );
+			assert.equal( s.toString(), '   def   ' );
+
+			s.trim();
+			assert.equal( s.toString(), 'def' );
+		});
+
+		it( 'should trim appended/prepended content', () => {
+			const s = new MagicString( ' abcdefghijkl ' );
+
+			s.prepend( '  ' ).append( '  ' ).trim();
+			assert.equal( s.toString(), 'abcdefghijkl' );
+		});
+
+		it( 'should trim empty string', () => {
+			const s = new MagicString( '   ' );
+
+			assert.equal( s.trim().toString(), '' );
+		});
+
+		it( 'should return this', () => {
+			const s = new MagicString( '  abcdefghijkl  ' );
+			assert.strictEqual( s.trim(), s );
+		});
+	});
+
+	describe( 'trimLines', () => {
+		it( 'should trim original content', () => {
+			const s = new MagicString( '\n\n   abcdefghijkl   \n\n' );
+
+			s.trimLines();
+			assert.equal( s.toString(), '   abcdefghijkl   ' );
+		});
+	});
+});
diff --git a/test/index.js b/test/index.js
deleted file mode 100644
index 5305692..0000000
--- a/test/index.js
+++ /dev/null
@@ -1,1684 +0,0 @@
-/*global require, describe, it, console */
-var assert = require( 'assert' );
-var SourceMapConsumer = require( 'source-map' ).SourceMapConsumer;
-var MagicString = require( '../' );
-
-require( 'source-map-support' ).install();
-require( 'console-group' ).install();
-
-describe( 'MagicString', function () {
-	describe( 'options', function () {
-		it( 'stores source file information', function () {
-			var s = new MagicString( 'abc', {
-				filename: 'foo.js'
-			});
-
-			assert.equal( s.filename, 'foo.js' );
-		});
-	});
-
-	describe( 'append', function () {
-		it( 'should append content', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-
-			s.append( 'xyz' );
-			assert.equal( s.toString(), 'abcdefghijklxyz' );
-
-			s.append( 'xyz' );
-			assert.equal( s.toString(), 'abcdefghijklxyzxyz' );
-		});
-
-		it( 'should return this', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			assert.strictEqual( s.append( 'xyz' ), s );
-		});
-
-		it( 'should throw when given non-string content', function () {
-			var s = new MagicString( '' );
-			assert.throws(
-				function () { s.append( [] ); },
-				TypeError
-			);
-		});
-	});
-
-	describe( 'clone', function () {
-		it( 'should clone a magic string', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-
-			s.overwrite( 3, 9, 'XYZ' );
-			var c = s.clone();
-
-			assert.notEqual( s, c );
-			assert.equal( c.toString(), 'abcXYZjkl' );
-		});
-
-		it( 'should clone filename info', function () {
-			var s = new MagicString( 'abcdefghijkl', { filename: 'foo.js' });
-			var c = s.clone();
-
-			assert.equal( c.filename, 'foo.js' );
-		});
-
-		it( 'should clone indentExclusionRanges', function () {
-			var array = [ 3, 6 ];
-			var source = new MagicString( 'abcdefghijkl', {
-				filename: 'foo.js',
-				indentExclusionRanges: array
-			});
-
-			var clone = source.clone();
-
-			assert.notStrictEqual( source.indentExclusionRanges, clone.indentExclusionRanges );
-			assert.deepEqual( source.indentExclusionRanges, clone.indentExclusionRanges );
-		});
-
-		it( 'should clone sourcemapLocations', function () {
-			var source = new MagicString( 'abcdefghijkl', {
-				filename: 'foo.js'
-			});
-
-			source.addSourcemapLocation( 3 );
-
-			var clone = source.clone();
-
-			assert.notStrictEqual( source.sourcemapLocations, clone.sourcemapLocations );
-			assert.deepEqual( source.sourcemapLocations, clone.sourcemapLocations );
-		});
-	});
-
-	describe( 'generateMap', function () {
-		it( 'should generate a sourcemap', function () {
-			var s = new MagicString( 'abcdefghijkl' ).remove( 3, 9 );
-
-			var map = s.generateMap({
-				file: 'output.md',
-				source: 'input.md',
-				includeContent: true,
-				hires: true
-			});
-
-			assert.equal( map.version, 3 );
-			assert.equal( map.file, 'output.md' );
-			assert.deepEqual( map.sources, [ 'input.md' ]);
-			assert.deepEqual( map.sourcesContent, [ 'abcdefghijkl' ]);
-			assert.equal( map.mappings, 'AAAA,CAAC,CAAC,CAAC,AAAM,CAAC,CAAC' );
-
-			assert.equal( map.toString(), '{"version":3,"file":"output.md","sources":["input.md"],"sourcesContent":["abcdefghijkl"],"names":[],"mappings":"AAAA,CAAC,CAAC,CAAC,AAAM,CAAC,CAAC"}' );
-			assert.equal( map.toUrl(), 'data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3V0cHV0Lm1kIiwic291cmNlcyI6WyJpbnB1dC5tZCJdLCJzb3VyY2VzQ29udGVudCI6WyJhYmNkZWZnaGlqa2wiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsQ0FBQyxDQUFDLENBQUMsQUFBTSxDQUFDLENBQUMifQ==' );
-
-			var smc = new SourceMapConsumer( map );
-			var loc;
-
-			loc = smc.originalPositionFor({ line: 1, column: 0 });
-			assert.equal( loc.line, 1 );
-			assert.equal( loc.column, 0 );
-
-			loc = smc.originalPositionFor({ line: 1, column: 1 });
-			assert.equal( loc.line, 1 );
-			assert.equal( loc.column, 1 );
-
-			loc = smc.originalPositionFor({ line: 1, column: 4 });
-			assert.equal( loc.line, 1 );
-			assert.equal( loc.column, 10 );
-		});
-
-		it( 'should generate a correct sourcemap for indented content', function () {
-			var s, map, smc, originLoc;
-
-			s = new MagicString( 'var answer = 42;\nconsole.log("the answer is %s", answer);' );
-
-			s.prepend( "'use strict';\n\n" );
-			s.indent( '\t' ).prepend( '(function () {\n' ).append( '\n}).call(global);' );
-
-			map = s.generateMap({
-				source: 'input.md',
-				includeContent: true,
-				hires: true
-			});
-
-			smc = new SourceMapConsumer( map );
-
-			originLoc = smc.originalPositionFor({ line: 5, column: 1 });
-			assert.equal( originLoc.line, 2 );
-			assert.equal( originLoc.column, 0 );
-		});
-
-		it( 'should generate a sourcemap using specified locations', function () {
-			var s, map, smc, loc;
-
-			s = new MagicString( 'abcdefghijkl' );
-
-			s.addSourcemapLocation( 0 );
-			s.addSourcemapLocation( 3 );
-			s.addSourcemapLocation( 10 );
-
-			s.remove( 6, 9 );
-			map = s.generateMap({
-				file: 'output.md',
-				source: 'input.md',
-				includeContent: true
-			});
-
-			assert.equal( map.version, 3 );
-			assert.equal( map.file, 'output.md' );
-			assert.deepEqual( map.sources, [ 'input.md' ]);
-			assert.deepEqual( map.sourcesContent, [ 'abcdefghijkl' ]);
-
-			assert.equal( map.toString(), '{"version":3,"file":"output.md","sources":["input.md"],"sourcesContent":["abcdefghijkl"],"names":[],"mappings":"AAAA,GAAG,GAAG,AAAG,CAAC"}' );
-			assert.equal( map.toUrl(), 'data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3V0cHV0Lm1kIiwic291cmNlcyI6WyJpbnB1dC5tZCJdLCJzb3VyY2VzQ29udGVudCI6WyJhYmNkZWZnaGlqa2wiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsR0FBRyxHQUFHLEFBQUcsQ0FBQyJ9' );
-
-			smc = new SourceMapConsumer( map );
-
-			loc = smc.originalPositionFor({ line: 1, column: 0 });
-			assert.equal( loc.line, 1 );
-			assert.equal( loc.column, 0 );
-
-			loc = smc.originalPositionFor({ line: 1, column: 3 });
-			assert.equal( loc.line, 1 );
-			assert.equal( loc.column, 3 );
-
-			loc = smc.originalPositionFor({ line: 1, column: 7 });
-			assert.equal( loc.line, 1 );
-			assert.equal( loc.column, 10 );
-		});
-
-		it( 'should correctly map inserted content', function () {
-			var s = new MagicString( 'function Foo () {}' );
-
-			s.overwrite( 9, 12, 'Bar' );
-
-			map = s.generateMap({
-				file: 'output.js',
-				source: 'input.js',
-				includeContent: true
-			});
-
-			smc = new SourceMapConsumer( map );
-
-			loc = smc.originalPositionFor({ line: 1, column: 9 });
-			assert.equal( loc.line, 1 );
-			assert.equal( loc.column, 9 );
-		});
-
-		it( 'should yield consistent results between insertLeft and insertRight', function () {
-			var s1 = new MagicString( 'abcdefghijkl' );
-			s1.insertLeft( 6, 'X' );
-
-			var s2 = new MagicString( 'abcdefghijkl' );
-			s2.insertRight( 6, 'X' );
-
-			var m1 = s1.generateMap({ file: 'output', source: 'input', includeContent: true });
-			var m2 = s2.generateMap({ file: 'output', source: 'input', includeContent: true });
-
-			assert.deepEqual( m1, m2 );
-		});
-
-		it( 'should recover original names', function () {
-			var s = new MagicString( 'function Foo () {}' );
-
-			s.overwrite( 9, 12, 'Bar', true );
-
-			map = s.generateMap({
-				file: 'output.js',
-				source: 'input.js',
-				includeContent: true
-			});
-
-			smc = new SourceMapConsumer( map );
-
-			loc = smc.originalPositionFor({ line: 1, column: 9 });
-			assert.equal( loc.name, 'Foo' );
-		});
-
-		it( 'should generate one segment per replacement', function () {
-			var s = new MagicString( 'var answer = 42' );
-			s.overwrite( 4, 10, 'number', true );
-
-			map = s.generateMap({
-				file: 'output.js',
-				source: 'input.js',
-				includeContent: true
-			});
-
-			smc = new SourceMapConsumer( map );
-
-			var numMappings = 0;
-			smc.eachMapping( function ( mapping ) {
-				numMappings += 1;
-			});
-
-			assert.equal( numMappings, 3 ); // one at 0, one at the edit, one afterwards
-		});
-
-		it( 'should generate a sourcemap that correctly locates moved content', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			s.move( 3, 6, 9 );
-
-			var result = s.toString();
-			var map = s.generateMap({
-				file: 'output.js',
-				source: 'input.js',
-				includeContent: true,
-				hires: true
-			});
-
-			var smc = new SourceMapConsumer( map );
-
-			'abcdefghijkl'.split( '' ).forEach( function ( letter, i ) {
-				var column = result.indexOf( letter );
-				var loc = smc.originalPositionFor({ line: 1, column: column });
-
-				assert.equal( loc.line, 1 );
-				assert.equal( loc.column, i );
-			});
-		});
-
-		it( 'generates a map with trimmed content (#53)', function () {
-			var s = new MagicString( 'abcdefghijkl ' ).trim();
-			var map = s.generateMap({
-				file: 'output',
-				source: 'input',
-				includeContent: true,
-				hires: true
-			});
-
-			var smc = new SourceMapConsumer( map );
-			var loc = smc.originalPositionFor({ line: 1, column: 11 });
-
-			assert.equal( loc.column, 11 );
-
-			s = new MagicString( ' abcdefghijkl' ).trim();
-			map = s.generateMap({
-				file: 'output',
-				source: 'input',
-				includeContent: true,
-				hires: true
-			});
-
-			smc = new SourceMapConsumer( map );
-			loc = smc.originalPositionFor({ line: 1, column: 1 });
-
-			assert.equal( loc.column, 2 );
-		});
-
-		it( 'skips empty segments at the start', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			s.remove( 0, 3 ).remove( 3, 6 );
-
-			var map = s.generateMap();
-			var smc = new SourceMapConsumer( map );
-			var loc = smc.originalPositionFor({ line: 1, column: 6 });
-
-			assert.equal( loc.column, 6 );
-		});
-
-		it( 'skips indentation at the start', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			s.indent( '    ' );
-
-			var map = s.generateMap();
-			assert.equal( map.mappings, 'IAAA' );
-		});
-	});
-
-	describe( 'getIndentString', function () {
-		it( 'should guess the indent string', function () {
-			var s = new MagicString( 'abc\n  def\nghi' );
-			assert.equal( s.getIndentString(), '  ' );
-		});
-
-		it( 'should return a tab if no lines are indented', function () {
-			var s = new MagicString( 'abc\ndef\nghi' );
-			assert.equal( s.getIndentString(), '\t' );
-		});
-	});
-
-	describe( 'indent', function () {
-		it( 'should indent content with a single tab character by default', function () {
-			var s = new MagicString( 'abc\ndef\nghi\njkl' );
-
-			s.indent();
-			assert.equal( s.toString(), '\tabc\n\tdef\n\tghi\n\tjkl' );
-
-			s.indent();
-			assert.equal( s.toString(), '\t\tabc\n\t\tdef\n\t\tghi\n\t\tjkl' );
-		});
-
-		it( 'should indent content, using existing indentation as a guide', function () {
-			var s = new MagicString( 'abc\n  def\n    ghi\n  jkl' );
-
-			s.indent();
-			assert.equal( s.toString(), '  abc\n    def\n      ghi\n    jkl' );
-
-			s.indent();
-			assert.equal( s.toString(), '    abc\n      def\n        ghi\n      jkl' );
-		});
-
-		it( 'should disregard single-space indentation when auto-indenting', function () {
-			var s = new MagicString( 'abc\n/**\n *comment\n */' );
-
-			s.indent();
-			assert.equal( s.toString(), '\tabc\n\t/**\n\t *comment\n\t */' );
-		});
-
-		it( 'should indent content using the supplied indent string', function () {
-			var s = new MagicString( 'abc\ndef\nghi\njkl' );
-
-			s.indent( '  ');
-			assert.equal( s.toString(), '  abc\n  def\n  ghi\n  jkl' );
-
-			s.indent( '>>' );
-			assert.equal( s.toString(), '>>  abc\n>>  def\n>>  ghi\n>>  jkl' );
-		});
-
-		it( 'should indent content using the empty string if specified (i.e. noop)', function () {
-			var s = new MagicString( 'abc\ndef\nghi\njkl' );
-
-			s.indent( '');
-			assert.equal( s.toString(), 'abc\ndef\nghi\njkl' );
-		});
-
-		it( 'should prevent excluded characters from being indented', function () {
-			var s = new MagicString( 'abc\ndef\nghi\njkl' );
-
-			s.indent( '  ', { exclude: [ 7, 15 ] });
-			assert.equal( s.toString(), '  abc\n  def\nghi\njkl' );
-
-			s.indent( '>>', { exclude: [ 7, 15 ] });
-			assert.equal( s.toString(), '>>  abc\n>>  def\nghi\njkl' );
-		});
-
-		it( 'should not add characters to empty lines', function () {
-			var s = new MagicString( '\n\nabc\ndef\n\nghi\njkl' );
-
-			s.indent();
-			assert.equal( s.toString(), '\n\n\tabc\n\tdef\n\n\tghi\n\tjkl' );
-
-			s.indent();
-			assert.equal( s.toString(), '\n\n\t\tabc\n\t\tdef\n\n\t\tghi\n\t\tjkl' );
-		});
-
-		it( 'should not add characters to empty lines, even on Windows', function () {
-			var s = new MagicString( '\r\n\r\nabc\r\ndef\r\n\r\nghi\r\njkl' );
-
-			s.indent();
-			assert.equal( s.toString(), '\r\n\r\n\tabc\r\n\tdef\r\n\r\n\tghi\r\n\tjkl' );
-
-			s.indent();
-			assert.equal( s.toString(), '\r\n\r\n\t\tabc\r\n\t\tdef\r\n\r\n\t\tghi\r\n\t\tjkl' );
-		});
-
-		it( 'should indent content with removals', function () {
-			var s = new MagicString( '/* remove this line */\nvar foo = 1;' );
-
-			s.remove( 0, 23 );
-			s.indent();
-
-			assert.equal( s.toString(), '\tvar foo = 1;' );
-		});
-
-		it( 'should not indent patches in the middle of a line', function () {
-			var s = new MagicString( 'class Foo extends Bar {}' );
-
-			s.overwrite( 18, 21, 'Baz' );
-			assert.equal( s.toString(), 'class Foo extends Baz {}' );
-
-			s.indent();
-			assert.equal( s.toString(), '\tclass Foo extends Baz {}' );
-		});
-
-		it( 'should return this', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			assert.strictEqual( s.indent(), s );
-		});
-
-		it( 'should return this on noop', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			assert.strictEqual( s.indent( '' ), s );
-		});
-	});
-
-	describe( 'insert', function () {
-		it( 'is deprecated', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			assert.throws( function () { s.insert( 6, 'X' ); }, /deprecated/ );
-		});
-
-		// TODO move this into insertRight and insertLeft tests
-
-		// it( 'should insert characters in the correct location', function () {
-		// 	var s = new MagicString( 'abcdefghijkl' );
-		//
-		// 	s.insert( 0, '>>>' );
-		// 	s.insert( 6, '***' );
-		// 	s.insert( 12, '<<<' );
-		//
-		// 	assert.equal( s.toString(), '>>>abcdef***ghijkl<<<' );
-		// });
-		//
-		// it( 'should return this', function () {
-		// 	var s = new MagicString( 'abcdefghijkl' );
-		// 	assert.strictEqual( s.insert( 0, 'a' ), s );
-		// });
-		//
-		// it( 'should insert repeatedly at the same position correctly', function () {
-		// 	var s = new MagicString( 'ab' );
-		// 	assert.equal( s.insert(1, '1').toString(), 'a1b' );
-		// 	assert.equal( s.insert(1, '2').toString(), 'a12b' );
-		// });
-		//
-		// it( 'should insert repeatedly at the beginning correctly', function () {
-		// 	var s = new MagicString( 'ab' );
-		// 	assert.equal( s.insert(0, '1').toString(), '1ab' );
-		// 	assert.equal( s.insert(0, '2').toString(), '12ab' );
-		// });
-		//
-		// it( 'should throw when given non-string content', function () {
-		// 	var s = new MagicString( '' );
-		// 	assert.throws(
-		// 		function () { s.insert( 0, [] ); },
-		// 		TypeError
-		// 	);
-		// });
-		//
-		// it( 'should allow inserting after removed range', function () {
-		// 	var s = new MagicString( 'abcd' );
-		// 	s.remove( 1, 2 );
-		// 	s.insert( 2, 'z' );
-		// 	assert.equal( s.toString(), 'azcd' );
-		// });
-	});
-
-	describe( 'insertLeft', function () {
-		it( 'inserts repeatedly in correct order', function () {
-			var s = new MagicString( 'ab' );
-			assert.equal( s.insertLeft(1, '1').toString(), 'a1b' );
-			assert.equal( s.insertLeft(1, '2').toString(), 'a12b' );
-		});
-
-		it( 'should return this', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			assert.strictEqual( s.insertLeft( 0, 'a' ), s );
-		});
-	});
-
-	describe( 'insertRight', function () {
-		it( 'inserts repeatedly in correct order', function () {
-			var s = new MagicString( 'ab' );
-			assert.equal( s.insertRight(1, '1').toString(), 'a1b' );
-			assert.equal( s.insertRight(1, '2').toString(), 'a21b' );
-		});
-
-		it( 'should return this', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			assert.strictEqual( s.insertLeft( 0, 'a' ), s );
-		});
-	});
-
-	describe( 'move', function () {
-		it( 'moves content from the start', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			s.move( 0, 3, 6 );
-
-			assert.equal( s.toString(), 'defabcghijkl' );
-		});
-
-		it( 'moves content to the start', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			s.move( 3, 6, 0 );
-
-			assert.equal( s.toString(), 'defabcghijkl' );
-		});
-
-		it( 'moves content from the end', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			s.move( 9, 12, 6 );
-
-			assert.equal( s.toString(), 'abcdefjklghi' );
-		});
-
-		it( 'moves content to the end', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			s.move( 6, 9, 12 );
-
-			assert.equal( s.toString(), 'abcdefjklghi' );
-		});
-
-		it( 'ignores redundant move', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			s.insertRight( 9, 'X' );
-			s.move( 9, 12, 6 );
-			s.insertLeft( 12, 'Y' );
-			s.move( 6, 9, 12 ); // this is redundant – [6,9] is already after [9,12]
-
-			assert.equal( s.toString(), 'abcdefXjklYghi' );
-		});
-
-		it( 'moves content to the middle', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			s.move( 3, 6, 9 );
-
-			assert.equal( s.toString(), 'abcghidefjkl' );
-		});
-
-		it( 'handles multiple moves of the same snippet', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-
-			s.move( 0, 3, 6 );
-			assert.equal( s.toString(), 'defabcghijkl' );
-
-			s.move( 0, 3, 9 );
-			assert.equal( s.toString(), 'defghiabcjkl' );
-		});
-
-		it( 'handles moves of adjacent snippets', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-
-			s.move( 0, 2, 6 );
-			assert.equal( s.toString(), 'cdefabghijkl' );
-
-			s.move( 2, 4, 6 );
-			assert.equal( s.toString(), 'efabcdghijkl' );
-		});
-
-		it( 'handles moves to same index', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			s.move( 0, 2, 6 ).move( 3, 5, 6 );
-
-			assert.equal( s.toString(), 'cfabdeghijkl' );
-		});
-
-		it( 'refuses to move a selection to inside itself', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-
-			assert.throws( function () {
-				s.move( 3, 6, 3 );
-			}, /Cannot move a selection inside itself/ );
-
-			assert.throws( function () {
-				s.move( 3, 6, 4 );
-			}, /Cannot move a selection inside itself/ );
-
-			assert.throws( function () {
-				s.move( 3, 6, 6 );
-			}, /Cannot move a selection inside itself/ );
-		});
-
-		it( 'allows edits of moved content', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-
-			s.move( 3, 6, 9 );
-			s.overwrite( 3, 6, 'DEF' );
-
-			assert.equal( s.toString(), 'abcghiDEFjkl' );
-
-			s = new MagicString( 'abcdefghijkl' );
-
-			s.move( 3, 6, 9 );
-			s.overwrite( 4, 5, 'E' );
-
-			assert.equal( s.toString(), 'abcghidEfjkl' );
-		});
-
-		// it( 'move follows inserts', function () {
-		// 	var s = new MagicString( 'abcdefghijkl' );
-		//
-		// 	s.insertLeft( 3, 'X' ).move( 6, 9, 3 );
-		// 	assert.equal( s.toString(), 'abcXghidefjkl' );
-		// });
-		//
-		// it( 'inserts follow move', function () {
-		// 	var s = new MagicString( 'abcdefghijkl' );
-		//
-		// 	s.insert( 3, 'X' ).move( 6, 9, 3 ).insert( 3, 'Y' );
-		// 	assert.equal( s.toString(), 'abcXghiYdefjkl' );
-		// });
-		//
-		// it( 'discards inserts at end of move by default', function () {
-		// 	var s = new MagicString( 'abcdefghijkl' );
-		//
-		// 	s.insert( 6, 'X' ).move( 3, 6, 9 );
-		// 	assert.equal( s.toString(), 'abcXghidefjkl' );
-		// });
-
-		it( 'moves content inserted at end of range', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-
-			s.insertLeft( 6, 'X' ).move( 3, 6, 9 );
-			assert.equal( s.toString(), 'abcghidefXjkl' );
-		});
-
-		it( 'returns this', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			assert.strictEqual( s.move( 3, 6, 9 ), s );
-		});
-	});
-
-	describe( 'overwrite', function () {
-		it( 'should replace characters', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-
-			s.overwrite( 5, 8, 'FGH' );
-			assert.equal( s.toString(), 'abcdeFGHijkl' );
-		});
-
-		it( 'should throw an error if overlapping replacements are attempted', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-
-			s.overwrite( 7, 11, 'xx' );
-
-			assert.throws( function () {
-				s.overwrite( 8, 12, 'yy' );
-			}, /Cannot split a chunk that has already been edited/ );
-
-			assert.equal( s.toString(), 'abcdefgxxl' );
-
-			s.overwrite( 6, 12, 'yes' );
-			assert.equal( s.toString(), 'abcdefyes' );
-		});
-
-		it( 'should allow contiguous but non-overlapping replacements', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-
-			s.overwrite( 3, 6, 'DEF' );
-			assert.equal( s.toString(), 'abcDEFghijkl' );
-
-			s.overwrite( 6, 9, 'GHI' );
-			assert.equal( s.toString(), 'abcDEFGHIjkl' );
-
-			s.overwrite( 0, 3, 'ABC' );
-			assert.equal( s.toString(), 'ABCDEFGHIjkl' );
-
-			s.overwrite( 9, 12, 'JKL' );
-			assert.equal( s.toString(), 'ABCDEFGHIJKL' );
-		});
-
-		it( 'does not replace zero-length inserts at overwrite start location', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-
-			s.remove( 0, 6 );
-			s.insertLeft( 6, 'DEF' );
-			s.overwrite( 6, 9, 'GHI' );
-			assert.equal( s.toString(), 'DEFGHIjkl' );
-		});
-
-		it( 'replaces zero-length inserts inside overwrite', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-
-			s.insertLeft( 6, 'XXX' );
-			s.overwrite( 3, 9, 'DEFGHI' );
-			assert.equal( s.toString(), 'abcDEFGHIjkl' );
-		});
-
-		it( 'replaces non-zero-length inserts inside overwrite', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-
-			s.overwrite( 3, 4, 'XXX' );
-			s.overwrite( 3, 5, 'DE' );
-			assert.equal( s.toString(), 'abcDEfghijkl' );
-
-			s.overwrite( 7, 8, 'YYY' );
-			s.overwrite( 6, 8, 'GH' );
-			assert.equal( s.toString(), 'abcDEfGHijkl' );
-		});
-
-		it( 'should return this', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			assert.strictEqual( s.overwrite( 3, 4, 'D' ), s );
-		});
-
-		it( 'should disallow overwriting zero-length ranges', function () {
-			var s = new MagicString( 'x' );
-			assert.throws( function () {
-				s.overwrite( 0, 0, 'anything' );
-			}, /Cannot overwrite a zero-length range – use insertLeft or insertRight instead/ );
-		});
-
-		it( 'should throw when given non-string content', function () {
-			var s = new MagicString( '' );
-			assert.throws(
-				function () { s.overwrite( 0, 1, [] ); },
-				TypeError
-			);
-		});
-
-		it ( 'replaces interior inserts', function() {
-			var s = new MagicString( 'abcdefghijkl' );
-
-			s.insertLeft( 1, '&' );
-			s.insertRight( 1, '^' );
-			s.insertLeft( 3, '!' );
-			s.insertRight( 3, '?' );
-			s.overwrite( 1, 3, '...' );
-			assert.equal( s.toString(), 'a&...?defghijkl' );
-		})
-	});
-
-	describe( 'prepend', function () {
-		it( 'should prepend content', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-
-			s.prepend( 'xyz' );
-			assert.equal( s.toString(), 'xyzabcdefghijkl' );
-
-			s.prepend( '123' );
-			assert.equal( s.toString(), '123xyzabcdefghijkl' );
-		});
-
-		it( 'should return this', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			assert.strictEqual( s.prepend( 'xyz' ), s );
-		});
-	});
-
-	describe( 'remove', function () {
-		it( 'should remove characters from the original string', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-
-			s.remove( 1, 5 );
-			assert.equal( s.toString(), 'afghijkl' );
-
-			s.remove( 9, 12 );
-			assert.equal( s.toString(), 'afghi' );
-		});
-
-		it( 'should remove from the start', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-
-			s.remove( 0, 6 );
-			assert.equal( s.toString(), 'ghijkl' );
-		});
-
-		it( 'should remove from the end', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-
-			s.remove( 6, 12 );
-			assert.equal( s.toString(), 'abcdef' );
-		});
-
-		it( 'should treat zero-length removals as a no-op', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-
-			s.remove( 0, 0 ).remove( 6, 6 ).remove( 9, -3 );
-			assert.equal( s.toString(), 'abcdefghijkl' );
-		});
-
-		it( 'should remove overlapping ranges', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-
-			s.remove( 3, 7 ).remove( 5, 9 );
-			assert.equal( s.toString(), 'abcjkl' );
-
-			s = new MagicString( 'abcdefghijkl' );
-
-			s.remove( 3, 7 ).remove( 4, 6 );
-			assert.equal( s.toString(), 'abchijkl' );
-		});
-
-		it( 'should remove overlapping ranges, redux', function () {
-			var s = new MagicString( 'abccde' );
-
-			s.remove( 2, 3 ); // c
-			s.remove( 1, 3 ); // bc
-			assert.equal( s.toString(), 'acde' );
-		});
-
-		it( 'should remove modified ranges', function () {
-			var s = new MagicString( 'abcdefghi' );
-
-			s.overwrite( 3, 6, 'DEF' );
-			s.remove( 2, 7 ); // cDEFg
-			assert.equal( s.slice( 1, 8 ), 'bh' );
-			assert.equal( s.toString(), 'abhi' );
-		});
-
-		it( 'should not remove content inserted after the end of removed range', function () {
-			var s = new MagicString( 'ab.c;' );
-
-			s.insertRight( 0, '(' );
-			s.insertRight( 4, ')' );
-			s.remove( 2, 4 );
-			assert.equal( s.toString(), '(ab);' );
-		});
-
-		it( 'should remove interior inserts', function () {
-			var s = new MagicString( 'abc;' );
-
-			s.insertLeft( 1, '[' );
-			s.insertRight( 1, '(' );
-			s.insertLeft( 2, ')' );
-			s.insertRight( 2, ']' );
-			s.remove( 1, 2 );
-			assert.equal( s.toString(), 'a[]c;' );
-		});
-
-		it( 'should provide a useful error when illegal removals are attempted', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-
-			s.overwrite( 5, 7, 'XX' );
-
-			assert.throws( function () {
-				s.remove( 4, 6 );
-			}, /Cannot split a chunk that has already been edited/ );
-		});
-
-		it( 'should return this', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			assert.strictEqual( s.remove( 3, 4 ), s );
-		});
-	});
-
-	describe( 'slice', function () {
-		it( 'should return the generated content between the specified original characters', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-
-			assert.equal( s.slice( 3, 9 ), 'defghi' );
-			s.overwrite( 4, 8, 'XX' );
-			assert.equal( s.slice( 3, 9 ), 'dXXi' );
-			s.overwrite( 2, 10, 'ZZ' );
-			assert.equal( s.slice( 1, 11 ), 'bZZk' );
-			assert.equal( s.slice( 2, 10 ), 'ZZ' );
-
-			assert.throws( function () {
-				s.slice( 3, 9 );
-			});
-		});
-
-		it( 'defaults `end` to the original string length', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			assert.equal( s.slice( 3 ), 'defghijkl' );
-		});
-
-		it( 'allows negative numbers as arguments', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			assert.equal( s.slice( -3 ), 'jkl' );
-			assert.equal( s.slice( 0, -3 ), 'abcdefghi' );
-		});
-
-		it( 'includes inserted characters, respecting insertion direction', function () {
-			var s = new MagicString( 'abefij' );
-
-			s.insertRight( 2, 'cd' );
-			s.insertLeft( 4, 'gh' );
-
-			assert.equal( s.slice(), 'abcdefghij' );
-			assert.equal( s.slice( 1, 5 ), 'bcdefghi' );
-			assert.equal( s.slice( 2, 4 ), 'cdefgh' );
-			assert.equal( s.slice( 3, 4 ), 'fgh' );
-			assert.equal( s.slice( 0, 2 ), 'ab' );
-			assert.equal( s.slice( 0, 3 ), 'abcde' );
-			assert.equal( s.slice( 4, 6 ), 'ij' );
-			assert.equal( s.slice( 3, 6 ), 'fghij' );
-		});
-
-		it( 'supports characters moved outward', function () {
-			var s = new MagicString( 'abcdEFghIJklmn' );
-
-			s.move( 4, 6, 2 );
-			s.move( 8, 10, 12 );
-			assert.equal( s.toString(), 'abEFcdghklIJmn' );
-
-			assert.equal( s.slice( 1, -1 ), 'bEFcdghklIJm' );
-			assert.equal( s.slice( 2, -2 ), 'cdghkl' );
-			assert.equal( s.slice( 3, -3 ), 'dghk' );
-			assert.equal( s.slice( 4, -4 ), 'EFcdghklIJ' );
-			assert.equal( s.slice( 5, -5 ), 'FcdghklI' );
-			assert.equal( s.slice( 6, -6 ), 'gh' );
-		});
-
-		it( 'supports characters moved inward', function () {
-			var s = new MagicString( 'abCDefghijKLmn' );
-
-			s.move( 2, 4, 6 );
-			s.move( 10, 12, 8 );
-			assert.equal( s.toString(), 'abefCDghKLijmn' );
-
-			assert.equal( s.slice( 1, -1 ), 'befCDghKLijm' );
-			assert.equal( s.slice( 2, -2 ), 'CDghKL' );
-			assert.equal( s.slice( 3, -3 ), 'DghK' );
-			assert.equal( s.slice( 4, -4 ), 'efCDghKLij' );
-			assert.equal( s.slice( 5, -5 ), 'fCDghKLi' );
-			assert.equal( s.slice( 6, -6 ), 'gh' );
-		});
-
-		it( 'supports characters moved opposing', function () {
-			var s = new MagicString( 'abCDefghIJkl' );
-
-			s.move( 2, 4, 8 );
-			s.move( 8, 10, 4 );
-			assert.equal( s.toString(), 'abIJefghCDkl' );
-
-			assert.equal( s.slice( 1, -1 ), 'bIJefghCDk' );
-			assert.equal( s.slice( 2, -2 ), '' );
-			assert.equal( s.slice( 3, -3 ), '' );
-			assert.equal( s.slice( -3, 3 ), 'JefghC' );
-			assert.equal( s.slice( 4, -4 ), 'efgh' );
-			assert.equal( s.slice( 0, 3 ), 'abIJefghC' );
-			assert.equal( s.slice( 3 ), 'Dkl' );
-			assert.equal( s.slice( 0, -3 ), 'abI' );
-			assert.equal( s.slice( -3 ), 'JefghCDkl' );
-		});
-
-		it( 'errors if replaced characters are used as slice anchors', function () {
-			var s = new MagicString( 'abcdef' );
-			s.overwrite( 2, 4, 'CD' );
-
-			assert.throws( function () {
-				s.slice( 2, 3 );
-			}, /slice end anchor/ );
-
-			assert.throws( function () {
-				s.slice( 3, 4 );
-			}, /slice start anchor/ );
-
-			assert.throws( function () {
-				s.slice( 3, 5 );
-			}, /slice start anchor/ );
-
-			assert.equal( s.slice( 1, 5 ), 'bCDe' );
-		});
-
-		it( 'does not error if slice is after removed characters', function () {
-			var s = new MagicString( 'abcdef' );
-
-			s.remove( 0, 2 );
-
-			assert.equal( s.slice( 2, 4 ), 'cd' );
-		});
-	});
-
-	describe( 'snip', function () {
-		it( 'should return a clone with content outside `start` and `end` removed', function () {
-			var s = new MagicString( 'abcdefghijkl', {
-				filename: 'foo.js'
-			});
-
-			s.overwrite( 6, 9, 'GHI' );
-
-			var snippet = s.snip( 3, 9 );
-			assert.equal( snippet.toString(), 'defGHI' );
-			assert.equal( snippet.filename, 'foo.js' );
-		});
-
-		it( 'should snip from the start', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			var snippet = s.snip( 0, 6 );
-
-			assert.equal( snippet.toString(), 'abcdef' );
-		});
-
-		it( 'should snip from the end', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			var snippet = s.snip( 6, 12 );
-
-			assert.equal( snippet.toString(), 'ghijkl' );
-		});
-
-		it( 'should respect original indices', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-			var snippet = s.snip( 3, 9 );
-
-			snippet.overwrite( 6, 9, 'GHI' );
-			assert.equal( snippet.toString(), 'defGHI' );
-		});
-	});
-
-	describe( 'trim', function () {
-		it( 'should trim original content', function () {
-			assert.equal( new MagicString( '   abcdefghijkl   ' ).trim().toString(), 'abcdefghijkl' );
-			assert.equal( new MagicString( '   abcdefghijkl' ).trim().toString(), 'abcdefghijkl' );
-			assert.equal( new MagicString( 'abcdefghijkl   ' ).trim().toString(), 'abcdefghijkl' );
-		});
-
-		it( 'should trim replaced content', function () {
-			var s = new MagicString( 'abcdefghijkl' );
-
-			s.overwrite( 0, 3, '   ' ).overwrite( 9, 12, '   ' ).trim();
-			assert.equal( s.toString(), 'defghi' );
-		});
-
-		it( 'should trim original content before replaced content', function () {
-			var s = new MagicString( 'abc   def' );
-
-			s.remove( 6, 9 );
-			assert.equal( s.toString(), 'abc   ' );
-
-			s.trim();
-			assert.equal( s.toString(), 'abc' );
-		});
-
-		it( 'should trim original content after replaced content', function () {
-			var s = new MagicString( 'abc   def' );
-
-			s.remove( 0, 3 );
-			assert.equal( s.toString(), '   def' );
-
-			s.trim();
-			assert.equal( s.toString(), 'def' );
-		});
-
-		it( 'should trim original content before and after replaced content', function () {
-			var s = new MagicString( 'abc   def   ghi' );
-
-			s.remove( 0, 3 );
-			s.remove( 12, 15 );
-			assert.equal( s.toString(), '   def   ' );
-
-			s.trim();
-			assert.equal( s.toString(), 'def' );
-		});
-
-		it( 'should trim appended/prepended content', function () {
-			var s = new MagicString( ' abcdefghijkl ' );
-
-			s.prepend( '  ' ).append( '  ' ).trim();
-			assert.equal( s.toString(), 'abcdefghijkl' );
-		});
-
-		it( 'should trim empty string', function () {
-			var s = new MagicString( '   ' );
-
-			assert.equal( s.trim().toString(), '' );
-		});
-
-		it( 'should return this', function () {
-			var s = new MagicString( '  abcdefghijkl  ' );
-			assert.strictEqual( s.trim(), s );
-		});
-	});
-
-	describe( 'trimLines', function () {
-		it( 'should trim original content', function () {
-			var s = new MagicString( '\n\n   abcdefghijkl   \n\n' );
-
-			s.trimLines();
-			assert.equal( s.toString(), '   abcdefghijkl   ' );
-		});
-	});
-});
-
-describe( 'MagicString.Bundle', function () {
-	describe( 'addSource', function () {
-		it( 'should return this', function () {
-			var b = new MagicString.Bundle();
-			var source = new MagicString( 'abcdefghijkl' );
-
-			assert.strictEqual( b.addSource({ content: source }), b );
-		});
-
-		it( 'should accept MagicString instance as a single argument', function () {
-			var b = new MagicString.Bundle();
-			var array = [];
-			var source = new MagicString( 'abcdefghijkl', {
-				filename: 'foo.js',
-				indentExclusionRanges: array
-			});
-
-			b.addSource( source );
-			assert.strictEqual( b.sources[0].content, source );
-			assert.strictEqual( b.sources[0].filename, 'foo.js' );
-			assert.strictEqual( b.sources[0].indentExclusionRanges, array );
-		});
-
-		it( 'respects MagicString init options with { content: source }', function () {
-			var b = new MagicString.Bundle();
-			var array = [];
-			var source = new MagicString( 'abcdefghijkl', {
-				filename: 'foo.js',
-				indentExclusionRanges: array
-			});
-
-			b.addSource({ content: source });
-			assert.strictEqual( b.sources[0].content, source );
-			assert.strictEqual( b.sources[0].filename, 'foo.js' );
-			assert.strictEqual( b.sources[0].indentExclusionRanges, array );
-		});
-	});
-
-	describe( 'append', function () {
-		it( 'should append content', function () {
-			var b = new MagicString.Bundle();
-
-			b.addSource({ content: new MagicString( '*' ) });
-
-			b.append( '123' ).append( '456' );
-			assert.equal( b.toString(), '*123456' );
-		});
-
-		it( 'should append content before subsequent sources', function () {
-			var b = new MagicString.Bundle();
-
-			b.addSource( new MagicString( '*' ) );
-
-			b.append( '123' ).addSource( new MagicString( '-' ) ).append( '456' );
-			assert.equal( b.toString(), '*123\n-456' );
-		});
-
-		it( 'should return this', function () {
-			var b = new MagicString.Bundle();
-			assert.strictEqual( b.append( 'x' ), b );
-		});
-	});
-
-	describe( 'clone', function () {
-		it( 'should clone a bundle', function () {
-			var b = new MagicString.Bundle(),
-				s1 = new MagicString( 'abcdef' ),
-				s2 = new MagicString( 'ghijkl' ),
-				clone;
-
-			b
-			.addSource({
-				content: s1
-			})
-			.addSource({
-				content: s2
-			})
-			.prepend( '>>>' )
-			.append( '<<<' );
-
-			clone = b.clone();
-
-			assert.equal( clone.toString(), '>>>abcdef\nghijkl<<<' );
-
-			s1.overwrite( 2, 4, 'XX' );
-			assert.equal( b.toString(), '>>>abXXef\nghijkl<<<' );
-			assert.equal( clone.toString(), '>>>abcdef\nghijkl<<<' );
-		});
-	});
-
-	describe( 'generateMap', function () {
-		it( 'should generate a sourcemap', function () {
-			var b, map, smc, loc;
-
-			b = new MagicString.Bundle();
-
-			b.addSource({
-				filename: 'foo.js',
-				content: new MagicString( 'var answer = 42;' )
-			});
-
-			b.addSource({
-				filename: 'bar.js',
-				content: new MagicString( 'console.log( answer );' )
-			});
-
-			map = b.generateMap({
-				file: 'bundle.js',
-				includeContent: true,
-				hires: true
-			});
-
-			assert.equal( map.version, 3 );
-			assert.equal( map.file, 'bundle.js' );
-			assert.deepEqual( map.sources, [ 'foo.js', 'bar.js' ]);
-			assert.deepEqual( map.sourcesContent, [ 'var answer = 42;', 'console.log( answer );' ]);
-
-			assert.equal( map.toString(), '{"version":3,"file":"bundle.js","sources":["foo.js","bar.js"],"sourcesContent":["var answer = 42;","console.log( answer );"],"names":[],"mappings":"AAAA,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;ACAf,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC"}' );
-			assert.equal( map.toUrl(), 'data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVuZGxlLmpzIiwic291cmNlcyI6WyJmb28uanMiLCJiYXIuanMiXSwic291cmNlc0NvbnRlbnQiOlsidmFyIGFuc3dlciA9IDQyOyIsImNvbnNvbGUubG9nKCBhbnN3ZXIgKTsiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUNBZixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE [...]
-
-			smc = new SourceMapConsumer( map );
-
-			loc = smc.originalPositionFor({ line: 1, column: 0 });
-			assert.equal( loc.line, 1 );
-			assert.equal( loc.column, 0 );
-			assert.equal( loc.source, 'foo.js' );
-
-			loc = smc.originalPositionFor({ line: 1, column: 1 });
-			assert.equal( loc.line, 1 );
-			assert.equal( loc.column, 1 );
-			assert.equal( loc.source, 'foo.js' );
-
-			loc = smc.originalPositionFor({ line: 2, column: 0 });
-			assert.equal( loc.line, 1 );
-			assert.equal( loc.column, 0 );
-			assert.equal( loc.source, 'bar.js' );
-
-			loc = smc.originalPositionFor({ line: 2, column: 1 });
-			assert.equal( loc.line, 1 );
-			assert.equal( loc.column, 1 );
-			assert.equal( loc.source, 'bar.js' );
-		});
-
-		it( 'should handle Windows-style paths', function () {
-			var b, map, smc, loc;
-
-			b = new MagicString.Bundle();
-
-			b.addSource({
-				filename: 'path\\to\\foo.js',
-				content: new MagicString( 'var answer = 42;' )
-			});
-
-			b.addSource({
-				filename: 'path\\to\\bar.js',
-				content: new MagicString( 'console.log( answer );' )
-			});
-
-			map = b.generateMap({
-				file: 'bundle.js',
-				includeContent: true,
-				hires: true
-			});
-
-			assert.equal( map.version, 3 );
-			assert.equal( map.file, 'bundle.js' );
-			assert.deepEqual( map.sources, [ 'path/to/foo.js', 'path/to/bar.js' ]);
-			assert.deepEqual( map.sourcesContent, [ 'var answer = 42;', 'console.log( answer );' ]);
-
-			assert.equal( map.toString(), '{"version":3,"file":"bundle.js","sources":["path/to/foo.js","path/to/bar.js"],"sourcesContent":["var answer = 42;","console.log( answer );"],"names":[],"mappings":"AAAA,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;ACAf,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC"}' );
-
-			smc = new SourceMapConsumer( map );
-
-			loc = smc.originalPositionFor({ line: 1, column: 0 });
-			assert.equal( loc.line, 1 );
-			assert.equal( loc.column, 0 );
-			assert.equal( loc.source, 'path/to/foo.js' );
-
-			loc = smc.originalPositionFor({ line: 1, column: 1 });
-			assert.equal( loc.line, 1 );
-			assert.equal( loc.column, 1 );
-			assert.equal( loc.source, 'path/to/foo.js' );
-
-			loc = smc.originalPositionFor({ line: 2, column: 0 });
-			assert.equal( loc.line, 1 );
-			assert.equal( loc.column, 0 );
-			assert.equal( loc.source, 'path/to/bar.js' );
-
-			loc = smc.originalPositionFor({ line: 2, column: 1 });
-			assert.equal( loc.line, 1 );
-			assert.equal( loc.column, 1 );
-			assert.equal( loc.source, 'path/to/bar.js' );
-		});
-
-		it( 'should handle edge case with intro content', function () {
-			var b, map, smc, loc;
-
-			b = new MagicString.Bundle();
-
-			b.addSource({
-				filename: 'foo.js',
-				content: new MagicString( 'var answer = 42;' )
-			});
-
-			b.addSource({
-				filename: 'bar.js',
-				content: new MagicString( '\nconsole.log( answer );' )
-			});
-
-			b.indent().prepend( '(function () {\n' ).append( '\n}());' );
-
-			map = b.generateMap({
-				file: 'bundle.js',
-				includeContent: true,
-				hires: true
-			});
-
-			smc = new SourceMapConsumer( map );
-
-			loc = smc.originalPositionFor({ line: 2, column: 1 });
-			assert.equal( loc.line, 1 );
-			assert.equal( loc.column, 0 );
-			assert.equal( loc.source, 'foo.js' );
-
-			loc = smc.originalPositionFor({ line: 2, column: 2 });
-			assert.equal( loc.line, 1 );
-			assert.equal( loc.column, 1 );
-			assert.equal( loc.source, 'foo.js' );
-
-			loc = smc.originalPositionFor({ line: 4, column: 1 });
-			assert.equal( loc.line, 2 );
-			assert.equal( loc.column, 0 );
-			assert.equal( loc.source, 'bar.js' );
-
-			loc = smc.originalPositionFor({ line: 4, column: 2 });
-			assert.equal( loc.line, 2 );
-			assert.equal( loc.column, 1 );
-			assert.equal( loc.source, 'bar.js' );
-		});
-
-		it( 'should allow missing file option when generating map', function () {
-			var b, map;
-
-			b = new MagicString.Bundle();
-
-			b.addSource({
-				filename: 'foo.js',
-				content: new MagicString( 'var answer = 42;' )
-			});
-
-			map = b.generateMap({
-				includeContent: true,
-				hires: true
-			});
-		});
-
-		it( 'should handle repeated sources', function () {
-			var b = new MagicString.Bundle();
-
-			var foo = new MagicString( 'var one = 1;\nvar three = 3;', {
-				filename: 'foo.js'
-			});
-
-			var bar = new MagicString( 'var two = 2;\nvar four = 4;', {
-				filename: 'bar.js'
-			});
-
-			b.addSource( foo.snip( 0, 12 ) );
-			b.addSource( bar.snip( 0, 12 ) );
-			b.addSource( foo.snip( 13, 27 ) );
-			b.addSource( bar.snip( 13, 26 ) );
-
-			var code = b.toString();
-			assert.equal( code, 'var one = 1;\nvar two = 2;\nvar three = 3;\nvar four = 4;' );
-
-			var map = b.generateMap({
-				includeContent: true,
-				hires: true
-			});
-
-			assert.equal( map.sources.length, 2 );
-			assert.equal( map.sourcesContent.length, 2 );
-
-			var smc = new SourceMapConsumer( map );
-			var loc;
-
-			loc = smc.originalPositionFor({ line: 1, column: 0 });
-			assert.equal( loc.line, 1 );
-			assert.equal( loc.column, 0 );
-			assert.equal( loc.source, 'foo.js' );
-
-			loc = smc.originalPositionFor({ line: 2, column: 0 });
-			assert.equal( loc.line, 1 );
-			assert.equal( loc.column, 0 );
-			assert.equal( loc.source, 'bar.js' );
-
-			loc = smc.originalPositionFor({ line: 3, column: 0 });
-			assert.equal( loc.line, 2 );
-			assert.equal( loc.column, 0 );
-			assert.equal( loc.source, 'foo.js' );
-
-			loc = smc.originalPositionFor({ line: 4, column: 0 });
-			assert.equal( loc.line, 2 );
-			assert.equal( loc.column, 0 );
-			assert.equal( loc.source, 'bar.js' );
-		});
-
-		it( 'should recover original names', function () {
-			var b = new MagicString.Bundle();
-
-			var one = new MagicString( 'function one () {}', { filename: 'one.js' });
-			var two = new MagicString( 'function two () {}', { filename: 'two.js' });
-
-			one.overwrite( 9, 12, 'three', true );
-			two.overwrite( 9, 12, 'four', true );
-
-			b.addSource( one );
-			b.addSource( two );
-
-			map = b.generateMap({
-				file: 'output.js',
-				source: 'input.js',
-				includeContent: true
-			});
-
-			smc = new SourceMapConsumer( map );
-
-			loc = smc.originalPositionFor({ line: 1, column: 9 });
-			assert.equal( loc.name, 'one' );
-
-			loc = smc.originalPositionFor({ line: 2, column: 9 });
-			assert.equal( loc.name, 'two' );
-		});
-
-		it( 'should exclude sources without filename from sourcemap', function () {
-			var b = new MagicString.Bundle();
-
-			var one = new MagicString( 'function one () {}', { filename: 'one.js' });
-			var two = new MagicString( 'function two () {}', { filename: null });
-			var three = new MagicString( 'function three () {}', { filename: 'three.js' });
-
-			b.addSource( one );
-			b.addSource( two );
-			b.addSource( three );
-
-			map = b.generateMap({
-				file: 'output.js',
-				source: 'input.js',
-				includeContent: true
-			});
-
-			smc = new SourceMapConsumer( map );
-
-			loc = smc.originalPositionFor({ line: 1, column: 9 });
-			assert.equal( loc.source, 'one.js' );
-
-			loc = smc.originalPositionFor({ line: 2, column: 9 });
-			assert.equal( loc.source, null );
-
-			loc = smc.originalPositionFor({ line: 3, column: 9 });
-			assert.equal( loc.source, 'three.js' );
-		});
-
-		it( 'handles prepended content', function () {
-			var b = new MagicString.Bundle();
-
-			var one = new MagicString( 'function one () {}', { filename: 'one.js' });
-			var two = new MagicString( 'function two () {}', { filename: 'two.js' });
-			two.prepend( 'function oneAndAHalf() {}\n' );
-
-			b.addSource( one );
-			b.addSource( two );
-
-			map = b.generateMap({
-				file: 'output.js',
-				source: 'input.js',
-				includeContent: true
-			});
-
-			smc = new SourceMapConsumer( map );
-
-			loc = smc.originalPositionFor({ line: 1, column: 9 });
-			assert.equal( loc.source, 'one.js' );
-
-			loc = smc.originalPositionFor({ line: 3, column: 9 });
-			assert.equal( loc.source, 'two.js' );
-		});
-
-		it( 'handles appended content', function () {
-			var b = new MagicString.Bundle();
-
-			var one = new MagicString( 'function one () {}', { filename: 'one.js' });
-			one.append( '\nfunction oneAndAHalf() {}' );
-			var two = new MagicString( 'function two () {}', { filename: 'two.js' });
-
-			b.addSource( one );
-			b.addSource( two );
-
-			map = b.generateMap({
-				file: 'output.js',
-				source: 'input.js',
-				includeContent: true
-			});
-
-			smc = new SourceMapConsumer( map );
-
-			loc = smc.originalPositionFor({ line: 1, column: 9 });
-			assert.equal( loc.source, 'one.js' );
-
-			loc = smc.originalPositionFor({ line: 3, column: 9 });
-			assert.equal( loc.source, 'two.js' );
-		});
-	});
-
-	describe( 'indent', function () {
-		it( 'should indent a bundle', function () {
-			var b = new MagicString.Bundle();
-
-			b.addSource({ content: new MagicString( 'abcdef' ) });
-			b.addSource({ content: new MagicString( 'ghijkl' ) });
-
-			b.indent().prepend( '>>>\n' ).append( '\n<<<' );
-			assert.equal( b.toString(), '>>>\n\tabcdef\n\tghijkl\n<<<' );
-		});
-
-		it( 'should ignore non-indented sources when guessing indentation', function () {
-			var b = new MagicString.Bundle();
-
-			b.addSource({ content: new MagicString( 'abcdef' ) });
-			b.addSource({ content: new MagicString( 'ghijkl' ) });
-			b.addSource({ content: new MagicString( '  mnopqr' ) });
-
-			b.indent();
-			assert.equal( b.toString(), '  abcdef\n  ghijkl\n    mnopqr' );
-		});
-
-		it( 'should respect indent exclusion ranges', function () {
-			var b = new MagicString.Bundle();
-
-			b.addSource({
-				content: new MagicString( 'abc\ndef\nghi\njkl' ),
-				indentExclusionRanges: [ 7, 15 ]
-			});
-
-			b.indent( '  ' );
-			assert.equal( b.toString(), '  abc\n  def\nghi\njkl' );
-
-			b.indent( '>>' );
-			assert.equal( b.toString(), '>>  abc\n>>  def\nghi\njkl' );
-		});
-
-		it( 'does not indent sources with no preceding newline, i.e. append()', function () {
-			var b = new MagicString.Bundle();
-
-			b.addSource( new MagicString( 'abcdef' ) );
-			b.addSource( new MagicString( 'ghijkl' ) );
-
-			b.prepend( '>>>' ).append( '<<<' ).indent();
-			assert.equal( b.toString(), '\t>>>abcdef\n\tghijkl<<<' );
-		});
-
-		it( 'should noop with an empty string', function () {
-			var b = new MagicString.Bundle();
-
-			b.addSource( new MagicString( 'abcdef' ) );
-			b.addSource( new MagicString( 'ghijkl' ) );
-
-			b.indent( '' );
-			assert.equal( b.toString(), 'abcdef\nghijkl' );
-		});
-
-		it( 'indents prepended content', function () {
-			var b = new MagicString.Bundle();
-			b.prepend( 'a\nb' ).indent();
-
-			assert.equal( b.toString(), '\ta\n\tb' );
-		});
-
-		it( 'indents content immediately following intro with trailing newline', function () {
-			var b = new MagicString.Bundle({ separator: '\n\n' });
-
-			var s = new MagicString( '2' );
-			b.addSource({ content: s });
-
-			b.prepend( '1\n' );
-
-			assert.equal( b.indent().toString(), '\t1\n\t2' );
-		});
-
-		it( 'should return this', function () {
-			var b = new MagicString.Bundle();
-			assert.strictEqual( b.indent(), b );
-		});
-
-		it( 'should return this on noop', function () {
-			var b = new MagicString.Bundle();
-			assert.strictEqual( b.indent( '' ), b );
-		});
-	});
-
-	describe( 'prepend', function () {
-		it( 'should append content', function () {
-			var b = new MagicString.Bundle();
-
-			b.addSource({ content: new MagicString( '*' ) });
-
-			b.prepend( '123' ).prepend( '456' );
-			assert.equal( b.toString(), '456123*' );
-		});
-
-		it( 'should return this', function () {
-			var b = new MagicString.Bundle();
-			assert.strictEqual( b.prepend( 'x' ), b );
-		});
-	});
-
-	describe( 'trim', function () {
-		it( 'should trim bundle', function () {
-			var b = new MagicString.Bundle();
-
-			b.addSource({
-				content: new MagicString( '   abcdef   ' )
-			});
-
-			b.addSource({
-				content: new MagicString( '   ghijkl   ' )
-			});
-
-			b.trim();
-			assert.equal( b.toString(), 'abcdef   \n   ghijkl' );
-		});
-
-		it( 'should handle funky edge cases', function () {
-			var b = new MagicString.Bundle();
-
-			b.addSource({
-				content: new MagicString( '   abcdef   ' )
-			});
-
-			b.addSource({
-				content: new MagicString( '   x   ' )
-			});
-
-			b.prepend( '\n>>>\n' ).append( '   ' );
-
-			b.trim();
-			assert.equal( b.toString(), '>>>\n   abcdef   \n   x' );
-		});
-
-		it( 'should return this', function () {
-			var b = new MagicString.Bundle();
-			assert.strictEqual( b.trim(), b );
-		});
-	});
-
-	describe( 'toString', function () {
-		it( 'should separate with a newline by default', function () {
-			var b = new MagicString.Bundle();
-
-			b.addSource( new MagicString( 'abc' ) );
-			b.addSource( new MagicString( 'def' ) );
-
-			assert.strictEqual( b.toString(), 'abc\ndef' );
-		});
-
-		it( 'should accept separator option', function () {
-			var b = new MagicString.Bundle({ separator: '==' });
-
-			b.addSource( new MagicString( 'abc' ) );
-			b.addSource( new MagicString( 'def' ) );
-
-			assert.strictEqual( b.toString(), 'abc==def' );
-		});
-
-		it( 'should accept empty string separator option', function () {
-			var b = new MagicString.Bundle({ separator: '' });
-
-			b.addSource( new MagicString( 'abc' ) );
-			b.addSource( new MagicString( 'def' ) );
-
-			assert.strictEqual( b.toString(), 'abcdef' );
-		});
-	});
-});
diff --git a/test/mocha.opts b/test/mocha.opts
new file mode 100644
index 0000000..c3af56e
--- /dev/null
+++ b/test/mocha.opts
@@ -0,0 +1 @@
+--compilers js:buble/register
\ No newline at end of file

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-magic-string.git



More information about the Pkg-javascript-commits mailing list