Skip to content

Instantly share code, notes, and snippets.

@ophura
Created September 23, 2025 10:10
Show Gist options
  • Select an option

  • Save ophura/1e0918361e776933b8770ee423144648 to your computer and use it in GitHub Desktop.

Select an option

Save ophura/1e0918361e776933b8770ee423144648 to your computer and use it in GitHub Desktop.
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;
using System.Linq;
namespace ApiUsageAnalyzer
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class ApiUsageAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor _descriptor = new DiagnosticDescriptor(
id: "ID0002",
title: "Mismatched ApiUsage target",
messageFormat: "Event '{1}' is marked with ApiUsage({0}.{2}) but is called with {0}.{3}",
category: "Usage",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true
);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(_descriptor);
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.RegisterSyntaxNodeAction(AnalyzeInvocation, SyntaxKind.InvocationExpression);
}
private static void AnalyzeInvocation(SyntaxNodeAnalysisContext context)
{
if (!(context.Node is InvocationExpressionSyntax invocation)) return;
var model = context.SemanticModel;
var arguments = invocation.ArgumentList.Arguments;
var enumInfo = model.GetTypeInfo(arguments.First().Expression);
var fields = enumInfo.Type.GetMembers().OfType<IFieldSymbol>().ToDictionary(field => field.ConstantValue, field => field.Name);
var target = model.GetConstantValue(arguments.First().Expression).Value;
var method = (string)model.GetConstantValue(arguments.Last().Expression).Value;
var type = context.ContainingSymbol.ContainingType;
var symbol = type.GetMembers(method).OfType<IMethodSymbol>().First();
var attribute = symbol.GetAttributes().First(data => data.AttributeClass.Name is "ApiUsageAttribute");
var usage = attribute.ConstructorArguments.First().Value;
if (target.Equals(usage)) return;
context.ReportDiagnostic(Diagnostic.Create(_descriptor, invocation.GetLocation(), enumInfo.Type.Name, method, fields[usage], fields[target]));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment