cuelang.org/go@v0.13.0/internal/golangorgx/gopls/cache/errors.go (about) 1 // Copyright 2019 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package cache 6 7 // This file defines routines to convert diagnostics from go list, go 8 // get, go/packages, parsing, type checking, and analysis into 9 // golang.Diagnostic form, and suggesting quick fixes. 10 11 import ( 12 "fmt" 13 "go/scanner" 14 "path/filepath" 15 "regexp" 16 "strconv" 17 "strings" 18 19 "cuelang.org/go/internal/golangorgx/gopls/protocol" 20 "cuelang.org/go/internal/golangorgx/gopls/protocol/command" 21 "cuelang.org/go/internal/golangorgx/gopls/settings" 22 "cuelang.org/go/internal/golangorgx/gopls/util/bug" 23 "cuelang.org/go/internal/golangorgx/tools/typesinternal" 24 "golang.org/x/tools/go/packages" 25 ) 26 27 func parseErrorDiagnostics(pkg *syntaxPackage, errList scanner.ErrorList) ([]*Diagnostic, error) { 28 // The first parser error is likely the root cause of the problem. 29 if errList.Len() <= 0 { 30 return nil, fmt.Errorf("no errors in %v", errList) 31 } 32 e := errList[0] 33 pgf, err := pkg.File(protocol.URIFromPath(e.Pos.Filename)) 34 if err != nil { 35 return nil, err 36 } 37 rng, err := pgf.Mapper.OffsetRange(e.Pos.Offset, e.Pos.Offset) 38 if err != nil { 39 return nil, err 40 } 41 return []*Diagnostic{{ 42 URI: pgf.URI, 43 Range: rng, 44 Severity: protocol.SeverityError, 45 Source: ParseError, 46 Message: e.Msg, 47 }}, nil 48 } 49 50 var importErrorRe = regexp.MustCompile(`could not import ([^\s]+)`) 51 var unsupportedFeatureRe = regexp.MustCompile(`.*require.* go(\d+\.\d+) or later`) 52 53 func goGetQuickFixes(moduleMode bool, uri protocol.DocumentURI, pkg string) []SuggestedFix { 54 // Go get only supports module mode for now. 55 if !moduleMode { 56 return nil 57 } 58 title := fmt.Sprintf("go get package %v", pkg) 59 cmd, err := command.NewGoGetPackageCommand(title, command.GoGetPackageArgs{ 60 URI: uri, 61 AddRequire: true, 62 Pkg: pkg, 63 }) 64 if err != nil { 65 bug.Reportf("internal error building 'go get package' fix: %v", err) 66 return nil 67 } 68 return []SuggestedFix{SuggestedFixFromCommand(cmd, protocol.QuickFix)} 69 } 70 71 func editGoDirectiveQuickFix(moduleMode bool, uri protocol.DocumentURI, version string) []SuggestedFix { 72 // Go mod edit only supports module mode. 73 if !moduleMode { 74 return nil 75 } 76 title := fmt.Sprintf("go mod edit -go=%s", version) 77 cmd, err := command.NewEditGoDirectiveCommand(title, command.EditGoDirectiveArgs{ 78 URI: uri, 79 Version: version, 80 }) 81 if err != nil { 82 bug.Reportf("internal error constructing 'edit go directive' fix: %v", err) 83 return nil 84 } 85 return []SuggestedFix{SuggestedFixFromCommand(cmd, protocol.QuickFix)} 86 } 87 88 // encodeDiagnostics gob-encodes the given diagnostics. 89 func encodeDiagnostics(srcDiags []*Diagnostic) []byte { 90 var gobDiags []gobDiagnostic 91 for _, srcDiag := range srcDiags { 92 var gobFixes []gobSuggestedFix 93 for _, srcFix := range srcDiag.SuggestedFixes { 94 gobFix := gobSuggestedFix{ 95 Message: srcFix.Title, 96 ActionKind: srcFix.ActionKind, 97 } 98 for uri, srcEdits := range srcFix.Edits { 99 for _, srcEdit := range srcEdits { 100 gobFix.TextEdits = append(gobFix.TextEdits, gobTextEdit{ 101 Location: protocol.Location{ 102 URI: uri, 103 Range: srcEdit.Range, 104 }, 105 NewText: []byte(srcEdit.NewText), 106 }) 107 } 108 } 109 if srcCmd := srcFix.Command; srcCmd != nil { 110 gobFix.Command = &gobCommand{ 111 Title: srcCmd.Title, 112 Command: srcCmd.Command, 113 Arguments: srcCmd.Arguments, 114 } 115 } 116 gobFixes = append(gobFixes, gobFix) 117 } 118 var gobRelated []gobRelatedInformation 119 for _, srcRel := range srcDiag.Related { 120 gobRel := gobRelatedInformation(srcRel) 121 gobRelated = append(gobRelated, gobRel) 122 } 123 gobDiag := gobDiagnostic{ 124 Location: protocol.Location{ 125 URI: srcDiag.URI, 126 Range: srcDiag.Range, 127 }, 128 Severity: srcDiag.Severity, 129 Code: srcDiag.Code, 130 CodeHref: srcDiag.CodeHref, 131 Source: string(srcDiag.Source), 132 Message: srcDiag.Message, 133 SuggestedFixes: gobFixes, 134 Related: gobRelated, 135 Tags: srcDiag.Tags, 136 } 137 gobDiags = append(gobDiags, gobDiag) 138 } 139 return diagnosticsCodec.Encode(gobDiags) 140 } 141 142 // decodeDiagnostics decodes the given gob-encoded diagnostics. 143 func decodeDiagnostics(data []byte) []*Diagnostic { 144 var gobDiags []gobDiagnostic 145 diagnosticsCodec.Decode(data, &gobDiags) 146 var srcDiags []*Diagnostic 147 for _, gobDiag := range gobDiags { 148 var srcFixes []SuggestedFix 149 for _, gobFix := range gobDiag.SuggestedFixes { 150 srcFix := SuggestedFix{ 151 Title: gobFix.Message, 152 ActionKind: gobFix.ActionKind, 153 } 154 for _, gobEdit := range gobFix.TextEdits { 155 if srcFix.Edits == nil { 156 srcFix.Edits = make(map[protocol.DocumentURI][]protocol.TextEdit) 157 } 158 srcEdit := protocol.TextEdit{ 159 Range: gobEdit.Location.Range, 160 NewText: string(gobEdit.NewText), 161 } 162 uri := gobEdit.Location.URI 163 srcFix.Edits[uri] = append(srcFix.Edits[uri], srcEdit) 164 } 165 if gobCmd := gobFix.Command; gobCmd != nil { 166 srcFix.Command = &protocol.Command{ 167 Title: gobCmd.Title, 168 Command: gobCmd.Command, 169 Arguments: gobCmd.Arguments, 170 } 171 } 172 srcFixes = append(srcFixes, srcFix) 173 } 174 var srcRelated []protocol.DiagnosticRelatedInformation 175 for _, gobRel := range gobDiag.Related { 176 srcRel := protocol.DiagnosticRelatedInformation(gobRel) 177 srcRelated = append(srcRelated, srcRel) 178 } 179 srcDiag := &Diagnostic{ 180 URI: gobDiag.Location.URI, 181 Range: gobDiag.Location.Range, 182 Severity: gobDiag.Severity, 183 Code: gobDiag.Code, 184 CodeHref: gobDiag.CodeHref, 185 Source: DiagnosticSource(gobDiag.Source), 186 Message: gobDiag.Message, 187 Tags: gobDiag.Tags, 188 Related: srcRelated, 189 SuggestedFixes: srcFixes, 190 } 191 srcDiags = append(srcDiags, srcDiag) 192 } 193 return srcDiags 194 } 195 196 // toSourceDiagnostic converts a gobDiagnostic to "source" form. 197 func toSourceDiagnostic(srcAnalyzer *settings.Analyzer, gobDiag *gobDiagnostic) *Diagnostic { 198 var related []protocol.DiagnosticRelatedInformation 199 for _, gobRelated := range gobDiag.Related { 200 related = append(related, protocol.DiagnosticRelatedInformation(gobRelated)) 201 } 202 203 severity := srcAnalyzer.Severity 204 if severity == 0 { 205 severity = protocol.SeverityWarning 206 } 207 208 diag := &Diagnostic{ 209 URI: gobDiag.Location.URI, 210 Range: gobDiag.Location.Range, 211 Severity: severity, 212 Code: gobDiag.Code, 213 CodeHref: gobDiag.CodeHref, 214 Source: DiagnosticSource(gobDiag.Source), 215 Message: gobDiag.Message, 216 Related: related, 217 Tags: srcAnalyzer.Tag, 218 } 219 220 // If the fixes only delete code, assume that the diagnostic is reporting dead code. 221 if onlyDeletions(diag.SuggestedFixes) { 222 diag.Tags = append(diag.Tags, protocol.Unnecessary) 223 } 224 return diag 225 } 226 227 // onlyDeletions returns true if fixes is non-empty and all of the suggested 228 // fixes are deletions. 229 func onlyDeletions(fixes []SuggestedFix) bool { 230 for _, fix := range fixes { 231 if fix.Command != nil { 232 return false 233 } 234 for _, edits := range fix.Edits { 235 for _, edit := range edits { 236 if edit.NewText != "" { 237 return false 238 } 239 if protocol.ComparePosition(edit.Range.Start, edit.Range.End) == 0 { 240 return false 241 } 242 } 243 } 244 } 245 return len(fixes) > 0 246 } 247 248 func typesCodeHref(linkTarget string, code typesinternal.ErrorCode) string { 249 return BuildLink(linkTarget, "cuelang.org/go/internal/golangorgx/tools/typesinternal", code.String()) 250 } 251 252 // BuildLink constructs a URL with the given target, path, and anchor. 253 func BuildLink(target, path, anchor string) string { 254 link := fmt.Sprintf("https://%s/%s", target, path) 255 if anchor == "" { 256 return link 257 } 258 return link + "#" + anchor 259 } 260 261 func parseGoListError(e packages.Error, dir string) (filename string, line, col8 int) { 262 input := e.Pos 263 if input == "" { 264 // No position. Attempt to parse one out of a 265 // go list error of the form "file:line:col: 266 // message" by stripping off the message. 267 input = strings.TrimSpace(e.Msg) 268 if i := strings.Index(input, ": "); i >= 0 { 269 input = input[:i] 270 } 271 } 272 273 filename, line, col8 = splitFileLineCol(input) 274 if !filepath.IsAbs(filename) { 275 filename = filepath.Join(dir, filename) 276 } 277 return filename, line, col8 278 } 279 280 // splitFileLineCol splits s into "filename:line:col", 281 // where line and col consist of decimal digits. 282 func splitFileLineCol(s string) (file string, line, col8 int) { 283 // Beware that the filename may contain colon on Windows. 284 285 // stripColonDigits removes a ":%d" suffix, if any. 286 stripColonDigits := func(s string) (rest string, num int) { 287 if i := strings.LastIndex(s, ":"); i >= 0 { 288 if v, err := strconv.ParseInt(s[i+1:], 10, 32); err == nil { 289 return s[:i], int(v) 290 } 291 } 292 return s, -1 293 } 294 295 // strip col ":%d" 296 s, n1 := stripColonDigits(s) 297 if n1 < 0 { 298 return s, 0, 0 // "filename" 299 } 300 301 // strip line ":%d" 302 s, n2 := stripColonDigits(s) 303 if n2 < 0 { 304 return s, n1, 0 // "filename:line" 305 } 306 307 return s, n2, n1 // "filename:line:col" 308 }