diff --git a/src/Framework/AspNetCoreAnalyzers/test/Infrastructure/WellKnownTypesTests.cs b/src/Framework/AspNetCoreAnalyzers/test/Infrastructure/WellKnownTypesTests.cs index 8c02e10752d8..7619fa767fd1 100644 --- a/src/Framework/AspNetCoreAnalyzers/test/Infrastructure/WellKnownTypesTests.cs +++ b/src/Framework/AspNetCoreAnalyzers/test/Infrastructure/WellKnownTypesTests.cs @@ -4,7 +4,9 @@ using System.Collections.Immutable; using Microsoft.AspNetCore.Analyzer.Testing; using Microsoft.AspNetCore.App.Analyzers.Infrastructure; +using Microsoft.AspNetCore.Razor.Hosting; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; namespace Microsoft.AspNetCore.Analyzers.Infrastructure; @@ -34,6 +36,52 @@ static void Main() Assert.Collection(diagnostics, d => Assert.Equal("TEST001", d.Id)); } + [Theory] + [InlineData("ExternAssembly")] + [InlineData("SystemFoo")] + [InlineData("MicrosoftFoo")] + public async Task ResolveAllWellKnownTypes_ToleratesDuplicateTypeNames(string assemblyName) + { + // Arrange + var source = TestSource.Read(@" +class Program +{ + static void Main() + { + } +} +"); + var referenceSource = """ + namespace Microsoft.AspNetCore.Builder + { + public static class EndpointRouteBuilderExtensions + { + } + } + """; + // Act + var project = TestDiagnosticAnalyzerRunner.CreateProjectWithReferencesInBinDir(GetType().Assembly, source.Source); + Stream assemblyStream = GetInMemoryAssemblyStreamForCode(referenceSource, assemblyName, project.MetadataReferences.ToArray()); + project = project.AddMetadataReference(MetadataReference.CreateFromStream(assemblyStream)); + var diagnostics = await Runner.GetDiagnosticsAsync(project); + + // Assert + Assert.Collection(diagnostics, d => Assert.Equal("TEST001", d.Id)); + } + + private static Stream GetInMemoryAssemblyStreamForCode(string code, string assemblyName, params MetadataReference[] references) + { + var tree = CSharpSyntaxTree.ParseText(code); + var trees = ImmutableArray.Create(tree); + var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); + var compilation = CSharpCompilation.Create(assemblyName, trees).WithOptions(options); + compilation = compilation.AddReferences(references); + var stream = new MemoryStream(); + var emitResult = compilation.Emit(stream); + stream.Seek(0, SeekOrigin.Begin); + return stream; + } + [DiagnosticAnalyzer(LanguageNames.CSharp)] private class TestAnalyzer : DiagnosticAnalyzer { diff --git a/src/Shared/RoslynUtils/WellKnownTypes.cs b/src/Shared/RoslynUtils/WellKnownTypes.cs index 27e44a583cbc..a3a5cd3061b7 100644 --- a/src/Shared/RoslynUtils/WellKnownTypes.cs +++ b/src/Shared/RoslynUtils/WellKnownTypes.cs @@ -74,7 +74,7 @@ public INamedTypeSymbol Get(WellKnownTypeData.WellKnownType type) private INamedTypeSymbol GetAndCache(int index) { - var result = _compilation.GetTypeByMetadataName(WellKnownTypeData.WellKnownTypeNames[index]); + var result = GetTypeByMetadataNameInTargetAssembly(WellKnownTypeData.WellKnownTypeNames[index]); if (result == null) { throw new InvalidOperationException($"Failed to resolve well-known type '{WellKnownTypeData.WellKnownTypeNames[index]}'."); @@ -86,6 +86,33 @@ private INamedTypeSymbol GetAndCache(int index) return _lazyWellKnownTypes[index]!; } + // Filter for types within well-known (framework-owned) assemblies only. + private INamedTypeSymbol? GetTypeByMetadataNameInTargetAssembly(string metadataName) + { + var types = _compilation.GetTypesByMetadataName(metadataName); + if (types.Length == 0) + { + return null; + } + + if (types.Length == 1) + { + return types[0]; + } + + // Multiple types match the name. This is most likely caused by someone reusing the namespace + type name in their apps or libraries. + // Workaround this situation by prioritizing types in System and Microsoft assemblies. + foreach (var type in types) + { + if (type.ContainingAssembly.Identity.Name.StartsWith("System.", StringComparison.Ordinal) + || type.ContainingAssembly.Identity.Name.StartsWith("Microsoft.", StringComparison.Ordinal)) + { + return type; + } + } + return null; + } + public bool IsType(ITypeSymbol type, WellKnownTypeData.WellKnownType[] wellKnownTypes) => IsType(type, wellKnownTypes, out var _); public bool IsType(ITypeSymbol type, WellKnownTypeData.WellKnownType[] wellKnownTypes, [NotNullWhen(true)] out WellKnownTypeData.WellKnownType? match)