Skip to content

Commit df9cf5c

Browse files
committed
fix code action for inserting missing record fields for v11 of ReScript
1 parent d06ce9f commit df9cf5c

File tree

1 file changed

+141
-76
lines changed

1 file changed

+141
-76
lines changed

Diff for: server/src/codeActions.ts

+141-76
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ export let findCodeActionsInDiagnosticsMessage = ({
152152
let codeActionEtractors = [
153153
simpleTypeMismatches,
154154
didYouMeanAction,
155-
addUndefinedRecordFields,
155+
addUndefinedRecordFieldsV10,
156+
addUndefinedRecordFieldsV11,
156157
simpleConversion,
157158
applyUncurried,
158159
simpleAddMissingCases,
@@ -310,11 +311,107 @@ let wrapInSome: codeActionExtractor = ({
310311
return false;
311312
};
312313

314+
let handleUndefinedRecordFieldsAction = ({
315+
recordFieldNames,
316+
codeActions,
317+
file,
318+
range,
319+
diagnostic,
320+
}: {
321+
recordFieldNames: string[];
322+
codeActions: filesCodeActions;
323+
file: string;
324+
range: p.Range;
325+
diagnostic: p.Diagnostic;
326+
}) => {
327+
if (recordFieldNames != null) {
328+
codeActions[file] = codeActions[file] || [];
329+
330+
// The formatter outputs trailing commas automatically if the record
331+
// definition is on multiple lines, and no trailing comma if it's on a
332+
// single line. We need to adapt to this so we don't accidentally
333+
// insert an invalid comma.
334+
let multilineRecordDefinitionBody = range.start.line !== range.end.line;
335+
336+
// Let's build up the text we're going to insert.
337+
let newText = "";
338+
339+
if (multilineRecordDefinitionBody) {
340+
// If it's a multiline body, we know it looks like this:
341+
// ```
342+
// let someRecord = {
343+
// atLeastOneExistingField: string,
344+
// }
345+
// ```
346+
// We can figure out the formatting from the range the code action
347+
// gives us. We'll insert to the direct left of the ending brace.
348+
349+
// The end char is the closing brace, and it's always going to be 2
350+
// characters back from the record fields.
351+
let paddingCharacters = multilineRecordDefinitionBody
352+
? range.end.character + 2
353+
: 0;
354+
let paddingContentRecordField = Array.from({
355+
length: paddingCharacters,
356+
}).join(" ");
357+
let paddingContentEndBrace = Array.from({
358+
length: range.end.character,
359+
}).join(" ");
360+
361+
recordFieldNames.forEach((fieldName, index) => {
362+
if (index === 0) {
363+
// This adds spacing from the ending brace up to the equivalent
364+
// of the last record field name, needed for the first inserted
365+
// record field name.
366+
newText += " ";
367+
} else {
368+
// The rest of the new record field names will start from a new
369+
// line, so they need left padding all the way to the same level
370+
// as the rest of the record fields.
371+
newText += paddingContentRecordField;
372+
}
373+
374+
newText += `${fieldName}: failwith("TODO"),\n`;
375+
});
376+
377+
// Let's put the end brace back where it was (we still have it to the direct right of us).
378+
newText += `${paddingContentEndBrace}`;
379+
} else {
380+
// A single line record definition body is a bit easier - we'll just add the new fields on the same line.
381+
newText += ", ";
382+
newText += recordFieldNames
383+
.map((fieldName) => `${fieldName}: failwith("TODO")`)
384+
.join(", ");
385+
}
386+
387+
let codeAction: p.CodeAction = {
388+
title: `Add missing record fields`,
389+
edit: {
390+
changes: {
391+
[file]: insertBeforeEndingChar(range, newText),
392+
},
393+
},
394+
diagnostics: [diagnostic],
395+
kind: p.CodeActionKind.QuickFix,
396+
isPreferred: true,
397+
};
398+
399+
codeActions[file].push({
400+
range,
401+
codeAction,
402+
});
403+
404+
return true;
405+
}
406+
407+
return false;
408+
};
409+
313410
// This action handles when the compiler errors on certain fields of a record
314411
// being undefined. We then offers an action that inserts all of the record
315412
// fields, with an `assert false` dummy value. `assert false` is so applying the
316413
// code action actually compiles.
317-
let addUndefinedRecordFields: codeActionExtractor = ({
414+
let addUndefinedRecordFieldsV10: codeActionExtractor = ({
318415
array,
319416
codeActions,
320417
diagnostic,
@@ -335,85 +432,53 @@ let addUndefinedRecordFields: codeActionExtractor = ({
335432
recordFieldNames.push(...line.trim().split(" "));
336433
});
337434

338-
if (recordFieldNames != null) {
339-
codeActions[file] = codeActions[file] || [];
435+
return handleUndefinedRecordFieldsAction({
436+
recordFieldNames,
437+
codeActions,
438+
diagnostic,
439+
file,
440+
range,
441+
});
442+
}
340443

341-
// The formatter outputs trailing commas automatically if the record
342-
// definition is on multiple lines, and no trailing comma if it's on a
343-
// single line. We need to adapt to this so we don't accidentally
344-
// insert an invalid comma.
345-
let multilineRecordDefinitionBody = range.start.line !== range.end.line;
346-
347-
// Let's build up the text we're going to insert.
348-
let newText = "";
349-
350-
if (multilineRecordDefinitionBody) {
351-
// If it's a multiline body, we know it looks like this:
352-
// ```
353-
// let someRecord = {
354-
// atLeastOneExistingField: string,
355-
// }
356-
// ```
357-
// We can figure out the formatting from the range the code action
358-
// gives us. We'll insert to the direct left of the ending brace.
359-
360-
// The end char is the closing brace, and it's always going to be 2
361-
// characters back from the record fields.
362-
let paddingCharacters = multilineRecordDefinitionBody
363-
? range.end.character + 2
364-
: 0;
365-
let paddingContentRecordField = Array.from({
366-
length: paddingCharacters,
367-
}).join(" ");
368-
let paddingContentEndBrace = Array.from({
369-
length: range.end.character,
370-
}).join(" ");
371-
372-
recordFieldNames.forEach((fieldName, index) => {
373-
if (index === 0) {
374-
// This adds spacing from the ending brace up to the equivalent
375-
// of the last record field name, needed for the first inserted
376-
// record field name.
377-
newText += " ";
378-
} else {
379-
// The rest of the new record field names will start from a new
380-
// line, so they need left padding all the way to the same level
381-
// as the rest of the record fields.
382-
newText += paddingContentRecordField;
383-
}
384-
385-
newText += `${fieldName}: assert false,\n`;
386-
});
444+
return false;
445+
};
387446

388-
// Let's put the end brace back where it was (we still have it to the direct right of us).
389-
newText += `${paddingContentEndBrace}`;
390-
} else {
391-
// A single line record definition body is a bit easier - we'll just add the new fields on the same line.
392-
newText += ", ";
393-
newText += recordFieldNames
394-
.map((fieldName) => `${fieldName}: assert false`)
395-
.join(", ");
396-
}
447+
let addUndefinedRecordFieldsV11: codeActionExtractor = ({
448+
array,
449+
codeActions,
450+
diagnostic,
451+
file,
452+
index,
453+
line,
454+
range,
455+
}) => {
456+
if (line.startsWith("Some required record fields are missing:")) {
457+
let recordFieldNames = line
458+
.trim()
459+
.split("Some required record fields are missing: ")[1]
460+
?.split(" ");
397461

398-
let codeAction: p.CodeAction = {
399-
title: `Add missing record fields`,
400-
edit: {
401-
changes: {
402-
[file]: insertBeforeEndingChar(range, newText),
403-
},
404-
},
405-
diagnostics: [diagnostic],
406-
kind: p.CodeActionKind.QuickFix,
407-
isPreferred: true,
408-
};
462+
// This collects the rest of the fields if fields are printed on
463+
// multiple lines.
464+
let stop = false;
465+
array.slice(index + 1).forEach((line) => {
466+
if (stop) return;
409467

410-
codeActions[file].push({
411-
range,
412-
codeAction,
413-
});
468+
recordFieldNames.push(...line.trim().split(".")[0].split(" "));
414469

415-
return true;
416-
}
470+
if (line.includes(".")) {
471+
stop = true;
472+
}
473+
});
474+
475+
return handleUndefinedRecordFieldsAction({
476+
recordFieldNames,
477+
codeActions,
478+
diagnostic,
479+
file,
480+
range,
481+
});
417482
}
418483

419484
return false;

0 commit comments

Comments
 (0)