Skip to content

Commit

Permalink
Allow OnlyVisibleToType on interfaces.
Browse files Browse the repository at this point in the history
  • Loading branch information
TimothyJCowen committed Nov 8, 2023
1 parent c33720c commit e9747d3
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 4 deletions.
9 changes: 9 additions & 0 deletions src/D2L.CodeStyle.Analyzers/Diagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}
}
45 changes: 44 additions & 1 deletion src/D2L.CodeStyle.Analyzers/Language/OnlyVisibleToAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -9,7 +11,8 @@ namespace D2L.CodeStyle.Analyzers.Language {
public sealed partial class OnlyVisibleToAnalyzer : DiagnosticAnalyzer {

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
Diagnostics.MemberNotVisibleToCaller
Diagnostics.MemberNotVisibleToCaller,
Diagnostics.TypeNotVisibleToCaller
);

public override void Initialize( AnalysisContext context ) {
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
)]
Expand Down
32 changes: 30 additions & 2 deletions tests/D2L.CodeStyle.Analyzers.Test/Specs/OnlyVisibleToAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
)]
Expand Down Expand Up @@ -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";

Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit e9747d3

Please sign in to comment.