Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,24 @@ describe('Editor actions provider', () => {
);
});

it('orders method suggestions with GET first and DELETE last using sortText', async () => {
// Monaco sorts completion items by sortText, falling back to label. Without
// an explicit sortText, alphabetical sorting puts DELETE first (#259251).
mockGetParsedRequests.mockResolvedValue([]);
const completionItems = await editorActionsProvider.provideCompletionItems(
mockModel,
mockPosition,
mockContext
);
const sortedByMonaco = [...(completionItems?.suggestions ?? [])].sort((a, b) =>
String(a.sortText ?? a.label).localeCompare(String(b.sortText ?? b.label))
);
const orderedLabels = sortedByMonaco.map((s) => s.label);
expect(orderedLabels[0]).toBe('GET');
expect(orderedLabels[orderedLabels.length - 1]).toBe('DELETE');
expect(orderedLabels).toEqual(['GET', 'POST', 'PUT', 'PATCH', 'HEAD', 'DELETE']);
});

it('returns completion items for url path if method already typed in', async () => {
// mock a parsed request that only has a method
mockGetParsedRequests.mockResolvedValue([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,27 @@ const filterTermsWithoutName = (terms: ResultTerm[]): ResultTerm[] =>
terms.filter((term) => term.name !== undefined && term.name !== '');

/*
* This function returns an array of completion items for the request method
* This function returns an array of completion items for the request method.
*
* The order is deliberate: Monaco sorts completion items by `sortText` and
* falls back to alphabetical label sorting, which would otherwise put DELETE
* first (#259251). GET is the safest verb to accept by default and DELETE
* the most destructive, so we pin GET first and DELETE last.
*/
const autocompleteMethods = ['GET', 'PUT', 'POST', 'DELETE', 'HEAD', 'PATCH'];
const autocompleteMethods = ['GET', 'POST', 'PUT', 'PATCH', 'HEAD', 'DELETE'];
export const getMethodCompletionItems = (
model: monaco.editor.ITextModel,
position: monaco.Position
): monaco.languages.CompletionItem[] => {
// get the word before suggestions to replace when selecting a suggestion from the list
const wordUntilPosition = model.getWordUntilPosition(position);
return autocompleteMethods.map((method) => ({
return autocompleteMethods.map((method, index) => ({
label: method,
insertText: method,
detail: i18nTexts.method,
// only used to configure the icon
kind: monaco.languages.CompletionItemKind.Constant,
sortText: String(index),
range: {
// replace the whole word with the suggestion
startColumn: wordUntilPosition.startColumn,
Expand Down
Loading