internal/span,lsp: disambiguate URIs, DocumentURIs, and paths

Create a real type for protocol.DocumentURIs. Remove span.NewURI in
favor of path/URI-specific constructors. Remove span.Parse's ability to
parse URI-based spans, which appears to be totally unused.

As a consequence, we no longer mangle non-file URIs to start with
file://, and crash all over the place when one is opened.

Updates golang/go#33699.

Change-Id: Ic7347c9768e38002b4ad9c84471329d0af7d2e05
Reviewed-on: https://go-review.googlesource.com/c/tools/+/219482
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/gopls/integration/replay/main.go b/gopls/integration/replay/main.go
index cd30dd6..1bd69ac 100644
--- a/gopls/integration/replay/main.go
+++ b/gopls/integration/replay/main.go
@@ -68,7 +68,7 @@
 	log.Printf("new %d, hist:%s", len(newMsgs), seen.Histogram)
 
 	ok := make(map[string]int)
-	f := func(x []*parse.Logmsg, label string, diags map[string][]p.Diagnostic) {
+	f := func(x []*parse.Logmsg, label string, diags map[p.DocumentURI][]p.Diagnostic) {
 		counts := make(map[parse.MsgType]int)
 		for _, l := range x {
 			if l.Method == "window/logMessage" {
@@ -102,9 +102,9 @@
 		}
 		log.Printf("%s: %s", label, msg)
 	}
-	mdiags := make(map[string][]p.Diagnostic)
+	mdiags := make(map[p.DocumentURI][]p.Diagnostic)
 	f(msgs, "old", mdiags)
-	vdiags := make(map[string][]p.Diagnostic)
+	vdiags := make(map[p.DocumentURI][]p.Diagnostic)
 	f(newMsgs, "new", vdiags)
 	buf := []string{}
 	for k := range ok {
diff --git a/internal/lsp/cache/errors.go b/internal/lsp/cache/errors.go
index 787a431..16ddd50 100644
--- a/internal/lsp/cache/errors.go
+++ b/internal/lsp/cache/errors.go
@@ -177,7 +177,7 @@
 
 func typeErrorRange(ctx context.Context, fset *token.FileSet, pkg *pkg, pos token.Pos) (span.Span, error) {
 	posn := fset.Position(pos)
-	ph, _, err := source.FindFileInPackage(pkg, span.FileURI(posn.Filename))
+	ph, _, err := source.FindFileInPackage(pkg, span.URIFromPath(posn.Filename))
 	if err != nil {
 		return span.Span{}, err
 	}
@@ -213,7 +213,7 @@
 }
 
 func scannerErrorRange(ctx context.Context, fset *token.FileSet, pkg *pkg, posn token.Position) (span.Span, error) {
-	ph, _, err := source.FindFileInPackage(pkg, span.FileURI(posn.Filename))
+	ph, _, err := source.FindFileInPackage(pkg, span.URIFromPath(posn.Filename))
 	if err != nil {
 		return span.Span{}, err
 	}
diff --git a/internal/lsp/cache/load.go b/internal/lsp/cache/load.go
index 24c1151..43b24fc 100644
--- a/internal/lsp/cache/load.go
+++ b/internal/lsp/cache/load.go
@@ -147,12 +147,12 @@
 	}
 
 	for _, filename := range pkg.CompiledGoFiles {
-		uri := span.FileURI(filename)
+		uri := span.URIFromPath(filename)
 		m.compiledGoFiles = append(m.compiledGoFiles, uri)
 		s.addID(uri, m.id)
 	}
 	for _, filename := range pkg.GoFiles {
-		uri := span.FileURI(filename)
+		uri := span.URIFromPath(filename)
 		m.goFiles = append(m.goFiles, uri)
 		s.addID(uri, m.id)
 	}
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index 2106699..e0b5ac8 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -236,7 +236,7 @@
 	if len(goFiles) != 1 {
 		return errors.Errorf("only expected 1 file, got %v", len(goFiles))
 	}
-	uri := span.FileURI(goFiles[0])
+	uri := span.URIFromPath(goFiles[0])
 	v.addIgnoredFile(uri) // to avoid showing diagnostics for builtin.go
 
 	// Get the FileHandle through the cache to avoid adding it to the snapshot
@@ -585,7 +585,7 @@
 	if modFile == os.DevNull {
 		return nil
 	}
-	v.realMod = span.FileURI(modFile)
+	v.realMod = span.URIFromPath(modFile)
 
 	// Now that we have set all required fields,
 	// check if the view has a valid build configuration.
@@ -618,7 +618,7 @@
 	if _, err := io.Copy(tempModFile, origFile); err != nil {
 		return err
 	}
-	v.tempMod = span.FileURI(tempModFile.Name())
+	v.tempMod = span.URIFromPath(tempModFile.Name())
 
 	// Copy go.sum file as well (if there is one).
 	sumFile := filepath.Join(filepath.Dir(modFile), "go.sum")
diff --git a/internal/lsp/cmd/capabilities_test.go b/internal/lsp/cmd/capabilities_test.go
index ee7ab58..a77da87 100644
--- a/internal/lsp/cmd/capabilities_test.go
+++ b/internal/lsp/cmd/capabilities_test.go
@@ -10,7 +10,6 @@
 	"golang.org/x/tools/internal/lsp"
 	"golang.org/x/tools/internal/lsp/cache"
 	"golang.org/x/tools/internal/lsp/protocol"
-	"golang.org/x/tools/internal/span"
 	errors "golang.org/x/xerrors"
 )
 
@@ -36,7 +35,7 @@
 	defer c.terminate(ctx)
 
 	params := &protocol.ParamInitialize{}
-	params.RootURI = string(span.FileURI(c.Client.app.wd))
+	params.RootURI = protocol.URIFromPath(c.Client.app.wd)
 	params.Capabilities.Workspace.Configuration = true
 
 	// Send an initialize request to the server.
@@ -55,7 +54,7 @@
 	}
 
 	// Open the file on the server side.
-	uri := protocol.NewURI(span.FileURI(tmpFile))
+	uri := protocol.URIFromPath(tmpFile)
 	if err := c.Server.DidOpen(ctx, &protocol.DidOpenTextDocumentParams{
 		TextDocument: protocol.TextDocumentItem{
 			URI:        uri,
diff --git a/internal/lsp/cmd/check.go b/internal/lsp/cmd/check.go
index 3331053..7d22db8 100644
--- a/internal/lsp/cmd/check.go
+++ b/internal/lsp/cmd/check.go
@@ -48,7 +48,7 @@
 	}
 	defer conn.terminate(ctx)
 	for _, arg := range args {
-		uri := span.FileURI(arg)
+		uri := span.URIFromPath(arg)
 		uris = append(uris, uri)
 		file := conn.AddFile(ctx, uri)
 		if file.err != nil {
diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go
index e442c8d..daf9091 100644
--- a/internal/lsp/cmd/cmd.go
+++ b/internal/lsp/cmd/cmd.go
@@ -237,7 +237,7 @@
 
 func (c *connection) initialize(ctx context.Context, options func(*source.Options)) error {
 	params := &protocol.ParamInitialize{}
-	params.RootURI = string(span.FileURI(c.Client.app.wd))
+	params.RootURI = protocol.URIFromPath(c.Client.app.wd)
 	params.Capabilities.Workspace.Configuration = true
 
 	// Make sure to respect configured options when sending initialize request.
@@ -373,8 +373,7 @@
 	c.filesMu.Lock()
 	defer c.filesMu.Unlock()
 
-	uri := span.NewURI(p.URI)
-	file := c.getFile(ctx, uri)
+	file := c.getFile(ctx, p.URI.SpanURI())
 	file.diagnostics = p.Diagnostics
 	return nil
 }
@@ -424,7 +423,7 @@
 	file.added = true
 	p := &protocol.DidOpenTextDocumentParams{
 		TextDocument: protocol.TextDocumentItem{
-			URI:        protocol.NewURI(uri),
+			URI:        protocol.URIFromSpanURI(uri),
 			LanguageID: source.DetectLanguage("", file.uri.Filename()).String(),
 			Version:    1,
 			Text:       string(file.mapper.Content),
diff --git a/internal/lsp/cmd/definition.go b/internal/lsp/cmd/definition.go
index 1845605..6a9f2b5 100644
--- a/internal/lsp/cmd/definition.go
+++ b/internal/lsp/cmd/definition.go
@@ -109,7 +109,7 @@
 	if hover == nil {
 		return errors.Errorf("%v: not an identifier", from)
 	}
-	file = conn.AddFile(ctx, span.NewURI(locs[0].URI))
+	file = conn.AddFile(ctx, locs[0].URI.SpanURI())
 	if file.err != nil {
 		return errors.Errorf("%v: %v", from, file.err)
 	}
diff --git a/internal/lsp/cmd/folding_range.go b/internal/lsp/cmd/folding_range.go
index d6e3b73..f655f30 100644
--- a/internal/lsp/cmd/folding_range.go
+++ b/internal/lsp/cmd/folding_range.go
@@ -50,7 +50,7 @@
 
 	p := protocol.FoldingRangeParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(from.URI()),
+			URI: protocol.URIFromSpanURI(from.URI()),
 		},
 	}
 
diff --git a/internal/lsp/cmd/implementation.go b/internal/lsp/cmd/implementation.go
index 98a6b8a..91c4d59 100644
--- a/internal/lsp/cmd/implementation.go
+++ b/internal/lsp/cmd/implementation.go
@@ -72,7 +72,7 @@
 
 	var spans []string
 	for _, impl := range implementations {
-		f := conn.AddFile(ctx, span.NewURI(impl.URI))
+		f := conn.AddFile(ctx, impl.URI.SpanURI())
 		span, err := f.mapper.Span(impl)
 		if err != nil {
 			return err
diff --git a/internal/lsp/cmd/imports.go b/internal/lsp/cmd/imports.go
index 2127e25..407509b 100644
--- a/internal/lsp/cmd/imports.go
+++ b/internal/lsp/cmd/imports.go
@@ -62,7 +62,7 @@
 	}
 	actions, err := conn.CodeAction(ctx, &protocol.CodeActionParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(uri),
+			URI: protocol.URIFromSpanURI(uri),
 		},
 	})
 	if err != nil {
@@ -74,7 +74,7 @@
 			continue
 		}
 		for _, c := range a.Edit.DocumentChanges {
-			if c.TextDocument.URI == string(uri) {
+			if c.TextDocument.URI.SpanURI() == uri {
 				edits = append(edits, c.Edits...)
 			}
 		}
diff --git a/internal/lsp/cmd/links.go b/internal/lsp/cmd/links.go
index a93ae8f..1d5a669 100644
--- a/internal/lsp/cmd/links.go
+++ b/internal/lsp/cmd/links.go
@@ -59,7 +59,7 @@
 	}
 	results, err := conn.DocumentLink(ctx, &protocol.DocumentLinkParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(uri),
+			URI: protocol.URIFromSpanURI(uri),
 		},
 	})
 	if err != nil {
diff --git a/internal/lsp/cmd/references.go b/internal/lsp/cmd/references.go
index cc85e79..573348d 100644
--- a/internal/lsp/cmd/references.go
+++ b/internal/lsp/cmd/references.go
@@ -73,7 +73,7 @@
 	}
 	var spans []string
 	for _, l := range locations {
-		f := conn.AddFile(ctx, span.NewURI(l.URI))
+		f := conn.AddFile(ctx, l.URI.SpanURI())
 		// convert location to span for user-friendly 1-indexed line
 		// and column numbers
 		span, err := f.mapper.Span(l)
diff --git a/internal/lsp/cmd/rename.go b/internal/lsp/cmd/rename.go
index e09cbce..33121aa 100644
--- a/internal/lsp/cmd/rename.go
+++ b/internal/lsp/cmd/rename.go
@@ -81,15 +81,15 @@
 	var orderedURIs []string
 	edits := map[span.URI][]protocol.TextEdit{}
 	for _, c := range edit.DocumentChanges {
-		uri := span.NewURI(c.TextDocument.URI)
+		uri := c.TextDocument.URI.SpanURI()
 		edits[uri] = append(edits[uri], c.Edits...)
-		orderedURIs = append(orderedURIs, c.TextDocument.URI)
+		orderedURIs = append(orderedURIs, string(uri))
 	}
 	sort.Strings(orderedURIs)
 	changeCount := len(orderedURIs)
 
 	for _, u := range orderedURIs {
-		uri := span.NewURI(u)
+		uri := span.URIFromURI(u)
 		cmdFile := conn.AddFile(ctx, uri)
 		filename := cmdFile.uri.Filename()
 
diff --git a/internal/lsp/cmd/signature.go b/internal/lsp/cmd/signature.go
index 7cc91cd..2cec976 100644
--- a/internal/lsp/cmd/signature.go
+++ b/internal/lsp/cmd/signature.go
@@ -59,7 +59,7 @@
 
 	tdpp := protocol.TextDocumentPositionParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(from.URI()),
+			URI: protocol.URIFromSpanURI(from.URI()),
 		},
 		Position: loc.Range.Start,
 	}
diff --git a/internal/lsp/cmd/suggested_fix.go b/internal/lsp/cmd/suggested_fix.go
index 19a53dc..b92cb25 100644
--- a/internal/lsp/cmd/suggested_fix.go
+++ b/internal/lsp/cmd/suggested_fix.go
@@ -70,7 +70,7 @@
 
 	p := protocol.CodeActionParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(uri),
+			URI: protocol.URIFromSpanURI(uri),
 		},
 		Context: protocol.CodeActionContext{
 			Only:        []protocol.CodeActionKind{protocol.QuickFix},
@@ -87,7 +87,7 @@
 			continue
 		}
 		for _, c := range a.Edit.DocumentChanges {
-			if c.TextDocument.URI == string(uri) {
+			if c.TextDocument.URI.SpanURI() == uri {
 				edits = append(edits, c.Edits...)
 			}
 		}
diff --git a/internal/lsp/cmd/symbols.go b/internal/lsp/cmd/symbols.go
index 41cc0f7..eb3aa02 100644
--- a/internal/lsp/cmd/symbols.go
+++ b/internal/lsp/cmd/symbols.go
@@ -44,7 +44,7 @@
 	from := span.Parse(args[0])
 	p := protocol.DocumentSymbolParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: string(from.URI()),
+			URI: protocol.URIFromSpanURI(from.URI()),
 		},
 	}
 
