Skip to content

fix issues with diagnostic deduplication #36825

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 13 additions & 20 deletions internal/tfdiags/compare_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ func TestDiagnosticComparer(t *testing.T) {
Severity: hcl.DiagError,
Summary: "error",
Detail: "this is an error",
Subject: &hcl.Range{
Filename: "foobar.tf",
Start: hcl.Pos{
Line: 0,
Column: 0,
Byte: 0,
},
End: hcl.Pos{
Line: 1,
Column: 1,
Byte: 1,
},
},
}

cases := map[string]struct {
Expand Down Expand Up @@ -81,26 +94,6 @@ func TestDiagnosticComparer(t *testing.T) {
}(),
expectDiff: true,
},
"reports that diagnostics match even if sources (Context) are different; ignored in simple comparison": {
diag1: hclDiagnostic{&baseError},
diag2: func() Diagnostic {
d := baseError
d.Context = &hcl.Range{
Filename: "foobar.tf",
Start: hcl.Pos{
Line: 0,
Column: 0,
Byte: 0,
},
End: hcl.Pos{
Line: 1,
Column: 1,
Byte: 1,
},
}
return hclDiagnostic{&d}
}(),
},
}

for tn, tc := range cases {
Expand Down
2 changes: 1 addition & 1 deletion internal/tfdiags/diagnostics.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ func (diags Diagnostics) AppendWithoutDuplicates(newDiags ...Diagnostic) Diagnos
if !ok {
// append what we cannot compare
diags = diags.Append(newItem)
continue
}

if diags.ContainsDiagnostic(cd) {
continue
}
Expand Down
248 changes: 248 additions & 0 deletions internal/tfdiags/diagnostics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -542,3 +542,251 @@ func TestWarnings(t *testing.T) {
})
}
}

