github.com/googleapis/api-linter@v1.65.2/lint/problem.go (about) 1 // Copyright 2019 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package lint 16 17 import ( 18 "encoding/json" 19 20 "github.com/jhump/protoreflect/desc" 21 dpb "google.golang.org/protobuf/types/descriptorpb" 22 ) 23 24 // Problem contains information about a result produced by an API Linter. 25 // 26 // All rules return []Problem. Most lint rules return 0 or 1 problems, but 27 // occasionally there are rules that may return more than one. 28 type Problem struct { 29 // Message provides a short description of the problem. 30 // This should be no more than a single sentence. 31 Message string 32 33 // Suggestion provides a suggested fix, if applicable. 34 // 35 // This integrates with certain IDEs to provide "push-button" fixes, 36 // so these need to be machine-readable, not just human-readable. 37 // Additionally, when setting `Suggestion`, one should almost always set 38 // `Location` also, to ensure that the text being replaced is sufficiently 39 // precise. 40 Suggestion string 41 42 // Descriptor provides the descriptor related to the problem. This must be 43 // set on every Problem. 44 // 45 // If `Location` is not specified, then the starting location of 46 // the descriptor is used as the location of the problem. 47 Descriptor desc.Descriptor 48 49 // Location provides the location of the problem. 50 // 51 // If unset, this defaults to the value of `Descriptor.GetSourceInfo()`. 52 // This should almost always be set if `Suggestion` is set. The best way to 53 // do this is by using the helper methods in `location.go`. 54 Location *dpb.SourceCodeInfo_Location 55 56 // RuleID provides the ID of the rule that this problem belongs to. 57 // DO NOT SET: The linter sets this automatically. 58 RuleID RuleName // FIXME: Make this private (cmd/summary_cli.go is the challenge). 59 60 // The category for this problem, based on user configuration. 61 category string 62 63 //lint:ignore U1000 ignored via golint previously 64 noPositional struct{} 65 } 66 67 // MarshalJSON defines how to represent a Problem in JSON. 68 func (p Problem) MarshalJSON() ([]byte, error) { 69 return json.Marshal(p.marshal()) 70 } 71 72 // MarshalYAML defines how to represent a Problem in YAML. 73 func (p Problem) MarshalYAML() (interface{}, error) { 74 return p.marshal(), nil 75 } 76 77 // Marshal defines how to represent a serialized Problem. 78 func (p Problem) marshal() interface{} { 79 // The descriptor is always set, and location may be set. 80 // If they are both set, prefer the location. 81 loc := p.Location 82 if loc == nil && p.Descriptor != nil { 83 loc = p.Descriptor.GetSourceInfo() 84 } 85 86 // Return a marshal-able structure. 87 return struct { 88 Message string `json:"message" yaml:"message"` 89 Suggestion string `json:"suggestion,omitempty" yaml:"suggestion,omitempty"` 90 Location fileLocation `json:"location" yaml:"location"` 91 RuleID RuleName `json:"rule_id" yaml:"rule_id"` 92 RuleDocURI string `json:"rule_doc_uri" yaml:"rule_doc_uri"` 93 Category string `json:"category,omitempty" yaml:"category,omitempty"` 94 }{ 95 p.Message, 96 p.Suggestion, 97 fileLocationFromPBLocation(loc, p.Descriptor), 98 p.RuleID, 99 p.GetRuleURI(), 100 p.category, 101 } 102 } 103 104 // GetRuleURI returns a URI to learn more about the problem. 105 func (p Problem) GetRuleURI() string { 106 return getRuleURL(string(p.RuleID), ruleURLMappings) 107 } 108 109 // position describes a one-based position in a source code file. 110 // They are one-indexed, as a human counts lines or columns. 111 type position struct { 112 Line int `json:"line_number" yaml:"line_number"` 113 Column int `json:"column_number" yaml:"column_number"` 114 } 115 116 // fileLocation describes a location in a source code file. 117 // 118 // Note: Positions are one-indexed, as a human counts lines or columns 119 // in a file. 120 type fileLocation struct { 121 Start position `json:"start_position" yaml:"start_position"` 122 End position `json:"end_position" yaml:"end_position"` 123 Path string `json:"path" yaml:"path"` 124 } 125 126 // fileLocationFromPBLocation returns a new fileLocation object based on a 127 // protocol buffer SourceCodeInfo_Location 128 func fileLocationFromPBLocation(l *dpb.SourceCodeInfo_Location, d desc.Descriptor) fileLocation { 129 // Spans are guaranteed by protobuf to have either three or four ints. 130 span := []int32{0, 0, 1} 131 if l != nil { 132 span = l.Span 133 } 134 135 var fl fileLocation 136 if d != nil { 137 fl = fileLocation{Path: d.GetFile().GetName()} 138 } 139 140 // If `span` has four ints; they correspond to 141 // [start line, start column, end line, end column]. 142 // 143 // We add one because spans are zero-indexed, but not to the end column 144 // because we want the ending position to be inclusive and not exclusive. 145 if len(span) == 4 { 146 fl.Start = position{ 147 Line: int(span[0]) + 1, 148 Column: int(span[1]) + 1, 149 } 150 fl.End = position{ 151 Line: int(span[2]) + 1, 152 Column: int(span[3]), 153 } 154 return fl 155 } 156 157 // Okay, `span` has three ints; they correspond to 158 // [start line, start column, end column]. 159 // 160 // We add one because spans are zero-indexed, but not to the end column 161 // because we want the ending position to be inclusive and not exclusive. 162 fl.Start = position{ 163 Line: int(span[0]) + 1, 164 Column: int(span[1]) + 1, 165 } 166 fl.End = position{ 167 Line: int(span[0]) + 1, 168 Column: int(span[2]), 169 } 170 return fl 171 }