diff --git a/Directory.Packages.props b/Directory.Packages.props
index 0767cdeb9c1..3bdc4add56a 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -32,13 +32,12 @@
-
+
-
@@ -50,7 +49,6 @@
-
diff --git a/src/Analyzers/Razor.Diagnostics.Analyzers.Test/Razor.Diagnostics.Analyzers.Test.csproj b/src/Analyzers/Razor.Diagnostics.Analyzers.Test/Razor.Diagnostics.Analyzers.Test.csproj
index d8977ed8c0f..ab94ba1cf89 100644
--- a/src/Analyzers/Razor.Diagnostics.Analyzers.Test/Razor.Diagnostics.Analyzers.Test.csproj
+++ b/src/Analyzers/Razor.Diagnostics.Analyzers.Test/Razor.Diagnostics.Analyzers.Test.csproj
@@ -8,8 +8,7 @@
-
-
+
diff --git a/src/Analyzers/Razor.Diagnostics.Analyzers.Test/Verifiers/CSharpAnalyzerVerifier`1+Test.cs b/src/Analyzers/Razor.Diagnostics.Analyzers.Test/Verifiers/CSharpAnalyzerVerifier`1+Test.cs
index d59cd6bfe2e..f01ea1f7665 100644
--- a/src/Analyzers/Razor.Diagnostics.Analyzers.Test/Verifiers/CSharpAnalyzerVerifier`1+Test.cs
+++ b/src/Analyzers/Razor.Diagnostics.Analyzers.Test/Verifiers/CSharpAnalyzerVerifier`1+Test.cs
@@ -3,14 +3,14 @@
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Diagnostics;
-using Microsoft.CodeAnalysis.Testing.Verifiers;
+using Microsoft.CodeAnalysis.Testing;
namespace Razor.Diagnostics.Analyzers.Test;
public static partial class CSharpAnalyzerVerifier
where TAnalyzer : DiagnosticAnalyzer, new()
{
- public class Test : CSharpAnalyzerTest
+ public class Test : CSharpAnalyzerTest
{
public Test()
{
diff --git a/src/Analyzers/Razor.Diagnostics.Analyzers.Test/Verifiers/CSharpAnalyzerVerifier`1.cs b/src/Analyzers/Razor.Diagnostics.Analyzers.Test/Verifiers/CSharpAnalyzerVerifier`1.cs
index a24421ec5b5..56194eec436 100644
--- a/src/Analyzers/Razor.Diagnostics.Analyzers.Test/Verifiers/CSharpAnalyzerVerifier`1.cs
+++ b/src/Analyzers/Razor.Diagnostics.Analyzers.Test/Verifiers/CSharpAnalyzerVerifier`1.cs
@@ -7,7 +7,6 @@
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Testing;
-using Microsoft.CodeAnalysis.Testing.Verifiers;
namespace Razor.Diagnostics.Analyzers.Test;
@@ -16,15 +15,15 @@ public static partial class CSharpAnalyzerVerifier
{
///
public static DiagnosticResult Diagnostic()
- => CSharpAnalyzerVerifier.Diagnostic();
+ => CSharpAnalyzerVerifier.Diagnostic();
///
public static DiagnosticResult Diagnostic(string diagnosticId)
- => CSharpAnalyzerVerifier.Diagnostic(diagnosticId);
+ => CSharpAnalyzerVerifier.Diagnostic(diagnosticId);
///
public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor)
- => CSharpAnalyzerVerifier.Diagnostic(descriptor);
+ => CSharpAnalyzerVerifier.Diagnostic(descriptor);
///
public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected)
diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Analyzers/DuplicateRazorFileIncludedAnalyzer.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Analyzers/DuplicateRazorFileIncludedAnalyzer.cs
new file mode 100644
index 00000000000..e0a943f3ccb
--- /dev/null
+++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Analyzers/DuplicateRazorFileIncludedAnalyzer.cs
@@ -0,0 +1,51 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Text;
+using Microsoft.NET.Sdk.Razor.SourceGenerators;
+
+namespace Microsoft.AspNetCore.Razor.SourceGenerators;
+
+#pragma warning disable RS1041 // Compiler extensions should be implemented in assemblies targeting netstandard2.0
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+#pragma warning restore RS1041 // Compiler extensions should be implemented in assemblies targeting netstandard2.0
+public class DuplicateRazorFileIncludedAnalyzer : DiagnosticAnalyzer
+{
+ private static readonly ImmutableArray s_supportedDiagnostics = ImmutableArray.Create(RazorDiagnostics.DuplicateRazorFileIncludedDescriptor);
+
+ public override ImmutableArray SupportedDiagnostics => s_supportedDiagnostics;
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+ context.RegisterCompilationStartAction(static compStartAction =>
+ {
+ var includedFiles = new HashSet(StringComparer.Ordinal);
+
+ compStartAction.RegisterAdditionalFileAction(additionalFilesContext =>
+ {
+ var additionalFile = additionalFilesContext.AdditionalFile;
+
+ if (additionalFile.Path.EndsWith(".cshtml", StringComparison.Ordinal) ||
+ additionalFile.Path.EndsWith(".razor", StringComparison.Ordinal))
+ {
+ if (!includedFiles.Add(additionalFile.Path))
+ {
+ var diagnostic = Diagnostic.Create(
+ RazorDiagnostics.DuplicateRazorFileIncludedDescriptor,
+ Location.Create(additionalFile.Path, new TextSpan(0, 0), new LinePositionSpan(LinePosition.Zero, LinePosition.Zero)),
+ additionalFile.Path);
+
+ additionalFilesContext.ReportDiagnostic(diagnostic);
+ }
+ }
+ });
+ });
+ }
+}
diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/DiagnosticIds.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/DiagnosticIds.cs
index d5cdb1480d2..8090dbf0fc3 100644
--- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/DiagnosticIds.cs
+++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/DiagnosticIds.cs
@@ -15,5 +15,6 @@ internal static class DiagnosticIds
public const string UnexpectedProjectItemReadCallId = "RSG007";
public const string InvalidRazorContextComputedId = "RSG008";
public const string MetadataReferenceNotProvidedId = "RSG009";
+ public const string DuplicateRazorFileFound = "RSG010";
}
}
diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/RazorDiagnostics.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/RazorDiagnostics.cs
index a44f75aa970..45d0cb16e72 100644
--- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/RazorDiagnostics.cs
+++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/RazorDiagnostics.cs
@@ -100,6 +100,15 @@ internal static class RazorDiagnostics
isEnabledByDefault: true
);
+ public static readonly DiagnosticDescriptor DuplicateRazorFileIncludedDescriptor = new DiagnosticDescriptor(
+ DiagnosticIds.DuplicateRazorFileFound,
+ new LocalizableResourceString(nameof(RazorSourceGeneratorResources.DuplicateRazorFileIncludedTitle), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)),
+ new LocalizableResourceString(nameof(RazorSourceGeneratorResources.DuplicateRazorFileIncludedMessage), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)),
+ "RazorSourceGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true
+ );
+
public static Diagnostic AsDiagnostic(this RazorDiagnostic razorDiagnostic)
{
var descriptor = new DiagnosticDescriptor(
diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/RazorSourceGeneratorResources.resx b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/RazorSourceGeneratorResources.resx
index f589cf9f855..593724cc171 100644
--- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/RazorSourceGeneratorResources.resx
+++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/RazorSourceGeneratorResources.resx
@@ -1,17 +1,17 @@
-
@@ -177,4 +177,10 @@
Expected a valid MetadataReference, but found none.
-
\ No newline at end of file
+
+ Razor file was included twice
+
+
+ File '{0}' was included in the build twice. This will cause generator build issues.
+
+
diff --git a/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/DuplicateRazorFileIncludedAnalyzerTest.cs b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/DuplicateRazorFileIncludedAnalyzerTest.cs
new file mode 100644
index 00000000000..ec1c8be47a3
--- /dev/null
+++ b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/DuplicateRazorFileIncludedAnalyzerTest.cs
@@ -0,0 +1,118 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Razor.SourceGenerators;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Testing;
+using Xunit;
+
+namespace Microsoft.NET.Sdk.Razor.SourceGenerators.Tests;
+
+public class DuplicateRazorFileIncludedAnalyzerTest
+{
+ [Theory]
+ [InlineData("Duplicate.cshtml")]
+ [InlineData("Duplicate.razor")]
+ public async Task Analyzer_ReportsDiagnostic_WhenDuplicateRazorFileIsIncluded(string fileName)
+ {
+ // Arrange
+ var test = new CSharpAnalyzerTest
+ {
+ TestState =
+ {
+ Sources =
+ {
+ // Need a non-empty source file to make the test helper happy
+ ("Test.cs", "public class Test {}"),
+ },
+ AdditionalFiles =
+ {
+ (fileName, "Duplicate
"),
+ (fileName, "Duplicate
"),
+ },
+ },
+ ExpectedDiagnostics =
+ {
+ new DiagnosticResult(RazorDiagnostics.DuplicateRazorFileIncludedDescriptor.Id, DiagnosticSeverity.Error)
+ .WithLocation(fileName, 1, 1)
+ .WithMessageFormat(RazorDiagnostics.DuplicateRazorFileIncludedDescriptor.MessageFormat.ToString())
+ .WithArguments(fileName),
+ },
+ };
+
+ // Act & Assert
+ await test.RunAsync();
+ }
+
+ [Theory]
+ [InlineData("Duplicate.cshtml", "duplicate.cshtml")]
+ [InlineData("Duplicate.razor", "duplicate.razor")]
+ public async Task Analyzer_NoDiagnostic_WhenDuplicateRazorFileIsIncluded_DifferentCase(string fileName1, string fileName2)
+ {
+ // Arrange
+ var test = new CSharpAnalyzerTest
+ {
+ TestState =
+ {
+ Sources =
+ {
+ // Need a non-empty source file to make the test helper happy
+ ("Test.cs", "public class Test {}"),
+ },
+ AdditionalFiles =
+ {
+ (fileName1, "Duplicate
"),
+ (fileName2, "Duplicate
"),
+ },
+ },
+ ExpectedDiagnostics =
+ {
+ },
+ };
+
+ // Act & Assert
+ await test.RunAsync();
+ }
+
+ [Theory]
+ [InlineData("Duplicate.cshtml")]
+ [InlineData("Duplicate.razor")]
+ public async Task Analyzer_ReportsDiagnostic_WhenThreeDuplicateRazorFilesAreIncluded(string fileName)
+ {
+ // Arrange
+ var test = new CSharpAnalyzerTest
+ {
+ TestState =
+ {
+ Sources =
+ {
+ // Need a non-empty source file to make the test helper happy
+ ("Test.cs", "public class Test {}"),
+ },
+ AdditionalFiles =
+ {
+ (fileName, "Duplicate
"),
+ (fileName, "Duplicate
"),
+ (fileName, "Duplicate
"),
+ },
+ },
+ ExpectedDiagnostics =
+ {
+ new DiagnosticResult(RazorDiagnostics.DuplicateRazorFileIncludedDescriptor.Id, DiagnosticSeverity.Error)
+ .WithLocation(fileName, 1, 1)
+ .WithMessageFormat(RazorDiagnostics.DuplicateRazorFileIncludedDescriptor.MessageFormat.ToString())
+ .WithArguments(fileName),
+ new DiagnosticResult(RazorDiagnostics.DuplicateRazorFileIncludedDescriptor.Id, DiagnosticSeverity.Error)
+ .WithLocation(fileName, 1, 1)
+ .WithMessageFormat(RazorDiagnostics.DuplicateRazorFileIncludedDescriptor.MessageFormat.ToString())
+ .WithArguments(fileName),
+ },
+ };
+
+ // Act & Assert
+ await test.RunAsync();
+ }
+}
+
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 581f9841034..e2456ee3c44 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
@@ -27,6 +27,7 @@
+
diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Microsoft.AspNetCore.Razor.Test.Common.Tooling.csproj b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Microsoft.AspNetCore.Razor.Test.Common.Tooling.csproj
index ec3d2ab0f63..92c0373c802 100644
--- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Microsoft.AspNetCore.Razor.Test.Common.Tooling.csproj
+++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Microsoft.AspNetCore.Razor.Test.Common.Tooling.csproj
@@ -44,7 +44,7 @@
-
+