Skip to content

Instantly share code, notes, and snippets.

@to11mtm
Created June 30, 2024 20:32
Show Gist options
  • Save to11mtm/5763c52eb7a31540aec5b3f2ab12540b to your computer and use it in GitHub Desktop.
Save to11mtm/5763c52eb7a31540aec5b3f2ab12540b to your computer and use it in GitHub Desktop.
roslyn analyzer hot mess
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.MSBuild;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ILogger = Microsoft.Extensions.Logging.ILogger;
using System.ComponentModel;
namespace System.Runtime.CompilerServices
{
[EditorBrowsable(EditorBrowsableState.Never)]
internal class IsExternalInit{}
}
namespace Analyzer
{
public enum ArgTypes
{
Unknown,
Literal,
Variable,
VariableProp,
MemberProp,
}
public readonly record struct ArgSet(
Nullable<int> EventIdSlot,
Nullable<int> exceptionSlot,
int messageSlot,
int argSlot,
string? Level);
public record InvocationSet(string Project, string File, int Line, string Level, ArgData? ExceptionPassed, ArgData Message, List<ArgData> Args);
public record ArgData(ArgTypes Type, string Data);
class Program
{
static async Task Main(string[] args)
{
var logInvokes = new List<InvocationSet>();
var workspace = MSBuildWorkspace.Create();
var solutionPath = args[0];
var solution = await workspace.OpenSolutionAsync(solutionPath);
foreach (var project in solution.Projects)
{
foreach (var document in project.Documents)
{
var model = await document.GetSemanticModelAsync();
var root = await document.GetSyntaxRootAsync();
var invocations = root.DescendantNodes()
.OfType<InvocationExpressionSyntax>();
foreach (var invocation in invocations)
{
var symbolInfo = model.GetSymbolInfo(invocation);
ArgSet arf = default;
if (symbolInfo.Symbol is IMethodSymbol methodSymbol &&
methodSymbol.IsExtensionMethod &&
methodSymbol.ContainingType.ToDisplayString() ==
"Microsoft.Extensions.Logging.LoggerExtensions")
{
ILogger logger;
if (methodSymbol.Name == "Log")
{
if (methodSymbol.Parameters.Length > 2)
{
if (methodSymbol.Parameters[2].Type.Name == "EventId")
{
if (methodSymbol.Parameters[3].Type.Name == "Exception")
{
arf = new ArgSet(2, 3, 4, 5, null);
}
else
{
arf = new ArgSet(2, null, 3, 4, null);
}
}
else if (methodSymbol.Parameters[2].Type.Name == "Exception")
{
arf = new ArgSet(null, 2, 3, 4, null);
}
else
{
arf = new ArgSet(null, null, 2, 3, null);
}
}
}
else if (methodSymbol.Name.StartsWith("Log"))
{
if (methodSymbol.Name.EndsWith("Information"))
{
arf = GetArgsForLevel(methodSymbol, "Information");
}
else if (methodSymbol.Name.EndsWith("Debug"))
{
arf = GetArgsForLevel(methodSymbol, "Debug");
}
else if (methodSymbol.Name.EndsWith("Error"))
{
arf = GetArgsForLevel(methodSymbol, "Error");
}
else if (methodSymbol.Name.EndsWith("Critical"))
{
arf = GetArgsForLevel(methodSymbol, "Critical");
}
else if (methodSymbol.Name.EndsWith("Warning"))
{
arf = GetArgsForLevel(methodSymbol, "Warning");
}
else if (methodSymbol.Name.EndsWith("Trace"))
{
arf = GetArgsForLevel(methodSymbol, "Trace");
}
}
var messageInfo = GetArgData(invocation.ArgumentList.Arguments[arf.messageSlot]);
var level = arf.Level ?? GetArgData(invocation.ArgumentList.Arguments[1]).Data;
var exceptionInfo = arf.exceptionSlot != null
? GetArgData(invocation.ArgumentList.Arguments[arf.exceptionSlot.Value])
: default;
var eventIdInfo = arf.EventIdSlot != null
? GetArgData(invocation.ArgumentList.Arguments[arf.EventIdSlot.Value])
: default;
var innerArgs = new List<ArgData>();
foreach (var argument in invocation.ArgumentList.Arguments.Skip(arf.argSlot-1))
{
// Check for simple constant value: like "something" or 123
(ArgTypes Literal, string ValueText) lit = default;
innerArgs.Add(GetArgData(argument));
}
var loc = invocation.GetLocation().GetLineSpan();
logInvokes.Add(new InvocationSet(project.AssemblyName, document.FilePath??string.Empty, invocation.GetLocation().GetLineSpan().Span.Start.Line, level, exceptionInfo, messageInfo, innerArgs ));
Console.WriteLine($"Found ILogger method call '{methodSymbol.Name}' in file {document.FilePath} at {invocation.GetLocation().GetLineSpan()}");
}
}
}
}
foreach (var se in logInvokes.OrderBy(a => a.File).ThenBy(a => a.Line)
.Select(a => a.Level + '|' + a.Message + '|' + a.Args.Aggregate("", (a, b) => a + b.ToString())))
{
Console.WriteLine(se);
}
}
private static ArgData GetArgData(ArgumentSyntax argument)
{
ArgData lit;
if (argument.Expression is LiteralExpressionSyntax literal)
{
lit = new(ArgTypes.Literal, literal.Token.ValueText);
//Console.WriteLine($"Found constant: {literal.Token.Value}");
}
// Check for a variable or property access: like variable or variable.Property
else if (argument.Expression is IdentifierNameSyntax identifier)
{
lit = new(ArgTypes.VariableProp,identifier.GetText().ToString());
Console.WriteLine($"Found variable: {identifier.Identifier.Text}");
}
else if (argument.Expression is MemberAccessExpressionSyntax memberAccess)
{
// Here we have something with a "." in it - so it may be a property access.
if (memberAccess.Expression is IdentifierNameSyntax variable)
{
lit = new(ArgTypes.MemberProp, $"{variable.Identifier.Text}.{memberAccess.Name.Identifier.Text}");
}
else
{
lit = new(ArgTypes.MemberProp, memberAccess.GetText().ToString());
}
}
else
{
lit = new(ArgTypes.Unknown, argument.Expression.GetText().ToString());
}
return lit;
}
private static ArgSet GetArgsForLevel(IMethodSymbol methodSymbol, string level)
{
ArgSet arf = default;
if (methodSymbol.Parameters.Length > 2)
{
if (methodSymbol.Parameters[1].Type.Name == "EventId")
{
if (methodSymbol.Parameters[2].Type.Name == "Exception")
{
arf = new ArgSet(1, 2, 3, 4, level);
}
else
{
arf = new ArgSet(1, null, 3, 4, level);
}
}
else if (methodSymbol.Parameters[1].Type.Name == "Exception")
{
arf = new ArgSet(null, 1, 2, 3, level);
}
else
{
arf = new ArgSet(null, null, 1, 2, level);
}
}
return arf;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment