diff --git a/src/Chunk.js b/src/Chunk.js index dfe0152..d83e036 100644 --- a/src/Chunk.js +++ b/src/Chunk.js @@ -18,10 +18,14 @@ export default function Chunk ( start, end, content ) { } Chunk.prototype = { - append ( content ) { + appendLeft ( content ) { this.outro += content; }, + appendRight ( content ) { + this.intro = this.intro + content; + }, + clone () { const chunk = new Chunk( this.start, this.end, this.original ); @@ -65,7 +69,11 @@ Chunk.prototype = { return this; }, - prepend ( content ) { + prependLeft ( content ) { + this.outro = content + this.outro; + }, + + prependRight ( content ) { this.intro = content + this.intro; }, diff --git a/src/MagicString.js b/src/MagicString.js index 0e9e32a..da357b4 100644 --- a/src/MagicString.js +++ b/src/MagicString.js @@ -7,6 +7,11 @@ import isObject from './utils/isObject.js'; import getLocator from './utils/getLocator.js'; import Stats from './utils/Stats.js'; +const warned = { + insertLeft: false, + insertRight: false +}; + export default function MagicString ( string, options = {} ) { const chunk = new Chunk( 0, string.length, string ); @@ -46,6 +51,44 @@ MagicString.prototype = { return this; }, + appendLeft ( index, content ) { + if ( typeof content !== 'string' ) throw new TypeError( 'inserted content must be a string' ); + + if ( DEBUG ) this.stats.time( 'insertLeft' ); + + this._split( index ); + + const chunk = this.byEnd[ index ]; + + if ( chunk ) { + chunk.appendLeft( content ); + } else { + this.intro += content; + } + + if ( DEBUG ) this.stats.timeEnd( 'insertLeft' ); + return this; + }, + + appendRight ( index, content ) { + if ( typeof content !== 'string' ) throw new TypeError( 'inserted content must be a string' ); + + if ( DEBUG ) this.stats.time( 'insertLeft' ); + + this._split( index ); + + const chunk = this.byStart[ index ]; + + if ( chunk ) { + chunk.appendRight( content ); + } else { + this.outro += content; + } + + if ( DEBUG ) this.stats.timeEnd( 'insertLeft' ); + return this; + }, + clone () { const cloned = new MagicString( this.original, { filename: this.filename }); @@ -173,10 +216,10 @@ MagicString.prototype = { shouldIndentNextCharacter = false; if ( charIndex === chunk.start ) { - chunk.prepend( indentStr ); + chunk.prependRight( indentStr ); } else { const rhs = chunk.split( charIndex ); - rhs.prepend( indentStr ); + rhs.prependRight( indentStr ); this.byStart[ charIndex ] = rhs; this.byEnd[ charIndex ] = chunk; @@ -204,41 +247,21 @@ MagicString.prototype = { }, insertLeft ( index, content ) { - if ( typeof content !== 'string' ) throw new TypeError( 'inserted content must be a string' ); - - if ( DEBUG ) this.stats.time( 'insertLeft' ); - - this._split( index ); - - const chunk = this.byEnd[ index ]; - - if ( chunk ) { - chunk.append( content ); - } else { - this.intro += content; + if ( !warned.insertLeft ) { + console.warn( 'magicString.insertLeft(...) is deprecated. Use magicString.appendLeft(...) instead' ); // eslint-disable-line no-console + warned.insertLeft = true; } - if ( DEBUG ) this.stats.timeEnd( 'insertLeft' ); - return this; + return this.appendLeft( index, content ); }, insertRight ( index, content ) { - if ( typeof content !== 'string' ) throw new TypeError( 'inserted content must be a string' ); - - if ( DEBUG ) this.stats.time( 'insertRight' ); - - this._split( index ); - - const chunk = this.byStart[ index ]; - - if ( chunk ) { - chunk.prepend( content ); - } else { - this.outro += content; + if ( !warned.insertRight ) { + console.warn( 'magicString.insertRight(...) is deprecated. Use magicString.prependRight(...) instead' ); // eslint-disable-line no-console + warned.insertRight = true; } - if ( DEBUG ) this.stats.timeEnd( 'insertRight' ); - return this; + return this.prependRight( index, content ); }, move ( start, end, index ) { @@ -338,6 +361,44 @@ MagicString.prototype = { return this; }, + prependLeft ( index, content ) { + if ( typeof content !== 'string' ) throw new TypeError( 'inserted content must be a string' ); + + if ( DEBUG ) this.stats.time( 'insertRight' ); + + this._split( index ); + + const chunk = this.byEnd[ index ]; + + if ( chunk ) { + chunk.prependLeft( content ); + } else { + this.intro = content + this.intro; + } + + if ( DEBUG ) this.stats.timeEnd( 'insertRight' ); + return this; + }, + + prependRight ( index, content ) { + if ( typeof content !== 'string' ) throw new TypeError( 'inserted content must be a string' ); + + if ( DEBUG ) this.stats.time( 'insertRight' ); + + this._split( index ); + + const chunk = this.byStart[ index ]; + + if ( chunk ) { + chunk.prependRight( content ); + } else { + this.outro = content + this.outro; + } + + if ( DEBUG ) this.stats.timeEnd( 'insertRight' ); + return this; + }, + remove ( start, end ) { while ( start < 0 ) start += this.original.length; while ( end < 0 ) end += this.original.length; diff --git a/test/MagicString.js b/test/MagicString.js index 4d424e8..d34a269 100644 --- a/test/MagicString.js +++ b/test/MagicString.js @@ -38,6 +38,78 @@ describe( 'MagicString', () => { }); }); + describe( '(ap|pre)pend(Left|Right)', () => { + it( 'preserves intended order', () => { + const s = new MagicString( '0123456789' ); + + s.insertLeft( 5, 'A' ); + s.insertRight( 5, 'a' ); + s.insertRight( 5, 'b' ); + s.insertLeft( 5, 'B' ); + s.insertLeft( 5, 'C' ); + s.insertRight( 5, 'c' ); + + assert.equal( s.toString(), '01234ABCcba56789' ); + assert.equal( s.slice(0, 5) , '01234ABC' ); + assert.equal( s.slice(5), 'cba56789' ); + + s.prependLeft( 5, '<' ); + s.prependLeft( 5, '{' ); + assert.equal( s.toString(), '01234{' ); + s.appendRight( 5, '}' ); + assert.equal( s.toString(), '01234{}56789' ); + + s.appendLeft(5, '('); // appendLeft is a synonym for insertLeft + s.appendLeft(5, '['); // appendLeft is a synonym for insertLeft + assert.equal( s.toString(), '01234{}56789' ); + + s.prependRight(5, ')'); // prependRight is a synonym for insertRight + s.prependRight(5, ']'); // prependRight is a synonym for insertRight + assert.equal( s.toString(), '01234{}56789' ); + + assert.equal( s.slice(0, 5), '01234{}56789' ); + }); + + it( 'preserves intended order at beginning of string', () => { + const s = new MagicString( 'x' ); + + s.appendLeft( 0, '1' ); + s.prependLeft( 0, '2' ); + s.appendLeft( 0, '3' ); + s.prependLeft( 0, '4' ); + + assert.equal( s.toString(), '4213x' ); + }); + + it( 'preserves intended order at end of string', () => { + const s = new MagicString( 'x' ); + + s.appendRight( 1, '1' ); + s.prependRight( 1, '2' ); + s.appendRight( 1, '3' ); + s.prependRight( 1, '4' ); + + assert.equal( s.toString(), 'x4213' ); + }); + }); + + describe( 'appendLeft', () => { + it( 'should return this', () => { + const s = new MagicString( 'abcdefghijkl' ); + assert.strictEqual( s.appendLeft( 0, 'a' ), s ); + }); + }); + + describe( 'appendRight', () => { + it( 'should return this', () => { + const s = new MagicString( 'abcdefghijkl' ); + assert.strictEqual( s.appendRight( 0, 'a' ), s ); + }); + }); + describe( 'clone', () => { it( 'should clone a magic string', () => { const s = new MagicString( 'abcdefghijkl' ); @@ -481,32 +553,6 @@ describe( 'MagicString', () => { // }); }); - describe( 'insertLeft', () => { - it( 'inserts repeatedly in correct order', () => { - const s = new MagicString( 'ab' ); - assert.equal( s.insertLeft(1, '1').toString(), 'a1b' ); - assert.equal( s.insertLeft(1, '2').toString(), 'a12b' ); - }); - - it( 'should return this', () => { - const s = new MagicString( 'abcdefghijkl' ); - assert.strictEqual( s.insertLeft( 0, 'a' ), s ); - }); - }); - - describe( 'insertRight', () => { - it( 'inserts repeatedly in correct order', () => { - const s = new MagicString( 'ab' ); - assert.equal( s.insertRight(1, '1').toString(), 'a1b' ); - assert.equal( s.insertRight(1, '2').toString(), 'a21b' ); - }); - - it( 'should return this', () => { - const s = new MagicString( 'abcdefghijkl' ); - assert.strictEqual( s.insertLeft( 0, 'a' ), s ); - }); - }); - describe( 'move', () => { it( 'moves content from the start', () => { const s = new MagicString( 'abcdefghijkl' ); @@ -750,6 +796,20 @@ describe( 'MagicString', () => { }); }); + describe( 'prependLeft', () => { + it( 'should return this', () => { + const s = new MagicString( 'abcdefghijkl' ); + assert.strictEqual( s.prependLeft( 0, 'a' ), s ); + }); + }); + + describe( 'prependRight', () => { + it( 'should return this', () => { + const s = new MagicString( 'abcdefghijkl' ); + assert.strictEqual( s.prependRight( 0, 'a' ), s ); + }); + }); + describe( 'remove', () => { it( 'should remove characters from the original string', () => { const s = new MagicString( 'abcdefghijkl' );