Skip to content
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
15 changes: 15 additions & 0 deletions internal/command/format/diagnostic.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,21 @@ func appendSourceSnippets(buf *bytes.Buffer, diag *viewsjson.Diagnostic, color *
}
}

// If either start or end is out of range for the code buffer then
// we'll cap them at the bounds just to avoid a panic, although
// this would happen only if there's a bug in the code generating
// the snippet objects.
if start < 0 {
start = 0
} else if start > len(code) {
start = len(code)
}
if end < 0 {
end = 0
} else if end > len(code) {
end = len(code)
}

before, highlight, after := code[0:start], code[start:end], code[end:]
code = fmt.Sprintf(color.Color("%s[underline]%s[reset]%s"), before, highlight, after)

Expand Down
15 changes: 13 additions & 2 deletions internal/command/views/json/diagnostic.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,12 +221,23 @@ func NewDiagnostic(diag tfdiags.Diagnostic, sources map[string][]byte) *Diagnost
// to the code snippet string.
start := highlightRange.Start.Byte - codeStartByte
end := start + (highlightRange.End.Byte - highlightRange.Start.Byte)
if start > len(codeStr) {

// We can end up with some quirky results here in edge cases like
// when a source range starts or ends at a newline character,
// so we'll cap the results at the bounds of the highlight range
// so that consumers of this data don't need to contend with
// out-of-bounds errors themselves.
if start < 0 {
start = 0
} else if start > len(codeStr) {
start = len(codeStr)
}
if end > len(codeStr) {
if end < 0 {
end = 0
} else if end > len(codeStr) {
end = len(codeStr)
}

diagnostic.Snippet.HighlightStartOffset = start
diagnostic.Snippet.HighlightEndOffset = end

Expand Down
48 changes: 47 additions & 1 deletion internal/command/views/json/diagnostic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ func TestNewDiagnostic(t *testing.T) {
}
}
`),
"short.tf": []byte("bad source code"),
"short.tf": []byte("bad source code"),
"odd-comment.tf": []byte("foo\n\n#\n"),
"values.tf": []byte(`[
var.a,
var.b,
Expand Down Expand Up @@ -285,6 +286,51 @@ func TestNewDiagnostic(t *testing.T) {
},
},
},
"error whose range starts at a newline": {
&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid newline",
Detail: "How awkward!",
Subject: &hcl.Range{
Filename: "odd-comment.tf",
Start: hcl.Pos{Line: 2, Column: 5, Byte: 4},
End: hcl.Pos{Line: 3, Column: 1, Byte: 6},
},
},
&Diagnostic{
Severity: "error",
Summary: "Invalid newline",
Detail: "How awkward!",
Range: &DiagnosticRange{
Filename: "odd-comment.tf",
Start: Pos{
Line: 2,
Column: 5,
Byte: 4,
},
End: Pos{
Line: 3,
Column: 1,
Byte: 6,
},
},
Snippet: &DiagnosticSnippet{
Code: `#`,
StartLine: 2,
Values: []DiagnosticExpressionValue{},

// Due to the range starting at a newline on a blank
// line, we end up stripping off the initial newline
// to produce only a one-line snippet. That would
// therefore cause the start offset to naturally be
// -1, just before the Code we returned, but then we
// force it to zero so that the result will still be
// in range for a byte-oriented slice of Code.
HighlightStartOffset: 0,
HighlightEndOffset: 1,
},
},
},
"error with source code subject and known expression": {
&hcl.Diagnostic{
Severity: hcl.DiagError,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"severity": "error",
"summary": "Invalid newline",
"detail": "How awkward!",
"range": {
"filename": "odd-comment.tf",
"start": {
"line": 2,
"column": 5,
"byte": 4
},
"end": {
"line": 3,
"column": 1,
"byte": 6
}
},
"snippet": {
"context": null,
"code": "#",
"start_line": 2,
"highlight_start_offset": 0,
"highlight_end_offset": 1,
"values": []
}
}