github.com/april1989/origin-go-tools@v0.0.32/cmd/guru/pos.go (about)

     1  // 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  
     5  package main
     6  
     7  // This file defines utilities for working with file positions.
     8  
     9  import (
    10  	"fmt"
    11  	"go/build"
    12  	"go/parser"
    13  	"go/token"
    14  	"os"
    15  	"path/filepath"
    16  	"strconv"
    17  	"strings"
    18  
    19  	"github.com/april1989/origin-go-tools/go/ast/astutil"
    20  	"github.com/april1989/origin-go-tools/go/buildutil"
    21  )
    22  
    23  // parseOctothorpDecimal returns the numeric value if s matches "#%d",
    24  // otherwise -1.
    25  func 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  
    34  // parsePos parses a string of the form "file:pos" or
    35  // 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  //
    41  func parsePos(pos string) (filename string, startOffset, endOffset int, err error) {
    42  	if pos == "" {
    43  		err = fmt.Errorf("no source position specified")
    44  		return
    45  	}
    46  
    47  	colon := strings.LastIndex(pos, ":")
    48  	if colon < 0 {
    49  		err = fmt.Errorf("bad position syntax %q", pos)
    50  		return
    51  	}
    52  	filename, offset := pos[:colon], pos[colon+1:]
    53  	startOffset = -1
    54  	endOffset = -1
    55  	if comma := strings.Index(offset, ","); comma < 0 {
    56  		// e.g. "foo.go:#123"
    57  		startOffset = parseOctothorpDecimal(offset)
    58  		endOffset = startOffset
    59  	} else {
    60  		// e.g. "foo.go:#123,#456"
    61  		startOffset = parseOctothorpDecimal(offset[:comma])
    62  		endOffset = parseOctothorpDecimal(offset[comma+1:])
    63  	}
    64  	if startOffset < 0 || endOffset < 0 {
    65  		err = fmt.Errorf("invalid offset %q in query position", offset)
    66  		return
    67  	}
    68  	return
    69  }
    70  
    71  // 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.
    74  //
    75  func fileOffsetToPos(file *token.File, startOffset, endOffset int) (start, end token.Pos, err error) {
    76  	// 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  //
    98  func 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  
   109  // fastQueryPos parses the position string and returns a queryPos.
   110  // It parses only a single file and does not run the type checker.
   111  func fastQueryPos(ctxt *build.Context, pos string) (*queryPos, error) {
   112  	filename, startOffset, endOffset, err := parsePos(pos)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  
   117  	// Parse the file, opening it the file via the build.Context
   118  	// so that we observe the effects of the -modified flag.
   119  	fset := token.NewFileSet()
   120  	cwd, _ := os.Getwd()
   121  	f, err := buildutil.ParseFile(fset, ctxt, nil, cwd, filename, parser.Mode(0))
   122  	// ParseFile usually returns a partial file along with an error.
   123  	// Only fail if there is no file.
   124  	if f == nil {
   125  		return nil, err
   126  	}
   127  	if !f.Pos().IsValid() {
   128  		return nil, fmt.Errorf("%s is not a Go source file", filename)
   129  	}
   130  
   131  	start, end, err := fileOffsetToPos(fset.File(f.Pos()), startOffset, endOffset)
   132  	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  }