From 9d63ba9da042c1f277373e9d1ec690c8e9e38927 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Mon, 11 Sep 2023 13:15:57 +0200 Subject: [PATCH 1/2] fix code action for inserting missing record fields for v11 of ReScript --- server/src/codeActions.ts | 217 +++++++++++++++++++++++++------------- 1 file changed, 141 insertions(+), 76 deletions(-) diff --git a/server/src/codeActions.ts b/server/src/codeActions.ts index 4252b0a55..045f06276 100644 --- a/server/src/codeActions.ts +++ b/server/src/codeActions.ts @@ -152,7 +152,8 @@ export let findCodeActionsInDiagnosticsMessage = ({ let codeActionEtractors = [ simpleTypeMismatches, didYouMeanAction, - addUndefinedRecordFields, + addUndefinedRecordFieldsV10, + addUndefinedRecordFieldsV11, simpleConversion, applyUncurried, simpleAddMissingCases, @@ -310,11 +311,107 @@ let wrapInSome: codeActionExtractor = ({ return false; }; +let handleUndefinedRecordFieldsAction = ({ + recordFieldNames, + codeActions, + file, + range, + diagnostic, +}: { + recordFieldNames: string[]; + codeActions: filesCodeActions; + file: string; + range: p.Range; + diagnostic: p.Diagnostic; +}) => { + if (recordFieldNames != null) { + codeActions[file] = codeActions[file] || []; + + // The formatter outputs trailing commas automatically if the record + // definition is on multiple lines, and no trailing comma if it's on a + // single line. We need to adapt to this so we don't accidentally + // insert an invalid comma. + let multilineRecordDefinitionBody = range.start.line !== range.end.line; + + // Let's build up the text we're going to insert. + let newText = ""; + + if (multilineRecordDefinitionBody) { + // If it's a multiline body, we know it looks like this: + // ``` + // let someRecord = { + // atLeastOneExistingField: string, + // } + // ``` + // We can figure out the formatting from the range the code action + // gives us. We'll insert to the direct left of the ending brace. + + // The end char is the closing brace, and it's always going to be 2 + // characters back from the record fields. + let paddingCharacters = multilineRecordDefinitionBody + ? range.end.character + 2 + : 0; + let paddingContentRecordField = Array.from({ + length: paddingCharacters, + }).join(" "); + let paddingContentEndBrace = Array.from({ + length: range.end.character, + }).join(" "); + + recordFieldNames.forEach((fieldName, index) => { + if (index === 0) { + // This adds spacing from the ending brace up to the equivalent + // of the last record field name, needed for the first inserted + // record field name. + newText += " "; + } else { + // The rest of the new record field names will start from a new + // line, so they need left padding all the way to the same level + // as the rest of the record fields. + newText += paddingContentRecordField; + } + + newText += `${fieldName}: failwith("TODO"),\n`; + }); + + // Let's put the end brace back where it was (we still have it to the direct right of us). + newText += `${paddingContentEndBrace}`; + } else { + // A single line record definition body is a bit easier - we'll just add the new fields on the same line. + newText += ", "; + newText += recordFieldNames + .map((fieldName) => `${fieldName}: failwith("TODO")`) + .join(", "); + } + + let codeAction: p.CodeAction = { + title: `Add missing record fields`, + edit: { + changes: { + [file]: insertBeforeEndingChar(range, newText), + }, + }, + diagnostics: [diagnostic], + kind: p.CodeActionKind.QuickFix, + isPreferred: true, + }; + + codeActions[file].push({ + range, + codeAction, + }); + + return true; + } + + return false; +}; + // This action handles when the compiler errors on certain fields of a record // being undefined. We then offers an action that inserts all of the record // fields, with an `assert false` dummy value. `assert false` is so applying the // code action actually compiles. -let addUndefinedRecordFields: codeActionExtractor = ({ +let addUndefinedRecordFieldsV10: codeActionExtractor = ({ array, codeActions, diagnostic, @@ -335,85 +432,53 @@ let addUndefinedRecordFields: codeActionExtractor = ({ recordFieldNames.push(...line.trim().split(" ")); }); - if (recordFieldNames != null) { - codeActions[file] = codeActions[file] || []; + return handleUndefinedRecordFieldsAction({ + recordFieldNames, + codeActions, + diagnostic, + file, + range, + }); + } - // The formatter outputs trailing commas automatically if the record - // definition is on multiple lines, and no trailing comma if it's on a - // single line. We need to adapt to this so we don't accidentally - // insert an invalid comma. - let multilineRecordDefinitionBody = range.start.line !== range.end.line; - - // Let's build up the text we're going to insert. - let newText = ""; - - if (multilineRecordDefinitionBody) { - // If it's a multiline body, we know it looks like this: - // ``` - // let someRecord = { - // atLeastOneExistingField: string, - // } - // ``` - // We can figure out the formatting from the range the code action - // gives us. We'll insert to the direct left of the ending brace. - - // The end char is the closing brace, and it's always going to be 2 - // characters back from the record fields. - let paddingCharacters = multilineRecordDefinitionBody - ? range.end.character + 2 - : 0; - let paddingContentRecordField = Array.from({ - length: paddingCharacters, - }).join(" "); - let paddingContentEndBrace = Array.from({ - length: range.end.character, - }).join(" "); - - recordFieldNames.forEach((fieldName, index) => { - if (index === 0) { - // This adds spacing from the ending brace up to the equivalent - // of the last record field name, needed for the first inserted - // record field name. - newText += " "; - } else { - // The rest of the new record field names will start from a new - // line, so they need left padding all the way to the same level - // as the rest of the record fields. - newText += paddingContentRecordField; - } - - newText += `${fieldName}: assert false,\n`; - }); + return false; +}; - // Let's put the end brace back where it was (we still have it to the direct right of us). - newText += `${paddingContentEndBrace}`; - } else { - // A single line record definition body is a bit easier - we'll just add the new fields on the same line. - newText += ", "; - newText += recordFieldNames - .map((fieldName) => `${fieldName}: assert false`) - .join(", "); - } +let addUndefinedRecordFieldsV11: codeActionExtractor = ({ + array, + codeActions, + diagnostic, + file, + index, + line, + range, +}) => { + if (line.startsWith("Some required record fields are missing:")) { + let recordFieldNames = line + .trim() + .split("Some required record fields are missing: ")[1] + ?.split(" "); - let codeAction: p.CodeAction = { - title: `Add missing record fields`, - edit: { - changes: { - [file]: insertBeforeEndingChar(range, newText), - }, - }, - diagnostics: [diagnostic], - kind: p.CodeActionKind.QuickFix, - isPreferred: true, - }; + // This collects the rest of the fields if fields are printed on + // multiple lines. + let stop = false; + array.slice(index + 1).forEach((line) => { + if (stop) return; - codeActions[file].push({ - range, - codeAction, - }); + recordFieldNames.push(...line.trim().split(".")[0].split(" ")); - return true; - } + if (line.includes(".")) { + stop = true; + } + }); + + return handleUndefinedRecordFieldsAction({ + recordFieldNames, + codeActions, + diagnostic, + file, + range, + }); } return false; From 536608e524f61aebe703cbbaa614be604748744b Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Mon, 11 Sep 2023 13:17:32 +0200 Subject: [PATCH 2/2] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index be71a6f22..d6f23f1d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - Add code action for wrapping patterns where option is expected with `Some`. https://github.com/rescript-lang/rescript-vscode/pull/806 - Better completion from identifiers with inferred types. https://github.com/rescript-lang/rescript-vscode/pull/808 - Make suggested template functions async when the target function returns a promise. https://github.com/rescript-lang/rescript-vscode/pull/816 +- Fix code action for inserting undefined record fields in ReScript v11. https://github.com/rescript-lang/rescript-vscode/pull/817 #### :nail_care: Polish