Created
June 30, 2024 20:32
-
-
Save to11mtm/5763c52eb7a31540aec5b3f2ab12540b to your computer and use it in GitHub Desktop.
roslyn analyzer hot mess
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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