diff options
Diffstat (limited to 'vendor/github.com/hashicorp/hcl2/hcl/diagnostic_text.go')
-rw-r--r-- | vendor/github.com/hashicorp/hcl2/hcl/diagnostic_text.go | 143 |
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 | ||
3 | import ( | 3 | import ( |
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 | ||
12 | type diagnosticTextWriter struct { | 15 | type 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 | ||
218 | func (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 | |||
246 | func (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 | |||
159 | func contextString(file *File, offset int) string { | 302 | func contextString(file *File, offset int) string { |
160 | type contextStringer interface { | 303 | type contextStringer interface { |
161 | ContextString(offset int) string | 304 | ContextString(offset int) string |