You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

142 lines
3.8 KiB

  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. package main
  5. // This file defines utilities for working with file positions.
  6. import (
  7. "fmt"
  8. "go/build"
  9. "go/parser"
  10. "go/token"
  11. "os"
  12. "path/filepath"
  13. "strconv"
  14. "strings"
  15. "golang.org/x/tools/go/ast/astutil"
  16. "golang.org/x/tools/go/buildutil"
  17. )
  18. // parseOctothorpDecimal returns the numeric value if s matches "#%d",
  19. // otherwise -1.
  20. func parseOctothorpDecimal(s string) int {
  21. if s != "" && s[0] == '#' {
  22. if s, err := strconv.ParseInt(s[1:], 10, 32); err == nil {
  23. return int(s)
  24. }
  25. }
  26. return -1
  27. }
  28. // parsePos parses a string of the form "file:pos" or
  29. // file:start,end" where pos, start, end match #%d and represent byte
  30. // offsets, and returns its components.
  31. //
  32. // (Numbers without a '#' prefix are reserved for future use,
  33. // e.g. to indicate line/column positions.)
  34. //
  35. func parsePos(pos string) (filename string, startOffset, endOffset int, err error) {
  36. if pos == "" {
  37. err = fmt.Errorf("no source position specified")
  38. return
  39. }
  40. colon := strings.LastIndex(pos, ":")
  41. if colon < 0 {
  42. err = fmt.Errorf("bad position syntax %q", pos)
  43. return
  44. }
  45. filename, offset := pos[:colon], pos[colon+1:]
  46. startOffset = -1
  47. endOffset = -1
  48. if comma := strings.Index(offset, ","); comma < 0 {
  49. // e.g. "foo.go:#123"
  50. startOffset = parseOctothorpDecimal(offset)
  51. endOffset = startOffset
  52. } else {
  53. // e.g. "foo.go:#123,#456"
  54. startOffset = parseOctothorpDecimal(offset[:comma])
  55. endOffset = parseOctothorpDecimal(offset[comma+1:])
  56. }
  57. if startOffset < 0 || endOffset < 0 {
  58. err = fmt.Errorf("invalid offset %q in query position", offset)
  59. return
  60. }
  61. return
  62. }
  63. // fileOffsetToPos translates the specified file-relative byte offsets
  64. // into token.Pos form. It returns an error if the file was not found
  65. // or the offsets were out of bounds.
  66. //
  67. func fileOffsetToPos(file *token.File, startOffset, endOffset int) (start, end token.Pos, err error) {
  68. // Range check [start..end], inclusive of both end-points.
  69. if 0 <= startOffset && startOffset <= file.Size() {
  70. start = file.Pos(int(startOffset))
  71. } else {
  72. err = fmt.Errorf("start position is beyond end of file")
  73. return
  74. }
  75. if 0 <= endOffset && endOffset <= file.Size() {
  76. end = file.Pos(int(endOffset))
  77. } else {
  78. err = fmt.Errorf("end position is beyond end of file")
  79. return
  80. }
  81. return
  82. }
  83. // sameFile returns true if x and y have the same basename and denote
  84. // the same file.
  85. //
  86. func sameFile(x, y string) bool {
  87. if filepath.Base(x) == filepath.Base(y) { // (optimisation)
  88. if xi, err := os.Stat(x); err == nil {
  89. if yi, err := os.Stat(y); err == nil {
  90. return os.SameFile(xi, yi)
  91. }
  92. }
  93. }
  94. return false
  95. }
  96. // fastQueryPos parses the position string and returns a queryPos.
  97. // It parses only a single file and does not run the type checker.
  98. func fastQueryPos(ctxt *build.Context, pos string) (*queryPos, error) {
  99. filename, startOffset, endOffset, err := parsePos(pos)
  100. if err != nil {
  101. return nil, err
  102. }
  103. // Parse the file, opening it the file via the build.Context
  104. // so that we observe the effects of the -modified flag.
  105. fset := token.NewFileSet()
  106. cwd, _ := os.Getwd()
  107. f, err := buildutil.ParseFile(fset, ctxt, nil, cwd, filename, parser.Mode(0))
  108. // ParseFile usually returns a partial file along with an error.
  109. // Only fail if there is no file.
  110. if f == nil {
  111. return nil, err
  112. }
  113. if !f.Pos().IsValid() {
  114. return nil, fmt.Errorf("%s is not a Go source file", filename)
  115. }
  116. start, end, err := fileOffsetToPos(fset.File(f.Pos()), startOffset, endOffset)
  117. if err != nil {
  118. return nil, err
  119. }
  120. path, exact := astutil.PathEnclosingInterval(f, start, end)
  121. if path == nil {
  122. return nil, fmt.Errorf("no syntax here")
  123. }
  124. return &queryPos{fset, start, end, path, exact, nil}, nil
  125. }