Skip to content

Use vscode-uri semantics for parsing URIs - #83593

Merged
dibarbet merged 50 commits into
dotnet:mainfrom
dibarbet:vscode-uri
Sep 2, 2026
Merged

Use vscode-uri semantics for parsing URIs#83593
dibarbet merged 50 commits into
dotnet:mainfrom
dibarbet:vscode-uri

Conversation

@dibarbet

@dibarbet dibarbet commented May 6, 2026

Copy link
Copy Markdown
Member

We've had a number of issues with using System.Uri over the years, mostly caused by VSCode's URI handling being more lenient than System.Uri (and System.Uri being more strict than even the URI RFC requires). Most recently is dotnet/vscode-csharp#9087 which this should resolve.

There've been a number of fixes, including implementing our own equality checks, delaying URI parsing until we absolutely require it, and others - but none have been perfect - we continually see System.Uri parsing failures.

The goal of this PR is to switch to VSCode's URI parsing behavior (from vscode-uri) and avoid using System.Uri altogether due to the number of issues we've had. This should ensure that any URI we get from VSCode we can parse.

Mostly - this is a server-side behavior change. However this is visible to clients as this does change how we convert a URI into a string for serialization. These now serialize using vscode-uri semantics, so things like drive letters will get encoded (c:/ -> file:///c%3A/). System.Uri left the colon in the drive letter unencoded in the URI string.

We also cannot entirely delete System.Uri handling from the LSP path yet - XAML EA APIs and various parts of Razor still rely on System.Uri - however we can limit usage to just those scenarios.

The implementation of ParsedUri is intended to match vscode-uri. For examples and details on how URIs are parsed, how they are equal, and how they round trip, see ParsedUriTests.

[ x ] - vsc
[ x ] - vs
[ ] - copilot

Microsoft Reviewers: Open in CodeFlow

@dibarbet
dibarbet force-pushed the vscode-uri branch 2 times, most recently from 9ca5e2b to 761fca8 Compare May 7, 2026 22:31
@dibarbet
dibarbet force-pushed the vscode-uri branch 2 times, most recently from 9eb64f0 to 8d6c2d0 Compare May 12, 2026 17:43
@dibarbet
dibarbet force-pushed the vscode-uri branch 3 times, most recently from 9a8a978 to d9118a9 Compare June 16, 2026 02:36
@dibarbet
dibarbet force-pushed the vscode-uri branch 2 times, most recently from df6903b to ea4a4b5 Compare June 24, 2026 22:53
@dibarbet
dibarbet force-pushed the vscode-uri branch 2 times, most recently from c9ba792 to 18cd21a Compare July 20, 2026 20:19
@dibarbet dibarbet changed the title wip Use vscode-uri semantics for parsing URIs Jul 20, 2026
@dibarbet
dibarbet force-pushed the vscode-uri branch 2 times, most recently from cdef7e6 to a20777c Compare July 20, 2026 23:28
@dibarbet

Copy link
Copy Markdown
Member Author

/pr-val

@github-actions

Copy link
Copy Markdown
Contributor

View PR Validation Run triggered by @dibarbet

Parameters
  • Validation Type: pr-val
  • Pipeline ID: 8972
  • Pipeline Version: main
  • PR Number: 83593
  • Commit SHA: a20777cd6c4ebd14ed49ff310e324d0b7052135c
  • Source Branch: vscode-uri
  • Target Branch: main
  • Build ID: 14714249

@dibarbet
dibarbet force-pushed the vscode-uri branch 2 times, most recently from 4e61e5f to b2c8d49 Compare July 21, 2026 23:25
@dibarbet
dibarbet marked this pull request as ready for review July 22, 2026 17:33
Copilot AI lite review requested due to automatic review settings July 22, 2026 17:33
@dibarbet
dibarbet requested review from a team as code owners July 22, 2026 17:33
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).
There may be pipelines that require an authorized user to comment /azp run to run.

@github-actions

Copy link
Copy Markdown
Contributor

View PR Validation Run triggered by @dibarbet

