Skip to content

Commit

Permalink
formatting: Return diff hunks instead of whole file content
Browse files Browse the repository at this point in the history
  • Loading branch information
radeksimko committed Apr 22, 2020
1 parent 1f6733c commit 49a288a
Show file tree
Hide file tree
Showing 14 changed files with 1,165 additions and 121 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/hashicorp/hcl/v2 v2.3.0
github.com/hashicorp/terraform-json v0.4.0
github.com/mitchellh/cli v1.0.0
github.com/pmezard/go-difflib v1.0.0
github.com/sourcegraph/go-lsp v0.0.0-20200117082640-b19bb38222e2
github.com/zclconf/go-cty v1.2.1
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
Expand Down
120 changes: 120 additions & 0 deletions internal/hcl/diff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package hcl

import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform-ls/internal/filesystem"
"github.com/hashicorp/terraform-ls/internal/source"
"github.com/pmezard/go-difflib/difflib"
)

type fileChange struct {
newText string
rng hcl.Range
opCode difflib.OpCode
}

func (ch *fileChange) Text() string {
return ch.newText
}

func (ch *fileChange) Range() hcl.Range {
return ch.rng
}

const (
OpReplace = 'r'
OpDelete = 'd'
OpInsert = 'i'
OpEqual = 'e'
)

// Diff calculates difference between File's content
// and after byte sequence and returns it as filesystem.FileChanges
func Diff(f filesystem.File, after []byte) filesystem.FileChanges {
return diffLines(f.Filename(),
source.MakeSourceLines(f.Filename(), f.Text()),
source.MakeSourceLines(f.Filename(), after))
}

// diffLines calculates difference between two source.Lines
// and returns them as filesystem.FileChanges
func diffLines(filename string, beforeLines, afterLines source.Lines) filesystem.FileChanges {
context := 3

m := difflib.NewMatcher(
source.StringLines(beforeLines),
source.StringLines(afterLines))

changes := make(filesystem.FileChanges, 0)

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
}

if c.Tag == OpReplace {
var rng hcl.Range
var newBytes []byte

for i, line := range beforeLines[beforeStart:beforeEnd] {
if i == 0 {
rng = line.Range()
continue
}
rng.End = line.Range().End
}

for _, line := range afterLines[afterStart:afterEnd] {
newBytes = append(newBytes, line.Bytes()...)
}

changes = append(changes, &fileChange{
newText: string(newBytes),
rng: rng,
})
continue
}

if c.Tag == OpDelete {
var deleteRng hcl.Range
for i, line := range beforeLines[beforeStart:beforeEnd] {
if i == 0 {
deleteRng = line.Range()
continue
}
deleteRng.End = line.Range().End
}
changes = append(changes, &fileChange{
newText: "",
rng: deleteRng,
opCode: c,
})
continue
}

if c.Tag == OpInsert {
var insertRng hcl.Range
insertRng.Start = beforeLines[beforeStart-1].Range().End
insertRng.End = beforeLines[beforeStart-1].Range().End
var newBytes []byte

for _, line := range afterLines[afterStart:afterEnd] {
newBytes = append(newBytes, line.Bytes()...)
}

changes = append(changes, &fileChange{
newText: string(newBytes),
rng: insertRng,
})
continue
}

}
}

return changes
}
201 changes: 201 additions & 0 deletions internal/hcl/diff_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package hcl

import (
"fmt"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform-ls/internal/filesystem"
"github.com/hashicorp/terraform-ls/internal/source"
"github.com/pmezard/go-difflib/difflib"
)

