diff --git a/eng/Versions.props b/eng/Versions.props
index d3d81f6cb7a..48c8a2f3b4a 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -74,7 +74,7 @@
17.5.101-preview-0002
- 1.1.2-beta1.22512.1
+ 1.1.2-beta1.23115.1
17.5.0-preview-2-33117-317
17.5.274-preview
4.6.0-2.23113.15
@@ -96,6 +96,7 @@
6.0.0-alpha.1.21072.5
$(Tooling_MicrosoftCodeAnalysisTestingVersion)
+ $(Tooling_MicrosoftCodeAnalysisTestingVersion)
$(Tooling_MicrosoftCodeAnalysisTestingVersion)
$(MicrosoftVisualStudioPackagesVersion)
0.1.149-beta
diff --git a/src/Compiler/Directory.Packages.props b/src/Compiler/Directory.Packages.props
index afb5b2d83eb..6b91bea3ddb 100644
--- a/src/Compiler/Directory.Packages.props
+++ b/src/Compiler/Directory.Packages.props
@@ -9,6 +9,7 @@
+
diff --git a/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/Microsoft.NET.Sdk.Razor.SourceGenerators.Test.csproj b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/Microsoft.NET.Sdk.Razor.SourceGenerators.Test.csproj
index eaf51ec31f8..04cf3559c8e 100644
--- a/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/Microsoft.NET.Sdk.Razor.SourceGenerators.Test.csproj
+++ b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/Microsoft.NET.Sdk.Razor.SourceGenerators.Test.csproj
@@ -17,6 +17,11 @@
+
+
+
+
+
@@ -26,6 +31,7 @@
+
diff --git a/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/RazorSourceGeneratorTests.cs b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/RazorSourceGeneratorTests.cs
index b2c2204cca5..7a8be538f3c 100644
--- a/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/RazorSourceGeneratorTests.cs
+++ b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/RazorSourceGeneratorTests.cs
@@ -9,20 +9,23 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
+using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Text;
using Microsoft.Extensions.DependencyModel;
using Microsoft.Extensions.DependencyModel.Resolution;
using Xunit;
-using Xunit.Sdk;
namespace Microsoft.NET.Sdk.Razor.SourceGenerators
{
+ using Verify = Verifiers.CSharpSourceGeneratorVerifier;
+
public class RazorSourceGeneratorTests
{
private static readonly Project _baseProject = CreateBaseProject();
@@ -31,42 +34,18 @@ public class RazorSourceGeneratorTests
public async Task SourceGenerator_RazorFiles_Works()
{
// Arrange
- var project = CreateTestProject(new()
+ var test = new RazorTest
{
- ["Pages/Index.razor"] = "Hello world
",
- });
-
- var compilation = await project.GetCompilationAsync();
- var driver = await GetDriverAsync(project);
-
- var result = RunGenerator(compilation!, ref driver)
- .VerifyPageOutput(
-@"#pragma checksum ""Pages/Index.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""6b5db227a6aa2228c777b0771108b184b1fc5df3""
-//
-#pragma warning disable 1591
-namespace MyApp.Pages
-{
- #line hidden
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Threading.Tasks;
- using Microsoft.AspNetCore.Components;
- public partial class Index : global::Microsoft.AspNetCore.Components.ComponentBase
- {
- #pragma warning disable 1998
- protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
- {
- __builder.AddMarkupContent(0, ""Hello world
"");
- }
- #pragma warning restore 1998
- }
-}
-#pragma warning restore 1591
-");
+ TestState =
+ {
+ AdditionalFiles =
+ {
+ ("/0/Pages/Index.razor", "Hello world
")
+ },
+ },
+ };
- Assert.Empty(result.Diagnostics);
- Assert.Single(result.GeneratedSources);
+ await test.AddMetadata().AddGeneratedSources().RunAsync();
}
internal class InMemoryAdditionalText : AdditionalText
@@ -2512,6 +2491,43 @@ public bool TryResolveAssemblyPaths(CompilationLibrary library, List? as
}
}
+ private class RazorTest : Verify.Test
+ {
+ public RazorTest([CallerFilePath] string? testFile = null, [CallerMemberName] string? testMethod = null)
+ : base(testFile, testMethod)
+ {
+ // Don't resolve any reference assemblies from NuGet
+ ReferenceAssemblies = new ReferenceAssemblies("custom");
+
+ foreach (var defaultCompileLibrary in DependencyContext.Load(typeof(RazorSourceGeneratorTests).Assembly)!.CompileLibraries)
+ {
+ foreach (var resolveReferencePath in defaultCompileLibrary.ResolveReferencePaths(new AppLocalResolver()))
+ {
+ TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(resolveReferencePath));
+ }
+ }
+
+ // The deps file in the project is incorrect and does not contain "compile" nodes for some references.
+ // However these binaries are always present in the bin output. As a "temporary" workaround, we'll add
+ // every dll file that's present in the test's build output as a metadatareference.
+ foreach (var assembly in Directory.EnumerateFiles(AppContext.BaseDirectory, "*.dll"))
+ {
+ if (!TestState.AdditionalReferences.Any(c => string.Equals(Path.GetFileNameWithoutExtension(c.Display), Path.GetFileNameWithoutExtension(assembly), StringComparison.OrdinalIgnoreCase)))
+ {
+ TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(assembly));
+ }
+ }
+ }
+
+ public NullableContextOptions NullableContextOptions { get; set; } = NullableContextOptions.Enable;
+
+ protected override CodeAnalysis.CompilationOptions CreateCompilationOptions()
+ {
+ var options = (CSharpCompilationOptions)base.CreateCompilationOptions();
+ return options.WithNullableContextOptions(NullableContextOptions);
+ }
+ }
+
private static Project CreateBaseProject()
{
var projectId = ProjectId.CreateNewId(debugName: "TestProject");
diff --git a/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/Resources/SourceGenerator_RazorFiles_Works/Pages_Index_razor.g.cs b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/Resources/SourceGenerator_RazorFiles_Works/Pages_Index_razor.g.cs
new file mode 100644
index 00000000000..c5129a923c4
--- /dev/null
+++ b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/Resources/SourceGenerator_RazorFiles_Works/Pages_Index_razor.g.cs
@@ -0,0 +1,22 @@
+#pragma checksum "/0/Pages/Index.razor" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "6b5db227a6aa2228c777b0771108b184b1fc5df3"
+//
+#pragma warning disable 1591
+namespace MyApp.Pages
+{
+ #line hidden
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Threading.Tasks;
+ using Microsoft.AspNetCore.Components;
+ public partial class Index : global::Microsoft.AspNetCore.Components.ComponentBase
+ {
+ #pragma warning disable 1998
+ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
+ {
+ __builder.AddMarkupContent(0, "Hello world
");
+ }
+ #pragma warning restore 1998
+ }
+}
+#pragma warning restore 1591
diff --git a/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/Verifiers/CSharpSourceGeneratorVerifier`1+Test.cs b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/Verifiers/CSharpSourceGeneratorVerifier`1+Test.cs
new file mode 100644
index 00000000000..ba6768e1d13
--- /dev/null
+++ b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/Verifiers/CSharpSourceGeneratorVerifier`1+Test.cs
@@ -0,0 +1,186 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+// Uncomment the following line to write expected files to disk
+////#define WRITE_EXPECTED
+
+#if WRITE_EXPECTED
+#warning WRITE_EXPECTED is fine for local builds, but should not be merged to the main branch.
+#endif
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Testing;
+using Microsoft.CodeAnalysis.Testing.Verifiers;
+
+namespace Microsoft.NET.Sdk.Razor.SourceGenerators.Verifiers
+{
+ public static partial class CSharpSourceGeneratorVerifier
+ where TSourceGenerator : IIncrementalGenerator, new()
+ {
+ public class Test : CSharpSourceGeneratorTest
+ {
+ private readonly string? _testFile;
+ private readonly string? _testMethod;
+
+ public Test([CallerFilePath] string? testFile = null, [CallerMemberName] string? testMethod = null)
+ {
+ CompilerDiagnostics = CompilerDiagnostics.Warnings;
+
+ _testFile = testFile;
+ _testMethod = testMethod;
+
+#if WRITE_EXPECTED
+ TestBehaviors |= TestBehaviors.SkipGeneratedSourcesCheck;
+#endif
+ }
+
+ public LanguageVersion LanguageVersion { get; set; } = LanguageVersion.Default;
+
+ protected override IEnumerable GetSourceGenerators()
+ {
+ yield return typeof(TSourceGenerator);
+ }
+
+ protected override CompilationOptions CreateCompilationOptions()
+ {
+ var compilationOptions = (CSharpCompilationOptions)base.CreateCompilationOptions();
+ return compilationOptions
+ .WithAllowUnsafe(false)
+ .WithWarningLevel(99)
+ .WithSpecificDiagnosticOptions(compilationOptions.SpecificDiagnosticOptions.SetItem("CS8019", ReportDiagnostic.Suppress));
+ }
+
+ protected override ParseOptions CreateParseOptions()
+ {
+ return ((CSharpParseOptions)base.CreateParseOptions()).WithLanguageVersion(LanguageVersion);
+ }
+
+ protected override async Task<(Compilation compilation, ImmutableArray generatorDiagnostics)> GetProjectCompilationAsync(Project project, IVerifier verifier, CancellationToken cancellationToken)
+ {
+ var resourceDirectory = Path.Combine(Path.GetDirectoryName(_testFile)!, "Resources", _testMethod!);
+
+ var (compilation, generatorDiagnostics) = await base.GetProjectCompilationAsync(project, verifier, cancellationToken);
+ var expectedNames = new HashSet();
+ foreach (var tree in compilation.SyntaxTrees.Skip(project.DocumentIds.Count))
+ {
+ WriteTreeToDiskIfNecessary(tree, resourceDirectory);
+ expectedNames.Add(Path.GetFileName(tree.FilePath));
+ }
+
+ var currentTestPrefix = $"{typeof(RazorSourceGeneratorTests).Assembly.GetName().Name}.Resources.{_testMethod}.";
+ foreach (var name in GetType().Assembly.GetManifestResourceNames())
+ {
+ if (!name.StartsWith(currentTestPrefix, StringComparison.Ordinal))
+ {
+ continue;
+ }
+
+ if (!expectedNames.Contains(name[currentTestPrefix.Length..]))
+ {
+ throw new InvalidOperationException($"Unexpected test resource: {name[currentTestPrefix.Length..]}");
+ }
+ }
+
+ return (compilation, generatorDiagnostics);
+ }
+
+ public Test AddMetadata()
+ {
+ var globalConfig = new StringBuilder(@"is_global = true
+
+build_property.RazorConfiguration = Default
+build_property.RootNamespace = MyApp
+build_property.RazorLangVersion = Latest
+build_property.GenerateRazorMetadataSourceChecksumAttributes = false
+");
+
+ foreach (var (filename, _) in TestState.AdditionalFiles)
+ {
+ globalConfig.AppendLine(CultureInfo.InvariantCulture, $@"[{filename}]
+build_metadata.AdditionalFiles.TargetPath = {Convert.ToBase64String(Encoding.UTF8.GetBytes(getRelativeFilePath(filename)))}");
+ }
+
+ TestState.AnalyzerConfigFiles.Add(("/.globalconfig", globalConfig.ToString()));
+
+ return this;
+
+ static string getRelativeFilePath(string absolutePath)
+ {
+ if (absolutePath.StartsWith("/0/", StringComparison.Ordinal))
+ {
+ return absolutePath["/0/".Length..];
+ }
+ else if (absolutePath.StartsWith("/", StringComparison.Ordinal))
+ {
+ return absolutePath["/".Length..];
+ }
+ else
+ {
+ return absolutePath;
+ }
+ }
+ }
+
+ ///
+ /// Loads expected generated sources from embedded resources based on the test name.
+ ///
+ /// The current test method name.
+ /// The current instance.
+ public Test AddGeneratedSources([CallerMemberName] string? testMethod = null)
+ {
+ var expectedPrefix = $"{typeof(RazorSourceGeneratorTests).Assembly.GetName().Name}.Resources.{testMethod}.";
+ foreach (var resourceName in typeof(Test).Assembly.GetManifestResourceNames())
+ {
+ if (!resourceName.StartsWith(expectedPrefix, StringComparison.Ordinal))
+ {
+ continue;
+ }
+
+ using var resourceStream = typeof(RazorSourceGeneratorTests).Assembly.GetManifestResourceStream(resourceName) ?? throw new InvalidOperationException();
+ using var reader = new StreamReader(resourceStream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 4096, leaveOpen: true);
+ var name = resourceName[expectedPrefix.Length..];
+ TestState.GeneratedSources.Add((typeof(RazorSourceGenerator), name, reader.ReadToEnd()));
+ }
+
+ // An error will be reported if there are no sources or generated sources in the compilation. To bypass
+ // during the initial test construction, we add a default empty generated source knowing that it will
+ // not be validated.
+ if (TestBehaviors.HasFlag(TestBehaviors.SkipGeneratedSourcesCheck) && !TestState.Sources.Any() && !TestState.GeneratedSources.Any())
+ {
+ TestState.GeneratedSources.Add(("/ignored_file", ""));
+ }
+
+ return this;
+ }
+
+ [Conditional("WRITE_EXPECTED")]
+ private static void WriteTreeToDiskIfNecessary(SyntaxTree tree, string resourceDirectory)
+ {
+ if (tree.Encoding is null)
+ {
+ throw new ArgumentException("Syntax tree encoding was not specified");
+ }
+
+ var name = Path.GetFileName(tree.FilePath);
+ var filePath = Path.Combine(resourceDirectory, name);
+ Directory.CreateDirectory(resourceDirectory);
+ File.WriteAllText(filePath, tree.GetText().ToString(), tree.Encoding);
+ }
+ }
+ }
+}
diff --git a/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/Verifiers/CSharpSourceGeneratorVerifier`1.cs b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/Verifiers/CSharpSourceGeneratorVerifier`1.cs
new file mode 100644
index 00000000000..1096ec87cc3
--- /dev/null
+++ b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/Verifiers/CSharpSourceGeneratorVerifier`1.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.CodeAnalysis;
+
+namespace Microsoft.NET.Sdk.Razor.SourceGenerators.Verifiers
+{
+ public static partial class CSharpSourceGeneratorVerifier
+ where TSourceGenerator : IIncrementalGenerator, new()
+ {
+ }
+}