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
5package cmd
6
7import (
8	"fmt"
9	"go/token"
10	"path/filepath"
11	"regexp"
12	"strconv"
13
14	"golang.org/x/tools/internal/lsp/source"
15)
16
17type Location struct {
18	Filename string   `json:"file"`
19	Start    Position `json:"start"`
20	End      Position `json:"end"`
21}
22
23type Position struct {
24	Line   int `json:"line"`
25	Column int `json:"column"`
26	Offset int `json:"offset"`
27}
28
29func newLocation(fset *token.FileSet, r source.Range) Location {
30	start := fset.Position(r.Start)
31	end := fset.Position(r.End)
32	// it should not be possible the following line to fail
33	filename, _ := source.ToURI(start.Filename).Filename()
34	return Location{
35		Filename: filename,
36		Start: Position{
37			Line:   start.Line,
38			Column: start.Column,
39			Offset: fset.File(r.Start).Offset(r.Start),
40		},
41		End: Position{
42			Line:   end.Line,
43			Column: end.Column,
44			Offset: fset.File(r.End).Offset(r.End),
45		},
46	}
47}
48
49var posRe = regexp.MustCompile(
50	`(?P<file>.*):(?P<start>(?P<sline>\d+):(?P<scol>\d)+|#(?P<soff>\d+))(?P<end>:(?P<eline>\d+):(?P<ecol>\d+)|#(?P<eoff>\d+))?$`)
51
52const (
53	posReAll = iota
54	posReFile
55	posReStart
56	posReSLine
57	posReSCol
58	posReSOff
59	posReEnd
60	posReELine
61	posReECol
62	posReEOff
63)
64
65func init() {
66	names := posRe.SubexpNames()
67	// verify all our submatch offsets are correct
68	for name, index := range map[string]int{
69		"file":  posReFile,
70		"start": posReStart,
71		"sline": posReSLine,
72		"scol":  posReSCol,
73		"soff":  posReSOff,
74		"end":   posReEnd,
75		"eline": posReELine,
76		"ecol":  posReECol,
77		"eoff":  posReEOff,
78	} {
79		if names[index] == name {
80			continue
81		}
82		// try to find it
83		for test := range names {
84			if names[test] == name {
85				panic(fmt.Errorf("Index for %s incorrect, wanted %v have %v", name, index, test))
86			}
87		}
88		panic(fmt.Errorf("Subexp %s does not exist", name))
89	}
90}
91
92// parseLocation parses a string of the form "file:pos" or
93// file:start,end" where pos, start, end match either a byte offset in the
94// form #%d or a line and column in the form %d,%d.
95func parseLocation(value string) (Location, error) {
96	var loc Location
97	m := posRe.FindStringSubmatch(value)
98	if m == nil {
99		return loc, fmt.Errorf("bad location syntax %q", value)
100	}
101	loc.Filename = m[posReFile]
102	if !filepath.IsAbs(loc.Filename) {
103		loc.Filename, _ = filepath.Abs(loc.Filename) // ignore error
104	}
105	if m[posReSLine] != "" {
106		v, err := strconv.ParseInt(m[posReSLine], 10, 32)
107		if err != nil {
108			return loc, err
109		}
110		loc.Start.Line = int(v)
111		v, err = strconv.ParseInt(m[posReSCol], 10, 32)
112		if err != nil {
113			return loc, err
114		}
115		loc.Start.Column = int(v)
116	} else {
117		v, err := strconv.ParseInt(m[posReSOff], 10, 32)
118		if err != nil {
119			return loc, err
120		}
121		loc.Start.Offset = int(v)
122	}
123	if m[posReEnd] == "" {
124		loc.End = loc.Start
125	} else {
126		if m[posReELine] != "" {
127			v, err := strconv.ParseInt(m[posReELine], 10, 32)
128			if err != nil {
129				return loc, err
130			}
131			loc.End.Line = int(v)
132			v, err = strconv.ParseInt(m[posReECol], 10, 32)
133			if err != nil {
134				return loc, err
135			}
136			loc.End.Column = int(v)
137		} else {
138			v, err := strconv.ParseInt(m[posReEOff], 10, 32)
139			if err != nil {
140				return loc, err
141			}
142			loc.End.Offset = int(v)
143		}
144	}
145	return loc, nil
146}
147
148func (l Location) Format(f fmt.State, c rune) {
149	// we should always have a filename
150	fmt.Fprint(f, l.Filename)
151	// are we in line:column format or #offset format
152	fmt.Fprintf(f, ":%v", l.Start)
153	if l.End != l.Start {
154		fmt.Fprintf(f, ",%v", l.End)
155	}
156}
157
158func (p Position) Format(f fmt.State, c rune) {
159	// are we in line:column format or #offset format
160	if p.Line > 0 {
161		fmt.Fprintf(f, "%d:%d", p.Line, p.Column)
162		return
163	}
164	fmt.Fprintf(f, "#%d", p.Offset)
165}
166