diff --git a/internal/lsp/code_action.go b/internal/lsp/code_action.go
index e7fe033..ea2274a 100644
--- a/internal/lsp/code_action.go
+++ b/internal/lsp/code_action.go
@@ -15,13 +15,12 @@
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/lsp/telemetry"
-	"golang.org/x/tools/internal/span"
 	"golang.org/x/tools/internal/telemetry/log"
 	errors "golang.org/x/xerrors"
 )
 
 func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) {
-	uri := span.NewURI(params.TextDocument.URI)
+	uri := params.TextDocument.URI.SpanURI()
 	view, err := s.session.ViewOf(uri)
 	if err != nil {
 		return nil, err
@@ -257,7 +256,7 @@
 			TextDocument: protocol.VersionedTextDocumentIdentifier{
 				Version: fh.Identity().Version,
 				TextDocumentIdentifier: protocol.TextDocumentIdentifier{
-					URI: protocol.NewURI(fh.Identity().URI),
+					URI: protocol.URIFromSpanURI(fh.Identity().URI),
 				},
 			},
 			Edits: edits,
diff --git a/internal/lsp/command.go b/internal/lsp/command.go
index 204849b..a5bccfc 100644
--- a/internal/lsp/command.go
+++ b/internal/lsp/command.go
@@ -16,7 +16,7 @@
 			return nil, errors.Errorf("expected one file URI for call to `go mod tidy`, got %v", params.Arguments)
 		}
 		// Confirm that this action is being taken on a go.mod file.
-		uri := span.NewURI(params.Arguments[0].(string))
+		uri := span.URIFromURI(params.Arguments[0].(string))
 		view, err := s.session.ViewOf(uri)
 		if err != nil {
 			return nil, err
@@ -37,7 +37,7 @@
 		if len(params.Arguments) < 2 {
 			return nil, errors.Errorf("expected one file URI and one dependency for call to `go get`, got %v", params.Arguments)
 		}
-		uri := span.NewURI(params.Arguments[0].(string))
+		uri := span.URIFromURI(params.Arguments[0].(string))
 		view, err := s.session.ViewOf(uri)
 		if err != nil {
 			return nil, err
diff --git a/internal/lsp/completion.go b/internal/lsp/completion.go
index 4ea01b6..a430683 100644
--- a/internal/lsp/completion.go
+++ b/internal/lsp/completion.go
@@ -11,13 +11,12 @@
 
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/span"
 	"golang.org/x/tools/internal/telemetry/log"
 	"golang.org/x/tools/internal/telemetry/tag"
 )
 
 func (s *Server) completion(ctx context.Context, params *protocol.CompletionParams) (*protocol.CompletionList, error) {
-	uri := span.NewURI(params.TextDocument.URI)
+	uri := params.TextDocument.URI.SpanURI()
 	view, err := s.session.ViewOf(uri)
 	if err != nil {
 		return nil, err
diff --git a/internal/lsp/completion_test.go b/internal/lsp/completion_test.go
index 949ad2f..93ecb27 100644
--- a/internal/lsp/completion_test.go
+++ b/internal/lsp/completion_test.go
@@ -137,7 +137,7 @@
 	list, err := r.server.Completion(r.ctx, &protocol.CompletionParams{
 		TextDocumentPositionParams: protocol.TextDocumentPositionParams{
 			TextDocument: protocol.TextDocumentIdentifier{
-				URI: protocol.NewURI(src.URI()),
+				URI: protocol.URIFromSpanURI(src.URI()),
 			},
 			Position: protocol.Position{
 				Line:      float64(src.Start().Line() - 1),
diff --git a/internal/lsp/definition.go b/internal/lsp/definition.go
index bb8fb90..6008728 100644
--- a/internal/lsp/definition.go
+++ b/internal/lsp/definition.go
@@ -9,11 +9,10 @@
 
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/span"
 )
 
 func (s *Server) definition(ctx context.Context, params *protocol.DefinitionParams) ([]protocol.Location, error) {
-	uri := span.NewURI(params.TextDocument.URI)
+	uri := params.TextDocument.URI.SpanURI()
 	view, err := s.session.ViewOf(uri)
 	if err != nil {
 		return nil, err
@@ -36,14 +35,14 @@
 	}
 	return []protocol.Location{
 		{
-			URI:   protocol.NewURI(ident.Declaration.URI()),
+			URI:   protocol.URIFromSpanURI(ident.Declaration.URI()),
 			Range: decRange,
 		},
 	}, nil
 }
 
 func (s *Server) typeDefinition(ctx context.Context, params *protocol.TypeDefinitionParams) ([]protocol.Location, error) {
-	uri := span.NewURI(params.TextDocument.URI)
+	uri := params.TextDocument.URI.SpanURI()
 	view, err := s.session.ViewOf(uri)
 	if err != nil {
 		return nil, err
@@ -66,7 +65,7 @@
 	}
 	return []protocol.Location{
 		{
-			URI:   protocol.NewURI(ident.Type.URI()),
+			URI:   protocol.URIFromSpanURI(ident.Type.URI()),
 			Range: identRange,
 		},
 	}, nil
diff --git a/internal/lsp/diagnostics.go b/internal/lsp/diagnostics.go
index 411344f..db8eeb5 100644
--- a/internal/lsp/diagnostics.go
+++ b/internal/lsp/diagnostics.go
@@ -178,7 +178,7 @@
 
 		if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
 			Diagnostics: toProtocolDiagnostics(diagnostics),
-			URI:         protocol.NewURI(key.id.URI),
+			URI:         protocol.URIFromSpanURI(key.id.URI),
 			Version:     key.id.Version,
 		}); err != nil {
 			if ctx.Err() == nil {
@@ -212,7 +212,7 @@
 		for _, rel := range diag.Related {
 			related = append(related, protocol.DiagnosticRelatedInformation{
 				Location: protocol.Location{
-					URI:   protocol.NewURI(rel.URI),
+					URI:   protocol.URIFromSpanURI(rel.URI),
 					Range: rel.Range,
 				},
 				Message: rel.Message,
diff --git a/internal/lsp/diff/difftest/difftest.go b/internal/lsp/diff/difftest/difftest.go
index 297515f..513a925 100644
--- a/internal/lsp/diff/difftest/difftest.go
+++ b/internal/lsp/diff/difftest/difftest.go
@@ -222,7 +222,7 @@
 	for _, test := range TestCases {
 		t.Run(test.Name, func(t *testing.T) {
 			t.Helper()
-			edits := compute(span.FileURI("/"+test.Name), test.In, test.Out)
+			edits := compute(span.URIFromPath("/"+test.Name), test.In, test.Out)
 			got := diff.ApplyEdits(test.In, edits)
 			unified := fmt.Sprint(diff.ToUnified(FileA, FileB, test.In, edits))
 			if got != test.Out {
diff --git a/internal/lsp/fake/workspace.go b/internal/lsp/fake/workspace.go
index fa8a474..b540163 100644
--- a/internal/lsp/fake/workspace.go
+++ b/internal/lsp/fake/workspace.go
@@ -101,16 +101,16 @@
 // URIToPath converts a uri to a workspace-relative path (or an absolute path,
 // if the uri is outside of the workspace).
 func (w *Workspace) URIToPath(uri protocol.DocumentURI) string {
-	root := w.RootURI() + "/"
-	if strings.HasPrefix(uri, root) {
-		return strings.TrimPrefix(uri, root)
+	root := w.RootURI().SpanURI().Filename()
+	path := uri.SpanURI().Filename()
+	if rel, err := filepath.Rel(root, path); err == nil && !strings.HasPrefix(rel, "..") {
+		return filepath.ToSlash(rel)
 	}
-	filename := span.NewURI(string(uri)).Filename()
-	return filepath.ToSlash(filename)
+	return filepath.ToSlash(path)
 }
 
 func toURI(fp string) protocol.DocumentURI {
-	return protocol.DocumentURI(span.FileURI(fp))
+	return protocol.DocumentURI(span.URIFromPath(fp))
 }
 
 // ReadFile reads a text file specified by a workspace-relative path.
diff --git a/internal/lsp/folding_range.go b/internal/lsp/folding_range.go
index 19b28eb..69c9f68 100644
--- a/internal/lsp/folding_range.go
+++ b/internal/lsp/folding_range.go
@@ -5,11 +5,10 @@
 
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/span"
 )
 
 func (s *Server) foldingRange(ctx context.Context, params *protocol.FoldingRangeParams) ([]protocol.FoldingRange, error) {
-	uri := span.NewURI(params.TextDocument.URI)
+	uri := params.TextDocument.URI.SpanURI()
 	view, err := s.session.ViewOf(uri)
 	if err != nil {
 		return nil, err
diff --git a/internal/lsp/format.go b/internal/lsp/format.go
index f3abeb1..1f70ba7 100644
--- a/internal/lsp/format.go
+++ b/internal/lsp/format.go
@@ -9,11 +9,10 @@
 
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/span"
 )
 
 func (s *Server) formatting(ctx context.Context, params *protocol.DocumentFormattingParams) ([]protocol.TextEdit, error) {
-	uri := span.NewURI(params.TextDocument.URI)
+	uri := params.TextDocument.URI.SpanURI()
 	view, err := s.session.ViewOf(uri)
 	if err != nil {
 		return nil, err
diff --git a/internal/lsp/general.go b/internal/lsp/general.go
index 0136392..c476d56 100644
--- a/internal/lsp/general.go
+++ b/internal/lsp/general.go
@@ -42,8 +42,8 @@
 	if len(s.pendingFolders) == 0 {
 		if params.RootURI != "" {
 			s.pendingFolders = []protocol.WorkspaceFolder{{
-				URI:  params.RootURI,
-				Name: path.Base(params.RootURI),
+				URI:  string(params.RootURI),
+				Name: path.Base(params.RootURI.SpanURI().Filename()),
 			}}
 		} else {
 			// No folders and no root--we are in single file mode.
@@ -165,8 +165,8 @@
 	viewErrors := make(map[span.URI]error)
 
 	for _, folder := range folders {
-		uri := span.NewURI(folder.URI)
-		_, snapshot, err := s.addView(ctx, folder.Name, span.NewURI(folder.URI))
+		uri := span.URIFromURI(folder.URI)
+		_, snapshot, err := s.addView(ctx, folder.Name, uri)
 		if err != nil {
 			viewErrors[uri] = err
 			continue
@@ -192,10 +192,10 @@
 	v := protocol.ParamConfiguration{
 		ConfigurationParams: protocol.ConfigurationParams{
 			Items: []protocol.ConfigurationItem{{
-				ScopeURI: protocol.NewURI(folder),
+				ScopeURI: string(folder),
 				Section:  "gopls",
 			}, {
-				ScopeURI: protocol.NewURI(folder),
+				ScopeURI: string(folder),
 				Section:  fmt.Sprintf("gopls-%s", name),
 			}},
 		},
diff --git a/internal/lsp/highlight.go b/internal/lsp/highlight.go
index 45e374b..c2f5071 100644
--- a/internal/lsp/highlight.go
+++ b/internal/lsp/highlight.go
@@ -10,12 +10,11 @@
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/lsp/telemetry"
-	"golang.org/x/tools/internal/span"
 	"golang.org/x/tools/internal/telemetry/log"
 )
 
 func (s *Server) documentHighlight(ctx context.Context, params *protocol.DocumentHighlightParams) ([]protocol.DocumentHighlight, error) {
-	uri := span.NewURI(params.TextDocument.URI)
+	uri := params.TextDocument.URI.SpanURI()
 	view, err := s.session.ViewOf(uri)
 	if err != nil {
 		return nil, err
diff --git a/internal/lsp/hover.go b/internal/lsp/hover.go
index 3305189..4ec04a5 100644
--- a/internal/lsp/hover.go
+++ b/internal/lsp/hover.go
@@ -9,11 +9,10 @@
 
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/span"
 )
 
 func (s *Server) hover(ctx context.Context, params *protocol.HoverParams) (*protocol.Hover, error) {
-	uri := span.NewURI(params.TextDocument.URI)
+	uri := params.TextDocument.URI.SpanURI()
 	view, err := s.session.ViewOf(uri)
 	if err != nil {
 		return nil, err
diff --git a/internal/lsp/implementation.go b/internal/lsp/implementation.go
index bae2832..4fb0219 100644
--- a/internal/lsp/implementation.go
+++ b/internal/lsp/implementation.go
@@ -9,11 +9,10 @@
 
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/span"
 )
 
 func (s *Server) implementation(ctx context.Context, params *protocol.ImplementationParams) ([]protocol.Location, error) {
-	uri := span.NewURI(params.TextDocument.URI)
+	uri := params.TextDocument.URI.SpanURI()
 	view, err := s.session.ViewOf(uri)
 	if err != nil {
 		return nil, err
diff --git a/internal/lsp/link.go b/internal/lsp/link.go
index 30e5a9c..c457d7e 100644
--- a/internal/lsp/link.go
+++ b/internal/lsp/link.go
@@ -21,7 +21,7 @@
 )
 
 func (s *Server) documentLink(ctx context.Context, params *protocol.DocumentLinkParams) ([]protocol.DocumentLink, error) {
-	uri := span.NewURI(params.TextDocument.URI)
+	uri := params.TextDocument.URI.SpanURI()
 	view, err := s.session.ViewOf(uri)
 	if err != nil {
 		return nil, err
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index 651592b..a893834 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -54,7 +54,7 @@
 		options := tests.DefaultOptions()
 		session.SetOptions(options)
 		options.Env = datum.Config.Env
-		v, _, err := session.NewView(ctx, datum.Config.Dir, span.FileURI(datum.Config.Dir), options)
+		v, _, err := session.NewView(ctx, datum.Config.Dir, span.URIFromPath(datum.Config.Dir), options)
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -74,7 +74,7 @@
 				continue
 			}
 			modifications = append(modifications, source.FileModification{
-				URI:        span.FileURI(filename),
+				URI:        span.URIFromPath(filename),
 				Action:     source.Open,
 				Version:    -1,
 				Text:       content,
@@ -158,7 +158,7 @@
 	}
 	ranges, err := r.server.FoldingRange(r.ctx, &protocol.FoldingRangeParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(uri),
+			URI: protocol.URIFromSpanURI(uri),
 		},
 	})
 	if err != nil {
@@ -176,7 +176,7 @@
 	}
 	ranges, err = r.server.FoldingRange(r.ctx, &protocol.FoldingRangeParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(uri),
+			URI: protocol.URIFromSpanURI(uri),
 		},
 	})
 	if err != nil {
@@ -310,7 +310,7 @@
 
 	edits, err := r.server.Formatting(r.ctx, &protocol.DocumentFormattingParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(uri),
+			URI: protocol.URIFromSpanURI(uri),
 		},
 	})
 	if err != nil {
@@ -338,7 +338,7 @@
 	filename := uri.Filename()
 	actions, err := r.server.CodeAction(r.ctx, &protocol.CodeActionParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(uri),
+			URI: protocol.URIFromSpanURI(uri),
 		},
 	})
 	if err != nil {
@@ -402,7 +402,7 @@
 	}
 	actions, err := r.server.CodeAction(r.ctx, &protocol.CodeActionParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(uri),
+			URI: protocol.URIFromSpanURI(uri),
 		},
 		Context: protocol.CodeActionContext{
 			Only:        []protocol.CodeActionKind{protocol.QuickFix},
@@ -482,7 +482,7 @@
 	}
 	if !d.OnlyHover {
 		didSomething = true
-		locURI := span.NewURI(locs[0].URI)
+		locURI := locs[0].URI.SpanURI()
 		lm, err := r.data.Mapper(locURI)
 		if err != nil {
 			t.Fatal(err)
@@ -525,7 +525,7 @@
 
 	var results []span.Span
 	for i := range locs {
-		locURI := span.NewURI(locs[i].URI)
+		locURI := locs[i].URI.SpanURI()
 		lm, err := r.data.Mapper(locURI)
 		if err != nil {
 			t.Fatal(err)
@@ -663,7 +663,7 @@
 
 	wedit, err := r.server.Rename(r.ctx, &protocol.RenameParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(uri),
+			URI: protocol.URIFromSpanURI(uri),
 		},
 		Position: loc.Range.Start,
 		NewName:  newText,
@@ -692,7 +692,7 @@
 		if i != 0 {
 			got += "\n"
 		}
-		uri := span.NewURI(orderedURIs[i])
+		uri := span.URIFromURI(orderedURIs[i])
 		if len(res) > 1 {
 			got += filepath.Base(uri.Filename()) + ":\n"
 		}
@@ -753,7 +753,7 @@
 func applyWorkspaceEdits(r *runner, wedit protocol.WorkspaceEdit) (map[span.URI]string, error) {
 	res := map[span.URI]string{}
 	for _, docEdits := range wedit.DocumentChanges {
-		uri := span.NewURI(docEdits.TextDocument.URI)
+		uri := docEdits.TextDocument.URI.SpanURI()
 		m, err := r.data.Mapper(uri)
 		if err != nil {
 			return nil, err
@@ -786,7 +786,7 @@
 func (r *runner) Symbols(t *testing.T, uri span.URI, expectedSymbols []protocol.DocumentSymbol) {
 	params := &protocol.DocumentSymbolParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: string(uri),
+			URI: protocol.URIFromSpanURI(uri),
 		},
 	}
 	symbols, err := r.server.DocumentSymbol(r.ctx, params)
@@ -881,7 +881,7 @@
 	}
 	tdpp := protocol.TextDocumentPositionParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(spn.URI()),
+			URI: protocol.URIFromSpanURI(spn.URI()),
 		},
 		Position: loc.Range.Start,
 	}
@@ -917,7 +917,7 @@
 	}
 	got, err := r.server.DocumentLink(r.ctx, &protocol.DocumentLinkParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(uri),
+			URI: protocol.URIFromSpanURI(uri),
 		},
 	})
 	if err != nil {
@@ -955,7 +955,7 @@
 		fset := token.NewFileSet()
 		f := fset.AddFile(fname, -1, len(test.text))
 		f.SetLinesForContent([]byte(test.text))
-		uri := span.FileURI(fname)
+		uri := span.URIFromPath(fname)
 		converter := span.NewContentConverter(fname, []byte(test.text))
 		mapper := &protocol.ColumnMapper{
 			URI:       uri,
diff --git a/internal/lsp/mod/diagnostics.go b/internal/lsp/mod/diagnostics.go
index 647bab3..401f9ef 100644
--- a/internal/lsp/mod/diagnostics.go
+++ b/internal/lsp/mod/diagnostics.go
@@ -105,7 +105,7 @@
 						TextDocument: protocol.VersionedTextDocumentIdentifier{
 							Version: fh.Identity().Version,
 							TextDocumentIdentifier: protocol.TextDocumentIdentifier{
-								URI: protocol.NewURI(fh.Identity().URI),
+								URI: protocol.URIFromSpanURI(fh.Identity().URI),
 							},
 						},
 						Edits: edits,
@@ -188,7 +188,7 @@
 						TextDocument: protocol.VersionedTextDocumentIdentifier{
 							Version: realfh.Identity().Version,
 							TextDocumentIdentifier: protocol.TextDocumentIdentifier{
-								URI: protocol.NewURI(realfh.Identity().URI),
+								URI: protocol.URIFromSpanURI(realfh.Identity().URI),
 							},
 						},
 						Edits: edits,
diff --git a/internal/lsp/mod/mod_test.go b/internal/lsp/mod/mod_test.go
index 8f78207..491cb68 100644
--- a/internal/lsp/mod/mod_test.go
+++ b/internal/lsp/mod/mod_test.go
@@ -41,7 +41,7 @@
 	if err != nil {
 		t.Fatal(err)
 	}
-	_, snapshot, err := session.NewView(ctx, "diagnostics_test", span.FileURI(folder), options)
+	_, snapshot, err := session.NewView(ctx, "diagnostics_test", span.URIFromPath(folder), options)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/internal/lsp/protocol/span.go b/internal/lsp/protocol/span.go
index 5c9c4d1..8363d5c 100644
--- a/internal/lsp/protocol/span.go
+++ b/internal/lsp/protocol/span.go
@@ -19,8 +19,16 @@
 	Content   []byte
 }
 
-func NewURI(uri span.URI) string {
-	return string(uri)
+func URIFromSpanURI(uri span.URI) DocumentURI {
+	return DocumentURI(uri)
+}
+
+func URIFromPath(path string) DocumentURI {
+	return URIFromSpanURI(span.URIFromPath(path))
+}
+
+func (u DocumentURI) SpanURI() span.URI {
+	return span.URIFromURI(string(u))
 }
 
 func (m *ColumnMapper) Location(s span.Span) (Location, error) {
@@ -28,7 +36,7 @@
 	if err != nil {
 		return Location{}, err
 	}
-	return Location{URI: NewURI(s.URI()), Range: rng}, nil
+	return Location{URI: URIFromSpanURI(s.URI()), Range: rng}, nil
 }
 
 func (m *ColumnMapper) Range(s span.Span) (Range, error) {
diff --git a/internal/lsp/protocol/tsclient.go b/internal/lsp/protocol/tsclient.go
index ab2abc9..53327ed 100644
--- a/internal/lsp/protocol/tsclient.go
+++ b/internal/lsp/protocol/tsclient.go
@@ -3,7 +3,7 @@
 // Package protocol contains data types and code for LSP jsonrpcs
 // generated automatically from vscode-languageserver-node
 // commit: 7b90c29d0cb5cd7b9c41084f6cb3781a955adeba
-// last fetched Thu Jan 23 2020 11:10:31 GMT-0500 (Eastern Standard Time)
+// last fetched Wed Feb 12 2020 17:16:47 GMT-0500 (Eastern Standard Time)
 
 // Code generated (see typescript/README.md) DO NOT EDIT.
 
diff --git a/internal/lsp/protocol/tsprotocol.go b/internal/lsp/protocol/tsprotocol.go
index e8be364..f5b66fe 100644
--- a/internal/lsp/protocol/tsprotocol.go
+++ b/internal/lsp/protocol/tsprotocol.go
@@ -1,7 +1,7 @@
 // Package protocol contains data types and code for LSP jsonrpcs
 // generated automatically from vscode-languageserver-node
 // commit: 7b90c29d0cb5cd7b9c41084f6cb3781a955adeba
-// last fetched Thu Jan 23 2020 11:10:31 GMT-0500 (Eastern Standard Time)
+// last fetched Wed Feb 12 2020 17:16:47 GMT-0500 (Eastern Standard Time)
 package protocol
 
 // Code generated (see typescript/README.md) DO NOT EDIT.
@@ -1532,7 +1532,7 @@
 /**
  * A tagging type for string properties that are actually URIs.
  */
-type DocumentURI = string
+type DocumentURI string
 
 /**
  * The client capabilities of a [ExecuteCommandRequest](#ExecuteCommandRequest).
diff --git a/internal/lsp/protocol/tsserver.go b/internal/lsp/protocol/tsserver.go
index 64b2114..7853449 100644
--- a/internal/lsp/protocol/tsserver.go
+++ b/internal/lsp/protocol/tsserver.go
@@ -3,7 +3,7 @@
 // Package protocol contains data types and code for LSP jsonrpcs
 // generated automatically from vscode-languageserver-node
 // commit: 7b90c29d0cb5cd7b9c41084f6cb3781a955adeba
-// last fetched Thu Jan 23 2020 11:10:31 GMT-0500 (Eastern Standard Time)
+// last fetched Wed Feb 12 2020 17:16:47 GMT-0500 (Eastern Standard Time)
 
 // Code generated (see typescript/README.md) DO NOT EDIT.
 
diff --git a/internal/lsp/protocol/typescript/code.ts b/internal/lsp/protocol/typescript/code.ts
index 38d52d4..fe35d80 100644
--- a/internal/lsp/protocol/typescript/code.ts
+++ b/internal/lsp/protocol/typescript/code.ts
@@ -601,7 +601,8 @@
   }
   typesOut.push(getComments(d.me))
   // d.alias doesn't seem to have comments
-  typesOut.push(`type ${goName(nm)} = ${goType(d.alias, nm)}\n`)
+  let aliasStr = goName(nm) == "DocumentURI" ? " " : " = "
+  typesOut.push(`type ${goName(nm)}${aliasStr}${goType(d.alias, nm)}\n`)
 }
 
 // return a go type and maybe an assocated javascript tag
diff --git a/internal/lsp/references.go b/internal/lsp/references.go
index d91fc16..068cfa6 100644
--- a/internal/lsp/references.go
+++ b/internal/lsp/references.go
@@ -9,11 +9,10 @@
 
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/span"
 )
 
 func (s *Server) references(ctx context.Context, params *protocol.ReferenceParams) ([]protocol.Location, error) {
-	uri := span.NewURI(params.TextDocument.URI)
+	uri := params.TextDocument.URI.SpanURI()
 	view, err := s.session.ViewOf(uri)
 	if err != nil {
 		return nil, err
@@ -41,7 +40,7 @@
 		}
 
 		locations = append(locations, protocol.Location{
-			URI:   protocol.NewURI(ref.URI()),
+			URI:   protocol.URIFromSpanURI(ref.URI()),
 			Range: refRange,
 		})
 	}
diff --git a/internal/lsp/rename.go b/internal/lsp/rename.go
index 04f0054..4043b14 100644
--- a/internal/lsp/rename.go
+++ b/internal/lsp/rename.go
@@ -9,11 +9,10 @@
 
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/span"
 )
 
 func (s *Server) rename(ctx context.Context, params *protocol.RenameParams) (*protocol.WorkspaceEdit, error) {
-	uri := span.NewURI(params.TextDocument.URI)
+	uri := params.TextDocument.URI.SpanURI()
 	view, err := s.session.ViewOf(uri)
 	if err != nil {
 		return nil, err
@@ -46,7 +45,7 @@
 }
 
 func (s *Server) prepareRename(ctx context.Context, params *protocol.PrepareRenameParams) (*protocol.Range, error) {
-	uri := span.NewURI(params.TextDocument.URI)
+	uri := params.TextDocument.URI.SpanURI()
 	view, err := s.session.ViewOf(uri)
 	if err != nil {
 		return nil, err
diff --git a/internal/lsp/server.go b/internal/lsp/server.go
index ab1c263..f4ba6b6 100644
--- a/internal/lsp/server.go
+++ b/internal/lsp/server.go
@@ -70,7 +70,7 @@
 }
 
 func (s *Server) codeLens(ctx context.Context, params *protocol.CodeLensParams) ([]protocol.CodeLens, error) {
-	uri := span.NewURI(params.TextDocument.URI)
+	uri := params.TextDocument.URI.SpanURI()
 	view, err := s.session.ViewOf(uri)
 	if err != nil {
 		return nil, err
@@ -95,7 +95,7 @@
 	paramMap := params.(map[string]interface{})
 	if method == "gopls/diagnoseFiles" {
 		for _, file := range paramMap["files"].([]interface{}) {
-			uri := span.NewURI(file.(string))
+			uri := span.URIFromURI(file.(string))
 			view, err := s.session.ViewOf(uri)
 			if err != nil {
 				return nil, err
@@ -105,7 +105,7 @@
 				return nil, err
 			}
 			if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
-				URI:         protocol.NewURI(uri),
+				URI:         protocol.URIFromSpanURI(uri),
 				Diagnostics: toProtocolDiagnostics(diagnostics),
 				Version:     fileID.Version,
 			}); err != nil {
diff --git a/internal/lsp/signature_help.go b/internal/lsp/signature_help.go
index 1cbe517..c514d58 100644
--- a/internal/lsp/signature_help.go
+++ b/internal/lsp/signature_help.go
@@ -9,13 +9,12 @@
 
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/span"
 	"golang.org/x/tools/internal/telemetry/log"
 	"golang.org/x/tools/internal/telemetry/tag"
 )
 
 func (s *Server) signatureHelp(ctx context.Context, params *protocol.SignatureHelpParams) (*protocol.SignatureHelp, error) {
-	uri := span.NewURI(params.TextDocument.URI)
+	uri := params.TextDocument.URI.SpanURI()
 	view, err := s.session.ViewOf(uri)
 	if err != nil {
 		return nil, err
diff --git a/internal/lsp/source/completion_format.go b/internal/lsp/source/completion_format.go
index f44b553..600ad5c 100644
--- a/internal/lsp/source/completion_format.go
+++ b/internal/lsp/source/completion_format.go
@@ -179,7 +179,7 @@
 	if !pos.IsValid() {
 		return item, nil
 	}
-	uri := span.FileURI(pos.Filename)
+	uri := span.URIFromPath(pos.Filename)
 
 	// Find the source file of the candidate, starting from a package
 	// that should have it in its dependencies.
@@ -213,7 +213,7 @@
 		return nil, nil
 	}
 
-	uri := span.FileURI(c.filename)
+	uri := span.URIFromPath(c.filename)
 	var ph ParseGoHandle
 	for _, h := range c.pkg.CompiledGoFiles() {
 		if h.File().Identity().URI == uri {
diff --git a/internal/lsp/source/implementation.go b/internal/lsp/source/implementation.go
index 9cd10ca..547e2c5 100644
--- a/internal/lsp/source/implementation.go
+++ b/internal/lsp/source/implementation.go
@@ -39,7 +39,7 @@
 			return nil, err
 		}
 		locations = append(locations, protocol.Location{
-			URI:   protocol.NewURI(rng.URI()),
+			URI:   protocol.URIFromSpanURI(rng.URI()),
 			Range: pr,
 		})
 	}
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index 6e52d6c..dd660ac 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -51,7 +51,7 @@
 		session := cache.NewSession()
 		options := tests.DefaultOptions()
 		options.Env = datum.Config.Env
-		view, _, err := session.NewView(ctx, "source_test", span.FileURI(datum.Config.Dir), options)
+		view, _, err := session.NewView(ctx, "source_test", span.URIFromPath(datum.Config.Dir), options)
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -67,7 +67,7 @@
 				continue
 			}
 			modifications = append(modifications, source.FileModification{
-				URI:        span.FileURI(filename),
+				URI:        span.URIFromPath(filename),
 				Action:     source.Open,
 				Version:    -1,
 				Text:       content,
@@ -547,7 +547,7 @@
 	}
 	var results []span.Span
 	for i := range locs {
-		locURI := span.NewURI(locs[i].URI)
+		locURI := locs[i].URI.SpanURI()
 		lm, err := r.data.Mapper(locURI)
 		if err != nil {
 			t.Fatal(err)
diff --git a/internal/lsp/source/util.go b/internal/lsp/source/util.go
index 1a66ad7..ffb166f 100644
--- a/internal/lsp/source/util.go
+++ b/internal/lsp/source/util.go
@@ -201,7 +201,7 @@
 
 func posToMappedRange(v View, pkg Package, pos, end token.Pos) (mappedRange, error) {
 	logicalFilename := v.Session().Cache().FileSet().File(pos).Position(pos).Filename
-	m, err := findMapperInPackage(v, pkg, span.FileURI(logicalFilename))
+	m, err := findMapperInPackage(v, pkg, span.URIFromPath(logicalFilename))
 	if err != nil {
 		return mappedRange{}, err
 	}
@@ -631,7 +631,7 @@
 	if tok == nil {
 		return nil, nil, errors.Errorf("no file for pos in package %s", searchpkg.ID())
 	}
-	uri := span.FileURI(tok.Name())
+	uri := span.URIFromPath(tok.Name())
 
 	var (
 		ph  ParseGoHandle
diff --git a/internal/lsp/source/workspace_symbol.go b/internal/lsp/source/workspace_symbol.go
index 009b9f4..f1033c5 100644
--- a/internal/lsp/source/workspace_symbol.go
+++ b/internal/lsp/source/workspace_symbol.go
@@ -53,7 +53,7 @@
 						Name: si.name,
 						Kind: si.kind,
 						Location: protocol.Location{
-							URI:   protocol.NewURI(fh.File().Identity().URI),
+							URI:   protocol.URIFromSpanURI(fh.File().Identity().URI),
 							Range: rng,
 						},
 					})
diff --git a/internal/lsp/symbols.go b/internal/lsp/symbols.go
index 0e167d3..b0d9371 100644
--- a/internal/lsp/symbols.go
+++ b/internal/lsp/symbols.go
@@ -10,7 +10,6 @@
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/lsp/telemetry"
-	"golang.org/x/tools/internal/span"
 	"golang.org/x/tools/internal/telemetry/log"
 	"golang.org/x/tools/internal/telemetry/trace"
 )
@@ -19,7 +18,7 @@
 	ctx, done := trace.StartSpan(ctx, "lsp.Server.documentSymbol")
 	defer done()
 
-	uri := span.NewURI(params.TextDocument.URI)
+	uri := params.TextDocument.URI.SpanURI()
 	view, err := s.session.ViewOf(uri)
 	if err != nil {
 		return nil, err
diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go
index 16dbec9..4b8c855 100644
--- a/internal/lsp/tests/tests.go
+++ b/internal/lsp/tests/tests.go
@@ -466,7 +466,7 @@
 				t.Helper()
 				dirs := make(map[string]struct{})
 				for _, si := range expectedSymbols {
-					d := filepath.Dir(si.Location.URI)
+					d := filepath.Dir(si.Location.URI.SpanURI().Filename())
 					if _, ok := dirs[d]; !ok {
 						dirs[d] = struct{}{}
 					}
@@ -1077,7 +1077,7 @@
 		Name: sym.Name,
 		Kind: sym.Kind,
 		Location: protocol.Location{
-			URI:   protocol.NewURI(spn.URI()),
+			URI:   protocol.URIFromSpanURI(spn.URI()),
 			Range: sym.SelectionRange,
 		},
 	}
diff --git a/internal/lsp/tests/util.go b/internal/lsp/tests/util.go
index f778757..dcea466 100644
--- a/internal/lsp/tests/util.go
+++ b/internal/lsp/tests/util.go
@@ -109,7 +109,7 @@
 func FilterWorkspaceSymbols(got []protocol.SymbolInformation, dirs map[string]struct{}) []protocol.SymbolInformation {
 	var result []protocol.SymbolInformation
 	for _, si := range got {
-		if _, ok := dirs[filepath.Dir(si.Location.URI)]; ok {
+		if _, ok := dirs[filepath.Dir(si.Location.URI.SpanURI().Filename())]; ok {
 			result = append(result, si)
 		}
 	}
diff --git a/internal/lsp/text_synchronization.go b/internal/lsp/text_synchronization.go
index 652132d..e4db204 100644
--- a/internal/lsp/text_synchronization.go
+++ b/internal/lsp/text_synchronization.go
@@ -19,7 +19,7 @@
 func (s *Server) didOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error {
 	_, err := s.didModifyFiles(ctx, []source.FileModification{
 		{
-			URI:        span.NewURI(params.TextDocument.URI),
+			URI:        params.TextDocument.URI.SpanURI(),
 			Action:     source.Open,
 			Version:    params.TextDocument.Version,
 			Text:       []byte(params.TextDocument.Text),
@@ -30,7 +30,7 @@
 }
 
 func (s *Server) didChange(ctx context.Context, params *protocol.DidChangeTextDocumentParams) error {
-	uri := span.NewURI(params.TextDocument.URI)
+	uri := params.TextDocument.URI.SpanURI()
 	text, err := s.changedText(ctx, uri, params.ContentChanges)
 	if err != nil {
 		return err
@@ -66,7 +66,7 @@
 	var modifications []source.FileModification
 	for _, change := range params.Changes {
 		modifications = append(modifications, source.FileModification{
-			URI:    span.NewURI(change.URI),
+			URI:    change.URI.SpanURI(),
 			Action: changeTypeToFileAction(change.Type),
 			OnDisk: true,
 		})
@@ -77,7 +77,7 @@
 
 func (s *Server) didSave(ctx context.Context, params *protocol.DidSaveTextDocumentParams) error {
 	c := source.FileModification{
-		URI:     span.NewURI(params.TextDocument.URI),
+		URI:     params.TextDocument.URI.SpanURI(),
 		Action:  source.Save,
 		Version: params.TextDocument.Version,
 	}
@@ -91,7 +91,7 @@
 func (s *Server) didClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error {
 	_, err := s.didModifyFiles(ctx, []source.FileModification{
 		{
-			URI:     span.NewURI(params.TextDocument.URI),
+			URI:     params.TextDocument.URI.SpanURI(),
 			Action:  source.Close,
 			Version: -1,
 			Text:    nil,
diff --git a/internal/span/parse.go b/internal/span/parse.go
index b3f268a..aa17c84 100644
--- a/internal/span/parse.go
+++ b/internal/span/parse.go
@@ -11,7 +11,7 @@
 )
 
 // Parse returns the location represented by the input.
-// All inputs are valid locations, as they can always be a pure filename.
+// Only file paths are accepted, not URIs.
 // The returned span will be normalized, and thus if printed may produce a
 // different string.
 func Parse(input string) Span {
@@ -32,12 +32,12 @@
 	}
 	switch {
 	case suf.sep == ":":
-		return New(NewURI(suf.remains), NewPoint(suf.num, hold, offset), Point{})
+		return New(URIFromPath(suf.remains), NewPoint(suf.num, hold, offset), Point{})
 	case suf.sep == "-":
 		// we have a span, fall out of the case to continue
 	default:
 		// separator not valid, rewind to either the : or the start
-		return New(NewURI(valid), NewPoint(hold, 0, offset), Point{})
+		return New(URIFromPath(valid), NewPoint(hold, 0, offset), Point{})
 	}
 	// only the span form can get here
 	// at this point we still don't know what the numbers we have mean
@@ -53,20 +53,20 @@
 	}
 	if suf.sep != ":" {
 		// turns out we don't have a span after all, rewind
-		return New(NewURI(valid), end, Point{})
+		return New(URIFromPath(valid), end, Point{})
 	}
 	valid = suf.remains
 	hold = suf.num
 	suf = rstripSuffix(suf.remains)
 	if suf.sep != ":" {
 		// line#offset only
-		return New(NewURI(valid), NewPoint(hold, 0, offset), end)
+		return New(URIFromPath(valid), NewPoint(hold, 0, offset), end)
 	}
 	// we have a column, so if end only had one number, it is also the column
 	if !hadCol {
 		end = NewPoint(suf.num, end.v.Line, end.v.Offset)
 	}
-	return New(NewURI(suf.remains), NewPoint(suf.num, hold, offset), end)
+	return New(URIFromPath(suf.remains), NewPoint(suf.num, hold, offset), end)
 }
 
 type suffix struct {
diff --git a/internal/span/span_test.go b/internal/span/span_test.go
index 8212d0c..150ea3f 100644
--- a/internal/span/span_test.go
+++ b/internal/span/span_test.go
@@ -14,8 +14,7 @@
 )
 
 var (
-	formats = []string{"%v", "%#v", "%+v"}
-	tests   = [][]string{
+	tests = [][]string{
 		{"C:/file_a", "C:/file_a", "file:///C:/file_a:1:1#0"},
 		{"C:/file_b:1:2", "C:/file_b:#1", "file:///C:/file_b:1:2#1"},
 		{"C:/file_c:1000", "C:/file_c:#9990", "file:///C:/file_c:1000:1#9990"},
@@ -30,7 +29,7 @@
 func TestFormat(t *testing.T) {
 	converter := lines(10)
 	for _, test := range tests {
-		for ti, text := range test {
+		for ti, text := range test[:2] {
 			spn := span.Parse(text)
 			if ti <= 1 {
 				// we can check %v produces the same as the input
diff --git a/internal/span/token.go b/internal/span/token.go
index d0ec03a..1710b77 100644
--- a/internal/span/token.go
+++ b/internal/span/token.go
@@ -75,7 +75,7 @@
 	if err != nil {
 		return Span{}, err
 	}
-	s.v.URI = FileURI(startFilename)
+	s.v.URI = URIFromPath(startFilename)
 	if r.End.IsValid() {
 		var endFilename string
 		endFilename, s.v.End.Line, s.v.End.Column, err = position(f, r.End)
diff --git a/internal/span/token_test.go b/internal/span/token_test.go
index db11df1..81b2631 100644
--- a/internal/span/token_test.go
+++ b/internal/span/token_test.go
@@ -32,10 +32,10 @@
 }
 
 var tokenTests = []span.Span{
-	span.New(span.FileURI("/a.go"), span.NewPoint(1, 1, 0), span.Point{}),
-	span.New(span.FileURI("/a.go"), span.NewPoint(3, 7, 20), span.NewPoint(3, 7, 20)),
-	span.New(span.FileURI("/b.go"), span.NewPoint(4, 9, 15), span.NewPoint(4, 13, 19)),
-	span.New(span.FileURI("/c.go"), span.NewPoint(4, 1, 26), span.Point{}),
+	span.New(span.URIFromPath("/a.go"), span.NewPoint(1, 1, 0), span.Point{}),
+	span.New(span.URIFromPath("/a.go"), span.NewPoint(3, 7, 20), span.NewPoint(3, 7, 20)),
+	span.New(span.URIFromPath("/b.go"), span.NewPoint(4, 9, 15), span.NewPoint(4, 13, 19)),
+	span.New(span.URIFromPath("/c.go"), span.NewPoint(4, 1, 26), span.Point{}),
 }
 
 func TestToken(t *testing.T) {
@@ -44,7 +44,7 @@
 	for _, f := range testdata {
 		file := fset.AddFile(f.uri, -1, len(f.content))
 		file.SetLinesForContent(f.content)
-		files[span.FileURI(f.uri)] = file
+		files[span.URIFromPath(f.uri)] = file
 	}
 	for _, test := range tokenTests {
 		f := files[test.URI()]
diff --git a/internal/span/uri.go b/internal/span/uri.go
index 26dc90c..a794a2d 100644
--- a/internal/span/uri.go
+++ b/internal/span/uri.go
@@ -49,28 +49,26 @@
 	return u.Path, nil
 }
 
-// NewURI returns a span URI for the string.
-// It will attempt to detect if the string is a file path or uri.
-func NewURI(s string) URI {
-	// If a path has a scheme, it is already a URI.
-	// We only handle the file:// scheme.
-	if i := len(fileScheme + "://"); strings.HasPrefix(s, "file:///") {
-		// Handle microsoft/vscode#75027 by making it a special case.
-		// On Windows, VS Code sends file URIs that look like file:///C%3A/x/y/z.
-		// Replace the %3A so that the URI looks like: file:///C:/x/y/z.
-		if strings.ToLower(s[i+2:i+5]) == "%3a" {
-			s = s[:i+2] + ":" + s[i+5:]
-		}
-		// 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 isWindowsDriveURIPath(s[i:]) {
-			s = s[:i+1] + strings.ToUpper(string(s[i+1])) + s[i+2:]
-		}
+func URIFromURI(s string) URI {
+	if !strings.HasPrefix(s, "file:///") {
 		return URI(s)
 	}
-	return FileURI(s)
+	// Handle Windows-specific glitches. We can't parse the URI -- it may not be valid.
+	path := s[len("file://"):]
+	// Handle microsoft/vscode#75027 by making it a special case.
+	// On Windows, VS Code sends file URIs that look like file:///C%3A/x/y/z.
+	// Replace the %3A so that the URI looks like: file:///C:/x/y/z.
+	if strings.ToLower(path[2:5]) == "%3a" {
+		path = path[:2] + ":" + path[5:]
+	}
+	// 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 isWindowsDriveURIPath(path) {
+		path = path[:1] + strings.ToUpper(string(path[1])) + path[2:]
+	}
+	return URI("file://" + path)
 }
 
 func CompareURI(a, b URI) int {
@@ -111,9 +109,9 @@
 	return os.SameFile(infoa, infob)
 }
 
-// FileURI returns a span URI for the supplied file path.
+// URIFromPath returns a span URI for the supplied file path.
 // It will always have the file scheme.
-func FileURI(path string) URI {
+func URIFromPath(path string) URI {
 	if path == "" {
 		return ""
 	}
diff --git a/internal/span/uri_test.go b/internal/span/uri_test.go
index a3754e3..be5bf8c 100644
--- a/internal/span/uri_test.go
+++ b/internal/span/uri_test.go
@@ -16,7 +16,7 @@
 // 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) {
+func TestURIFromPath(t *testing.T) {
 	for _, test := range []struct {
 		path, wantFile string
 		wantURI        span.URI
@@ -56,25 +56,42 @@
 			wantFile: `C:/Go/src/bob george/george/george.go`,
 			wantURI:  span.URI("file:///C:/Go/src/bob%20george/george/george.go"),
 		},
+	} {
+		got := span.URIFromPath(test.path)
+		if got != test.wantURI {
+			t.Errorf("URIFromPath(%q): got %q, expected %q", test.path, got, test.wantURI)
+		}
+		gotFilename := got.Filename()
+		if gotFilename != test.wantFile {
+			t.Errorf("Filename(%q): got %q, expected %q", got, gotFilename, test.wantFile)
+		}
+	}
+}
+
+func TestURIFromURI(t *testing.T) {
+	for _, test := range []struct {
+		inputURI, wantFile string
+		wantURI            span.URI
+	}{
 		{
-			path:     `file:///c:/Go/src/bob%20george/george/george.go`,
+			inputURI: `file:///c:/Go/src/bob%20george/george/george.go`,
 			wantFile: `C:/Go/src/bob george/george/george.go`,
 			wantURI:  span.URI("file:///C:/Go/src/bob%20george/george/george.go"),
 		},
 		{
-			path:     `file:///C%3A/Go/src/bob%20george/george/george.go`,
+			inputURI: `file:///C%3A/Go/src/bob%20george/george/george.go`,
 			wantFile: `C:/Go/src/bob george/george/george.go`,
 			wantURI:  span.URI("file:///C:/Go/src/bob%20george/george/george.go"),
 		},
 		{
-			path:     `file:///path/to/%25p%25ercent%25/per%25cent.go`,
+			inputURI: `file:///path/to/%25p%25ercent%25/per%25cent.go`,
 			wantFile: `/path/to/%p%ercent%/per%cent.go`,
 			wantURI:  span.URI(`file:///path/to/%25p%25ercent%25/per%25cent.go`),
 		},
 	} {
-		got := span.NewURI(test.path)
+		got := span.URIFromURI(test.inputURI)
 		if got != test.wantURI {
-			t.Errorf("NewURI(%q): got %q, expected %q", test.path, got, test.wantURI)
+			t.Errorf("NewURI(%q): got %q, expected %q", test.inputURI, got, test.wantURI)
 		}
 		gotFilename := got.Filename()
 		if gotFilename != test.wantFile {
diff --git a/internal/span/uri_windows_test.go b/internal/span/uri_windows_test.go
index 1370b19..cb145ad 100644
--- a/internal/span/uri_windows_test.go
+++ b/internal/span/uri_windows_test.go
@@ -16,7 +16,7 @@
 // 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) {
+func TestURIFromPath(t *testing.T) {
 	for _, test := range []struct {
 		path, wantFile string
 		wantURI        span.URI
@@ -56,28 +56,46 @@
 			wantFile: `C:\Go\src\bob george\george\george.go`,
 			wantURI:  span.URI("file:///C:/Go/src/bob%20george/george/george.go"),
 		},
+	} {
+		got := span.URIFromPath(test.path)
+		if got != test.wantURI {
+			t.Errorf("URIFromPath(%q): got %q, expected %q", test.path, got, test.wantURI)
+		}
+		gotFilename := got.Filename()
+		if gotFilename != test.wantFile {
+			t.Errorf("Filename(%q): got %q, expected %q", got, gotFilename, test.wantFile)
+		}
+	}
+}
+
+func TestURIFromURI(t *testing.T) {
+	for _, test := range []struct {
+		inputURI, wantFile string
+		wantURI            span.URI
+	}{
 		{
-			path:     `file:///c:/Go/src/bob%20george/george/george.go`,
+			inputURI: `file:///c:/Go/src/bob%20george/george/george.go`,
 			wantFile: `C:\Go\src\bob george\george\george.go`,
 			wantURI:  span.URI("file:///C:/Go/src/bob%20george/george/george.go"),
 		},
 		{
-			path:     `file:///C%3A/Go/src/bob%20george/george/george.go`,
+			inputURI: `file:///C%3A/Go/src/bob%20george/george/george.go`,
 			wantFile: `C:\Go\src\bob george\george\george.go`,
 			wantURI:  span.URI("file:///C:/Go/src/bob%20george/george/george.go"),
 		},
 		{
-			path:     `file:///c:/path/to/%25p%25ercent%25/per%25cent.go`,
+			inputURI: `file:///c:/path/to/%25p%25ercent%25/per%25cent.go`,
 			wantFile: `C:\path\to\%p%ercent%\per%cent.go`,
 			wantURI:  span.URI(`file:///C:/path/to/%25p%25ercent%25/per%25cent.go`),
 		},
 	} {
-		got := span.NewURI(test.path)
+		got := span.URIFromURI(test.inputURI)
 		if got != test.wantURI {
-			t.Errorf("ToURI: got %s, expected %s", got, test.wantURI)
+			t.Errorf("NewURI(%q): got %q, expected %q", test.inputURI, got, test.wantURI)
 		}
-		if got.Filename() != test.wantFile {
-			t.Errorf("Filename: got %s, expected %s", got.Filename(), test.wantFile)
+		gotFilename := got.Filename()
+		if gotFilename != test.wantFile {
+			t.Errorf("Filename(%q): got %q, expected %q", got, gotFilename, test.wantFile)
 		}
 	}
 }