Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion benchmark/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import MagicString from '../dist/index.mjs'
Benchmark.support.decompilation = false

const results = []
const total = 8
const total = 10
const isTTY = process.stdout.isTTY
let current = 0

Expand Down Expand Up @@ -126,6 +126,20 @@ async function bench() {
}
})

await runWithInstance('hasChanged (no edit)', inputs, (s) => {
s.hasChanged()
})
await runWithInstance(
'hasChanged (edit)',
inputs,
(s) => {
s.hasChanged()
},
(s) => {
s.replace(/replacement/g, 'replacement\nReplacement')
},
)

printResults()
}

Expand Down
31 changes: 30 additions & 1 deletion src/MagicString.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1153,7 +1153,36 @@ export default class MagicString {
* Indicates if the string has been changed.
*/
hasChanged(): boolean {
return this.original !== this.toString()
if (this.intro || this.outro)
return true

let originalIndex = 0
let chunk: Chunk | null = this.firstChunk

while (chunk) {
// Any intro/outro on a chunk means content was inserted
if (chunk.intro || chunk.outro)
return true

// Chunks must be in original order, otherwise content was moved
if (chunk.start !== originalIndex)
return true

// For edited chunks, check if content actually differs from original;
// compare lengths first so a slice is only allocated when they match
if (
chunk.edited
&& (chunk.content.length !== chunk.end - chunk.start
|| chunk.content !== this.original.slice(chunk.start, chunk.end))
) {
return true
}

originalIndex = chunk.end
chunk = chunk.next
}

return originalIndex !== this.original.length
}

/** @internal */
Expand Down
48 changes: 48 additions & 0 deletions test/MagicString.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1891,6 +1891,54 @@ describe('magicString', () => {

assert.ok(clone.hasChanged())
})

it('should return false when edited content is identical to the original', () => {
const s = new MagicString('abcdef')

s.overwrite(0, 6, 'abcdef')
assert.ok(!s.hasChanged())

const t = new MagicString('abcdef')

t.update(2, 4, 'cd')
assert.ok(!t.hasChanged())
})

it('should return false after reset reverts the edits', () => {
const s = new MagicString('abcdef')

s.remove(1, 3)
assert.ok(s.hasChanged())

s.reset(1, 3)
assert.ok(!s.hasChanged())
})

it('should return true when content is only moved', () => {
const s = new MagicString('abcdef')

s.move(0, 2, 6)
assert.ok(s.hasChanged())
})

it('should return true when content is inserted without changing existing characters', () => {
const s = new MagicString('abcdef')

s.appendLeft(3, '/* comment */')
assert.ok(s.hasChanged())

const t = new MagicString('abcdef')

t.append(' // end')
assert.ok(t.hasChanged())
})

it('should return true when trailing content is removed (chain no longer spans the original)', () => {
const s = new MagicString('abcdef')

s.remove(3, 6)
assert.ok(s.hasChanged())
})
})

describe('replace', () => {
Expand Down
Loading