github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/tfdiags/consolidate_warnings.go (about) 1 package tfdiags 2 3 import "fmt" 4 5 // ConsolidateWarnings checks if there is an unreasonable amount of warnings 6 // with the same summary in the receiver and, if so, returns a new diagnostics 7 // with some of those warnings consolidated into a single warning in order 8 // to reduce the verbosity of the output. 9 // 10 // This mechanism is here primarily for diagnostics printed out at the CLI. In 11 // other contexts it is likely better to just return the warnings directly, 12 // particularly if they are going to be interpreted by software rather than 13 // by a human reader. 14 // 15 // The returned slice always has a separate backing array from the reciever, 16 // but some diagnostic values themselves might be shared. 17 // 18 // The definition of "unreasonable" is given as the threshold argument. At most 19 // that many warnings with the same summary will be shown. 20 func (diags Diagnostics) ConsolidateWarnings(threshold int) Diagnostics { 21 if len(diags) == 0 { 22 return nil 23 } 24 25 newDiags := make(Diagnostics, 0, len(diags)) 26 27 // We'll track how many times we've seen each warning summary so we can 28 // decide when to start consolidating. Once we _have_ started consolidating, 29 // we'll also track the object representing the consolidated warning 30 // so we can continue appending to it. 31 warningStats := make(map[string]int) 32 warningGroups := make(map[string]*warningGroup) 33 34 for _, diag := range diags { 35 severity := diag.Severity() 36 if severity != Warning || diag.Source().Subject == nil { 37 // Only warnings can get special treatment, and we only 38 // consolidate warnings that have source locations because 39 // our primary goal here is to deal with the situation where 40 // some configuration language feature is producing a warning 41 // each time it's used across a potentially-large config. 42 newDiags = newDiags.Append(diag) 43 continue 44 } 45 46 desc := diag.Description() 47 summary := desc.Summary 48 if g, ok := warningGroups[summary]; ok { 49 // We're already grouping this one, so we'll just continue it. 50 g.Append(diag) 51 continue 52 } 53 54 warningStats[summary]++ 55 if warningStats[summary] == threshold { 56 // Initially creating the group doesn't really change anything 57 // visibly in the result, since a group with only one warning 58 // is just a passthrough anyway, but once we do this any additional 59 // warnings with the same summary will get appended to this group. 60 g := &warningGroup{} 61 newDiags = newDiags.Append(g) 62 warningGroups[summary] = g 63 g.Append(diag) 64 continue 65 } 66 67 // If this warning is not consolidating yet then we'll just append 68 // it directly. 69 newDiags = newDiags.Append(diag) 70 } 71 72 return newDiags 73 } 74 75 // A warningGroup is one or more warning diagnostics grouped together for 76 // UI consolidation purposes. 77 // 78 // A warningGroup with only one diagnostic in it is just a passthrough for 79 // that one diagnostic. If it has more than one then it will behave mostly 80 // like the first one but its detail message will include an additional 81 // sentence mentioning the consolidation. A warningGroup with no diagnostics 82 // at all is invalid and will panic when used. 83 type warningGroup struct { 84 Warnings Diagnostics 85 } 86 87 var _ Diagnostic = (*warningGroup)(nil) 88 89 func (wg *warningGroup) Severity() Severity { 90 return wg.Warnings[0].Severity() 91 } 92 93 func (wg *warningGroup) Description() Description { 94 desc := wg.Warnings[0].Description() 95 if len(wg.Warnings) < 2 { 96 return desc 97 } 98 extraCount := len(wg.Warnings) - 1 99 var msg string 100 switch extraCount { 101 case 1: 102 msg = "(and one more similar warning elsewhere)" 103 default: 104 msg = fmt.Sprintf("(and %d more similar warnings elsewhere)", extraCount) 105 } 106 if desc.Detail != "" { 107 desc.Detail = desc.Detail + "\n\n" + msg 108 } else { 109 desc.Detail = msg 110 } 111 return desc 112 } 113 114 func (wg *warningGroup) Source() Source { 115 return wg.Warnings[0].Source() 116 } 117 118 func (wg *warningGroup) FromExpr() *FromExpr { 119 return wg.Warnings[0].FromExpr() 120 } 121 122 func (wg *warningGroup) ExtraInfo() interface{} { 123 return wg.Warnings[0].ExtraInfo() 124 } 125 126 func (wg *warningGroup) Append(diag Diagnostic) { 127 if diag.Severity() != Warning { 128 panic("can't append a non-warning diagnostic to a warningGroup") 129 } 130 wg.Warnings = append(wg.Warnings, diag) 131 } 132 133 // WarningGroupSourceRanges can be used in conjunction with 134 // Diagnostics.ConsolidateWarnings to recover the full set of original source 135 // locations from a consolidated warning. 136 // 137 // For convenience, this function accepts any diagnostic and will just return 138 // the single Source value from any diagnostic that isn't a warning group. 139 func WarningGroupSourceRanges(diag Diagnostic) []Source { 140 wg, ok := diag.(*warningGroup) 141 if !ok { 142 return []Source{diag.Source()} 143 } 144 145 ret := make([]Source, len(wg.Warnings)) 146 for i, wrappedDiag := range wg.Warnings { 147 ret[i] = wrappedDiag.Source() 148 } 149 return ret 150 }