Skip to content

Tooling: Don't throw exceptions when generating code for file rooted outside of project #11864

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 16, 2025

Conversation

DustinCampbell
Copy link
Member

@DustinCampbell DustinCampbell commented May 15, 2025

Fixes AzDO 2477693

It is a perfectly valid scenario to use a file rooted outside of the project containing it. For example, a NuGet package might add Razor files as content along with "TargetPath" metadata to a project. However, recent changes broke this scenario.

The incorrect change was to start calling RazorProjectFileSystem.GetItem(...) with the physical file path of a document. This is almost never correct because DefaultRazorProjectFileSystem.GetItem(...) will throw if the path passed in is rooted outside of the project root. Instead, it's more correct to pass in the document's target path, which is essentially a "logical path" to the file under the project root. When the target path is passed in, DefaultRazorProjectFileSystem.GetItem(...) will return a file path under the project root, which is what is needed by tooling to compute the applicable import file paths.

I've fixed this issue comprehensively by correcting three other issues that I found along the way.

  1. DefaultRazorProjecFileSystem.NormalizeAndEnsureValidPath(...) shouldn't join two rooted paths. Instead, if the path passed is rooted outside of the project root, it should just return the path. This was noticed because a rooted path was being passed in when it shouldn't have been.
  2. ProjectStateUpdate shouldn't throw when trying to send "success" telemetry on a failure.
  3. Ensure that tooling calls RazorProjectFileSystem.GetItem(...) with a document's target path rather than its physical file path.

CI Build: https://dev.azure.com/dnceng/internal/_build/results?buildId=2710729&view=results
Test Insertion: https://dev.azure.com/devdiv/DevDiv/_git/VS/pullrequest/637065

Add ReadOnlySpan<T>.StartsWith(...) and EndWith methods that take a single T value. These were added for convenience in .NET 9.

Note that these are polyfills that call into the .NET methods on .NET 9 or higher.
Add a couple of methods from System.IO.Path to PathUtilities:

- IsPathFullyQualified - Doesn't exist on net472 or netstandard2.0
- IsPathRooted - The overload that takes a ReadOnlySpan<char> (used by IsPathFullyQualfied) doesn't exist on net472 or netstandard2.0
In a prior implementation, DefaultRazorProjectFIleSystem.NormalizeAndEnsureValidPath(...) called Path.Combine(...) which would return the second path if both were rooted. However, that behavior was not carried into the new implementation, resulting in cases where a path can be "double" rooted if it is an absolute path outside of the project root. This change uses PathUtilities.IsPathFullQualified(...) to restore the previous behavior.
If ITagHelperResolver.GetTagHelpersAsync(...) fails, it returns a default (not empty) ImmutableArray<TagHelperDescriptor>. If that happens, we would end up throwing an exception when trying to report success telemetry by accessing the "Length" property on the result. This change checks first to see if the result was actually a failure and reports failure instead, avoiding the exception.
Recently, a couple of calls to RazorProjectFileSystem.GetItem(...) were changed to pass a file path rather than a target path. This results in an exception being thrown in the DefaultRazorProjectFileSystem if the file path is rooted outside of the project root. Instead, it's important to pass the "target path", i.e. the "logical" path of a file within the project. This avoids breaking completely valid scenarios where a file is included in the Razor project from an outside path (such as a content file in a NuGet package).
@DustinCampbell DustinCampbell requested review from a team as code owners May 15, 2025 22:40
@DustinCampbell
Copy link
Member Author

/backport to release/dev17.14

Copy link

Started backporting to release/dev17.14: https://github.com/dotnet/razor/actions/runs/15069900799

Copy link

@DustinCampbell backporting to "release/dev17.14" failed, the patch most likely resulted in conflicts:

$ git am --3way --empty=keep --ignore-whitespace --keep-non-patch changes.patch

Applying: Add StartsWith and EndsWith extension methods
Applying: PathUtilities: Add IsPathFullyQualified and IsPathRooted methods
Applying: DefaultRazorProjectFileSystem: Don't join two rooted paths
Applying: ProjectStateUpdater: Don't throw when reporting success
Applying: Call RazorProjectFileSystem.GetItem(...) with correct path
Using index info to reconstruct a base tree...
M	src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshotExtensions.cs
M	src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/Legacy/ILegacyDocumentSnapshot.cs
Falling back to patching base and 3-way merge...
Auto-merging src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/Legacy/ILegacyDocumentSnapshot.cs
CONFLICT (content): Merge conflict in src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/Legacy/ILegacyDocumentSnapshot.cs
Auto-merging src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshotExtensions.cs
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
hint: When you have resolved this problem, run "git am --continue".
hint: If you prefer to skip this patch, run "git am --skip" instead.
hint: To restore the original branch and stop patching, run "git am --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Patch failed at 0005 Call RazorProjectFileSystem.GetItem(...) with correct path
Error: The process '/usr/bin/git' failed with exit code 128

Please backport manually!

@DustinCampbell
Copy link
Member Author

Please backport manually!

Boo!

@phil-allen-msft phil-allen-msft merged commit 761db5d into dotnet:main May 16, 2025
11 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone May 16, 2025
phil-allen-msft added a commit that referenced this pull request May 16, 2025
…de for file rooted outside of project (#11868)

Backport of #11864 to release/dev17.14

## Customer Impact

## Regression

- [x] Yes - #11320 (This change
inadvertently removed some exception swallowing that "hid" the problem
from the user.)
- [ ] No

## Testing

- Manual testing of scenario with known repro project. The issue is
present without the fix and resolved with the fix.
- Tested mainline scenarios, such as OrchardCore and MudBlazor. These
continue working with the fix.

## Risk

Medium. This affects how every Razor file is compiled in Visual Studio
at design-time. However, this risk is mitigated by testing large
solutions such as OrchardCore and MudBlazor.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants