diff --git a/packages/core/src/amazonq/commons/diff.ts b/packages/core/src/amazonq/commons/diff.ts index f9621252b38..b506cbaa8e9 100644 --- a/packages/core/src/amazonq/commons/diff.ts +++ b/packages/core/src/amazonq/commons/diff.ts @@ -35,7 +35,9 @@ export async function computeDiff(leftPath: string, rightPath: string, tabId: st const leftFile = await vscode.workspace.openTextDocument(left) const rightFile = await vscode.workspace.openTextDocument(right) - const changes = diffLines(leftFile.getText(), rightFile.getText()) + const changes = diffLines(leftFile.getText(), rightFile.getText(), { + ignoreWhitespace: true, + }) let charsAdded = 0 let charsRemoved = 0 diff --git a/packages/core/src/test/amazonq/common/diff.test.ts b/packages/core/src/test/amazonq/common/diff.test.ts index 04d7878186f..ea36b86419e 100644 --- a/packages/core/src/test/amazonq/common/diff.test.ts +++ b/packages/core/src/test/amazonq/common/diff.test.ts @@ -140,21 +140,15 @@ describe('diff', () => { }) describe('computeDiff', () => { - const mockLeftDocument = { - getText: () => 'line1\nline2', + const mockOriginalDocument = { + getText: () => 'line1\nline2\nline3\n', uri: vscode.Uri.file('test.txt'), fileName: 'test.txt', } - const mockRightDocument = { - getText: () => 'line1\nline3\nline4\nline5', - uri: vscode.Uri.file('test.txt'), - fileName: 'test.txt', - } - - it('returns 0 added or removed chars and lines for the same file', async () => { + it('returns no addition or removal for the same file', async () => { sandbox.stub(FileSystem.prototype, 'exists').resolves(true) - sandbox.stub(vscode.workspace, 'openTextDocument').resolves(mockLeftDocument as unknown as TextDocument) + sandbox.stub(vscode.workspace, 'openTextDocument').resolves(mockOriginalDocument as unknown as TextDocument) const { changes, charsAdded, linesAdded, charsRemoved, linesRemoved } = await computeDiff( filePath, @@ -164,8 +158,8 @@ describe('diff', () => { const expectedChanges = [ { - count: 2, - value: 'line1\nline2', + count: 3, + value: 'line1\nline2\nline3\n', }, ] @@ -176,14 +170,19 @@ describe('diff', () => { assert.equal(linesRemoved, 0) }) - it('returns expected added or removed chars and lines for different files', async () => { + it('counts insertion of new line', async () => { + const mockInsertionDocument = { + getText: () => 'line1\ninserted\nline2\nline3\n', + uri: vscode.Uri.file('test.txt'), + fileName: 'test.txt', + } sandbox.stub(FileSystem.prototype, 'exists').resolves(true) sandbox .stub(vscode.workspace, 'openTextDocument') .onFirstCall() - .resolves(mockLeftDocument as unknown as TextDocument) + .resolves(mockOriginalDocument as unknown as TextDocument) .onSecondCall() - .resolves(mockRightDocument as unknown as TextDocument) + .resolves(mockInsertionDocument as unknown as TextDocument) const { changes, charsAdded, linesAdded, charsRemoved, linesRemoved } = await computeDiff( filePath, @@ -197,24 +196,294 @@ describe('diff', () => { value: 'line1\n', }, { + added: true, count: 1, + removed: undefined, + value: 'inserted\n', + }, + { + count: 2, + value: 'line2\nline3\n', + }, + ] + + assert.deepEqual(changes, expectedChanges) + assert.equal(charsAdded, 8) + assert.equal(linesAdded, 1) + assert.equal(charsRemoved, 0) + assert.equal(linesRemoved, 0) + }) + + it('counts insertion of multiple lines', async () => { + const mockMultipleInsertionDocument = { + getText: () => 'line1\ninserted1\ninserted2\nline2\nline3\ninserted3\n', + uri: vscode.Uri.file('test.txt'), + fileName: 'test.txt', + } + sandbox.stub(FileSystem.prototype, 'exists').resolves(true) + sandbox + .stub(vscode.workspace, 'openTextDocument') + .onFirstCall() + .resolves(mockOriginalDocument as unknown as TextDocument) + .onSecondCall() + .resolves(mockMultipleInsertionDocument as unknown as TextDocument) + + const { changes, charsAdded, linesAdded, charsRemoved, linesRemoved } = await computeDiff( + filePath, + rightPath, + tabId + ) + + const expectedChanges = [ + { + count: 1, + value: 'line1\n', + }, + { + added: true, + count: 2, + removed: undefined, + value: 'inserted1\ninserted2\n', + }, + { + count: 2, + value: 'line2\nline3\n', + }, + { + added: true, + count: 1, + removed: undefined, + value: 'inserted3\n', + }, + ] + + assert.deepEqual(changes, expectedChanges) + assert.equal(charsAdded, 27) + assert.equal(linesAdded, 3) + assert.equal(charsRemoved, 0) + assert.equal(linesRemoved, 0) + }) + + it('counts modification of existing line', async () => { + const mockModificationDocument = { + getText: () => 'line1\nmodified\nline3\n', + uri: vscode.Uri.file('test.txt'), + fileName: 'test.txt', + } + sandbox.stub(FileSystem.prototype, 'exists').resolves(true) + sandbox + .stub(vscode.workspace, 'openTextDocument') + .onFirstCall() + .resolves(mockOriginalDocument as unknown as TextDocument) + .onSecondCall() + .resolves(mockModificationDocument as unknown as TextDocument) + + const { changes, charsAdded, linesAdded, charsRemoved, linesRemoved } = await computeDiff( + filePath, + rightPath, + tabId + ) + + const expectedChanges = [ + { + count: 1, + value: 'line1\n', + }, + { added: undefined, + count: 1, removed: true, - value: 'line2', + value: 'line2\n', }, { - count: 3, added: true, + count: 1, removed: undefined, - value: 'line3\nline4\nline5', + value: 'modified\n', + }, + { + count: 1, + value: 'line3\n', }, ] assert.deepEqual(changes, expectedChanges) - assert.equal(charsAdded, 15) - assert.equal(linesAdded, 3) + assert.equal(charsAdded, 8) + assert.equal(linesAdded, 1) + assert.equal(charsRemoved, 5) + assert.equal(linesRemoved, 1) + }) + + it('counts deletion of existing line', async () => { + const mockDeletionDocument = { + getText: () => 'line1\nline3\n', + uri: vscode.Uri.file('test.txt'), + fileName: 'test.txt', + } + sandbox.stub(FileSystem.prototype, 'exists').resolves(true) + sandbox + .stub(vscode.workspace, 'openTextDocument') + .onFirstCall() + .resolves(mockOriginalDocument as unknown as TextDocument) + .onSecondCall() + .resolves(mockDeletionDocument as unknown as TextDocument) + + const { changes, charsAdded, linesAdded, charsRemoved, linesRemoved } = await computeDiff( + filePath, + rightPath, + tabId + ) + + const expectedChanges = [ + { + count: 1, + value: 'line1\n', + }, + { + added: undefined, + count: 1, + removed: true, + value: 'line2\n', + }, + { + count: 1, + value: 'line3\n', + }, + ] + + assert.deepEqual(changes, expectedChanges) + assert.equal(charsAdded, 0) + assert.equal(linesAdded, 0) assert.equal(charsRemoved, 5) assert.equal(linesRemoved, 1) }) + + it('counts deletion of existing line and then adding a new line', async () => { + const mockDeletionAndAdditionDocument = { + getText: () => 'line1\nline3\nline4\n', + uri: vscode.Uri.file('test.txt'), + fileName: 'test.txt', + } + sandbox.stub(FileSystem.prototype, 'exists').resolves(true) + sandbox + .stub(vscode.workspace, 'openTextDocument') + .onFirstCall() + .resolves(mockOriginalDocument as unknown as TextDocument) + .onSecondCall() + .resolves(mockDeletionAndAdditionDocument as unknown as TextDocument) + + const { changes, charsAdded, linesAdded, charsRemoved, linesRemoved } = await computeDiff( + filePath, + rightPath, + tabId + ) + + const expectedChanges = [ + { + count: 1, + value: 'line1\n', + }, + { + added: undefined, + count: 1, + removed: true, + value: 'line2\n', + }, + { + count: 1, + value: 'line3\n', + }, + { + added: true, + count: 1, + removed: undefined, + value: 'line4\n', + }, + ] + + assert.deepEqual(changes, expectedChanges) + assert.equal(charsAdded, 5) + assert.equal(linesAdded, 1) + assert.equal(charsRemoved, 5) + assert.equal(linesRemoved, 1) + }) + + it('counts a new empty line', async () => { + const mockEmptyLineDocument = { + getText: () => 'line1\nline2\n\nline3\n', + uri: vscode.Uri.file('test.txt'), + fileName: 'test.txt', + } + sandbox.stub(FileSystem.prototype, 'exists').resolves(true) + sandbox + .stub(vscode.workspace, 'openTextDocument') + .onFirstCall() + .resolves(mockOriginalDocument as unknown as TextDocument) + .onSecondCall() + .resolves(mockEmptyLineDocument as unknown as TextDocument) + + const { changes, charsAdded, linesAdded, charsRemoved, linesRemoved } = await computeDiff( + filePath, + rightPath, + tabId + ) + + const expectedChanges = [ + { + count: 2, + value: 'line1\nline2\n', + }, + { + added: true, + count: 1, + removed: undefined, + value: '\n', + }, + { + count: 1, + value: 'line3\n', + }, + ] + + assert.deepEqual(changes, expectedChanges) + assert.equal(charsAdded, 0) + assert.equal(linesAdded, 1) + assert.equal(charsRemoved, 0) + assert.equal(linesRemoved, 0) + }) + + it('ignores leading and trailing whitespaces', async () => { + const mockWhitespaceDocument = { + getText: () => ' line1 \n line2 \n line3 \n', + uri: vscode.Uri.file('test.txt'), + fileName: 'test.txt', + } + sandbox.stub(FileSystem.prototype, 'exists').resolves(true) + sandbox + .stub(vscode.workspace, 'openTextDocument') + .onFirstCall() + .resolves(mockOriginalDocument as unknown as TextDocument) + .onSecondCall() + .resolves(mockWhitespaceDocument as unknown as TextDocument) + + const { changes, charsAdded, linesAdded, charsRemoved, linesRemoved } = await computeDiff( + filePath, + rightPath, + tabId + ) + + const expectedChanges = [ + { + count: 3, + value: 'line1\nline2\nline3\n', + }, + ] + + assert.deepEqual(changes, expectedChanges) + assert.equal(charsAdded, 0) + assert.equal(linesAdded, 0) + assert.equal(charsRemoved, 0) + assert.equal(linesRemoved, 0) + }) }) })