mirror of
https://github.com/hashicorp/terraform.git
synced 2026-02-03 20:50:59 -05:00
refactor: extract write snippet in diagnostic rendering
This commit is contained in:
parent
ea7f1e3043
commit
3a36e7732b
1 changed files with 93 additions and 87 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue