This commit is contained in:
chrosey
2017-09-13 07:52:34 +02:00
parent a1f16c37f4
commit 2340b0226b
24621 changed files with 2912161 additions and 149 deletions
+256
View File
@@ -0,0 +1,256 @@
# changelog
## 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
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+124
View File
@@ -0,0 +1,124 @@
{
"_args": [
[
{
"raw": "magic-string@^0.14.0",
"scope": null,
"escapedName": "magic-string",
"name": "magic-string",
"rawSpec": "^0.14.0",
"spec": ">=0.14.0 <0.15.0",
"type": "range"
},
"c:\\xampp\\htdocs\\laravel\\node_modules\\buble"
]
],
"_from": "magic-string@>=0.14.0 <0.15.0",
"_id": "magic-string@0.14.0",
"_inCache": true,
"_location": "/magic-string",
"_nodeVersion": "6.0.0",
"_npmOperationalInternal": {
"host": "packages-12-west.internal.npmjs.com",
"tmp": "tmp/magic-string-0.14.0.tgz_1463368287313_0.27023717132396996"
},
"_npmUser": {
"name": "rich_harris",
"email": "richard.a.harris@gmail.com"
},
"_npmVersion": "3.8.6",
"_phantomChildren": {},
"_requested": {
"raw": "magic-string@^0.14.0",
"scope": null,
"escapedName": "magic-string",
"name": "magic-string",
"rawSpec": "^0.14.0",
"spec": ">=0.14.0 <0.15.0",
"type": "range"
},
"_requiredBy": [
"/buble"
],
"_resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.14.0.tgz",
"_shasum": "57224aef1701caeed273b17a39a956e72b172462",
"_shrinkwrap": null,
"_spec": "magic-string@^0.14.0",
"_where": "c:\\xampp\\htdocs\\laravel\\node_modules\\buble",
"author": {
"name": "Rich Harris"
},
"bugs": {
"url": "https://github.com/rich-harris/magic-string/issues"
},
"dependencies": {
"vlq": "^0.2.1"
},
"description": "Modify strings, generate sourcemaps",
"devDependencies": {
"codecov.io": "^0.1.6",
"console-group": "^0.2.0",
"es6-promise": "^3.0.2",
"eslint": "^2.0.0",
"istanbul": "^0.4.1",
"mocha": "^2.3.4",
"remap-istanbul": "^0.6.1",
"resolve": "^1.1.6",
"rollup": "^0.26.0",
"rollup-plugin-buble": "^0.7.0",
"rollup-plugin-node-resolve": "^1.5.0",
"rollup-plugin-replace": "^1.1.0",
"source-map": "^0.5.3",
"source-map-support": "^0.4.0"
},
"directories": {},
"dist": {
"shasum": "57224aef1701caeed273b17a39a956e72b172462",
"tarball": "https://registry.npmjs.org/magic-string/-/magic-string-0.14.0.tgz"
},
"files": [
"src/*",
"dist/*",
"README.md"
],
"gitHead": "bc0f83ee126d0475aeef00ebc01d8c412a59cf1c",
"homepage": "https://github.com/rich-harris/magic-string#readme",
"jsnext:main": "dist/magic-string.es6.js",
"keywords": [
"string",
"string manipulation",
"sourcemap",
"templating",
"transpilation"
],
"license": "MIT",
"main": "dist/magic-string.cjs.js",
"maintainers": [
{
"name": "rich_harris",
"email": "richard.a.harris@gmail.com"
}
],
"name": "magic-string",
"optionalDependencies": {},
"readme": "ERROR: No README data found!",
"repository": {
"type": "git",
"url": "git+https://github.com/rich-harris/magic-string.git"
},
"scripts": {
"build": " npm run build:cjs && npm run build:es6 && npm run build:umd",
"build:cjs": "rollup -c -f cjs -o dist/magic-string.cjs.js",
"build:es6": "rollup -c -f es6 -o dist/magic-string.es6.js",
"build:umd": "rollup -c --environment DEPS -f umd -o dist/magic-string.umd.js",
"ci": "npm run test-coverage && codecov < coverage/coverage-remapped.lcov",
"lint": "eslint src",
"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",
"prepublish": "rm -rf dist && npm test && npm run build:es6 && npm run build:umd",
"pretest": "npm run build:cjs",
"pretest-coverage": "npm run build:cjs",
"test": "mocha",
"test-coverage": "rm -rf coverage/* && istanbul cover --report json node_modules/.bin/_mocha -- -u exports -R spec test/index.js"
},
"version": "0.14.0"
}
+241
View File
@@ -0,0 +1,241 @@
import MagicString from './MagicString.js';
import SourceMap from './utils/SourceMap.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 ) {
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;
}
};
function getSemis ( str ) {
return new Array( str.split( '\n' ).length ).join( ';' );
}
+144
View File
@@ -0,0 +1,144 @@
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.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;
}
}
};
+523
View File
@@ -0,0 +1,523 @@
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.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 ) {
first.outro = '';
let chunk = first.next;
while ( chunk !== last ) {
chunk.edit( '', false );
chunk.intro = chunk.outro = '';
chunk = chunk.next;
}
chunk.edit( '', false );
chunk.intro = '';
}
}
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;
}
};
+6
View File
@@ -0,0 +1,6 @@
import MagicString from './MagicString.js';
import Bundle from './Bundle.js';
MagicString.Bundle = Bundle;
export default MagicString;
+21
View File
@@ -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() );
}
};
+18
View File
@@ -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;
}
}
+13
View File
@@ -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;
+132
View File
@@ -0,0 +1,132 @@
import { encode } from 'vlq';
import getLocator from './getLocator.js';
export default function encodeMappings ( original, intro, 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 ) {
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;
}
}
while ( chunk ) {
let loc = locate( chunk.start );
if ( chunk.intro.length ) {
addEdit( chunk.intro, '', loc, -1, !!chunk.previous );
}
if ( chunk.edited ) {
addEdit( chunk.content, chunk.original, loc, chunk.storeName ? names.indexOf( chunk.original ) : -1, !!chunk.previous );
} else {
addUneditedChunk( chunk, loc );
}
if ( chunk.outro.length ) {
addEdit( chunk.outro, '', loc, -1, !!chunk.previous );
}
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;
const encoded = 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( ';' );
return encoded;
}
+35
View File
@@ -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];
}
};
}
+18
View File
@@ -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( '/' );
}
+25
View File
@@ -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( ' ' );
}
+1
View File
@@ -0,0 +1 @@
export default Object.prototype.hasOwnProperty;
+5
View File
@@ -0,0 +1,5 @@
const toString = Object.prototype.toString;
export default function isObject ( thing ) {
return toString.call( thing ) === '[object Object]';
}