internal/span: always uppercase the drive letter for Windows
Drive letters are always case-insensitive, so we should standardize them
by always keeping them uppercase.
Updates golang/go#36904
Change-Id: I8de25b175790b01627f947600c1511edf38c316c
Reviewed-on: https://go-review.googlesource.com/c/tools/+/217080
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
(cherry picked from commit 0725381040373f87e53f74b3416069109eceb74e)
Reviewed-on: https://go-review.googlesource.com/c/tools/+/217088
Reviewed-by: Michael Matloob <matloob@golang.org>
diff --git a/internal/span/uri.go b/internal/span/uri.go
index e05a9e6..1c39134 100644
--- a/internal/span/uri.go
+++ b/internal/span/uri.go
@@ -41,9 +41,12 @@
if u.Scheme != fileScheme {
return "", fmt.Errorf("only file URIs are supported, got %q from %q", u.Scheme, uri)
}
- if isWindowsDriveURI(u.Path) {
- u.Path = u.Path[1:]
+ // If the URI is a Windows URI, we trim the leading "/" and lowercase
+ // the drive letter, which will never be case sensitive.
+ if isWindowsDriveURIPath(u.Path) {
+ u.Path = strings.ToUpper(string(u.Path[1])) + u.Path[2:]
}
+
return u.Path, nil
}
@@ -53,7 +56,16 @@
if u, err := url.PathUnescape(s); err == nil {
s = u
}
+ // If a path has a scheme, it is already a URI.
+ // We only handle the file:// scheme.
if strings.HasPrefix(s, fileScheme+"://") {
+ // File URIs from Windows may have lowercase drive letters.
+ // Since drive letters are guaranteed to be case insensitive,
+ // we change them to uppercase to remain consistent.
+ // For example, file:///c:/x/y/z becomes file:///C:/x/y/z.
+ if i := len(fileScheme + "://"); isWindowsDriveURIPath(s[i:]) {
+ s = s[:i+1] + strings.ToUpper(string(s[i+1])) + s[i+2:]
+ }
return URI(s)
}
return FileURI(s)
@@ -117,7 +129,7 @@
}
// Check the file path again, in case it became absolute.
if isWindowsDrivePath(path) {
- path = "/" + path
+ path = "/" + strings.ToUpper(string(path[0])) + path[1:]
}
path = filepath.ToSlash(path)
u := url.URL{
@@ -133,8 +145,9 @@
// isWindowsDrivePath returns true if the file path is of the form used by
// Windows. We check if the path begins with a drive letter, followed by a ":".
+// For example: C:/x/y/z.
func isWindowsDrivePath(path string) bool {
- if len(path) < 4 {
+ if len(path) < 3 {
return false
}
return unicode.IsLetter(rune(path[0])) && path[1] == ':'
@@ -142,9 +155,8 @@
// isWindowsDriveURI returns true if the file URI is of the format used by
// Windows URIs. The url.Parse package does not specially handle Windows paths
-// (see https://golang.org/issue/6027). We check if the URI path has
-// a drive prefix (e.g. "/C:"). If so, we trim the leading "/".
-func isWindowsDriveURI(uri string) bool {
+// (see golang/go#6027). We check if the URI path has a drive prefix (e.g. "/C:").
+func isWindowsDriveURIPath(uri string) bool {
if len(uri) < 4 {
return false
}
diff --git a/internal/span/uri_test.go b/internal/span/uri_test.go
index 907b47b..d29f130 100644
--- a/internal/span/uri_test.go
+++ b/internal/span/uri_test.go
@@ -2,10 +2,11 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+// +build !windows
+
package span_test
import (
- "path/filepath"
"testing"
"golang.org/x/tools/internal/span"
@@ -16,36 +17,58 @@
// tests by using only forward slashes, assuming that the standard library
// functions filepath.ToSlash and filepath.FromSlash do not need testing.
func TestURI(t *testing.T) {
- for _, test := range []string{
- ``,
- `C:/Windows/System32`,
- `C:/Go/src/bob.go`,
- `c:/Go/src/bob.go`,
- `/path/to/dir`,
- `/a/b/c/src/bob.go`,
- `c:/Go/src/bob george/george/george.go`,
+ for _, test := range []struct {
+ path, wantFile string
+ wantURI span.URI
+ }{
+ {
+ path: ``,
+ wantFile: ``,
+ wantURI: span.URI(""),
+ },
+ {
+ path: `C:/Windows/System32`,
+ wantFile: `C:/Windows/System32`,
+ wantURI: span.URI("file:///C:/Windows/System32"),
+ },
+ {
+ path: `C:/Go/src/bob.go`,
+ wantFile: `C:/Go/src/bob.go`,
+ wantURI: span.URI("file:///C:/Go/src/bob.go"),
+ },
+ {
+ path: `c:/Go/src/bob.go`,
+ wantFile: `C:/Go/src/bob.go`,
+ wantURI: span.URI("file:///C:/Go/src/bob.go"),
+ },
+ {
+ path: `/path/to/dir`,
+ wantFile: `/path/to/dir`,
+ wantURI: span.URI("file:///path/to/dir"),
+ },
+ {
+ path: `/a/b/c/src/bob.go`,
+ wantFile: `/a/b/c/src/bob.go`,
+ wantURI: span.URI("file:///a/b/c/src/bob.go"),
+ },
+ {
+ path: `c:/Go/src/bob george/george/george.go`,
+ wantFile: `C:/Go/src/bob george/george/george.go`,
+ wantURI: span.URI("file:///C:/Go/src/bob george/george/george.go"),
+ },
+ {
+ path: `file:///c:/Go/src/bob george/george/george.go`,
+ wantFile: `C:/Go/src/bob george/george/george.go`,
+ wantURI: span.URI("file:///C:/Go/src/bob george/george/george.go"),
+ },
} {
- testPath := filepath.FromSlash(test)
- expectPath := testPath
- if len(test) > 0 && test[0] == '/' {
- if abs, err := filepath.Abs(expectPath); err == nil {
- expectPath = abs
- }
+ got := span.NewURI(test.path)
+ if got != test.wantURI {
+ t.Errorf("ToURI: got %s, expected %s", got, test.wantURI)
}
- expectURI := filepath.ToSlash(expectPath)
- if len(expectURI) > 0 {
- if expectURI[0] != '/' {
- expectURI = "/" + expectURI
- }
- expectURI = "file://" + expectURI
- }
- uri := span.FileURI(testPath)
- if expectURI != string(uri) {
- t.Errorf("ToURI: expected %s, got %s", expectURI, uri)
- }
- filename := uri.Filename()
- if expectPath != filename {
- t.Errorf("Filename: expected %s, got %s", expectPath, filename)
+ gotFilename := got.Filename()
+ if gotFilename != test.wantFile {
+ t.Errorf("Filename: got %s, expected %s", gotFilename, test.wantFile)
}
}
}
diff --git a/internal/span/uri_windows_test.go b/internal/span/uri_windows_test.go
new file mode 100644
index 0000000..2eb07e7
--- /dev/null
+++ b/internal/span/uri_windows_test.go
@@ -0,0 +1,73 @@
+// Copyright 2020 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.
+
+// +build windows
+
+package span_test
+
+import (
+ "testing"
+
+ "golang.org/x/tools/internal/span"
+)
+
+// TestURI tests the conversion between URIs and filenames. The test cases
+// include Windows-style URIs and filepaths, but we avoid having OS-specific
+// tests by using only forward slashes, assuming that the standard library
+// functions filepath.ToSlash and filepath.FromSlash do not need testing.
+func TestURI(t *testing.T) {
+ for _, test := range []struct {
+ path, wantFile string
+ wantURI span.URI
+ }{
+ {
+ path: ``,
+ wantFile: ``,
+ wantURI: span.URI(""),
+ },
+ {
+ path: `C:\Windows\System32`,
+ wantFile: `C:\Windows\System32`,
+ wantURI: span.URI("file:///C:/Windows/System32"),
+ },
+ {
+ path: `C:\Go\src\bob.go`,
+ wantFile: `C:\Go\src\bob.go`,
+ wantURI: span.URI("file:///C:/Go/src/bob.go"),
+ },
+ {
+ path: `c:\Go\src\bob.go`,
+ wantFile: `C:\Go\src\bob.go`,
+ wantURI: span.URI("file:///C:/Go/src/bob.go"),
+ },
+ {
+ path: `\path\to\dir`,
+ wantFile: `C:\path\to\dir`,
+ wantURI: span.URI("file:///C:/path/to/dir"),
+ },
+ {
+ path: `\a\b\c\src\bob.go`,
+ wantFile: `C:\a\b\c\src\bob.go`,
+ wantURI: span.URI("file:///C:/a/b/c/src/bob.go"),
+ },
+ {
+ path: `c:\Go\src\bob george\george\george.go`,
+ wantFile: `C:\Go\src\bob george\george\george.go`,
+ wantURI: span.URI("file:///C:/Go/src/bob george/george/george.go"),
+ },
+ {
+ path: `file:///c:/Go/src/bob george/george/george.go`,
+ wantFile: `C:\Go\src\bob george\george\george.go`,
+ wantURI: span.URI("file:///C:/Go/src/bob george/george/george.go"),
+ },
+ } {
+ got := span.NewURI(test.path)
+ if got != test.wantURI {
+ t.Errorf("ToURI: got %s, expected %s", got, test.wantURI)
+ }
+ if got.Filename() != test.wantFile {
+ t.Errorf("Filename: got %s, expected %s", got.Filename(), test.wantFile)
+ }
+ }
+}