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.
21 "golang.org/x/tools/internal/lsp/protocol"
22 "golang.org/x/tools/internal/span"
23 errors "golang.org/x/xerrors"
26 // MappedRange provides mapped protocol.Range for a span.Range, accounting for
27 // UTF-16 code points.
28 type MappedRange struct {
30 m *protocol.ColumnMapper
32 // protocolRange is the result of converting the spanRange using the mapper.
33 // It is computed on-demand.
34 protocolRange *protocol.Range
37 // NewMappedRange returns a MappedRange for the given start and end token.Pos.
38 func NewMappedRange(fset *token.FileSet, m *protocol.ColumnMapper, start, end token.Pos) MappedRange {
40 spanRange: span.Range{
44 Converter: m.Converter,
50 func (s MappedRange) Range() (protocol.Range, error) {
51 if s.protocolRange == nil {
52 spn, err := s.spanRange.Span()
54 return protocol.Range{}, err
56 prng, err := s.m.Range(spn)
58 return protocol.Range{}, err
60 s.protocolRange = &prng
62 return *s.protocolRange, nil
65 func (s MappedRange) Span() (span.Span, error) {
66 return s.spanRange.Span()
69 func (s MappedRange) SpanRange() span.Range {
73 func (s MappedRange) URI() span.URI {
77 // GetParsedFile is a convenience function that extracts the Package and
78 // ParsedGoFile for a file in a Snapshot. pkgPolicy is one of NarrowestPackage/
80 func GetParsedFile(ctx context.Context, snapshot Snapshot, fh FileHandle, pkgPolicy PackageFilter) (Package, *ParsedGoFile, error) {
81 pkg, err := snapshot.PackageForFile(ctx, fh.URI(), TypecheckWorkspace, pkgPolicy)
85 pgh, err := pkg.File(fh.URI())
89 func IsGenerated(ctx context.Context, snapshot Snapshot, uri span.URI) bool {
90 fh, err := snapshot.GetFile(ctx, uri)
94 pgf, err := snapshot.ParseGo(ctx, fh, ParseHeader)
98 tok := snapshot.FileSet().File(pgf.File.Pos())
102 for _, commentGroup := range pgf.File.Comments {
103 for _, comment := range commentGroup.List {
104 if matched := generatedRx.MatchString(comment.Text); matched {
105 // Check if comment is at the beginning of the line in source.
106 if pos := tok.Position(comment.Slash); pos.Column == 1 {
115 func nodeToProtocolRange(snapshot Snapshot, pkg Package, n ast.Node) (protocol.Range, error) {
116 mrng, err := posToMappedRange(snapshot, pkg, n.Pos(), n.End())
118 return protocol.Range{}, err
123 func objToMappedRange(snapshot Snapshot, pkg Package, obj types.Object) (MappedRange, error) {
124 if pkgName, ok := obj.(*types.PkgName); ok {
125 // An imported Go package has a package-local, unqualified name.
126 // When the name matches the imported package name, there is no
127 // identifier in the import spec with the local package name.
130 // import "go/ast" // name "ast" matches package name
131 // import a "go/ast" // name "a" does not match package name
133 // When the identifier does not appear in the source, have the range
134 // of the object be the import path, including quotes.
135 if pkgName.Imported().Name() == pkgName.Name() {
136 return posToMappedRange(snapshot, pkg, obj.Pos(), obj.Pos()+token.Pos(len(pkgName.Imported().Path())+2))
139 return nameToMappedRange(snapshot, pkg, obj.Pos(), obj.Name())
142 func nameToMappedRange(snapshot Snapshot, pkg Package, pos token.Pos, name string) (MappedRange, error) {
143 return posToMappedRange(snapshot, pkg, pos, pos+token.Pos(len(name)))
146 func posToMappedRange(snapshot Snapshot, pkg Package, pos, end token.Pos) (MappedRange, error) {
147 logicalFilename := snapshot.FileSet().File(pos).Position(pos).Filename
148 pgf, _, err := findFileInDeps(pkg, span.URIFromPath(logicalFilename))
150 return MappedRange{}, err
153 return MappedRange{}, errors.Errorf("invalid position for %v", pos)
156 return MappedRange{}, errors.Errorf("invalid position for %v", end)
158 return NewMappedRange(snapshot.FileSet(), pgf.Mapper, pos, end), nil
161 // Matches cgo generated comment as well as the proposed standard:
162 // https://golang.org/s/generatedcode
163 var generatedRx = regexp.MustCompile(`// .*DO NOT EDIT\.?`)
165 func DetectLanguage(langID, filename string) FileKind {
174 // Fallback to detecting the language based on the file extension.
175 switch filepath.Ext(filename) {
180 default: // fallback to Go
185 func (k FileKind) String() string {
196 // nodeAtPos returns the index and the node whose position is contained inside
198 func nodeAtPos(nodes []ast.Node, pos token.Pos) (ast.Node, int) {
202 for i, node := range nodes {
203 if node.Pos() <= pos && pos <= node.End() {
210 // IsInterface returns if a types.Type is an interface
211 func IsInterface(T types.Type) bool {
212 return T != nil && types.IsInterface(T)
215 // FormatNode returns the "pretty-print" output for an ast node.
216 func FormatNode(fset *token.FileSet, n ast.Node) string {
217 var buf strings.Builder
218 if err := printer.Fprint(&buf, fset, n); err != nil {
224 // Deref returns a pointer's element type, traversing as many levels as needed.
225 // Otherwise it returns typ.
226 func Deref(typ types.Type) types.Type {
228 p, ok := typ.Underlying().(*types.Pointer)
236 func SortDiagnostics(d []*Diagnostic) {
237 sort.Slice(d, func(i int, j int) bool {
238 return CompareDiagnostic(d[i], d[j]) < 0
242 func CompareDiagnostic(a, b *Diagnostic) int {
243 if r := protocol.CompareRange(a.Range, b.Range); r != 0 {
246 if a.Source < b.Source {
249 if a.Message < b.Message {
252 if a.Message == b.Message {
258 // FindPosInPackage finds the parsed file for a position in a given search
260 func FindPosInPackage(snapshot Snapshot, searchpkg Package, pos token.Pos) (*ParsedGoFile, Package, error) {
261 tok := snapshot.FileSet().File(pos)
263 return nil, nil, errors.Errorf("no file for pos in package %s", searchpkg.ID())
265 uri := span.URIFromPath(tok.Name())
267 pgf, pkg, err := findFileInDeps(searchpkg, uri)
274 // findFileInDeps finds uri in pkg or its dependencies.
275 func findFileInDeps(pkg Package, uri span.URI) (*ParsedGoFile, Package, error) {
276 queue := []Package{pkg}
277 seen := make(map[string]bool)
282 seen[pkg.ID()] = true
284 if pgf, err := pkg.File(uri); err == nil {
287 for _, dep := range pkg.Imports() {
289 queue = append(queue, dep)
293 return nil, nil, errors.Errorf("no file for %s in package %s", uri, pkg.ID())
296 // MarshalArgs encodes the given arguments to json.RawMessages. This function
297 // is used to construct arguments to a protocol.Command.
301 // jsonArgs, err := EncodeArgs(1, "hello", true, StructuredArg{42, 12.6})
303 func MarshalArgs(args ...interface{}) ([]json.RawMessage, error) {
304 var out []json.RawMessage
305 for _, arg := range args {
306 argJSON, err := json.Marshal(arg)
310 out = append(out, argJSON)
315 // UnmarshalArgs decodes the given json.RawMessages to the variables provided
316 // by args. Each element of args should be a pointer.
324 // structured StructuredArg
326 // err := UnmarshalArgs(args, &num, &str, &bul, &structured)
328 func UnmarshalArgs(jsonArgs []json.RawMessage, args ...interface{}) error {
329 if len(args) != len(jsonArgs) {
330 return fmt.Errorf("DecodeArgs: expected %d input arguments, got %d JSON arguments", len(args), len(jsonArgs))
332 for i, arg := range args {
333 if err := json.Unmarshal(jsonArgs[i], arg); err != nil {
340 // ImportPath returns the unquoted import path of s,
341 // or "" if the path is not properly quoted.
342 func ImportPath(s *ast.ImportSpec) string {
343 t, err := strconv.Unquote(s.Path.Value)
350 // NodeContains returns true if a node encloses a given position pos.
351 func NodeContains(n ast.Node, pos token.Pos) bool {
352 return n != nil && n.Pos() <= pos && pos <= n.End()
355 // CollectScopes returns all scopes in an ast path, ordered as innermost scope
357 func CollectScopes(info *types.Info, path []ast.Node, pos token.Pos) []*types.Scope {
358 // scopes[i], where i<len(path), is the possibly nil Scope of path[i].
359 var scopes []*types.Scope
360 for _, n := range path {
361 // Include *FuncType scope if pos is inside the function body.
362 switch node := n.(type) {
364 if node.Body != nil && NodeContains(node.Body, pos) {
368 if node.Body != nil && NodeContains(node.Body, pos) {
372 scopes = append(scopes, info.Scopes[n])
377 // Qualifier returns a function that appropriately formats a types.PkgName
378 // appearing in a *ast.File.
379 func Qualifier(f *ast.File, pkg *types.Package, info *types.Info) types.Qualifier {
380 // Construct mapping of import paths to their defined or implicit names.
381 imports := make(map[*types.Package]string)
382 for _, imp := range f.Imports {
385 obj = info.Defs[imp.Name]
387 obj = info.Implicits[imp]
389 if pkgname, ok := obj.(*types.PkgName); ok {
390 imports[pkgname.Imported()] = pkgname.Name()
393 // Define qualifier to replace full package paths with names of the imports.
394 return func(p *types.Package) string {
398 if name, ok := imports[p]; ok {