Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/test/inline/inlineGenerateCode.stest.ts
13388 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 assert from 'assert';
7
import { EditCodeIntent } from '../../src/extension/intents/node/editCodeIntent';
8
import { GenerateCodeIntent } from '../../src/extension/intents/node/generateCodeIntent';
9
import { TestingServiceCollection } from '../../src/platform/test/node/services';
10
import { URI } from '../../src/util/vs/base/common/uri';
11
import { Uri } from '../../src/vscodeTypes';
12
import { NonExtensionConfiguration, ssuite, stest } from '../base/stest';
13
import { KnownDiagnosticProviders } from '../simulation/diagnosticProviders';
14
import { simulateInlineChat, simulateInlineChatIntent } from '../simulation/inlineChatSimulator';
15
import { assertContainsAllSnippets, assertNoDiagnosticsAsync, assertNoSyntacticDiagnosticsAsync } from '../simulation/outcomeValidators';
16
import { assertConversationalOutcome, assertInlineEdit, assertInlineEditShape, assertOccursOnce, assertOneOf, assertSomeStrings, fromFixture, toFile } from '../simulation/stestUtil';
17
import { EditTestStrategy, IScenario } from '../simulation/types';
18
19
function executeEditTestStrategy(
20
strategy: EditTestStrategy,
21
testingServiceCollection: TestingServiceCollection,
22
scenario: IScenario
23
): Promise<void> {
24
if (strategy === EditTestStrategy.Inline) {
25
return simulateInlineChat(testingServiceCollection, scenario);
26
} else if (EditTestStrategy.InlineChatIntent) {
27
return simulateInlineChatIntent(testingServiceCollection, scenario);
28
} else {
29
throw new Error('Invalid edit test strategy');
30
}
31
}
32
33
function forInlineChatIntent(callback: (strategy: EditTestStrategy, variant: '-InlineChatIntent', nonExtensionConfigurations?: NonExtensionConfiguration[]) => void): void {
34
callback(EditTestStrategy.InlineChatIntent, '-InlineChatIntent', [['chat.agent.autoFix', false]]);
35
}
36
37
forInlineChatIntent((strategy, variant, nonExtensionConfigurations) => {
38
39
ssuite({ title: `generate${variant}`, location: 'inline' }, () => {
40
stest({ description: 'gen-ts-ltrim', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {
41
return executeEditTestStrategy(strategy, testingServiceCollection, {
42
files: [
43
{ kind: 'relativeFile', fileName: 'new.ts', fileContents: '' }
44
],
45
queries: [
46
{
47
file: 'new.ts',
48
selection: [0, 0],
49
query: 'generate a function that will remove whitespace from the start of a string',
50
expectedIntent: GenerateCodeIntent.ID,
51
validate: async (outcome, workspace, accessor) => {
52
assertInlineEdit(outcome);
53
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
54
assertContainsAllSnippets(outcome.fileContents, ['function', ': string'], 'gen-ts-ltrim-01');
55
},
56
},
57
{
58
query: 'change it to take as argument the characters to remove from the start',
59
expectedIntent: EditCodeIntent.ID,
60
validate: async (outcome, workspace, accessor) => {
61
assertInlineEdit(outcome);
62
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
63
const text = outcome.fileContents;
64
const f1 = text.indexOf('function ');
65
const f2 = text.indexOf('function ', f1 + 1);
66
assert(f2 === -1);
67
assertContainsAllSnippets(text, ['function', ': string'], 'gen-ts-ltrim-02');
68
},
69
}
70
],
71
});
72
});
73
74
stest({ description: 'Generate a nodejs server', language: 'javascript', nonExtensionConfigurations }, (testingServiceCollection) => {
75
return executeEditTestStrategy(strategy, testingServiceCollection, {
76
files: [{ kind: 'relativeFile', fileName: 'server.js', fileContents: '' }],
77
queries: [
78
{
79
file: 'server.js',
80
selection: [0, 0],
81
query: 'generate a nodejs server that responds with "Hello World"',
82
expectedIntent: GenerateCodeIntent.ID,
83
validate: async (outcome, workspace, accessor) => {
84
assertInlineEdit(outcome);
85
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
86
assertContainsAllSnippets(outcome.fileContents, ['http', 'createServer', 'listen', 'Hello World']);
87
},
88
},
89
{
90
query: 'change it to respond with "Goodbye World"',
91
expectedIntent: EditCodeIntent.ID,
92
validate: async (outcome, workspace, accessor) => {
93
assertInlineEdit(outcome);
94
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
95
assertContainsAllSnippets(outcome.fileContents, ['http', 'createServer', 'listen', 'Goodbye World']);
96
},
97
},
98
],
99
});
100
});
101
102
stest({ description: 'generate rtrim', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {
103
104
if (1) {
105
throw new Error('SKIPPED');
106
}
107
108
return executeEditTestStrategy(strategy, testingServiceCollection, {
109
files: [
110
fromFixture('gen-top-level-function/charCode.ts'),
111
fromFixture('gen-top-level-function/strings.ts'),
112
],
113
queries: [
114
{
115
file: 'strings.ts',
116
selection: [770, 0],
117
query: 'generate rtrim',
118
expectedIntent: GenerateCodeIntent.ID,
119
validate: async (outcome, workspace, accessor) => {
120
assertInlineEdit(outcome);
121
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
122
const edit = assertInlineEditShape(outcome, [{
123
line: 770,
124
originalLength: 0,
125
modifiedLength: undefined
126
}, {
127
line: 770,
128
originalLength: 1,
129
modifiedLength: undefined
130
}]);
131
assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['rtrim', 'needle.length']);
132
},
133
},
134
],
135
});
136
});
137
138
stest({ description: 'issue #2342: Use inline chat to generate a new function/property replaces other code', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {
139
return executeEditTestStrategy(strategy, testingServiceCollection, {
140
files: [
141
fromFixture('gen-top-level-function/charCode.ts'),
142
fromFixture('gen-top-level-function/strings.ts'),
143
],
144
queries: [
145
{
146
file: 'strings.ts',
147
selection: [31, 0],
148
query: 'generate a fibonacci',
149
expectedIntent: GenerateCodeIntent.ID,
150
validate: async (outcome, workspace, accessor) => {
151
assertInlineEdit(outcome);
152
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
153
const edit = assertInlineEditShape(outcome, [{
154
line: 31,
155
originalLength: 0,
156
modifiedLength: undefined
157
}, {
158
line: 31,
159
originalLength: 1,
160
modifiedLength: undefined
161
}]);
162
const changedModifiedLines = edit.changedModifiedLines.join('\n');
163
assertContainsAllSnippets(changedModifiedLines, ['function']);
164
assertOneOf([
165
() => assertContainsAllSnippets(changedModifiedLines, ['fibonacci']),
166
() => assertContainsAllSnippets(changedModifiedLines, ['Fibonacci']), // e.g., `generateFibonacci()`
167
]);
168
},
169
},
170
],
171
});
172
});
173
174
stest({ description: 'issue #3602: gen method', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {
175
return executeEditTestStrategy(strategy, testingServiceCollection, {
176
files: [
177
fromFixture('gen-method-issue-3602/editor.ts'),
178
],
179
queries: [
180
{
181
file: 'editor.ts',
182
selection: [39, 0],
183
query: 'add an async function that moves a block of lines up by one',
184
expectedIntent: GenerateCodeIntent.ID,
185
validate: async (outcome, workspace, accessor) => {
186
assertInlineEdit(outcome);
187
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
188
const edit = assertInlineEditShape(outcome, [{
189
line: 39,
190
originalLength: 0,
191
modifiedLength: undefined
192
}, {
193
line: 39,
194
originalLength: 1,
195
modifiedLength: undefined
196
}]);
197
const changedModifiedLines = edit.changedModifiedLines.join('\n');
198
assertContainsAllSnippets(changedModifiedLines, ['async'], 'gen-method-issue-3602');
199
assertOneOf([
200
() => assertContainsAllSnippets(changedModifiedLines, ['moveBlockUp']),
201
() => assertContainsAllSnippets(changedModifiedLines, ['moveLinesUp']),
202
]);
203
},
204
},
205
],
206
});
207
});
208
209
stest({ description: 'issue #3604: gen nestjs route', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {
210
return executeEditTestStrategy(strategy, testingServiceCollection, {
211
files: [
212
fromFixture('gen-nestjs-route-issue-3604/app.controller.ts'),
213
],
214
queries: [
215
{
216
file: 'app.controller.ts',
217
selection: [12, 4],
218
query: 'add a new /about page',
219
expectedIntent: GenerateCodeIntent.ID,
220
validate: async (outcome, workspace, accessor) => {
221
assertInlineEdit(outcome);
222
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
223
const edit = assertInlineEditShape(outcome, [{
224
line: 12,
225
originalLength: 0,
226
modifiedLength: undefined
227
}, {
228
line: 12,
229
originalLength: 1,
230
modifiedLength: undefined
231
}]);
232
assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['@Get', '(): string']);
233
},
234
},
235
],
236
});
237
});
238
239
stest({ description: 'gen a palindrom fn', language: 'python', nonExtensionConfigurations }, (testingServiceCollection) => {
240
return executeEditTestStrategy(strategy, testingServiceCollection, {
241
files: [
242
{ kind: 'relativeFile', fileName: 'new.py', fileContents: '' }
243
],
244
queries: [
245
{
246
file: 'new.py',
247
selection: [0, 0],
248
query: 'generate a function that checks if a string is a palindrome',
249
expectedIntent: GenerateCodeIntent.ID,
250
validate: async (outcome, workspace, accessor) => {
251
assertInlineEdit(outcome);
252
assertContainsAllSnippets(outcome.fileContents, ['def', '[::-1]'], 'gen-python-palindrome');
253
},
254
},
255
],
256
});
257
});
258
259
stest({ description: 'issue #3597: gen twice', language: 'javascript', nonExtensionConfigurations }, (testingServiceCollection) => {
260
return executeEditTestStrategy(strategy, testingServiceCollection, {
261
files: [
262
fromFixture('gen-twice-issue-3597/new.js')
263
],
264
queries: [
265
{
266
file: 'new.js',
267
selection: [27, 0],
268
query: 'create a function that checks whether a given number is a prime number',
269
expectedIntent: GenerateCodeIntent.ID,
270
validate: async (outcome, workspace, accessor) => {
271
assertInlineEdit(outcome);
272
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
273
const edit = assertInlineEditShape(outcome, [{
274
line: 27,
275
originalLength: 0,
276
modifiedLength: undefined
277
}, {
278
line: 27,
279
originalLength: 1,
280
modifiedLength: undefined
281
}]);
282
assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['function']);
283
},
284
},
285
{
286
query: 'create a function that checks if a given number is a fibonacci number',
287
expectedIntent: GenerateCodeIntent.ID,
288
validate: async (outcome, workspace, accessor) => {
289
assertInlineEdit(outcome);
290
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
291
const edit = assertInlineEditShape(outcome, [{
292
line: ~1,
293
originalLength: 0,
294
modifiedLength: undefined,
295
}, {
296
line: ~1,
297
originalLength: 1,
298
modifiedLength: undefined,
299
}, {
300
line: ~0,
301
originalLength: 0,
302
modifiedLength: undefined,
303
}]);
304
assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['function']);
305
}
306
}
307
],
308
});
309
});
310
311
stest({ description: 'issue #3782: gen twice', language: 'javascript', nonExtensionConfigurations }, (testingServiceCollection) => {
312
return executeEditTestStrategy(strategy, testingServiceCollection, {
313
files: [
314
{ kind: 'relativeFile', fileName: 'new.js', fileContents: '' }
315
],
316
queries: [
317
{
318
file: 'new.js',
319
selection: [0, 0],
320
query: 'create a function `fibonacci` that computes the fibonacci numbers',
321
expectedIntent: GenerateCodeIntent.ID,
322
validate: async (outcome, workspace, accessor) => {
323
assertInlineEdit(outcome);
324
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
325
const edit = assertInlineEditShape(outcome, {
326
line: 0,
327
originalLength: 1,
328
modifiedLength: undefined,
329
});
330
assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['function fibonacci']);
331
},
332
},
333
{
334
query: 'create a second function `getPrimes` that compute prime numbers',
335
expectedIntent: GenerateCodeIntent.ID,
336
validate: async (outcome, workspace, accessor) => {
337
assertInlineEdit(outcome);
338
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
339
const edit = assertInlineEditShape(outcome, [{
340
line: ~1,
341
originalLength: 0,
342
modifiedLength: undefined,
343
}, {
344
line: ~0,
345
originalLength: 0,
346
modifiedLength: undefined,
347
}]);
348
assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['function getPrimes']);
349
}
350
},
351
{
352
query: 'generate a function `getFibonacciPrimes` that compute the first 100 prime numbers that are also fibonacci numbers using the `fibonacci` and the`getPrimes` function',
353
expectedIntent: GenerateCodeIntent.ID,
354
validate: async (outcome, workspace, accessor) => {
355
assertInlineEdit(outcome);
356
const text = outcome.fileContents;
357
// we need to have 2 functions
358
const f1 = text.indexOf('function fibonacci');
359
const f2 = text.indexOf('\nfunction getPrimes', f1 + 1);
360
const f3 = text.indexOf('\nfunction getFibonacciPrimes', f2 + 1);
361
assert(!(f1 === -1 || f2 === -1 || f3 === -1));
362
}
363
},
364
],
365
});
366
});
367
368
stest({ description: 'parse keybindings', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {
369
return executeEditTestStrategy(strategy, testingServiceCollection, {
370
files: [fromFixture('gen/keybindingParser.ts')],
371
queries: [{
372
file: 'keybindingParser.ts',
373
selection: [15, 8],
374
query: 'parse ctrl+ shift+ alt+ cmd+ and remove them from input',
375
expectedIntent: GenerateCodeIntent.ID,
376
validate: async (outcome, workspace, accessor) => {
377
assertInlineEdit(outcome);
378
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
379
const edit = assertInlineEditShape(outcome, [{
380
line: 15,
381
originalLength: 1,
382
modifiedLength: undefined
383
}, {
384
line: 15,
385
originalLength: 0,
386
modifiedLength: undefined
387
}]);
388
assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['ctrl = true', 'shift = true', 'alt = true', 'meta = true']);
389
}
390
}]
391
});
392
});
393
394
stest({ description: 'issue #2303: FILEPATH not removed from generated code in empty file', language: 'python', nonExtensionConfigurations }, (testingServiceCollection) => {
395
const uri = Uri.parse('untitled:Untitled-1');
396
return executeEditTestStrategy(strategy, testingServiceCollection, {
397
files: [{
398
kind: 'qualifiedFile',
399
uri,
400
fileContents: '',
401
languageId: 'python'
402
}],
403
queries: [
404
{
405
file: uri,
406
selection: [0, 0],
407
query: 'reverse a linked list in python',
408
expectedIntent: GenerateCodeIntent.ID,
409
validate: async (outcome, workspace, accessor) => {
410
assertInlineEdit(outcome);
411
assert.strictEqual(outcome.fileContents.includes('# FILEPATH'), false);
412
},
413
},
414
],
415
});
416
});
417
418
stest({ description: 'issue #2589: IllegalArgument: line must be non-negative', language: 'json', nonExtensionConfigurations }, (testingServiceCollection) => {
419
const uri = Uri.parse('file:///home/.prettierrc');
420
return executeEditTestStrategy(strategy, testingServiceCollection, {
421
files: [{
422
kind: 'qualifiedFile',
423
uri,
424
fileContents: '',
425
languageId: 'json'
426
}],
427
queries: [
428
{
429
file: uri,
430
selection: [0, 0],
431
query: 'use tabs',
432
expectedIntent: GenerateCodeIntent.ID,
433
validate: async (outcome, workspace, accessor) => {
434
assertInlineEdit(outcome);
435
},
436
},
437
],
438
});
439
});
440
441
stest({ description: 'issue #2269: BEGIN and END were included in diff', language: 'python', nonExtensionConfigurations }, (testingServiceCollection) => {
442
const uri = Uri.parse('untitled:Untitled-1');
443
return executeEditTestStrategy(strategy, testingServiceCollection, {
444
files: [{
445
kind: 'qualifiedFile',
446
uri,
447
fileContents: '',
448
languageId: 'javascript'
449
}],
450
queries: [
451
{
452
file: uri,
453
selection: [0, 0],
454
query: 'create a simple express server',
455
expectedIntent: GenerateCodeIntent.ID,
456
validate: async (outcome, workspace, accessor) => {
457
assertInlineEdit(outcome);
458
assert.strictEqual(outcome.fileContents.includes('BEGIN'), false);
459
},
460
},
461
{
462
query: 'add a date route that returns the current date and time',
463
expectedIntent: EditCodeIntent.ID,
464
validate: async (outcome, workspace, accessor) => {
465
assertInlineEdit(outcome);
466
assert.strictEqual(outcome.fileContents.includes('BEGIN'), false);
467
},
468
},
469
{
470
query: 'create an eval route that evaluates a mathematical equation. Support addition, multiplication, division, subtraction and square root',
471
expectedIntent: EditCodeIntent.ID,
472
validate: async (outcome, workspace, accessor) => {
473
assertInlineEdit(outcome);
474
assert.strictEqual(outcome.fileContents.includes('BEGIN'), false);
475
},
476
}
477
],
478
});
479
});
480
481
stest({ description: 'Streaming gets confused due to jsdoc', language: 'json', nonExtensionConfigurations }, (testingServiceCollection) => {
482
483
if (1) {
484
throw new Error('SKIPPED');
485
}
486
487
return executeEditTestStrategy(strategy, testingServiceCollection, {
488
files: [
489
fromFixture('gen-top-level-function/strings.ts'),
490
],
491
queries: [
492
{
493
file: 'strings.ts',
494
selection: [75, 0],
495
query: 'add fibonacci function and use jsdoc to document it',
496
expectedIntent: GenerateCodeIntent.ID,
497
validate: async (outcome, workspace, accessor) => {
498
assertInlineEdit(outcome);
499
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
500
const edit = assertInlineEditShape(outcome, [{
501
line: 75,
502
originalLength: 0,
503
modifiedLength: undefined
504
}, {
505
line: 75,
506
originalLength: 1,
507
modifiedLength: undefined
508
}]);
509
assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['function fibonacci']);
510
},
511
}
512
],
513
});
514
});
515
516
stest({ description: 'code below cursor is not duplicated', language: 'html', nonExtensionConfigurations }, (testingServiceCollection) => {
517
return executeEditTestStrategy(strategy, testingServiceCollection, {
518
files: [
519
fromFixture('editing-html/index.html')
520
],
521
queries: [
522
{
523
file: 'index.html',
524
selection: [3, 3],
525
query: 'make cursor use hand.png',
526
expectedIntent: GenerateCodeIntent.ID,
527
validate: async (outcome, workspace, accessor) => {
528
assertInlineEdit(outcome);
529
assertOccursOnce(outcome.fileContents, '<html>');
530
assertOccursOnce(outcome.fileContents, '<head>');
531
assertOccursOnce(outcome.fileContents, '<style>');
532
assertOccursOnce(outcome.fileContents, '</style>');
533
assertOccursOnce(outcome.fileContents, '</head>');
534
assertOccursOnce(outcome.fileContents, '<body>');
535
assertOccursOnce(outcome.fileContents, '</body>');
536
assertOccursOnce(outcome.fileContents, '</html>');
537
}
538
}
539
],
540
});
541
});
542
543
stest({ description: 'issue #3370: generate code duplicates too much', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {
544
return executeEditTestStrategy(strategy, testingServiceCollection, {
545
files: [
546
fromFixture('gen/inlayHintsController.ts')
547
],
548
queries: [
549
{
550
file: 'inlayHintsController.ts',
551
selection: [673, 0],
552
query: 'add a function that takes a string and a length. the function should crop the string at length and insert `...` instead. The length is fuzzy and if possible the ellipses shouldn\'t follow whitespace or other "funny" characters',
553
expectedIntent: GenerateCodeIntent.ID,
554
validate: async (outcome, workspace, accessor) => {
555
assertInlineEdit(outcome);
556
557
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
558
const edit = assertInlineEditShape(outcome, [{
559
line: 673,
560
originalLength: 0,
561
modifiedLength: undefined
562
}, {
563
line: 673,
564
originalLength: 1,
565
modifiedLength: undefined
566
}]);
567
assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['function', '...']);
568
assertOccursOnce(edit.changedModifiedLines.join('\n'), 'function');
569
}
570
}
571
],
572
});
573
});
574
575
stest({ description: 'issue #2496: Range of interest is imprecise after a streaming edit', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {
576
577
if (1) {
578
throw new Error('SKIPPED');
579
}
580
581
return executeEditTestStrategy(strategy, testingServiceCollection, {
582
files: [
583
fromFixture('gen/strings.ts')
584
],
585
queries: [
586
{
587
file: 'strings.ts',
588
selection: [49, 0],
589
query: 'add fibonacci(n) returning the nth fibonacci with recursion',
590
expectedIntent: GenerateCodeIntent.ID,
591
validate: async (outcome, workspace, accessor) => {
592
assertInlineEdit(outcome);
593
594
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
595
const edit = assertInlineEditShape(outcome, [{
596
line: 49,
597
originalLength: 0,
598
modifiedLength: undefined
599
}, {
600
line: 49,
601
originalLength: 1,
602
modifiedLength: undefined
603
}]);
604
assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['fibonacci']);
605
}
606
},
607
{
608
file: 'strings.ts',
609
selection: [49, 3],
610
wholeRange: [49, 0, 49, 3], // we want to simulate 100% vscode's behavior and vscode is peculiar here
611
query: 'avoid using recursion',
612
expectedIntent: EditCodeIntent.ID,
613
validate: async (outcome, workspace, accessor) => {
614
assertInlineEdit(outcome);
615
616
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
617
assertOccursOnce(outcome.fileContents, 'function fibonacci');
618
}
619
}
620
],
621
});
622
});
623
624
stest({ description: 'issue release#142: Inline chat updates code outside of area I expect', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {
625
return executeEditTestStrategy(strategy, testingServiceCollection, {
626
files: [
627
fromFixture('edit/issue-release-142/testAuthProvider.ts')
628
],
629
queries: [
630
{
631
file: 'testAuthProvider.ts',
632
selection: [28, 8],
633
query: 'implement this',
634
expectedIntent: GenerateCodeIntent.ID,
635
validate: async (outcome, workspace, accessor) => {
636
assertInlineEdit(outcome);
637
638
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
639
const edit = assertInlineEditShape(outcome, [{
640
line: 28,
641
originalLength: 0,
642
modifiedLength: undefined
643
}, {
644
line: 28,
645
originalLength: 1,
646
modifiedLength: undefined
647
}]);
648
assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['sessionId', 'this._onDidChangeSessions']);
649
assertSomeStrings(edit.changedModifiedLines.join('\n'), ['filter', 'splice'], 1);
650
}
651
}
652
],
653
});
654
});
655
656
stest({ description: 'issue #3778: Incorrect streaming edits', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {
657
return executeEditTestStrategy(strategy, testingServiceCollection, {
658
files: [
659
fromFixture('gen/modelLines.ts')
660
],
661
queries: [
662
{
663
file: 'modelLines.ts',
664
selection: [3, 0],
665
query: 'implement this!',
666
expectedIntent: GenerateCodeIntent.ID,
667
validate: async (outcome, workspace, accessor) => {
668
assertInlineEdit(outcome);
669
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
670
assertInlineEditShape(outcome, [{
671
line: 3,
672
originalLength: 0,
673
modifiedLength: undefined
674
}, {
675
line: 3,
676
originalLength: 1,
677
modifiedLength: undefined
678
}]);
679
}
680
}
681
],
682
});
683
});
684
685
stest({ description: 'issue #4179: Imports aren\'t inserted to the top of the file anymore', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {
686
return executeEditTestStrategy(strategy, testingServiceCollection, {
687
files: [
688
fromFixture('gen/4179.ts')
689
],
690
queries: [
691
{
692
file: '4179.ts',
693
selection: [10, 0],
694
query: 'use node-lib to read a file',
695
expectedIntent: GenerateCodeIntent.ID,
696
validate: async (outcome, workspace, accessor) => {
697
assertInlineEdit(outcome);
698
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
699
const firstLine = outcome.fileContents.split(/\r\n|\r|\n/g)[0];
700
assertSomeStrings(firstLine, ['import', 'require'], 1);
701
}
702
}
703
],
704
});
705
});
706
707
stest({ description: 'Remember my name', language: 'javascript', nonExtensionConfigurations }, (testingServiceCollection) => {
708
709
const uri = URI.from({ scheme: 'foo', path: '/bar/baz.js' });
710
711
return executeEditTestStrategy(strategy, testingServiceCollection, {
712
files: [{
713
kind: 'qualifiedFile',
714
uri,
715
fileContents: '',
716
languageId: 'javascript'
717
}],
718
queries: [
719
{
720
file: uri,
721
selection: [0, 0],
722
query: 'My name is Siglinde. Remember my name',
723
expectedIntent: GenerateCodeIntent.ID,
724
validate: async (outcome, workspace, accessor) => {
725
assertConversationalOutcome(outcome);
726
},
727
},
728
{
729
file: uri,
730
selection: [0, 0],
731
query: 'Generate a class that represents a person',
732
expectedIntent: GenerateCodeIntent.ID,
733
validate: async (outcome, workspace, accessor) => {
734
assertInlineEdit(outcome);
735
assert.ok(outcome.fileContents.includes('Person'));
736
},
737
},
738
{
739
// file: uri,
740
query: 'Print my name as a sample usage of the class',
741
expectedIntent: GenerateCodeIntent.ID,
742
validate: async (outcome, workspace, accessor) => {
743
assertInlineEdit(outcome);
744
745
assert.ok(outcome.fileContents.includes('Siglinde'));
746
},
747
}
748
],
749
});
750
});
751
752
stest({ description: 'issue #4080: Implementing a getter/method duplicates the signature', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {
753
return executeEditTestStrategy(strategy, testingServiceCollection, {
754
files: [
755
fromFixture('gen/4080.ts')
756
],
757
queries: [
758
{
759
file: '4080.ts',
760
selection: [40, 2],
761
query: 'implement this!',
762
expectedIntent: GenerateCodeIntent.ID,
763
validate: async (outcome, workspace, accessor) => {
764
assertInlineEdit(outcome);
765
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
766
const edit = assertInlineEditShape(outcome, [{
767
line: 40,
768
originalLength: 0,
769
modifiedLength: undefined
770
}, {
771
line: 40,
772
originalLength: 1,
773
modifiedLength: undefined
774
}]);
775
assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['CharCode.Tab', 'CharCode.Space', 'CharCode.LineFeed', 'while']);
776
}
777
}
778
],
779
});
780
});
781
782
stest({ description: 'issue #3439: Bad edits in this case', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {
783
return executeEditTestStrategy(strategy, testingServiceCollection, {
784
files: [
785
fromFixture('gen/commandCenterControl.ts')
786
],
787
queries: [
788
{
789
file: 'commandCenterControl.ts',
790
selection: [188, 0],
791
query: 'when label contains \n or \r replace them with the rendered unicode character',
792
expectedIntent: GenerateCodeIntent.ID,
793
validate: async (outcome, workspace, accessor) => {
794
assertInlineEdit(outcome);
795
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
796
const edit = assertInlineEditShape(outcome, [{
797
line: 188,
798
originalLength: 0,
799
modifiedLength: undefined
800
}, {
801
line: 188,
802
originalLength: 1,
803
modifiedLength: undefined
804
}]);
805
assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['replace', '\\r', '\\n']);
806
}
807
}
808
],
809
});
810
});
811
812
stest({ description: 'cpp code generation', language: 'cpp', nonExtensionConfigurations }, (testingServiceCollection) => {
813
return executeEditTestStrategy(strategy, testingServiceCollection, {
814
files: [
815
fromFixture('cpp/basic/main.cpp')
816
],
817
queries: [
818
{
819
file: 'main.cpp',
820
selection: [15, 0],
821
query: 'add validation to ensure that the input is not empty',
822
expectedIntent: GenerateCodeIntent.ID,
823
validate: async (outcome, workspace, accessor) => {
824
assertInlineEdit(outcome);
825
assertContainsAllSnippets(outcome.fileContents, ['empty']);
826
assertSomeStrings(outcome.fileContents, ['if', 'while']);
827
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'cpp');
828
}
829
}
830
],
831
});
832
});
833
834
stest({ description: 'templated code generation', language: 'cpp', nonExtensionConfigurations }, (testingServiceCollection) => {
835
return executeEditTestStrategy(strategy, testingServiceCollection, {
836
files: [
837
fromFixture('cpp/headers/json_fwd.hpp'),
838
fromFixture('cpp/headers/abi_macros.hpp')
839
],
840
queries: [
841
{
842
file: 'json_fwd.hpp',
843
selection: [71, 0],
844
query: 'add a sorted_map specialization',
845
expectedIntent: GenerateCodeIntent.ID,
846
validate: async (outcome, workspace, accessor) => {
847
assertInlineEdit(outcome);
848
// Validate the generated code matches naming and formatting conventions.
849
assertSomeStrings(outcome.fileContents, ['sorted_map', 'sorted_json', 'basic_json<nlohmann::sorted_map>']);
850
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'cpp');
851
}
852
}
853
],
854
});
855
});
856
857
stest({ description: 'issue #224: Lots of lines deleted when using interactive chat in a markdown file', language: 'markdown', nonExtensionConfigurations }, (testingServiceCollection) => {
858
859
if (1) {
860
throw new Error('SKIPPED');
861
}
862
863
return executeEditTestStrategy(strategy, testingServiceCollection, {
864
files: [
865
fromFixture('gen/CHANGELOG.md')
866
],
867
queries: [
868
{
869
file: 'CHANGELOG.md',
870
selection: [1, 0],
871
query: 'Add release notes for version 0.62.0',
872
expectedIntent: GenerateCodeIntent.ID,
873
validate: async (outcome, workspace, accessor) => {
874
assertInlineEdit(outcome);
875
assertInlineEditShape(outcome, [{
876
line: 1,
877
originalLength: 1,
878
modifiedLength: undefined
879
}]);
880
},
881
},
882
],
883
});
884
});
885
886
stest({ description: 'doesn\'t handle markdown code response', language: 'markdown', nonExtensionConfigurations }, (testingServiceCollection) => {
887
const uri = Uri.parse('untitled:Untitled-1');
888
return executeEditTestStrategy(strategy, testingServiceCollection, {
889
files: [{
890
kind: 'qualifiedFile',
891
uri,
892
fileContents: '',
893
languageId: 'markdown'
894
}],
895
queries: [
896
{
897
file: uri,
898
selection: [0, 0],
899
query: 'describe fibonacci in markdown',
900
expectedIntent: GenerateCodeIntent.ID,
901
validate: async (outcome, workspace, accessor) => {
902
assertInlineEdit(outcome);
903
},
904
},
905
],
906
});
907
});
908
909
stest({ description: 'issue #5439: import List in python', language: 'python', nonExtensionConfigurations }, (testingServiceCollection) => {
910
return executeEditTestStrategy(strategy, testingServiceCollection, {
911
files: [
912
fromFixture('gen/5439.py')
913
],
914
queries: [
915
{
916
file: '5439.py',
917
selection: [2, 0],
918
query: 'import List',
919
expectedIntent: GenerateCodeIntent.ID,
920
validate: async (outcome, workspace, accessor) => {
921
assertInlineEdit(outcome);
922
assertInlineEditShape(outcome, [{
923
line: 2,
924
originalLength: 1,
925
modifiedLength: 1
926
}]);
927
},
928
},
929
],
930
});
931
});
932
933
stest({ description: 'issue #6234: generate a TS interface for some JSON', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {
934
return executeEditTestStrategy(strategy, testingServiceCollection, {
935
files: [fromFixture('gen/6234/top-packages.ts')],
936
queries: [
937
{
938
file: 'top-packages.ts',
939
selection: [4, 0, 4, 0],
940
query: 'generate an interface for this JSON:\n\n```\n{"name":"chalk","version":"5.3.0","description":"Terminal string styling done right","keywords":["color","colour","colors","terminal","console","cli","string","ansi","style","styles","tty","formatting","rgb","256","shell","xterm","log","logging","command-line","text"],"publisher":{"username":"sindresorhus","email":"[email protected]"},"maintainers":[{"username":"sindresorhus","email":"[email protected]"},{"username":"qix","email":"[email protected]"}],"links":{"npm":"https://www.npmjs.com/package/chalk","homepage":"https://github.com/chalk/chalk#readme","repository":"https://github.com/chalk/chalk"}}\n```',
941
diagnostics: 'tsc',
942
expectedIntent: 'generate',
943
validate: async (outcome, workspace, accessor) => {
944
await assertNoDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
945
assertInlineEdit(outcome);
946
assertInlineEditShape(outcome, [{
947
line: 4,
948
originalLength: 1,
949
modifiedLength: undefined
950
}]);
951
}
952
}
953
]
954
});
955
});
956
957
stest({ description: 'Inline chat response did not use code block #6554', language: 'powershell', nonExtensionConfigurations }, (testingServiceCollection) => {
958
return executeEditTestStrategy(strategy, testingServiceCollection, {
959
files: [fromFixture('gen/6554/update-vs-base.ps1')],
960
queries: [
961
{
962
file: 'update-vs-base.ps1',
963
selection: [13, 0, 13, 0],
964
query: 'copy folder to new location',
965
expectedIntent: 'generate',
966
validate: async (outcome, workspace, accessor) => {
967
assert.equal(outcome.type, 'inlineEdit');
968
}
969
}
970
]
971
});
972
});
973
974
stest({ description: 'variables are used when generating', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {
975
return executeEditTestStrategy(strategy, testingServiceCollection, {
976
files: [
977
fromFixture('gen/variables/example.ts'),
978
fromFixture('gen/variables/output.ts')
979
],
980
queries: [
981
{
982
file: 'output.ts',
983
selection: [0, 0],
984
query: 'generate the same function like in #file:example.ts but for ArrayBuffer',
985
expectedIntent: 'generate',
986
validate: async (outcome, workspace, accessor) => {
987
assertInlineEdit(outcome);
988
assertContainsAllSnippets(outcome.fileContents, ['ArrayBuffer']);
989
assertSomeStrings(outcome.fileContents, ['arrayBufferInsert', 'arrayInsert'], 1);
990
}
991
}
992
]
993
});
994
});
995
996
stest({ description: 'too much code generated #6696', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {
997
return executeEditTestStrategy(strategy, testingServiceCollection, {
998
files: [toFile({
999
fileName: 'generate/issue-6696/heatmapServiceImpl.ts',
1000
fileContents: `/*---------------------------------------------------------------------------------------------\n * Copyright (c) Microsoft Corporation and GitHub. All rights reserved.\n *--------------------------------------------------------------------------------------------*/\n\nimport * as vscode from 'vscode';\nimport { DisposableStore } from '../../../util/vs/base/common/lifecycle';\nimport { ResourceMap } from '../../../util/vs/base/common/map';\nimport { IDocumentHeatMap, IDocumentHeatMapEntry, IHeatMapService } from '../common/heatmapService';\n\nclass DocumentHeatMap implements IDocumentHeatMap {\n\n\tprivate readonly _entries: IDocumentHeatMapEntry[] = [];\n\n\t\n\n\tgetEntries(): IDocumentHeatMapEntry[] {\n\t\treturn this._entries;\n\t}\n\n\tmarkClosed(): void {\n\n\t}\n\n\thandleSelectionChange(e: vscode.TextEditorSelectionChangeEvent): void {\n\t\tthis._entries.push({\n\t\t\ttimeStamp: Date.now(),\n\t\t\tposition: e.selections[0].active\n\t\t});\n\t}\n\n\thandleTextDocumentChange(e: vscode.TextDocumentChangeEvent): void {\n\n\t}\n}\n\nexport class HeatMapServiceImpl implements IHeatMapService {\n\n\t_serviceBrand: undefined;\n\n\tprivate readonly _store = new DisposableStore();\n\n\tprivate readonly _map = new ResourceMap<DocumentHeatMap>();\n\n\tconstructor() {\n\t\tthis._store.add(vscode.window.onDidChangeTextEditorSelection(e => {\n\t\t\tthis._ensureHeatMap(e.textEditor.document.uri).handleSelectionChange(e);\n\t\t}));\n\t\tthis._store.add(vscode.workspace.onDidChangeTextDocument(e => {\n\t\t\tthis._ensureHeatMap(e.document.uri).handleTextDocumentChange(e);\n\t\t}));\n\t\tthis._store.add(vscode.workspace.onDidCloseTextDocument(e => {\n\t\t\t//\n\t\t\tthis._map.get(e.uri)?.markClosed();\n\t\t}));\n\t}\n\n\tdispose(): void {\n\t\tthis._store.dispose();\n\t}\n\n\tgetDocumentHeatMap(uri: vscode.Uri): IDocumentHeatMap | undefined {\n\t\treturn this._map.get(uri);\n\t}\n\n\tprivate _ensureHeatMap(uri: vscode.Uri): DocumentHeatMap {\n\t\tlet heatMap = this._map.get(uri);\n\t\tif (!heatMap) {\n\t\t\theatMap = new DocumentHeatMap();\n\t\t\tthis._map.set(uri, heatMap);\n\t\t}\n\t\treturn heatMap;\n\t}\n}\n`
1001
})],
1002
queries: [
1003
{
1004
file: 'generate/issue-6696/heatmapServiceImpl.ts',
1005
selection: [13, 1, 13, 1],
1006
query: 'add constructor that takes the vscode.TextDocument',
1007
diagnostics: 'tsc',
1008
expectedIntent: 'generate',
1009
validate: async (outcome, workspace, accessor) => {
1010
assertInlineEdit(outcome);
1011
1012
const allNewText = outcome.appliedEdits.map(edit => edit.newText).join('');
1013
1014
assert.strictEqual(allNewText.includes('TextDocument'), true);
1015
assert.strictEqual(allNewText.includes('vscode.TextDocument'), true);
1016
assert.strictEqual(allNewText.includes('store.add'), false);
1017
assert.strictEqual(allNewText.includes('onDidChange'), false);
1018
}
1019
}
1020
]
1021
});
1022
});
1023
1024
stest({ description: 'issue #6163', language: 'json', nonExtensionConfigurations }, (testingServiceCollection) => {
1025
return executeEditTestStrategy(strategy, testingServiceCollection, {
1026
files: [fromFixture('generate/issue-6163/package.json')],
1027
queries: [
1028
{
1029
file: 'package.json',
1030
selection: [14, 1, 14, 1],
1031
query: 'add scripts section which invokes .esbuild.ts',
1032
expectedIntent: 'generate',
1033
validate: async (outcome, workspace, accessor) => {
1034
assertInlineEdit(outcome);
1035
assertInlineEditShape(outcome, [{
1036
line: 14,
1037
originalLength: 1,
1038
modifiedLength: undefined
1039
}]);
1040
}
1041
}
1042
]
1043
});
1044
});
1045
1046
stest({ description: 'issue #6788', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {
1047
return executeEditTestStrategy(strategy, testingServiceCollection, {
1048
files: [
1049
fromFixture('generate/issue-6788/terminalSuggestAddon.ts')
1050
],
1051
queries: [
1052
{
1053
file: 'terminalSuggestAddon.ts',
1054
selection: [551, 2, 551, 2],
1055
query: 'get common prefix length of replacementText and completion.label',
1056
diagnostics: 'tsc',
1057
expectedIntent: 'generate',
1058
validate: async (outcome, workspace, accessor) => {
1059
assertInlineEdit(outcome);
1060
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
1061
}
1062
}
1063
]
1064
});
1065
});
1066
1067
stest({ description: 'issue #6505', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {
1068
return executeEditTestStrategy(strategy, testingServiceCollection, {
1069
files: [fromFixture('generate/issue-6505/chatParserTypes.ts')],
1070
queries: [
1071
{
1072
file: 'chatParserTypes.ts',
1073
selection: [84, 1, 84, 1],
1074
query: 'add a getter isSynthetic when range.length = 0',
1075
diagnostics: 'tsc',
1076
expectedIntent: 'generate',
1077
validate: async (outcome, workspace, accessor) => {
1078
assertInlineEdit(outcome);
1079
await assertNoDiagnosticsAsync(accessor, outcome, workspace, KnownDiagnosticProviders.tscIgnoreImportErrors);
1080
const edit = assertInlineEditShape(outcome, [{
1081
line: 84,
1082
originalLength: 1,
1083
modifiedLength: undefined
1084
}]);
1085
assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['this.range.length']);
1086
}
1087
}
1088
]
1089
});
1090
});
1091
1092
stest({ description: 'issue #7772', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {
1093
return executeEditTestStrategy(strategy, testingServiceCollection, {
1094
files: [fromFixture('generate/issue-7772/builds.ts')],
1095
queries: [
1096
{
1097
file: 'builds.ts',
1098
selection: [141, 8, 141, 8],
1099
query: 'compare the `path` sha256 with the `sha256`',
1100
diagnostics: 'tsc',
1101
expectedIntent: 'generate',
1102
validate: async (outcome, workspace, accessor) => {
1103
assertInlineEdit(outcome);
1104
await assertNoDiagnosticsAsync(accessor, outcome, workspace, KnownDiagnosticProviders.tscIgnoreImportErrors);
1105
}
1106
}
1107
]
1108
});
1109
});
1110
1111
stest({ description: 'Issue #7088', language: 'powershell', nonExtensionConfigurations }, (accessor) => {
1112
return executeEditTestStrategy(strategy, accessor, {
1113
files: [toFile({
1114
filePath: fromFixture('generate/issue-7088/Microsoft.PowerShell_profile.ps1')
1115
})],
1116
queries: [
1117
{
1118
file: 'Microsoft.PowerShell_profile.ps1',
1119
selection: [3, 0, 3, 0],
1120
query: 'set alias c to code-insiders',
1121
expectedIntent: 'generate',
1122
validate: async (outcome, workspace, accessor) => {
1123
assertInlineEdit(outcome);
1124
}
1125
}
1126
]
1127
});
1128
});
1129
});
1130
});
1131
1132