diff --git a/.editorconfig b/.editorconfig index 3817b0fe689..3189f0d9ab7 100644 --- a/.editorconfig +++ b/.editorconfig @@ -103,6 +103,10 @@ csharp_style_unused_value_expression_statement_preference = discard_variable:non # IDE0065 csharp_using_directive_placement = outside_namespace:suggestion +# Roslyn analyzers +dotnet_diagnostic.RS0006.severity = error +dotnet_diagnostic.RS0023.severity = error + # xUnit1004: Test methods should not be skipped dotnet_diagnostic.xUnit1004.severity = refactoring diff --git a/docs/Onboarding Docs/01_Razor_Basics.md b/docs/Onboarding Docs/01_Razor_Basics.md new file mode 100644 index 00000000000..254306d4da0 --- /dev/null +++ b/docs/Onboarding Docs/01_Razor_Basics.md @@ -0,0 +1,50 @@ +# Razor Basics + +Razor is a templating language used in ASP.NET for creating dynamic web pages. It's not a programming language itself, but a markup syntax for embedding code (C# or VB.NET) into HTML. + +In a Razor file, you can use a combination of several languages: + +| Language | Usage | Supported in .NET Core and .NET 5+ | +| --- | --- | --- | +| **Razor syntax** | Used to embed and execute server-side code within HTML. | Yes | +| **C#** | The server-side language used within Razor templates. Most commonly used with Razor. | Yes | +| **HTML** | Used to structure the content on web pages. | Yes | +| **JavaScript** | Used for client-side scripting in Razor templates. | Yes | +| **CSS** | Used for styling web pages. | Yes | +| **VB.NET** | Can be used in Razor syntax in the older .NET Framework. | No | + +Please note that while Razor syntax does support VB.NET in the older .NET Framework, VB.NET is not supported in .NET Core or .NET 5 and onwards. In these newer frameworks, only C# is supported. + +## Razor File Types + +Razor files typically come in three extensions: `.cshtml`, `.vbhtml`, and `.razor`. Each extension corresponds to a specific type of Razor file and determines its usage within an application: + +| File Extension | Type | Description | Usage | +| --- | --- | --- | --- | +| `.cshtml` | Razor View | Part of the MVC (Model-View-Controller) pattern, where the View is responsible for the presentation logic. Located within the Views folder of an MVC application and associated with a Controller. | Used in MVC applications for complex scenarios where separation of concerns is important. | +| `.cshtml` | Razor Page | A page-based programming model that makes building web UI easier and more productive. Located within the Pages folder of a Razor Pages application and includes a `@page` directive at the top. | Used in Razor Pages applications for simpler scenarios where a full MVC model might be overkill. | +| `.razor` | Razor Component (Blazor) | Used in Blazor, a framework for building interactive client-side web UI with .NET. Each `.razor` file is a self-contained component that can include both the markup and the processing logic. | Used in Blazor applications for building interactive client-side web UIs. | +| `.vbhtml` | Razor View (VB.NET) | Part of the MVC (Model-View-Controller) pattern, where the View is responsible for the presentation logic. Located within the Views folder of an MVC application and associated with a Controller. | Used in older MVC applications written in VB.NET. | + +## Razor Editors: Legacy vs New + +| Aspect | Razor Legacy | Legacy .NET Core Razor Editor | New .NET Core Razor Editor | +| --- | --- | --- | --- | +| **Introduction** | Introduced with ASP.NET MVC 3. | Older Razor editor for ASP.NET Core projects. | Updated Razor editor introduced in Visual Studio 2019 version 16.8. | +| **Usage** | Used in ASP.NET MVC and ASP.NET Web Pages applications. | Used for editing Razor views and pages in ASP.NET Core projects. | Used for editing Razor views and pages in ASP.NET Core projects. | +| **Source code** | Closed source. | Closed source. | [Open source on GitHub](https://github.com/dotnet/razor/) | +| **File Extensions** | `.cshtml` for C#, `.vbhtml` for VB.NET. | `.cshtml` and `.razor` | `.cshtml` and `.razor` | +| **Functionality** | Creates dynamic web pages that combine HTML and server-side code. | Provides basic features like syntax highlighting and IntelliSense for Razor syntax. | Provides improved functionality and performance, including better IntelliSense, improved syntax highlighting, support for Razor formatting, better diagnostics, and features like "Go to Definition" and "Find All References" for Razor components and their parameters. | +| **Support** | Supported for maintaining existing applications. | Phased out, not recommended for new projects. | Actively supported and recommended for new projects. | +| **Configuration** | Used by default for .NET Framework applications. | The legacy .NET Core editor is off by default. | The new .NET Core editor is used by default. | +| **Implementation** | Monolithic design, language services implemented by the editor, no LSP or TextMate grammars, limited VS integration. | Same as Razor Legacy | Uses LSP for language services, TextMate grammars for syntax highlighting, integrated with VS editor API, includes Blazor support. | + +## Razor Support Across ASP.NET Versions + +Different versions of ASP.NET support different features of Razor. Here's a summary: + +| TFM | Razor Support | +| --- | --- | +| **.NET Framework (<= 4.8)** | Supports Razor syntax with C# and VB.NET. Used in ASP.NET MVC and ASP.NET Web Pages applications. | +| **.NET Core 1.x - 3.1** | Supports Razor syntax with C# only. Used in ASP.NET Core MVC, Razor Pages applications, and had preview support for Blazor. | +| **.NET 5+** | Supports Razor syntax with C# only. Used in ASP.NET Core MVC, Razor Pages, and Blazor applications. | \ No newline at end of file diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index b68c5598926..beeaf6a200e 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -90,9 +90,9 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime 3a25a7f1cc446b60678ed25c9d829420d6321eba - + https://github.com/dotnet/arcade - 888985fb9a9ae4cb30bca75f98af9126c839e660 + 96c2cee493aa1542c0b06a6498e0379eb11e005f diff --git a/global.json b/global.json index e0ee01f1c90..5b6decebb6f 100644 --- a/global.json +++ b/global.json @@ -21,6 +21,6 @@ "rollForward": "latestPatch" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24060.4" + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24074.2" } } diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentMarkupEncodingPass.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentMarkupEncodingPass.cs index 945d0f60f3f..cc061f1e3ed 100644 --- a/src/Compiler/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentMarkupEncodingPass.cs +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentMarkupEncodingPass.cs @@ -13,6 +13,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Components; internal class ComponentMarkupEncodingPass : ComponentIntermediateNodePassBase, IRazorOptimizationPass { + private readonly RazorLanguageVersion _version; + + public ComponentMarkupEncodingPass(RazorLanguageVersion version) + { + _version = version; + } + // Runs after ComponentMarkupBlockPass public override int Order => ComponentMarkupDiagnosticPass.DefaultOrder + 20; @@ -29,7 +36,7 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentInte return; } - var rewriter = new Rewriter(); + var rewriter = new Rewriter(_version); rewriter.Visit(documentNode); } @@ -47,10 +54,37 @@ private class Rewriter : IntermediateNodeWalker private static readonly char[] EncodedCharacters = new[] { '\r', '\n', '\t', '<', '>' }; + private readonly bool _avoidEncodingScripts; private readonly Dictionary _seenEntities = new Dictionary(StringComparer.Ordinal); + private bool _avoidEncodingContent; + + public Rewriter(RazorLanguageVersion version) + { + _avoidEncodingScripts = version.CompareTo(RazorLanguageVersion.Version_8_0) >= 0; + } + + public override void VisitMarkupElement(MarkupElementIntermediateNode node) + { + // We don't want to HTML-encode literal content inside +
console.log('@msg');
+ +
console.log('No variable');
+ +
+ console.log('@msg'); +
+ +
+ console.log('No variable'); +
+ """; + var project = CreateTestProject(new() + { + ["Views/Home/Index.cshtml"] = $""" + {code} + @(await Html.RenderComponentAsync(RenderMode.Static)) + """, + ["Shared/Component1.razor"] = $""" + Component: + {code} + """, + }); + var compilation = await project.GetCompilationAsync(); + var driver = await GetDriverAsync(project, options => + { + options.TestGlobalOptions["build_property.RazorLangVersion"] = razorLangVersion; + }); + + // Act + var result = RunGenerator(compilation!, ref driver, out compilation); + + // Assert + result.Diagnostics.Verify(); + Assert.Equal(2, result.GeneratedSources.Length); + var suffix = razorLangVersion == "7.0" ? "7" : "8"; + result.VerifyOutputsMatchBaseline(suffix: suffix); + await VerifyRazorPageMatchesBaselineAsync(compilation, "Views_Home_Index", suffix: suffix); + } + [Fact, WorkItem("https://github.com/dotnet/razor/issues/9051")] public async Task LineMapping() { diff --git a/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/RazorSourceGeneratorTestsBase.cs b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/RazorSourceGeneratorTestsBase.cs index 42386bed61b..e356d7907f6 100644 --- a/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/RazorSourceGeneratorTestsBase.cs +++ b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/RazorSourceGeneratorTestsBase.cs @@ -210,7 +210,7 @@ static IEnumerable GetViewStartNames(string name) } protected static async Task VerifyRazorPageMatchesBaselineAsync(Compilation compilation, string name, - [CallerFilePath] string testPath = "", [CallerMemberName] string testName = "") + [CallerFilePath] string testPath = "", [CallerMemberName] string testName = "", string suffix = "") { var html = await RenderRazorPageAsync(compilation, name); Extensions.VerifyTextMatchesBaseline( @@ -218,7 +218,8 @@ protected static async Task VerifyRazorPageMatchesBaselineAsync(Compilation comp fileName: name, extension: "html", testPath: testPath, - testName: testName); + testName: testName, + suffix: suffix); } protected static Project CreateTestProject( @@ -420,21 +421,22 @@ public static GeneratorRunResult VerifyPageOutput(this GeneratorRunResult result return result; } - private static string CreateBaselineDirectory(string testPath, string testName) + private static string CreateBaselineDirectory(string testPath, string testName, string suffix) { var baselineDirectory = Path.Join( _testProjectRoot, "TestFiles", Path.GetFileNameWithoutExtension(testPath)!, - testName); + testName, + suffix); Directory.CreateDirectory(baselineDirectory); return baselineDirectory; } public static GeneratorRunResult VerifyOutputsMatchBaseline(this GeneratorRunResult result, - [CallerFilePath] string testPath = "", [CallerMemberName] string testName = "") + [CallerFilePath] string testPath = "", [CallerMemberName] string testName = "", string suffix = "") { - var baselineDirectory = CreateBaselineDirectory(testPath, testName); + var baselineDirectory = CreateBaselineDirectory(testPath, testName, suffix); var touchedFiles = new HashSet(); foreach (var source in result.GeneratedSources) @@ -453,10 +455,10 @@ public static GeneratorRunResult VerifyOutputsMatchBaseline(this GeneratorRunRes } public static void VerifyTextMatchesBaseline(string actualText, string fileName, string extension, - [CallerFilePath] string testPath = "", [CallerMemberName] string testName = "") + [CallerFilePath] string testPath = "", [CallerMemberName] string testName = "", string suffix = "") { // Create output directory. - var baselineDirectory = CreateBaselineDirectory(testPath, testName); + var baselineDirectory = CreateBaselineDirectory(testPath, testName, suffix); // Generate baseline if enabled. var baselinePath = Path.Join(baselineDirectory, $"{fileName}.{extension}"); diff --git a/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/TestFiles/RazorSourceGeneratorComponentTests/ScriptTag_WithVariable/7/Shared_Component1_razor.g.cs b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/TestFiles/RazorSourceGeneratorComponentTests/ScriptTag_WithVariable/7/Shared_Component1_razor.g.cs new file mode 100644 index 00000000000..bd781b272f6 --- /dev/null +++ b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/TestFiles/RazorSourceGeneratorComponentTests/ScriptTag_WithVariable/7/Shared_Component1_razor.g.cs @@ -0,0 +1,87 @@ +#pragma checksum "Shared/Component1.razor" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "b72b3eb93cc4a906714929a4849d9f7aac2ef62a" +// +#pragma warning disable 1591 +namespace MyApp.Shared +{ + #line hidden + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + public partial class Component1 : global::Microsoft.AspNetCore.Components.ComponentBase + { + #pragma warning disable 1998 + protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder) + { + __builder.AddMarkupContent(0, "Component:\r\n"); +#nullable restore +#line 2 "Shared/Component1.razor" + var msg = "What's up"; + +#line default +#line hidden +#nullable disable + __builder.OpenElement(1, "script"); + __builder.AddContent(2, "console.log(\'"); +#nullable restore +#line (3,23)-(3,26) 24 "Shared/Component1.razor" +__builder.AddContent(3, msg); + +#line default +#line hidden +#nullable disable + __builder.AddContent(4, "\');"); + __builder.CloseElement(); + __builder.AddMarkupContent(5, "\r\n"); + __builder.OpenElement(6, "div"); + __builder.AddContent(7, "console.log(\'"); +#nullable restore +#line (4,20)-(4,23) 24 "Shared/Component1.razor" +__builder.AddContent(8, msg); + +#line default +#line hidden +#nullable disable + __builder.AddContent(9, "\');"); + __builder.CloseElement(); + __builder.AddMarkupContent(10, "\r\n"); + __builder.OpenElement(11, "script"); + __builder.AddContent(12, "console.log(\'No variable\');"); + __builder.CloseElement(); + __builder.AddMarkupContent(13, "\r\n"); + __builder.AddMarkupContent(14, "
console.log(\'No variable\');
\r\n"); + __builder.OpenElement(15, "script"); + __builder.AddMarkupContent(16, "\r\n console.log(\'"); +#nullable restore +#line (8,19)-(8,22) 25 "Shared/Component1.razor" +__builder.AddContent(17, msg); + +#line default +#line hidden +#nullable disable + __builder.AddMarkupContent(18, "\');\r\n"); + __builder.CloseElement(); + __builder.AddMarkupContent(19, "\r\n"); + __builder.OpenElement(20, "div"); + __builder.AddMarkupContent(21, "\r\n console.log(\'"); +#nullable restore +#line (11,19)-(11,22) 25 "Shared/Component1.razor" +__builder.AddContent(22, msg); + +#line default +#line hidden +#nullable disable + __builder.AddMarkupContent(23, "\');\r\n"); + __builder.CloseElement(); + __builder.AddMarkupContent(24, "\r\n"); + __builder.OpenElement(25, "script"); + __builder.AddMarkupContent(26, "\r\n console.log(\'No variable\');\r\n"); + __builder.CloseElement(); + __builder.AddMarkupContent(27, "\r\n"); + __builder.AddMarkupContent(28, "
\r\n console.log(\'No variable\');\r\n
"); + } + #pragma warning restore 1998 + } +} +#pragma warning restore 1591 diff --git a/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/TestFiles/RazorSourceGeneratorComponentTests/ScriptTag_WithVariable/7/Views_Home_Index.html b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/TestFiles/RazorSourceGeneratorComponentTests/ScriptTag_WithVariable/7/Views_Home_Index.html new file mode 100644 index 00000000000..8ddd8dbd255 --- /dev/null +++ b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/TestFiles/RazorSourceGeneratorComponentTests/ScriptTag_WithVariable/7/Views_Home_Index.html @@ -0,0 +1,33 @@ + +
console.log('What's up');
+ +
console.log('No variable');
+ +
+ console.log('What's up'); +
+ +
+ console.log('No variable'); +
+Component: + +
console.log('What's up');
+ +
console.log('No variable');
+ +
+ console.log('What's up'); +
+ +
+ console.log('No variable'); +
\ No newline at end of file diff --git a/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/TestFiles/RazorSourceGeneratorComponentTests/ScriptTag_WithVariable/7/Views_Home_Index_cshtml.g.cs b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/TestFiles/RazorSourceGeneratorComponentTests/ScriptTag_WithVariable/7/Views_Home_Index_cshtml.g.cs new file mode 100644 index 00000000000..3b365eb1309 --- /dev/null +++ b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/TestFiles/RazorSourceGeneratorComponentTests/ScriptTag_WithVariable/7/Views_Home_Index_cshtml.g.cs @@ -0,0 +1,95 @@ +#pragma checksum "Views/Home/Index.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "87d8a07c5693b48e5469d0cbc37b8ccc95b4cce2" +// +#pragma warning disable 1591 +[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.Views_Home_Index), @"mvc.1.0.view", @"/Views/Home/Index.cshtml")] +namespace AspNetCoreGeneratedDocument +{ + #line hidden + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Mvc; + using global::Microsoft.AspNetCore.Mvc.Rendering; + using global::Microsoft.AspNetCore.Mvc.ViewFeatures; + [global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute("Identifier", "/Views/Home/Index.cshtml")] + [global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute] + #nullable restore + internal sealed class Views_Home_Index : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage + #nullable disable + { + #pragma warning disable 1998 + public async override global::System.Threading.Tasks.Task ExecuteAsync() + { +#nullable restore +#line 1 "Views/Home/Index.cshtml" + var msg = "What's up"; + +#line default +#line hidden +#nullable disable + WriteLiteral("\r\n
console.log(\'"); +#nullable restore +#line (3,20)-(3,23) 6 "Views/Home/Index.cshtml" +Write(msg); + +#line default +#line hidden +#nullable disable + WriteLiteral("\');
\r\n\r\n
console.log(\'No variable\');
\r\n\r\n
\r\n console.log(\'"); +#nullable restore +#line (10,19)-(10,22) 6 "Views/Home/Index.cshtml" +Write(msg); + +#line default +#line hidden +#nullable disable + WriteLiteral("\');\r\n
\r\n\r\n
\r\n console.log(\'No variable\');\r\n
\r\n"); +#nullable restore +#line (18,3)-(18,78) 6 "Views/Home/Index.cshtml" +Write(await Html.RenderComponentAsync(RenderMode.Static)); + +#line default +#line hidden +#nullable disable + } + #pragma warning restore 1998 + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; } = default!; + #nullable disable + } +} +#pragma warning restore 1591 diff --git a/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/TestFiles/RazorSourceGeneratorComponentTests/ScriptTag_WithVariable/8/Shared_Component1_razor.g.cs b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/TestFiles/RazorSourceGeneratorComponentTests/ScriptTag_WithVariable/8/Shared_Component1_razor.g.cs new file mode 100644 index 00000000000..b35787c5519 --- /dev/null +++ b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/TestFiles/RazorSourceGeneratorComponentTests/ScriptTag_WithVariable/8/Shared_Component1_razor.g.cs @@ -0,0 +1,81 @@ +#pragma checksum "Shared/Component1.razor" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "b72b3eb93cc4a906714929a4849d9f7aac2ef62a" +// +#pragma warning disable 1591 +namespace MyApp.Shared +{ + #line hidden + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + public partial class Component1 : global::Microsoft.AspNetCore.Components.ComponentBase + { + #pragma warning disable 1998 + protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder) + { + __builder.AddMarkupContent(0, "Component:\r\n"); +#nullable restore +#line 2 "Shared/Component1.razor" + var msg = "What's up"; + +#line default +#line hidden +#nullable disable + __builder.OpenElement(1, "script"); + __builder.AddMarkupContent(2, "console.log(\'"); +#nullable restore +#line (3,23)-(3,26) 24 "Shared/Component1.razor" +__builder.AddContent(3, msg); + +#line default +#line hidden +#nullable disable + __builder.AddMarkupContent(4, "\');"); + __builder.CloseElement(); + __builder.AddMarkupContent(5, "\r\n"); + __builder.OpenElement(6, "div"); + __builder.AddContent(7, "console.log(\'"); +#nullable restore +#line (4,20)-(4,23) 24 "Shared/Component1.razor" +__builder.AddContent(8, msg); + +#line default +#line hidden +#nullable disable + __builder.AddContent(9, "\');"); + __builder.CloseElement(); + __builder.AddMarkupContent(10, "\r\n"); + __builder.AddMarkupContent(11, "\r\n"); + __builder.AddMarkupContent(12, "
console.log(\'No variable\');
\r\n"); + __builder.OpenElement(13, "script"); + __builder.AddMarkupContent(14, "\r\n console.log(\'"); +#nullable restore +#line (8,19)-(8,22) 25 "Shared/Component1.razor" +__builder.AddContent(15, msg); + +#line default +#line hidden +#nullable disable + __builder.AddMarkupContent(16, "\');\r\n"); + __builder.CloseElement(); + __builder.AddMarkupContent(17, "\r\n"); + __builder.OpenElement(18, "div"); + __builder.AddMarkupContent(19, "\r\n console.log(\'"); +#nullable restore +#line (11,19)-(11,22) 25 "Shared/Component1.razor" +__builder.AddContent(20, msg); + +#line default +#line hidden +#nullable disable + __builder.AddMarkupContent(21, "\');\r\n"); + __builder.CloseElement(); + __builder.AddMarkupContent(22, "\r\n"); + __builder.AddMarkupContent(23, "\r\n"); + __builder.AddMarkupContent(24, "
\r\n console.log(\'No variable\');\r\n
"); + } + #pragma warning restore 1998 + } +} +#pragma warning restore 1591 diff --git a/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/TestFiles/RazorSourceGeneratorComponentTests/ScriptTag_WithVariable/8/Views_Home_Index.html b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/TestFiles/RazorSourceGeneratorComponentTests/ScriptTag_WithVariable/8/Views_Home_Index.html new file mode 100644 index 00000000000..b3dce76bf40 --- /dev/null +++ b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/TestFiles/RazorSourceGeneratorComponentTests/ScriptTag_WithVariable/8/Views_Home_Index.html @@ -0,0 +1,33 @@ + +
console.log('What's up');
+ +
console.log('No variable');
+ +
+ console.log('What's up'); +
+ +
+ console.log('No variable'); +
+Component: + +
console.log('What's up');
+ +
console.log('No variable');
+ +
+ console.log('What's up'); +
+ +
+ console.log('No variable'); +
\ No newline at end of file diff --git a/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/TestFiles/RazorSourceGeneratorComponentTests/ScriptTag_WithVariable/8/Views_Home_Index_cshtml.g.cs b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/TestFiles/RazorSourceGeneratorComponentTests/ScriptTag_WithVariable/8/Views_Home_Index_cshtml.g.cs new file mode 100644 index 00000000000..3b365eb1309 --- /dev/null +++ b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/TestFiles/RazorSourceGeneratorComponentTests/ScriptTag_WithVariable/8/Views_Home_Index_cshtml.g.cs @@ -0,0 +1,95 @@ +#pragma checksum "Views/Home/Index.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "87d8a07c5693b48e5469d0cbc37b8ccc95b4cce2" +// +#pragma warning disable 1591 +[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.Views_Home_Index), @"mvc.1.0.view", @"/Views/Home/Index.cshtml")] +namespace AspNetCoreGeneratedDocument +{ + #line hidden + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Mvc; + using global::Microsoft.AspNetCore.Mvc.Rendering; + using global::Microsoft.AspNetCore.Mvc.ViewFeatures; + [global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute("Identifier", "/Views/Home/Index.cshtml")] + [global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute] + #nullable restore + internal sealed class Views_Home_Index : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage + #nullable disable + { + #pragma warning disable 1998 + public async override global::System.Threading.Tasks.Task ExecuteAsync() + { +#nullable restore +#line 1 "Views/Home/Index.cshtml" + var msg = "What's up"; + +#line default +#line hidden +#nullable disable + WriteLiteral("\r\n
console.log(\'"); +#nullable restore +#line (3,20)-(3,23) 6 "Views/Home/Index.cshtml" +Write(msg); + +#line default +#line hidden +#nullable disable + WriteLiteral("\');
\r\n\r\n
console.log(\'No variable\');
\r\n\r\n
\r\n console.log(\'"); +#nullable restore +#line (10,19)-(10,22) 6 "Views/Home/Index.cshtml" +Write(msg); + +#line default +#line hidden +#nullable disable + WriteLiteral("\');\r\n
\r\n\r\n
\r\n console.log(\'No variable\');\r\n
\r\n"); +#nullable restore +#line (18,3)-(18,78) 6 "Views/Home/Index.cshtml" +Write(await Html.RenderComponentAsync(RenderMode.Static)); + +#line default +#line hidden +#nullable disable + } + #pragma warning restore 1998 + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; } = default!; + #nullable disable + } +} +#pragma warning restore 1591 diff --git a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorSemanticTokensBenchmark.cs b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorSemanticTokensBenchmark.cs index f849a43e31a..73c6b99dcaf 100644 --- a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorSemanticTokensBenchmark.cs +++ b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorSemanticTokensBenchmark.cs @@ -120,8 +120,9 @@ protected internal override void Builder(IServiceCollection collection) private void EnsureServicesInitialized() { var languageServer = RazorLanguageServer.GetInnerLanguageServerForTesting(); + var legend = new RazorSemanticTokensLegend(new VSInternalClientCapabilities { SupportsVisualStudioExtensions = true }); RazorSemanticTokenService = languageServer.GetRequiredService(); - RazorSemanticTokenService.ApplyCapabilities(new(), new VSInternalClientCapabilities { SupportsVisualStudioExtensions = true }); + RazorSemanticTokenService.SetTokensLegend(legend); VersionCache = languageServer.GetRequiredService(); ProjectSnapshotManagerDispatcher = languageServer.GetRequiredService(); } diff --git a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorSemanticTokensScrollingBenchmark.cs b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorSemanticTokensScrollingBenchmark.cs index 5d52999e867..289fd5b5f41 100644 --- a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorSemanticTokensScrollingBenchmark.cs +++ b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorSemanticTokensScrollingBenchmark.cs @@ -58,7 +58,7 @@ public async Task InitializeRazorSemanticAsync() DocumentContext = new VersionedDocumentContext(documentUri, documentSnapshot, projectContext: null, version: 1); SemanticTokensLegend = new RazorSemanticTokensLegend(new VSInternalClientCapabilities() { SupportsVisualStudioExtensions = true }); - RazorSemanticTokenService.ApplyCapabilities(new(), new VSInternalClientCapabilities() { SupportsVisualStudioExtensions = true }); + RazorSemanticTokenService.SetTokensLegend(SemanticTokensLegend); var text = await DocumentSnapshot.GetTextAsync().ConfigureAwait(false); Range = new Range diff --git a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/ProjectSystem/ProjectSnapshotManagerBenchmarkBase.StaticTagHelperResolver.cs b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/ProjectSystem/ProjectSnapshotManagerBenchmarkBase.StaticTagHelperResolver.cs deleted file mode 100644 index 0ebc9127124..00000000000 --- a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/ProjectSystem/ProjectSnapshotManagerBenchmarkBase.StaticTagHelperResolver.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root for license information. - -using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Razor.Language; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; -using Microsoft.CodeAnalysis.Razor.Workspaces; - -namespace Microsoft.AspNetCore.Razor.Microbenchmarks; - -public abstract partial class ProjectSnapshotManagerBenchmarkBase -{ - private class StaticTagHelperResolver(ImmutableArray tagHelpers) : ITagHelperResolver - { - public ValueTask> GetTagHelpersAsync( - Project workspaceProject, - IProjectSnapshot projectSnapshot, - CancellationToken cancellationToken) - => new(tagHelpers); - } -} diff --git a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/ProjectSystem/ProjectSnapshotManagerBenchmarkBase.cs b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/ProjectSystem/ProjectSnapshotManagerBenchmarkBase.cs index e9ca40232c2..2f56b2a0960 100644 --- a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/ProjectSystem/ProjectSnapshotManagerBenchmarkBase.cs +++ b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/ProjectSystem/ProjectSnapshotManagerBenchmarkBase.cs @@ -8,9 +8,7 @@ using Microsoft.AspNetCore.Razor.PooledObjects; using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Razor.ProjectSystem; -using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CodeAnalysis.Text; namespace Microsoft.AspNetCore.Razor.Microbenchmarks; @@ -20,7 +18,6 @@ public abstract partial class ProjectSnapshotManagerBenchmarkBase internal HostProject HostProject { get; } internal ImmutableArray Documents { get; } internal ImmutableArray TextLoaders { get; } - internal ITagHelperResolver TagHelperResolver { get; } protected string RepoRoot { get; } protected ProjectSnapshotManagerBenchmarkBase(int documentCount = 100) @@ -59,20 +56,15 @@ protected ProjectSnapshotManagerBenchmarkBase(int documentCount = 100) } Documents = documents.ToImmutable(); - - var tagHelpers = CommonResources.LegacyTagHelpers; - TagHelperResolver = new StaticTagHelperResolver(tagHelpers); } internal DefaultProjectSnapshotManager CreateProjectSnapshotManager() { - var services = TestServices.Create( - [TagHelperResolver], - Array.Empty()); + var services = TestServices.Create([], []); return new DefaultProjectSnapshotManager( new TestErrorReporter(), - Array.Empty(), + triggers: [], #pragma warning disable CA2000 // Dispose objects before losing scope new AdhocWorkspace(services), StaticProjectEngineFactoryProvider.Instance, diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Cohost/CohostDocumentContextFactory.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Cohost/CohostDocumentContextFactory.cs index 688e905d376..c1ff2a03a67 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Cohost/CohostDocumentContextFactory.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Cohost/CohostDocumentContextFactory.cs @@ -10,6 +10,7 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Cohost; // NOTE: This is not a "normal" MEF export (ie, exporting an interface) purely because of a strange desire to keep API in // RazorCohostRequestContextExtensions looking like the previous code in the non-cohost world. +[Shared] [ExportRazorStatelessLspService(typeof(CohostDocumentContextFactory))] [method: ImportingConstructor] internal class CohostDocumentContextFactory(DocumentSnapshotFactory documentSnapshotFactory, IDocumentVersionCache documentVersionCache) : AbstractRazorLspService diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Cohost/CohostDocumentSnapshot.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Cohost/CohostDocumentSnapshot.cs index cc6f1d94a28..c44ba67ec78 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Cohost/CohostDocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Cohost/CohostDocumentSnapshot.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; @@ -36,11 +35,6 @@ internal class CohostDocumentSnapshot(TextDocument textDocument, IProjectSnapsho public bool TryGetTextVersion(out VersionStamp result) => _textDocument.TryGetTextVersion(out result); - public ImmutableArray GetImports() - { - return DocumentState.GetImportsCore(Project, FilePath.AssumeNotNull(), FileKind.AssumeNotNull()); - } - public async Task GetGeneratedOutputAsync() { // TODO: We don't need to worry about locking if we get called from the didOpen/didChange LSP requests, as CLaSP diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Common/VSInternalServerCapabilitiesExtensions.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Common/VSInternalServerCapabilitiesExtensions.cs new file mode 100644 index 00000000000..dc2c094f0c5 --- /dev/null +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Common/VSInternalServerCapabilitiesExtensions.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Microsoft.AspNetCore.Razor.LanguageServer.Common; + +internal static class VSInternalServerCapabilitiesExtensions +{ + public static void EnableDocumentColorProvider(this VSInternalServerCapabilities serverCapabilities) + { + serverCapabilities.DocumentColorProvider = new DocumentColorOptions(); + } + + public static void EnableSemanticTokens(this VSInternalServerCapabilities serverCapabilities, SemanticTokensLegend legend) + { + serverCapabilities.SemanticTokensOptions = new SemanticTokensOptions + { + Full = false, + Legend = legend, + Range = true, + }; + } +} diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DocumentColor/DocumentColorEndpoint.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DocumentColor/DocumentColorEndpoint.cs index 62be9a55deb..b0b93968c80 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DocumentColor/DocumentColorEndpoint.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DocumentColor/DocumentColorEndpoint.cs @@ -4,6 +4,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.LanguageServer.Common; using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -18,7 +19,7 @@ internal sealed class DocumentColorEndpoint(IDocumentColorService documentColorS public bool MutatesSolutionState => false; public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, VSInternalClientCapabilities clientCapabilities) - => _documentColorService.ApplyCapabilities(serverCapabilities, clientCapabilities); + => serverCapabilities.EnableDocumentColorProvider(); public TextDocumentIdentifier GetTextDocumentIdentifier(DocumentColorParams request) => request.TextDocument; diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DocumentColor/DocumentColorService.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DocumentColor/DocumentColorService.cs index 127c7d57284..6be178feae6 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DocumentColor/DocumentColorService.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DocumentColor/DocumentColorService.cs @@ -13,11 +13,6 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.DocumentColor; [method: ImportingConstructor] internal sealed class DocumentColorService() : IDocumentColorService { - public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, VSInternalClientCapabilities clientCapabilities) - { - serverCapabilities.DocumentColorProvider = new DocumentColorOptions(); - } - public async Task GetColorInformationAsync(IClientConnection clientConnection, DocumentColorParams request, VersionedDocumentContext? documentContext, CancellationToken cancellationToken) { // Workaround for Web Tools bug https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1743689 where they sometimes diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DocumentColor/IDocumentColorService.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DocumentColor/IDocumentColorService.cs index d4e266509df..3c5b7b0cb1a 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DocumentColor/IDocumentColorService.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DocumentColor/IDocumentColorService.cs @@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.DocumentColor; -internal interface IDocumentColorService : ICapabilitiesProvider +internal interface IDocumentColorService { Task GetColorInformationAsync(IClientConnection clientConnection, DocumentColorParams request, VersionedDocumentContext? documentContext, CancellationToken cancellationToken); } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServerWrapper.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServerWrapper.cs index bc6366ccf62..bed7377edca 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServerWrapper.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServerWrapper.cs @@ -77,6 +77,9 @@ private static JsonRpc CreateJsonRpc(Stream input, Stream output) var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(output, input, messageFormatter)); + // Get more information about exceptions that occur during RPC method invocations. + jsonRpc.ExceptionStrategy = ExceptionProcessing.ISerializable; + return jsonRpc; } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Semantic/SemanticTokensRangeEndpoint.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Semantic/SemanticTokensRangeEndpoint.cs index 0f2df413c1f..1c989dc428d 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Semantic/SemanticTokensRangeEndpoint.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Semantic/SemanticTokensRangeEndpoint.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.LanguageServer.Common; using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -23,7 +24,9 @@ internal sealed class SemanticTokensRangeEndpoint( public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, VSInternalClientCapabilities clientCapabilities) { - _semanticTokensInfoService.ApplyCapabilities(serverCapabilities, clientCapabilities); + var legend = new RazorSemanticTokensLegend(clientCapabilities); + serverCapabilities.EnableSemanticTokens(legend.Legend); + _semanticTokensInfoService.SetTokensLegend(legend); } public TextDocumentIdentifier GetTextDocumentIdentifier(SemanticTokensRangeParams request) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Semantic/Services/IRazorSemanticTokenInfoService.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Semantic/Services/IRazorSemanticTokenInfoService.cs index 2029c027edb..b499a047c97 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Semantic/Services/IRazorSemanticTokenInfoService.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Semantic/Services/IRazorSemanticTokenInfoService.cs @@ -7,7 +7,8 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Semantic; -internal interface IRazorSemanticTokensInfoService : ICapabilitiesProvider +internal interface IRazorSemanticTokensInfoService { Task GetSemanticTokensAsync(IClientConnection clientConnection, TextDocumentIdentifier textDocumentIdentifier, Range range, VersionedDocumentContext documentContext, bool colorBackground, CancellationToken cancellationToken); + void SetTokensLegend(RazorSemanticTokensLegend legend); } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Semantic/Services/RazorSemanticTokensInfoService.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Semantic/Services/RazorSemanticTokensInfoService.cs index a83ee92e054..769f3f39434 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Semantic/Services/RazorSemanticTokensInfoService.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Semantic/Services/RazorSemanticTokensInfoService.cs @@ -44,16 +44,10 @@ internal class RazorSemanticTokensInfoService( private RazorSemanticTokensLegend? _razorSemanticTokensLegend; - public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, VSInternalClientCapabilities clientCapabilities) + [MemberNotNull(nameof(_razorSemanticTokensLegend))] + public void SetTokensLegend(RazorSemanticTokensLegend legend) { - _razorSemanticTokensLegend = new RazorSemanticTokensLegend(clientCapabilities); - - serverCapabilities.SemanticTokensOptions = new SemanticTokensOptions - { - Full = false, - Legend = _razorSemanticTokensLegend.Legend, - Range = true, - }; + _razorSemanticTokensLegend = legend; } public async Task GetSemanticTokensAsync( diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ITagHelperResolver.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ITagHelperResolver.cs index 62748deaa5e..34716da3f88 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ITagHelperResolver.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ITagHelperResolver.cs @@ -5,12 +5,11 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Razor.ProjectSystem; namespace Microsoft.CodeAnalysis.Razor.Workspaces; -internal interface ITagHelperResolver : IWorkspaceService +internal interface ITagHelperResolver { ValueTask> GetTagHelpersAsync( Project workspaceProject, diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs index 760db0c5edd..e43ca72a06c 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System; -using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; @@ -27,9 +26,6 @@ public DocumentSnapshot(ProjectSnapshot project, DocumentState state) State = state ?? throw new ArgumentNullException(nameof(state)); } - public virtual ImmutableArray GetImports() - => State.GetImports(ProjectInternal); - public Task GetTextAsync() => State.GetTextAsync(); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs index c8e5874a99b..7a902d4ed46 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.PooledObjects; using Microsoft.CodeAnalysis.Text; @@ -80,11 +81,6 @@ private ComputedStateTracker ComputedState return ComputedState.GetGeneratedOutputAndVersionAsync(project, document); } - public ImmutableArray GetImports(ProjectSnapshot project) - { - return GetImportsCore(project, HostDocument.FilePath, HostDocument.FileKind); - } - public async Task GetTextAsync() { if (TryGetText(out var text)) @@ -502,7 +498,7 @@ private static async Task GetRazorSourceDocumentAsync(IDocu internal static async Task> GetImportsAsync(IDocumentSnapshot document) { - var imports = document.GetImports(); + var imports = DocumentState.GetImportsCore(document.Project, document.FilePath.AssumeNotNull(), document.FileKind.AssumeNotNull()); using var result = new PooledArrayBuilder(imports.Length); foreach (var snapshot in imports) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshot.cs index 0e7e9f626fa..c1306beee09 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshot.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; @@ -17,8 +16,6 @@ internal interface IDocumentSnapshot IProjectSnapshot Project { get; } bool SupportsOutput { get; } - ImmutableArray GetImports(); - Task GetTextAsync(); Task GetTextVersionAsync(); Task GetGeneratedOutputAsync(); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs index f923e1140d8..68f7b345d16 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System; -using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading.Tasks; @@ -36,9 +35,6 @@ public ImportDocumentSnapshot(IProjectSnapshot project, RazorProjectItem item) public Task GetGeneratedOutputAsync() => throw new NotSupportedException(); - public ImmutableArray GetImports() - => ImmutableArray.Empty; - public async Task GetTextAsync() { using (var stream = _importItem.Read()) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectWorkspaceStateGenerator.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectWorkspaceStateGenerator.cs index 6d019bcb0d0..717303a71b7 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectWorkspaceStateGenerator.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectWorkspaceStateGenerator.cs @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -#nullable disable - using System; using System.Collections.Generic; using System.Composition; @@ -20,43 +18,34 @@ namespace Microsoft.CodeAnalysis.Razor; [Shared] [Export(typeof(IProjectWorkspaceStateGenerator))] -[Export(typeof(IProjectSnapshotChangeTrigger))] [method: ImportingConstructor] internal sealed class ProjectWorkspaceStateGenerator( + IProjectSnapshotManagerAccessor projectManagerAccessor, + ITagHelperResolver tagHelperResolver, ProjectSnapshotManagerDispatcher dispatcher, + IErrorReporter errorReporter, ITelemetryReporter telemetryReporter) - : IProjectWorkspaceStateGenerator, IProjectSnapshotChangeTrigger, IDisposable + : IProjectWorkspaceStateGenerator, IDisposable { // Internal for testing internal readonly Dictionary Updates = new(); - private readonly ProjectSnapshotManagerDispatcher _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); - private readonly ITelemetryReporter _telemetryReporter = telemetryReporter ?? throw new ArgumentNullException(nameof(telemetryReporter)); + private readonly IProjectSnapshotManagerAccessor _projectManagerAccessor = projectManagerAccessor; + private readonly ITagHelperResolver _tagHelperResolver = tagHelperResolver; + private readonly ProjectSnapshotManagerDispatcher _dispatcher = dispatcher; + private readonly IErrorReporter _errorReporter = errorReporter; + private readonly ITelemetryReporter _telemetryReporter = telemetryReporter; private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(initialCount: 1); - private ProjectSnapshotManagerBase _projectManager; - private ITagHelperResolver _tagHelperResolver; private bool _disposed; // Used in unit tests to ensure we can control when background work starts. - public ManualResetEventSlim BlockBackgroundWorkStart { get; set; } + public ManualResetEventSlim? BlockBackgroundWorkStart { get; set; } // Used in unit tests to ensure we can know when background work finishes. - public ManualResetEventSlim NotifyBackgroundWorkCompleted { get; set; } + public ManualResetEventSlim? NotifyBackgroundWorkCompleted { get; set; } - public void Initialize(ProjectSnapshotManagerBase projectManager) - { - if (projectManager is null) - { - throw new ArgumentNullException(nameof(projectManager)); - } - - _projectManager = projectManager; - - _tagHelperResolver = _projectManager.Workspace.Services.GetRequiredService(); - } - - public void Update(Project workspaceProject, IProjectSnapshot projectSnapshot, CancellationToken cancellationToken) + public void Update(Project? workspaceProject, IProjectSnapshot projectSnapshot, CancellationToken cancellationToken) { if (projectSnapshot is null) { @@ -119,7 +108,7 @@ public void Dispose() BlockBackgroundWorkStart?.Set(); } - private async Task UpdateWorkspaceStateAsync(Project workspaceProject, IProjectSnapshot projectSnapshot, CancellationToken cancellationToken) + private async Task UpdateWorkspaceStateAsync(Project? workspaceProject, IProjectSnapshot projectSnapshot, CancellationToken cancellationToken) { // We fire this up on a background thread so we could have been disposed already, and if so, waiting on our semaphore // throws an exception. @@ -163,7 +152,7 @@ private async Task UpdateWorkspaceStateAsync(Project workspaceProject, IProjectS if (workspaceProject != null) { var csharpLanguageVersion = LanguageVersion.Default; - var csharpParseOptions = (CSharpParseOptions)workspaceProject.ParseOptions; + var csharpParseOptions = workspaceProject.ParseOptions as CSharpParseOptions; if (csharpParseOptions is null) { Debug.Fail("Workspace project should always have CSharp parse options."); @@ -203,7 +192,7 @@ private async Task UpdateWorkspaceStateAsync(Project workspaceProject, IProjectS new Property("result", "error")); await _dispatcher.RunOnDispatcherThreadAsync( - () => _projectManager.ReportError(ex, projectSnapshot), + () => _errorReporter.ReportError(ex, projectSnapshot), // Don't allow errors to be cancelled CancellationToken.None).ConfigureAwait(false); return; @@ -236,7 +225,7 @@ await _dispatcher.RunOnDispatcherThreadAsync( { // This is something totally unexpected, let's just send it over to the project manager. await _dispatcher.RunOnDispatcherThreadAsync( - () => _projectManager.ReportError(ex), + () => _errorReporter.ReportError(ex), // Don't allow errors to be cancelled CancellationToken.None).ConfigureAwait(false); } @@ -264,7 +253,7 @@ private void ReportWorkspaceStateChange(ProjectKey projectKey, ProjectWorkspaceS { _dispatcher.AssertDispatcherThread(); - _projectManager.ProjectWorkspaceStateChanged(projectKey, workspaceStateChange); + _projectManagerAccessor.Instance.ProjectWorkspaceStateChanged(projectKey, workspaceStateChange); } private void OnStartingBackgroundWork() @@ -285,26 +274,10 @@ private void OnBackgroundWorkCompleted() } // Internal for testing - internal class UpdateItem + internal class UpdateItem(Task task, CancellationTokenSource cts) { - public UpdateItem(Task task, CancellationTokenSource cts) - { - if (task is null) - { - throw new ArgumentNullException(nameof(task)); - } - - if (cts is null) - { - throw new ArgumentNullException(nameof(cts)); - } - - Task = task; - Cts = cts; - } - - public Task Task { get; } + public Task Task { get; } = task; - public CancellationTokenSource Cts { get; } + public CancellationTokenSource Cts { get; } = cts; } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperResolverFactory.Resolver.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperResolverFactory.Resolver.cs deleted file mode 100644 index 0500776726d..00000000000 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperResolverFactory.Resolver.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root for license information. - -using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Razor; -using Microsoft.AspNetCore.Razor.Language; -using Microsoft.AspNetCore.Razor.Telemetry; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; -using Microsoft.CodeAnalysis.Razor.Workspaces; - -namespace Microsoft.CodeAnalysis.Razor; - -internal partial class TagHelperResolverFactory -{ - private sealed class Resolver(ITelemetryReporter telemetryReporter) : ITagHelperResolver - { - private readonly CompilationTagHelperResolver _resolver = new(telemetryReporter); - - public ValueTask> GetTagHelpersAsync( - Project workspaceProject, - IProjectSnapshot projectSnapshot, - CancellationToken cancellationToken) - => _resolver.GetTagHelpersAsync(workspaceProject, projectSnapshot.GetProjectEngine(), cancellationToken); - } -} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperResolverFactory.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperResolverFactory.cs deleted file mode 100644 index 7cd07d1e6b4..00000000000 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperResolverFactory.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root for license information. - -using System.Composition; -using Microsoft.AspNetCore.Razor.Telemetry; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Razor.Workspaces; - -namespace Microsoft.CodeAnalysis.Razor; - -[Shared] -[ExportWorkspaceServiceFactory(typeof(ITagHelperResolver), ServiceLayer.Default)] -[method: ImportingConstructor] -internal partial class TagHelperResolverFactory(ITelemetryReporter telemetryReporter) : IWorkspaceServiceFactory -{ - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new Resolver(telemetryReporter); -} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/OOPTagHelperResolverFactory.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/OOPTagHelperResolverFactory.cs deleted file mode 100644 index 8197dae7862..00000000000 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/OOPTagHelperResolverFactory.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root for license information. - -using System.Composition; -using Microsoft.AspNetCore.Razor.Telemetry; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Razor; -using Microsoft.CodeAnalysis.Razor.Workspaces; - -namespace Microsoft.CodeAnalysis.Remote.Razor; - -[Shared] -[ExportWorkspaceServiceFactory(typeof(ITagHelperResolver), ServiceLayer.Host)] -[method: ImportingConstructor] -internal class OOPTagHelperResolverFactory( - IErrorReporter errorReporter, - ITelemetryReporter telemetryReporter) : IWorkspaceServiceFactory -{ - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new OOPTagHelperResolver( - workspaceServices.Workspace, - errorReporter, - telemetryReporter); -} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/OOPTagHelperResolver.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/OutOfProcTagHelperResolver.cs similarity index 91% rename from src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/OOPTagHelperResolver.cs rename to src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/OutOfProcTagHelperResolver.cs index 0b63a48cfbb..7d381c61b70 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/OOPTagHelperResolver.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/OutOfProcTagHelperResolver.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Immutable; +using System.ComponentModel.Composition; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor; @@ -18,21 +19,17 @@ namespace Microsoft.CodeAnalysis.Remote.Razor; -internal class OOPTagHelperResolver : ITagHelperResolver +[Export(typeof(ITagHelperResolver))] +[method: ImportingConstructor] +internal class OutOfProcTagHelperResolver( + IProjectSnapshotManagerAccessor projectManagerAccessor, + IErrorReporter errorReporter, + ITelemetryReporter telemetryReporter) : ITagHelperResolver { - private readonly Workspace _workspace; - private readonly IErrorReporter _errorReporter; - private readonly CompilationTagHelperResolver _innerResolver; - private readonly TagHelperResultCache _resultCache; - - public OOPTagHelperResolver(Workspace workspace, IErrorReporter errorReporter, ITelemetryReporter telemetryReporter) - { - _errorReporter = errorReporter ?? throw new ArgumentNullException(nameof(errorReporter)); - _workspace = workspace ?? throw new ArgumentNullException(nameof(workspace)); - - _innerResolver = new CompilationTagHelperResolver(telemetryReporter); - _resultCache = new TagHelperResultCache(); - } + private readonly IProjectSnapshotManagerAccessor _projectManagerAccessor = projectManagerAccessor; + private readonly IErrorReporter _errorReporter = errorReporter; + private readonly CompilationTagHelperResolver _innerResolver = new(telemetryReporter); + private readonly TagHelperResultCache _resultCache = new(); public async ValueTask> GetTagHelpersAsync( Project workspaceProject, @@ -75,7 +72,7 @@ protected virtual async ValueTask> ResolveTa // // This will change in the future to an easier to consume API but for VS RTM this is what we have. var remoteClient = await RazorRemoteHostClient.TryGetClientAsync( - _workspace.Services, + _projectManagerAccessor.Instance.Workspace.Services, RazorServiceDescriptors.TagHelperProviderServiceDescriptors, RazorRemoteServiceCallbackDispatcherRegistry.Empty, cancellationToken); diff --git a/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs b/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs index b37a0eaaca6..7b1d6b3534e 100644 --- a/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs +++ b/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.Editor; using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Threading; @@ -24,7 +25,7 @@ internal class DefaultVisualStudioDocumentTracker : VisualStudioDocumentTracker private readonly JoinableTaskContext _joinableTaskContext; private readonly string _filePath; private readonly string _projectPath; - private readonly ProjectSnapshotManager _projectManager; + private readonly IProjectSnapshotManagerAccessor _projectManagerAccessor; private readonly WorkspaceEditorSettings _workspaceEditorSettings; private readonly ITextBuffer _textBuffer; private readonly ImportDocumentManager _importDocumentManager; @@ -41,7 +42,7 @@ public DefaultVisualStudioDocumentTracker( JoinableTaskContext joinableTaskContext, string filePath, string projectPath, - ProjectSnapshotManager projectManager, + IProjectSnapshotManagerAccessor projectManagerAccessor, WorkspaceEditorSettings workspaceEditorSettings, IProjectEngineFactoryProvider projectEngineFactoryProvider, ITextBuffer textBuffer, @@ -67,9 +68,9 @@ public DefaultVisualStudioDocumentTracker( throw new ArgumentNullException(nameof(projectPath)); } - if (projectManager is null) + if (projectManagerAccessor is null) { - throw new ArgumentNullException(nameof(projectManager)); + throw new ArgumentNullException(nameof(projectManagerAccessor)); } if (workspaceEditorSettings is null) @@ -96,7 +97,7 @@ public DefaultVisualStudioDocumentTracker( _joinableTaskContext = joinableTaskContext; _filePath = filePath; _projectPath = projectPath; - _projectManager = projectManager; + _projectManagerAccessor = projectManagerAccessor; _workspaceEditorSettings = workspaceEditorSettings; _textBuffer = textBuffer; _importDocumentManager = importDocumentManager; @@ -180,7 +181,7 @@ public void Subscribe() _projectSnapshot = GetOrCreateProject(_projectPath); _isSupportedProject = true; - _projectManager.Changed += ProjectManager_Changed; + _projectManagerAccessor.Instance.Changed += ProjectManager_Changed; _workspaceEditorSettings.Changed += EditorSettingsManager_Changed; _importDocumentManager.Changed += Import_Changed; @@ -193,9 +194,11 @@ public void Subscribe() { _dispatcher.AssertDispatcherThread(); - var projectKey = _projectManager.GetAllProjectKeys(projectPath).FirstOrDefault(); + var projectManager = _projectManagerAccessor.Instance; - if (_projectManager.GetLoadedProject(projectKey) is not { } project) + var projectKey = projectManager.GetAllProjectKeys(projectPath).FirstOrDefault(); + + if (projectManager.GetLoadedProject(projectKey) is not { } project) { return new EphemeralProjectSnapshot(_projectEngineFactoryProvider, projectPath); } @@ -214,7 +217,7 @@ public void Unsubscribe() _importDocumentManager.OnUnsubscribed(this); - _projectManager.Changed -= ProjectManager_Changed; + _projectManagerAccessor.Instance.Changed -= ProjectManager_Changed; _workspaceEditorSettings.Changed -= EditorSettingsManager_Changed; _importDocumentManager.Changed -= Import_Changed; @@ -246,7 +249,7 @@ internal void ProjectManager_Changed(object sender, ProjectChangeEventArgs e) string.Equals(_projectPath, e.ProjectFilePath, StringComparison.OrdinalIgnoreCase)) { // This will be the new snapshot unless the project was removed. - _projectSnapshot = _projectManager.GetLoadedProject(e.ProjectKey); + _projectSnapshot = _projectManagerAccessor.Instance.GetLoadedProject(e.ProjectKey); switch (e.Kind) { diff --git a/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTrackerFactory.cs b/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTrackerFactory.cs index c23fb142b2a..32a4f70d686 100644 --- a/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTrackerFactory.cs +++ b/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTrackerFactory.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Razor.ProjectEngineHost; using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.Editor; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Threading; @@ -19,14 +19,14 @@ internal class DefaultVisualStudioDocumentTrackerFactory : VisualStudioDocumentT private readonly ITextDocumentFactoryService _textDocumentFactory; private readonly ProjectPathProvider _projectPathProvider; private readonly ImportDocumentManager _importDocumentManager; - private readonly ProjectSnapshotManager _projectManager; + private readonly IProjectSnapshotManagerAccessor _projectManagerAccessor; private readonly WorkspaceEditorSettings _workspaceEditorSettings; private readonly IProjectEngineFactoryProvider _projectEngineFactoryProvider; public DefaultVisualStudioDocumentTrackerFactory( ProjectSnapshotManagerDispatcher dispatcher, JoinableTaskContext joinableTaskContext, - ProjectSnapshotManager projectManager, + IProjectSnapshotManagerAccessor projectManagerAccessor, WorkspaceEditorSettings workspaceEditorSettings, ProjectPathProvider projectPathProvider, ITextDocumentFactoryService textDocumentFactory, @@ -35,7 +35,7 @@ public DefaultVisualStudioDocumentTrackerFactory( { _dispatcher = dispatcher; _joinableTaskContext = joinableTaskContext; - _projectManager = projectManager; + _projectManagerAccessor = projectManagerAccessor; _workspaceEditorSettings = workspaceEditorSettings; _projectPathProvider = projectPathProvider; _textDocumentFactory = textDocumentFactory; @@ -63,7 +63,7 @@ public DefaultVisualStudioDocumentTrackerFactory( var filePath = textDocument.FilePath; var tracker = new DefaultVisualStudioDocumentTracker( - _dispatcher, _joinableTaskContext, filePath, projectPath, _projectManager, _workspaceEditorSettings, _projectEngineFactoryProvider, textBuffer, _importDocumentManager); + _dispatcher, _joinableTaskContext, filePath, projectPath, _projectManagerAccessor, _workspaceEditorSettings, _projectEngineFactoryProvider, textBuffer, _importDocumentManager); return tracker; } diff --git a/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTrackerFactoryFactory.cs b/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTrackerFactoryFactory.cs index fb07e9a8bda..a619c0913bd 100644 --- a/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTrackerFactoryFactory.cs +++ b/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTrackerFactoryFactory.cs @@ -7,7 +7,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.Editor; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Threading; @@ -19,12 +19,12 @@ namespace Microsoft.VisualStudio.Editor.Razor; internal class DefaultVisualStudioDocumentTrackerFactoryFactory( ProjectSnapshotManagerDispatcher dispatcher, JoinableTaskContext joinableTaskContext, + IProjectSnapshotManagerAccessor projectManagerAccessor, ITextDocumentFactoryService textDocumentFactory, IProjectEngineFactoryProvider projectEngineFactoryProvider) : ILanguageServiceFactory { public ILanguageService CreateLanguageService(HostLanguageServices languageServices) { - var projectManager = languageServices.GetRequiredService(); var workspaceEditorSettings = languageServices.GetRequiredService(); var importDocumentManager = languageServices.GetRequiredService(); @@ -33,7 +33,7 @@ public ILanguageService CreateLanguageService(HostLanguageServices languageServi return new DefaultVisualStudioDocumentTrackerFactory( dispatcher, joinableTaskContext, - projectManager, + projectManagerAccessor, workspaceEditorSettings, projectPathProvider, textDocumentFactory, diff --git a/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParser.cs b/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParser.cs index ef800e5ef15..e3f31a0c0b4 100644 --- a/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParser.cs +++ b/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParser.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.Editor; using Microsoft.Extensions.Internal; +using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Threading; using static Microsoft.VisualStudio.Editor.Razor.BackgroundParser; @@ -36,7 +37,7 @@ internal class DefaultVisualStudioRazorParser : VisualStudioRazorParser, IDispos private readonly object _idleLock = new(); private readonly object _updateStateLock = new(); - private readonly VisualStudioCompletionBroker _completionBroker; + private readonly ICompletionBroker _completionBroker; private readonly VisualStudioDocumentTracker _documentTracker; private readonly JoinableTaskContext _joinableTaskContext; private readonly IProjectEngineFactoryProvider _projectEngineFactoryProvider; @@ -63,7 +64,7 @@ public DefaultVisualStudioRazorParser( VisualStudioDocumentTracker documentTracker, IProjectEngineFactoryProvider projectEngineFactoryProvider, IErrorReporter errorReporter, - VisualStudioCompletionBroker completionBroker) + ICompletionBroker completionBroker) { if (joinableTaskContext is null) { diff --git a/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParserFactory.cs b/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParserFactory.cs index b4d6136d0d7..2e4b2f64234 100644 --- a/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParserFactory.cs +++ b/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParserFactory.cs @@ -4,6 +4,7 @@ using System; using Microsoft.AspNetCore.Razor.ProjectEngineHost; using Microsoft.CodeAnalysis.Razor; +using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Threading; namespace Microsoft.VisualStudio.Editor.Razor; @@ -11,12 +12,12 @@ namespace Microsoft.VisualStudio.Editor.Razor; internal class DefaultVisualStudioRazorParserFactory( JoinableTaskContext joinableTaskContext, IErrorReporter errorReporter, - VisualStudioCompletionBroker completionBroker, + ICompletionBroker completionBroker, IProjectEngineFactoryProvider projectEngineFactoryProvider) : VisualStudioRazorParserFactory { private readonly JoinableTaskContext _joinableTaskContext = joinableTaskContext; private readonly IProjectEngineFactoryProvider _projectEngineFactoryProvider = projectEngineFactoryProvider; - private readonly VisualStudioCompletionBroker _completionBroker = completionBroker; + private readonly ICompletionBroker _completionBroker = completionBroker; private readonly IErrorReporter _errorReporter = errorReporter; public override VisualStudioRazorParser Create(VisualStudioDocumentTracker documentTracker) diff --git a/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParserFactoryFactory.cs b/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParserFactoryFactory.cs index 9868acb6986..add91340fb1 100644 --- a/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParserFactoryFactory.cs +++ b/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParserFactoryFactory.cs @@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Razor; +using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Threading; namespace Microsoft.VisualStudio.Editor.Razor; @@ -16,16 +17,13 @@ namespace Microsoft.VisualStudio.Editor.Razor; internal class DefaultVisualStudioRazorParserFactoryFactory( JoinableTaskContext joinableTaskContext, IProjectEngineFactoryProvider projectEngineFactoryProvider, + ICompletionBroker completionBroker, IErrorReporter errorReporter) : ILanguageServiceFactory { public ILanguageService CreateLanguageService(HostLanguageServices languageServices) - { - var completionBroker = languageServices.GetRequiredService(); - - return new DefaultVisualStudioRazorParserFactory( + => new DefaultVisualStudioRazorParserFactory( joinableTaskContext, errorReporter, completionBroker, projectEngineFactoryProvider); - } } diff --git a/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/Documents/DefaultFileChangeTrackerFactory.cs b/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/Documents/DefaultFileChangeTrackerFactory.cs index dc2c0d32370..03c51bc456f 100644 --- a/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/Documents/DefaultFileChangeTrackerFactory.cs +++ b/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/Documents/DefaultFileChangeTrackerFactory.cs @@ -2,10 +2,12 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System; +using System.Composition; using Microsoft.CodeAnalysis.Host.Mef; namespace Microsoft.VisualStudio.Editor.Razor.Documents; +[Shared] [ExportWorkspaceService(typeof(FileChangeTrackerFactory), layer: ServiceLayer.Editor)] internal class DefaultFileChangeTrackerFactory : FileChangeTrackerFactory { diff --git a/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/Documents/EditorDocumentManagerListener.cs b/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/Documents/EditorDocumentManagerListener.cs index 51747b710dd..59b438370c4 100644 --- a/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/Documents/EditorDocumentManagerListener.cs +++ b/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/Documents/EditorDocumentManagerListener.cs @@ -24,6 +24,7 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents; // other triggers with lesser priority so we can attach to Changed sooner. We happen to be so important because we control the // open/close state of documents. If other triggers depend on a document being open/closed (some do) then we need to ensure we // can mark open/closed prior to them running. +[Shared] [Export(typeof(IProjectSnapshotChangeTrigger))] internal class EditorDocumentManagerListener : IPriorityProjectSnapshotChangeTrigger { diff --git a/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/VisualStudioCompletionBroker.cs b/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/VisualStudioCompletionBroker.cs deleted file mode 100644 index 30e5f5b1cc8..00000000000 --- a/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/VisualStudioCompletionBroker.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root for license information. - -using Microsoft.CodeAnalysis.Host; -using Microsoft.VisualStudio.Text.Editor; - -namespace Microsoft.VisualStudio.Editor.Razor; - -internal abstract class VisualStudioCompletionBroker : ILanguageService -{ - public abstract bool IsCompletionActive(ITextView textView); -} diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/Cohost/CohostDocumentColorEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/Cohost/CohostDocumentColorEndpoint.cs index eac4ecd4b01..491ef208dae 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/Cohost/CohostDocumentColorEndpoint.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/Cohost/CohostDocumentColorEndpoint.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.LanguageServer; +using Microsoft.AspNetCore.Razor.LanguageServer.Common; using Microsoft.AspNetCore.Razor.LanguageServer.DocumentColor; using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; using Microsoft.CodeAnalysis.Razor.Logging; @@ -14,6 +15,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.Cohost; +[Shared] [RazorLanguageServerEndpoint(Methods.TextDocumentDocumentColorName)] [ExportRazorStatelessLspService(typeof(CohostDocumentColorEndpoint))] [Export(typeof(ICapabilitiesProvider))] @@ -32,8 +34,8 @@ internal sealed class CohostDocumentColorEndpoint( protected override RazorTextDocumentIdentifier? GetRazorTextDocumentIdentifier(DocumentColorParams request) => request.TextDocument.ToRazorTextDocumentIdentifier(); - public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, VSInternalClientCapabilities clientCapabilities) - => _documentColorService.ApplyCapabilities(serverCapabilities, clientCapabilities); + public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, VSInternalClientCapabilities _) + => serverCapabilities.EnableDocumentColorProvider(); protected override Task HandleRequestAsync(DocumentColorParams request, RazorCohostRequestContext context, CancellationToken cancellationToken) { diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/Cohost/CohostSemanticTokensRangeEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/Cohost/CohostSemanticTokensRangeEndpoint.cs index 70a5716d7e2..c5333ae5d25 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/Cohost/CohostSemanticTokensRangeEndpoint.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/Cohost/CohostSemanticTokensRangeEndpoint.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.LanguageServer; +using Microsoft.AspNetCore.Razor.LanguageServer.Common; using Microsoft.AspNetCore.Razor.LanguageServer.Semantic; using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; using Microsoft.CodeAnalysis.Razor.Logging; @@ -15,6 +16,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.Cohost; +[Shared] [RazorLanguageServerEndpoint(Methods.TextDocumentSemanticTokensRangeName)] [ExportRazorStatelessLspService(typeof(CohostSemanticTokensRangeEndpoint))] [Export(typeof(ICapabilitiesProvider))] @@ -36,7 +38,11 @@ internal sealed class CohostSemanticTokensRangeEndpoint( => request.TextDocument.ToRazorTextDocumentIdentifier(); public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, VSInternalClientCapabilities clientCapabilities) - => _semanticTokensInfoService.ApplyCapabilities(serverCapabilities, clientCapabilities); + { + var legend = new RazorSemanticTokensLegend(clientCapabilities); + serverCapabilities.EnableSemanticTokens(legend.Legend); + _semanticTokensInfoService.SetTokensLegend(legend); + } protected override Task HandleRequestAsync(SemanticTokensRangeParams request, RazorCohostRequestContext context, CancellationToken cancellationToken) { diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorCustomMessageTarget.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorCustomMessageTarget.cs index 866e04dd4b5..1148b687333 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorCustomMessageTarget.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorCustomMessageTarget.cs @@ -25,6 +25,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor; +[Shared] [Export(typeof(IRazorCustomMessageTarget))] [Export(typeof(RazorCustomMessageTarget))] internal partial class RazorCustomMessageTarget : IRazorCustomMessageTarget diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Editor/DefaultVisualStudioCompletionBroker.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Editor/DefaultVisualStudioCompletionBroker.cs deleted file mode 100644 index c1a09be5391..00000000000 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Editor/DefaultVisualStudioCompletionBroker.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root for license information. - -using System; -using Microsoft.VisualStudio.Editor.Razor; -using Microsoft.VisualStudio.Language.Intellisense; -using Microsoft.VisualStudio.Text.Editor; - -namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor; - -internal class DefaultVisualStudioCompletionBroker : VisualStudioCompletionBroker -{ - private readonly ICompletionBroker _completionBroker; - - public DefaultVisualStudioCompletionBroker(ICompletionBroker completionBroker) - { - if (completionBroker is null) - { - throw new ArgumentNullException(nameof(completionBroker)); - } - - _completionBroker = completionBroker; - } - - public override bool IsCompletionActive(ITextView textView) - { - if (textView is null) - { - throw new ArgumentNullException(nameof(textView)); - } - - var completionIsActive = _completionBroker.IsCompletionActive(textView); - return completionIsActive; - } -} diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Editor/DefaultVisualStudioCompletionBrokerFactory.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Editor/DefaultVisualStudioCompletionBrokerFactory.cs deleted file mode 100644 index faac1fc0199..00000000000 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Editor/DefaultVisualStudioCompletionBrokerFactory.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root for license information. - -using System; -using System.Composition; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Razor; -using Microsoft.VisualStudio.Editor.Razor; -using Microsoft.VisualStudio.Language.Intellisense; - -namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor; - -[Shared] -[ExportLanguageServiceFactory(typeof(VisualStudioCompletionBroker), RazorLanguage.Name, ServiceLayer.Default)] -internal class DefaultVisualStudioCompletionBrokerFactory : ILanguageServiceFactory -{ - private readonly ICompletionBroker _completionBroker; - - [ImportingConstructor] - public DefaultVisualStudioCompletionBrokerFactory(ICompletionBroker completionBroker) - { - if (completionBroker is null) - { - throw new ArgumentNullException(nameof(completionBroker)); - } - - _completionBroker = completionBroker; - } - - public ILanguageService CreateLanguageService(HostLanguageServices languageServices) - { - if (languageServices is null) - { - throw new ArgumentNullException(nameof(languageServices)); - } - - return new DefaultVisualStudioCompletionBroker(_completionBroker); - } -} diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultWindowsRazorProjectHost.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultWindowsRazorProjectHost.cs index c43acacdfe8..fe9f98b5670 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultWindowsRazorProjectHost.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultWindowsRazorProjectHost.cs @@ -12,7 +12,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.Razor.Workspaces; -using Microsoft.VisualStudio.LanguageServices; using Microsoft.VisualStudio.ProjectSystem; using Microsoft.VisualStudio.ProjectSystem.Properties; using Item = System.Collections.Generic.KeyValuePair>; @@ -35,33 +34,20 @@ internal class DefaultWindowsRazorProjectHost : WindowsRazorProjectHostBase Rules.RazorGenerateWithTargetPath.SchemaName, ConfigurationGeneralSchemaName, }); - private readonly LanguageServerFeatureOptions _languageServerFeatureOptions; + private readonly LanguageServerFeatureOptions? _languageServerFeatureOptions; [ImportingConstructor] public DefaultWindowsRazorProjectHost( IUnconfiguredProjectCommonServices commonServices, - [Import(typeof(VisualStudioWorkspace))] Workspace workspace, - ProjectSnapshotManagerDispatcher projectSnapshotManagerDispatcher, + IProjectSnapshotManagerAccessor projectManagerAccessor, + ProjectSnapshotManagerDispatcher dispatcher, ProjectConfigurationFilePathStore projectConfigurationFilePathStore, - LanguageServerFeatureOptions languageServerFeatureOptions) - : base(commonServices, workspace, projectSnapshotManagerDispatcher, projectConfigurationFilePathStore) + LanguageServerFeatureOptions? languageServerFeatureOptions) + : base(commonServices, projectManagerAccessor, dispatcher, projectConfigurationFilePathStore) { _languageServerFeatureOptions = languageServerFeatureOptions; } - // Internal for testing -#pragma warning disable CS8618 // Non-nullable variable must contain a non-null value when exiting constructor. Consider declaring it as nullable. - internal DefaultWindowsRazorProjectHost( -#pragma warning restore CS8618 // Non-nullable variable must contain a non-null value when exiting constructor. Consider declaring it as nullable. - IUnconfiguredProjectCommonServices commonServices, - Workspace workspace, - ProjectSnapshotManagerDispatcher projectSnapshotManagerDispatcher, - ProjectConfigurationFilePathStore projectConfigurationFilePathStore, - ProjectSnapshotManagerBase projectManager) - : base(commonServices, workspace, projectSnapshotManagerDispatcher, projectConfigurationFilePathStore, projectManager) - { - } - protected override ImmutableHashSet GetRuleNames() => s_ruleNames; protected override async Task HandleProjectChangeAsync(string sliceDimensions, IProjectVersionedValue update) diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/FallbackWindowsRazorProjectHost.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/FallbackWindowsRazorProjectHost.cs index 9ba9e2631d8..1aa509c98da 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/FallbackWindowsRazorProjectHost.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/FallbackWindowsRazorProjectHost.cs @@ -14,7 +14,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.Razor.Workspaces; -using Microsoft.VisualStudio.LanguageServices; using Microsoft.VisualStudio.ProjectSystem; using ContentItem = Microsoft.CodeAnalysis.Razor.ProjectSystem.ManagedProjectSystemSchema.ContentItem; using ItemReference = Microsoft.CodeAnalysis.Razor.ProjectSystem.ManagedProjectSystemSchema.ItemReference; @@ -37,33 +36,20 @@ internal class FallbackWindowsRazorProjectHost : WindowsRazorProjectHostBase NoneItem.SchemaName, ConfigurationGeneralSchemaName, }); - private readonly LanguageServerFeatureOptions _languageServerFeatureOptions; + private readonly LanguageServerFeatureOptions? _languageServerFeatureOptions; [ImportingConstructor] public FallbackWindowsRazorProjectHost( IUnconfiguredProjectCommonServices commonServices, - [Import(typeof(VisualStudioWorkspace))] Workspace workspace, - ProjectSnapshotManagerDispatcher projectSnapshotManagerDispatcher, + IProjectSnapshotManagerAccessor projectManagerAccessor, + ProjectSnapshotManagerDispatcher dispatcher, ProjectConfigurationFilePathStore projectConfigurationFilePathStore, - LanguageServerFeatureOptions languageServerFeatureOptions) - : base(commonServices, workspace, projectSnapshotManagerDispatcher, projectConfigurationFilePathStore) + LanguageServerFeatureOptions? languageServerFeatureOptions) + : base(commonServices, projectManagerAccessor, dispatcher, projectConfigurationFilePathStore) { _languageServerFeatureOptions = languageServerFeatureOptions; } - // Internal for testing -#pragma warning disable CS8618 // Non-nullable variable must contain a non-null value when exiting constructor. Consider declaring it as nullable. - internal FallbackWindowsRazorProjectHost( -#pragma warning restore CS8618 // Non-nullable variable must contain a non-null value when exiting constructor. Consider declaring it as nullable. - IUnconfiguredProjectCommonServices commonServices, - Workspace workspace, - ProjectSnapshotManagerDispatcher projectSnapshotManagerDispatcher, - ProjectConfigurationFilePathStore projectConfigurationFilePathStore, - ProjectSnapshotManagerBase projectManager) - : base(commonServices, workspace, projectSnapshotManagerDispatcher, projectConfigurationFilePathStore, projectManager) - { - } - protected override ImmutableHashSet GetRuleNames() => s_ruleNames; protected override async Task HandleProjectChangeAsync(string sliceDimensions, IProjectVersionedValue update) diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/WindowsRazorProjectHostBase.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/WindowsRazorProjectHostBase.cs index 430b33b569a..d14cba2c7a4 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/WindowsRazorProjectHostBase.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/WindowsRazorProjectHostBase.cs @@ -4,14 +4,13 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.ComponentModel.Composition; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; using Microsoft.AspNetCore.Razor; -using Microsoft.VisualStudio.LanguageServices; +using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.VisualStudio.ProjectSystem; using Microsoft.VisualStudio.ProjectSystem.Properties; using Microsoft.VisualStudio.Threading; @@ -22,8 +21,8 @@ internal abstract class WindowsRazorProjectHostBase : OnceInitializedOnceDispose { private static readonly DataflowLinkOptions s_dataflowLinkOptions = new DataflowLinkOptions() { PropagateCompletion = true }; - private readonly Workspace _workspace; - private readonly ProjectSnapshotManagerDispatcher _projectSnapshotManagerDispatcher; + private readonly IProjectSnapshotManagerAccessor _projectManagerAccessor; + private readonly ProjectSnapshotManagerDispatcher _dispatcher; private readonly AsyncSemaphore _lock; private ProjectSnapshotManagerBase? _projectManager; @@ -41,10 +40,10 @@ internal abstract class WindowsRazorProjectHostBase : OnceInitializedOnceDispose // 250ms between publishes to prevent bursts of changes yet still be responsive to changes. internal int EnqueueDelay { get; set; } = 250; - public WindowsRazorProjectHostBase( + protected WindowsRazorProjectHostBase( IUnconfiguredProjectCommonServices commonServices, - [Import(typeof(VisualStudioWorkspace))] Workspace workspace, - ProjectSnapshotManagerDispatcher projectSnapshotManagerDispatcher, + IProjectSnapshotManagerAccessor projectManagerAccessor, + ProjectSnapshotManagerDispatcher dispatcher, ProjectConfigurationFilePathStore projectConfigurationFilePathStore) : base(commonServices.ThreadingService.JoinableTaskContext) { @@ -53,14 +52,9 @@ public WindowsRazorProjectHostBase( throw new ArgumentNullException(nameof(commonServices)); } - if (workspace is null) - { - throw new ArgumentNullException(nameof(workspace)); - } - - if (projectSnapshotManagerDispatcher is null) + if (dispatcher is null) { - throw new ArgumentNullException(nameof(projectSnapshotManagerDispatcher)); + throw new ArgumentNullException(nameof(dispatcher)); } if (projectConfigurationFilePathStore is null) @@ -69,30 +63,13 @@ public WindowsRazorProjectHostBase( } CommonServices = commonServices; - _workspace = workspace; - _projectSnapshotManagerDispatcher = projectSnapshotManagerDispatcher; + _projectManagerAccessor = projectManagerAccessor; + _dispatcher = dispatcher; _lock = new AsyncSemaphore(initialCount: 1); ProjectConfigurationFilePathStore = projectConfigurationFilePathStore; } - // Internal for testing - protected WindowsRazorProjectHostBase( - IUnconfiguredProjectCommonServices commonServices, - Workspace workspace, - ProjectSnapshotManagerDispatcher projectSnapshotManagerDispatcher, - ProjectConfigurationFilePathStore projectConfigurationFilePathStore, - ProjectSnapshotManagerBase projectManager) - : this(commonServices, workspace, projectSnapshotManagerDispatcher, projectConfigurationFilePathStore) - { - if (projectManager is null) - { - throw new ArgumentNullException(nameof(projectManager)); - } - - _projectManager = projectManager; - } - protected abstract ImmutableHashSet GetRuleNames(); protected abstract Task HandleProjectChangeAsync(string sliceDimensions, IProjectVersionedValue update); @@ -277,17 +254,16 @@ internal Task OnProjectRenamingAsync(string oldProjectFilePath, string newProjec } // Should only be called from the project snapshot manager's specialized thread. + [MemberNotNull(nameof(_projectManager))] protected ProjectSnapshotManagerBase GetProjectManager() { - _projectSnapshotManagerDispatcher.AssertDispatcherThread(); - - _projectManager ??= (ProjectSnapshotManagerBase)_workspace.Services.GetLanguageServices(RazorLanguage.Name).GetRequiredService(); + _dispatcher.AssertDispatcherThread(); - return _projectManager; + return _projectManager ??= _projectManagerAccessor.Instance; } protected Task UpdateAsync(Action action, CancellationToken cancellationToken) - => _projectSnapshotManagerDispatcher.RunOnDispatcherThreadAsync(action, cancellationToken); + => _dispatcher.RunOnDispatcherThreadAsync(action, cancellationToken); protected void UninitializeProjectUnsafe(ProjectKey projectKey) { diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/VisualStudioProjectSnapshotManagerAccessor.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/VisualStudioProjectSnapshotManagerAccessor.cs index caed7bb092b..fb2bef71a5a 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/VisualStudioProjectSnapshotManagerAccessor.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/VisualStudioProjectSnapshotManagerAccessor.cs @@ -2,11 +2,11 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System.ComponentModel.Composition; -using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.Workspaces; +using Microsoft.VisualStudio.Threading; namespace Microsoft.VisualStudio.LanguageServices.Razor; @@ -14,27 +14,50 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor; [Export(typeof(IProjectSnapshotManagerAccessor))] [method: ImportingConstructor] internal sealed class VisualStudioProjectSnapshotManagerAccessor( - [Import(typeof(VisualStudioWorkspace))] Workspace workspace) + [Import(typeof(VisualStudioWorkspace))] Workspace workspace, + ProjectSnapshotManagerDispatcher dispatcher, + JoinableTaskContext joinableTaskContext) : IProjectSnapshotManagerAccessor { private readonly Workspace _workspace = workspace; + private readonly ProjectSnapshotManagerDispatcher _dispatcher = dispatcher; + private readonly JoinableTaskFactory _jtf = joinableTaskContext.Factory; + private ProjectSnapshotManagerBase? _projectManager; public ProjectSnapshotManagerBase Instance { get { - EnsureInitialized(); + if (_projectManager is { } projectManager) + { + return projectManager; + } - return _projectManager; - } - } + if (_dispatcher.IsDispatcherThread) + { + return _projectManager ??= Create(); + } - [MemberNotNull(nameof(_projectManager))] - private void EnsureInitialized() - { - _projectManager ??= (ProjectSnapshotManagerBase)_workspace.Services - .GetLanguageServices(RazorLanguage.Name) - .GetRequiredService(); + // The JTF.Run isn't great, but it should go away with IProjectSnapshotManagerAccessor. + // ProjectSnapshotManager must be created on the dispatcher scheduler because it calls the + // Initialize() method of any IProjectSnapshotChangeTrigger its created with. + + return _jtf.Run(async () => + { + await _dispatcher.DispatcherScheduler; + + return _projectManager ??= Create(); + }); + + ProjectSnapshotManagerBase Create() + { + _dispatcher.AssertDispatcherThread(); + + return (ProjectSnapshotManagerBase)_workspace.Services + .GetLanguageServices(RazorLanguage.Name) + .GetRequiredService(); + } + } } } diff --git a/src/Razor/src/Microsoft.VisualStudio.LiveShare.Razor/Guest/ProjectSnapshotSynchronizationService.cs b/src/Razor/src/Microsoft.VisualStudio.LiveShare.Razor/Guest/ProjectSnapshotSynchronizationService.cs index 3115e98c292..26514ba983b 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LiveShare.Razor/Guest/ProjectSnapshotSynchronizationService.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LiveShare.Razor/Guest/ProjectSnapshotSynchronizationService.cs @@ -5,7 +5,9 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.VisualStudio.Threading; using IAsyncDisposable = Microsoft.VisualStudio.Threading.IAsyncDisposable; @@ -16,38 +18,24 @@ internal class ProjectSnapshotSynchronizationService : ICollaborationService, IA private readonly JoinableTaskFactory _joinableTaskFactory; private readonly CollaborationSession _sessionContext; private readonly IProjectSnapshotManagerProxy _hostProjectManagerProxy; - private readonly ProjectSnapshotManagerBase _projectSnapshotManager; + private readonly IProjectSnapshotManagerAccessor _projectManagerAccessor; + private readonly IErrorReporter _errorReporter; + private readonly ProjectSnapshotManagerDispatcher _dispatcher; public ProjectSnapshotSynchronizationService( JoinableTaskFactory joinableTaskFactory, CollaborationSession sessionContext, IProjectSnapshotManagerProxy hostProjectManagerProxy, - ProjectSnapshotManagerBase projectSnapshotManager) + IProjectSnapshotManagerAccessor projectManagerAccessor, + IErrorReporter errorReporter, + ProjectSnapshotManagerDispatcher dispatcher) { - if (joinableTaskFactory is null) - { - throw new ArgumentNullException(nameof(joinableTaskFactory)); - } - - if (sessionContext is null) - { - throw new ArgumentNullException(nameof(sessionContext)); - } - - if (hostProjectManagerProxy is null) - { - throw new ArgumentNullException(nameof(hostProjectManagerProxy)); - } - - if (projectSnapshotManager is null) - { - throw new ArgumentNullException(nameof(projectSnapshotManager)); - } - _joinableTaskFactory = joinableTaskFactory; _sessionContext = sessionContext; _hostProjectManagerProxy = hostProjectManagerProxy; - _projectSnapshotManager = projectSnapshotManager; + _projectManagerAccessor = projectManagerAccessor; + _errorReporter = errorReporter; + _dispatcher = dispatcher; } public async Task InitializeAsync(CancellationToken cancellationToken) @@ -58,51 +46,62 @@ public async Task InitializeAsync(CancellationToken cancellationToken) var projectManagerState = await _hostProjectManagerProxy.GetProjectManagerStateAsync(cancellationToken); - await InitializeGuestProjectManagerAsync(projectManagerState.ProjectHandles, cancellationToken); + await InitializeGuestProjectManagerAsync(projectManagerState.ProjectHandles); } public async Task DisposeAsync() { _hostProjectManagerProxy.Changed -= HostProxyProjectManager_Changed; - await _joinableTaskFactory.SwitchToMainThreadAsync(); + await _dispatcher.DispatcherScheduler; + + var projectManager = _projectManagerAccessor.Instance; - var projects = _projectSnapshotManager.GetProjects(); + var projects = projectManager.GetProjects(); foreach (var project in projects) { try { - _projectSnapshotManager.ProjectRemoved(project.Key); + projectManager.ProjectRemoved(project.Key); } catch (Exception ex) { - _projectSnapshotManager.ReportError(ex, project); + projectManager.ReportError(ex, project); } } } + ValueTask System.IAsyncDisposable.DisposeAsync() + { + return new ValueTask(DisposeAsync()); + } + // Internal for testing - internal void UpdateGuestProjectManager(ProjectChangeEventProxyArgs args) + internal async ValueTask UpdateGuestProjectManagerAsync(ProjectChangeEventProxyArgs args) { + await _dispatcher.DispatcherScheduler; + + var projectManager = _projectManagerAccessor.Instance; + if (args.Kind == ProjectProxyChangeKind.ProjectAdded) { var guestPath = ResolveGuestPath(args.ProjectFilePath); var guestIntermediateOutputPath = ResolveGuestPath(args.IntermediateOutputPath); var hostProject = new HostProject(guestPath, guestIntermediateOutputPath, args.Newer!.Configuration, args.Newer.RootNamespace); - _projectSnapshotManager.ProjectAdded(hostProject); + projectManager.ProjectAdded(hostProject); if (args.Newer.ProjectWorkspaceState != null) { - _projectSnapshotManager.ProjectWorkspaceStateChanged(hostProject.Key, args.Newer.ProjectWorkspaceState); + projectManager.ProjectWorkspaceStateChanged(hostProject.Key, args.Newer.ProjectWorkspaceState); } } else if (args.Kind == ProjectProxyChangeKind.ProjectRemoved) { var guestPath = ResolveGuestPath(args.ProjectFilePath); - var projectKeys = _projectSnapshotManager.GetAllProjectKeys(guestPath); + var projectKeys = projectManager.GetAllProjectKeys(guestPath); foreach (var projectKey in projectKeys) { - _projectSnapshotManager.ProjectRemoved(projectKey); + projectManager.ProjectRemoved(projectKey); } } else if (args.Kind == ProjectProxyChangeKind.ProjectChanged) @@ -112,37 +111,37 @@ internal void UpdateGuestProjectManager(ProjectChangeEventProxyArgs args) var guestPath = ResolveGuestPath(args.Newer.FilePath); var guestIntermediateOutputPath = ResolveGuestPath(args.Newer.IntermediateOutputPath); var hostProject = new HostProject(guestPath, guestIntermediateOutputPath, args.Newer.Configuration, args.Newer.RootNamespace); - _projectSnapshotManager.ProjectConfigurationChanged(hostProject); + projectManager.ProjectConfigurationChanged(hostProject); } else if (args.Older.ProjectWorkspaceState != args.Newer.ProjectWorkspaceState || args.Older.ProjectWorkspaceState?.Equals(args.Newer.ProjectWorkspaceState) == false) { var guestPath = ResolveGuestPath(args.Newer.FilePath); - var projectKeys = _projectSnapshotManager.GetAllProjectKeys(guestPath); + var projectKeys = projectManager.GetAllProjectKeys(guestPath); foreach (var projectKey in projectKeys) { - _projectSnapshotManager.ProjectWorkspaceStateChanged(projectKey, args.Newer.ProjectWorkspaceState); + projectManager.ProjectWorkspaceStateChanged(projectKey, args.Newer.ProjectWorkspaceState); } } } } - private async Task InitializeGuestProjectManagerAsync( - IReadOnlyList projectHandles, - CancellationToken cancellationToken) + private async Task InitializeGuestProjectManagerAsync(IReadOnlyList projectHandles) { - await _joinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await _dispatcher.DispatcherScheduler; + + var projectManager = _projectManagerAccessor.Instance; foreach (var projectHandle in projectHandles) { var guestPath = ResolveGuestPath(projectHandle.FilePath); var guestIntermediateOutputPath = ResolveGuestPath(projectHandle.IntermediateOutputPath); var hostProject = new HostProject(guestPath, guestIntermediateOutputPath, projectHandle.Configuration, projectHandle.RootNamespace); - _projectSnapshotManager.ProjectAdded(hostProject); + projectManager.ProjectAdded(hostProject); if (projectHandle.ProjectWorkspaceState is not null) { - _projectSnapshotManager.ProjectWorkspaceStateChanged(hostProject.Key, projectHandle.ProjectWorkspaceState); + projectManager.ProjectWorkspaceStateChanged(hostProject.Key, projectHandle.ProjectWorkspaceState); } } } @@ -158,13 +157,11 @@ private void HostProxyProjectManager_Changed(object sender, ProjectChangeEventPr { try { - await _joinableTaskFactory.SwitchToMainThreadAsync(); - - UpdateGuestProjectManager(args); + await UpdateGuestProjectManagerAsync(args); } catch (Exception ex) { - _projectSnapshotManager.ReportError(ex); + _errorReporter.ReportError(ex); } }); } @@ -173,9 +170,4 @@ private string ResolveGuestPath(Uri filePath) { return _sessionContext.ConvertSharedUriToLocalPath(filePath); } - - ValueTask System.IAsyncDisposable.DisposeAsync() - { - return new ValueTask(DisposeAsync()); - } } diff --git a/src/Razor/src/Microsoft.VisualStudio.LiveShare.Razor/Guest/ProjectSnapshotSynchronizationServiceFactory.cs b/src/Razor/src/Microsoft.VisualStudio.LiveShare.Razor/Guest/ProjectSnapshotSynchronizationServiceFactory.cs index 6a3b7786e42..6f05bc7c591 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LiveShare.Razor/Guest/ProjectSnapshotSynchronizationServiceFactory.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LiveShare.Razor/Guest/ProjectSnapshotSynchronizationServiceFactory.cs @@ -1,14 +1,11 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -using System; using System.ComponentModel.Composition; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Razor; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; -using Microsoft.VisualStudio.LanguageServices; +using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.VisualStudio.LiveShare.Razor.Serialization; using Microsoft.VisualStudio.Threading; using Newtonsoft.Json; @@ -16,38 +13,13 @@ namespace Microsoft.VisualStudio.LiveShare.Razor.Guest; [ExportCollaborationService(typeof(ProjectSnapshotSynchronizationService), Scope = SessionScope.Guest)] -internal class ProjectSnapshotSynchronizationServiceFactory : ICollaborationServiceFactory +[method: ImportingConstructor] +internal class ProjectSnapshotSynchronizationServiceFactory( + JoinableTaskContext joinableTaskContext, + IProjectSnapshotManagerAccessor projectManagerAccessor, + IErrorReporter errorReporter, + ProjectSnapshotManagerDispatcher dispatcher) : ICollaborationServiceFactory { - private readonly ProxyAccessor _proxyAccessor; - private readonly JoinableTaskContext _joinableTaskContext; - private readonly Workspace _workspace; - - [ImportingConstructor] - public ProjectSnapshotSynchronizationServiceFactory( - ProxyAccessor proxyAccessor, - JoinableTaskContext joinableTaskContext, - [Import(typeof(VisualStudioWorkspace))] Workspace workspace) - { - if (proxyAccessor is null) - { - throw new ArgumentNullException(nameof(proxyAccessor)); - } - - if (joinableTaskContext is null) - { - throw new ArgumentNullException(nameof(joinableTaskContext)); - } - - if (workspace is null) - { - throw new ArgumentNullException(nameof(workspace)); - } - - _proxyAccessor = proxyAccessor; - _joinableTaskContext = joinableTaskContext; - _workspace = workspace; - } - public async Task CreateServiceAsync(CollaborationSession sessionContext, CancellationToken cancellationToken) { // This collaboration service depends on these serializers being immediately available so we need to register these now so the @@ -55,15 +27,14 @@ public async Task CreateServiceAsync(CollaborationSession var serializer = (JsonSerializer)sessionContext.GetService(typeof(JsonSerializer)); serializer.Converters.RegisterRazorLiveShareConverters(); - var languageServices = _workspace.Services.GetLanguageServices(RazorLanguage.Name); - var projectManager = (ProjectSnapshotManagerBase)languageServices.GetRequiredService(); - var projectSnapshotManagerProxy = await sessionContext.GetRemoteServiceAsync(typeof(IProjectSnapshotManagerProxy).Name, cancellationToken); var synchronizationService = new ProjectSnapshotSynchronizationService( - _joinableTaskContext.Factory, + joinableTaskContext.Factory, sessionContext, projectSnapshotManagerProxy, - projectManager); + projectManagerAccessor, + errorReporter, + dispatcher); await synchronizationService.InitializeAsync(cancellationToken); diff --git a/src/Razor/src/Microsoft.VisualStudio.LiveShare.Razor/Host/DefaultProjectSnapshotManagerProxy.cs b/src/Razor/src/Microsoft.VisualStudio.LiveShare.Razor/Host/DefaultProjectSnapshotManagerProxy.cs index f20b18071ba..b51c85d6926 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LiveShare.Razor/Host/DefaultProjectSnapshotManagerProxy.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LiveShare.Razor/Host/DefaultProjectSnapshotManagerProxy.cs @@ -16,7 +16,7 @@ namespace Microsoft.VisualStudio.LiveShare.Razor.Host; internal class DefaultProjectSnapshotManagerProxy : IProjectSnapshotManagerProxy, ICollaborationService, IDisposable { private readonly CollaborationSession _session; - private readonly ProjectSnapshotManagerDispatcher _projectSnapshotManagerDispatcher; + private readonly ProjectSnapshotManagerDispatcher _dispatcher; private readonly ProjectSnapshotManager _projectSnapshotManager; private readonly JoinableTaskFactory _joinableTaskFactory; private readonly AsyncSemaphore _latestStateSemaphore; @@ -28,7 +28,7 @@ internal class DefaultProjectSnapshotManagerProxy : IProjectSnapshotManagerProxy public DefaultProjectSnapshotManagerProxy( CollaborationSession session, - ProjectSnapshotManagerDispatcher projectSnapshotManagerDispatcher, + ProjectSnapshotManagerDispatcher dispatcher, ProjectSnapshotManager projectSnapshotManager, JoinableTaskFactory joinableTaskFactory) { @@ -37,9 +37,9 @@ public DefaultProjectSnapshotManagerProxy( throw new ArgumentNullException(nameof(session)); } - if (projectSnapshotManagerDispatcher is null) + if (dispatcher is null) { - throw new ArgumentNullException(nameof(projectSnapshotManagerDispatcher)); + throw new ArgumentNullException(nameof(dispatcher)); } if (projectSnapshotManager is null) @@ -53,7 +53,7 @@ public DefaultProjectSnapshotManagerProxy( } _session = session; - _projectSnapshotManagerDispatcher = projectSnapshotManagerDispatcher; + _dispatcher = dispatcher; _projectSnapshotManager = projectSnapshotManager; _joinableTaskFactory = joinableTaskFactory; @@ -81,7 +81,7 @@ public async Task GetProjectManagerStateAsync( public void Dispose() { - _projectSnapshotManagerDispatcher.AssertDispatcherThread(); + _dispatcher.AssertDispatcherThread(); _projectSnapshotManager.Changed -= ProjectSnapshotManager_Changed; _latestStateSemaphore.Dispose(); @@ -133,7 +133,7 @@ internal async Task CalculateUpdatedStateAsync private void ProjectSnapshotManager_Changed(object sender, ProjectChangeEventArgs args) { - _projectSnapshotManagerDispatcher.AssertDispatcherThread(); + _dispatcher.AssertDispatcherThread(); if (_disposed) { @@ -164,7 +164,7 @@ private void ProjectSnapshotManager_Changed(object sender, ProjectChangeEventArg private void OnChanged(ProjectChangeEventProxyArgs args) { - _projectSnapshotManagerDispatcher.AssertDispatcherThread(); + _dispatcher.AssertDispatcherThread(); if (_disposed) { diff --git a/src/Razor/src/Microsoft.VisualStudio.LiveShare.Razor/Host/DefaultProjectSnapshotManagerProxyFactory.cs b/src/Razor/src/Microsoft.VisualStudio.LiveShare.Razor/Host/DefaultProjectSnapshotManagerProxyFactory.cs index 0d3f096d32a..eb0bd4a5041 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LiveShare.Razor/Host/DefaultProjectSnapshotManagerProxyFactory.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LiveShare.Razor/Host/DefaultProjectSnapshotManagerProxyFactory.cs @@ -5,10 +5,8 @@ using System.ComponentModel.Composition; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Razor; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; -using Microsoft.VisualStudio.LanguageServices; +using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.VisualStudio.LiveShare.Razor.Serialization; using Microsoft.VisualStudio.Threading; using Newtonsoft.Json; @@ -20,38 +18,12 @@ namespace Microsoft.VisualStudio.LiveShare.Razor.Host; Name = nameof(IProjectSnapshotManagerProxy), Scope = SessionScope.Host, Role = ServiceRole.RemoteService)] -internal class DefaultProjectSnapshotManagerProxyFactory : ICollaborationServiceFactory +[method: ImportingConstructor] +internal class DefaultProjectSnapshotManagerProxyFactory( + ProjectSnapshotManagerDispatcher dispatcher, + JoinableTaskContext joinableTaskContext, + IProjectSnapshotManagerAccessor projectManagerAccessor) : ICollaborationServiceFactory { - private readonly ProjectSnapshotManagerDispatcher _projectSnapshotManagerDispatcher; - private readonly JoinableTaskContext _joinableTaskContext; - private readonly Workspace _workspace; - - [ImportingConstructor] - public DefaultProjectSnapshotManagerProxyFactory( - ProjectSnapshotManagerDispatcher projectSnapshotManagerDispatcher, - JoinableTaskContext joinableTaskContext, - [Import(typeof(VisualStudioWorkspace))] Workspace workspace) - { - if (projectSnapshotManagerDispatcher is null) - { - throw new ArgumentNullException(nameof(projectSnapshotManagerDispatcher)); - } - - if (joinableTaskContext is null) - { - throw new ArgumentNullException(nameof(joinableTaskContext)); - } - - if (workspace is null) - { - throw new ArgumentNullException(nameof(workspace)); - } - - _projectSnapshotManagerDispatcher = projectSnapshotManagerDispatcher; - _joinableTaskContext = joinableTaskContext; - _workspace = workspace; - } - public Task CreateServiceAsync(CollaborationSession session, CancellationToken cancellationToken) { if (session is null) @@ -62,10 +34,8 @@ public Task CreateServiceAsync(CollaborationSession sessi var serializer = (JsonSerializer)session.GetService(typeof(JsonSerializer)); serializer.Converters.RegisterRazorLiveShareConverters(); - var razorLanguageServices = _workspace.Services.GetLanguageServices(RazorLanguage.Name); - var projectSnapshotManager = razorLanguageServices.GetRequiredService(); - - var service = new DefaultProjectSnapshotManagerProxy(session, _projectSnapshotManagerDispatcher, projectSnapshotManager, _joinableTaskContext.Factory); + var service = new DefaultProjectSnapshotManagerProxy( + session, dispatcher, projectManagerAccessor.Instance, joinableTaskContext.Factory); return Task.FromResult(service); } } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs index d723c11537b..3a7c2e3b1d3 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs @@ -280,9 +280,6 @@ internal static IDocumentSnapshot CreateDocumentSnapshot(string path, ImmutableA documentSnapshot .Setup(d => d.GetGeneratedOutputAsync()) .ReturnsAsync(codeDocument); - documentSnapshot - .Setup(d => d.GetImports()) - .Returns(imports); documentSnapshot .Setup(d => d.FilePath) .Returns(path); diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/OpenDocumentGeneratorTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/OpenDocumentGeneratorTest.cs index 454a0d13fec..b8fbe744d4e 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/OpenDocumentGeneratorTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/OpenDocumentGeneratorTest.cs @@ -4,7 +4,6 @@ #nullable disable using System; -using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Semantic/SemanticTokensTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Semantic/SemanticTokensTest.cs index 88ff982bbc6..929419ed5de 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Semantic/SemanticTokensTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Semantic/SemanticTokensTest.cs @@ -1011,7 +1011,9 @@ private async Task CreateServiceAsync( featureOptions, LoggerFactory, telemetryReporter: null); - service.ApplyCapabilities(new(), new VSInternalClientCapabilities { SupportsVisualStudioExtensions = true }); + + var legend = new RazorSemanticTokensLegend(new VSInternalClientCapabilities { SupportsVisualStudioExtensions = true }); + service.SetTokensLegend(legend); return service; } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/LanguageServer/TestDocumentSnapshot.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/LanguageServer/TestDocumentSnapshot.cs index 527abb034ff..b0292152edb 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/LanguageServer/TestDocumentSnapshot.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/LanguageServer/TestDocumentSnapshot.cs @@ -88,11 +88,6 @@ public override Task GetGeneratedOutputAsync() return Task.FromResult(_codeDocument); } - public override ImmutableArray GetImports() - { - return ImmutableArray.Empty; - } - public override bool TryGetGeneratedOutput(out RazorCodeDocument result) { if (_codeDocument is null) diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/TestTagHelperResolver.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/TestTagHelperResolver.cs index eab306f2b85..bb88bd5f412 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/TestTagHelperResolver.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/TestTagHelperResolver.cs @@ -11,15 +11,15 @@ namespace Microsoft.AspNetCore.Razor.Test.Common; -internal class TestTagHelperResolver : ITagHelperResolver +internal class TestTagHelperResolver(ImmutableArray tagHelpers) : ITagHelperResolver { - public ImmutableArray TagHelpers { get; set; } = ImmutableArray.Empty; + public ImmutableArray TagHelpers { get; } = tagHelpers; public ValueTask> GetTagHelpersAsync( Project workspaceProject, IProjectSnapshot projectSnapshot, CancellationToken cancellationToken) { - return new(TagHelpers.ToImmutableArray()); + return new(TagHelpers); } } diff --git a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultDocumentSnapshotTest.cs b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultDocumentSnapshotTest.cs index f34d8790b23..943b2c31d24 100644 --- a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultDocumentSnapshotTest.cs +++ b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultDocumentSnapshotTest.cs @@ -1,15 +1,11 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -#nullable disable - using System; -using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.ProjectSystem; using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Razor.Workspaces.Extensions; using Microsoft.CodeAnalysis.Text; using Xunit; @@ -60,11 +56,6 @@ public DefaultDocumentSnapshotTest(ITestOutputHelper testOutput) _nestedComponentDocument = new DocumentSnapshot(project, documentState); } - protected override void ConfigureWorkspaceServices(List services) - { - services.Add(new TestTagHelperResolver()); - } - [Fact] public async Task GCCollect_OutputIsNoLongerCached() { diff --git a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotTest.cs b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotTest.cs index 701c3bd6af1..4c8734c90c7 100644 --- a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotTest.cs +++ b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotTest.cs @@ -1,16 +1,12 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -#nullable disable - -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.ProjectSystem; using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; -using Microsoft.CodeAnalysis.Host; using Xunit; using Xunit.Abstractions; @@ -21,13 +17,10 @@ public class DefaultProjectSnapshotTest : WorkspaceTestBase private readonly HostDocument[] _documents; private readonly HostProject _hostProject; private readonly ProjectWorkspaceState _projectWorkspaceState; - private readonly TestTagHelperResolver _tagHelperResolver; public DefaultProjectSnapshotTest(ITestOutputHelper testOutput) : base(testOutput) { - _tagHelperResolver = new TestTagHelperResolver(); - _hostProject = new HostProject(TestProjectData.SomeProject.FilePath, TestProjectData.SomeProject.IntermediateOutputPath, FallbackRazorConfiguration.MVC_2_0, TestProjectData.SomeProject.RootNamespace); _projectWorkspaceState = ProjectWorkspaceState.Create(ImmutableArray.Create( TagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly").Build())); @@ -42,11 +35,6 @@ public DefaultProjectSnapshotTest(ITestOutputHelper testOutput) ]; } - protected override void ConfigureWorkspaceServices(List services) - { - services.Add(_tagHelperResolver); - } - protected override void ConfigureProjectEngine(RazorProjectEngineBuilder builder) { builder.SetImportFeature(new TestImportProjectFeature()); @@ -82,6 +70,7 @@ public void IsImportDocument_NonImportDocument_ReturnsFalse() var snapshot = new ProjectSnapshot(state); var document = snapshot.GetDocument(_documents[0].FilePath); + Assert.NotNull(document); // Act var result = snapshot.IsImportDocument(document); @@ -100,6 +89,7 @@ public void IsImportDocument_ImportDocument_ReturnsTrue() var snapshot = new ProjectSnapshot(state); var document = snapshot.GetDocument(TestProjectData.SomeProjectImportFile.FilePath); + Assert.NotNull(document); // Act var result = snapshot.IsImportDocument(document); @@ -117,6 +107,7 @@ public void GetRelatedDocuments_NonImportDocument_ReturnsEmpty() var snapshot = new ProjectSnapshot(state); var document = snapshot.GetDocument(_documents[0].FilePath); + Assert.NotNull(document); // Act var documents = snapshot.GetRelatedDocuments(document); @@ -136,6 +127,7 @@ public void GetRelatedDocuments_ImportDocument_ReturnsRelated() var snapshot = new ProjectSnapshot(state); var document = snapshot.GetDocument(TestProjectData.SomeProjectImportFile.FilePath); + Assert.NotNull(document); // Act var documents = snapshot.GetRelatedDocuments(document); diff --git a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateGeneratedOutputTest.cs b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateGeneratedOutputTest.cs index 71e4b3e9d7a..485558753c8 100644 --- a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateGeneratedOutputTest.cs +++ b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateGeneratedOutputTest.cs @@ -1,9 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -#nullable disable - -using System.Collections.Generic; using System.Collections.Immutable; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; @@ -11,7 +8,6 @@ using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Text; using Xunit; using Xunit.Abstractions; @@ -23,7 +19,6 @@ public class ProjectStateGeneratedOutputTest : WorkspaceTestBase private readonly HostDocument _hostDocument; private readonly HostProject _hostProject; private readonly HostProject _hostProjectWithConfigurationChange; - private readonly TestTagHelperResolver _tagHelperResolver; private readonly ImmutableArray _someTagHelpers; private readonly SourceText _text; @@ -33,8 +28,6 @@ public ProjectStateGeneratedOutputTest(ITestOutputHelper testOutput) _hostProject = new HostProject(TestProjectData.SomeProject.FilePath, TestProjectData.SomeProject.IntermediateOutputPath, FallbackRazorConfiguration.MVC_2_0, TestProjectData.SomeProject.RootNamespace); _hostProjectWithConfigurationChange = new HostProject(TestProjectData.SomeProject.FilePath, TestProjectData.SomeProject.IntermediateOutputPath, FallbackRazorConfiguration.MVC_1_0, TestProjectData.SomeProject.RootNamespace); - _tagHelperResolver = new TestTagHelperResolver(); - _someTagHelpers = ImmutableArray.Create( TagHelperDescriptorBuilder.Create("Test1", "TestAssembly").Build()); @@ -43,11 +36,6 @@ public ProjectStateGeneratedOutputTest(ITestOutputHelper testOutput) _text = SourceText.From("Hello, world!"); } - protected override void ConfigureWorkspaceServices(List services) - { - services.Add(_tagHelperResolver); - } - protected override void ConfigureProjectEngine(RazorProjectEngineBuilder builder) { builder.SetImportFeature(new TestImportProjectFeature()); diff --git a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateTest.cs b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateTest.cs index d60c998b004..77fd4a8f7b2 100644 --- a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateTest.cs +++ b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateTest.cs @@ -2,9 +2,7 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; @@ -12,7 +10,6 @@ using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Text; using Xunit; using Xunit.Abstractions; @@ -25,13 +22,9 @@ public class ProjectStateTest : WorkspaceTestBase private readonly HostProject _hostProject; private readonly HostProject _hostProjectWithConfigurationChange; private readonly ProjectWorkspaceState _projectWorkspaceState; - private readonly ImmutableArray _someTagHelpers; private readonly Func> _textLoader; private readonly SourceText _text; - [AllowNull] - private TestTagHelperResolver _tagHelperResolver; - public ProjectStateTest(ITestOutputHelper testOutput) : base(testOutput) { @@ -41,9 +34,6 @@ public ProjectStateTest(ITestOutputHelper testOutput) ImmutableArray.Create( TagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly").Build())); - _someTagHelpers = ImmutableArray.Create( - TagHelperDescriptorBuilder.Create("Test1", "TestAssembly").Build()); - _documents = new HostDocument[] { TestProjectData.SomeProjectFile1, @@ -57,12 +47,6 @@ public ProjectStateTest(ITestOutputHelper testOutput) _textLoader = () => Task.FromResult(TextAndVersion.Create(_text, VersionStamp.Create())); } - protected override void ConfigureWorkspaceServices(List services) - { - _tagHelperResolver = new TestTagHelperResolver(); - services.Add(_tagHelperResolver); - } - protected override void ConfigureProjectEngine(RazorProjectEngineBuilder builder) { builder.SetImportFeature(new TestImportProjectFeature()); @@ -556,8 +540,6 @@ public void ProjectState_WithHostProject_ConfigurationChange_UpdatesConfiguratio var originalTagHelpers = original.TagHelpers; var originalProjectWorkspaceStateVersion = original.ConfigurationVersion; - _tagHelperResolver.TagHelpers = _someTagHelpers; - // Act var state = original.WithHostProject(_hostProjectWithConfigurationChange); @@ -601,8 +583,6 @@ public void ProjectState_WithHostProject_RootNamespaceChange_UpdatesConfiguratio _ = original.TagHelpers; _ = original.ConfigurationVersion; - _tagHelperResolver.TagHelpers = _someTagHelpers; - // Act var state = original.WithHostProject(hostProjectWithRootNamespaceChange); @@ -719,9 +699,6 @@ public void ProjectState_WithProjectWorkspaceState_Changed_TagHelpersChanged() var changed = ProjectWorkspaceState.Default; - // Now create some tag helpers - _tagHelperResolver.TagHelpers = _someTagHelpers; - // Act var state = original.WithProjectWorkspaceState(changed); @@ -756,9 +733,6 @@ public void ProjectState_WithProjectWorkspaceState_IdenticalState_Caches() var changed = ProjectWorkspaceState.Create(original.TagHelpers, original.CSharpLanguageVersion); - // Now create some tag helpers - _tagHelperResolver.TagHelpers = _someTagHelpers; - // Act var state = original.WithProjectWorkspaceState(changed); diff --git a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/RazorDocumentExcerptServiceTest.cs b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/RazorDocumentExcerptServiceTest.cs index 1d27d7b3aec..dde27094ecc 100644 --- a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/RazorDocumentExcerptServiceTest.cs +++ b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/RazorDocumentExcerptServiceTest.cs @@ -1,15 +1,10 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -#nullable disable - -using System.Collections.Generic; using System.Threading.Tasks; -using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.ExternalAccess.Razor; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Xunit; using Xunit.Abstractions; @@ -18,11 +13,6 @@ namespace Microsoft.CodeAnalysis.Razor; public class RazorDocumentExcerptServiceTest(ITestOutputHelper testOutput) : DocumentExcerptServiceTestBase(testOutput) { - protected override void ConfigureWorkspaceServices(List services) - { - services.Add(new TestTagHelperResolver()); - } - [Fact] public async Task TryGetExcerptInternalAsync_SingleLine_CanClassifyCSharp() { diff --git a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/RazorSpanMappingServiceTest.cs b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/RazorSpanMappingServiceTest.cs index 3437a6237c9..6d545c8769a 100644 --- a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/RazorSpanMappingServiceTest.cs +++ b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/RazorSpanMappingServiceTest.cs @@ -1,16 +1,12 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -#nullable disable - using System; -using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.ProjectSystem; using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Text; using Xunit; @@ -23,11 +19,6 @@ public class RazorSpanMappingServiceTest(ITestOutputHelper testOutput) : Workspa private readonly HostProject _hostProject = TestProjectData.SomeProject; private readonly HostDocument _hostDocument = TestProjectData.SomeProjectFile1; - protected override void ConfigureWorkspaceServices(List services) - { - services.Add(new TestTagHelperResolver()); - } - [Fact] public async Task TryGetMappedSpans_SpanMatchesSourceMapping_ReturnsTrue() { @@ -41,6 +32,7 @@ public async Task TryGetMappedSpans_SpanMatchesSourceMapping_ReturnsTrue() .WithAddedHostDocument(_hostDocument, () => Task.FromResult(TextAndVersion.Create(sourceText, VersionStamp.Create())))); var document = project.GetDocument(_hostDocument.FilePath); + Assert.NotNull(document); var service = new RazorSpanMappingService(document); var output = await document.GetGeneratedOutputAsync(); @@ -73,6 +65,7 @@ public async Task TryGetMappedSpans_SpanMatchesSourceMappingAndPosition_ReturnsT .WithAddedHostDocument(_hostDocument, () => Task.FromResult(TextAndVersion.Create(sourceText, VersionStamp.Create())))); var document = project.GetDocument(_hostDocument.FilePath); + Assert.NotNull(document); var service = new RazorSpanMappingService(document); var output = await document.GetGeneratedOutputAsync(); @@ -106,6 +99,7 @@ public async Task TryGetMappedSpans_SpanWithinSourceMapping_ReturnsTrue() .WithAddedHostDocument(_hostDocument, () => Task.FromResult(TextAndVersion.Create(sourceText, VersionStamp.Create())))); var document = project.GetDocument(_hostDocument.FilePath); + Assert.NotNull(document); var service = new RazorSpanMappingService(document); var output = await document.GetGeneratedOutputAsync(); @@ -138,6 +132,7 @@ public async Task TryGetMappedSpans_SpanOutsideSourceMapping_ReturnsFalse() .WithAddedHostDocument(_hostDocument, () => Task.FromResult(TextAndVersion.Create(sourceText, VersionStamp.Create())))); var document = project.GetDocument(_hostDocument.FilePath); + Assert.NotNull(document); var service = new RazorSpanMappingService(document); var output = await document.GetGeneratedOutputAsync(); diff --git a/src/Razor/test/Microsoft.CodeAnalysis.Remote.Razor.Test/OOPTagHelperResolverTest.TestProjectSnapshotManager.cs b/src/Razor/test/Microsoft.CodeAnalysis.Remote.Razor.Test/OutOfProcTagHelperResolverTest.TestProjectSnapshotManager.cs similarity index 95% rename from src/Razor/test/Microsoft.CodeAnalysis.Remote.Razor.Test/OOPTagHelperResolverTest.TestProjectSnapshotManager.cs rename to src/Razor/test/Microsoft.CodeAnalysis.Remote.Razor.Test/OutOfProcTagHelperResolverTest.TestProjectSnapshotManager.cs index 95ce8ddcad6..f0cef110d5e 100644 --- a/src/Razor/test/Microsoft.CodeAnalysis.Remote.Razor.Test/OOPTagHelperResolverTest.TestProjectSnapshotManager.cs +++ b/src/Razor/test/Microsoft.CodeAnalysis.Remote.Razor.Test/OutOfProcTagHelperResolverTest.TestProjectSnapshotManager.cs @@ -12,7 +12,7 @@ namespace Microsoft.CodeAnalysis.Remote.Razor; -public partial class OOPTagHelperResolverTest +public partial class OutOfProcTagHelperResolverTest { private static readonly Lazy s_projectSnapshotManagerDispatcher = new(() => { diff --git a/src/Razor/test/Microsoft.CodeAnalysis.Remote.Razor.Test/OOPTagHelperResolverTest.TestResolver.cs b/src/Razor/test/Microsoft.CodeAnalysis.Remote.Razor.Test/OutOfProcTagHelperResolverTest.TestResolver.cs similarity index 89% rename from src/Razor/test/Microsoft.CodeAnalysis.Remote.Razor.Test/OOPTagHelperResolverTest.TestResolver.cs rename to src/Razor/test/Microsoft.CodeAnalysis.Remote.Razor.Test/OutOfProcTagHelperResolverTest.TestResolver.cs index a46ae2744d4..07433f57a99 100644 --- a/src/Razor/test/Microsoft.CodeAnalysis.Remote.Razor.Test/OOPTagHelperResolverTest.TestResolver.cs +++ b/src/Razor/test/Microsoft.CodeAnalysis.Remote.Razor.Test/OutOfProcTagHelperResolverTest.TestResolver.cs @@ -11,19 +11,19 @@ using Microsoft.AspNetCore.Razor.Utilities; using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.ProjectSystem; -using Xunit; +using Microsoft.CodeAnalysis.Razor.Workspaces; #pragma warning disable VSTHRD110 // Observe result of async calls namespace Microsoft.CodeAnalysis.Remote.Razor; -public partial class OOPTagHelperResolverTest +public partial class OutOfProcTagHelperResolverTest { private class TestResolver( - Workspace workspace, + IProjectSnapshotManagerAccessor projectManagerAccessor, IErrorReporter errorReporter, ITelemetryReporter telemetryReporter) - : OOPTagHelperResolver(workspace, errorReporter, telemetryReporter) + : OutOfProcTagHelperResolver(projectManagerAccessor, errorReporter, telemetryReporter) { public Func>>? OnResolveOutOfProcess { get; init; } diff --git a/src/Razor/test/Microsoft.CodeAnalysis.Remote.Razor.Test/OOPTagHelperResolverTest.cs b/src/Razor/test/Microsoft.CodeAnalysis.Remote.Razor.Test/OutOfProcTagHelperResolverTest.cs similarity index 70% rename from src/Razor/test/Microsoft.CodeAnalysis.Remote.Razor.Test/OOPTagHelperResolverTest.cs rename to src/Razor/test/Microsoft.CodeAnalysis.Remote.Razor.Test/OutOfProcTagHelperResolverTest.cs index d91c8feccac..1793e11e776 100644 --- a/src/Razor/test/Microsoft.CodeAnalysis.Remote.Razor.Test/OOPTagHelperResolverTest.cs +++ b/src/Razor/test/Microsoft.CodeAnalysis.Remote.Razor.Test/OutOfProcTagHelperResolverTest.cs @@ -11,9 +11,9 @@ using Microsoft.AspNetCore.Razor.ProjectEngineHost; using Microsoft.AspNetCore.Razor.Serialization; using Microsoft.AspNetCore.Razor.Telemetry; -using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; using Microsoft.AspNetCore.Razor.Utilities; using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CodeAnalysis.Remote.Razor.Test; using Moq; using Xunit; @@ -21,41 +21,47 @@ namespace Microsoft.CodeAnalysis.Remote.Razor; -public partial class OOPTagHelperResolverTest : TagHelperDescriptorTestBase +public partial class OutOfProcTagHelperResolverTest : TagHelperDescriptorTestBase { - private readonly IProjectEngineFactoryProvider _projectEngineFactoryProvider; - private readonly ImmutableArray _customFactories; private readonly HostProject _hostProject_For_2_0; private readonly HostProject _hostProject_For_NonSerializableConfiguration; - private readonly ProjectSnapshotManagerBase _projectManager; private readonly Project _workspaceProject; - private readonly Workspace _workspace; + private readonly IProjectSnapshotManagerAccessor _projectManagerAccessor; - public OOPTagHelperResolverTest(ITestOutputHelper testOutput) + public OutOfProcTagHelperResolverTest(ITestOutputHelper testOutput) : base(testOutput) { - _hostProject_For_2_0 = new HostProject("Test.csproj", "/obj", FallbackRazorConfiguration.MVC_2_0, rootNamespace: null); + _hostProject_For_2_0 = new HostProject( + projectFilePath: "Test.csproj", + intermediateOutputPath: "/obj", + razorConfiguration: FallbackRazorConfiguration.MVC_2_0, + rootNamespace: null); _hostProject_For_NonSerializableConfiguration = new HostProject( - "Test.csproj", "/obj", - new ProjectSystemRazorConfiguration(RazorLanguageVersion.Version_2_1, "Random-0.1", []), rootNamespace: null); + projectFilePath: "Test.csproj", + intermediateOutputPath: "/obj", + razorConfiguration: new ProjectSystemRazorConfiguration(RazorLanguageVersion.Version_2_1, "Random-0.1", []), + rootNamespace: null); - _customFactories = ImmutableArray.Create( + var workspace = new AdhocWorkspace(); + AddDisposable(workspace); + + var info = ProjectInfo.Create(ProjectId.CreateNewId("Test"), VersionStamp.Default, "Test", "Test", LanguageNames.CSharp, filePath: "Test.csproj"); + _workspaceProject = workspace.CurrentSolution.AddProject(info).GetProject(info.Id).AssumeNotNull(); + + var customFactories = ImmutableArray.Create( CreateFactory("MVC-2.0"), // We don't really use this factory, we just use it to ensure that the call is going to go out of process. CreateFactory("Test-2")); - _projectEngineFactoryProvider = new ProjectEngineFactoryProvider(_customFactories); - - var testServices = TestServices.Create([], []); - - _workspace = new AdhocWorkspace(testServices); - AddDisposable(_workspace); - - var info = ProjectInfo.Create(ProjectId.CreateNewId("Test"), VersionStamp.Default, "Test", "Test", LanguageNames.CSharp, filePath: "Test.csproj"); - _workspaceProject = _workspace.CurrentSolution.AddProject(info).GetProject(info.Id).AssumeNotNull(); + var projectEngineFactoryProvider = new ProjectEngineFactoryProvider(customFactories); + var projectManager = new TestProjectSnapshotManager(workspace, projectEngineFactoryProvider); - _projectManager = new TestProjectSnapshotManager(_workspace, _projectEngineFactoryProvider); + var projectManagerAccessorMock = new Mock(MockBehavior.Strict); + projectManagerAccessorMock + .SetupGet(x => x.Instance) + .Returns(projectManager); + _projectManagerAccessor = projectManagerAccessorMock.Object; static IProjectEngineFactory CreateFactory(string configurationName) { @@ -72,13 +78,13 @@ static IProjectEngineFactory CreateFactory(string configurationName) public async Task GetTagHelpersAsync_WithSerializableCustomFactory_GoesOutOfProcess() { // Arrange - _projectManager.ProjectAdded(_hostProject_For_2_0); + _projectManagerAccessor.Instance.ProjectAdded(_hostProject_For_2_0); - var projectSnapshot = _projectManager.GetLoadedProject(_hostProject_For_2_0.Key).AssumeNotNull(); + var projectSnapshot = _projectManagerAccessor.Instance.GetLoadedProject(_hostProject_For_2_0.Key).AssumeNotNull(); var calledOutOfProcess = false; - var resolver = new TestResolver(_workspace, ErrorReporter, NoOpTelemetryReporter.Instance) + var resolver = new TestResolver(_projectManagerAccessor, ErrorReporter, NoOpTelemetryReporter.Instance) { OnResolveOutOfProcess = (p) => { @@ -101,13 +107,13 @@ public async Task GetTagHelpersAsync_WithSerializableCustomFactory_GoesOutOfProc public async Task GetTagHelpersAsync_WithNonSerializableCustomFactory_StaysInProcess() { // Arrange - _projectManager.ProjectAdded(_hostProject_For_NonSerializableConfiguration); + _projectManagerAccessor.Instance.ProjectAdded(_hostProject_For_NonSerializableConfiguration); - var projectSnapshot = _projectManager.GetLoadedProject(_hostProject_For_2_0.Key).AssumeNotNull(); + var projectSnapshot = _projectManagerAccessor.Instance.GetLoadedProject(_hostProject_For_2_0.Key).AssumeNotNull(); var calledInProcess = false; - var resolver = new TestResolver(_workspace, ErrorReporter, NoOpTelemetryReporter.Instance) + var resolver = new TestResolver(_projectManagerAccessor, ErrorReporter, NoOpTelemetryReporter.Instance) { OnResolveInProcess = (p) => { @@ -130,15 +136,15 @@ public async Task GetTagHelpersAsync_WithNonSerializableCustomFactory_StaysInPro public async Task GetTagHelpersAsync_OperationCanceledException_DoesNotGetWrapped() { // Arrange - _projectManager.ProjectAdded(_hostProject_For_2_0); + _projectManagerAccessor.Instance.ProjectAdded(_hostProject_For_2_0); - var projectSnapshot = _projectManager.GetLoadedProject(_hostProject_For_2_0.Key).AssumeNotNull(); + var projectSnapshot = _projectManagerAccessor.Instance.GetLoadedProject(_hostProject_For_2_0.Key).AssumeNotNull(); var calledOutOfProcess = false; var calledInProcess = false; var cancellationToken = new CancellationToken(canceled: true); - var resolver = new TestResolver(_workspace, ErrorReporter, NoOpTelemetryReporter.Instance) + var resolver = new TestResolver(_projectManagerAccessor, ErrorReporter, NoOpTelemetryReporter.Instance) { OnResolveInProcess = (p) => { @@ -166,7 +172,7 @@ public async Task GetTagHelpersAsync_OperationCanceledException_DoesNotGetWrappe public void CalculateTagHelpersFromDelta_NewProject() { // Arrange - var resolver = new TestResolver(_workspace, ErrorReporter, NoOpTelemetryReporter.Instance); + var resolver = new TestResolver(_projectManagerAccessor, ErrorReporter, NoOpTelemetryReporter.Instance); var initialDelta = new TagHelperDeltaResult(IsDelta: false, ResultId: 1, Project1TagHelperChecksums, ImmutableArray.Empty); // Act @@ -180,7 +186,7 @@ public void CalculateTagHelpersFromDelta_NewProject() public void CalculateTagHelpersFromDelta_DeltaFailedToApplyToKnownProject() { // Arrange - var resolver = new TestResolver(_workspace, ErrorReporter, NoOpTelemetryReporter.Instance); + var resolver = new TestResolver(_projectManagerAccessor, ErrorReporter, NoOpTelemetryReporter.Instance); var initialDelta = new TagHelperDeltaResult(IsDelta: false, ResultId: 1, Project1TagHelperChecksums, ImmutableArray.Empty); resolver.PublicProduceChecksumsFromDelta(Project1Id, lastResultId: -1, initialDelta); var newTagHelperSet = ImmutableArray.Create(TagHelper1_Project1.Checksum); @@ -197,7 +203,7 @@ public void CalculateTagHelpersFromDelta_DeltaFailedToApplyToKnownProject() public void CalculateTagHelpersFromDelta_NoopResult() { // Arrange - var resolver = new TestResolver(_workspace, ErrorReporter, NoOpTelemetryReporter.Instance); + var resolver = new TestResolver(_projectManagerAccessor, ErrorReporter, NoOpTelemetryReporter.Instance); var initialDelta = new TagHelperDeltaResult(IsDelta: false, ResultId: 1, Project1TagHelperChecksums, ImmutableArray.Empty); resolver.PublicProduceChecksumsFromDelta(Project1Id, lastResultId: -1, initialDelta); var noopDelta = new TagHelperDeltaResult(IsDelta: true, initialDelta.ResultId, ImmutableArray.Empty, ImmutableArray.Empty); @@ -213,7 +219,7 @@ public void CalculateTagHelpersFromDelta_NoopResult() public void CalculateTagHelpersFromDelta_ReplacedTagHelpers() { // Arrange - var resolver = new TestResolver(_workspace, ErrorReporter, NoOpTelemetryReporter.Instance); + var resolver = new TestResolver(_projectManagerAccessor, ErrorReporter, NoOpTelemetryReporter.Instance); var initialDelta = new TagHelperDeltaResult(IsDelta: false, ResultId: 1, Project1TagHelperChecksums, ImmutableArray.Empty); resolver.PublicProduceChecksumsFromDelta(Project1Id, lastResultId: -1, initialDelta); var changedDelta = new TagHelperDeltaResult(IsDelta: true, initialDelta.ResultId + 1, ImmutableArray.Create(TagHelper2_Project2.Checksum), ImmutableArray.Create(TagHelper2_Project1.Checksum)); diff --git a/src/Razor/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultRazorDocumentManagerTest.cs b/src/Razor/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultRazorDocumentManagerTest.cs index 7a5d4cfcf08..e48159a3505 100644 --- a/src/Razor/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultRazorDocumentManagerTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultRazorDocumentManagerTest.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.Editor; using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Utilities; @@ -28,7 +29,7 @@ public class DefaultRazorDocumentManagerTest : ProjectSnapshotManagerDispatcherT private readonly IContentType _razorCoreContentType; private readonly IContentType _nonRazorCoreContentType; - private readonly ProjectSnapshotManager _projectManager; + private readonly IProjectSnapshotManagerAccessor _projectManagerAccessor; private readonly WorkspaceEditorSettings _workspaceEditorSettings; private readonly ImportDocumentManager _importDocumentManager; @@ -43,12 +44,19 @@ public DefaultRazorDocumentManagerTest(ITestOutputHelper testOutput) c => c.IsOfType(It.IsAny()) == false, MockBehavior.Strict); - _projectManager = Mock.Of( + var projectManager = Mock.Of( p => p.GetProjects() == ImmutableArray.Empty && - p.GetLoadedProject(It.IsAny()) == null && - p.GetAllProjectKeys(It.IsAny()) == System.Collections.Immutable.ImmutableArray.Empty, + p.GetLoadedProject(It.IsAny()) == null && + p.GetAllProjectKeys(It.IsAny()) == ImmutableArray.Empty, MockBehavior.Strict); + var projectManagerAccessorMock = new Mock(MockBehavior.Strict); + projectManagerAccessorMock + .SetupGet(x => x.Instance) + .Returns(projectManager); + + _projectManagerAccessor = projectManagerAccessorMock.Object; + _workspaceEditorSettings = new DefaultWorkspaceEditorSettings( Mock.Of(MockBehavior.Strict)); @@ -85,7 +93,7 @@ public async Task OnTextViewOpened_ForRazorTextBuffer_AddsTextViewToTracker() Mock.Of(b => b.ContentType == _razorCoreContentType && b.Properties == new PropertyCollection(), MockBehavior.Strict), }; var documentTracker = new DefaultVisualStudioDocumentTracker( - Dispatcher, JoinableTaskContext, FilePath, ProjectPath, _projectManager, _workspaceEditorSettings, + Dispatcher, JoinableTaskContext, FilePath, ProjectPath, _projectManagerAccessor, _workspaceEditorSettings, ProjectEngineFactories.DefaultProvider, buffers[0], _importDocumentManager) as VisualStudioDocumentTracker; var editorFactoryService = Mock.Of( factoryService => factoryService.TryGetDocumentTracker( @@ -110,7 +118,7 @@ public async Task OnTextViewOpened_SubscribesAfterFirstTextViewOpened() Mock.Of(b => b.ContentType == _nonRazorCoreContentType && b.Properties == new PropertyCollection(), MockBehavior.Strict), }; var documentTracker = new DefaultVisualStudioDocumentTracker( - Dispatcher, JoinableTaskContext, FilePath, ProjectPath, _projectManager, _workspaceEditorSettings, ProjectEngineFactories.DefaultProvider, buffers[0], _importDocumentManager) as VisualStudioDocumentTracker; + Dispatcher, JoinableTaskContext, FilePath, ProjectPath, _projectManagerAccessor, _workspaceEditorSettings, ProjectEngineFactories.DefaultProvider, buffers[0], _importDocumentManager) as VisualStudioDocumentTracker; var editorFactoryService = Mock.Of(f => f.TryGetDocumentTracker(It.IsAny(), out documentTracker) == true, MockBehavior.Strict); var documentManager = new DefaultRazorDocumentManager(Dispatcher, JoinableTaskContext, editorFactoryService); @@ -156,14 +164,14 @@ public async Task OnTextViewClosed_ForAnyTextBufferWithTracker_RemovesTextView() // Preload the buffer's properties with a tracker, so it's like we've already tracked this one. var documentTracker = new DefaultVisualStudioDocumentTracker( - Dispatcher, JoinableTaskContext, FilePath, ProjectPath, _projectManager, _workspaceEditorSettings, + Dispatcher, JoinableTaskContext, FilePath, ProjectPath, _projectManagerAccessor, _workspaceEditorSettings, ProjectEngineFactories.DefaultProvider, buffers[0], _importDocumentManager); documentTracker.AddTextView(textView1); documentTracker.AddTextView(textView2); buffers[0].Properties.AddProperty(typeof(VisualStudioDocumentTracker), documentTracker); documentTracker = new DefaultVisualStudioDocumentTracker( - Dispatcher, JoinableTaskContext, FilePath, ProjectPath, _projectManager, _workspaceEditorSettings, + Dispatcher, JoinableTaskContext, FilePath, ProjectPath, _projectManagerAccessor, _workspaceEditorSettings, ProjectEngineFactories.DefaultProvider, buffers[1], _importDocumentManager); documentTracker.AddTextView(textView1); documentTracker.AddTextView(textView2); @@ -195,7 +203,7 @@ public async Task OnTextViewClosed_UnsubscribesAfterLastTextViewClosed() Mock.Of(b => b.ContentType == _nonRazorCoreContentType && b.Properties == new PropertyCollection(), MockBehavior.Strict), }; var documentTracker = new DefaultVisualStudioDocumentTracker( - Dispatcher, JoinableTaskContext, FilePath, ProjectPath, _projectManager, _workspaceEditorSettings, + Dispatcher, JoinableTaskContext, FilePath, ProjectPath, _projectManagerAccessor, _workspaceEditorSettings, ProjectEngineFactories.DefaultProvider, buffers[0], _importDocumentManager); buffers[0].Properties.AddProperty(typeof(VisualStudioDocumentTracker), documentTracker); var editorFactoryService = Mock.Of(MockBehavior.Strict); diff --git a/src/Razor/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioDocumentTrackerTest.cs b/src/Razor/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioDocumentTrackerTest.cs index b7c991fb0d2..aa248462837 100644 --- a/src/Razor/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioDocumentTrackerTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioDocumentTrackerTest.cs @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -#nullable disable - using System; using System.Collections.Generic; using Microsoft.AspNetCore.Razor.Language; @@ -10,10 +8,10 @@ using Microsoft.AspNetCore.Razor.Test.Common.Editor; using Microsoft.AspNetCore.Razor.Test.Common.ProjectSystem; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.Editor; using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.VisualStudio.Editor.Razor.Documents; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; @@ -30,15 +28,13 @@ public class DefaultVisualStudioDocumentTrackerTest : ProjectSnapshotManagerDisp private readonly ITextBuffer _textBuffer; private readonly string _filePath; private readonly string _projectPath; - private readonly string _rootNamespace; + private readonly string? _rootNamespace; private readonly HostProject _hostProject; private readonly HostProject _updatedHostProject; private readonly HostProject _otherHostProject; - private Project _workspaceProject; + private Project? _workspaceProject; private readonly ImportDocumentManager _importDocumentManager; private readonly WorkspaceEditorSettings _workspaceEditorSettings; - private readonly List _someTagHelpers; - private TestTagHelperResolver _tagHelperResolver; private readonly ProjectSnapshotManagerBase _projectManager; private readonly DefaultVisualStudioDocumentTracker _documentTracker; @@ -58,13 +54,13 @@ public DefaultVisualStudioDocumentTrackerTest(ITestOutputHelper testOutput) _workspaceEditorSettings = new DefaultWorkspaceEditorSettings(Mock.Of(MockBehavior.Strict)); - _someTagHelpers = new List() - { - TagHelperDescriptorBuilder.Create("test", "test").Build(), - }; - _projectManager = new TestProjectSnapshotManager(Workspace, ProjectEngineFactoryProvider, Dispatcher) { AllowNotifyListeners = true }; + var projectManagerAccessorMock = new Mock(MockBehavior.Strict); + projectManagerAccessorMock + .SetupGet(x => x.Instance) + .Returns(_projectManager); + _hostProject = new HostProject(_projectPath, TestProjectData.SomeProject.IntermediateOutputPath, FallbackRazorConfiguration.MVC_2_1, _rootNamespace); _updatedHostProject = new HostProject(_projectPath, TestProjectData.SomeProject.IntermediateOutputPath, FallbackRazorConfiguration.MVC_2_0, _rootNamespace); _otherHostProject = new HostProject(TestProjectData.AnotherProject.FilePath, TestProjectData.AnotherProject.IntermediateOutputPath, FallbackRazorConfiguration.MVC_2_0, TestProjectData.AnotherProject.RootNamespace); @@ -74,19 +70,13 @@ public DefaultVisualStudioDocumentTrackerTest(ITestOutputHelper testOutput) JoinableTaskFactory.Context, _filePath, _projectPath, - _projectManager, + projectManagerAccessorMock.Object, _workspaceEditorSettings, ProjectEngineFactoryProvider, _textBuffer, _importDocumentManager); } - protected override void ConfigureWorkspaceServices(List services) - { - _tagHelperResolver = new TestTagHelperResolver(); - services.Add(_tagHelperResolver); - } - protected override void ConfigureWorkspace(AdhocWorkspace workspace) { _workspaceProject = workspace.AddProject(ProjectInfo.Create( @@ -164,7 +154,7 @@ public void EditorSettingsManager_Changed_TriggersContextChanged() }; // Act - _documentTracker.EditorSettingsManager_Changed(null, null); + _documentTracker.EditorSettingsManager_Changed(null!, null!); // Assert Assert.True(called); @@ -176,7 +166,7 @@ public void ProjectManager_Changed_ProjectAdded_TriggersContextChanged() // Arrange _projectManager.ProjectAdded(_hostProject); - var e = new ProjectChangeEventArgs(null, _projectManager.GetLoadedProject(_hostProject.Key), ProjectChangeKind.ProjectAdded); + var e = new ProjectChangeEventArgs(null!, _projectManager.GetLoadedProject(_hostProject.Key)!, ProjectChangeKind.ProjectAdded); var called = false; _documentTracker.ContextChanged += (sender, args) => @@ -199,7 +189,7 @@ public void ProjectManager_Changed_ProjectChanged_TriggersContextChanged() // Arrange _projectManager.ProjectAdded(_hostProject); - var e = new ProjectChangeEventArgs(null, _projectManager.GetLoadedProject(_hostProject.Key), ProjectChangeKind.ProjectChanged); + var e = new ProjectChangeEventArgs(null!, _projectManager.GetLoadedProject(_hostProject.Key)!, ProjectChangeKind.ProjectChanged); var called = false; _documentTracker.ContextChanged += (sender, args) => @@ -225,7 +215,7 @@ public void ProjectManager_Changed_ProjectRemoved_TriggersContextChanged_WithEph var project = _projectManager.GetLoadedProject(_hostProject.Key); _projectManager.ProjectRemoved(_hostProject.Key); - var e = new ProjectChangeEventArgs(project, null, ProjectChangeKind.ProjectRemoved); + var e = new ProjectChangeEventArgs(project!, null!, ProjectChangeKind.ProjectRemoved); var called = false; _documentTracker.ContextChanged += (sender, args) => @@ -249,7 +239,7 @@ public void ProjectManager_Changed_IgnoresUnknownProject() // Arrange _projectManager.ProjectAdded(_otherHostProject); - var e = new ProjectChangeEventArgs(null, _projectManager.GetLoadedProject(_otherHostProject.Key), ProjectChangeKind.ProjectChanged); + var e = new ProjectChangeEventArgs(null!, _projectManager.GetLoadedProject(_otherHostProject.Key)!, ProjectChangeKind.ProjectChanged); var called = false; _documentTracker.ContextChanged += (sender, args) => called = true; @@ -275,7 +265,7 @@ public void Import_Changed_ImportAssociatedWithDocument_TriggersContextChanged() var importChangedArgs = new ImportChangedEventArgs("path/to/import", FileChangeKind.Changed, new[] { _filePath }); // Act - _documentTracker.Import_Changed(null, importChangedArgs); + _documentTracker.Import_Changed(null!, importChangedArgs); // Assert Assert.True(called); @@ -290,7 +280,7 @@ public void Import_Changed_UnrelatedImport_DoesNothing() var importChangedArgs = new ImportChangedEventArgs("path/to/import", FileChangeKind.Changed, new[] { "path/to/differentfile" }); // Act & Assert (Does not throw) - _documentTracker.Import_Changed(null, importChangedArgs); + _documentTracker.Import_Changed(null!, importChangedArgs); } [UIFact] diff --git a/src/Razor/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioRazorParserIntegrationTest.cs b/src/Razor/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioRazorParserIntegrationTest.cs index 72a148c9d00..3ca9aa68cf0 100644 --- a/src/Razor/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioRazorParserIntegrationTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioRazorParserIntegrationTest.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Collections.ObjectModel; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -14,6 +15,7 @@ using Microsoft.AspNetCore.Razor.Test.Common.Editor; using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Moq; @@ -764,8 +766,24 @@ public void Dispose() } } - private class TestCompletionBroker : VisualStudioCompletionBroker + private class TestCompletionBroker : ICompletionBroker { - public override bool IsCompletionActive(ITextView textView) => false; + public ICompletionSession CreateCompletionSession(ITextView textView, ITrackingPoint triggerPoint, bool trackCaret) + => throw new NotImplementedException(); + + public void DismissAllSessions(ITextView textView) + => throw new NotImplementedException(); + + public ReadOnlyCollection GetSessions(ITextView textView) + => throw new NotImplementedException(); + + public bool IsCompletionActive(ITextView textView) + => false; + + public ICompletionSession TriggerCompletion(ITextView textView) + => throw new NotImplementedException(); + + public ICompletionSession TriggerCompletion(ITextView textView, ITrackingPoint triggerPoint, bool trackCaret) + => throw new NotImplementedException(); } } diff --git a/src/Razor/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioRazorParserTest.cs b/src/Razor/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioRazorParserTest.cs index e8742cfedca..ca9d24e6948 100644 --- a/src/Razor/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioRazorParserTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioRazorParserTest.cs @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -#nullable disable - using System; using System.Threading; using System.Threading.Tasks; @@ -11,6 +9,7 @@ using Microsoft.AspNetCore.Razor.Test.Common.Editor; using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.VisualStudio.Language.Intellisense; using Moq; using Xunit; using Xunit.Abstractions; @@ -47,57 +46,54 @@ public DefaultVisualStudioRazorParserTest(ITestOutputHelper testOutput) } private VisualStudioDocumentTracker CreateDocumentTracker(bool isSupportedProject = true, int versionNumber = 0) - { - var documentTracker = Mock.Of(tracker => - tracker.TextBuffer == new TestTextBuffer(new StringTextSnapshot(string.Empty, versionNumber), /* contentType */ null) && + => Mock.Of(tracker => + tracker.TextBuffer == new TestTextBuffer(new StringTextSnapshot(string.Empty, versionNumber), /* contentType */ null) && tracker.ProjectPath == "c:\\SomeProject.csproj" && tracker.ProjectSnapshot == _projectSnapshot && tracker.FilePath == "c:\\SomeFilePath.cshtml" && tracker.IsSupportedProject == isSupportedProject, MockBehavior.Strict); - return documentTracker; - } + private DefaultVisualStudioRazorParser CreateParser(VisualStudioDocumentTracker documentTracker) + => new DefaultVisualStudioRazorParser( + JoinableTaskContext, + documentTracker, + _projectEngineFactoryProvider, + ErrorReporter, + Mock.Of(MockBehavior.Strict)); [UIFact] public async Task GetLatestCodeDocumentAsync_WaitsForParse() { // Arrange var documentTracker = CreateDocumentTracker(); - using (var parser = new DefaultVisualStudioRazorParser( - JoinableTaskContext, - documentTracker, - _projectEngineFactoryProvider, - ErrorReporter, - Mock.Of(MockBehavior.Strict))) - { - var latestChange = new SourceChange(0, 0, string.Empty); - var latestSnapshot = documentTracker.TextBuffer.CurrentSnapshot; - parser._latestChangeReference = new BackgroundParser.ChangeReference(latestChange, latestSnapshot); - var codeDocument = TestRazorCodeDocument.CreateEmpty(); - var syntaxTree = RazorSyntaxTree.Parse(TestRazorSourceDocument.Create()); - codeDocument.SetSyntaxTree(syntaxTree); - var args = new BackgroundParserResultsReadyEventArgs( - parser._latestChangeReference, - codeDocument); + using var parser = CreateParser(documentTracker); + var latestChange = new SourceChange(0, 0, string.Empty); + var latestSnapshot = documentTracker.TextBuffer.CurrentSnapshot; + parser._latestChangeReference = new BackgroundParser.ChangeReference(latestChange, latestSnapshot); + var codeDocument = TestRazorCodeDocument.CreateEmpty(); + var syntaxTree = RazorSyntaxTree.Parse(TestRazorSourceDocument.Create()); + codeDocument.SetSyntaxTree(syntaxTree); + var args = new BackgroundParserResultsReadyEventArgs( + parser._latestChangeReference, + codeDocument); - // Act - 1 - var getLatestCodeDocumentTask = parser.GetLatestCodeDocumentAsync(StringTextSnapshot.Empty); + // Act - 1 + var getLatestCodeDocumentTask = parser.GetLatestCodeDocumentAsync(StringTextSnapshot.Empty); - // Assert - 1 - Assert.False(getLatestCodeDocumentTask.IsCompleted); + // Assert - 1 + Assert.False(getLatestCodeDocumentTask.IsCompleted); - // Act - 2 - await Task.Run(() => parser.OnResultsReady(sender: null, args)); + // Act - 2 + await Task.Run(() => parser.OnResultsReady(sender: null!, args)); - // Assert - 2 - Assert.True(getLatestCodeDocumentTask.IsCompleted); + // Assert - 2 + Assert.True(getLatestCodeDocumentTask.IsCompleted); - // Act - 3 - var latestCodeDocument = await getLatestCodeDocumentTask; + // Act - 3 + var latestCodeDocument = await getLatestCodeDocumentTask; - // Assert - 3 - Assert.Same(latestCodeDocument, codeDocument); - } + // Assert - 3 + Assert.Same(latestCodeDocument, codeDocument); } [UIFact] @@ -105,38 +101,31 @@ public async Task GetLatestCodeDocumentAsync_NoPendingChangesReturnsImmediately( { // Arrange var documentTracker = CreateDocumentTracker(); - using (var parser = new DefaultVisualStudioRazorParser( - JoinableTaskContext, - documentTracker, - _projectEngineFactoryProvider, - ErrorReporter, - Mock.Of(MockBehavior.Strict))) - { - var latestChange = new SourceChange(0, 0, string.Empty); - var latestSnapshot = documentTracker.TextBuffer.CurrentSnapshot; - parser._latestChangeReference = new BackgroundParser.ChangeReference(latestChange, latestSnapshot); - var codeDocument = TestRazorCodeDocument.CreateEmpty(); - var syntaxTree = RazorSyntaxTree.Parse(TestRazorSourceDocument.Create()); - codeDocument.SetSyntaxTree(syntaxTree); - var args = new BackgroundParserResultsReadyEventArgs( - parser._latestChangeReference, - codeDocument); + using var parser = CreateParser(documentTracker); + var latestChange = new SourceChange(0, 0, string.Empty); + var latestSnapshot = documentTracker.TextBuffer.CurrentSnapshot; + parser._latestChangeReference = new BackgroundParser.ChangeReference(latestChange, latestSnapshot); + var codeDocument = TestRazorCodeDocument.CreateEmpty(); + var syntaxTree = RazorSyntaxTree.Parse(TestRazorSourceDocument.Create()); + codeDocument.SetSyntaxTree(syntaxTree); + var args = new BackgroundParserResultsReadyEventArgs( + parser._latestChangeReference, + codeDocument); - // Initialize the document with some content so we have a syntax tree to return. - await Task.Run(() => parser.OnResultsReady(sender: null, args)); + // Initialize the document with some content so we have a syntax tree to return. + await Task.Run(() => parser.OnResultsReady(sender: null!, args)); - // Act - 1 - var getLatestCodeDocumentTask = parser.GetLatestCodeDocumentAsync(StringTextSnapshot.Empty); + // Act - 1 + var getLatestCodeDocumentTask = parser.GetLatestCodeDocumentAsync(StringTextSnapshot.Empty); - // Assert - 1 - Assert.True(getLatestCodeDocumentTask.IsCompleted); + // Assert - 1 + Assert.True(getLatestCodeDocumentTask.IsCompleted); - // Act - 2 - var latestCodeDocument = await getLatestCodeDocumentTask; + // Act - 2 + var latestCodeDocument = await getLatestCodeDocumentTask; - // Assert - 2 - Assert.Same(latestCodeDocument, codeDocument); - } + // Assert - 2 + Assert.Same(latestCodeDocument, codeDocument); } [UIFact] @@ -144,25 +133,18 @@ public void GetLatestCodeDocumentAsync_MultipleCallsSameSnapshotMemoizesReturned { // Arrange var documentTracker = CreateDocumentTracker(); - using (var parser = new DefaultVisualStudioRazorParser( - JoinableTaskContext, - documentTracker, - _projectEngineFactoryProvider, - ErrorReporter, - Mock.Of(MockBehavior.Strict))) - { - var latestChange = new SourceChange(0, 0, string.Empty); - var latestSnapshot = documentTracker.TextBuffer.CurrentSnapshot; - parser._latestChangeReference = new BackgroundParser.ChangeReference(latestChange, latestSnapshot); - var sameSnapshot = StringTextSnapshot.Empty; + using var parser = CreateParser(documentTracker); + var latestChange = new SourceChange(0, 0, string.Empty); + var latestSnapshot = documentTracker.TextBuffer.CurrentSnapshot; + parser._latestChangeReference = new BackgroundParser.ChangeReference(latestChange, latestSnapshot); + var sameSnapshot = StringTextSnapshot.Empty; - // Act - var getLatestCodeDocumentTask1 = parser.GetLatestCodeDocumentAsync(sameSnapshot); - var getLatestCodeDocumentTask2 = parser.GetLatestCodeDocumentAsync(sameSnapshot); + // Act + var getLatestCodeDocumentTask1 = parser.GetLatestCodeDocumentAsync(sameSnapshot); + var getLatestCodeDocumentTask2 = parser.GetLatestCodeDocumentAsync(sameSnapshot); - // Assert - Assert.Same(getLatestCodeDocumentTask1, getLatestCodeDocumentTask2); - } + // Assert + Assert.Same(getLatestCodeDocumentTask1, getLatestCodeDocumentTask2); } [UIFact] @@ -170,26 +152,19 @@ public void GetLatestCodeDocumentAsync_MultipleCallsDifferentSnapshotsReturnDiff { // Arrange var documentTracker = CreateDocumentTracker(); - using (var parser = new DefaultVisualStudioRazorParser( - JoinableTaskContext, - documentTracker, - _projectEngineFactoryProvider, - ErrorReporter, - Mock.Of(MockBehavior.Strict))) - { - var latestChange = new SourceChange(0, 0, string.Empty); - var latestSnapshot = documentTracker.TextBuffer.CurrentSnapshot; - parser._latestChangeReference = new BackgroundParser.ChangeReference(latestChange, latestSnapshot); - var snapshot1 = new StringTextSnapshot("Snapshot 1"); - var snapshot2 = new StringTextSnapshot("Snapshot 2"); + using var parser = CreateParser(documentTracker); + var latestChange = new SourceChange(0, 0, string.Empty); + var latestSnapshot = documentTracker.TextBuffer.CurrentSnapshot; + parser._latestChangeReference = new BackgroundParser.ChangeReference(latestChange, latestSnapshot); + var snapshot1 = new StringTextSnapshot("Snapshot 1"); + var snapshot2 = new StringTextSnapshot("Snapshot 2"); - // Act - var getLatestCodeDocumentTask1 = parser.GetLatestCodeDocumentAsync(snapshot1); - var getLatestCodeDocumentTask2 = parser.GetLatestCodeDocumentAsync(snapshot2); + // Act + var getLatestCodeDocumentTask1 = parser.GetLatestCodeDocumentAsync(snapshot1); + var getLatestCodeDocumentTask2 = parser.GetLatestCodeDocumentAsync(snapshot2); - // Assert - Assert.NotSame(getLatestCodeDocumentTask1, getLatestCodeDocumentTask2); - } + // Assert + Assert.NotSame(getLatestCodeDocumentTask1, getLatestCodeDocumentTask2); } [UIFact] @@ -198,38 +173,31 @@ public async Task GetLatestCodeDocumentAsync_LatestChangeIsNewerThenRequested_Re // Arrange var documentTracker = CreateDocumentTracker(versionNumber: 1337); var olderSnapshot = new StringTextSnapshot("Older", versionNumber: 910); - using (var parser = new DefaultVisualStudioRazorParser( - JoinableTaskContext, - documentTracker, - _projectEngineFactoryProvider, - ErrorReporter, - Mock.Of(MockBehavior.Strict))) - { - var latestChange = new SourceChange(0, 0, string.Empty); - var latestSnapshot = documentTracker.TextBuffer.CurrentSnapshot; - parser._latestChangeReference = new BackgroundParser.ChangeReference(latestChange, latestSnapshot); - var codeDocument = TestRazorCodeDocument.CreateEmpty(); - var syntaxTree = RazorSyntaxTree.Parse(TestRazorSourceDocument.Create()); - codeDocument.SetSyntaxTree(syntaxTree); - var args = new BackgroundParserResultsReadyEventArgs( - parser._latestChangeReference, - codeDocument); + using var parser = CreateParser(documentTracker); + var latestChange = new SourceChange(0, 0, string.Empty); + var latestSnapshot = documentTracker.TextBuffer.CurrentSnapshot; + parser._latestChangeReference = new BackgroundParser.ChangeReference(latestChange, latestSnapshot); + var codeDocument = TestRazorCodeDocument.CreateEmpty(); + var syntaxTree = RazorSyntaxTree.Parse(TestRazorSourceDocument.Create()); + codeDocument.SetSyntaxTree(syntaxTree); + var args = new BackgroundParserResultsReadyEventArgs( + parser._latestChangeReference, + codeDocument); - // Initialize the document with some content so we have a syntax tree to return. - await Task.Run(() => parser.OnResultsReady(sender: null, args)); + // Initialize the document with some content so we have a syntax tree to return. + await Task.Run(() => parser.OnResultsReady(sender: null!, args)); - // Act - 1 - var getLatestCodeDocumentTask = parser.GetLatestCodeDocumentAsync(olderSnapshot); + // Act - 1 + var getLatestCodeDocumentTask = parser.GetLatestCodeDocumentAsync(olderSnapshot); - // Assert - 1 - Assert.True(getLatestCodeDocumentTask.IsCompleted); + // Assert - 1 + Assert.True(getLatestCodeDocumentTask.IsCompleted); - // Act - 2 - var latestCodeDocument = await getLatestCodeDocumentTask; + // Act - 2 + var latestCodeDocument = await getLatestCodeDocumentTask; - // Assert - 2 - Assert.Same(latestCodeDocument, codeDocument); - } + // Assert - 2 + Assert.Same(latestCodeDocument, codeDocument); } [UIFact] @@ -241,12 +209,7 @@ public async Task GetLatestCodeDocumentAsync_ParserDisposed_ReturnsImmediately() var syntaxTree = RazorSyntaxTree.Parse(TestRazorSourceDocument.Create()); DefaultVisualStudioRazorParser parser; codeDocument.SetSyntaxTree(syntaxTree); - using (parser = new DefaultVisualStudioRazorParser( - JoinableTaskContext, - documentTracker, - _projectEngineFactoryProvider, - ErrorReporter, - Mock.Of(MockBehavior.Strict))) + using (parser = CreateParser(documentTracker)) { var latestChange = new SourceChange(0, 0, string.Empty); var latestSnapshot = documentTracker.TextBuffer.CurrentSnapshot; @@ -256,7 +219,7 @@ public async Task GetLatestCodeDocumentAsync_ParserDisposed_ReturnsImmediately() codeDocument); // Initialize the document with some content so we have a syntax tree to return. - await Task.Run(() => parser.OnResultsReady(sender: null, args)); + await Task.Run(() => parser.OnResultsReady(sender: null!, args)); } var newerSnapshot = new StringTextSnapshot("Newer", versionNumber: 1337); @@ -383,12 +346,7 @@ public void CodeDocumentRequest_CancelToCompleteNoops() public void ReparseOnUIThread_NoopsIfDisposed() { // Arrange - var parser = new DefaultVisualStudioRazorParser( - JoinableTaskContext, - CreateDocumentTracker(), - _projectEngineFactoryProvider, - ErrorReporter, - Mock.Of(MockBehavior.Strict)); + var parser = CreateParser(CreateDocumentTracker()); parser.Dispose(); // Act & Assert @@ -399,12 +357,7 @@ public void ReparseOnUIThread_NoopsIfDisposed() public void OnIdle_NoopsIfDisposed() { // Arrange - var parser = new DefaultVisualStudioRazorParser( - JoinableTaskContext, - CreateDocumentTracker(), - _projectEngineFactoryProvider, - ErrorReporter, - Mock.Of(MockBehavior.Strict)); + var parser = CreateParser(CreateDocumentTracker()); parser.Dispose(); // Act & Assert @@ -415,12 +368,7 @@ public void OnIdle_NoopsIfDisposed() public void OnDocumentStructureChanged_NoopsIfDisposed() { // Arrange - var parser = new DefaultVisualStudioRazorParser( - JoinableTaskContext, - CreateDocumentTracker(), - _projectEngineFactoryProvider, - ErrorReporter, - Mock.Of(MockBehavior.Strict)); + var parser = CreateParser(CreateDocumentTracker()); parser.Dispose(); // Act & Assert @@ -431,26 +379,19 @@ public void OnDocumentStructureChanged_NoopsIfDisposed() public void OnDocumentStructureChanged_IgnoresEditsThatAreOld() { // Arrange - using (var parser = new DefaultVisualStudioRazorParser( - JoinableTaskContext, - CreateDocumentTracker(), - _projectEngineFactoryProvider, - ErrorReporter, - Mock.Of(MockBehavior.Strict))) - { - var called = false; - parser.DocumentStructureChanged += (sender, e) => called = true; - parser._latestChangeReference = new BackgroundParser.ChangeReference(null, new StringTextSnapshot(string.Empty)); - var args = new BackgroundParserResultsReadyEventArgs( - new BackgroundParser.ChangeReference(new SourceChange(0, 0, string.Empty), new StringTextSnapshot(string.Empty)), - TestRazorCodeDocument.CreateEmpty()); + using var parser = CreateParser(CreateDocumentTracker()); + var called = false; + parser.DocumentStructureChanged += (sender, e) => called = true; + parser._latestChangeReference = new BackgroundParser.ChangeReference(null, new StringTextSnapshot(string.Empty)); + var args = new BackgroundParserResultsReadyEventArgs( + new BackgroundParser.ChangeReference(new SourceChange(0, 0, string.Empty), new StringTextSnapshot(string.Empty)), + TestRazorCodeDocument.CreateEmpty()); - // Act - parser.OnDocumentStructureChanged(args); + // Act + parser.OnDocumentStructureChanged(args); - // Assert - Assert.False(called); - } + // Assert + Assert.False(called); } [UIFact] @@ -458,30 +399,23 @@ public void OnDocumentStructureChanged_FiresForLatestTextBufferEdit() { // Arrange var documentTracker = CreateDocumentTracker(); - using (var parser = new DefaultVisualStudioRazorParser( - JoinableTaskContext, - documentTracker, - _projectEngineFactoryProvider, - ErrorReporter, - Mock.Of(MockBehavior.Strict))) - { - var called = false; - parser.DocumentStructureChanged += (sender, e) => called = true; - var latestChange = new SourceChange(0, 0, string.Empty); - var latestSnapshot = documentTracker.TextBuffer.CurrentSnapshot; - parser._latestChangeReference = new BackgroundParser.ChangeReference(latestChange, latestSnapshot); - var codeDocument = TestRazorCodeDocument.CreateEmpty(); - codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(TestRazorSourceDocument.Create())); - var args = new BackgroundParserResultsReadyEventArgs( - parser._latestChangeReference, - codeDocument); + using var parser = CreateParser(documentTracker); + var called = false; + parser.DocumentStructureChanged += (sender, e) => called = true; + var latestChange = new SourceChange(0, 0, string.Empty); + var latestSnapshot = documentTracker.TextBuffer.CurrentSnapshot; + parser._latestChangeReference = new BackgroundParser.ChangeReference(latestChange, latestSnapshot); + var codeDocument = TestRazorCodeDocument.CreateEmpty(); + codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(TestRazorSourceDocument.Create())); + var args = new BackgroundParserResultsReadyEventArgs( + parser._latestChangeReference, + codeDocument); - // Act - parser.OnDocumentStructureChanged(args); + // Act + parser.OnDocumentStructureChanged(args); - // Assert - Assert.True(called); - } + // Assert + Assert.True(called); } [UIFact] @@ -489,96 +423,69 @@ public void OnDocumentStructureChanged_FiresForOnlyLatestTextBufferReparseEdit() { // Arrange var documentTracker = CreateDocumentTracker(); - using (var parser = new DefaultVisualStudioRazorParser( - JoinableTaskContext, - documentTracker, - _projectEngineFactoryProvider, - ErrorReporter, - Mock.Of(MockBehavior.Strict))) - { - var called = false; - parser.DocumentStructureChanged += (sender, e) => called = true; - var latestSnapshot = documentTracker.TextBuffer.CurrentSnapshot; - parser._latestChangeReference = new BackgroundParser.ChangeReference(null, latestSnapshot); - var codeDocument = TestRazorCodeDocument.CreateEmpty(); - codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(TestRazorSourceDocument.Create())); - var badArgs = new BackgroundParserResultsReadyEventArgs( - // This is a different reparse edit, shouldn't be fired for this call - new BackgroundParser.ChangeReference(null, latestSnapshot), - codeDocument); - var goodArgs = new BackgroundParserResultsReadyEventArgs( - parser._latestChangeReference, - codeDocument); + using var parser = CreateParser(documentTracker); + var called = false; + parser.DocumentStructureChanged += (sender, e) => called = true; + var latestSnapshot = documentTracker.TextBuffer.CurrentSnapshot; + parser._latestChangeReference = new BackgroundParser.ChangeReference(null, latestSnapshot); + var codeDocument = TestRazorCodeDocument.CreateEmpty(); + codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(TestRazorSourceDocument.Create())); + var badArgs = new BackgroundParserResultsReadyEventArgs( + // This is a different reparse edit, shouldn't be fired for this call + new BackgroundParser.ChangeReference(null, latestSnapshot), + codeDocument); + var goodArgs = new BackgroundParserResultsReadyEventArgs( + parser._latestChangeReference, + codeDocument); - // Act - 1 - parser.OnDocumentStructureChanged(badArgs); + // Act - 1 + parser.OnDocumentStructureChanged(badArgs); - // Assert - 1 - Assert.False(called); + // Assert - 1 + Assert.False(called); - // Act - 2 - parser.OnDocumentStructureChanged(goodArgs); + // Act - 2 + parser.OnDocumentStructureChanged(goodArgs); - // Assert - 2 - Assert.True(called); - } + // Assert - 2 + Assert.True(called); } [UIFact] public void StartIdleTimer_DoesNotRestartTimerWhenAlreadyRunning() { // Arrange - using (var parser = new DefaultVisualStudioRazorParser( - JoinableTaskContext, - CreateDocumentTracker(), - _projectEngineFactoryProvider, - ErrorReporter, - Mock.Of(MockBehavior.Strict)) - { - BlockBackgroundIdleWork = new ManualResetEventSlim(), - _idleDelay = TimeSpan.FromSeconds(5) - }) - { - parser.StartIdleTimer(); - using (var currentTimer = parser._idleTimer) - { - - // Act - parser.StartIdleTimer(); - var afterTimer = parser._idleTimer; - - // Assert - Assert.NotNull(currentTimer); - Assert.Same(currentTimer, afterTimer); - } - } + using var parser = CreateParser(CreateDocumentTracker()); + parser.BlockBackgroundIdleWork = new ManualResetEventSlim(); + parser._idleDelay = TimeSpan.FromSeconds(5); + parser.StartIdleTimer(); + using var currentTimer = parser._idleTimer; + + // Act + parser.StartIdleTimer(); + var afterTimer = parser._idleTimer; + + // Assert + Assert.NotNull(currentTimer); + Assert.Same(currentTimer, afterTimer); } [UIFact] public void StopIdleTimer_StopsTimer() { // Arrange - using (var parser = new DefaultVisualStudioRazorParser( - JoinableTaskContext, - CreateDocumentTracker(), - _projectEngineFactoryProvider, - ErrorReporter, - Mock.Of(MockBehavior.Strict)) - { - BlockBackgroundIdleWork = new ManualResetEventSlim(), - _idleDelay = TimeSpan.FromSeconds(5) - }) - { - parser.StartIdleTimer(); - var currentTimer = parser._idleTimer; + using var parser = CreateParser(CreateDocumentTracker()); + parser.BlockBackgroundIdleWork = new ManualResetEventSlim(); + parser._idleDelay = TimeSpan.FromSeconds(5); + parser.StartIdleTimer(); + var currentTimer = parser._idleTimer; - // Act - parser.StopIdleTimer(); + // Act + parser.StopIdleTimer(); - // Assert - Assert.NotNull(currentTimer); - Assert.Null(parser._idleTimer); - } + // Assert + Assert.NotNull(currentTimer); + Assert.Null(parser._idleTimer); } [UIFact] @@ -587,22 +494,15 @@ public void StopParser_DetachesFromTextBufferChangeLoop() // Arrange var documentTracker = CreateDocumentTracker(); var textBuffer = (TestTextBuffer)documentTracker.TextBuffer; - using (var parser = new DefaultVisualStudioRazorParser( - JoinableTaskContext, - CreateDocumentTracker(), - _projectEngineFactoryProvider, - ErrorReporter, - Mock.Of(MockBehavior.Strict))) - { - parser.StartParser(); + using var parser = CreateParser(documentTracker); + parser.StartParser(); - // Act - parser.StopParser(); + // Act + parser.StopParser(); - // Assert - Assert.Empty(textBuffer.AttachedChangedEvents); - Assert.Null(parser._parser); - } + // Assert + Assert.Empty(textBuffer.AttachedChangedEvents); + Assert.Null(parser._parser); } [UIFact] @@ -611,57 +511,39 @@ public void StartParser_AttachesToTextBufferChangeLoop() // Arrange var documentTracker = CreateDocumentTracker(); var textBuffer = (TestTextBuffer)documentTracker.TextBuffer; - using (var parser = new DefaultVisualStudioRazorParser( - JoinableTaskContext, - documentTracker, - _projectEngineFactoryProvider, - ErrorReporter, - Mock.Of(MockBehavior.Strict))) - { - // Act - parser.StartParser(); + using var parser = CreateParser(documentTracker); - // Assert - Assert.Single(textBuffer.AttachedChangedEvents); - Assert.NotNull(parser._parser); - } + // Act + parser.StartParser(); + + // Assert + Assert.Single(textBuffer.AttachedChangedEvents); + Assert.NotNull(parser._parser); } [UIFact] public void TryReinitializeParser_ReturnsTrue_IfProjectIsSupported() { // Arrange - using (var parser = new DefaultVisualStudioRazorParser( - JoinableTaskContext, - CreateDocumentTracker(isSupportedProject: true), - _projectEngineFactoryProvider, - ErrorReporter, - Mock.Of(MockBehavior.Strict))) - { - // Act - var result = parser.TryReinitializeParser(); + using var parser = CreateParser(CreateDocumentTracker(isSupportedProject: true)); - // Assert - Assert.True(result); - } + // Act + var result = parser.TryReinitializeParser(); + + // Assert + Assert.True(result); } [UIFact] public void TryReinitializeParser_ReturnsFalse_IfProjectIsNotSupported() { // Arrange - using (var parser = new DefaultVisualStudioRazorParser( - JoinableTaskContext, - CreateDocumentTracker(isSupportedProject: false), - _projectEngineFactoryProvider, - ErrorReporter, - Mock.Of(MockBehavior.Strict))) - { - // Act - var result = parser.TryReinitializeParser(); + using var parser = CreateParser(CreateDocumentTracker(isSupportedProject: false)); - // Assert - Assert.False(result); - } + // Act + var result = parser.TryReinitializeParser(); + + // Assert + Assert.False(result); } } diff --git a/src/Razor/test/Microsoft.VisualStudio.Editor.Razor.Test/ProjectWorkspaceStateGeneratorTest.cs b/src/Razor/test/Microsoft.VisualStudio.Editor.Razor.Test/ProjectWorkspaceStateGeneratorTest.cs index 00049c5e1b2..26c5a6031af 100644 --- a/src/Razor/test/Microsoft.VisualStudio.Editor.Razor.Test/ProjectWorkspaceStateGeneratorTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.Editor.Razor.Test/ProjectWorkspaceStateGeneratorTest.cs @@ -1,14 +1,12 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -#nullable disable - using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.ProjectEngineHost; using Microsoft.AspNetCore.Razor.ProjectSystem; @@ -17,7 +15,6 @@ using Microsoft.AspNetCore.Razor.Test.Common.Editor; using Microsoft.AspNetCore.Razor.Test.Common.ProjectSystem; using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Moq; using Xunit; @@ -28,7 +25,7 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces; public class ProjectWorkspaceStateGeneratorTest : ProjectSnapshotManagerDispatcherTestBase { private readonly IProjectEngineFactoryProvider _projectEngineFactoryProvider; - private readonly ImmutableArray _resolvableTagHelpers; + private readonly TestTagHelperResolver _tagHelperResolver; private readonly Workspace _workspace; private readonly Project _workspaceProject; private readonly ProjectSnapshot _projectSnapshot; @@ -39,14 +36,10 @@ public ProjectWorkspaceStateGeneratorTest(ITestOutputHelper testOutput) { _projectEngineFactoryProvider = Mock.Of(MockBehavior.Strict); - var tagHelperResolver = new TestTagHelperResolver() - { - TagHelpers = ImmutableArray.Create(TagHelperDescriptorBuilder.Create("ResolvableTagHelper", "TestAssembly").Build()) - }; + _tagHelperResolver = new TestTagHelperResolver( + ImmutableArray.Create(TagHelperDescriptorBuilder.Create("ResolvableTagHelper", "TestAssembly").Build())); - _resolvableTagHelpers = tagHelperResolver.TagHelpers; - var workspaceServices = new List() { tagHelperResolver }; - var testServices = TestServices.Create(workspaceServices, Enumerable.Empty()); + var testServices = TestServices.Create([], []); _workspace = TestWorkspace.Create(testServices); AddDisposable(_workspace); var projectId = ProjectId.CreateNewId("Test"); @@ -57,109 +50,111 @@ public ProjectWorkspaceStateGeneratorTest(ITestOutputHelper testOutput) "Test", LanguageNames.CSharp, TestProjectData.SomeProject.FilePath)); - _workspaceProject = solution.GetProject(projectId); + _workspaceProject = solution.GetProject(projectId).AssumeNotNull(); _projectSnapshot = new ProjectSnapshot( ProjectState.Create(_projectEngineFactoryProvider, TestProjectData.SomeProject, ProjectWorkspaceState.Default)); _projectWorkspaceStateWithTagHelpers = ProjectWorkspaceState.Create(ImmutableArray.Create( TagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly").Build())); } + private IProjectSnapshotManagerAccessor CreateProjectManagerAccessor() + { + var projectManager = new TestProjectSnapshotManager(_workspace, _projectEngineFactoryProvider, Dispatcher); + var mock = new Mock(MockBehavior.Strict); + mock.SetupGet(x => x.Instance).Returns(projectManager); + + return mock.Object; + } + [UIFact] public void Dispose_MakesUpdateNoop() { // Arrange - using (var stateGenerator = new ProjectWorkspaceStateGenerator(Dispatcher, NoOpTelemetryReporter.Instance)) - { - stateGenerator.BlockBackgroundWorkStart = new ManualResetEventSlim(initialState: false); + var projectManagerAccessor = CreateProjectManagerAccessor(); + using var stateGenerator = new ProjectWorkspaceStateGenerator(projectManagerAccessor, _tagHelperResolver, Dispatcher, ErrorReporter, NoOpTelemetryReporter.Instance); + stateGenerator.BlockBackgroundWorkStart = new ManualResetEventSlim(initialState: false); - // Act - stateGenerator.Dispose(); - stateGenerator.Update(_workspaceProject, _projectSnapshot, DisposalToken); + // Act + stateGenerator.Dispose(); + stateGenerator.Update(_workspaceProject, _projectSnapshot, DisposalToken); - // Assert - Assert.Empty(stateGenerator.Updates); - } + // Assert + Assert.Empty(stateGenerator.Updates); } [UIFact] public void Update_StartsUpdateTask() { // Arrange - using (var stateGenerator = new ProjectWorkspaceStateGenerator(Dispatcher, NoOpTelemetryReporter.Instance)) - { - stateGenerator.BlockBackgroundWorkStart = new ManualResetEventSlim(initialState: false); + var projectManagerAccessor = CreateProjectManagerAccessor(); + using var stateGenerator = new ProjectWorkspaceStateGenerator(projectManagerAccessor, _tagHelperResolver, Dispatcher, ErrorReporter, NoOpTelemetryReporter.Instance); + stateGenerator.BlockBackgroundWorkStart = new ManualResetEventSlim(initialState: false); - // Act - stateGenerator.Update(_workspaceProject, _projectSnapshot, DisposalToken); + // Act + stateGenerator.Update(_workspaceProject, _projectSnapshot, DisposalToken); - // Assert - var update = Assert.Single(stateGenerator.Updates); - Assert.False(update.Value.Task.IsCompleted); - } + // Assert + var update = Assert.Single(stateGenerator.Updates); + Assert.False(update.Value.Task.IsCompleted); } [UIFact] public void Update_SoftCancelsIncompleteTaskForSameProject() { // Arrange - using (var stateGenerator = new ProjectWorkspaceStateGenerator(Dispatcher, NoOpTelemetryReporter.Instance)) - { - stateGenerator.BlockBackgroundWorkStart = new ManualResetEventSlim(initialState: false); - stateGenerator.Update(_workspaceProject, _projectSnapshot, DisposalToken); - var initialUpdate = stateGenerator.Updates.Single().Value; - - // Act - stateGenerator.Update(_workspaceProject, _projectSnapshot, DisposalToken); - - // Assert - Assert.True(initialUpdate.Cts.IsCancellationRequested); - } + var projectManagerAccessor = CreateProjectManagerAccessor(); + using var stateGenerator = new ProjectWorkspaceStateGenerator(projectManagerAccessor, _tagHelperResolver, Dispatcher, ErrorReporter, NoOpTelemetryReporter.Instance); + stateGenerator.BlockBackgroundWorkStart = new ManualResetEventSlim(initialState: false); + stateGenerator.Update(_workspaceProject, _projectSnapshot, DisposalToken); + var initialUpdate = stateGenerator.Updates.Single().Value; + + // Act + stateGenerator.Update(_workspaceProject, _projectSnapshot, DisposalToken); + + // Assert + Assert.True(initialUpdate.Cts.IsCancellationRequested); } [UIFact] public async Task Update_NullWorkspaceProject_ClearsProjectWorkspaceState() { // Arrange - using (var stateGenerator = new ProjectWorkspaceStateGenerator(Dispatcher, NoOpTelemetryReporter.Instance)) - { - stateGenerator.NotifyBackgroundWorkCompleted = new ManualResetEventSlim(initialState: false); - var projectManager = new TestProjectSnapshotManager(_workspace, _projectEngineFactoryProvider, Dispatcher); - stateGenerator.Initialize(projectManager); - projectManager.ProjectAdded(_projectSnapshot.HostProject); - projectManager.ProjectWorkspaceStateChanged(_projectSnapshot.Key, _projectWorkspaceStateWithTagHelpers); - - // Act - stateGenerator.Update(workspaceProject: null, _projectSnapshot, DisposalToken); - - // Jump off the UI thread so the background work can complete. - await Task.Run(() => stateGenerator.NotifyBackgroundWorkCompleted.Wait(TimeSpan.FromSeconds(3))); - - // Assert - var newProjectSnapshot = projectManager.GetLoadedProject(_projectSnapshot.Key); - Assert.Empty(newProjectSnapshot.TagHelpers); - } + var projectManagerAccessor = CreateProjectManagerAccessor(); + using var stateGenerator = new ProjectWorkspaceStateGenerator(projectManagerAccessor, _tagHelperResolver, Dispatcher, ErrorReporter, NoOpTelemetryReporter.Instance); + stateGenerator.NotifyBackgroundWorkCompleted = new ManualResetEventSlim(initialState: false); + projectManagerAccessor.Instance.ProjectAdded(_projectSnapshot.HostProject); + projectManagerAccessor.Instance.ProjectWorkspaceStateChanged(_projectSnapshot.Key, _projectWorkspaceStateWithTagHelpers); + + // Act + stateGenerator.Update(workspaceProject: null, _projectSnapshot, DisposalToken); + + // Jump off the UI thread so the background work can complete. + await Task.Run(() => stateGenerator.NotifyBackgroundWorkCompleted.Wait(TimeSpan.FromSeconds(3))); + + // Assert + var newProjectSnapshot = projectManagerAccessor.Instance.GetLoadedProject(_projectSnapshot.Key); + Assert.NotNull(newProjectSnapshot); + Assert.Empty(newProjectSnapshot.TagHelpers); } [UIFact] public async Task Update_ResolvesTagHelpersAndUpdatesWorkspaceState() { // Arrange - using (var stateGenerator = new ProjectWorkspaceStateGenerator(Dispatcher, NoOpTelemetryReporter.Instance)) - { - stateGenerator.NotifyBackgroundWorkCompleted = new ManualResetEventSlim(initialState: false); - var projectManager = new TestProjectSnapshotManager(_workspace, _projectEngineFactoryProvider, Dispatcher); - stateGenerator.Initialize(projectManager); - projectManager.ProjectAdded(_projectSnapshot.HostProject); - - // Act - stateGenerator.Update(_workspaceProject, _projectSnapshot, DisposalToken); - - // Jump off the UI thread so the background work can complete. - await Task.Run(() => stateGenerator.NotifyBackgroundWorkCompleted.Wait(TimeSpan.FromSeconds(3))); - - // Assert - var newProjectSnapshot = projectManager.GetLoadedProject(_projectSnapshot.Key); - Assert.Equal(_resolvableTagHelpers, newProjectSnapshot.TagHelpers); - } + var projectManagerAccessor = CreateProjectManagerAccessor(); + using var stateGenerator = new ProjectWorkspaceStateGenerator(projectManagerAccessor, _tagHelperResolver, Dispatcher, ErrorReporter, NoOpTelemetryReporter.Instance); + stateGenerator.NotifyBackgroundWorkCompleted = new ManualResetEventSlim(initialState: false); + projectManagerAccessor.Instance.ProjectAdded(_projectSnapshot.HostProject); + + // Act + stateGenerator.Update(_workspaceProject, _projectSnapshot, DisposalToken); + + // Jump off the UI thread so the background work can complete. + await Task.Run(() => stateGenerator.NotifyBackgroundWorkCompleted.Wait(TimeSpan.FromSeconds(3))); + + // Assert + var newProjectSnapshot = projectManagerAccessor.Instance.GetLoadedProject(_projectSnapshot.Key); + Assert.NotNull(newProjectSnapshot); + Assert.Equal(_tagHelperResolver.TagHelpers, newProjectSnapshot.TagHelpers); } } diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs index a618de1ec33..164a6c21d42 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -14,7 +12,6 @@ using Microsoft.AspNetCore.Razor.ProjectSystem; using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.AspNetCore.Razor.Test.Common.Editor; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Text; using Moq; using Xunit; @@ -29,7 +26,6 @@ public class DefaultProjectSnapshotManagerTest : ProjectSnapshotManagerDispatche private readonly HostProject _hostProject2; private readonly HostProject _hostProjectWithConfigurationChange; private readonly ProjectWorkspaceState _projectWorkspaceStateWithTagHelpers; - private readonly TestTagHelperResolver _tagHelperResolver; private readonly TestProjectSnapshotManager _projectManager; private readonly SourceText _sourceText; @@ -39,11 +35,6 @@ public DefaultProjectSnapshotManagerTest(ITestOutputHelper testOutput) var someTagHelpers = ImmutableArray.Create( TagHelperDescriptorBuilder.Create("Test1", "TestAssembly").Build()); - _tagHelperResolver = new TestTagHelperResolver() - { - TagHelpers = someTagHelpers, - }; - _documents = new HostDocument[] { TestProjectData.SomeProjectFile1, @@ -62,21 +53,11 @@ public DefaultProjectSnapshotManagerTest(ITestOutputHelper testOutput) _projectManager = new TestProjectSnapshotManager(triggers: [], Workspace, ProjectEngineFactoryProvider, Dispatcher); - _projectWorkspaceStateWithTagHelpers = ProjectWorkspaceState.Create(_tagHelperResolver.TagHelpers); + _projectWorkspaceStateWithTagHelpers = ProjectWorkspaceState.Create(someTagHelpers); _sourceText = SourceText.From("Hello world"); } - protected override void ConfigureWorkspaceServices(List services) - { - if (services is null) - { - throw new ArgumentNullException(nameof(services)); - } - - services.Add(_tagHelperResolver); - } - [UIFact] public void Initialize_DoneInCorrectOrderBasedOnInitializePriorityPriority() { @@ -103,7 +84,7 @@ public void DocumentAdded_AddsDocument() _projectManager.Reset(); // Act - _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null); + _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null!); // Assert var snapshot = _projectManager.GetSnapshot(_hostProject); @@ -120,7 +101,7 @@ public void DocumentAdded_AddsDocument_Legacy() _projectManager.Reset(); // Act - _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null); + _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null!); // Assert var snapshot = _projectManager.GetSnapshot(_hostProject); @@ -129,7 +110,7 @@ public void DocumentAdded_AddsDocument_Legacy() d => { Assert.Equal(_documents[0].FilePath, d); - Assert.Equal(FileKinds.Legacy, snapshot.GetDocument(d).FileKind); + Assert.Equal(FileKinds.Legacy, snapshot.GetDocument(d)!.FileKind); }); Assert.Equal(ProjectChangeKind.DocumentAdded, _projectManager.ListenersNotifiedOf); @@ -143,7 +124,7 @@ public void DocumentAdded_AddsDocument_Component() _projectManager.Reset(); // Act - _projectManager.DocumentAdded(_hostProject.Key, _documents[3], null); + _projectManager.DocumentAdded(_hostProject.Key, _documents[3], null!); // Assert var snapshot = _projectManager.GetSnapshot(_hostProject); @@ -152,7 +133,7 @@ public void DocumentAdded_AddsDocument_Component() d => { Assert.Equal(_documents[3].FilePath, d); - Assert.Equal(FileKinds.Component, snapshot.GetDocument(d).FileKind); + Assert.Equal(FileKinds.Component, snapshot.GetDocument(d)!.FileKind); }); Assert.Equal(ProjectChangeKind.DocumentAdded, _projectManager.ListenersNotifiedOf); @@ -163,11 +144,11 @@ public void DocumentAdded_IgnoresDuplicate() { // Arrange _projectManager.ProjectAdded(_hostProject); - _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null); + _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null!); _projectManager.Reset(); // Act - _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null); + _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null!); // Assert var snapshot = _projectManager.GetSnapshot(_hostProject); @@ -182,7 +163,7 @@ public void DocumentAdded_IgnoresUnknownProject() // Arrange // Act - _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null); + _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null!); // Assert var snapshot = _projectManager.GetSnapshot(_hostProject); @@ -197,11 +178,12 @@ public async Task DocumentAdded_NullLoader_HasEmptyText() _projectManager.Reset(); // Act - _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null); + _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null!); // Assert var snapshot = _projectManager.GetSnapshot(_hostProject); var document = snapshot.GetDocument(snapshot.DocumentFilePaths.Single()); + Assert.NotNull(document); var text = await document.GetTextAsync(); Assert.Equal(0, text.Length); @@ -222,6 +204,7 @@ public async Task DocumentAdded_WithLoader_LoadesText() // Assert var snapshot = _projectManager.GetSnapshot(_hostProject); var document = snapshot.GetDocument(snapshot.DocumentFilePaths.Single()); + Assert.NotNull(document); var actual = await document.GetTextAsync(); Assert.Same(expected, actual); @@ -238,7 +221,7 @@ public void DocumentAdded_CachesTagHelpers() var originalTagHelpers = _projectManager.GetSnapshot(_hostProject).TagHelpers; // Act - _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null); + _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null!); // Assert var newTagHelpers = _projectManager.GetSnapshot(_hostProject).TagHelpers; @@ -261,7 +244,7 @@ public void DocumentAdded_CachesProjectEngine() var projectEngine = snapshot.GetProjectEngine(); // Act - _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null); + _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null!); // Assert snapshot = _projectManager.GetSnapshot(_hostProject); @@ -273,9 +256,9 @@ public void DocumentRemoved_RemovesDocument() { // Arrange _projectManager.ProjectAdded(_hostProject); - _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null); - _projectManager.DocumentAdded(_hostProject.Key, _documents[1], null); - _projectManager.DocumentAdded(_hostProject.Key, _documents[2], null); + _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null!); + _projectManager.DocumentAdded(_hostProject.Key, _documents[1], null!); + _projectManager.DocumentAdded(_hostProject.Key, _documents[2], null!); _projectManager.Reset(); // Act @@ -327,9 +310,9 @@ public void DocumentRemoved_CachesTagHelpers() // Arrange _projectManager.ProjectAdded(_hostProject); _projectManager.ProjectWorkspaceStateChanged(_hostProject.Key, _projectWorkspaceStateWithTagHelpers); - _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null); - _projectManager.DocumentAdded(_hostProject.Key, _documents[1], null); - _projectManager.DocumentAdded(_hostProject.Key, _documents[2], null); + _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null!); + _projectManager.DocumentAdded(_hostProject.Key, _documents[1], null!); + _projectManager.DocumentAdded(_hostProject.Key, _documents[2], null!); _projectManager.Reset(); var originalTagHelpers = _projectManager.GetSnapshot(_hostProject).TagHelpers; @@ -352,9 +335,9 @@ public void DocumentRemoved_CachesProjectEngine() { // Arrange _projectManager.ProjectAdded(_hostProject); - _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null); - _projectManager.DocumentAdded(_hostProject.Key, _documents[1], null); - _projectManager.DocumentAdded(_hostProject.Key, _documents[2], null); + _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null!); + _projectManager.DocumentAdded(_hostProject.Key, _documents[1], null!); + _projectManager.DocumentAdded(_hostProject.Key, _documents[2], null!); _projectManager.Reset(); var snapshot = _projectManager.GetSnapshot(_hostProject); @@ -372,7 +355,7 @@ public async Task DocumentOpened_UpdatesDocument() { // Arrange _projectManager.ProjectAdded(_hostProject); - _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null); + _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null!); _projectManager.Reset(); // Act @@ -382,7 +365,7 @@ public async Task DocumentOpened_UpdatesDocument() Assert.Equal(ProjectChangeKind.DocumentChanged, _projectManager.ListenersNotifiedOf); var snapshot = _projectManager.GetSnapshot(_hostProject); - var text = await snapshot.GetDocument(_documents[0].FilePath).GetTextAsync(); + var text = await snapshot.GetDocument(_documents[0].FilePath)!.GetTextAsync(); Assert.Same(_sourceText, text); Assert.True(_projectManager.IsDocumentOpen(_documents[0].FilePath)); @@ -393,7 +376,7 @@ public async Task DocumentClosed_UpdatesDocument() { // Arrange _projectManager.ProjectAdded(_hostProject); - _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null); + _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null!); _projectManager.DocumentOpened(_hostProject.Key, _documents[0].FilePath, _sourceText); _projectManager.Reset(); @@ -409,7 +392,7 @@ public async Task DocumentClosed_UpdatesDocument() Assert.Equal(ProjectChangeKind.DocumentChanged, _projectManager.ListenersNotifiedOf); var snapshot = _projectManager.GetSnapshot(_hostProject); - var text = await snapshot.GetDocument(_documents[0].FilePath).GetTextAsync(); + var text = await snapshot.GetDocument(_documents[0].FilePath)!.GetTextAsync(); Assert.Same(expected, text); Assert.False(_projectManager.IsDocumentOpen(_documents[0].FilePath)); } @@ -419,7 +402,7 @@ public async Task DocumentClosed_AcceptsChange() { // Arrange _projectManager.ProjectAdded(_hostProject); - _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null); + _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null!); _projectManager.Reset(); var expected = SourceText.From("Hi"); @@ -432,7 +415,7 @@ public async Task DocumentClosed_AcceptsChange() Assert.Equal(ProjectChangeKind.DocumentChanged, _projectManager.ListenersNotifiedOf); var snapshot = _projectManager.GetSnapshot(_hostProject); - var text = await snapshot.GetDocument(_documents[0].FilePath).GetTextAsync(); + var text = await snapshot.GetDocument(_documents[0].FilePath)!.GetTextAsync(); Assert.Same(expected, text); } @@ -441,7 +424,7 @@ public async Task DocumentChanged_Snapshot_UpdatesDocument() { // Arrange _projectManager.ProjectAdded(_hostProject); - _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null); + _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null!); _projectManager.DocumentOpened(_hostProject.Key, _documents[0].FilePath, _sourceText); _projectManager.Reset(); @@ -454,7 +437,7 @@ public async Task DocumentChanged_Snapshot_UpdatesDocument() Assert.Equal(ProjectChangeKind.DocumentChanged, _projectManager.ListenersNotifiedOf); var snapshot = _projectManager.GetSnapshot(_hostProject); - var text = await snapshot.GetDocument(_documents[0].FilePath).GetTextAsync(); + var text = await snapshot.GetDocument(_documents[0].FilePath)!.GetTextAsync(); Assert.Same(expected, text); } @@ -463,7 +446,7 @@ public async Task DocumentChanged_Loader_UpdatesDocument() { // Arrange _projectManager.ProjectAdded(_hostProject); - _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null); + _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null!); _projectManager.DocumentOpened(_hostProject.Key, _documents[0].FilePath, _sourceText); _projectManager.Reset(); @@ -477,7 +460,7 @@ public async Task DocumentChanged_Loader_UpdatesDocument() Assert.Equal(ProjectChangeKind.DocumentChanged, _projectManager.ListenersNotifiedOf); var snapshot = _projectManager.GetSnapshot(_hostProject); - var text = await snapshot.GetDocument(_documents[0].FilePath).GetTextAsync(); + var text = await snapshot.GetDocument(_documents[0].FilePath)!.GetTextAsync(); Assert.Same(expected, text); } @@ -637,7 +620,7 @@ public void NestedNotifications_NotifiesListenersInCorrectOrder() _projectManager.NotifyChangedEvents = true; // Act - _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null); + _projectManager.DocumentAdded(_hostProject.Key, _documents[0], null!); // Assert Assert.Equal(new[] { ProjectChangeKind.DocumentAdded, ProjectChangeKind.DocumentChanged, ProjectChangeKind.DocumentRemoved }, listenerNotifications); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultWindowsRazorProjectHostTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultWindowsRazorProjectHostTest.cs index 6ccb8ef9ea7..f5d6a3081f7 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultWindowsRazorProjectHostTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultWindowsRazorProjectHostTest.cs @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Razor.ProjectEngineHost; using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.AspNetCore.Razor.Test.Common.Editor; +using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.VisualStudio.ProjectSystem; using Microsoft.VisualStudio.ProjectSystem.Properties; using Moq; @@ -31,12 +32,20 @@ public class DefaultWindowsRazorProjectHostTest : ProjectSnapshotManagerDispatch private readonly PropertyCollection _razorGeneralProperties; private readonly PropertyCollection _configurationGeneral; private readonly TestProjectSnapshotManager _projectManager; + private readonly IProjectSnapshotManagerAccessor _projectManagerAccessor; public DefaultWindowsRazorProjectHostTest(ITestOutputHelper testOutput) : base(testOutput) { _projectManager = new TestProjectSnapshotManager(Workspace, ProjectEngineFactoryProvider, Dispatcher); + var projectManagerAccessorMock = new Mock(MockBehavior.Strict); + projectManagerAccessorMock + .SetupGet(x => x.Instance) + .Returns(_projectManager); + + _projectManagerAccessor = projectManagerAccessorMock.Object; + var projectConfigurationFilePathStore = new Mock(MockBehavior.Strict); projectConfigurationFilePathStore.Setup(s => s.Remove(It.IsAny())).Verifiable(); _projectConfigurationFilePathStore = projectConfigurationFilePathStore.Object; @@ -621,7 +630,7 @@ public async Task DefaultRazorProjectHost_UIThread_CreateAndDispose_Succeeds() { // Arrange var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); - var host = new DefaultWindowsRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager); + var host = new DefaultWindowsRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore, languageServerFeatureOptions: null); // Act & Assert await host.LoadAsync(); @@ -636,7 +645,7 @@ public async Task DefaultRazorProjectHost_BackgroundThread_CreateAndDispose_Succ { // Arrange var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); - var host = new DefaultWindowsRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager); + var host = new DefaultWindowsRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore, languageServerFeatureOptions: null); // Act & Assert await Task.Run(async () => await host.LoadAsync()); @@ -655,7 +664,7 @@ public async Task DefaultRazorProjectHost_OnProjectChanged_NoRulesDefined() }; var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); - var host = new DefaultWindowsRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager); + var host = new DefaultWindowsRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore, languageServerFeatureOptions: null); // Act & Assert await Task.Run(async () => await host.LoadAsync()); @@ -699,7 +708,7 @@ public async Task OnProjectChanged_ReadsProperties_InitializesProject() var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); - var host = new DefaultWindowsRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager); + var host = new DefaultWindowsRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore, languageServerFeatureOptions: null); host.SkipIntermediateOutputPathExistCheck_TestOnly = true; await Task.Run(async () => await host.LoadAsync()); @@ -763,7 +772,7 @@ public async Task OnProjectChanged_NoVersionFound_DoesNotIniatializeProject() }; var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); - var host = new DefaultWindowsRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager); + var host = new DefaultWindowsRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore, languageServerFeatureOptions: null); await Task.Run(async () => await host.LoadAsync()); Assert.Empty(_projectManager.GetProjects()); @@ -814,7 +823,7 @@ public async Task OnProjectChanged_UpdateProject_MarksSolutionOpen() }; var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); - var host = new DefaultWindowsRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager); + var host = new DefaultWindowsRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore, languageServerFeatureOptions: null); host.SkipIntermediateOutputPathExistCheck_TestOnly = true; await Task.Run(async () => await host.LoadAsync()); @@ -865,7 +874,7 @@ public async Task OnProjectChanged_UpdateProject_Succeeds() }; var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); - var host = new DefaultWindowsRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager); + var host = new DefaultWindowsRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore, languageServerFeatureOptions: null); host.SkipIntermediateOutputPathExistCheck_TestOnly = true; await Task.Run(async () => await host.LoadAsync()); @@ -1021,7 +1030,7 @@ public async Task OnProjectChanged_VersionRemoved_DeinitializesProject() }; var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); - var host = new DefaultWindowsRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager); + var host = new DefaultWindowsRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore, languageServerFeatureOptions: null); host.SkipIntermediateOutputPathExistCheck_TestOnly = true; await Task.Run(async () => await host.LoadAsync()); @@ -1096,7 +1105,7 @@ public async Task OnProjectChanged_AfterDispose_IgnoresUpdate() }; var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); - var host = new DefaultWindowsRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager); + var host = new DefaultWindowsRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore, languageServerFeatureOptions: null); host.SkipIntermediateOutputPathExistCheck_TestOnly = true; await Task.Run(async () => await host.LoadAsync()); @@ -1176,7 +1185,7 @@ public async Task OnProjectRenamed_RemovesHostProject_CopiesConfiguration() var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); - var host = new DefaultWindowsRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager); + var host = new DefaultWindowsRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore, languageServerFeatureOptions: null); host.SkipIntermediateOutputPathExistCheck_TestOnly = true; await Task.Run(async () => await host.LoadAsync()); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/FallbackWindowsRazorProjectHostTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/FallbackWindowsRazorProjectHostTest.cs index 8e193de40ef..ea309888c5a 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/FallbackWindowsRazorProjectHostTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/FallbackWindowsRazorProjectHostTest.cs @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.ProjectEngineHost; using Microsoft.AspNetCore.Razor.Test.Common.Editor; +using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.VisualStudio.ProjectSystem; using Microsoft.VisualStudio.ProjectSystem.Properties; using Moq; @@ -26,6 +27,7 @@ public class FallbackWindowsRazorProjectHostTest : ProjectSnapshotManagerDispatc { private readonly ItemCollection _referenceItems; private readonly TestProjectSnapshotManager _projectManager; + private readonly IProjectSnapshotManagerAccessor _projectManagerAccessor; private readonly ProjectConfigurationFilePathStore _projectConfigurationFilePathStore; private readonly ItemCollection _contentItems; private readonly ItemCollection _noneItems; @@ -35,6 +37,13 @@ public FallbackWindowsRazorProjectHostTest(ITestOutputHelper testOutput) { _projectManager = new TestProjectSnapshotManager(Workspace, ProjectEngineFactoryProvider, Dispatcher); + var projectManagerAccessorMock = new Mock(MockBehavior.Strict); + projectManagerAccessorMock + .SetupGet(x => x.Instance) + .Returns(_projectManager); + + _projectManagerAccessor = projectManagerAccessorMock.Object; + var projectConfigurationFilePathStore = new Mock(MockBehavior.Strict); projectConfigurationFilePathStore.Setup(s => s.Remove(It.IsAny())).Verifiable(); _projectConfigurationFilePathStore = projectConfigurationFilePathStore.Object; @@ -62,7 +71,7 @@ public void GetChangedAndRemovedDocuments_ReturnsChangedContentAndNoneItems() }); var services = new TestProjectSystemServices("C:\\To\\Test.csproj"); - var host = new TestFallbackRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager); + var host = new TestFallbackRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore); var changes = new TestProjectChangeDescription[] { afterChangeContentItems.ToChange(_contentItems.ToSnapshot()), @@ -104,7 +113,7 @@ public void GetCurrentDocuments_ReturnsContentAndNoneItems() }); var services = new TestProjectSystemServices("C:\\To\\Test.csproj"); - var host = new TestFallbackRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager); + var host = new TestFallbackRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore); var changes = new TestProjectChangeDescription[] { _contentItems.ToChange(), @@ -147,7 +156,7 @@ public void GetCurrentDocuments_IgnoresDotRazorFiles() }); var services = new TestProjectSystemServices("C:\\To\\Test.csproj"); - var host = new TestFallbackRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager); + var host = new TestFallbackRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore); var changes = new TestProjectChangeDescription[] { _contentItems.ToChange(), @@ -168,7 +177,7 @@ public void TryGetRazorDocument_NoFilePath_ReturnsFalse() // Arrange var services = new TestProjectSystemServices("C:\\To\\Test.csproj"); - var host = new TestFallbackRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager); + var host = new TestFallbackRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore); var itemState = new Dictionary() { [ItemReference.LinkPropertyName] = "Index.cshtml", @@ -188,7 +197,7 @@ public void TryGetRazorDocument_NonRazorFilePath_ReturnsFalse() // Arrange var services = new TestProjectSystemServices("C:\\Path\\Test.csproj"); - var host = new TestFallbackRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager); + var host = new TestFallbackRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore); var itemState = new Dictionary() { [ItemReference.FullPathPropertyName] = "C:\\Path\\site.css", @@ -208,7 +217,7 @@ public void TryGetRazorDocument_NonRazorTargetPath_ReturnsFalse() // Arrange var services = new TestProjectSystemServices("C:\\Path\\To\\Test.csproj"); - var host = new TestFallbackRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager); + var host = new TestFallbackRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore); var itemState = new Dictionary() { [ItemReference.LinkPropertyName] = "site.html", @@ -230,7 +239,7 @@ public void TryGetRazorDocument_JustFilePath_ReturnsTrue() var expectedPath = "C:\\Path\\Index.cshtml"; var services = new TestProjectSystemServices("C:\\Path\\Test.csproj"); - var host = new TestFallbackRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager); + var host = new TestFallbackRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore); var itemState = new Dictionary() { [ItemReference.FullPathPropertyName] = expectedPath, @@ -253,7 +262,7 @@ public void TryGetRazorDocument_LinkedFilepath_ReturnsTrue() var expectedTargetPath = "C:\\Path\\To\\Index.cshtml"; var services = new TestProjectSystemServices("C:\\Path\\To\\Test.csproj"); - var host = new TestFallbackRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager); + var host = new TestFallbackRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore); var itemState = new Dictionary() { [ItemReference.LinkPropertyName] = "Index.cshtml", @@ -277,7 +286,7 @@ public void TryGetRazorDocument_SetsLegacyFileKind() var expectedTargetPath = "C:\\Path\\To\\Index.cshtml"; var services = new TestProjectSystemServices("C:\\Path\\To\\Test.csproj"); - var host = new TestFallbackRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager); + var host = new TestFallbackRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore); var itemState = new Dictionary() { [ItemReference.LinkPropertyName] = "Index.cshtml", @@ -300,7 +309,7 @@ public async Task FallbackRazorProjectHost_UIThread_CreateAndDispose_Succeeds() // Arrange var services = new TestProjectSystemServices("C:\\To\\Test.csproj"); - var host = new TestFallbackRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager); + var host = new TestFallbackRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore); // Act & Assert await host.LoadAsync(); @@ -316,7 +325,7 @@ public async Task FallbackRazorProjectHost_BackgroundThread_CreateAndDispose_Suc // Arrange var services = new TestProjectSystemServices("Test.csproj"); - var host = new TestFallbackRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager); + var host = new TestFallbackRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore); // Act & Assert await Task.Run(async () => await host.LoadAsync()); @@ -336,7 +345,7 @@ public async Task OnProjectChanged_NoRulesDefined() var services = new TestProjectSystemServices("Test.csproj"); - var host = new TestFallbackRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager) + var host = new TestFallbackRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore) { AssemblyVersion = new Version(2, 0), }; @@ -372,7 +381,7 @@ public async Task OnProjectChanged_ReadsProperties_InitializesProject() var services = new TestProjectSystemServices("C:\\Path\\Test.csproj"); - var host = new TestFallbackRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager) + var host = new TestFallbackRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore) { AssemblyVersion = new Version(2, 0), // Mock for reading the assembly's version }; @@ -407,7 +416,7 @@ public async Task OnProjectChanged_NoAssemblyFound_DoesNotIniatializeProject() }; var services = new TestProjectSystemServices("Test.csproj"); - var host = new TestFallbackRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager); + var host = new TestFallbackRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore); await Task.Run(async () => await host.LoadAsync()); Assert.Empty(_projectManager.GetProjects()); @@ -435,7 +444,7 @@ public async Task OnProjectChanged_AssemblyFoundButCannotReadVersion_DoesNotInia var services = new TestProjectSystemServices("Test.csproj"); - var host = new TestFallbackRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager); + var host = new TestFallbackRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore); await Task.Run(async () => await host.LoadAsync()); Assert.Empty(_projectManager.GetProjects()); @@ -474,7 +483,7 @@ public async Task OnProjectChanged_UpdateProject_Succeeds() var services = new TestProjectSystemServices("C:\\Path\\Test.csproj"); - var host = new TestFallbackRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager) + var host = new TestFallbackRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore) { AssemblyVersion = new Version(2, 0), }; @@ -519,7 +528,7 @@ public async Task OnProjectChanged_VersionRemoved_DeinitializesProject() var services = new TestProjectSystemServices("Test.csproj"); - var host = new TestFallbackRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager) + var host = new TestFallbackRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore) { AssemblyVersion = new Version(2, 0), }; @@ -564,7 +573,7 @@ public async Task OnProjectChanged_AfterDispose_IgnoresUpdate() var services = new TestProjectSystemServices("C:\\Path\\Test.csproj"); - var host = new TestFallbackRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager) + var host = new TestFallbackRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore) { AssemblyVersion = new Version(2, 0), }; @@ -609,7 +618,7 @@ public async Task OnProjectRenamed_RemovesHostProject_CopiesConfiguration() var services = new TestProjectSystemServices("Test.csproj"); - var host = new TestFallbackRazorProjectHost(services, Workspace, Dispatcher, _projectConfigurationFilePathStore, _projectManager) + var host = new TestFallbackRazorProjectHost(services, _projectManagerAccessor, Dispatcher, _projectConfigurationFilePathStore) { AssemblyVersion = new Version(2, 0), // Mock for reading the assembly's version }; @@ -642,11 +651,10 @@ private class TestFallbackRazorProjectHost : FallbackWindowsRazorProjectHost { internal TestFallbackRazorProjectHost( IUnconfiguredProjectCommonServices commonServices, - Workspace workspace, - ProjectSnapshotManagerDispatcher projectSnapshotManagerDispatcher, - ProjectConfigurationFilePathStore projectConfigurationFilePathStore, - ProjectSnapshotManagerBase projectManager) - : base(commonServices, workspace, projectSnapshotManagerDispatcher, projectConfigurationFilePathStore, projectManager) + IProjectSnapshotManagerAccessor projectManagerAccessor, + ProjectSnapshotManagerDispatcher dispatcher, + ProjectConfigurationFilePathStore projectConfigurationFilePathStore) + : base(commonServices, projectManagerAccessor, dispatcher, projectConfigurationFilePathStore, languageServerFeatureOptions: null) { base.SkipIntermediateOutputPathExistCheck_TestOnly = true; } diff --git a/src/Razor/test/Microsoft.VisualStudio.LiveShare.Razor.Test/Guest/ProjectSnapshotSynchronizationServiceTest.cs b/src/Razor/test/Microsoft.VisualStudio.LiveShare.Razor.Test/Guest/ProjectSnapshotSynchronizationServiceTest.cs index 09bcdaab56a..eb3347cd2af 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LiveShare.Razor.Test/Guest/ProjectSnapshotSynchronizationServiceTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LiveShare.Razor.Test/Guest/ProjectSnapshotSynchronizationServiceTest.cs @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -#nullable disable - using System; using System.Collections.Immutable; using System.Linq; @@ -12,8 +10,8 @@ using Microsoft.AspNetCore.Razor.ProjectSystem; using Microsoft.AspNetCore.Razor.Test.Common.Editor; using Microsoft.AspNetCore.Razor.Test.Common.ProjectSystem; -using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.VisualStudio.LiveShare.Razor.Test; using Moq; using Xunit; @@ -21,10 +19,10 @@ namespace Microsoft.VisualStudio.LiveShare.Razor.Guest; -public class ProjectSnapshotSynchronizationServiceTest : WorkspaceTestBase +public class ProjectSnapshotSynchronizationServiceTest : ProjectSnapshotManagerDispatcherWorkspaceTestBase { private readonly CollaborationSession _sessionContext; - private readonly TestProjectSnapshotManager _projectSnapshotManager; + private readonly IProjectSnapshotManagerAccessor _projectManagerAccessor; private readonly ProjectWorkspaceState _projectWorkspaceStateWithTagHelpers; public ProjectSnapshotSynchronizationServiceTest(ITestOutputHelper testOutput) @@ -32,7 +30,14 @@ public ProjectSnapshotSynchronizationServiceTest(ITestOutputHelper testOutput) { _sessionContext = new TestCollaborationSession(isHost: false); - _projectSnapshotManager = new TestProjectSnapshotManager(Workspace, ProjectEngineFactoryProvider, new TestProjectSnapshotManagerDispatcher()); + var projectManager = new TestProjectSnapshotManager(Workspace, ProjectEngineFactoryProvider, new TestProjectSnapshotManagerDispatcher()); + + var projectManagerAccessorMock = new Mock(MockBehavior.Strict); + projectManagerAccessorMock + .SetupGet(x => x.Instance) + .Returns(projectManager); + + _projectManagerAccessor = projectManagerAccessorMock.Object; _projectWorkspaceStateWithTagHelpers = ProjectWorkspaceState.Create(ImmutableArray.Create( TagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly").Build())); @@ -55,13 +60,17 @@ public async Task InitializeAsync_RetrievesHostProjectManagerStateAndInitializes JoinableTaskFactory, _sessionContext, hostProjectManagerProxy, - _projectSnapshotManager); + _projectManagerAccessor, + ErrorReporter, + Dispatcher); // Act await synchronizationService.InitializeAsync(DisposalToken); // Assert - var project = Assert.Single(_projectSnapshotManager.GetProjects()); + var projects = await Dispatcher.RunOnDispatcherThreadAsync( + _projectManagerAccessor.Instance.GetProjects, DisposalToken); + var project = Assert.Single(projects); Assert.Equal("/guest/path/project.csproj", project.FilePath); Assert.Same(RazorConfiguration.Default, project.Configuration); @@ -73,7 +82,7 @@ public async Task InitializeAsync_RetrievesHostProjectManagerStateAndInitializes } [Fact] - public void UpdateGuestProjectManager_ProjectAdded() + public async Task UpdateGuestProjectManager_ProjectAdded() { // Arrange var newHandle = new ProjectSnapshotHandleProxy( @@ -86,14 +95,18 @@ public void UpdateGuestProjectManager_ProjectAdded() JoinableTaskFactory, _sessionContext, Mock.Of(MockBehavior.Strict), - _projectSnapshotManager); + _projectManagerAccessor, + ErrorReporter, + Dispatcher); var args = new ProjectChangeEventProxyArgs(older: null, newHandle, ProjectProxyChangeKind.ProjectAdded); // Act - synchronizationService.UpdateGuestProjectManager(args); + await synchronizationService.UpdateGuestProjectManagerAsync(args); // Assert - var project = Assert.Single(_projectSnapshotManager.GetProjects()); + var projects = await Dispatcher.RunOnDispatcherThreadAsync( + _projectManagerAccessor.Instance.GetProjects, DisposalToken); + var project = Assert.Single(projects); Assert.Equal("/guest/path/project.csproj", project.FilePath); Assert.Same(RazorConfiguration.Default, project.Configuration); @@ -105,7 +118,7 @@ public void UpdateGuestProjectManager_ProjectAdded() } [Fact] - public void UpdateGuestProjectManager_ProjectRemoved() + public async Task UpdateGuestProjectManager_ProjectRemoved() { // Arrange var olderHandle = new ProjectSnapshotHandleProxy( @@ -118,20 +131,27 @@ public void UpdateGuestProjectManager_ProjectRemoved() JoinableTaskFactory, _sessionContext, Mock.Of(MockBehavior.Strict), - _projectSnapshotManager); + _projectManagerAccessor, + ErrorReporter, + Dispatcher); var hostProject = new HostProject("/guest/path/project.csproj", "/guest/path/obj", RazorConfiguration.Default, "project"); - _projectSnapshotManager.ProjectAdded(hostProject); + + await Dispatcher.RunOnDispatcherThreadAsync( + () => _projectManagerAccessor.Instance.ProjectAdded(hostProject), + DisposalToken); var args = new ProjectChangeEventProxyArgs(olderHandle, newer: null, ProjectProxyChangeKind.ProjectRemoved); // Act - synchronizationService.UpdateGuestProjectManager(args); + await synchronizationService.UpdateGuestProjectManagerAsync(args); // Assert - Assert.Empty(_projectSnapshotManager.GetProjects()); + var projects = await Dispatcher.RunOnDispatcherThreadAsync( + _projectManagerAccessor.Instance.GetProjects, DisposalToken); + Assert.Empty(projects); } [Fact] - public void UpdateGuestProjectManager_ProjectChanged_ConfigurationChange() + public async Task UpdateGuestProjectManager_ProjectChanged_ConfigurationChange() { // Arrange var oldHandle = new ProjectSnapshotHandleProxy( @@ -151,24 +171,32 @@ public void UpdateGuestProjectManager_ProjectChanged_ConfigurationChange() JoinableTaskFactory, _sessionContext, Mock.Of(MockBehavior.Strict), - _projectSnapshotManager); + _projectManagerAccessor, + ErrorReporter, + Dispatcher); var hostProject = new HostProject("/guest/path/project.csproj", "/guest/path/obj", RazorConfiguration.Default, "project"); - _projectSnapshotManager.ProjectAdded(hostProject); - _projectSnapshotManager.ProjectConfigurationChanged(hostProject); + await Dispatcher.RunOnDispatcherThreadAsync(() => + { + var projectManager = _projectManagerAccessor.Instance; + projectManager.ProjectAdded(hostProject); + projectManager.ProjectConfigurationChanged(hostProject); + }, DisposalToken); var args = new ProjectChangeEventProxyArgs(oldHandle, newHandle, ProjectProxyChangeKind.ProjectChanged); // Act - synchronizationService.UpdateGuestProjectManager(args); + await synchronizationService.UpdateGuestProjectManagerAsync(args); // Assert - var project = Assert.Single(_projectSnapshotManager.GetProjects()); + var projects = await Dispatcher.RunOnDispatcherThreadAsync( + _projectManagerAccessor.Instance.GetProjects, DisposalToken); + var project = Assert.Single(projects); Assert.Equal("/guest/path/project.csproj", project.FilePath); Assert.Same(newConfiguration, project.Configuration); Assert.Empty(project.TagHelpers); } [Fact] - public void UpdateGuestProjectManager_ProjectChanged_ProjectWorkspaceStateChange() + public async Task UpdateGuestProjectManager_ProjectChanged_ProjectWorkspaceStateChange() { // Arrange var oldHandle = new ProjectSnapshotHandleProxy( @@ -188,17 +216,25 @@ public void UpdateGuestProjectManager_ProjectChanged_ProjectWorkspaceStateChange JoinableTaskFactory, _sessionContext, Mock.Of(MockBehavior.Strict), - _projectSnapshotManager); + _projectManagerAccessor, + ErrorReporter, + Dispatcher); var hostProject = new HostProject("/guest/path/project.csproj", "/guest/path/obj", RazorConfiguration.Default, "project"); - _projectSnapshotManager.ProjectAdded(hostProject); - _projectSnapshotManager.ProjectWorkspaceStateChanged(hostProject.Key, oldHandle.ProjectWorkspaceState); + await Dispatcher.RunOnDispatcherThreadAsync(() => + { + var projectManager = _projectManagerAccessor.Instance; + projectManager.ProjectAdded(hostProject); + projectManager.ProjectWorkspaceStateChanged(hostProject.Key, oldHandle.ProjectWorkspaceState); + }, DisposalToken); var args = new ProjectChangeEventProxyArgs(oldHandle, newHandle, ProjectProxyChangeKind.ProjectChanged); // Act - synchronizationService.UpdateGuestProjectManager(args); + await synchronizationService.UpdateGuestProjectManagerAsync(args); // Assert - var project = Assert.Single(_projectSnapshotManager.GetProjects()); + var projects = await Dispatcher.RunOnDispatcherThreadAsync( + _projectManagerAccessor.Instance.GetProjects, DisposalToken); + var project = Assert.Single(projects); Assert.Equal("/guest/path/project.csproj", project.FilePath); Assert.Same(RazorConfiguration.Default, project.Configuration);