Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Licensed under the MIT license. See License.txt in the project root for license information.

using System.Collections.Immutable;
using System.Threading.Tasks;
using Microsoft.VisualStudio.ProjectSystem;
using Microsoft.VisualStudio.ProjectSystem.Properties;

namespace Microsoft.VisualStudio.Razor.ProjectSystem;
Expand All @@ -16,8 +18,17 @@ internal sealed class TestAccessor(WindowsRazorProjectHostBase @this)

internal bool GetIntermediateOutputPathFromProjectChange(IImmutableDictionary<string, IProjectRuleSnapshot> state, out string? result)
{
_this.SkipIntermediateOutputPathExistCheck_TestOnly = true;
_this._skipDirectoryExistCheck_TestOnly = true;
return _this.TryGetIntermediateOutputPath(state, out result);
}

internal Task InitializeAsync()
=> _this.InitializeAsync();

internal Task OnProjectChangedAsync(string sliceDimensions, IProjectVersionedValue<IProjectSubscriptionUpdate> update)
=> _this.OnProjectChangedAsync(sliceDimensions, update);

internal Task OnProjectRenamingAsync(string oldProjectFilePath, string newProjectFilePath)
=> _this.OnProjectRenamingAsync(oldProjectFilePath, newProjectFilePath);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@ internal abstract partial class WindowsRazorProjectHostBase : OnceInitializedOnc

internal const string ConfigurationGeneralSchemaName = "ConfigurationGeneral";

// Internal settable for testing
// 250ms between publishes to prevent bursts of changes yet still be responsive to changes.
internal int EnqueueDelay { get; set; } = 250;
private bool _skipDirectoryExistCheck_TestOnly;

protected WindowsRazorProjectHostBase(
IUnconfiguredProjectCommonServices commonServices,
Expand All @@ -61,14 +59,6 @@ protected WindowsRazorProjectHostBase(

protected IUnconfiguredProjectCommonServices CommonServices { get; }

internal bool SkipIntermediateOutputPathExistCheck_TestOnly { get; set; }

// internal for tests. The product will call through the IProjectDynamicLoadComponent interface.
internal Task LoadAsync()
{
return InitializeAsync();
}

protected sealed override Task InitializeCoreAsync(CancellationToken cancellationToken)
{
CommonServices.UnconfiguredProject.ProjectRenaming += UnconfiguredProject_ProjectRenamingAsync;
Expand Down Expand Up @@ -155,8 +145,7 @@ private void SlicesChanged(IProjectVersionedValue<ConfigurationSubscriptionSourc
}
}

// Internal for testing
internal Task OnProjectChangedAsync(string sliceDimensions, IProjectVersionedValue<IProjectSubscriptionUpdate> update)
private Task OnProjectChangedAsync(string sliceDimensions, IProjectVersionedValue<IProjectSubscriptionUpdate> update)
{
if (IsDisposing || IsDisposed)
{
Expand Down Expand Up @@ -204,8 +193,7 @@ await ExecuteWithLockAsync(
}
}

// Internal for tests
internal Task OnProjectRenamingAsync(string oldProjectFilePath, string newProjectFilePath)
private Task OnProjectRenamingAsync(string oldProjectFilePath, string newProjectFilePath)
{
// When a project gets renamed we expect any rules watched by the derived class to fire.
//
Expand Down Expand Up @@ -320,7 +308,6 @@ protected bool TryGetBeforeIntermediateOutputPath(IImmutableDictionary<string, I
return TryGetIntermediateOutputPathFromProjectRuleSnapshot(beforeValues, out path);
}

// virtual for testing
protected virtual bool TryGetIntermediateOutputPath(
IImmutableDictionary<string, IProjectRuleSnapshot> state,
[NotNullWhen(returnValue: true)] out string? path)
Expand Down Expand Up @@ -357,15 +344,11 @@ private bool TryGetIntermediateOutputPathFromProjectRuleSnapshot(IProjectRuleSna
var basePath = new DirectoryInfo(baseIntermediateOutputPathValue).Parent;
var joinedPath = Path.Combine(basePath.FullName, intermediateOutputPathValue);

if (!SkipIntermediateOutputPathExistCheck_TestOnly && !Directory.Exists(joinedPath))
if (!Path.IsPathRooted(baseIntermediateOutputPathValue))
{
// The directory doesn't exist for the currently executing application.
// This can occur in Razor class library scenarios because:
// 1. Razor class libraries base intermediate path is not absolute. Meaning instead of C:/project/obj it returns /obj.
// 2. Our `new DirectoryInfo(...).Parent` call above is forgiving so if the path passed to it isn't absolute (Razor class library scenario) it utilizes Directory.GetCurrentDirectory where
// in this case would be the C:/Windows/System path
// Because of the above two issues the joinedPath ends up looking like "C:\WINDOWS\system32\obj\Debug\netstandard2.0\" which doesn't actually exist and of course isn't writeable. The end-user effect of this
// quirk means that you don't get any component completions for Razor class libraries because we're unable to capture their project state information.
// For Razor class libraries, the base intermediate path is relative. Meaning instead of C:/project/obj it returns /obj.
// The `new DirectoryInfo(...).Parent` call above is forgiving so if the path passed to it isn't absolute (Razor class library scenario) it utilizes Directory.GetCurrentDirectory, which
// could be the C:/Windows/System path, or the solution path, or anything really.
//
// To workaround these inconsistencies with Razor class libraries we fall back to the MSBuildProjectDirectory and build what we think is the intermediate output path.
joinedPath = ResolveFallbackIntermediateOutputPath(rule, intermediateOutputPathValue);
Expand All @@ -381,7 +364,7 @@ private bool TryGetIntermediateOutputPathFromProjectRuleSnapshot(IProjectRuleSna
return true;
}

private static string? ResolveFallbackIntermediateOutputPath(IProjectRuleSnapshot rule, string intermediateOutputPathValue)
private string? ResolveFallbackIntermediateOutputPath(IProjectRuleSnapshot rule, string intermediateOutputPathValue)
{
if (!rule.Properties.TryGetValue(MSBuildProjectDirectoryPropertyName, out var projectDirectory))
{
Expand All @@ -390,7 +373,7 @@ private bool TryGetIntermediateOutputPathFromProjectRuleSnapshot(IProjectRuleSna
}

var joinedPath = Path.Combine(projectDirectory, intermediateOutputPathValue);
if (!Directory.Exists(joinedPath))
if (!_skipDirectoryExistCheck_TestOnly && !Directory.Exists(joinedPath))
{
return null;
}
Expand Down
Loading
Loading