From e631fb61bb616df50ecef6d1961ea170b9003faa Mon Sep 17 00:00:00 2001 From: Wahid Rizka Fathurrohman Date: Fri, 11 Sep 2026 14:19:04 +0700 Subject: [PATCH] fix: carry trimStart and trimEnd into the outro and intro trimStart() trimmed the intro and then each chunk, but stopped after the last chunk without looking at the outro, and trimEnd() never reached the intro. When everything before the outro (or after the intro) was blank, the string kept its leading or trailing whitespace, and a Bundle, which uses the result to decide whether to keep going, went on to trim the next separator and source as well. Both now carry on into the outro or intro, as Chunk#trimStart and Chunk#trimEnd already do for a chunk. --- src/MagicString.ts | 10 ++++++++-- test/Bundle.test.ts | 20 ++++++++++++++++++++ test/MagicString.test.ts | 14 ++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/MagicString.ts b/src/MagicString.ts index be55b24c..bbbfbaa4 100644 --- a/src/MagicString.ts +++ b/src/MagicString.ts @@ -1248,7 +1248,10 @@ export default class MagicString { chunk = chunk.previous } while (chunk) - return false + // like Chunk#trimEnd, carry on into the intro once everything after it has + // been trimmed away, since the intro is then the end of the string + this.intro = this.intro.replace(rx, '') + return this.intro.length > 0 } /** @@ -1288,7 +1291,10 @@ export default class MagicString { chunk = chunk.next } while (chunk) - return false + // like Chunk#trimStart, carry on into the outro once everything before it + // has been trimmed away, since the outro is then the start of the string + this.outro = this.outro.replace(rx, '') + return this.outro.length > 0 } /** diff --git a/test/Bundle.test.ts b/test/Bundle.test.ts index bcdd3a80..9ffee904 100644 --- a/test/Bundle.test.ts +++ b/test/Bundle.test.ts @@ -1124,6 +1124,26 @@ describe('bundle', () => { b.trimEnd() assert.equal(b.toString(), 'abc ;') }) + + it('should stop trimStart at a blank source that still has appended content', () => { + const b = new Bundle() + + b.addSource({ content: new MagicString(' ').append('X') }) + b.addSource({ content: new MagicString(' Y') }) + + b.trimStart() + assert.equal(b.toString(), 'X\n Y') + }) + + it('should stop trimEnd at a blank source that still has prepended content', () => { + const b = new Bundle() + + b.addSource({ content: new MagicString('Y ') }) + b.addSource({ content: new MagicString(' ').prepend('X') }) + + b.trimEnd() + assert.equal(b.toString(), 'Y \nX') + }) }) describe('toString', () => { diff --git a/test/MagicString.test.ts b/test/MagicString.test.ts index 4395f6b3..3a7d791b 100644 --- a/test/MagicString.test.ts +++ b/test/MagicString.test.ts @@ -2378,6 +2378,20 @@ describe('magicString', () => { assert.equal(s.toString(), 'x abc') }) + it('should carry trimStart into the global outro when nothing comes before it', () => { + const s = new MagicString(' ') + s.append(' x ') + s.trimStart() + assert.equal(s.toString(), 'x ') + }) + + it('should carry trimEnd into the global intro when nothing comes after it', () => { + const s = new MagicString(' ') + s.prepend(' x ') + s.trimEnd() + assert.equal(s.toString(), ' x') + }) + it('should trim original content', () => { assert.equal(new MagicString(' abcdefghijkl ').trim().toString(), 'abcdefghijkl') assert.equal(new MagicString(' abcdefghijkl').trim().toString(), 'abcdefghijkl')