refactor: extract write snippet in diagnostic rendering

This commit is contained in:
Daniel Schmidt 2026-01-13 14:30:52 +01:00
parent ea7f1e3043
commit 3a36e7732b

View file

@ -229,7 +229,6 @@ type snippetFormatter struct {
func (f *snippetFormatter) write() {
diag := f.diag
buf := f.buf
color := f.color
if diag.Address != "" {
fmt.Fprintf(buf, " with %s,\n", diag.Address)
}
@ -253,109 +252,116 @@ func (f *snippetFormatter) write() {
contextStr = fmt.Sprintf(", in %s", *snippet.Context)
}
fmt.Fprintf(buf, " on %s line %d%s:\n", diag.Range.Filename, diag.Range.Start.Line, contextStr)
f.writeSnippet(snippet, code)
}
// Split the snippet and render the highlighted section with underlines
start := snippet.HighlightStartOffset
end := snippet.HighlightEndOffset
buf.WriteByte('\n')
}
// Only buggy diagnostics can have an end range before the start, but
// we need to ensure we don't crash here if that happens.
if end < start {
end = start + 1
if end > len(code) {
end = len(code)
}
}
func (f *snippetFormatter) writeSnippet(snippet *viewsjson.DiagnosticSnippet, code string) {
buf := f.buf
color := f.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) {
// Split the snippet and render the highlighted section with underlines
start := snippet.HighlightStartOffset
end := snippet.HighlightEndOffset
// Only buggy diagnostics can have an end range before the start, but
// we need to ensure we don't crash here if that happens.
if end < start {
end = start + 1
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)
// 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)
}
// Split the snippet into lines and render one at a time
lines := strings.Split(code, "\n")
for i, line := range lines {
fmt.Fprintf(
buf, "%4d: %s\n",
snippet.StartLine+i,
line,
)
before, highlight, after := code[0:start], code[start:end], code[end:]
code = fmt.Sprintf(color.Color("%s[underline]%s[reset]%s"), before, highlight, after)
// Split the snippet into lines and render one at a time
lines := strings.Split(code, "\n")
for i, line := range lines {
fmt.Fprintf(
buf, "%4d: %s\n",
snippet.StartLine+i,
line,
)
}
if len(snippet.Values) > 0 || (snippet.FunctionCall != nil && snippet.FunctionCall.Signature != nil) || snippet.TestAssertionExpr != nil {
// The diagnostic may also have information about the dynamic
// values of relevant variables at the point of evaluation.
// This is particularly useful for expressions that get evaluated
// multiple times with different values, such as blocks using
// "count" and "for_each", or within "for" expressions.
values := slices.Clone(snippet.Values)
sort.Slice(values, func(i, j int) bool {
return values[i].Traversal < values[j].Traversal
})
fmt.Fprint(buf, color.Color(" [dark_gray]├────────────────[reset]\n"))
if callInfo := snippet.FunctionCall; callInfo != nil && callInfo.Signature != nil {
fmt.Fprintf(buf, color.Color(" [dark_gray]│[reset] while calling [bold]%s[reset]("), callInfo.CalledAs)
for i, param := range callInfo.Signature.Params {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(param.Name)
}
if param := callInfo.Signature.VariadicParam; param != nil {
if len(callInfo.Signature.Params) > 0 {
buf.WriteString(", ")
}
buf.WriteString(param.Name)
buf.WriteString("...")
}
buf.WriteString(")\n")
}
if len(snippet.Values) > 0 || (snippet.FunctionCall != nil && snippet.FunctionCall.Signature != nil) || snippet.TestAssertionExpr != nil {
// The diagnostic may also have information about the dynamic
// values of relevant variables at the point of evaluation.
// This is particularly useful for expressions that get evaluated
// multiple times with different values, such as blocks using
// "count" and "for_each", or within "for" expressions.
values := slices.Clone(snippet.Values)
sort.Slice(values, func(i, j int) bool {
return values[i].Traversal < values[j].Traversal
})
// always print the values unless in the case of a test assertion, where we only print them if the user has requested verbose output
printValues := snippet.TestAssertionExpr == nil || snippet.TestAssertionExpr.ShowVerbose
fmt.Fprint(buf, color.Color(" [dark_gray]├────────────────[reset]\n"))
if callInfo := snippet.FunctionCall; callInfo != nil && callInfo.Signature != nil {
// The diagnostic may also have information about failures from test assertions
// in a `terraform test` run. This is useful for understanding the values that
// were being compared when the assertion failed.
// Also, we'll print a JSON diff of the two values to make it easier to see the
// differences.
if snippet.TestAssertionExpr != nil {
f.printTestDiagOutput(snippet.TestAssertionExpr)
}
fmt.Fprintf(buf, color.Color(" [dark_gray]│[reset] while calling [bold]%s[reset]("), callInfo.CalledAs)
for i, param := range callInfo.Signature.Params {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(param.Name)
}
if param := callInfo.Signature.VariadicParam; param != nil {
if len(callInfo.Signature.Params) > 0 {
buf.WriteString(", ")
}
buf.WriteString(param.Name)
buf.WriteString("...")
}
buf.WriteString(")\n")
}
if printValues {
for _, value := range values {
// if the statement is one line, we'll just print it as is
// otherwise, we have to ensure that each line is indented correctly
// and that the first line has the traversal information
valSlice := strings.Split(value.Statement, "\n")
fmt.Fprintf(buf, color.Color(" [dark_gray]│[reset] [bold]%s[reset] %s\n"),
value.Traversal, valSlice[0])
// always print the values unless in the case of a test assertion, where we only print them if the user has requested verbose output
printValues := snippet.TestAssertionExpr == nil || snippet.TestAssertionExpr.ShowVerbose
// The diagnostic may also have information about failures from test assertions
// in a `terraform test` run. This is useful for understanding the values that
// were being compared when the assertion failed.
// Also, we'll print a JSON diff of the two values to make it easier to see the
// differences.
if snippet.TestAssertionExpr != nil {
f.printTestDiagOutput(snippet.TestAssertionExpr)
}
if printValues {
for _, value := range values {
// if the statement is one line, we'll just print it as is
// otherwise, we have to ensure that each line is indented correctly
// and that the first line has the traversal information
valSlice := strings.Split(value.Statement, "\n")
fmt.Fprintf(buf, color.Color(" [dark_gray]│[reset] [bold]%s[reset] %s\n"),
value.Traversal, valSlice[0])
for _, line := range valSlice[1:] {
fmt.Fprintf(buf, color.Color(" [dark_gray]│[reset] %s\n"), line)
}
for _, line := range valSlice[1:] {
fmt.Fprintf(buf, color.Color(" [dark_gray]│[reset] %s\n"), line)
}
}
}
}
buf.WriteByte('\n')
}
func (f *snippetFormatter) printTestDiagOutput(diag *viewsjson.DiagnosticTestBinaryExpr) {