diff --git a/internal/decoder/validations/missing_required_attribute.go b/internal/decoder/validations/missing_required_attribute.go index 4485d68b..5b485899 100644 --- a/internal/decoder/validations/missing_required_attribute.go +++ b/internal/decoder/validations/missing_required_attribute.go @@ -49,7 +49,11 @@ func (mra MissingRequiredAttribute) Visit(ctx context.Context, node hclsyntax.No Summary: fmt.Sprintf("Required attribute %q not specified", name), Detail: fmt.Sprintf("An attribute named %q is required here", name), Subject: nodeType.SrcRange.Ptr(), + Extra: map[string]interface{}{ + "MissingAttribute": name, + }, }) + } } } diff --git a/internal/langserver/handlers/code_action.go b/internal/langserver/handlers/code_action.go index e062eec7..90709dc8 100644 --- a/internal/langserver/handlers/code_action.go +++ b/internal/langserver/handlers/code_action.go @@ -74,6 +74,60 @@ func (svc *service) textDocumentCodeAction(ctx context.Context, params lsp.CodeA }, }, }) + case ilsp.Quickfix: + + var extractedMissingAttrs []string + + for _, diag := range params.Context.Diagnostics { + diagExtra, ok := diag.Data.(map[string]interface{}) + if !ok { + svc.logger.Printf("Diagnostic Data does not have the expected type, skipping") + continue + } + missingAttributeRaw, found := diagExtra["MissingAttribute"] + if !found { + continue + } + missingAttribute, ok := missingAttributeRaw.(string) + if !ok { + svc.logger.Printf("MissingAttribute does not have the expected type, skipping") + continue + } + extractedMissingAttrs = append(extractedMissingAttrs, missingAttribute) + } + + if len(extractedMissingAttrs) == 0 { + svc.logger.Printf("No missing attributes found") + return ca, err + } + + var edits []lsp.TextEdit + + rng := params.Range + rng.Start.Line = rng.End.Line + rng.Start.Character = 0 + rng.End.Character = 0 + + newText := "" + + for _, missingAttr := range extractedMissingAttrs { + newText = newText + fmt.Sprintf(" %s = null\n", missingAttr) + } + + edits = append(edits, lsp.TextEdit{ + Range: rng, + NewText: newText, + }) + + ca = append(ca, lsp.CodeAction{ + Title: "Add missing attributes", + Kind: action, + Edit: lsp.WorkspaceEdit{ + Changes: map[lsp.DocumentURI][]lsp.TextEdit{ + lsp.DocumentURI(dh.FullURI()): edits, + }, + }, + }) } } diff --git a/internal/langserver/handlers/handlers_test.go b/internal/langserver/handlers/handlers_test.go index fa8d8b24..d4f1ff22 100644 --- a/internal/langserver/handlers/handlers_test.go +++ b/internal/langserver/handlers/handlers_test.go @@ -51,7 +51,7 @@ func initializeResponse(t *testing.T, commandPrefix string) string { "referencesProvider": true, "documentSymbolProvider": true, "codeActionProvider": { - "codeActionKinds": ["source.formatAll.terraform"] + "codeActionKinds": ["quickfix", "source.formatAll.terraform"] }, "codeLensProvider": {}, "documentLinkProvider": {}, diff --git a/internal/lsp/code_actions.go b/internal/lsp/code_actions.go index b4dc6bf6..c08429c1 100644 --- a/internal/lsp/code_actions.go +++ b/internal/lsp/code_actions.go @@ -12,6 +12,7 @@ import ( const ( // SourceFormatAllTerraform is a Terraform specific format code action. SourceFormatAllTerraform = "source.formatAll.terraform" + Quickfix = "quickfix" ) type CodeActions map[lsp.CodeActionKind]bool @@ -35,6 +36,7 @@ var ( // files to be formatted, but not terraform files (or vice versa). SupportedCodeActions = CodeActions{ SourceFormatAllTerraform: true, + Quickfix: true, } ) diff --git a/internal/lsp/diagnostics.go b/internal/lsp/diagnostics.go index 656652ea..bbe5bb6c 100644 --- a/internal/lsp/diagnostics.go +++ b/internal/lsp/diagnostics.go @@ -38,6 +38,7 @@ func HCLDiagsToLSP(hclDiags hcl.Diagnostics, source string) []lsp.Diagnostic { Severity: HCLSeverityToLSP(hclDiag.Severity), Source: source, Message: msg, + Data: hclDiag.Extra, }) }