func TestAppendWithoutDuplicates(t *testing.T) {
type diagFlat struct {
Severity Severity
Summary string
Detail string
Subject *SourceRange
Context *SourceRange
}

tests := map[string]struct {
Cons func(Diagnostics) Diagnostics
Want []diagFlat
}{
"nil": {
func(diags Diagnostics) Diagnostics {
return nil
},
nil,
},
"errors.New": {
// these could be from different locations, so we can't dedupe them
func(diags Diagnostics) Diagnostics {
return diags.Append(
errors.New("oh no bad"),
errors.New("oh no bad"),
)
},
[]diagFlat{
{
Severity: Error,
Summary: "oh no bad",
},
{
Severity: Error,
Summary: "oh no bad",
},
},
},
"hcl.Diagnostic": {
func(diags Diagnostics) Diagnostics {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Something bad happened",
Detail: "It was really, really bad.",
Subject: &hcl.Range{
Filename: "foo.tf",
Start: hcl.Pos{Line: 1, Column: 10, Byte: 9},
End: hcl.Pos{Line: 2, Column: 3, Byte: 25},
},
Context: &hcl.Range{
Filename: "foo.tf",
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 3, Column: 1, Byte: 30},
},
})
// exact same diag
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Something bad happened",
Detail: "It was really, really bad.",
Subject: &hcl.Range{
Filename: "foo.tf",
Start: hcl.Pos{Line: 1, Column: 10, Byte: 9},
End: hcl.Pos{Line: 2, Column: 3, Byte: 25},
},
Context: &hcl.Range{
Filename: "foo.tf",
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 3, Column: 1, Byte: 30},
},
})
// same diag as prev, different location
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Something bad happened",
Detail: "It was really, really bad.",
Subject: &hcl.Range{
Filename: "foo.tf",
Start: hcl.Pos{Line: 4, Column: 10, Byte: 40},
End: hcl.Pos{Line: 5, Column: 3, Byte: 55},
},
Context: &hcl.Range{
Filename: "foo.tf",
Start: hcl.Pos{Line: 4, Column: 1, Byte: 40},
End: hcl.Pos{Line: 6, Column: 1, Byte: 60},
},
})
return diags
},
[]diagFlat{
{
Severity: Error,
Summary: "Something bad happened",
Detail: "It was really, really bad.",
Subject: &SourceRange{
Filename: "foo.tf",
Start: SourcePos{Line: 1, Column: 10, Byte: 9},
End: SourcePos{Line: 2, Column: 3, Byte: 25},
},
Context: &SourceRange{
Filename: "foo.tf",
Start: SourcePos{Line: 1, Column: 1, Byte: 0},
End: SourcePos{Line: 3, Column: 1, Byte: 30},
},
},
{
Severity: Error,
Summary: "Something bad happened",
Detail: "It was really, really bad.",
Subject: &SourceRange{
Filename: "foo.tf",
Start: SourcePos{Line: 4, Column: 10, Byte: 40},
End: SourcePos{Line: 5, Column: 3, Byte: 55},
},
Context: &SourceRange{
Filename: "foo.tf",
Start: SourcePos{Line: 4, Column: 1, Byte: 40},
End: SourcePos{Line: 6, Column: 1, Byte: 60},
},
},
},
},
"simple warning": {
func(diags Diagnostics) Diagnostics {
diags = diags.Append(SimpleWarning("Don't forget your toothbrush!"))
diags = diags.Append(SimpleWarning("Don't forget your toothbrush!"))
return diags
},
[]diagFlat{
{
Severity: Warning,
Summary: "Don't forget your toothbrush!",
},
{
Severity: Warning,
Summary: "Don't forget your toothbrush!",
},
},
},
"hcl.Diagnostic extra": {
// Extra can contain anything, and we don't know how to compare
// those values, so we can't dedupe them
func(diags Diagnostics) Diagnostics {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Something bad happened",
Detail: "It was really, really bad.",
Extra: 42,
Subject: &hcl.Range{
Filename: "foo.tf",
Start: hcl.Pos{Line: 1, Column: 10, Byte: 9},
End: hcl.Pos{Line: 2, Column: 3, Byte: 25},
},
})
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Something bad happened",
Detail: "It was really, really bad.",
Extra: 38,
Subject: &hcl.Range{
Filename: "foo.tf",
Start: hcl.Pos{Line: 1, Column: 10, Byte: 9},
End: hcl.Pos{Line: 2, Column: 3, Byte: 25},
},
})
return diags
},
[]diagFlat{
{
Severity: Error,
Summary: "Something bad happened",
Detail: "It was really, really bad.",
Subject: &SourceRange{
Filename: "foo.tf",
Start: SourcePos{Line: 1, Column: 10, Byte: 9},
End: SourcePos{Line: 2, Column: 3, Byte: 25},
},
},
{
Severity: Error,
Summary: "Something bad happened",
Detail: "It was really, really bad.",
Subject: &SourceRange{
Filename: "foo.tf",
Start: SourcePos{Line: 1, Column: 10, Byte: 9},
End: SourcePos{Line: 2, Column: 3, Byte: 25},
},
},
},
},
"hcl.Diagnostic no-location": {
// Extra can contain anything, and we don't know how to compare
// those values, so we can't dedupe them
func(diags Diagnostics) Diagnostics {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Something bad happened",
Detail: "It was really, really bad.",
})
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Something bad happened",
Detail: "It was really, really bad.",
})
return diags
},
[]diagFlat{
{
Severity: Error,
Summary: "Something bad happened",
Detail: "It was really, really bad.",
},
{
Severity: Error,
Summary: "Something bad happened",
Detail: "It was really, really bad.",
},
},
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
var deduped Diagnostics

diags := test.Cons(nil)
deduped = deduped.AppendWithoutDuplicates(diags...)

var got []diagFlat
for _, item := range deduped {
desc := item.Description()
source := item.Source()
got = append(got, diagFlat{
Severity: item.Severity(),
Summary: desc.Summary,
Detail: desc.Detail,
Subject: source.Subject,
Context: source.Context,
})
}

if !reflect.DeepEqual(got, test.Want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
}
})
}
}
8 changes: 7 additions & 1 deletion internal/tfdiags/hcl.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,19 @@ func (d hclDiagnostic) Equals(otherDiag ComparableDiagnostic) bool {
return false
}

// we can't compare extra values without knowing what they are
if d.ExtraInfo() != nil || od.ExtraInfo() != nil {
return false
}

return true
}

func hclRangeEquals(l, r *hcl.Range) bool {
if l == nil || r == nil {
return l == r
return false
}

if l.Filename != r.Filename {
return false
}
Expand Down