|
|
|
|
@ -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)
|
|
|
|
|
|
|
|
|
|
// 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 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)
|
|
|
|
|
|
|
|
|
|
// 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 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(", ")
|
|
|
|
|
}
|
|
|
|
|
if param := callInfo.Signature.VariadicParam; param != nil {
|
|
|
|
|
if len(callInfo.Signature.Params) > 0 {
|
|
|
|
|
buf.WriteString(", ")
|
|
|
|
|
}
|
|
|
|
|
buf.WriteString(param.Name)
|
|
|
|
|
buf.WriteString("...")
|
|
|
|
|
buf.WriteString(param.Name)
|
|
|
|
|
}
|
|
|
|
|
if param := callInfo.Signature.VariadicParam; param != nil {
|
|
|
|
|
if len(callInfo.Signature.Params) > 0 {
|
|
|
|
|
buf.WriteString(", ")
|
|
|
|
|
}
|
|
|
|
|
buf.WriteString(")\n")
|
|
|
|
|
buf.WriteString(param.Name)
|
|
|
|
|
buf.WriteString("...")
|
|
|
|
|
}
|
|
|
|
|
buf.WriteString(")\n")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
buf.WriteByte('\n')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (f *snippetFormatter) printTestDiagOutput(diag *viewsjson.DiagnosticTestBinaryExpr) {
|
|
|
|
|
|