Skip to content

Commit 6f0fa2d

Browse files
authored
fix code block parsing: exclude all code-blocks which dont start with… (#276)
* fix code block parsing: exclude all code-blocks which dont start with method * audit fix * format
1 parent 028b8ba commit 6f0fa2d

File tree

6 files changed

+256
-82
lines changed

6 files changed

+256
-82
lines changed

package-lock.json

Lines changed: 8 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/common/client.test.js

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,19 @@ describe('QdrantClientExtended', () => {
2424
});
2525
await client.downloadSnapshot(collectionName, snapshotName);
2626

27-
// check that fetch was called with correct arguments
28-
expect(fetchSpy).toHaveBeenCalledWith(
29-
new Request(`${url}/collections/${collectionName}/snapshots/${snapshotName}`, {
30-
method: 'GET',
31-
headers: {
32-
'Content-Disposition': `attachment; filename="${snapshotName}"`,
33-
'Content-Type': 'application/gzip',
34-
'api-key': apiKey,
35-
},
36-
}),
37-
{ signal: controller.signal }
38-
);
27+
expect(fetchSpy).toHaveBeenCalledTimes(1);
28+
29+
// Extract the actual arguments
30+
const [actualRequest, actualOptions] = fetchSpy.mock.calls[0];
31+
32+
// Check the URL and method
33+
expect(actualRequest.url).toBe(`${url}/collections/${collectionName}/snapshots/${snapshotName}`);
34+
expect(actualRequest.method).toBe('GET');
35+
36+
// Check the headers
37+
expect(actualRequest.headers.get('Content-Disposition')).toBe(`attachment; filename="${snapshotName}"`);
38+
expect(actualRequest.headers.get('Content-Type')).toBe('application/gzip');
39+
expect(actualRequest.headers.get('api-key')).toBe(apiKey);
3940
});
4041
});
4142

src/components/CodeEditorWindow/index.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ const CodeEditorWindow = ({ onChange, code, onChangeResult, setRequestCount }) =
119119
onChangeResult('code', bigIntJSON.stringify(result));
120120
setRequestCount((prev) => prev - 1);
121121
});
122+
} else {
123+
// Remove the decoration
124+
decorations = editor.deltaDecorations([decorations[0]], []);
125+
// Remove the command
126+
editor.addCommand(monaco.KeyMod.CtrlCmd + monaco.KeyCode.Enter, () => {});
122127
}
123128
});
124129
}

src/components/CodeEditorWindow/parser.test.js

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ GET collections/startups
4747
}
4848
4949
PUT collections/demo1
50-
5150
{
5251
"vectors":
5352
{
@@ -61,7 +60,7 @@ PUT collections/demo1
6160
describe('parser', () => {
6261
it('Should extract query blocks from code', () => {
6362
const requests = getCodeBlocks(testCode);
64-
expect(requests.length).toEqual(10);
63+
expect(requests.length).toEqual(8);
6564
expect(requests[1].blockStartLine).toEqual(3);
6665
expect(requests[1].blockEndLine).toEqual(10);
6766
expect(requests[2].blockStartLine).toEqual(requests[2].blockEndLine);
@@ -164,25 +163,6 @@ describe('parser', () => {
164163
// }
165164
// }
166165
block = selectBlock(blocks, 38);
167-
requests = codeParse(block.blockText);
168-
expect(requests.method).toEqual(null);
169-
expect(requests.endpoint).toEqual(null);
170-
expect(requests.error).toEqual('Add Headline or remove the line gap between json and headline (if any)');
171-
172-
// 47
173-
// PUT collections/demo1
174-
// //47 this line should be ignored
175-
// {
176-
// "vectors":
177-
// {
178-
// "size": 1,
179-
// "distance": "Cosine"
180-
// }
181-
// }
182-
block = selectBlock(blocks, 47);
183-
requests = codeParse(block.blockText);
184-
expect(requests.method).toEqual(null);
185-
expect(requests.endpoint).toEqual(null);
186-
expect(requests.error).toEqual('Add Headline or remove the line gap between json and headline (if any)');
166+
expect(block).toEqual(null);
187167
});
188168
});

src/components/EditorCommon/config/Rules.js

Lines changed: 139 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -137,53 +137,150 @@ export function selectBlock(blocks, location) {
137137
return null;
138138
}
139139

140-
export function getCodeBlocks(codeText) {
141-
const codeArray = codeText.split(/\r?\n/);
142-
const blocksArray = [];
143-
let block = { blockText: '', blockStartLine: null, blockEndLine: null };
144-
let backetcount = 0;
145-
let codeStarLine = 0;
146-
let codeEndline = 0;
147-
for (let i = 0; i < codeArray.length; ++i) {
148-
// dealing for request which have JSON Body
149-
if (codeArray[i].includes('{')) {
150-
if (backetcount === 0) {
151-
codeStarLine = i;
152-
block.blockText = codeArray[i - 1] + ' \n ';
140+
/*
141+
A function which parses text into blocks of code.
142+
143+
Each block is an HTTP request, which starts with a method (GET, POST, etc) and optionally has a JSON body.
144+
145+
Example of code block:
146+
147+
```
148+
POST collections/collection_name/points/scroll
149+
{
150+
"limit": 10,
151+
"filter": {
152+
"must":{
153+
"key": "city",
154+
"match": {
155+
"value": "Berlin"
153156
}
154-
backetcount = backetcount + codeArray[i].match(/{/gi).length;
155157
}
156-
if (codeArray[i].includes('}')) {
157-
backetcount = backetcount - codeArray[i].match(/}/gi).length;
158-
if (backetcount === 0) {
159-
codeEndline = i + 1;
160-
}
158+
}
159+
}
160+
```
161+
162+
Function should find all such blocks in the text and return them as an array of objects.
163+
Example of return value:
164+
165+
```
166+
[
167+
{
168+
blockText: '<here full text of the block>',
169+
blockStartLine: 7,
170+
blockEndLine: 16
171+
}
172+
]
173+
```
174+
175+
All other lines should be ignored.
176+
177+
Example of input text:
178+
179+
```
180+
// List all collections
181+
GET collections
182+
183+
// Get collection info
184+
GET collections/collection_name
185+
186+
// List points in a collection, using filter
187+
POST collections/collection_name/points/scroll
188+
{
189+
"limit": 10
190+
}
191+
192+
unrelated text
193+
```
194+
195+
Example of return value:
196+
197+
```
198+
[
199+
{
200+
blockText: 'GET collections',
201+
blockStartLine: 1,
202+
blockEndLine: 1
203+
},
204+
{
205+
blockText: 'GET collections/collection_name',
206+
blockStartLine: 3,
207+
blockEndLine: 3
208+
},
209+
{
210+
blockText: 'POST collections/collection_name/points/scroll\n{\n "limit": 10\n}',
211+
blockStartLine: 7,
212+
blockEndLine: 10
213+
}
214+
]
215+
216+
*/
217+
export function getCodeBlocks(text) {
218+
// Define HTTP methods
219+
const HTTP_METHODS = new Set(Method);
220+
221+
const lines = text.split(/\r?\n/);
222+
const blocks = [];
223+
const totalLines = lines.length;
224+
let i = 0;
225+
226+
while (i < totalLines) {
227+
const line = lines[i].trim();
228+
if (line === '') {
229+
i++;
230+
continue; // Ignore empty lines
161231
}
162-
if (codeStarLine) {
163-
block.blockStartLine = codeStarLine;
164-
block.blockText = block.blockText + codeArray[i] + '\n';
165-
if (codeEndline) {
166-
block.blockEndLine = codeEndline;
167-
blocksArray.push(block);
168-
codeEndline = 0;
169-
codeStarLine = 0;
170-
block = { blockText: '', blockStartLine: null, blockEndLine: null };
232+
233+
// Split the line by whitespace to get the first word
234+
const firstWord = line.split(/\s+/)[0].toUpperCase();
235+
236+
if (HTTP_METHODS.has(firstWord)) {
237+
const blockStartLine = i + 1; // 1-based indexing
238+
let blockText = lines[i];
239+
let blockEndLine = blockStartLine;
240+
241+
// Check if next line exists and starts with '{'
242+
let j = i + 1;
243+
if (j < totalLines && lines[j].trim().startsWith('{')) {
244+
// Start of JSON body
245+
const jsonLines = [];
246+
let braceCount = 0;
247+
while (j < totalLines) {
248+
const currentLine = lines[j];
249+
jsonLines.push(currentLine);
250+
// Count braces to find the matching closing brace
251+
for (const char of currentLine) {
252+
if (char === '{') braceCount++;
253+
else if (char === '}') braceCount--;
254+
}
255+
if (braceCount === 0) {
256+
break; // Found the closing brace
257+
}
258+
j++;
259+
}
260+
261+
if (braceCount !== 0) {
262+
// JSON body is not closed
263+
i++;
264+
continue;
265+
}
266+
blockText += '\n' + jsonLines.join('\n');
267+
blockEndLine = j + 1;
268+
i = j + 1;
269+
} else {
270+
// No JSON body
271+
i++;
171272
}
172-
}
173273

174-
// dealing for request which don't have JSON Body
175-
if (
176-
codeArray[i].replace(/\s/g, '').length &&
177-
backetcount === 0 &&
178-
!codeArray[i].includes('}') &&
179-
!codeArray[i + 1]?.includes('{')
180-
) {
181-
block.blockText = codeArray[i];
182-
block.blockStartLine = i + 1;
183-
block.blockEndLine = i + 1;
184-
blocksArray.push(block);
185-
block = { blockText: '', blockStartLine: null, blockEndLine: null };
274+
blocks.push({
275+
blockText: blockText,
276+
blockStartLine: blockStartLine,
277+
blockEndLine: blockEndLine,
278+
});
279+
} else {
280+
// Not a code block start
281+
i++;
186282
}
187283
}
188-
return blocksArray;
284+
285+
return blocks;
189286
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { getCodeBlocks } from '../config/Rules';
3+
4+
describe('get-code-blocks', () => {
5+
it('should return 4 code blocks', () => {
6+
const codeText = `
7+
// List all collections
8+
GET collections
9+
10+
// Get collection info
11+
GET collections/collection_name
12+
13+
// List points in a collection, using filter
14+
POST collections/collection_name/points/scroll
15+
{
16+
"limit": 10,
17+
"filter": {
18+
"must": [
19+
{
20+
"key": "city",
21+
"match": {
22+
"any": [
23+
"San Francisco",
24+
"New York",
25+
"Berlin"
26+
]
27+
}
28+
}
29+
]
30+
}
31+
}
32+
33+
lalal something unrelated
34+
35+
GET collections/collection_name/points/{point_id}
36+
`;
37+
let blocks = getCodeBlocks(codeText);
38+
39+
expect(blocks.length).toBe(4);
40+
});
41+
42+
it('should return 1 code block with correct start and end lines', () => {
43+
const codeText = `
44+
// List all collections
45+
GET collections
46+
47+
// Get collection info
48+
THIS IS NOT A CODE BLOCK
49+
{
50+
lalalal
51+
}
52+
53+
`;
54+
55+
let blocks = getCodeBlocks(codeText);
56+
57+
expect(blocks.length).toBe(1);
58+
expect(blocks[0].blockStartLine).toBe(3);
59+
expect(blocks[0].blockEndLine).toBe(3);
60+
});
61+
62+
it('incomplete block should be ignored', () => {
63+
const codeText = `
64+
// List all collections
65+
GET collections
66+
67+
// Scrolling through points
68+
POST collections/collection_name/points/scroll
69+
{
70+
"limit": 10
71+
72+
This is not a complete block
73+
74+
// Get point info
75+
GET collections/collection_name/points/{point_id}
76+
77+
`;
78+
let blocks = getCodeBlocks(codeText);
79+
80+
expect(blocks.length).toBe(2);
81+
82+
expect(blocks[0].blockStartLine).toBe(3);
83+
expect(blocks[0].blockEndLine).toBe(3);
84+
// The incomplete block should be ignored
85+
// Go straight to the next block
86+
expect(blocks[1].blockStartLine).toBe(13);
87+
expect(blocks[1].blockEndLine).toBe(13);
88+
});
89+
});

0 commit comments

Comments
 (0)