blob: 2e659fe4244a6d63028004e8c623382327980c29 [file] [log] [blame]
Alan Donovan73473792016-02-11 17:57:17 -05001// Copyright 2013 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
5package main
6
7// This file defines utilities for working with file positions.
8
9import (
10 "fmt"
Alan Donovan6c84e9e2016-03-03 13:42:12 -050011 "go/build"
Alan Donovan73473792016-02-11 17:57:17 -050012 "go/parser"
13 "go/token"
14 "os"
15 "path/filepath"
16 "strconv"
17 "strings"
18
19 "golang.org/x/tools/go/ast/astutil"
Alan Donovan6c84e9e2016-03-03 13:42:12 -050020 "golang.org/x/tools/go/buildutil"
Alan Donovan73473792016-02-11 17:57:17 -050021)
22
23// parseOctothorpDecimal returns the numeric value if s matches "#%d",
24// otherwise -1.
25func parseOctothorpDecimal(s string) int {
26 if s != "" && s[0] == '#' {
27 if s, err := strconv.ParseInt(s[1:], 10, 32); err == nil {
28 return int(s)
29 }
30 }
31 return -1
32}
33
Alan Donovan1d6b8692016-02-12 16:57:12 -050034// parsePos parses a string of the form "file:pos" or
Alan Donovan73473792016-02-11 17:57:17 -050035// file:start,end" where pos, start, end match #%d and represent byte
36// offsets, and returns its components.
37//
38// (Numbers without a '#' prefix are reserved for future use,
39// e.g. to indicate line/column positions.)
40//
Alan Donovan1d6b8692016-02-12 16:57:12 -050041func parsePos(pos string) (filename string, startOffset, endOffset int, err error) {
42 if pos == "" {
43 err = fmt.Errorf("no source position specified")
Alan Donovan73473792016-02-11 17:57:17 -050044 return
45 }
46
Alan Donovan1d6b8692016-02-12 16:57:12 -050047 colon := strings.LastIndex(pos, ":")
Alan Donovan73473792016-02-11 17:57:17 -050048 if colon < 0 {
Alan Donovan1d6b8692016-02-12 16:57:12 -050049 err = fmt.Errorf("bad position syntax %q", pos)
Alan Donovan73473792016-02-11 17:57:17 -050050 return
51 }
Alan Donovan1d6b8692016-02-12 16:57:12 -050052 filename, offset := pos[:colon], pos[colon+1:]
Alan Donovan73473792016-02-11 17:57:17 -050053 startOffset = -1
54 endOffset = -1
Koichi Shiraishif8ed2e42017-01-26 04:08:12 +090055 if comma := strings.Index(offset, ","); comma < 0 {
Alan Donovan73473792016-02-11 17:57:17 -050056 // e.g. "foo.go:#123"
57 startOffset = parseOctothorpDecimal(offset)
58 endOffset = startOffset
59 } else {
60 // e.g. "foo.go:#123,#456"
Koichi Shiraishif8ed2e42017-01-26 04:08:12 +090061 startOffset = parseOctothorpDecimal(offset[:comma])
62 endOffset = parseOctothorpDecimal(offset[comma+1:])
Alan Donovan73473792016-02-11 17:57:17 -050063 }
64 if startOffset < 0 || endOffset < 0 {
Alan Donovan1d6b8692016-02-12 16:57:12 -050065 err = fmt.Errorf("invalid offset %q in query position", offset)
Alan Donovan73473792016-02-11 17:57:17 -050066 return
67 }
68 return
69}
70
Alan Donovane08a7ae2016-02-14 22:14:31 -050071// fileOffsetToPos translates the specified file-relative byte offsets
72// into token.Pos form. It returns an error if the file was not found
73// or the offsets were out of bounds.
Alan Donovan73473792016-02-11 17:57:17 -050074//
Alan Donovane08a7ae2016-02-14 22:14:31 -050075func fileOffsetToPos(file *token.File, startOffset, endOffset int) (start, end token.Pos, err error) {
Alan Donovan73473792016-02-11 17:57:17 -050076 // Range check [start..end], inclusive of both end-points.
77
78 if 0 <= startOffset && startOffset <= file.Size() {
79 start = file.Pos(int(startOffset))
80 } else {
81 err = fmt.Errorf("start position is beyond end of file")
82 return
83 }
84
85 if 0 <= endOffset && endOffset <= file.Size() {
86 end = file.Pos(int(endOffset))
87 } else {
88 err = fmt.Errorf("end position is beyond end of file")
89 return
90 }
91
92 return
93}
94
95// sameFile returns true if x and y have the same basename and denote
96// the same file.
97//
98func sameFile(x, y string) bool {
99 if filepath.Base(x) == filepath.Base(y) { // (optimisation)
100 if xi, err := os.Stat(x); err == nil {
101 if yi, err := os.Stat(y); err == nil {
102 return os.SameFile(xi, yi)
103 }
104 }
105 }
106 return false
107}
108
Alan Donovane08a7ae2016-02-14 22:14:31 -0500109// fastQueryPos parses the position string and returns a queryPos.
110// It parses only a single file and does not run the type checker.
Alan Donovan6c84e9e2016-03-03 13:42:12 -0500111func fastQueryPos(ctxt *build.Context, pos string) (*queryPos, error) {
Alan Donovan1d6b8692016-02-12 16:57:12 -0500112 filename, startOffset, endOffset, err := parsePos(pos)
Alan Donovan73473792016-02-11 17:57:17 -0500113 if err != nil {
114 return nil, err
115 }
116
Alan Donovan6c84e9e2016-03-03 13:42:12 -0500117 // Parse the file, opening it the file via the build.Context
118 // so that we observe the effects of the -modified flag.
Alan Donovan73473792016-02-11 17:57:17 -0500119 fset := token.NewFileSet()
Alan Donovan6c84e9e2016-03-03 13:42:12 -0500120 cwd, _ := os.Getwd()
121 f, err := buildutil.ParseFile(fset, ctxt, nil, cwd, filename, parser.Mode(0))
Alan Donovanfb3df4a2016-02-15 14:50:53 -0500122 // ParseFile usually returns a partial file along with an error.
123 // Only fail if there is no file.
124 if f == nil {
Alan Donovan73473792016-02-11 17:57:17 -0500125 return nil, err
126 }
Alan Donovand2abdd52016-03-07 09:47:24 -0500127 if !f.Pos().IsValid() {
128 return nil, fmt.Errorf("%s is not a Go source file", filename)
129 }
Alan Donovan73473792016-02-11 17:57:17 -0500130
Alan Donovane08a7ae2016-02-14 22:14:31 -0500131 start, end, err := fileOffsetToPos(fset.File(f.Pos()), startOffset, endOffset)
Alan Donovan73473792016-02-11 17:57:17 -0500132 if err != nil {
133 return nil, err
134 }
135
136 path, exact := astutil.PathEnclosingInterval(f, start, end)
137 if path == nil {
138 return nil, fmt.Errorf("no syntax here")
139 }
140
141 return &queryPos{fset, start, end, path, exact, nil}, nil
142}