|
|
// Copyright 2013 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 main
// This file defines utilities for working with file positions.
import ( "fmt" "go/build" "go/parser" "go/token" "os" "path/filepath" "strconv" "strings"
"golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/buildutil" )
// parseOctothorpDecimal returns the numeric value if s matches "#%d",
// otherwise -1.
func parseOctothorpDecimal(s string) int { if s != "" && s[0] == '#' { if s, err := strconv.ParseInt(s[1:], 10, 32); err == nil { return int(s) } } return -1 }
// parsePos parses a string of the form "file:pos" or
// file:start,end" where pos, start, end match #%d and represent byte
// offsets, and returns its components.
//
// (Numbers without a '#' prefix are reserved for future use,
// e.g. to indicate line/column positions.)
//
func parsePos(pos string) (filename string, startOffset, endOffset int, err error) { if pos == "" { err = fmt.Errorf("no source position specified") return }
colon := strings.LastIndex(pos, ":") if colon < 0 { err = fmt.Errorf("bad position syntax %q", pos) return } filename, offset := pos[:colon], pos[colon+1:] startOffset = -1 endOffset = -1 if comma := strings.Index(offset, ","); comma < 0 { // e.g. "foo.go:#123"
startOffset = parseOctothorpDecimal(offset) endOffset = startOffset } else { // e.g. "foo.go:#123,#456"
startOffset = parseOctothorpDecimal(offset[:comma]) endOffset = parseOctothorpDecimal(offset[comma+1:]) } if startOffset < 0 || endOffset < 0 { err = fmt.Errorf("invalid offset %q in query position", offset) return } return }
// fileOffsetToPos translates the specified file-relative byte offsets
// into token.Pos form. It returns an error if the file was not found
// or the offsets were out of bounds.
//
func fileOffsetToPos(file *token.File, startOffset, endOffset int) (start, end token.Pos, err error) { // Range check [start..end], inclusive of both end-points.
if 0 <= startOffset && startOffset <= file.Size() { start = file.Pos(int(startOffset)) } else { err = fmt.Errorf("start position is beyond end of file") return }
if 0 <= endOffset && endOffset <= file.Size() { end = file.Pos(int(endOffset)) } else { err = fmt.Errorf("end position is beyond end of file") return }
return }
// sameFile returns true if x and y have the same basename and denote
// the same file.
//
func sameFile(x, y string) bool { if filepath.Base(x) == filepath.Base(y) { // (optimisation)
if xi, err := os.Stat(x); err == nil { if yi, err := os.Stat(y); err == nil { return os.SameFile(xi, yi) } } } return false }
// fastQueryPos parses the position string and returns a queryPos.
// It parses only a single file and does not run the type checker.
func fastQueryPos(ctxt *build.Context, pos string) (*queryPos, error) { filename, startOffset, endOffset, err := parsePos(pos) if err != nil { return nil, err }
// Parse the file, opening it the file via the build.Context
// so that we observe the effects of the -modified flag.
fset := token.NewFileSet() cwd, _ := os.Getwd() f, err := buildutil.ParseFile(fset, ctxt, nil, cwd, filename, parser.Mode(0)) // ParseFile usually returns a partial file along with an error.
// Only fail if there is no file.
if f == nil { return nil, err } if !f.Pos().IsValid() { return nil, fmt.Errorf("%s is not a Go source file", filename) }
start, end, err := fileOffsetToPos(fset.File(f.Pos()), startOffset, endOffset) if err != nil { return nil, err }
path, exact := astutil.PathEnclosingInterval(f, start, end) if path == nil { return nil, fmt.Errorf("no syntax here") }
return &queryPos{fset, start, end, path, exact, nil}, nil }
|