diff --git a/internal/hcl/diff.go b/internal/hcl/diff.go index 64a1ecab1..2788f60e9 100644 --- a/internal/hcl/diff.go +++ b/internal/hcl/diff.go @@ -62,13 +62,15 @@ func diffLines(filename string, beforeLines, afterLines source.Lines) document.C for _, group := range m.GetGroupedOpCodes(context) { for _, c := range group { - beforeStart, beforeEnd := c.I1, c.I2 - afterStart, afterEnd := c.J1, c.J2 - if c.Tag == OpEqual { continue } + // lines to pick from the original document (to delete/replace/insert to) + beforeStart, beforeEnd := c.I1, c.I2 + // lines to pick from the new document (to replace ^ with) + afterStart, afterEnd := c.J1, c.J2 + if c.Tag == OpReplace { var rng *hcl.Range var newBytes []byte @@ -121,6 +123,10 @@ func diffLines(filename string, beforeLines, afterLines source.Lines) document.C if beforeStart == beforeEnd { line := beforeLines[beforeStart] insertRng = line.Range.Ptr() + + // We're inserting to the beginning of the line + // which we represent as 0-length range in HCL + insertRng.End = insertRng.Start } else { for i, line := range beforeLines[beforeStart:beforeEnd] { if i == 0 { diff --git a/internal/hcl/diff_test.go b/internal/hcl/diff_test.go index d3cd0ff4c..247366f26 100644 --- a/internal/hcl/diff_test.go +++ b/internal/hcl/diff_test.go @@ -178,6 +178,43 @@ ccc`, }, }, }, + { + name: "insertion to existing line", + beforeCfg: `resource "aws_lambda_function" "f" { + environment { + variables = { + a = "b" + } + } +} +`, + afterCfg: `resource "aws_lambda_function" "f" { + environment { + variables = { + a = "b" + } + } +} +`, + expectedChanges: document.Changes{ + &fileChange{ + newText: " environment {\n variables = {\n a = \"b\"\n", + rng: &hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 1, Byte: 37}, + End: hcl.Pos{Line: 6, Column: 1, Byte: 107}, + }, + }, + &fileChange{ + newText: " }\n", + rng: &hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 7, Column: 1, Byte: 113}, + End: hcl.Pos{Line: 7, Column: 1, Byte: 113}, + }, + }, + }, + }, { "empty to newline", ``, diff --git a/internal/langserver/handlers/formatting_test.go b/internal/langserver/handlers/formatting_test.go index d9e9bcba7..1d18a3406 100644 --- a/internal/langserver/handlers/formatting_test.go +++ b/internal/langserver/handlers/formatting_test.go @@ -277,3 +277,126 @@ func TestLangServer_formatting_variables(t *testing.T) { ] }`) } + +func TestLangServer_formatting_diffBug(t *testing.T) { + tmpDir := TempDir(t) + + cfg := `resource "aws_lambda_function" "f" { + environment { + variables = { + a = "b" + } + } +} +` + formattedCfg := `resource "aws_lambda_function" "f" { + environment { + variables = { + a = "b" + } + } +} +` + + ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{ + TerraformCalls: &exec.TerraformMockCalls{ + PerWorkDir: map[string][]*mock.Call{ + tmpDir.Path(): { + { + Method: "Version", + Repeatability: 1, + Arguments: []interface{}{ + mock.AnythingOfType(""), + }, + ReturnArguments: []interface{}{ + version.Must(version.NewVersion("0.12.0")), + nil, + nil, + }, + }, + { + Method: "GetExecPath", + Repeatability: 1, + ReturnArguments: []interface{}{ + "", + }, + }, + { + Method: "Format", + Repeatability: 1, + Arguments: []interface{}{ + mock.AnythingOfType("*context.valueCtx"), + []byte(cfg), + }, + ReturnArguments: []interface{}{ + []byte(formattedCfg), + nil, + }, + }, + }, + }, + }, + })) + stop := ls.Start(t) + defer stop() + + ls.Call(t, &langserver.CallRequest{ + Method: "initialize", + ReqParams: fmt.Sprintf(`{ + "capabilities": {}, + "rootUri": %q, + "processId": 12345 + }`, tmpDir.URI)}) + ls.Notify(t, &langserver.CallRequest{ + Method: "initialized", + ReqParams: "{}", + }) + ls.Call(t, &langserver.CallRequest{ + Method: "textDocument/didOpen", + ReqParams: fmt.Sprintf(`{ + "textDocument": { + "version": 0, + "languageId": "terraform", + "text": `+fmt.Sprintf("%q", cfg)+`, + "uri": "%s/main.tf" + } + }`, tmpDir.URI)}) + ls.CallAndExpectResponse(t, &langserver.CallRequest{ + Method: "textDocument/formatting", + ReqParams: fmt.Sprintf(`{ + "textDocument": { + "uri": "%s/main.tf" + } + }`, tmpDir.URI)}, `{ + "jsonrpc": "2.0", + "id": 3, + "result": [ + { + "range": { + "start": { + "line": 1, + "character": 0 + }, + "end": { + "line": 5, + "character": 0 + } + }, + "newText": " environment {\n variables = {\n a = \"b\"\n" + }, + { + "range": { + "start": { + "line": 6, + "character": 0 + }, + "end": { + "line": 6, + "character": 0 + } + }, + "newText":" }\n" + } + ] + }`) +}