namespace BizHawk.Analyzers; using System.Collections.Generic; using System.Linq; using System.Threading; public static class RoslynUtils { public static SyntaxNode? EnclosingTypeDeclarationSyntax(this CSharpSyntaxNode node) { var parent = node.Parent; while (parent is not (null or TypeDeclarationSyntax)) parent = parent.Parent; return parent; } public static string GetMethodName(this ConversionOperatorDeclarationSyntax cods) => cods.ImplicitOrExplicitKeyword.ToString() is "implicit" ? WellKnownMemberNames.ImplicitConversionName : cods.CheckedKeyword.IsEmpty() ? WellKnownMemberNames.ExplicitConversionName : WellKnownMemberNames.CheckedExplicitConversionName; public static string GetMethodName(this OperatorDeclarationSyntax ods) => ods.OperatorToken.ToString() switch { "!" => WellKnownMemberNames.LogicalNotOperatorName, "!=" => WellKnownMemberNames.InequalityOperatorName, "%" => WellKnownMemberNames.ModulusOperatorName, "&" => WellKnownMemberNames.BitwiseAndOperatorName, "&&" => WellKnownMemberNames.LogicalAndOperatorName, "*" => ods.CheckedKeyword.IsEmpty() ? WellKnownMemberNames.MultiplyOperatorName : WellKnownMemberNames.CheckedMultiplyOperatorName, "+" => ods.ParameterList.Parameters.Count is 1 ? WellKnownMemberNames.UnaryPlusOperatorName : ods.CheckedKeyword.IsEmpty() ? WellKnownMemberNames.AdditionOperatorName : WellKnownMemberNames.CheckedAdditionOperatorName, "++" => ods.CheckedKeyword.IsEmpty() ? WellKnownMemberNames.IncrementOperatorName : WellKnownMemberNames.CheckedIncrementOperatorName, "-" => ods.ParameterList.Parameters.Count is 1 ? ods.CheckedKeyword.IsEmpty() ? WellKnownMemberNames.UnaryNegationOperatorName : WellKnownMemberNames.CheckedUnaryNegationOperatorName : ods.CheckedKeyword.IsEmpty() ? WellKnownMemberNames.SubtractionOperatorName : WellKnownMemberNames.CheckedSubtractionOperatorName, "--" => ods.CheckedKeyword.IsEmpty() ? WellKnownMemberNames.DecrementOperatorName : WellKnownMemberNames.CheckedDecrementOperatorName, "/" => ods.CheckedKeyword.IsEmpty() ? WellKnownMemberNames.DivisionOperatorName : WellKnownMemberNames.CheckedDivisionOperatorName, "<" => WellKnownMemberNames.LessThanOperatorName, "<<" => WellKnownMemberNames.LeftShiftOperatorName, "<=" => WellKnownMemberNames.LessThanOrEqualOperatorName, "==" => WellKnownMemberNames.EqualityOperatorName, ">" => WellKnownMemberNames.GreaterThanOperatorName, ">=" => WellKnownMemberNames.GreaterThanOrEqualOperatorName, ">>" => WellKnownMemberNames.RightShiftOperatorName, ">>>" => WellKnownMemberNames.UnsignedRightShiftOperatorName, "^" => WellKnownMemberNames.ExclusiveOrOperatorName, "false" => WellKnownMemberNames.FalseOperatorName, "true" => WellKnownMemberNames.TrueOperatorName, "|" => WellKnownMemberNames.BitwiseOrOperatorName, "||" => WellKnownMemberNames.LogicalOrOperatorName, "~" => WellKnownMemberNames.OnesComplementOperatorName, // ...and some operators only exist in VB.NET (dw, you're not missing anything) _ => throw new ArgumentException(paramName: nameof(ods), message: "pretend this is a BHI6660 unexpected token in AST (in this case, a new kind of operator was added to C#)"), }; private static ITypeSymbol? GetThrownExceptionType(this SemanticModel model, ExpressionSyntax exprSyn) => exprSyn is ObjectCreationExpressionSyntax ? model.GetTypeInfo(exprSyn).Type : null; // code reads `throw ` public static ITypeSymbol? GetThrownExceptionType(this SemanticModel model, ThrowExpressionSyntax tes) => model.GetThrownExceptionType(tes.Expression); public static ITypeSymbol? GetThrownExceptionType(this SemanticModel model, ThrowStatementSyntax tss) => model.GetThrownExceptionType(tss.Expression!); public static TypeInfo GetTypeInfo(this SemanticModel model, IArgumentOperation argOp, CancellationToken cancellationToken) { var syn = argOp.Syntax; return model.GetTypeInfo(syn is ArgumentSyntax argSyn ? argSyn.Expression : syn, cancellationToken); } public static bool IsEmpty(this SyntaxToken token) => token.ToString().Length is 0; public static Location LocWithoutReceiver(this InvocationExpressionSyntax ies) { var location = ies.GetLocation(); if (ies.Expression is MemberAccessExpressionSyntax maes) { location = Location.Create(location.SourceTree!, location.SourceSpan.Slice(maes.Expression.Span.Length)); } return location; } public static Location LocWithoutReceiver(this IInvocationOperation operation) => ((InvocationExpressionSyntax) operation.Syntax).LocWithoutReceiver(); public static bool Matches(this ISymbol expected, ISymbol? actual) => SymbolEqualityComparer.Default.Equals(expected, actual); #if false // easier to just `.OriginalDefinition` always public static bool Matches(this INamedTypeSymbol expected, INamedTypeSymbol? actual, bool degenericise) { if (degenericise) { if (expected.IsGenericType && !expected.IsUnboundGenericType) return expected.OriginalDefinition.Matches(actual, degenericise: true); if (actual is not null && actual.IsGenericType && !actual.IsUnboundGenericType) return expected.Matches(actual.OriginalDefinition, degenericise: true); } return expected.Matches(actual as ISymbol); } #endif public static IEnumerable Matching( this SyntaxList list, INamedTypeSymbol targetAttrSym, SemanticModel semanticModel, CancellationToken cancellationToken) => list.SelectMany(static als => als.Attributes) .Where(aSyn => targetAttrSym.Matches(semanticModel.GetTypeInfo(aSyn, cancellationToken).Type)); public static IEnumerable Matching( this SyntaxList list, INamedTypeSymbol targetAttrSym, SyntaxNodeAnalysisContext snac) => list.Matching(targetAttrSym, snac.SemanticModel, snac.CancellationToken); public static TextSpan Slice(this TextSpan span, int start) => TextSpan.FromBounds(start: span.Start + start, end: span.End); public static TextSpan Slice(this TextSpan span, int start, int length) => new(start: span.Start + start, length: length); }