From e9747d34f128be2cafdcaa26888bcfd8e739ac2b Mon Sep 17 00:00:00 2001 From: TJ Cowen Date: Wed, 8 Nov 2023 13:14:37 -0500 Subject: [PATCH] Allow OnlyVisibleToType on interfaces. --- src/D2L.CodeStyle.Analyzers/Diagnostics.cs | 9 ++++ .../Language/OnlyVisibleToAnalyzer.cs | 45 ++++++++++++++++++- .../Contract/OnlyVisibleToTypeAttribute.cs | 2 +- .../Specs/OnlyVisibleToAnalyzer.cs | 32 ++++++++++++- 4 files changed, 84 insertions(+), 4 deletions(-) diff --git a/src/D2L.CodeStyle.Analyzers/Diagnostics.cs b/src/D2L.CodeStyle.Analyzers/Diagnostics.cs index 0b3a3ffa..f923e444 100644 --- a/src/D2L.CodeStyle.Analyzers/Diagnostics.cs +++ b/src/D2L.CodeStyle.Analyzers/Diagnostics.cs @@ -766,5 +766,14 @@ public static class Diagnostics { defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true ); + + public static readonly DiagnosticDescriptor TypeNotVisibleToCaller = new DiagnosticDescriptor( + id: "D2L0103", + title: "Type is not visible to caller", + messageFormat: "The type '{0}' has restricted its visibility to an explicit set of callers", + category: "Correctness", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true + ); } } diff --git a/src/D2L.CodeStyle.Analyzers/Language/OnlyVisibleToAnalyzer.cs b/src/D2L.CodeStyle.Analyzers/Language/OnlyVisibleToAnalyzer.cs index 8748c47f..4ff34e16 100644 --- a/src/D2L.CodeStyle.Analyzers/Language/OnlyVisibleToAnalyzer.cs +++ b/src/D2L.CodeStyle.Analyzers/Language/OnlyVisibleToAnalyzer.cs @@ -1,5 +1,7 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; @@ -9,7 +11,8 @@ namespace D2L.CodeStyle.Analyzers.Language { public sealed partial class OnlyVisibleToAnalyzer : DiagnosticAnalyzer { public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create( - Diagnostics.MemberNotVisibleToCaller + Diagnostics.MemberNotVisibleToCaller, + Diagnostics.TypeNotVisibleToCaller ); public override void Initialize( AnalysisContext context ) { @@ -60,6 +63,46 @@ public static void OnCompilationStart( CompilationStartAnalysisContext context ) }, OperationKind.PropertyReference ); + + context.RegisterSyntaxNodeAction( + context => { + AnalyzeTypeUsage( context, (IdentifierNameSyntax)context.Node, model ); + }, + SyntaxKind.IdentifierName + ); + } + + private static void AnalyzeTypeUsage( + SyntaxNodeAnalysisContext context, + IdentifierNameSyntax syntax, + in Model model + ) { + // If there is no caller, we have nothing to check + INamedTypeSymbol? caller = context.ContainingSymbol?.ContainingType; + if( caller == null ) { + return; + } + + // If the identifier itself is not for an interface, we have nothing to check + if( context.SemanticModel.GetSymbolInfo( syntax ).Symbol is not INamedTypeSymbol symbol + || symbol.TypeKind != TypeKind.Interface ) { + return; + } + + // Perform the visibility check + if( model.IsVisibleTo( caller, symbol ) ) { + return; + } + + Diagnostic diagnostic = Diagnostic.Create( + Diagnostics.TypeNotVisibleToCaller, + syntax.GetLocation(), + messageArgs: new[] { + symbol.Name + } + ); + + context.ReportDiagnostic( diagnostic ); } private static void AnalyzeMemberUsage( diff --git a/src/D2L.CodeStyle.Annotations/Contract/OnlyVisibleToTypeAttribute.cs b/src/D2L.CodeStyle.Annotations/Contract/OnlyVisibleToTypeAttribute.cs index 8a752ff5..cb8b08ab 100644 --- a/src/D2L.CodeStyle.Annotations/Contract/OnlyVisibleToTypeAttribute.cs +++ b/src/D2L.CodeStyle.Annotations/Contract/OnlyVisibleToTypeAttribute.cs @@ -3,7 +3,7 @@ namespace D2L.CodeStyle.Annotations.Contract { [AttributeUsage( - validOn: AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property, + validOn: AttributeTargets.Constructor | AttributeTargets.Interface | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true, Inherited = false )] diff --git a/tests/D2L.CodeStyle.Analyzers.Test/Specs/OnlyVisibleToAnalyzer.cs b/tests/D2L.CodeStyle.Analyzers.Test/Specs/OnlyVisibleToAnalyzer.cs index 68f63426..f70e4cd1 100644 --- a/tests/D2L.CodeStyle.Analyzers.Test/Specs/OnlyVisibleToAnalyzer.cs +++ b/tests/D2L.CodeStyle.Analyzers.Test/Specs/OnlyVisibleToAnalyzer.cs @@ -9,7 +9,7 @@ namespace D2L.CodeStyle.Annotations.Contract { [AttributeUsage( - validOn: AttributeTargets.Method | AttributeTargets.Property, + validOn: AttributeTargets.Interface | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true, Inherited = false )] @@ -84,7 +84,7 @@ public static int RestrictedPropertySetter { set { } } namespace TestCases { - public static class AllowedCaller { + public static partial class AllowedCaller { public const string MetadataName = "TestCases.AllowedCaller"; @@ -206,6 +206,34 @@ public static void Test() { // =============================================================================== +namespace Targets { + [OnlyVisibleToType( TestCases.AllowedCaller.MetadataName, "OnlyVisibleToAnalyzer" )] + public interface ITypeRestrictedInterface { } + public interface ITypeUnrestrictedInterface { } +} + +namespace TestCases { + public static partial class AllowedCaller { + public static void UnrestrictedInterface() { + ITypeUnrestrictedInterface @interface = null; + } + public static void RestrictedInterface() { + ITypeRestrictedInterface @interface = null; + } + } + + public static partial class DisallowedCaller { + public static void UnrestrictedInterface() { + ITypeUnrestrictedInterface @interface = null; + } + public static void RestrictedInterface() { + /* TypeNotVisibleToCaller(ITypeRestrictedInterface) */ ITypeRestrictedInterface /**/ @interface = null; + } + } +} + +// =============================================================================== + namespace Targets { public static class UnknownTargetTypes {