Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/linkify/test/node/linkifier.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 { suite, test } from 'vitest';
7
import { CancellationToken } from '../../../../util/vs/base/common/cancellation';
8
import { coalesceParts, LinkifiedPart, LinkifyLocationAnchor } from '../../common/linkifiedText';
9
import { ILinkifier, LinkifierContext } from '../../common/linkifyService';
10
import { assertPartsEqual, createTestLinkifierService, workspaceFile } from './util';
11
12
const emptyContext: LinkifierContext = { requestId: undefined, references: [] };
13
14
suite('Stateful Linkifier', () => {
15
16
async function runLinkifier(linkifier: ILinkifier, parts: readonly string[]): Promise<LinkifiedPart[]> {
17
const out: LinkifiedPart[] = [];
18
for (const part of parts) {
19
out.push(...(await linkifier.append(part, CancellationToken.None)).parts);
20
}
21
22
out.push(...(await linkifier.flush(CancellationToken.None))?.parts ?? []);
23
return coalesceParts(out);
24
}
25
26
test(`Should not linkify inside of markdown code blocks`, async () => {
27
const linkifier = createTestLinkifierService(
28
'file.ts',
29
'src/file.ts',
30
).createLinkifier(emptyContext);
31
32
const parts: string[] = [
33
'[file.ts](file.ts)',
34
'\n',
35
'```',
36
'\n',
37
'[file.ts](file.ts)',
38
'\n',
39
'```',
40
'\n',
41
'[file.ts](file.ts)',
42
];
43
44
const result = await runLinkifier(linkifier, parts);
45
assertPartsEqual(result, [
46
new LinkifyLocationAnchor(workspaceFile('file.ts')),
47
['\n',
48
'```',
49
'\n',
50
'[file.ts](file.ts)', // no linkification here
51
'\n',
52
'```',
53
'\n'
54
].join(''),
55
new LinkifyLocationAnchor(workspaceFile('file.ts'))
56
]);
57
});
58
59
test(`Should handle link tokens`, async () => {
60
const linkifier = createTestLinkifierService(
61
'file.ts',
62
'src/file.ts',
63
).createLinkifier(emptyContext);
64
65
{
66
// Tokens for `[file.ts](file.ts)`
67
const parts: string[] = [
68
'[file',
69
'.ts',
70
'](',
71
'file',
72
'.ts',
73
')',
74
];
75
76
const result = await runLinkifier(linkifier, parts);
77
assertPartsEqual(result, [
78
new LinkifyLocationAnchor(workspaceFile('file.ts')),
79
]);
80
}
81
{
82
// Another potential tokenization for `[file.ts](file.ts)`
83
const parts: string[] = [
84
'[',
85
'file',
86
'.ts',
87
'](',
88
'file',
89
'.ts',
90
')',
91
];
92
93
const result = await runLinkifier(linkifier, parts);
94
assertPartsEqual(result, [
95
new LinkifyLocationAnchor(workspaceFile('file.ts')),
96
]);
97
}
98
{
99
// With leading space potential tokenization for `[file.ts](file.ts)`
100
const parts: string[] = [
101
' [',
102
'file',
103
'.ts',
104
'](',
105
'file',
106
'.ts',
107
')',
108
];
109
110
const result = await runLinkifier(linkifier, parts);
111
assertPartsEqual(result, [
112
' ',
113
new LinkifyLocationAnchor(workspaceFile('file.ts')),
114
]);
115
}
116
});
117
118
test(`Should handle inline code with spaces`, async () => {
119
const linkText = 'LINK';
120
121
const linkifier = createTestLinkifierService(
122
'file.ts',
123
'src/file.ts',
124
).createLinkifier(emptyContext, [
125
{
126
create: () => ({
127
async linkify(newText) {
128
if (/\s*`[^`]+`\s*/.test(newText)) {
129
return { parts: [linkText] };
130
}
131
return;
132
},
133
})
134
}
135
]);
136
137
const parts: string[] = [
138
'`code ',
139
' more`',
140
];
141
142
const result = await runLinkifier(linkifier, parts);
143
assertPartsEqual(result, [
144
linkText
145
]);
146
});
147
148
test(`Should not linkify inside of markdown fenced code block containing fenced code blocks (#5708)`, async () => {
149
const linkifier = createTestLinkifierService(
150
'file.ts',
151
).createLinkifier(emptyContext);
152
153
const parts: string[] = [
154
'[file.ts](file.ts)',
155
'\n',
156
'```md',
157
'\n',
158
'[file.ts](file.ts)',
159
'\n',
160
'\t```ts',
161
'\n',
162
`\t1 + 1`,
163
'\n',
164
'\t[file.ts](file.ts)',
165
'\n',
166
'\t```',
167
'\n',
168
'[file.ts](file.ts)',
169
'\n',
170
'```',
171
'\n',
172
'[file.ts](file.ts)',
173
];
174
175
const result = await runLinkifier(linkifier, parts);
176
assertPartsEqual(result, [
177
new LinkifyLocationAnchor(workspaceFile('file.ts')),
178
[
179
'\n',
180
'```md',
181
'\n',
182
'[file.ts](file.ts)', // No linkification
183
'\n',
184
'\t```ts',
185
'\n',
186
`\t1 + 1`,
187
'\n',
188
'\t[file.ts](file.ts)', // No linkification
189
'\n',
190
'\t```',
191
'\n',
192
'[file.ts](file.ts)', // No linkification
193
'\n',
194
'```',
195
'\n',
196
].join(''),
197
new LinkifyLocationAnchor(workspaceFile('file.ts'))
198
]);
199
});
200
201
test(`Should not linkify inside tilde markdown code blocks`, async () => {
202
const linkifier = createTestLinkifierService(
203
'file.ts',
204
).createLinkifier(emptyContext);
205
206
const parts: string[] = [
207
'[file.ts](file.ts)',
208
'\n',
209
'~~~',
210
'\n',
211
'[file.ts](file.ts)',
212
'\n',
213
'~~~',
214
'\n',
215
'[file.ts](file.ts)',
216
];
217
218
const result = await runLinkifier(linkifier, parts);
219
assertPartsEqual(result, [
220
new LinkifyLocationAnchor(workspaceFile('file.ts')),
221
[
222
'\n',
223
'~~~',
224
'\n',
225
'[file.ts](file.ts)', // no linkification here
226
'\n',
227
'~~~',
228
'\n',
229
].join(''),
230
new LinkifyLocationAnchor(workspaceFile('file.ts'))
231
]);
232
});
233
234
test(`Should correctly handle fenced code blocks split over multiple parts`, async () => {
235
const linkifier = createTestLinkifierService(
236
'file.ts',
237
).createLinkifier(emptyContext);
238
239
const parts: string[] = [
240
'[file.ts](file.ts)',
241
'\n',
242
'```ts',
243
'\n',
244
'[file.ts](file.ts)',
245
'\n``', // Split ending backtick
246
'`',
247
'\n',
248
'[file.ts](file.ts)',
249
];
250
251
const result = await runLinkifier(linkifier, parts);
252
assertPartsEqual(result, [
253
new LinkifyLocationAnchor(workspaceFile('file.ts')),
254
[
255
'\n',
256
'```ts',
257
'\n',
258
'[file.ts](file.ts)', // no linkification here
259
'\n',
260
'```',
261
'\n',
262
].join(''),
263
new LinkifyLocationAnchor(workspaceFile('file.ts'))
264
]);
265
});
266
267
test(`Should correctly handle fenced code blocks when opening fence is split`, async () => {
268
const linkifier = createTestLinkifierService(
269
'file.ts',
270
).createLinkifier(emptyContext);
271
272
const parts: string[] = [
273
'[file.ts](file.ts)',
274
'\n',
275
'``', // Split opening backticks
276
'`ts',
277
'\n',
278
'[file.ts](file.ts)',
279
'\n``', // Split ending backtick
280
'`',
281
'\n',
282
'[file.ts](file.ts)',
283
];
284
285
const result = await runLinkifier(linkifier, parts);
286
assertPartsEqual(result, [
287
new LinkifyLocationAnchor(workspaceFile('file.ts')),
288
[
289
'\n',
290
'```ts',
291
'\n',
292
'[file.ts](file.ts)', // no linkification here
293
'\n',
294
'```',
295
'\n',
296
].join(''),
297
new LinkifyLocationAnchor(workspaceFile('file.ts'))
298
]);
299
});
300
301
test(`Should de-linkify links without schemes`, async () => {
302
const linkifier = createTestLinkifierService().createLinkifier(emptyContext);
303
304
const parts: string[] = [
305
'[text](file.ts) [`text`](/file.ts)',
306
];
307
308
const result = await runLinkifier(linkifier, parts);
309
assertPartsEqual(result, [
310
'text `text`'
311
]);
312
});
313
314
test(`Should de-linkify Windows absolute paths with drive letters`, async () => {
315
const linkifier = createTestLinkifierService().createLinkifier(emptyContext);
316
317
const parts: string[] = [
318
'[text](c:\\src\\file.ts)',
319
];
320
321
const result = await runLinkifier(linkifier, parts);
322
assertPartsEqual(result, [
323
'text'
324
]);
325
});
326
327
test(`Should not unlinkify text inside of code blocks`, async () => {
328
const linkifier = createTestLinkifierService().createLinkifier(emptyContext);
329
330
const parts: string[] = [
331
'```md\n',
332
`[g](x)\n`,
333
'```',
334
];
335
336
const result = await runLinkifier(linkifier, parts);
337
assertPartsEqual(result, [
338
[
339
'```md\n',
340
`[g](x)\n`,
341
'```'
342
].join('')
343
]);
344
});
345
346
test(`Should not unlikify text inside of inline code`, async () => {
347
{
348
const linkifier = createTestLinkifierService().createLinkifier(emptyContext);
349
const result = await runLinkifier(linkifier, [
350
'a `J[g](x)` b',
351
]);
352
assertPartsEqual(result, [
353
'a `J[g](x)` b'
354
]);
355
}
356
{
357
const linkifier = createTestLinkifierService().createLinkifier(emptyContext);
358
const result = await runLinkifier(linkifier, [
359
'a `b [c](d) e` f',
360
]);
361
assertPartsEqual(result, [
362
'a `b [c](d) e` f'
363
]);
364
}
365
});
366
367
test(`Should not unlikify text inside of math blocks code`, async () => {
368
{
369
const linkifier = createTestLinkifierService(
370
'file1.ts',
371
'file2.ts',
372
).createLinkifier(emptyContext);
373
374
const result = await runLinkifier(linkifier, [
375
'[file1.ts](file1.ts)\n',
376
'$$\n',
377
`J[g](x)\n`,
378
'$$\n',
379
'[file2.ts](file2.ts)'
380
]);
381
assertPartsEqual(result, [
382
new LinkifyLocationAnchor(workspaceFile('file1.ts')),
383
[
384
'',
385
'$$',
386
'J[g](x)',
387
'$$',
388
'',
389
].join('\n'),
390
new LinkifyLocationAnchor(workspaceFile('file2.ts')),
391
]);
392
}
393
});
394
395
test(`Should not touch code inside of inline math equations`, async () => {
396
{
397
const linkifier = createTestLinkifierService().createLinkifier(emptyContext);
398
399
const result = await runLinkifier(linkifier, [
400
'a $J[g](x)$ b',
401
]);
402
assertPartsEqual(result, [
403
'a $J[g](x)$ b'
404
]);
405
}
406
{
407
const linkifier = createTestLinkifierService().createLinkifier(emptyContext);
408
409
const result = await runLinkifier(linkifier, [
410
'a $c [g](x) d$ x',
411
]);
412
assertPartsEqual(result, [
413
'a $c [g](x) d$ x',
414
]);
415
}
416
});
417
});
418
419