aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/hcl2/hcl/diagnostic_text.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/hashicorp/hcl2/hcl/diagnostic_text.go')
-rw-r--r--vendor/github.com/hashicorp/hcl2/hcl/diagnostic_text.go143
1 files changed, 143 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/hcl2/hcl/diagnostic_text.go b/vendor/github.com/hashicorp/hcl2/hcl/diagnostic_text.go
index dfa473a..0b4a262 100644
--- a/vendor/github.com/hashicorp/hcl2/hcl/diagnostic_text.go
+++ b/vendor/github.com/hashicorp/hcl2/hcl/diagnostic_text.go
@@ -2,11 +2,14 @@ package hcl
2 2
3import ( 3import (
4 "bufio" 4 "bufio"
5 "bytes"
5 "errors" 6 "errors"
6 "fmt" 7 "fmt"
7 "io" 8 "io"
9 "sort"
8 10
9 wordwrap "github.com/mitchellh/go-wordwrap" 11 wordwrap "github.com/mitchellh/go-wordwrap"
12 "github.com/zclconf/go-cty/cty"
10) 13)
11 14
12type diagnosticTextWriter struct { 15type diagnosticTextWriter struct {
@@ -133,6 +136,62 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error {
133 136
134 w.wr.Write([]byte{'\n'}) 137 w.wr.Write([]byte{'\n'})
135 } 138 }
139
140 if diag.Expression != nil && diag.EvalContext != nil {
141 // We will attempt to render the values for any variables
142 // referenced in the given expression as additional context, for
143 // situations where the same expression is evaluated multiple
144 // times in different scopes.
145 expr := diag.Expression
146 ctx := diag.EvalContext
147
148 vars := expr.Variables()
149 stmts := make([]string, 0, len(vars))
150 seen := make(map[string]struct{}, len(vars))
151 for _, traversal := range vars {
152 val, diags := traversal.TraverseAbs(ctx)
153 if diags.HasErrors() {
154 // Skip anything that generates errors, since we probably
155 // already have the same error in our diagnostics set
156 // already.
157 continue
158 }
159
160 traversalStr := w.traversalStr(traversal)
161 if _, exists := seen[traversalStr]; exists {
162 continue // don't show duplicates when the same variable is referenced multiple times
163 }
164 switch {
165 case !val.IsKnown():
166 // Can't say anything about this yet, then.
167 continue
168 case val.IsNull():
169 stmts = append(stmts, fmt.Sprintf("%s set to null", traversalStr))
170 default:
171 stmts = append(stmts, fmt.Sprintf("%s as %s", traversalStr, w.valueStr(val)))
172 }
173 seen[traversalStr] = struct{}{}
174 }
175
176 sort.Strings(stmts) // FIXME: Should maybe use a traversal-aware sort that can sort numeric indexes properly?
177 last := len(stmts) - 1
178
179 for i, stmt := range stmts {
180 switch i {
181 case 0:
182 w.wr.Write([]byte{'w', 'i', 't', 'h', ' '})
183 default:
184 w.wr.Write([]byte{' ', ' ', ' ', ' ', ' '})
185 }
186 w.wr.Write([]byte(stmt))
187 switch i {
188 case last:
189 w.wr.Write([]byte{'.', '\n', '\n'})
190 default:
191 w.wr.Write([]byte{',', '\n'})
192 }
193 }
194 }
136 } 195 }
137 196
138 if diag.Detail != "" { 197 if diag.Detail != "" {
@@ -156,6 +215,90 @@ func (w *diagnosticTextWriter) WriteDiagnostics(diags Diagnostics) error {
156 return nil 215 return nil
157} 216}
158 217
218func (w *diagnosticTextWriter) traversalStr(traversal Traversal) string {
219 // This is a specialized subset of traversal rendering tailored to
220 // producing helpful contextual messages in diagnostics. It is not
221 // comprehensive nor intended to be used for other purposes.
222
223 var buf bytes.Buffer
224 for _, step := range traversal {
225 switch tStep := step.(type) {
226 case TraverseRoot:
227 buf.WriteString(tStep.Name)
228 case TraverseAttr:
229 buf.WriteByte('.')
230 buf.WriteString(tStep.Name)
231 case TraverseIndex:
232 buf.WriteByte('[')
233 if keyTy := tStep.Key.Type(); keyTy.IsPrimitiveType() {
234 buf.WriteString(w.valueStr(tStep.Key))
235 } else {
236 // We'll just use a placeholder for more complex values,
237 // since otherwise our result could grow ridiculously long.
238 buf.WriteString("...")
239 }
240 buf.WriteByte(']')
241 }
242 }
243 return buf.String()
244}
245
246func (w *diagnosticTextWriter) valueStr(val cty.Value) string {
247 // This is a specialized subset of value rendering tailored to producing
248 // helpful but concise messages in diagnostics. It is not comprehensive
249 // nor intended to be used for other purposes.
250
251 ty := val.Type()
252 switch {
253 case val.IsNull():
254 return "null"
255 case !val.IsKnown():
256 // Should never happen here because we should filter before we get
257 // in here, but we'll do something reasonable rather than panic.
258 return "(not yet known)"
259 case ty == cty.Bool:
260 if val.True() {
261 return "true"
262 }
263 return "false"
264 case ty == cty.Number:
265 bf := val.AsBigFloat()
266 return bf.Text('g', 10)
267 case ty == cty.String:
268 // Go string syntax is not exactly the same as HCL native string syntax,
269 // but we'll accept the minor edge-cases where this is different here
270 // for now, just to get something reasonable here.
271 return fmt.Sprintf("%q", val.AsString())
272 case ty.IsCollectionType() || ty.IsTupleType():
273 l := val.LengthInt()
274 switch l {
275 case 0:
276 return "empty " + ty.FriendlyName()
277 case 1:
278 return ty.FriendlyName() + " with 1 element"
279 default:
280 return fmt.Sprintf("%s with %d elements", ty.FriendlyName(), l)
281 }
282 case ty.IsObjectType():
283 atys := ty.AttributeTypes()
284 l := len(atys)
285 switch l {
286 case 0:
287 return "object with no attributes"
288 case 1:
289 var name string
290 for k := range atys {
291 name = k
292 }
293 return fmt.Sprintf("object with 1 attribute %q", name)
294 default:
295 return fmt.Sprintf("object with %d attributes", l)
296 }
297 default:
298 return ty.FriendlyName()
299 }
300}
301
159func contextString(file *File, offset int) string { 302func contextString(file *File, offset int) string {
160 type contextStringer interface { 303 type contextStringer interface {
161 ContextString(offset int) string 304 ContextString(offset int) string