func TestDiff(t *testing.T) {
testCases := []struct {
name string
beforeCfg, afterCfg string
expectedChanges filesystem.FileChanges
}{
{
"no-op",
`aaa
bbb
ccc`,
`aaa
bbb
ccc`,
filesystem.FileChanges{},
},
{
"two separate lines replaced",
`resource "aws_vpc" "name" {
cidr_block = "sdf"
tags = {
"key" = "value"
sdfasd = 1
s = 3
}
}`,
`resource "aws_vpc" "name" {
cidr_block = "sdf"
tags = {
"key" = "value"
sdfasd = 1
s = 3
}
}`,
filesystem.FileChanges{
&fileChange{
newText: ` "key" = "value"
`,
rng: hcl.Range{
Filename: "test.tf",
Start: hcl.Pos{Line: 4, Column: 1, Byte: 60},
End: hcl.Pos{Line: 5, Column: 1, Byte: 80},
},
},
&fileChange{
newText: ` s = 3
`,
rng: hcl.Range{
Filename: "test.tf",
Start: hcl.Pos{Line: 6, Column: 1, Byte: 95},
End: hcl.Pos{Line: 7, Column: 1, Byte: 105},
},
},
},
},
{
"whitespace shrinking",
`resource "aws_vpc" "name" {
cidr_block = "sdf"
tags = {
"key" = "value"
sdfasd = 1
s = 3
}
}`,
`resource "aws_vpc" "name" {
cidr_block = "sdf"
tags = {
"key" = "value"
sdfasd = 1
s = 3
}
}`,
filesystem.FileChanges{
&fileChange{
newText: "",
rng: hcl.Range{
Filename: "test.tf",
Start: hcl.Pos{Line: 7, Column: 1, Byte: 111},
End: hcl.Pos{Line: 9, Column: 1, Byte: 113},
},
},
},
},
{
"trailing whitespace removal",
`resource "aws_vpc" "name" {
}`,
`resource "aws_vpc" "name" {
}`,
filesystem.FileChanges{
&fileChange{
newText: "\n",
rng: hcl.Range{
Filename: "test.tf",
Start: hcl.Pos{Line: 2, Column: 1, Byte: 28},
End: hcl.Pos{Line: 3, Column: 1, Byte: 31},
},
},
},
},
{
"new line insertion",
`resource "aws_vpc" "name" {}`,
`resource "aws_vpc" "name" {
}`,
filesystem.FileChanges{
&fileChange{
newText: `resource "aws_vpc" "name" {
}`,
rng: hcl.Range{
Filename: "test.tf",
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 29, Byte: 28},
},
},
},
},
{
"new line insertion at EOF",
`resource "aws_vpc" "name" {
}
`,
`resource "aws_vpc" "name" {
}
`,
filesystem.FileChanges{
&fileChange{
newText: "\n",
rng: hcl.Range{
Start: hcl.Pos{Line: 3, Column: 1, Byte: 30},
End: hcl.Pos{Line: 3, Column: 1, Byte: 30},
},
},
},
},
{
"line insertion",
`resource "aws_vpc" "name" {
attr1 = "one"
attr3 = "three"
}`,
`resource "aws_vpc" "name" {
attr1 = "one"
attr2 = "two"
attr3 = "three"
}`,
filesystem.FileChanges{
&fileChange{
newText: ` attr2 = "two"
`,
rng: hcl.Range{
Filename: "test.tf",
Start: hcl.Pos{Line: 3, Column: 1, Byte: 44},
End: hcl.Pos{Line: 4, Column: 1, Byte: 45},
},
},
},
},
}

for i, tc := range testCases {
t.Run(fmt.Sprintf("%d-%s", i, tc.name), func(t *testing.T) {
linesBefore := source.MakeSourceLines("test.tf",
[]byte(tc.beforeCfg))
linesAfter := source.MakeSourceLines("test.tf",
[]byte(tc.afterCfg))

changes := diffLines("test.tf", linesBefore, linesAfter)

opts := cmp.Options{
cmp.AllowUnexported(fileChange{}),
cmpopts.IgnoreTypes(difflib.OpCode{}),
}

if diff := cmp.Diff(tc.expectedChanges, changes, opts...); diff != "" {
t.Fatalf("Changes don't match: %s", diff)
}
})
}
}
34 changes: 0 additions & 34 deletions internal/hcl/hcl.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
package hcl

import (
"fmt"

"github.com/hashicorp/hcl/v2"
hcllib "github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/terraform-ls/internal/filesystem"
)

type File interface {
BlockAtPosition(filesystem.FilePosition) (*hcllib.Block, hcllib.Pos, error)
Diff([]byte) (filesystem.FileChanges, error)
}

type file struct {
Expand Down Expand Up @@ -49,36 +45,6 @@ func (f *file) BlockAtPosition(filePos filesystem.FilePosition) (*hcllib.Block,
return b, pos, nil
}

func (f *file) Diff(target []byte) (filesystem.FileChanges, error) {
var changes filesystem.FileChanges

ast, _ := f.ast()
body, ok := ast.Body.(*hclsyntax.Body)
if !ok {
return nil, fmt.Errorf("invalid configuration format: %T", ast.Body)
}

changes = append(changes, &fileChange{
newText: target,
rng: body.Range(),
})

return changes, nil
}

type fileChange struct {
newText []byte
rng hcl.Range
}

func (ch *fileChange) Text() string {
return string(ch.newText)
}

func (ch *fileChange) Range() hcl.Range {
return ch.rng
}

func (f *file) blockAtPosition(pos hcllib.Pos) (*hcllib.Block, error) {
ast, _ := f.ast()

Expand Down
Loading

0 comments on commit 49a288a

Please sign in to comment.