Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/test/common/services/findSectionHeaders.test.ts
3296 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 * as assert from 'assert';
7
import { FindSectionHeaderOptions, ISectionHeaderFinderTarget, findSectionHeaders } from '../../../common/services/findSectionHeaders.js';
8
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
9
10
class TestSectionHeaderFinderTarget implements ISectionHeaderFinderTarget {
11
constructor(private readonly lines: string[]) { }
12
13
getLineCount(): number {
14
return this.lines.length;
15
}
16
17
getLineContent(lineNumber: number): string {
18
return this.lines[lineNumber - 1];
19
}
20
}
21
22
suite('FindSectionHeaders', () => {
23
24
ensureNoDisposablesAreLeakedInTestSuite();
25
26
test('finds simple section headers', () => {
27
const model = new TestSectionHeaderFinderTarget([
28
'regular line',
29
'MARK: My Section',
30
'another line',
31
'MARK: Another Section',
32
'last line'
33
]);
34
35
const options: FindSectionHeaderOptions = {
36
findRegionSectionHeaders: false,
37
findMarkSectionHeaders: true,
38
markSectionHeaderRegex: 'MARK:\\s*(?<label>.*)$'
39
};
40
41
const headers = findSectionHeaders(model, options);
42
assert.strictEqual(headers.length, 2);
43
44
assert.strictEqual(headers[0].text, 'My Section');
45
assert.strictEqual(headers[0].range.startLineNumber, 2);
46
assert.strictEqual(headers[0].range.endLineNumber, 2);
47
48
assert.strictEqual(headers[1].text, 'Another Section');
49
assert.strictEqual(headers[1].range.startLineNumber, 4);
50
assert.strictEqual(headers[1].range.endLineNumber, 4);
51
});
52
53
test('finds section headers with separators', () => {
54
const model = new TestSectionHeaderFinderTarget([
55
'regular line',
56
'MARK: -My Section',
57
'another line',
58
'MARK: - Another Section',
59
'last line'
60
]);
61
62
const options: FindSectionHeaderOptions = {
63
findRegionSectionHeaders: false,
64
findMarkSectionHeaders: true,
65
markSectionHeaderRegex: 'MARK:\\s*(?<separator>-?)\\s*(?<label>.*)$'
66
};
67
68
const headers = findSectionHeaders(model, options);
69
assert.strictEqual(headers.length, 2);
70
71
assert.strictEqual(headers[0].text, 'My Section');
72
assert.strictEqual(headers[0].hasSeparatorLine, true);
73
74
assert.strictEqual(headers[1].text, 'Another Section');
75
assert.strictEqual(headers[1].hasSeparatorLine, true);
76
});
77
78
test('finds multi-line section headers with separators', () => {
79
const model = new TestSectionHeaderFinderTarget([
80
'regular line',
81
'// ==========',
82
'// My Section',
83
'// ==========',
84
'code...',
85
'// ==========',
86
'// Another Section',
87
'// ==========',
88
'more code...'
89
]);
90
91
const options: FindSectionHeaderOptions = {
92
findRegionSectionHeaders: false,
93
findMarkSectionHeaders: true,
94
markSectionHeaderRegex: '^\/\/ =+\\n^\/\/ (?<label>[^\\n]+?)\\n^\/\/ =+$'
95
};
96
97
const headers = findSectionHeaders(model, options);
98
assert.strictEqual(headers.length, 2);
99
100
assert.strictEqual(headers[0].text, 'My Section');
101
assert.strictEqual(headers[0].range.startLineNumber, 2);
102
assert.strictEqual(headers[0].range.endLineNumber, 4);
103
104
assert.strictEqual(headers[1].text, 'Another Section');
105
assert.strictEqual(headers[1].range.startLineNumber, 6);
106
assert.strictEqual(headers[1].range.endLineNumber, 8);
107
});
108
109
test('handles overlapping multi-line section headers correctly', () => {
110
const model = new TestSectionHeaderFinderTarget([
111
'// ==========',
112
'// Section 1',
113
'// ==========',
114
'// ==========', // This line starts another header
115
'// Section 2',
116
'// ==========',
117
]);
118
119
const options: FindSectionHeaderOptions = {
120
findRegionSectionHeaders: false,
121
findMarkSectionHeaders: true,
122
markSectionHeaderRegex: '^\/\/ =+\\n^\/\/ (?<label>[^\\n]+?)\\n^\/\/ =+$'
123
};
124
125
const headers = findSectionHeaders(model, options);
126
assert.strictEqual(headers.length, 2);
127
128
assert.strictEqual(headers[0].text, 'Section 1');
129
assert.strictEqual(headers[0].range.startLineNumber, 1);
130
assert.strictEqual(headers[0].range.endLineNumber, 3);
131
132
assert.strictEqual(headers[1].text, 'Section 2');
133
assert.strictEqual(headers[1].range.startLineNumber, 4);
134
assert.strictEqual(headers[1].range.endLineNumber, 6);
135
});
136
137
test('section headers must be in comments when specified', () => {
138
const model = new TestSectionHeaderFinderTarget([
139
'// ==========',
140
'// Section 1', // This one is in a comment
141
'// ==========',
142
'==========', // This one isn't
143
'Section 2',
144
'=========='
145
]);
146
147
const options: FindSectionHeaderOptions = {
148
findRegionSectionHeaders: false,
149
findMarkSectionHeaders: true,
150
markSectionHeaderRegex: '^(?:\/\/ )?=+\\n^(?:\/\/ )?(?<label>[^\\n]+?)\\n^(?:\/\/ )?=+$'
151
};
152
153
// Both patterns match, but the second one should be filtered out by the token check
154
const headers = findSectionHeaders(model, options);
155
assert.strictEqual(headers[0].shouldBeInComments, true);
156
});
157
158
test('handles section headers at chunk boundaries', () => {
159
// Create enough lines to ensure we cross chunk boundaries
160
const lines: string[] = [];
161
for (let i = 0; i < 150; i++) {
162
lines.push('line ' + i);
163
}
164
165
// Add headers near the chunk boundary (chunk size is 100)
166
lines[97] = '// ==========';
167
lines[98] = '// Section 1';
168
lines[99] = '// ==========';
169
lines[100] = '// ==========';
170
lines[101] = '// Section 2';
171
lines[102] = '// ==========';
172
173
const model = new TestSectionHeaderFinderTarget(lines);
174
175
const options: FindSectionHeaderOptions = {
176
findRegionSectionHeaders: false,
177
findMarkSectionHeaders: true,
178
markSectionHeaderRegex: '^\/\/ =+\\n^\/\/ (?<label>[^\\n]+?)\\n^\/\/ =+$'
179
};
180
181
const headers = findSectionHeaders(model, options);
182
assert.strictEqual(headers.length, 2);
183
184
assert.strictEqual(headers[0].text, 'Section 1');
185
assert.strictEqual(headers[0].range.startLineNumber, 98);
186
assert.strictEqual(headers[0].range.endLineNumber, 100);
187
188
assert.strictEqual(headers[1].text, 'Section 2');
189
assert.strictEqual(headers[1].range.startLineNumber, 101);
190
assert.strictEqual(headers[1].range.endLineNumber, 103);
191
});
192
193
test('handles empty regex gracefully without infinite loop', () => {
194
const model = new TestSectionHeaderFinderTarget([
195
'line 1',
196
'line 2',
197
'line 3'
198
]);
199
200
const options: FindSectionHeaderOptions = {
201
findRegionSectionHeaders: false,
202
findMarkSectionHeaders: true,
203
markSectionHeaderRegex: '' // Empty string that would cause infinite loop
204
};
205
206
const headers = findSectionHeaders(model, options);
207
assert.strictEqual(headers.length, 0, 'Should return no headers for empty regex');
208
});
209
210
test('handles whitespace-only regex gracefully without infinite loop', () => {
211
const model = new TestSectionHeaderFinderTarget([
212
'line 1',
213
'line 2',
214
'line 3'
215
]);
216
217
const options: FindSectionHeaderOptions = {
218
findRegionSectionHeaders: false,
219
findMarkSectionHeaders: true,
220
markSectionHeaderRegex: ' ' // Whitespace that would cause infinite loop
221
};
222
223
const headers = findSectionHeaders(model, options);
224
assert.strictEqual(headers.length, 0, 'Should return no headers for whitespace-only regex');
225
});
226
227
test('correctly advances past matches without infinite loop', () => {
228
const model = new TestSectionHeaderFinderTarget([
229
'// ==========',
230
'// Section 1',
231
'// ==========',
232
'some code',
233
'// ==========',
234
'// Section 2',
235
'// ==========',
236
'more code',
237
'// ==========',
238
'// Section 3',
239
'// ==========',
240
]);
241
242
const options: FindSectionHeaderOptions = {
243
findRegionSectionHeaders: false,
244
findMarkSectionHeaders: true,
245
markSectionHeaderRegex: '^\/\/ =+\\n^\/\/ (?<label>[^\\n]+?)\\n^\/\/ =+$'
246
};
247
248
const headers = findSectionHeaders(model, options);
249
assert.strictEqual(headers.length, 3, 'Should find all three section headers');
250
assert.strictEqual(headers[0].text, 'Section 1');
251
assert.strictEqual(headers[1].text, 'Section 2');
252
assert.strictEqual(headers[2].text, 'Section 3');
253
});
254
255
test('handles consecutive section headers correctly', () => {
256
const model = new TestSectionHeaderFinderTarget([
257
'// ==========',
258
'// Section 1',
259
'// ==========',
260
'// ==========', // This line is both the end of Section 1 and start of Section 2
261
'// Section 2',
262
'// ==========',
263
]);
264
265
const options: FindSectionHeaderOptions = {
266
findRegionSectionHeaders: false,
267
findMarkSectionHeaders: true,
268
markSectionHeaderRegex: '^\/\/ =+\\n^\/\/ (?<label>[^\\n]+?)\\n^\/\/ =+$'
269
};
270
271
const headers = findSectionHeaders(model, options);
272
assert.strictEqual(headers.length, 2, 'Should find both section headers');
273
assert.strictEqual(headers[0].text, 'Section 1');
274
assert.strictEqual(headers[1].text, 'Section 2');
275
});
276
277
test('handles nested separators correctly', () => {
278
const model = new TestSectionHeaderFinderTarget([
279
'// ==============',
280
'// Major Section',
281
'// ==============',
282
'',
283
'// ----------',
284
'// Subsection',
285
'// ----------',
286
]);
287
288
const options: FindSectionHeaderOptions = {
289
findRegionSectionHeaders: false,
290
findMarkSectionHeaders: true,
291
markSectionHeaderRegex: '^\/\/ [-=]+\\n^\/\/ (?<label>[^\\n]+?)\\n^\/\/ [-=]+$'
292
};
293
294
const headers = findSectionHeaders(model, options);
295
assert.strictEqual(headers.length, 2, 'Should find both section headers');
296
assert.strictEqual(headers[0].text, 'Major Section');
297
assert.strictEqual(headers[1].text, 'Subsection');
298
});
299
300
test('handles section headers at chunk boundaries correctly', () => {
301
const lines: string[] = [];
302
// Fill up to near the chunk boundary (chunk size is 100)
303
for (let i = 0; i < 97; i++) {
304
lines.push(`line ${i}`);
305
}
306
307
// Add a section header that would cross the chunk boundary
308
lines.push('// =========='); // line 97
309
lines.push('// Section 1'); // line 98
310
lines.push('// =========='); // line 99
311
lines.push('// =========='); // line 100 (chunk boundary)
312
lines.push('// Section 2'); // line 101
313
lines.push('// =========='); // line 102
314
315
// Add more content after
316
for (let i = 103; i < 150; i++) {
317
lines.push(`line ${i}`);
318
}
319
320
const model = new TestSectionHeaderFinderTarget(lines);
321
322
const options: FindSectionHeaderOptions = {
323
findRegionSectionHeaders: false,
324
findMarkSectionHeaders: true,
325
markSectionHeaderRegex: '^\/\/ =+\\n^\/\/ (?<label>[^\\n]+?)\\n^\/\/ =+$'
326
};
327
328
const headers = findSectionHeaders(model, options);
329
assert.strictEqual(headers.length, 2, 'Should find both section headers across chunk boundary');
330
331
assert.strictEqual(headers[0].text, 'Section 1');
332
assert.strictEqual(headers[0].range.startLineNumber, 98);
333
assert.strictEqual(headers[0].range.endLineNumber, 100);
334
335
assert.strictEqual(headers[1].text, 'Section 2');
336
assert.strictEqual(headers[1].range.startLineNumber, 101);
337
assert.strictEqual(headers[1].range.endLineNumber, 103);
338
});
339
340
test('handles overlapping section headers without duplicates', () => {
341
const model = new TestSectionHeaderFinderTarget([
342
'// ==========', // Line 1
343
'// Section 1', // Line 2 - This is part of first header
344
'// ==========', // Line 3 - This is the end of first
345
'// Section 2', // Line 4 - This is not a header
346
'// ==========', // Line 5
347
'// ==========', // Line 6 - Start of second header
348
'// Section 3', // Line 7
349
'// ===========' // Line 8
350
]);
351
352
const options: FindSectionHeaderOptions = {
353
findRegionSectionHeaders: false,
354
findMarkSectionHeaders: true,
355
markSectionHeaderRegex: '^\/\/ =+\\n^\/\/ (?<label>[^\\n]+?)\\n^\/\/ =+$'
356
};
357
358
const headers = findSectionHeaders(model, options);
359
assert.strictEqual(headers.length, 2);
360
361
assert.strictEqual(headers[0].text, 'Section 1');
362
assert.strictEqual(headers[0].range.startLineNumber, 1);
363
assert.strictEqual(headers[0].range.endLineNumber, 3);
364
365
// assert.strictEqual(headers[1].text, 'Section 2');
366
// assert.strictEqual(headers[1].range.startLineNumber, 3);
367
// assert.strictEqual(headers[1].range.endLineNumber, 5);
368
369
assert.strictEqual(headers[1].text, 'Section 3');
370
assert.strictEqual(headers[1].range.startLineNumber, 6);
371
assert.strictEqual(headers[1].range.endLineNumber, 8);
372
});
373
374
test('handles partially overlapping multiline section headers correctly', () => {
375
const model = new TestSectionHeaderFinderTarget([
376
'// ================', // Line 1
377
'// Major Section 1', // Line 2
378
'// ================', // Line 3
379
'// --------', // Line 4 - Start of subsection that overlaps with end of major section
380
'// Subsection 1.1', // Line 5
381
'// --------', // Line 6
382
'// ================', // Line 7
383
'// Major Section 2', // Line 8
384
'// ================', // Line 9
385
]);
386
387
const options: FindSectionHeaderOptions = {
388
findRegionSectionHeaders: false,
389
findMarkSectionHeaders: true,
390
markSectionHeaderRegex: '^\/\/ [-=]+\\n^\/\/ (?<label>[^\\n]+?)\\n^\/\/ [-=]+$'
391
};
392
393
const headers = findSectionHeaders(model, options);
394
assert.strictEqual(headers.length, 3);
395
396
assert.strictEqual(headers[0].text, 'Major Section 1');
397
assert.strictEqual(headers[0].range.startLineNumber, 1);
398
assert.strictEqual(headers[0].range.endLineNumber, 3);
399
400
assert.strictEqual(headers[1].text, 'Subsection 1.1');
401
assert.strictEqual(headers[1].range.startLineNumber, 4);
402
assert.strictEqual(headers[1].range.endLineNumber, 6);
403
404
assert.strictEqual(headers[2].text, 'Major Section 2');
405
assert.strictEqual(headers[2].range.startLineNumber, 7);
406
assert.strictEqual(headers[2].range.endLineNumber, 9);
407
});
408
});
409
410