Parameters
  • Validation Type: pr-val
  • Pipeline ID: 8972
  • Pipeline Version: main
  • PR Number: 83593
  • Commit SHA: e75762d8b68f54ab703af4aee8980847b24b7c4d
  • Source Branch: vscode-uri
  • Target Branch: main
  • Build ID: 15108206

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 17dcbb0d-711d-4cca-91c3-574ec31aeea8
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 17dcbb0d-711d-4cca-91c3-574ec31aeea8
Copilot AI review requested due to automatic review settings August 28, 2026 00:30
@dibarbet

Copy link
Copy Markdown
Member Author

/pr-val

@github-actions

Copy link
Copy Markdown
Contributor

View PR Validation Run triggered by @dibarbet

Parameters
  • Validation Type: pr-val
  • Pipeline ID: 8972
  • Pipeline Version: main
  • PR Number: 83593
  • Commit SHA: 9eb6d8ddce85c92726567f9607b557793682adcd
  • Source Branch: vscode-uri
  • Target Branch: main
  • Build ID: 15127059

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

Review tier: Lite
Findings: 1 Medium severity · 4 Low severity

New issues introduced by this change (2)
Severity Finding
Low severity src/​LanguageServer/​Protocol/​Protocol/​DocumentUri.cs — The XML doc references Protocol.ParsedUri, but there is no Protocol alias/type/namespace here;…
Medium severity src/​LanguageServer/​Protocol/​Extensions/​Extensions.cs — Project file path comparisons should be OS-sensitive. Using StringComparison.OrdinalIgnoreCase
Pre-existing issues (3)
Severity Finding
Low severity src/​LanguageServer/​Protocol/​Protocol/​ParsedUri.css_singleSlashStart and s_doubleSlashStart are declared but never used in this port. If they’re… View comment
Low severity src/​Razor/​src/​Razor/​src/​Microsoft.CodeAnalysis.Razor.Workspaces/​UriExtensions.cs — These #pragma warning disable comments include "TODO" text. This repo’s guidance is to avoid… View comment
Low severity src/​LanguageServer/​Protocol/​Protocol/​DocumentUri.csDocumentUri(Uri) no longer seeds the cached parsed representation, so the first access to… View comment
Issues resolved since last review (1)
Severity Finding
Low severity src/​LanguageServer/​BannedSymbols.txt — This PR introduces a new core URI abstraction (ParsedUri) and also adds a new repo-level rule… View resolved comment
Suppressed comments (2)

src/LanguageServer/Protocol/Protocol/DocumentUri.cs:49

  • Protocol.ParsedUri.Parse(...) looks like an accidental qualification; there is no Protocol alias/type here. This should call ParsedUri.Parse(...) directly or it won’t compile.
    src/LanguageServer/Protocol/Protocol/DocumentUri.cs:67
  • Obsolete message says "Use ParsedDocUri instead" but the property is named ParsedDocumentUri. The message should match the API name to avoid confusion during migrations.

Comment thread src/LanguageServer/Protocol/Protocol/DocumentUri.cs
Comment thread src/LanguageServer/Protocol/Extensions/Extensions.cs
Copilot AI review requested due to automatic review settings August 28, 2026 01:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

Review tier: Lite
Findings: 1 High severity · 3 Low severity

New issues introduced by this change (1)
Severity Finding
High severity src/​LanguageServer/​Protocol/​Extensions/​Extensions.cs — GetRequiredLegacySystemUri calls the obsolete DocumentUri.ParsedUri without suppressing CS0618…
Pre-existing issues (3)
Severity Finding
Low severity src/​LanguageServer/​Protocol/​Protocol/​ParsedUri.css_singleSlashStart and s_doubleSlashStart are declared but never used in this port. If they’re… View comment
Low severity src/​Razor/​src/​Razor/​src/​Microsoft.CodeAnalysis.Razor.Workspaces/​UriExtensions.cs — These #pragma warning disable comments include "TODO" text. This repo’s guidance is to avoid… View comment
Low severity src/​LanguageServer/​Protocol/​Protocol/​DocumentUri.csDocumentUri(Uri) no longer seeds the cached parsed representation, so the first access to… View comment
Issues resolved since last review (2)
Severity Finding
Medium severity src/​LanguageServer/​Protocol/​Extensions/​Extensions.cs — Project file path comparisons should be OS-sensitive. Using StringComparison.OrdinalIgnoreCaseView resolved comment
Low severity src/​LanguageServer/​Protocol/​Protocol/​DocumentUri.cs — The XML doc references Protocol.ParsedUri, but there is no Protocol alias/type/namespace here;… View resolved comment
Suppressed comments (3)

