github.com/opentofu/opentofu@v1.7.1/internal/tfdiags/hcl.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package tfdiags 7 8 import ( 9 "github.com/hashicorp/hcl/v2" 10 ) 11 12 // hclDiagnostic is a Diagnostic implementation that wraps a HCL Diagnostic 13 type hclDiagnostic struct { 14 diag *hcl.Diagnostic 15 } 16 17 var _ Diagnostic = hclDiagnostic{} 18 19 func (d hclDiagnostic) Severity() Severity { 20 switch d.diag.Severity { 21 case hcl.DiagWarning: 22 return Warning 23 default: 24 return Error 25 } 26 } 27 28 func (d hclDiagnostic) Description() Description { 29 return Description{ 30 Summary: d.diag.Summary, 31 Detail: d.diag.Detail, 32 } 33 } 34 35 func (d hclDiagnostic) Source() Source { 36 var ret Source 37 if d.diag.Subject != nil { 38 rng := SourceRangeFromHCL(*d.diag.Subject) 39 ret.Subject = &rng 40 } 41 if d.diag.Context != nil { 42 rng := SourceRangeFromHCL(*d.diag.Context) 43 ret.Context = &rng 44 } 45 return ret 46 } 47 48 func (d hclDiagnostic) FromExpr() *FromExpr { 49 if d.diag.Expression == nil || d.diag.EvalContext == nil { 50 return nil 51 } 52 return &FromExpr{ 53 Expression: d.diag.Expression, 54 EvalContext: d.diag.EvalContext, 55 } 56 } 57 58 func (d hclDiagnostic) ExtraInfo() interface{} { 59 return d.diag.Extra 60 } 61 62 // SourceRangeFromHCL constructs a SourceRange from the corresponding range 63 // type within the HCL package. 64 func SourceRangeFromHCL(hclRange hcl.Range) SourceRange { 65 return SourceRange{ 66 Filename: hclRange.Filename, 67 Start: SourcePos{ 68 Line: hclRange.Start.Line, 69 Column: hclRange.Start.Column, 70 Byte: hclRange.Start.Byte, 71 }, 72 End: SourcePos{ 73 Line: hclRange.End.Line, 74 Column: hclRange.End.Column, 75 Byte: hclRange.End.Byte, 76 }, 77 } 78 } 79 80 // ToHCL constructs a HCL Range from the receiving SourceRange. This is the 81 // opposite of SourceRangeFromHCL. 82 func (r SourceRange) ToHCL() hcl.Range { 83 return hcl.Range{ 84 Filename: r.Filename, 85 Start: hcl.Pos{ 86 Line: r.Start.Line, 87 Column: r.Start.Column, 88 Byte: r.Start.Byte, 89 }, 90 End: hcl.Pos{ 91 Line: r.End.Line, 92 Column: r.End.Column, 93 Byte: r.End.Byte, 94 }, 95 } 96 } 97 98 // ToHCL constructs a hcl.Diagnostics containing the same diagnostic messages 99 // as the receiving tfdiags.Diagnostics. 100 // 101 // This conversion preserves the data that HCL diagnostics are able to 102 // preserve but would be lossy in a round trip from tfdiags to HCL and then 103 // back to tfdiags, because it will lose the specific type information of 104 // the source diagnostics. In most cases this will not be a significant 105 // problem, but could produce an awkward result in some special cases such 106 // as converting the result of ConsolidateWarnings, which will force the 107 // resulting warning groups to be flattened early. 108 func (diags Diagnostics) ToHCL() hcl.Diagnostics { 109 if len(diags) == 0 { 110 return nil 111 } 112 ret := make(hcl.Diagnostics, len(diags)) 113 for i, diag := range diags { 114 severity := diag.Severity() 115 desc := diag.Description() 116 source := diag.Source() 117 fromExpr := diag.FromExpr() 118 119 hclDiag := &hcl.Diagnostic{ 120 Summary: desc.Summary, 121 Detail: desc.Detail, 122 Severity: severity.ToHCL(), 123 } 124 if source.Subject != nil { 125 hclDiag.Subject = source.Subject.ToHCL().Ptr() 126 } 127 if source.Context != nil { 128 hclDiag.Context = source.Context.ToHCL().Ptr() 129 } 130 if fromExpr != nil { 131 hclDiag.Expression = fromExpr.Expression 132 hclDiag.EvalContext = fromExpr.EvalContext 133 } 134 135 ret[i] = hclDiag 136 } 137 return ret 138 }