Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/platform/inlineEdits/test/common/statelessNextEditProviers.spec.ts
13405 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { describe, expect, it } from 'vitest';
7
import { LineEdit, LineReplacement } from '../../../../util/vs/editor/common/core/edits/lineEdit';
8
import { StringEdit, StringReplacement } from '../../../../util/vs/editor/common/core/edits/stringEdit';
9
import { LineRange } from '../../../../util/vs/editor/common/core/ranges/lineRange';
10
import { OffsetRange } from '../../../../util/vs/editor/common/core/ranges/offsetRange';
11
import { StringText } from '../../../../util/vs/editor/common/core/text/abstractText';
12
import { Edits } from '../../common/dataTypes/edit';
13
import { StatelessNextEditDocument } from '../../common/statelessNextEditProvider';
14
import { editWouldDeleteWhatWasJustInserted2, IgnoreWhitespaceOnlyChanges } from '../../common/statelessNextEditProviders';
15
16
describe('IgnoreFormattingChangesAspect', () => {
17
// Helper to create test cases with less boilerplate
18
function createEdit(baseLines: string[], newLines: string[]): LineReplacement {
19
return new LineReplacement(new LineRange(1, baseLines.length + 1), newLines);
20
}
21
22
function isFormattingOnly(base: string[], edited: string[]): boolean {
23
return IgnoreWhitespaceOnlyChanges._isFormattingOnlyChange(base, createEdit(base, edited));
24
}
25
26
// Test the core algorithm: formatting-only changes preserve content after whitespace removal
27
it('identifies formatting vs content changes correctly', () => {
28
// Formatting-only: content identical after removing whitespace
29
expect(isFormattingOnly(['x=1;'], ['x = 1;'])).toBe(true);
30
expect(isFormattingOnly([' x'], ['x'])).toBe(true);
31
expect(isFormattingOnly(['a', 'b'], ['a b'])).toBe(true);
32
33
// Content changes: content differs after removing whitespace
34
expect(isFormattingOnly(['x=1;'], ['x=2;'])).toBe(false);
35
expect(isFormattingOnly(['x'], ['x+1'])).toBe(false);
36
expect(isFormattingOnly(['a'], ['a', 'b'])).toBe(false);
37
});
38
39
// Representative examples of common scenarios
40
describe('common scenarios', () => {
41
const testCases = [
42
// Formatting-only changes
43
{ name: 'indentation', base: [' code'], edited: [' code'], expected: true },
44
{ name: 'space normalization', base: ['a b'], edited: ['a b'], expected: true },
45
{ name: 'line breaks', base: ['a;', 'b;'], edited: ['a; b;'], expected: true },
46
{ name: 'empty lines', base: [' '], edited: ['\t'], expected: true },
47
48
// Content changes
49
{ name: 'value change', base: ['x=1'], edited: ['x=2'], expected: false },
50
{ name: 'added code', base: ['f()'], edited: ['f()', 'g()'], expected: false },
51
{ name: 'removed code', base: ['a', 'b'], edited: ['a'], expected: false },
52
];
53
54
it.each(testCases)('$name', ({ base, edited, expected }) => {
55
expect(isFormattingOnly(base, edited)).toBe(expected);
56
});
57
});
58
59
// Edge cases that could break the algorithm
60
describe('edge cases', () => {
61
it('handles empty content correctly', () => {
62
expect(isFormattingOnly([''], [''])).toBe(true);
63
expect(isFormattingOnly([''], [' '])).toBe(true);
64
expect(isFormattingOnly([' '], [''])).toBe(true);
65
});
66
67
it('handles single character changes', () => {
68
expect(isFormattingOnly(['a'], ['a '])).toBe(true);
69
expect(isFormattingOnly(['a'], ['b'])).toBe(false);
70
});
71
});
72
});
73
74
describe('editWouldDeleteWhatWasJustInserted', () => {
75
76
it('does not incorrectly flag multi-line removals', async () => {
77
const file =
78
`const modifiedTimes: Map<string, number> = new Map()
79
80
export async function getForceFreshForDir(
81
cacheEntry:
82
| CacheEntry
83
| null
84
| undefined
85
| Promise<CacheEntry | null | undefined>,
86
...dirs: Array<string | undefined | null>
87
) {
88
const truthyDirs = dirs.filter(Boolean)
89
for (const d of truthyDirs) {
90
if (!path.isAbsolute(d)) {
91
throw new Error(\`Trying to get force fresh for non-absolute path: \${d}\`)
92
}
93
}
94
95
const resolvedCacheEntry = await cacheEntry
96
if (!resolvedCacheEntry) return true
97
const latestModifiedTime = truthyDirs.reduce((latest, dir) => {
98
const modifiedTime = modifiedTimes.get(dir)
99
return modifiedTime && modifiedTime > latest ? modifiedTime : latest
100
}, 0)
101
if (!latestModifiedTime) return undefined
102
return latestModifiedTime > resolvedCacheEntry.metadata.createdTime
103
? true
104
: undefined
105
return latestModifiedTime > resolvedCacheEntry.metadata.createdTime
106
? true
107
: undefined
108
}
109
`;
110
111
const lineEdit = new LineEdit([new LineReplacement(new LineRange(28, 31), [])]); //[28,31)->[])
112
113
const recentEdits = Edits.single(new StringEdit([
114
new StringReplacement(new OffsetRange(740, 746), 'return '),
115
new StringReplacement(new OffsetRange(806, 808), ''),
116
new StringReplacement(new OffsetRange(811, 875), '? true\\n\\t\\t: undefined')
117
]));
118
119
const r = editWouldDeleteWhatWasJustInserted2({ documentAfterEdits: new StringText(file), recentEdits } as StatelessNextEditDocument, lineEdit);
120
121
expect(r).toMatchInlineSnapshot(`false`);
122
});
123
124
});
125
126