| // Copyright 2018 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package source |
| |
| import ( |
| "context" |
| "go/token" |
| "strconv" |
| "strings" |
| |
| "golang.org/x/tools/go/packages" |
| ) |
| |
| type Diagnostic struct { |
| token.Position |
| Message string |
| } |
| |
| func Diagnostics(ctx context.Context, f File) (map[string][]Diagnostic, error) { |
| pkg, err := f.GetPackage() |
| if err != nil { |
| return nil, err |
| } |
| // Prepare the reports we will send for this package. |
| reports := make(map[string][]Diagnostic) |
| for _, filename := range pkg.GoFiles { |
| reports[filename] = []Diagnostic{} |
| } |
| var parseErrors, typeErrors []packages.Error |
| for _, err := range pkg.Errors { |
| switch err.Kind { |
| case packages.ParseError: |
| parseErrors = append(parseErrors, err) |
| case packages.TypeError: |
| typeErrors = append(typeErrors, err) |
| default: |
| // ignore other types of errors |
| continue |
| } |
| } |
| // Don't report type errors if there are parse errors. |
| diags := typeErrors |
| if len(parseErrors) > 0 { |
| diags = parseErrors |
| } |
| for _, diag := range diags { |
| pos := errorPos(diag) |
| diagnostic := Diagnostic{ |
| Position: pos, |
| Message: diag.Msg, |
| } |
| if _, ok := reports[pos.Filename]; ok { |
| reports[pos.Filename] = append(reports[pos.Filename], diagnostic) |
| } |
| } |
| return reports, nil |
| } |
| |
| // FromTokenPosition converts a token.Position (1-based line and column |
| // number) to a token.Pos (byte offset value). |
| // It requires the token file the pos belongs to in order to do this. |
| func FromTokenPosition(f *token.File, pos token.Position) token.Pos { |
| line := lineStart(f, pos.Line) |
| return line + token.Pos(pos.Column-1) // TODO: this is wrong, bytes not characters |
| } |
| |
| func errorPos(pkgErr packages.Error) token.Position { |
| remainder1, first, hasLine := chop(pkgErr.Pos) |
| remainder2, second, hasColumn := chop(remainder1) |
| var pos token.Position |
| if hasLine && hasColumn { |
| pos.Filename = remainder2 |
| pos.Line = second |
| pos.Column = first |
| } else if hasLine { |
| pos.Filename = remainder1 |
| pos.Line = first |
| } |
| return pos |
| } |
| |
| func chop(text string) (remainder string, value int, ok bool) { |
| i := strings.LastIndex(text, ":") |
| if i < 0 { |
| return text, 0, false |
| } |
| v, err := strconv.ParseInt(text[i+1:], 10, 64) |
| if err != nil { |
| return text, 0, false |
| } |
| return text[:i], int(v), true |
| } |