Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201105173854-bc9fc8d8c4bc / internal / span / uri.go
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
5 package span
6
7 import (
8         "fmt"
9         "net/url"
10         "os"
11         "path"
12         "path/filepath"
13         "runtime"
14         "strings"
15         "unicode"
16 )
17
18 const fileScheme = "file"
19
20 // URI represents the full URI for a file.
21 type URI string
22
23 func (uri URI) IsFile() bool {
24         return strings.HasPrefix(string(uri), "file://")
25 }
26
27 // Filename returns the file path for the given URI.
28 // It is an error to call this on a URI that is not a valid filename.
29 func (uri URI) Filename() string {
30         filename, err := filename(uri)
31         if err != nil {
32                 panic(err)
33         }
34         return filepath.FromSlash(filename)
35 }
36
37 func filename(uri URI) (string, error) {
38         if uri == "" {
39                 return "", nil
40         }
41         u, err := url.ParseRequestURI(string(uri))
42         if err != nil {
43                 return "", err
44         }
45         if u.Scheme != fileScheme {
46                 return "", fmt.Errorf("only file URIs are supported, got %q from %q", u.Scheme, uri)
47         }
48         // If the URI is a Windows URI, we trim the leading "/" and lowercase
49         // the drive letter, which will never be case sensitive.
50         if isWindowsDriveURIPath(u.Path) {
51                 u.Path = strings.ToUpper(string(u.Path[1])) + u.Path[2:]
52         }
53         return u.Path, nil
54 }
55
56 func URIFromURI(s string) URI {
57         if !strings.HasPrefix(s, "file://") {
58                 return URI(s)
59         }
60
61         if !strings.HasPrefix(s, "file:///") {
62                 // VS Code sends URLs with only two slashes, which are invalid. golang/go#39789.
63                 s = "file:///" + s[len("file://"):]
64         }
65         // Even though the input is a URI, it may not be in canonical form. VS Code
66         // in particular over-escapes :, @, etc. Unescape and re-encode to canonicalize.
67         path, err := url.PathUnescape(s[len("file://"):])
68         if err != nil {
69                 panic(err)
70         }
71
72         // File URIs from Windows may have lowercase drive letters.
73         // Since drive letters are guaranteed to be case insensitive,
74         // we change them to uppercase to remain consistent.
75         // For example, file:///c:/x/y/z becomes file:///C:/x/y/z.
76         if isWindowsDriveURIPath(path) {
77                 path = path[:1] + strings.ToUpper(string(path[1])) + path[2:]
78         }
79         u := url.URL{Scheme: fileScheme, Path: path}
80         return URI(u.String())
81 }
82
83 func CompareURI(a, b URI) int {
84         if equalURI(a, b) {
85                 return 0
86         }
87         if a < b {
88                 return -1
89         }
90         return 1
91 }
92
93 func equalURI(a, b URI) bool {
94         if a == b {
95                 return true
96         }
97         // If we have the same URI basename, we may still have the same file URIs.
98         if !strings.EqualFold(path.Base(string(a)), path.Base(string(b))) {
99                 return false
100         }
101         fa, err := filename(a)
102         if err != nil {
103                 return false
104         }
105         fb, err := filename(b)
106         if err != nil {
107                 return false
108         }
109         // Stat the files to check if they are equal.
110         infoa, err := os.Stat(filepath.FromSlash(fa))
111         if err != nil {
112                 return false
113         }
114         infob, err := os.Stat(filepath.FromSlash(fb))
115         if err != nil {
116                 return false
117         }
118         return os.SameFile(infoa, infob)
119 }
120
121 // URIFromPath returns a span URI for the supplied file path.
122 // It will always have the file scheme.
123 func URIFromPath(path string) URI {
124         if path == "" {
125                 return ""
126         }
127         // Handle standard library paths that contain the literal "$GOROOT".
128         // TODO(rstambler): The go/packages API should allow one to determine a user's $GOROOT.
129         const prefix = "$GOROOT"
130         if len(path) >= len(prefix) && strings.EqualFold(prefix, path[:len(prefix)]) {
131                 suffix := path[len(prefix):]
132                 path = runtime.GOROOT() + suffix
133         }
134         if !isWindowsDrivePath(path) {
135                 if abs, err := filepath.Abs(path); err == nil {
136                         path = abs
137                 }
138         }
139         // Check the file path again, in case it became absolute.
140         if isWindowsDrivePath(path) {
141                 path = "/" + strings.ToUpper(string(path[0])) + path[1:]
142         }
143         path = filepath.ToSlash(path)
144         u := url.URL{
145                 Scheme: fileScheme,
146                 Path:   path,
147         }
148         return URI(u.String())
149 }
150
151 // isWindowsDrivePath returns true if the file path is of the form used by
152 // Windows. We check if the path begins with a drive letter, followed by a ":".
153 // For example: C:/x/y/z.
154 func isWindowsDrivePath(path string) bool {
155         if len(path) < 3 {
156                 return false
157         }
158         return unicode.IsLetter(rune(path[0])) && path[1] == ':'
159 }
160
161 // isWindowsDriveURI returns true if the file URI is of the format used by
162 // Windows URIs. The url.Parse package does not specially handle Windows paths
163 // (see golang/go#6027), so we check if the URI path has a drive prefix (e.g. "/C:").
164 func isWindowsDriveURIPath(uri string) bool {
165         if len(uri) < 4 {
166                 return false
167         }
168         return uri[0] == '/' && unicode.IsLetter(rune(uri[1])) && uri[2] == ':'
169 }