[Pkg-javascript-commits] [node-magic-string] 01/02: New upstream version 0.16.0
Julien Puydt
julien.puydt at laposte.net
Tue Nov 15 07:18:47 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 c0a21340e5c9d698966b20ca32f1c0b1357fc87f
Author: Julien Puydt <julien.puydt at laposte.net>
Date: Tue Nov 1 15:42:31 2016 +0100
New upstream version 0.16.0
---
.eslintrc | 34 +
.gitignore | 8 +
.travis.yml | 11 +
CHANGELOG.md | 273 +++++++
README.md | 233 ++++++
appveyor.yml | 23 +
example/app.inlinemap.js | 4 +
example/app.js | 3 +
example/app.js.map | 1 +
example/app.source.js | 2 +
example/build.js | 35 +
example/index.html | 1 +
package.json | 59 ++
rollup.config.js | 21 +
src/Bundle.js | 240 ++++++
src/Chunk.js | 146 ++++
src/MagicString.js | 519 +++++++++++++
src/index-legacy.js | 6 +
src/index.js | 4 +
src/utils/SourceMap.js | 21 +
src/utils/Stats.js | 18 +
src/utils/btoa.js | 13 +
src/utils/encodeMappings.js | 137 ++++
src/utils/getLocator.js | 35 +
src/utils/getRelativePath.js | 18 +
src/utils/getSemis.js | 3 +
src/utils/guessIndent.js | 25 +
src/utils/hasOwnProp.js | 1 +
src/utils/isObject.js | 5 +
test/index.js | 1684 ++++++++++++++++++++++++++++++++++++++++++
30 files changed, 3583 insertions(+)
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..dfefff3
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,34 @@
+{
+ "rules": {
+ "indent": [ 2, "tab", { "SwitchCase": 1 }],
+ "quotes": [ 2, "single" ],
+ "linebreak-style": [ 2, "unix" ],
+ "semi": [ 2, "always" ],
+ "no-mixed-spaces-and-tabs": [ 2, "smart-tabs" ],
+ "object-shorthand": [2, "always" ],
+ "no-const-assign": 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,
+ "arrow-spacing": 2,
+
+ "no-cond-assign": 0
+ },
+ "env": {
+ "es6": true,
+ "browser": true
+ },
+ "globals": {
+ "DEBUG": true,
+ "process": true,
+ "Buffer": true
+ },
+ "extends": "eslint:recommended",
+ "parserOptions": {
+ "ecmaVersion": 6,
+ "sourceType": "module"
+ }
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7a9e43e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+.DS_Store
+node_modules
+sandbox
+.gobbl*
+.tmp
+!.babelrc
+coverage
+dist
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..f5bf60c
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,11 @@
+sudo: false
+language: node_js
+node_js:
+ - "0.12"
+ - "4"
+ - "5"
+env:
+ global:
+ - BUILD_TIMEOUT=10000
+install: npm install
+script: npm test
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..20fa5b0
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,273 @@
+# magic-string changelog
+
+## 0.16.0
+
+* Include inserts in range for `overwrite` and `remove` operations ([#89](https://github.com/Rich-Harris/magic-string/pull/89))
+* Make options optional for `bundle.generateMap(...)` ([#73](https://github.com/Rich-Harris/magic-string/pull/73))
+
+## 0.15.2
+
+* Generate correct bundle sourcemap with prepended/appended content
+
+## 0.15.1
+
+* Minor sourcemap fixes
+
+## 0.15.0
+
+* Use named export of `Bundle` in ES build, so ES consumers of magic-string can tree-shake it out
+
+## 0.14.0
+
+* Throw if overwrite of zero-length range is attempted
+* Correctly handle redundant move operations
+
+## 0.13.1
+
+* Fix a bevy of `s.slice()` issues ([#62](https://github.com/Rich-Harris/magic-string/pull/62))
+
+## 0.13.0
+
+* Breaking: `insertAfter` is now `insertLeft`, `insertBefore` is now `insertRight`
+* Breaking: `insert` is no longer available. Use `insertLeft` and `insertRight`
+* Significant performance improvements
+
+## 0.12.1
+
+* Fix sourcemap generation with `insertAfter` and `insertBefore`
+
+## 0.12.0
+
+* Add `insertAfter` and `insertBefore` methods
+
+## 0.11.4
+
+* Fix two regression bugs with `trim()`
+* More informative error message on illegal removals
+
+## 0.11.3
+
+* Fix trim methods to ensure correct sourcemaps with trimmed content ([#53](https://github.com/Rich-Harris/magic-string/pull/53))
+
+## 0.11.2
+
+* Support sourcemaps with moved content
+
+## 0.11.1
+
+* Use `findIndex` helper for 0.12 support
+
+## 0.11.0
+
+* Add experimental `move()` method
+* Refactor internals to support `move()`
+
+## 0.10.2
+
+* Do not overwrite inserts at the end of patched ranges ([#35](https://github.com/Rich-Harris/magic-string/pull/35))
+
+## 0.10.1
+
+* Zero-length inserts are not removed on adjacent overwrites
+
+## 0.10.0
+
+* Complete rewrite, resulting in ~40x speed increase ([#30](https://github.com/Rich-Harris/magic-string/pull/30))
+* Breaking – `magicString.locate` and `locateOrigin` are deprecated
+* More forgiving rules about contiguous patches, and which ranges are valid with `magicString.slice(...)`
+
+## 0.9.1
+
+* Update deps
+
+## 0.9.0
+
+* Update build process
+
+## 0.8.0
+
+* Add an ES6 build, change default UMD build to CommonJS (but keeping existing UMD build with bundled dependencies)
+* Make properties non-enumerable, for cleaner logging
+* Update dependencies
+
+## 0.7.0
+
+* The `names` array is populated when generating sourcemaps, and mappings include name indices where appropriate ([#16](https://github.com/Rich-Harris/magic-string/issues/16))
+* Replaced content is mapped correctly in sourcemaps ([#15](https://github.com/Rich-Harris/magic-string/issues/15))
+
+## 0.6.6
+
+* Adjust mappings correctly when removing replaced content
+* Error correctly when removed characters are used as slice anchors
+
+## 0.6.5
+
+* Fix `jsnext:main` in package.json
+
+## 0.6.4
+
+* Fix bug with positive integer coercion
+
+## 0.6.3
+
+* Intro content is correctly indented
+* Content following an intro with trailing newline is correctly indented
+
+## 0.6.2
+
+* Noop indents are still chainable (fixes bug introduced in 0.6.1)
+
+## 0.6.1
+
+* Indenting with an empty string is a noop
+
+## 0.6.0
+
+* Use rollup for bundling, instead of esperanto
+
+## 0.5.3
+
+* Correct sourcemap generation with bundles containing varied separators
+* `s.clone()` clones indent exclusion ranges and sourcemap locations
+
+## 0.5.2
+
+* `s.slice()` accepts negative numbers, and the second argument can be omitted (means 'original string length'), just like `String.prototype.slice`
+* More informative error message when trying to overwrite content illegally
+
+## 0.5.1
+
+* Allow bundle separator to be the empty string
+* Indenting is handled correctly with empty string separator
+
+## 0.5.0
+
+* `s.replace()` is deprecated in favour of `s.overwrite()` (identical signature)
+* `bundle.addSource()` can take a `MagicString` instance as its sole argument, for convenience
+* The `options` in `new MagicString(str, options)` can include `filename` and `indentExclusionRanges` options, which will be used when bundling
+* New method: `s.snip( start, end )`
+
+## 0.4.9
+
+* `file` option is optional when generating a bundle sourcemap
+
+## 0.4.7
+
+* Repeated insertions at position 0 behave the same as other positions ([#10](https://github.com/Rich-Harris/magic-string/pull/10))
+
+## 0.4.6
+
+* Overlapping ranges can be removed
+* Non-string content is rejected ([#9](https://github.com/Rich-Harris/magic-string/pull/9))
+
+## 0.4.5
+
+* Implement `source.addSourcemapLocation()`
+
+## 0.4.4
+
+* Another Windows fix, this time for file paths when bundling
+
+## 0.4.3
+
+* Handle Windows-style CRLF newlines when determining whether a line is empty
+
+## 0.4.2
+
+* Fix typo in package.json (d'oh again)
+* Use only relative paths for internal modules - makes bundling with dependents (i.e. esperanto) possible
+
+## 0.4.1
+
+* Includes correct files in npm package (d'oh)
+
+## 0.4.0
+
+* Using experimental Esperanto feature ([esperantojs/esperanto#68](https://github.com/esperantojs/esperanto/issues/68)) to generate version with `vlq` dependency included
+
+## 0.3.1
+
+* Fixes a bug whereby multiple insertions at the same location would cause text to repeat ([#5](https://github.com/Rich-Harris/magic-string/issues/5))
+
+## 0.3.0
+
+* Breaking change - `source.indentStr` is `null` if no lines are indented. Use `source.getIndentString()` for the old behaviour (guess, and if no lines are indented, return `\t`)
+* `bundle.getIndentString()` ignores sources with no indented lines when guessing indentation ([#3](https://github.com/Rich-Harris/magic-string/issues/3))
+
+## 0.2.7
+
+* `source.trimLines()` removes empty lines from start/end of source, leaving other whitespace untouched
+* Indentation is not added to an empty source
+
+## 0.2.6
+
+* Performance improvement - adjustments are only made when necessary
+
+## 0.2.5
+
+* Single spaces are ignored when guessing indentation - experience shows these are more likely to be e.g. JSDoc comments than actual indentation
+* `bundle.addSource()` can take an `indentExclusionRanges` option
+
+## 0.2.4
+
+* Empty lines are not indented
+
+## 0.2.3
+
+* Fixes edge case with bundle sourcemaps
+
+## 0.2.2
+
+* Make `sources` paths in sourcemaps relative to `options.file`
+
+## 0.2.1
+
+* Minor fix for `bundle.indent()`
+
+## 0.2.0
+
+* Implement `MagicString.Bundle` for concatenating magic strings
+
+## 0.1.10
+
+* Fix sourcemap encoding
+
+## 0.1.9
+
+* Better performance when indenting large chunks of code
+
+## 0.1.8
+
+* Sourcemaps generated with `s.generateMap()` have a `toUrl()` method that generates a DataURI
+
+## 0.1.7
+
+* Implement `s.insert( index, content )` - roughly equivalent to `s.replace( index, index, content )`
+
+## 0.1.6
+
+* Version bump for npm's benefit
+
+## 0.1.5
+
+* `s.indent({ exclude: [ x, y ] })` prevents lines between (original) characters `x` and `y` from being indented. Multiple exclusion ranges are also supported (e.g. `exclude: [[a, b], [c, d]]`)
+
+## 0.1.4
+
+* `s.locate()` doesn't throw out-of-bound error if index is equal to original string's length
+
+## 0.1.3
+
+* `s.trim()` returns `this` (i.e. is chainable)
+
+## 0.1.2
+
+* Implement `s.slice()`
+
+## 0.1.1
+
+* Implement `s.trim()`
+
+## 0.1.0
+
+* First release
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b971bd4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,233 @@
+# magic-string
+
+<p align="center">
+ <a href="https://travis-ci.org/Rich-Harris/magic-string">
+ <img src="http://img.shields.io/travis/Rich-Harris/magic-string.svg"
+ alt="build status">
+ </a>
+ <a href="https://npmjs.org/package/magic-string">
+ <img src="https://img.shields.io/npm/v/magic-string.svg"
+ alt="npm version">
+ </a>
+ <a href="https://github.com/Rich-Harris/magic-string/blob/master/LICENSE.md">
+ <img src="https://img.shields.io/npm/l/magic-string.svg"
+ alt="license">
+ </a>
+ <a href="https://david-dm.org/Rich-Harris/magic-string">
+ <img src="https://david-dm.org/Rich-Harris/magic-string.svg"
+ alt="dependency status">
+ </a>
+ <a href="http://codecov.io/github/Rich-Harris/magic-string?branch=master">
+ <img src="http://codecov.io/github/Rich-Harris/magic-string/coverage.svg?branch=master" alt="Coverage via Codecov" />
+ </a>
+</p>
+
+Suppose you have some source code. You want to make some light modifications to it - replacing a few characters here and there, wrapping it with a header and footer, etc - and ideally you'd like to generate a source map at the end of it. You've thought about using something like [recast](https://github.com/benjamn/recast) (which allows you to generate an AST from some JavaScript, manipulate it, and reprint it with a sourcemap without losing your comments and formatting), but it seems lik [...]
+
+Your requirements are, frankly, rather niche. But they're requirements that I also have, and for which I made magic-string. It's a small, fast utility for manipulating strings and generating sourcemaps.
+
+## Installation
+
+magic-string works in both node.js and browser environments. For node, install with npm:
+
+```bash
+npm i magic-string
+```
+
+To use in browser, grab the [magic-string.deps.js](https://raw.githubusercontent.com/Rich-Harris/magic-string/master/dist/magic-string.deps.js) file and add it to your page:
+
+```html
+<script src='magic-string.deps.js'></script>
+```
+
+(It also works with various module systems, if you prefer that sort of thing - it has a dependency on [vlq](https://github.com/Rich-Harris/vlq).)
+
+## Usage
+
+These examples assume you're in node.js, or something similar:
+
+```js
+var MagicString = require( 'magic-string' );
+var s = new MagicString( 'problems = 99' );
+
+s.overwrite( 0, 8, 'answer' );
+s.toString(); // 'answer = 99'
+
+s.overwrite( 11, 13, '42' ); // character indices always refer to the original string
+s.toString(); // 'answer = 42'
+
+s.prepend( 'var ' ).append( ';' ); // most methods are chainable
+s.toString(); // 'var answer = 42;'
+
+var map = s.generateMap({
+ source: 'source.js',
+ file: 'converted.js.map',
+ includeContent: true
+}); // generates a v3 sourcemap
+
+require( 'fs' ).writeFile( 'converted.js', s.toString() );
+require( 'fs' ).writeFile( 'converted.js.map', map.toString() );
+```
+
+You can pass an options argument:
+
+```js
+var s = new MagicString( someCode, {
+ // both these options will be used if you later
+ // call `bundle.addSource( s )` - see below
+ filename: 'foo.js',
+ indentExclusionRanges: [/*...*/]
+});
+```
+
+## Methods
+
+### s.addSourcemapLocation( index )
+
+Adds the specified character index (with respect to the original string) to sourcemap mappings, if `hires` is `false` (see below).
+
+### s.append( content )
+
+Appends the specified content to the end of the string. Returns `this`.
+
+### s.clone()
+
+Does what you'd expect.
+
+### s.generateMap( options )
+
+Generates a [version 3 sourcemap](https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit). All options are, well, optional:
+
+* `file` - the filename where you plan to write the sourcemap
+* `source` - the filename of the file containing the original source
+* `includeContent` - whether to include the original content in the map's `sourcesContent` array
+* `hires` - whether the mapping should be high-resolution. Hi-res mappings map every single character, meaning (for example) your devtools will always be able to pinpoint the exact location of function calls and so on. With lo-res mappings, devtools may only be able to identify the correct line - but they're quicker to generate and less bulky. If sourcemap locations have been specified with `s.addSourceMapLocation()`, they will be used here.
+
+The returned sourcemap has two (non-enumerable) methods attached for convenience:
+
+* `toString` - returns the equivalent of `JSON.stringify(map)`
+* `toUrl` - returns a DataURI containing the sourcemap. Useful for doing this sort of thing:
+
+```js
+code += '\n//# sourceMappingURL=' + map.toUrl();
+```
+
+### s.indent( prefix[, options] )
+
+Prefixes each line of the string with `prefix`. If `prefix` is not supplied, the indentation will be guessed from the original content, falling back to a single tab character. Returns `this`.
+
+The `options` argument can have an `exclude` property, which is an array of `[start, end]` character ranges. These ranges will be excluded from the indentation - useful for (e.g.) multiline strings.
+
+### 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`.
+
+### 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`.
+
+### s.locate( index )
+
+**DEPRECATED** since 0.10 – see [#30](https://github.com/Rich-Harris/magic-string/pull/30)
+
+### s.locateOrigin( index )
+
+**DEPRECATED** since 0.10 – see [#30](https://github.com/Rich-Harris/magic-string/pull/30)
+
+### s.move( start, end, newIndex )
+
+Moves the characters from `start` and `end` to `index`. Returns `this`.
+
+### s.overwrite( start, end, content[, storeName] )
+
+Replaces the characters from `start` to `end` with `content`. The same restrictions as `s.remove()` apply. Returns `this`. If `storeName` is `true`, the original name will be stored for later inclusion in a sourcemap's `names` array.
+
+### s.prepend( content )
+
+Prepends the string with the specified content. Returns `this`.
+
+### 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`.
+
+### s.slice( start, end )
+
+Returns the content of the generated string that corresponds to the slice between `start` and `end` of the original string. Throws error if the indices are for characters that were already removed.
+
+### s.snip( start, end )
+
+Returns a clone of `s`, with all content before the `start` and `end` characters of the original string removed.
+
+### s.toString()
+
+Returns the generated string.
+
+### s.trim([ charType ])
+
+Trims content matching `charType` (defaults to `\s`, i.e. whitespace) from the start and end. Returns `this`.
+
+### s.trimStart([ charType ])
+
+Trims content matching `charType` (defaults to `\s`, i.e. whitespace) from the start. Returns `this`.
+
+### s.trimEnd([ charType ])
+
+Trims content matching `charType` (defaults to `\s`, i.e. whitespace) from the end. Returns `this`.
+
+### s.trimLines()
+
+Removes empty lines from the start and end. Returns `this`.
+
+## Bundling
+
+To concatenate several sources, use `MagicString.Bundle`:
+
+```js
+var bundle = new MagicString.Bundle();
+
+bundle.addSource({
+ filename: 'foo.js',
+ content: new MagicString( 'var answer = 42;' )
+});
+
+bundle.addSource({
+ filename: 'bar.js',
+ content: new MagicString( 'console.log( answer )' )
+});
+
+// Advanced: a source can include an `indentExclusionRanges` property
+// alongside `filename` and `content`. This will be passed to `s.indent()`
+// - see documentation above
+
+bundle.indent() // optionally, pass an indent string, otherwise it will be guessed
+ .prepend( '(function () {\n' )
+ .append( '}());' );
+
+bundle.toString();
+// (function () {
+// var answer = 42;
+// console.log( answer );
+// }());
+
+// options are as per `s.generateMap()` above
+var map = bundle.generateMap({
+ file: 'bundle.js',
+ includeContent: true,
+ hires: true
+});
+```
+
+As an alternative syntax, if you a) don't have `filename` or `indentExclusionRanges` options, or b) passed those in when you used `new MagicString(...)`, you can simply pass the `MagicString` instance itself:
+
+```js
+var bundle = new MagicString.Bundle();
+var source = new MagicString( someCode, {
+ filename: 'foo.js'
+});
+
+bundle.addSource( source );
+```
+
+## License
+
+MIT
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..e326df6
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,23 @@
+# Test against this version of Node.js
+environment:
+ matrix:
+ # node.js
+ - nodejs_version: "5"
+
+# Install scripts. (runs after repo cloning)
+install:
+ # Get the latest stable version of Node.js or io.js
+ - ps: Install-Product node $env:nodejs_version
+ # install modules
+ - npm install
+
+# Post-install test scripts.
+test_script:
+ # Output useful info for debugging.
+ - node --version
+ - npm --version
+ # run tests
+ - npm test
+
+# Don't actually build.
+build: off
diff --git a/example/app.inlinemap.js b/example/app.inlinemap.js
new file mode 100644
index 0000000..4666ba7
--- /dev/null
+++ b/example/app.inlinemap.js
@@ -0,0 +1,4 @@
+var answer = 'yes';
+console.log( answer );
+//# sourceMappingURL=app.js.map
+//#sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwLmpzLm1hcCIsInNvdXJjZXMiOlsiYXBwLnNvdXJjZS5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJ2YXIgZm9vID0gJ3llcyc7XG5jb25zb2xlLmxvZyggZm9vICk7Il0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLENBQUMsQ0FBQyxDQUFDLENBQ1csQ0FBQSxDQUFBLENBQUEsQ0FBQSxDQUFBLENBRFAsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ2YsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBRSxDQUFBLENBQUEsQ0FBQ [...]
\ No newline at end of file
diff --git a/example/app.js b/example/app.js
new file mode 100644
index 0000000..4fe076c
--- /dev/null
+++ b/example/app.js
@@ -0,0 +1,3 @@
+var answer = 'yes';
+console.log( answer );
+//# sourceMappingURL=app.js.map
\ No newline at end of file
diff --git a/example/app.js.map b/example/app.js.map
new file mode 100644
index 0000000..3c24c79
--- /dev/null
+++ b/example/app.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"app.js.map","sources":["app.source.js"],"sourcesContent":["var foo = 'yes';\nconsole.log( foo );"],"names":[],"mappings":"AAAA,CAAC,CAAC,CAAC,CACW,CAAA,CAAA,CAAA,CAAA,CAAA,CADP,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACf,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAAA,CAAA,CAAA,CAAA,CAAA,CAAd,CAAC,CAAC"}
\ No newline at end of file
diff --git a/example/app.source.js b/example/app.source.js
new file mode 100644
index 0000000..197cc9e
--- /dev/null
+++ b/example/app.source.js
@@ -0,0 +1,2 @@
+var foo = 'yes';
+console.log( foo );
\ No newline at end of file
diff --git a/example/build.js b/example/build.js
new file mode 100644
index 0000000..644255e
--- /dev/null
+++ b/example/build.js
@@ -0,0 +1,35 @@
+var fs = require( 'fs' ),
+ MagicString = require( '../' );
+
+process.chdir( __dirname );
+
+fs.readFile( 'app.source.js', function ( err, result ) {
+ var source,
+ magicString,
+ pattern = /foo/g,
+ match,
+ transpiled,
+ map;
+
+ if ( err ) throw err;
+
+ source = result.toString();
+ magicString = new MagicString( result.toString() );
+
+ while ( match = pattern.exec( source ) ) {
+ magicString.replace( match.index, match.index + 3, 'answer' );
+ }
+
+ transpiled = magicString.toString() + '\n//# sourceMappingURL=app.js.map';
+ map = magicString.generateMap({
+ file: 'app.js.map',
+ source: 'app.source.js',
+ includeContent: true,
+ hires: true
+ });
+
+ fs.writeFile( 'app.js', transpiled );
+ fs.writeFile( 'app.js.map', map );
+
+ fs.writeFile( 'app.inlinemap.js', transpiled + '\n//#sourceMappingURL=' + map.toUrl() );
+});
\ No newline at end of file
diff --git a/example/index.html b/example/index.html
new file mode 100644
index 0000000..04f4ffb
--- /dev/null
+++ b/example/index.html
@@ -0,0 +1 @@
+<script src='app.inlinemap.js'></script>
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..3c6f944
--- /dev/null
+++ b/package.json
@@ -0,0 +1,59 @@
+{
+ "name": "magic-string",
+ "description": "Modify strings, generate sourcemaps",
+ "author": "Rich Harris",
+ "version": "0.16.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",
+ "license": "MIT",
+ "dependencies": {
+ "vlq": "^0.2.1"
+ },
+ "devDependencies": {
+ "codecov.io": "^0.1.6",
+ "console-group": "^0.2.1",
+ "eslint": "^2.11.1",
+ "istanbul": "^0.4.3",
+ "mocha": "^3.0.1",
+ "remap-istanbul": "^0.6.4",
+ "resolve": "^1.1.7",
+ "rollup": "^0.34.5",
+ "rollup-plugin-buble": "^0.12.1",
+ "rollup-plugin-node-resolve": "^2.0.0",
+ "rollup-plugin-replace": "^1.1.0",
+ "source-map": "^0.5.6",
+ "source-map-support": "^0.4.0"
+ },
+ "keywords": [
+ "string",
+ "string manipulation",
+ "sourcemap",
+ "templating",
+ "transpilation"
+ ],
+ "scripts": {
+ "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",
+ "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: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",
+ "watch:cjs": "rollup -w -c",
+ "watch:es6": "rollup -w -c --environment ES",
+ "watch:umd": "rollup -w -c --environment DEPS",
+ "watch": "npm run watch:es6"
+ },
+ "files": [
+ "src/*",
+ "dist/*",
+ "README.md"
+ ]
+}
diff --git a/rollup.config.js b/rollup.config.js
new file mode 100644
index 0000000..ef0aa20
--- /dev/null
+++ b/rollup.config.js
@@ -0,0 +1,21 @@
+import buble from 'rollup-plugin-buble';
+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';
+
+export default {
+ entry: process.env.ES ? 'src/index.js' : 'src/index-legacy.js',
+ dest: 'dist/magic-string.' + format + '.js',
+ format: format,
+ exports: process.env.ES ? 'named' : 'default',
+ plugins: [
+ buble({ exclude: 'node_modules/**' }),
+ nodeResolve({ jsnext: true, skip: external }),
+ replace({ DEBUG: false })
+ ],
+ moduleName: 'MagicString',
+ external: external,
+ sourceMap: true
+};
diff --git a/src/Bundle.js b/src/Bundle.js
new file mode 100644
index 0000000..7814ea8
--- /dev/null
+++ b/src/Bundle.js
@@ -0,0 +1,240 @@
+import MagicString from './MagicString.js';
+import SourceMap from './utils/SourceMap.js';
+import getSemis from './utils/getSemis.js';
+import getRelativePath from './utils/getRelativePath.js';
+import hasOwnProp from './utils/hasOwnProp.js';
+import isObject from './utils/isObject.js';
+
+export default function Bundle ( options = {} ) {
+ this.intro = options.intro || '';
+ this.separator = options.separator !== undefined ? options.separator : '\n';
+
+ this.sources = [];
+
+ this.uniqueSources = [];
+ this.uniqueSourceIndexByFilename = {};
+}
+
+Bundle.prototype = {
+ addSource ( source ) {
+ if ( source instanceof MagicString ) {
+ return this.addSource({
+ content: source,
+ filename: source.filename,
+ separator: this.separator
+ });
+ }
+
+ if ( !isObject( source ) || !source.content ) {
+ throw new Error( 'bundle.addSource() takes an object with a `content` property, which should be an instance of MagicString, and an optional `filename`' );
+ }
+
+ [ 'filename', 'indentExclusionRanges', 'separator' ].forEach( option => {
+ if ( !hasOwnProp.call( source, option ) ) source[ option ] = source.content[ option ];
+ });
+
+ if ( source.separator === undefined ) { // TODO there's a bunch of this sort of thing, needs cleaning up
+ source.separator = this.separator;
+ }
+
+ if ( source.filename ) {
+ if ( !hasOwnProp.call( this.uniqueSourceIndexByFilename, source.filename ) ) {
+ this.uniqueSourceIndexByFilename[ source.filename ] = this.uniqueSources.length;
+ this.uniqueSources.push({ filename: source.filename, content: source.content.original });
+ } else {
+ const uniqueSource = this.uniqueSources[ this.uniqueSourceIndexByFilename[ source.filename ] ];
+ if ( source.content.original !== uniqueSource.content ) {
+ throw new Error( `Illegal source: same filename (${source.filename}), different contents` );
+ }
+ }
+ }
+
+ this.sources.push( source );
+ return this;
+ },
+
+ append ( str, options ) {
+ this.addSource({
+ content: new MagicString( str ),
+ separator: ( options && options.separator ) || ''
+ });
+
+ return this;
+ },
+
+ clone () {
+ const bundle = new Bundle({
+ intro: this.intro,
+ separator: this.separator
+ });
+
+ this.sources.forEach( source => {
+ bundle.addSource({
+ filename: source.filename,
+ content: source.content.clone(),
+ separator: source.separator
+ });
+ });
+
+ return bundle;
+ },
+
+ generateMap ( options ) {
+ options = options || {};
+
+ let offsets = {};
+
+ let names = [];
+ this.sources.forEach( source => {
+ Object.keys( source.content.storedNames ).forEach( name => {
+ if ( !~names.indexOf( name ) ) names.push( name );
+ });
+ });
+
+ const encoded = (
+ getSemis( this.intro ) +
+ this.sources.map( ( source, i ) => {
+ const prefix = ( i > 0 ) ? ( getSemis( source.separator ) || ',' ) : '';
+ let mappings;
+
+ // we don't bother encoding sources without a filename
+ if ( !source.filename ) {
+ mappings = getSemis( source.content.toString() );
+ } else {
+ const sourceIndex = this.uniqueSourceIndexByFilename[ source.filename ];
+ mappings = source.content.getMappings( options.hires, 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 = {};
+
+ this.sources.forEach( source => {
+ const indentStr = source.content.indentStr;
+
+ if ( indentStr === null ) return;
+
+ if ( !indentStringCounts[ indentStr ] ) indentStringCounts[ indentStr ] = 0;
+ indentStringCounts[ indentStr ] += 1;
+ });
+
+ return ( Object.keys( indentStringCounts ).sort( ( a, b ) => {
+ return indentStringCounts[a] - indentStringCounts[b];
+ })[0] ) || '\t';
+ },
+
+ indent ( indentStr ) {
+ if ( !arguments.length ) {
+ indentStr = this.getIndentString();
+ }
+
+ if ( indentStr === '' ) return this; // noop
+
+ let trailingNewline = !this.intro || this.intro.slice( -1 ) === '\n';
+
+ this.sources.forEach( ( source, i ) => {
+ const separator = source.separator !== undefined ? source.separator : this.separator;
+ const indentStart = trailingNewline || ( i > 0 && /\r?\n$/.test( separator ) );
+
+ source.content.indent( indentStr, {
+ exclude: source.indentExclusionRanges,
+ indentStart//: trailingNewline || /\r?\n$/.test( separator ) //true///\r?\n/.test( separator )
+ });
+
+ // TODO this is a very slow way to determine this
+ trailingNewline = source.content.toString().slice( 0, -1 ) === '\n';
+ });
+
+ if ( this.intro ) {
+ this.intro = indentStr + this.intro.replace( /^[^\n]/gm, ( match, index ) => {
+ return index > 0 ? indentStr + match : match;
+ });
+ }
+
+ return this;
+ },
+
+ prepend ( str ) {
+ this.intro = str + this.intro;
+ return this;
+ },
+
+ 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();
+
+ return str;
+ }).join( '' );
+
+ return this.intro + body;
+ },
+
+ trimLines () {
+ return this.trim('[\\r\\n]');
+ },
+
+ trim ( charType ) {
+ return this.trimStart( charType ).trimEnd( charType );
+ },
+
+ trimStart ( charType ) {
+ const rx = new RegExp( '^' + ( charType || '\\s' ) + '+' );
+ this.intro = this.intro.replace( rx, '' );
+
+ if ( !this.intro ) {
+ let source;
+ let i = 0;
+
+ do {
+ source = this.sources[i];
+
+ if ( !source ) {
+ break;
+ }
+
+ source.content.trimStart( charType );
+ i += 1;
+ } while ( source.content.toString() === '' ); // TODO faster way to determine non-empty source?
+ }
+
+ return this;
+ },
+
+ trimEnd ( charType ) {
+ const rx = new RegExp( ( charType || '\\s' ) + '+$' );
+
+ let source;
+ let i = this.sources.length - 1;
+
+ do {
+ source = this.sources[i];
+
+ if ( !source ) {
+ this.intro = this.intro.replace( rx, '' );
+ break;
+ }
+
+ source.content.trimEnd( charType );
+ i -= 1;
+ } while ( source.content.toString() === '' ); // TODO faster way to determine non-empty source?
+
+ return this;
+ }
+};
diff --git a/src/Chunk.js b/src/Chunk.js
new file mode 100644
index 0000000..dfe0152
--- /dev/null
+++ b/src/Chunk.js
@@ -0,0 +1,146 @@
+export default function Chunk ( start, end, content ) {
+ this.start = start;
+ this.end = end;
+ this.original = content;
+
+ this.intro = '';
+ this.outro = '';
+
+ this.content = content;
+ this.storeName = false;
+ this.edited = false;
+
+ // we make these non-enumerable, for sanity while debugging
+ Object.defineProperties( this, {
+ previous: { writable: true, value: null },
+ next: { writable: true, value: null }
+ });
+}
+
+Chunk.prototype = {
+ append ( content ) {
+ this.outro += content;
+ },
+
+ clone () {
+ const chunk = new Chunk( this.start, this.end, this.original );
+
+ chunk.intro = this.intro;
+ chunk.outro = this.outro;
+ chunk.content = this.content;
+ chunk.storeName = this.storeName;
+ chunk.edited = this.edited;
+
+ return chunk;
+ },
+
+ contains ( index ) {
+ return this.start < index && index < this.end;
+ },
+
+ eachNext ( fn ) {
+ let chunk = this;
+ while ( chunk ) {
+ fn( chunk );
+ chunk = chunk.next;
+ }
+ },
+
+ eachPrevious ( fn ) {
+ let chunk = this;
+ while ( chunk ) {
+ fn( chunk );
+ chunk = chunk.previous;
+ }
+ },
+
+ edit ( content, storeName ) {
+ this.content = content;
+ this.intro = '';
+ this.outro = '';
+ this.storeName = storeName;
+
+ this.edited = true;
+
+ return this;
+ },
+
+ prepend ( content ) {
+ this.intro = content + this.intro;
+ },
+
+ split ( index ) {
+ const sliceIndex = index - this.start;
+
+ const originalBefore = this.original.slice( 0, sliceIndex );
+ const originalAfter = this.original.slice( sliceIndex );
+
+ this.original = originalBefore;
+
+ const newChunk = new Chunk( index, this.end, originalAfter );
+ newChunk.outro = this.outro;
+ this.outro = '';
+
+ this.end = index;
+
+ if ( this.edited ) {
+ // TODO is this block necessary?...
+ newChunk.edit( '', false );
+ this.content = '';
+ } else {
+ this.content = originalBefore;
+ }
+
+ newChunk.next = this.next;
+ if ( newChunk.next ) newChunk.next.previous = newChunk;
+ newChunk.previous = this;
+ this.next = newChunk;
+
+ return newChunk;
+ },
+
+ toString () {
+ return this.intro + this.content + this.outro;
+ },
+
+ trimEnd ( rx ) {
+ this.outro = this.outro.replace( rx, '' );
+ if ( this.outro.length ) return true;
+
+ const trimmed = this.content.replace( rx, '' );
+
+ if ( trimmed.length ) {
+ if ( trimmed !== this.content ) {
+ this.split( this.start + trimmed.length ).edit( '', false );
+ }
+
+ return true;
+ } else {
+ this.edit( '', false );
+
+ this.intro = this.intro.replace( rx, '' );
+ if ( this.intro.length ) return true;
+ }
+ },
+
+ trimStart ( rx ) {
+ this.intro = this.intro.replace( rx, '' );
+ if ( this.intro.length ) return true;
+
+ const trimmed = this.content.replace( rx, '' );
+
+ if ( trimmed.length ) {
+ if ( trimmed !== this.content ) {
+ this.split( this.end - trimmed.length );
+ this.edit( '', false );
+ }
+
+ return true;
+ } else {
+ this.edit( '', false );
+
+ this.outro = this.outro.replace( rx, '' );
+ if ( this.outro.length ) return true;
+ }
+ }
+};
diff --git a/src/MagicString.js b/src/MagicString.js
new file mode 100644
index 0000000..f1756a0
--- /dev/null
+++ b/src/MagicString.js
@@ -0,0 +1,519 @@
+import Chunk from './Chunk.js';
+import SourceMap from './utils/SourceMap.js';
+import guessIndent from './utils/guessIndent.js';
+import encodeMappings from './utils/encodeMappings.js';
+import getRelativePath from './utils/getRelativePath.js';
+import isObject from './utils/isObject.js';
+import getLocator from './utils/getLocator.js';
+import Stats from './utils/Stats.js';
+
+export default function MagicString ( string, options = {} ) {
+ const chunk = new Chunk( 0, string.length, string );
+
+ Object.defineProperties( this, {
+ original: { writable: true, value: string },
+ outro: { writable: true, value: '' },
+ intro: { writable: true, value: '' },
+ firstChunk: { writable: true, value: chunk },
+ lastChunk: { writable: true, value: chunk },
+ lastSearchedChunk: { writable: true, value: chunk },
+ byStart: { writable: true, value: {} },
+ byEnd: { writable: true, value: {} },
+ filename: { writable: true, value: options.filename },
+ indentExclusionRanges: { writable: true, value: options.indentExclusionRanges },
+ sourcemapLocations: { writable: true, value: {} },
+ storedNames: { writable: true, value: {} },
+ indentStr: { writable: true, value: guessIndent( string ) }
+ });
+
+ if ( DEBUG ) {
+ Object.defineProperty( this, 'stats', { value: new Stats() });
+ }
+
+ this.byStart[ 0 ] = chunk;
+ this.byEnd[ string.length ] = chunk;
+}
+
+MagicString.prototype = {
+ addSourcemapLocation ( char ) {
+ this.sourcemapLocations[ char ] = true;
+ },
+
+ append ( content ) {
+ if ( typeof content !== 'string' ) throw new TypeError( 'outro content must be a string' );
+
+ this.outro += content;
+ return this;
+ },
+
+ clone () {
+ let cloned = new MagicString( this.original, { filename: this.filename });
+
+ let originalChunk = this.firstChunk;
+ let clonedChunk = cloned.firstChunk = cloned.lastSearchedChunk = originalChunk.clone();
+
+ while ( originalChunk ) {
+ cloned.byStart[ clonedChunk.start ] = clonedChunk;
+ cloned.byEnd[ clonedChunk.end ] = clonedChunk;
+
+ const nextOriginalChunk = originalChunk.next;
+ const nextClonedChunk = nextOriginalChunk && nextOriginalChunk.clone();
+
+ if ( nextClonedChunk ) {
+ clonedChunk.next = nextClonedChunk;
+ nextClonedChunk.previous = clonedChunk;
+
+ clonedChunk = nextClonedChunk;
+ }
+
+ originalChunk = nextOriginalChunk;
+ }
+
+ cloned.lastChunk = clonedChunk;
+
+ if ( this.indentExclusionRanges ) {
+ cloned.indentExclusionRanges = typeof this.indentExclusionRanges[0] === 'number' ?
+ [ this.indentExclusionRanges[0], this.indentExclusionRanges[1] ] :
+ this.indentExclusionRanges.map( range => [ range.start, range.end ] );
+ }
+
+ Object.keys( this.sourcemapLocations ).forEach( loc => {
+ cloned.sourcemapLocations[ loc ] = true;
+ });
+
+ return cloned;
+ },
+
+ generateMap ( options ) {
+ options = options || {};
+
+ const names = Object.keys( this.storedNames );
+
+ if ( DEBUG ) this.stats.time( 'generateMap' );
+ const map = new SourceMap({
+ file: ( options.file ? options.file.split( /[\/\\]/ ).pop() : null ),
+ sources: [ options.source ? getRelativePath( options.file || '', options.source ) : null ],
+ sourcesContent: options.includeContent ? [ this.original ] : [ null ],
+ names,
+ mappings: this.getMappings( options.hires, 0, {}, names )
+ });
+ if ( DEBUG ) this.stats.timeEnd( 'generateMap' );
+
+ return map;
+ },
+
+ getIndentString () {
+ 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 );
+ },
+
+ indent ( indentStr, options ) {
+ const pattern = /^[^\r\n]/gm;
+
+ if ( isObject( indentStr ) ) {
+ options = indentStr;
+ indentStr = undefined;
+ }
+
+ indentStr = indentStr !== undefined ? indentStr : ( this.indentStr || '\t' );
+
+ if ( indentStr === '' ) return this; // noop
+
+ options = options || {};
+
+ // Process exclusion ranges
+ let isExcluded = {};
+
+ if ( options.exclude ) {
+ let 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;
+ }
+ });
+ }
+
+ let shouldIndentNextCharacter = options.indentStart !== false;
+ const replacer = match => {
+ if ( shouldIndentNextCharacter ) return `${indentStr}${match}`;
+ shouldIndentNextCharacter = true;
+ return match;
+ };
+
+ this.intro = this.intro.replace( pattern, replacer );
+
+ let charIndex = 0;
+
+ let chunk = this.firstChunk;
+
+ while ( chunk ) {
+ const end = chunk.end;
+
+ if ( chunk.edited ) {
+ if ( !isExcluded[ charIndex ] ) {
+ chunk.content = chunk.content.replace( pattern, replacer );
+
+ if ( chunk.content.length ) {
+ shouldIndentNextCharacter = chunk.content[ chunk.content.length - 1 ] === '\n';
+ }
+ }
+ } else {
+ charIndex = chunk.start;
+
+ while ( charIndex < end ) {
+ if ( !isExcluded[ charIndex ] ) {
+ const char = this.original[ charIndex ];
+
+ if ( char === '\n' ) {
+ shouldIndentNextCharacter = true;
+ } else if ( char !== '\r' && shouldIndentNextCharacter ) {
+ shouldIndentNextCharacter = false;
+
+ if ( charIndex === chunk.start ) {
+ chunk.prepend( indentStr );
+ } else {
+ const rhs = chunk.split( charIndex );
+ rhs.prepend( indentStr );
+
+ this.byStart[ charIndex ] = rhs;
+ this.byEnd[ charIndex ] = chunk;
+
+ chunk = rhs;
+ }
+ }
+ }
+
+ charIndex += 1;
+ }
+ }
+
+ charIndex = chunk.end;
+ chunk = chunk.next;
+ }
+
+ this.outro = this.outro.replace( pattern, replacer );
+
+ return this;
+ },
+
+ insert () {
+ throw new Error( 'magicString.insert(...) is deprecated. Use insertRight(...) or insertLeft(...)' );
+ },
+
+ 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 ( DEBUG ) this.stats.timeEnd( 'insertLeft' );
+ return this;
+ },
+
+ 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 ( DEBUG ) this.stats.timeEnd( 'insertRight' );
+ return this;
+ },
+
+ move ( start, end, index ) {
+ if ( index >= start && index <= end ) throw new Error( 'Cannot move a selection inside itself' );
+
+ if ( DEBUG ) this.stats.time( 'move' );
+
+ this._split( start );
+ this._split( end );
+ this._split( index );
+
+ const first = this.byStart[ start ];
+ const last = this.byEnd[ end ];
+
+ const oldLeft = first.previous;
+ const oldRight = last.next;
+
+ const newRight = this.byStart[ index ];
+ if ( !newRight && last === this.lastChunk ) return this;
+ const newLeft = newRight ? newRight.previous : this.lastChunk;
+
+ if ( oldLeft ) oldLeft.next = oldRight;
+ if ( oldRight ) oldRight.previous = oldLeft;
+
+ if ( newLeft ) newLeft.next = first;
+ if ( newRight ) newRight.previous = last;
+
+ if ( !first.previous ) this.firstChunk = last.next;
+ if ( !last.next ) {
+ this.lastChunk = first.previous;
+ this.lastChunk.next = null;
+ }
+
+ first.previous = newLeft;
+ last.next = newRight;
+
+ if ( !newLeft ) this.firstChunk = first;
+ if ( !newRight ) this.lastChunk = last;
+
+ if ( DEBUG ) this.stats.timeEnd( 'move' );
+ return this;
+ },
+
+ overwrite ( start, end, content, storeName ) {
+ if ( typeof content !== 'string' ) throw new TypeError( 'replacement content must be a string' );
+
+ while ( start < 0 ) start += this.original.length;
+ while ( end < 0 ) end += this.original.length;
+
+ if ( end > this.original.length ) throw new Error( 'end is out of bounds' );
+ if ( start === end ) throw new Error( 'Cannot overwrite a zero-length range – use insertLeft or insertRight instead' );
+
+ if ( DEBUG ) this.stats.time( 'overwrite' );
+
+ this._split( start );
+ this._split( end );
+
+ if ( storeName ) {
+ const original = this.original.slice( start, end );
+ this.storedNames[ original ] = true;
+ }
+
+ const first = this.byStart[ start ];
+ const last = this.byEnd[ end ];
+
+ 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 );
+ }
+ }
+
+ else {
+ // must be inserting at the end
+ const newChunk = new Chunk( start, end, '' ).edit( content, storeName );
+
+ // TODO last chunk in the array may not be the last chunk, if it's moved...
+ last.next = newChunk;
+ newChunk.previous = last;
+ }
+
+ if ( DEBUG ) this.stats.timeEnd( 'overwrite' );
+ return this;
+ },
+
+ prepend ( content ) {
+ if ( typeof content !== 'string' ) throw new TypeError( 'outro content must be a string' );
+
+ this.intro = content + this.intro;
+ return this;
+ },
+
+ remove ( start, end ) {
+ while ( start < 0 ) start += this.original.length;
+ while ( end < 0 ) end += this.original.length;
+
+ if ( start === end ) return this;
+
+ if ( start < 0 || end > this.original.length ) throw new Error( 'Character is out of bounds' );
+ if ( start > end ) throw new Error( 'end must be greater than start' );
+
+ return this.overwrite( start, end, '', false );
+ },
+
+ slice ( start = 0, end = this.original.length ) {
+ while ( start < 0 ) start += this.original.length;
+ while ( end < 0 ) end += this.original.length;
+
+ let result = '';
+
+ // find start chunk
+ let chunk = this.firstChunk;
+ while ( chunk && ( chunk.start > start || chunk.end <= start ) ) {
+
+ // found end chunk before start
+ if ( chunk.start < end && chunk.end >= end ) {
+ return result;
+ }
+
+ chunk = chunk.next;
+ }
+
+ if ( chunk && chunk.edited && chunk.start !== start ) throw new Error(`Cannot use replaced character ${start} as slice start anchor.`);
+
+ let startChunk = chunk;
+ while ( chunk ) {
+ if ( chunk.intro && ( startChunk !== chunk || chunk.start === start ) ) {
+ result += chunk.intro;
+ }
+
+ const containsEnd = chunk.start < end && chunk.end >= end;
+ if ( containsEnd && chunk.edited && chunk.end !== end ) throw new Error(`Cannot use replaced character ${end} as slice end anchor.`);
+
+ const sliceStart = startChunk === chunk ? start - chunk.start : 0;
+ const sliceEnd = containsEnd ? chunk.content.length + end - chunk.end : chunk.content.length;
+
+ result += chunk.content.slice( sliceStart, sliceEnd );
+
+ if ( chunk.outro && ( !containsEnd || chunk.end === end ) ) {
+ result += chunk.outro;
+ }
+
+ if ( containsEnd ) {
+ break;
+ }
+
+ chunk = chunk.next;
+ }
+
+ return result;
+ },
+
+ // TODO deprecate this? not really very useful
+ snip ( start, end ) {
+ const clone = this.clone();
+ clone.remove( 0, start );
+ clone.remove( end, clone.original.length );
+
+ return clone;
+ },
+
+ _split ( index ) {
+ if ( this.byStart[ index ] || this.byEnd[ index ] ) return;
+
+ if ( DEBUG ) this.stats.time( '_split' );
+
+ let chunk = this.lastSearchedChunk;
+ const searchForward = index > chunk.end;
+
+ while ( true ) {
+ if ( chunk.contains( index ) ) return this._splitChunk( chunk, index );
+
+ chunk = searchForward ?
+ this.byStart[ chunk.end ] :
+ this.byEnd[ chunk.start ];
+ }
+ },
+
+ _splitChunk ( chunk, index ) {
+ if ( chunk.edited && chunk.content.length ) { // zero-length edited chunks are a special case (overlapping replacements)
+ const loc = getLocator( this.original )( index );
+ throw new Error( `Cannot split a chunk that has already been edited (${loc.line}:${loc.column} – "${chunk.original}")` );
+ }
+
+ const newChunk = chunk.split( index );
+
+ this.byEnd[ index ] = chunk;
+ this.byStart[ index ] = newChunk;
+ this.byEnd[ newChunk.end ] = newChunk;
+
+ if ( chunk === this.lastChunk ) this.lastChunk = newChunk;
+
+ this.lastSearchedChunk = chunk;
+ if ( DEBUG ) this.stats.timeEnd( '_split' );
+ return true;
+ },
+
+ toString () {
+ let str = this.intro;
+
+ let chunk = this.firstChunk;
+ while ( chunk ) {
+ str += chunk.toString();
+ chunk = chunk.next;
+ }
+
+ return str + this.outro;
+ },
+
+ trimLines () {
+ return this.trim('[\\r\\n]');
+ },
+
+ trim ( charType ) {
+ return this.trimStart( charType ).trimEnd( charType );
+ },
+
+ trimEnd ( charType ) {
+ const rx = new RegExp( ( charType || '\\s' ) + '+$' );
+
+ this.outro = this.outro.replace( rx, '' );
+ if ( this.outro.length ) return this;
+
+ let chunk = this.lastChunk;
+
+ do {
+ const end = chunk.end;
+ const aborted = chunk.trimEnd( rx );
+
+ // if chunk was trimmed, we have a new lastChunk
+ if ( chunk.end !== end ) {
+ this.lastChunk = chunk.next;
+
+ this.byEnd[ chunk.end ] = chunk;
+ this.byStart[ chunk.next.start ] = chunk.next;
+ }
+
+ if ( aborted ) return this;
+ chunk = chunk.previous;
+ } while ( chunk );
+
+ return this;
+ },
+
+ trimStart ( charType ) {
+ const rx = new RegExp( '^' + ( charType || '\\s' ) + '+' );
+
+ this.intro = this.intro.replace( rx, '' );
+ if ( this.intro.length ) return this;
+
+ let chunk = this.firstChunk;
+
+ do {
+ const end = chunk.end;
+ const aborted = chunk.trimStart( rx );
+
+ if ( chunk.end !== end ) {
+ // special case...
+ if ( chunk === this.lastChunk ) this.lastChunk = chunk.next;
+
+ this.byEnd[ chunk.end ] = chunk;
+ this.byStart[ chunk.next.start ] = chunk.next;
+ }
+
+ if ( aborted ) return this;
+ chunk = chunk.next;
+ } while ( chunk );
+
+ return this;
+ }
+};
diff --git a/src/index-legacy.js b/src/index-legacy.js
new file mode 100644
index 0000000..16d6f4d
--- /dev/null
+++ b/src/index-legacy.js
@@ -0,0 +1,6 @@
+import MagicString from './MagicString.js';
+import Bundle from './Bundle.js';
+
+MagicString.Bundle = Bundle;
+
+export default MagicString;
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..594d8db
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,4 @@
+import MagicString from './MagicString.js';
+
+export default MagicString;
+export { default as Bundle } from './Bundle.js';
diff --git a/src/utils/SourceMap.js b/src/utils/SourceMap.js
new file mode 100644
index 0000000..f3dde82
--- /dev/null
+++ b/src/utils/SourceMap.js
@@ -0,0 +1,21 @@
+import btoa from './btoa.js';
+
+export default function SourceMap ( properties ) {
+ this.version = 3;
+
+ this.file = properties.file;
+ this.sources = properties.sources;
+ this.sourcesContent = properties.sourcesContent;
+ this.names = properties.names;
+ this.mappings = properties.mappings;
+}
+
+SourceMap.prototype = {
+ toString () {
+ return JSON.stringify( this );
+ },
+
+ toUrl () {
+ return 'data:application/json;charset=utf-8;base64,' + btoa( this.toString() );
+ }
+};
diff --git a/src/utils/Stats.js b/src/utils/Stats.js
new file mode 100644
index 0000000..46d4c0c
--- /dev/null
+++ b/src/utils/Stats.js
@@ -0,0 +1,18 @@
+export default class Stats {
+ constructor () {
+ Object.defineProperties( this, {
+ startTimes: { value: {} }
+ });
+ }
+
+ time ( label ) {
+ this.startTimes[ label ] = process.hrtime();
+ }
+
+ timeEnd ( label ) {
+ const elapsed = process.hrtime( this.startTimes[ label ] );
+
+ if ( !this[ label ] ) this[ label ] = 0;
+ this[ label ] += elapsed[0] * 1e3 + elapsed[1] * 1e-6;
+ }
+}
diff --git a/src/utils/btoa.js b/src/utils/btoa.js
new file mode 100644
index 0000000..889bf81
--- /dev/null
+++ b/src/utils/btoa.js
@@ -0,0 +1,13 @@
+let _btoa;
+
+if ( typeof window !== 'undefined' && typeof window.btoa === 'function' ) {
+ _btoa = window.btoa;
+} else if ( typeof Buffer === 'function' ) {
+ _btoa = str => new Buffer( str ).toString( 'base64' );
+} else {
+ _btoa = () => {
+ throw new Error( 'Unsupported environment: `window.btoa` or `Buffer` should be supported.' );
+ };
+}
+
+export default _btoa;
diff --git a/src/utils/encodeMappings.js b/src/utils/encodeMappings.js
new file mode 100644
index 0000000..b96efca
--- /dev/null
+++ b/src/utils/encodeMappings.js
@@ -0,0 +1,137 @@
+import { encode } from 'vlq';
+import getSemis from './getSemis.js';
+import getLocator from './getLocator.js';
+
+const nonWhitespace = /\S/;
+
+export default function encodeMappings ( original, intro, outro, chunk, hires, sourcemapLocations, sourceIndex, offsets, names ) {
+ let rawLines = [];
+
+ let generatedCodeLine = intro.split( '\n' ).length - 1;
+ let rawSegments = rawLines[ generatedCodeLine ] = [];
+
+ let generatedCodeColumn = 0;
+
+ const locate = getLocator( original );
+
+ function addEdit ( content, original, loc, nameIndex, i ) {
+ if ( i || ( content.length && nonWhitespace.test( content ) ) ) {
+ rawSegments.push({
+ generatedCodeLine,
+ generatedCodeColumn,
+ sourceCodeLine: loc.line,
+ sourceCodeColumn: loc.column,
+ sourceCodeName: nameIndex,
+ sourceIndex
+ });
+ }
+
+ let lines = content.split( '\n' );
+ let lastLine = lines.pop();
+
+ if ( lines.length ) {
+ generatedCodeLine += lines.length;
+ rawLines[ generatedCodeLine ] = rawSegments = [];
+ generatedCodeColumn = lastLine.length;
+ } else {
+ generatedCodeColumn += lastLine.length;
+ }
+
+ lines = original.split( '\n' );
+ lastLine = lines.pop();
+
+ if ( lines.length ) {
+ loc.line += lines.length;
+ loc.column = lastLine.length;
+ } else {
+ loc.column += lastLine.length;
+ }
+ }
+
+ function addUneditedChunk ( chunk, loc ) {
+ let originalCharIndex = chunk.start;
+ let first = true;
+
+ while ( originalCharIndex < chunk.end ) {
+ if ( hires || first || sourcemapLocations[ originalCharIndex ] ) {
+ rawSegments.push({
+ generatedCodeLine,
+ generatedCodeColumn,
+ sourceCodeLine: loc.line,
+ sourceCodeColumn: loc.column,
+ sourceCodeName: -1,
+ sourceIndex
+ });
+ }
+
+ if ( original[ originalCharIndex ] === '\n' ) {
+ loc.line += 1;
+ loc.column = 0;
+ generatedCodeLine += 1;
+ rawLines[ generatedCodeLine ] = rawSegments = [];
+ generatedCodeColumn = 0;
+ } else {
+ loc.column += 1;
+ generatedCodeColumn += 1;
+ }
+
+ originalCharIndex += 1;
+ first = false;
+ }
+ }
+
+ let hasContent = false;
+
+ while ( chunk ) {
+ let loc = locate( chunk.start );
+
+ if ( chunk.intro.length ) {
+ addEdit( chunk.intro, '', loc, -1, hasContent );
+ }
+
+ if ( chunk.edited ) {
+ addEdit( chunk.content, chunk.original, loc, chunk.storeName ? names.indexOf( chunk.original ) : -1, hasContent );
+ } else {
+ addUneditedChunk( chunk, loc );
+ }
+
+ if ( chunk.outro.length ) {
+ addEdit( chunk.outro, '', loc, -1, hasContent );
+ }
+
+ if ( chunk.content || chunk.intro || chunk.outro ) hasContent = true;
+
+ const nextChunk = chunk.next;
+ chunk = nextChunk;
+ }
+
+ offsets.sourceIndex = offsets.sourceIndex || 0;
+ offsets.sourceCodeLine = offsets.sourceCodeLine || 0;
+ offsets.sourceCodeColumn = offsets.sourceCodeColumn || 0;
+ offsets.sourceCodeName = offsets.sourceCodeName || 0;
+
+ return rawLines.map( segments => {
+ let generatedCodeColumn = 0;
+
+ return segments.map( segment => {
+ let arr = [
+ segment.generatedCodeColumn - generatedCodeColumn,
+ segment.sourceIndex - offsets.sourceIndex,
+ segment.sourceCodeLine - offsets.sourceCodeLine,
+ segment.sourceCodeColumn - offsets.sourceCodeColumn
+ ];
+
+ generatedCodeColumn = segment.generatedCodeColumn;
+ offsets.sourceIndex = segment.sourceIndex;
+ offsets.sourceCodeLine = segment.sourceCodeLine;
+ offsets.sourceCodeColumn = segment.sourceCodeColumn;
+
+ if ( ~segment.sourceCodeName ) {
+ arr.push( segment.sourceCodeName - offsets.sourceCodeName );
+ offsets.sourceCodeName = segment.sourceCodeName;
+ }
+
+ return encode( arr );
+ }).join( ',' );
+ }).join( ';' ) + getSemis(outro);
+}
diff --git a/src/utils/getLocator.js b/src/utils/getLocator.js
new file mode 100644
index 0000000..d2ea142
--- /dev/null
+++ b/src/utils/getLocator.js
@@ -0,0 +1,35 @@
+export default function getLocator ( source ) {
+ let originalLines = source.split( '\n' );
+
+ let start = 0;
+ let lineRanges = originalLines.map( ( line, i ) => {
+ const end = start + line.length + 1;
+ const range = { start, end, line: i };
+
+ start = end;
+ return range;
+ });
+
+ let i = 0;
+
+ function rangeContains ( range, index ) {
+ return range.start <= index && index < range.end;
+ }
+
+ function getLocation ( range, index ) {
+ return { line: range.line, column: index - range.start };
+ }
+
+ return function locate ( index ) {
+ let range = lineRanges[i];
+
+ const d = index >= range.end ? 1 : -1;
+
+ while ( range ) {
+ if ( rangeContains( range, index ) ) return getLocation( range, index );
+
+ i += d;
+ range = lineRanges[i];
+ }
+ };
+}
diff --git a/src/utils/getRelativePath.js b/src/utils/getRelativePath.js
new file mode 100644
index 0000000..a05cb19
--- /dev/null
+++ b/src/utils/getRelativePath.js
@@ -0,0 +1,18 @@
+export default function getRelativePath ( from, to ) {
+ let fromParts = from.split( /[\/\\]/ );
+ let toParts = to.split( /[\/\\]/ );
+
+ fromParts.pop(); // get dirname
+
+ while ( fromParts[0] === toParts[0] ) {
+ fromParts.shift();
+ toParts.shift();
+ }
+
+ if ( fromParts.length ) {
+ let i = fromParts.length;
+ while ( i-- ) fromParts[i] = '..';
+ }
+
+ return fromParts.concat( toParts ).join( '/' );
+}
diff --git a/src/utils/getSemis.js b/src/utils/getSemis.js
new file mode 100644
index 0000000..4ead76e
--- /dev/null
+++ b/src/utils/getSemis.js
@@ -0,0 +1,3 @@
+export default function getSemis ( str ) {
+ return new Array( str.split( '\n' ).length ).join( ';' );
+}
diff --git a/src/utils/guessIndent.js b/src/utils/guessIndent.js
new file mode 100644
index 0000000..ce2e8d6
--- /dev/null
+++ b/src/utils/guessIndent.js
@@ -0,0 +1,25 @@
+export default function guessIndent ( code ) {
+ const lines = code.split( '\n' );
+
+ const tabbed = lines.filter( line => /^\t+/.test( line ) );
+ const spaced = lines.filter( line => /^ {2,}/.test( line ) );
+
+ if ( tabbed.length === 0 && spaced.length === 0 ) {
+ return null;
+ }
+
+ // More lines tabbed than spaced? Assume tabs, and
+ // default to tabs in the case of a tie (or nothing
+ // to go on)
+ if ( tabbed.length >= spaced.length ) {
+ return '\t';
+ }
+
+ // Otherwise, we need to guess the multiple
+ const min = spaced.reduce( ( previous, current ) => {
+ const numSpaces = /^ +/.exec( current )[0].length;
+ return Math.min( numSpaces, previous );
+ }, Infinity );
+
+ return new Array( min + 1 ).join( ' ' );
+}
diff --git a/src/utils/hasOwnProp.js b/src/utils/hasOwnProp.js
new file mode 100644
index 0000000..54db1c2
--- /dev/null
+++ b/src/utils/hasOwnProp.js
@@ -0,0 +1 @@
+export default Object.prototype.hasOwnProperty;
\ No newline at end of file
diff --git a/src/utils/isObject.js b/src/utils/isObject.js
new file mode 100644
index 0000000..3660761
--- /dev/null
+++ b/src/utils/isObject.js
@@ -0,0 +1,5 @@
+const toString = Object.prototype.toString;
+
+export default function isObject ( thing ) {
+ return toString.call( thing ) === '[object Object]';
+}
diff --git a/test/index.js b/test/index.js
new file mode 100644
index 0000000..5305692
--- /dev/null
+++ b/test/index.js
@@ -0,0 +1,1684 @@
+/*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' );
+ });
+ });
+});
--
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