src/LanguageServer/Protocol/Protocol/DocumentUri.cs:68

  • The Obsolete message references "ParsedDocUri", but the property is now named ParsedDocumentUri. This reads like a typo and makes guidance to callers unclear.
    src/LanguageServer/Protocol/Extensions/Extensions.cs:228
  • GetProject now compares project.FilePath to the request URI path using OrdinalIgnoreCase unconditionally. That can produce incorrect matches on case-sensitive file systems (Linux/macOS) and differs from Roslyn’s established path comparison behavior. Use PathUtilities.Comparer (OS-appropriate) instead.
    public static Project? GetProject(this Solution solution, TextDocumentIdentifier projectIdentifier)
    {
        // We need to parse the URI (scheme, file path) to be able to lookup the URI in the solution.
        var parsedDocumentUri = projectIdentifier.DocumentUri.ParsedDocumentUri;
        if (parsedDocumentUri is null)
        {
            return null;
        }

        var projects = solution.Projects.WhereAsArray(project => string.Equals(project.FilePath, parsedDocumentUri.FsPath, StringComparison.OrdinalIgnoreCase));
        return !projects.Any()

src/LanguageServer/Protocol/Extensions/ProtocolConversions.cs:635

  • HelpLinkToCodeDescription now wraps any non-null string into a DocumentUri without validating that it’s an absolute URI. This is a behavior change from the prior Uri.TryCreate-based validation (via DiagnosticData.GetValidHelpLinkUri) and can accidentally emit invalid/empty href values (e.g., empty string becomes a parsed file URI under non-strict parsing). Consider validating with ParsedUri (strict for URLs, and ParsedUri.File for absolute file paths) and returning null on parse failure.
    public static LSP.CodeDescription? HelpLinkToCodeDescription(string? helpLinkUri)
    {
        return (helpLinkUri != null) ? new LSP.CodeDescription { Href = new DocumentUri(helpLinkUri) } : null;
    }

Comment on lines +75 to +79
#pragma warning disable RS0030 // Intentional for backwards compatibility.
var parsedUri = documentUri.ParsedUri;
Contract.ThrowIfNull(parsedUri, $"URI {documentUri} could not be parsed");
#pragma warning restore RS0030 // Do not use banned APIs
return parsedUri;
Copilot AI review requested due to automatic review settings September 2, 2026 17:32
@dibarbet
dibarbet enabled auto-merge September 2, 2026 17:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

It introduces at least one correctness issue in path comparison (OrdinalIgnoreCase instead of PathUtilities.Comparer) and contains misleading obsolete guidance text that should be fixed before merge.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Lite
Findings: 1 High severity · 1 Medium severity · 4 Low severity

New issues introduced by this change (2)
Severity Finding
Medium severity src/​LanguageServer/​Protocol/​Extensions/​Extensions.csGetProject compares project.FilePath to parsedDocumentUri.FsPath using…
Low severity src/​LanguageServer/​Protocol/​Protocol/​DocumentUri.cs — The [Obsolete] message for ParsedUri says "Use ParsedDocUri instead", but the property is named…
Pre-existing issues (4)
Severity Finding
High severity src/​LanguageServer/​Protocol/​Extensions/​Extensions.cs — GetRequiredLegacySystemUri calls the obsolete DocumentUri.ParsedUri without suppressing CS0618… View comment
Low severity src/​LanguageServer/​Protocol/​Protocol/​ParsedUri.css_singleSlashStart and s_doubleSlashStart are declared but never used in this port. If they’re… View comment
Low severity src/​Razor/​src/​Razor/​src/​Microsoft.CodeAnalysis.Razor.Workspaces/​UriExtensions.cs — These #pragma warning disable comments include "TODO" text. This repo’s guidance is to avoid… View comment
Low severity src/​LanguageServer/​Protocol/​Protocol/​DocumentUri.csDocumentUri(Uri) no longer seeds the cached parsed representation, so the first access to… View comment

Comment on lines 218 to 230
public static Project? GetProject(this Solution solution, TextDocumentIdentifier projectIdentifier)
{
// We need to parse the URI (scheme, file path) to be able to lookup the URI in the solution.
if (projectIdentifier.DocumentUri.ParsedUri is null)
var parsedDocumentUri = projectIdentifier.DocumentUri.ParsedDocumentUri;
if (parsedDocumentUri is null)
{
return null;
}

var projects = solution.Projects.WhereAsArray(project => project.FilePath == projectIdentifier.DocumentUri.ParsedUri.LocalPath);
var projects = solution.Projects.WhereAsArray(project => string.Equals(project.FilePath, parsedDocumentUri.FsPath, StringComparison.OrdinalIgnoreCase));
return !projects.Any()
? null
: FindItemInProjectContext(projects, projectIdentifier, projectIdGetter: (item) => item.Id, defaultGetter: () => projects[0]);
Comment on lines +67 to +68
[Obsolete("Use ParsedDocUri instead. Tracking: https://github.com/dotnet/roslyn/issues/84785")]
public Uri? ParsedUri
Copilot AI review requested due to automatic review settings September 2, 2026 19:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

It introduces an OS-inappropriate case-insensitive project path comparison and contains a misleading Obsolete message that should be corrected.

Review tier: Lite
Findings: 1 High severity · 1 Medium severity · 4 Low severity

Pre-existing issues (6)
Severity Finding
High severity src/​LanguageServer/​Protocol/​Extensions/​Extensions.cs — GetRequiredLegacySystemUri calls the obsolete DocumentUri.ParsedUri without suppressing CS0618… View comment
Medium severity src/​LanguageServer/​Protocol/​Extensions/​Extensions.csGetProject compares project.FilePath to parsedDocumentUri.FsPath using… View comment
Low severity src/​LanguageServer/​Protocol/​Protocol/​DocumentUri.cs — The [Obsolete] message for ParsedUri says "Use ParsedDocUri instead", but the property is named… View comment
Low severity src/​LanguageServer/​Protocol/​Protocol/​ParsedUri.css_singleSlashStart and s_doubleSlashStart are declared but never used in this port. If they’re… View comment
Low severity src/​Razor/​src/​Razor/​src/​Microsoft.CodeAnalysis.Razor.Workspaces/​UriExtensions.cs — These #pragma warning disable comments include "TODO" text. This repo’s guidance is to avoid… View comment
Low severity src/​LanguageServer/​Protocol/​Protocol/​DocumentUri.csDocumentUri(Uri) no longer seeds the cached parsed representation, so the first access to… View comment
Suppressed comments (2)

src/LanguageServer/Protocol/Protocol/DocumentUri.cs:68

  • The Obsolete message refers to "ParsedDocUri", but the property is named ParsedDocumentUri. This makes the guidance misleading for callers.
    src/LanguageServer/Protocol/Extensions/Extensions.cs:228
  • Project file paths are compared using OrdinalIgnoreCase unconditionally. On Unix-like platforms this can incorrectly match the wrong project when paths differ only by casing; Roslyn generally uses PathUtilities.Comparer for path equality to respect platform semantics.
            return null;
        }

        var projects = solution.Projects.WhereAsArray(project => string.Equals(project.FilePath, parsedDocumentUri.FsPath, StringComparison.OrdinalIgnoreCase));
        return !projects.Any()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants