diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/DetectPreviewFeatureAnalyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/DetectPreviewFeatureAnalyzer.cs index a2c0972639..2d12642bb5 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/DetectPreviewFeatureAnalyzer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/DetectPreviewFeatureAnalyzer.cs @@ -394,7 +394,7 @@ private static void ProcessPropertyOrMethodAttributes(SymbolAnalysisContext cont { if (SymbolIsAnnotatedAsPreview(baseInterfaceMember, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol)) { - string baseInterfaceMemberName = baseInterfaceMember.ContainingType != null ? baseInterfaceMember.ContainingType.Name + "." + baseInterfaceMember.Name : baseInterfaceMember.Name; + string baseInterfaceMemberName = baseInterfaceMember.ContainingSymbol != null ? baseInterfaceMember.ContainingSymbol.Name + "." + baseInterfaceMember.Name : baseInterfaceMember.Name; context.ReportDiagnostic(propertyOrMethodSymbol.CreateDiagnostic(ImplementsPreviewMethodRule, propertyOrMethodSymbol.Name, baseInterfaceMemberName)); } } @@ -404,7 +404,7 @@ private static void ProcessPropertyOrMethodAttributes(SymbolAnalysisContext cont ISymbol overridden = propertyOrMethodSymbol.GetOverriddenMember(); if (SymbolIsAnnotatedAsPreview(overridden, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol)) { - string overriddenName = overridden.ContainingType != null ? overridden.ContainingType.Name + "." + overridden.Name : overridden.Name; + string overriddenName = overridden.ContainingSymbol != null ? overridden.ContainingSymbol.Name + "." + overridden.Name : overridden.Name; context.ReportDiagnostic(propertyOrMethodSymbol.CreateDiagnostic(OverridesPreviewMethodRule, propertyOrMethodSymbol.Name, overriddenName)); } } @@ -551,11 +551,11 @@ private static bool OperationUsesPreviewFeatures(OperationAnalysisContext contex if (SymbolIsAnnotatedOrUsesPreviewTypes(methodSymbol, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol, out referencedPreviewSymbol)) { // Constructor symbols have the name .ctor. Return the containing type instead so we get meaningful names in the diagnostic message - referencedPreviewSymbol = referencedPreviewSymbol.ContainingType; + referencedPreviewSymbol = referencedPreviewSymbol.ContainingSymbol; return true; } - if (SymbolIsAnnotatedOrUsesPreviewTypes(methodSymbol.ContainingType, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol, out referencedPreviewSymbol)) + if (SymbolIsAnnotatedOrUsesPreviewTypes(methodSymbol.ContainingSymbol, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol, out referencedPreviewSymbol)) { return true; } @@ -626,10 +626,10 @@ private static bool SymbolIsAnnotatedAsPreview(ISymbol symbol, ConcurrentDiction return true; } - INamedTypeSymbol? parent = symbol.ContainingType; - if (parent is INamespaceSymbol) + ISymbol? parent = symbol.ContainingSymbol; + while (parent is INamespaceSymbol) { - parent = parent.ContainingType; + parent = parent.ContainingSymbol; } if (parent != null) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DetectPreviewFeatureTests.Dependencies.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DetectPreviewFeatureTests.Dependencies.cs new file mode 100644 index 0000000000..9ddbe3f2ea --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DetectPreviewFeatureTests.Dependencies.cs @@ -0,0 +1,186 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Test.Utilities; +using Xunit; +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Runtime.DetectPreviewFeatureAnalyzer, + Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; + +namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests +{ + public partial class DetectPreviewFeatureUnitTests + { + [Theory] + [InlineData("assembly")] + [InlineData("module")] + public async Task TestAssemblyDoesntUsePreviewDependency(string assemblyOrModule) + { + // No diagnostic when we don't use any APIs from an assembly marked with Preview + string csCurrentAssemblyCode = @" +using System; + +public class Program +{ + public void ProgramMethod() + { + new Program(); + } +}"; + string csDepedencyCode = @$"[{assemblyOrModule}: System.Runtime.Versioning.RequiresPreviewFeatures]"; + + var test = SetupDependencyAndTestCSWithOneSourceFile(csCurrentAssemblyCode, csDepedencyCode); + await test.RunAsync(); + } + + [Theory] + [InlineData("assembly")] + [InlineData("module")] + public async Task TestCallAPIsFromAssemblyMarkedAsPreview(string assemblyOrModule) + { + string csDependencyCode = @" +public class Library +{ + public void AMethod() { } + private int _property; + public int AProperty + { + get => 1; + set + { + _property = value; + } + } +}"; + csDependencyCode = @$"[{assemblyOrModule}: System.Runtime.Versioning.RequiresPreviewFeatures] {csDependencyCode}"; + + string csCurrentAssemblyCode = @" +using System; + +public class Program +{ + public void ProgramMethod() + { + Library library = {|#1:new Library()|}; + + {|#0:library.AMethod()|}; + int prop = {|#2:library.AProperty|}; + } +}"; + var test = SetupDependencyAndTestCSWithOneSourceFile(csCurrentAssemblyCode, csDependencyCode); + test.ExpectedDiagnostics.Add(VerifyCS.Diagnostic(DetectPreviewFeatureAnalyzer.GeneralPreviewFeatureAttributeRule).WithLocation(0).WithArguments("AMethod")); + test.ExpectedDiagnostics.Add(VerifyCS.Diagnostic(DetectPreviewFeatureAnalyzer.GeneralPreviewFeatureAttributeRule).WithLocation(1).WithArguments("Library")); + test.ExpectedDiagnostics.Add(VerifyCS.Diagnostic(DetectPreviewFeatureAnalyzer.GeneralPreviewFeatureAttributeRule).WithLocation(2).WithArguments("AProperty")); + await test.RunAsync(); + } + + [Theory] + [InlineData("assembly")] + [InlineData("module")] + public async Task TestNoCallsToPreviewDependency(string assemblyOrModule) + { + string csDependencyCode = @" +public class Library +{ + public void AMethod() { } + private int _property; + public int AProperty + { + get => 1; + set + { + _property = value; + } + } +}"; + csDependencyCode = @$"[{assemblyOrModule}: System.Runtime.Versioning.RequiresPreviewFeatures] {csDependencyCode}"; + + string csCurrentAssemblyCode = @" +using System; + +public class Program +{ + public void ProgramMethod() + { + } +}"; + var test = SetupDependencyAndTestCSWithOneSourceFile(csCurrentAssemblyCode, csDependencyCode); + await test.RunAsync(); + } + + [Fact] + public async Task TestMixtureOfPreviewAPIsInDependency() + { + string csDependencyCode = @" +public class Library +{ + public void AMethod() + { +#pragma warning disable CA2252 + APreviewMethod(); +#pragma warning enable CA2252 + } + + [System.Runtime.Versioning.RequiresPreviewFeatures] + public void APreviewMethod() { } +}"; + + string csCurrentAssemblyCode = @" +using System; + +public class Program +{ + public void ProgramMethod() + { + Library library = new Library(); + + library.AMethod(); + {|#0:library.APreviewMethod()|}; + } +}"; + var test = SetupDependencyAndTestCSWithOneSourceFile(csCurrentAssemblyCode, csDependencyCode); + test.ExpectedDiagnostics.Add(VerifyCS.Diagnostic(DetectPreviewFeatureAnalyzer.GeneralPreviewFeatureAttributeRule).WithLocation(0).WithArguments("APreviewMethod")); + await test.RunAsync(); + } + + [Fact] + public async Task TestDeepNestingOfPreviewAPIsInDependency() + { + string csDependencyCode = @" +public class Library +{ + [System.Runtime.Versioning.RequiresPreviewFeatures] + public class NestedClass0 + { + public class NestedClass1 + { + public class NestedClass2 + { + public class NestedClass3 + { + public void APreviewMethod() { } + } + } + } + } +}"; + + string csCurrentAssemblyCode = @" +using System; + +public class Program +{ + public void ProgramMethod() + { + Library.NestedClass0.NestedClass1.NestedClass2.NestedClass3 nestedClass = {|#0:new()|}; + + {|#1:nestedClass.APreviewMethod()|}; + } +}"; + var test = SetupDependencyAndTestCSWithOneSourceFile(csCurrentAssemblyCode, csDependencyCode); + test.ExpectedDiagnostics.Add(VerifyCS.Diagnostic(DetectPreviewFeatureAnalyzer.GeneralPreviewFeatureAttributeRule).WithLocation(0).WithArguments("NestedClass3")); + test.ExpectedDiagnostics.Add(VerifyCS.Diagnostic(DetectPreviewFeatureAnalyzer.GeneralPreviewFeatureAttributeRule).WithLocation(1).WithArguments("APreviewMethod")); + await test.RunAsync(); + } + } +} \ No newline at end of file diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DetectPreviewFeatureUnitTests.Misc.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DetectPreviewFeatureUnitTests.Misc.cs index 9a2045f573..4a7d91bdf4 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DetectPreviewFeatureUnitTests.Misc.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DetectPreviewFeatureUnitTests.Misc.cs @@ -15,15 +15,45 @@ private static VerifyCS.Test TestCS(string csInput) { return new VerifyCS.Test { + ReferenceAssemblies = AdditionalMetadataReferences.Net60, LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.CSharp10, TestState = { Sources = { csInput - } + }, }, + }; + } + + private static VerifyCS.Test SetupDependencyAndTestCSWithOneSourceFile(string csInput, string csDependencyCode) + { + return new VerifyCS.Test + { ReferenceAssemblies = AdditionalMetadataReferences.Net60, + LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.CSharp10, + TestState = + { + Sources = + { + csInput + }, + AdditionalProjects = + { + ["PreviewAssembly"] = + { + Sources = + { + ("/PreviewAssembly/AssemblyInfo.g.cs", csDependencyCode) + }, + }, + }, + AdditionalProjectReferences = + { + "PreviewAssembly", + }, + }, }; }