namespace BizHawk.Analyzers; using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AmbiguousMoneyToFloatConversionAnalyzer : DiagnosticAnalyzer { private static readonly DiagnosticDescriptor DiagAmbiguousMoneyToFloatConversion = new( id: "BHI1105", title: "Use unambiguous decimal<=>float/double conversion methods", messageFormat: "use {0} for checked conversion, or {1} for unchecked", category: "Usage", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(DiagAmbiguousMoneyToFloatConversion); public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); context.RegisterCompilationStartAction(initContext => { initContext.RegisterOperationAction(oac => { var conversionOp = (IConversionOperation) oac.Operation; var typeOutput = conversionOp.Type?.SpecialType ?? SpecialType.None; var typeInput = conversionOp.Operand.Type?.SpecialType ?? SpecialType.None; bool isToDecimal; bool isDoublePrecision; if (typeOutput is SpecialType.System_Decimal) { if (typeInput is SpecialType.System_Double) isDoublePrecision = true; else if (typeInput is SpecialType.System_Single) isDoublePrecision = false; else return; isToDecimal = true; } else if (typeInput is SpecialType.System_Decimal) { if (typeOutput is SpecialType.System_Double) isDoublePrecision = true; else if (typeOutput is SpecialType.System_Single) isDoublePrecision = false; else return; isToDecimal = false; } else { return; } var conversionSyn = conversionOp.Syntax; //TODO check the suggested methods are accessible (i.e. BizHawk.Common is referenced) oac.ReportDiagnostic(Diagnostic.Create( DiagAmbiguousMoneyToFloatConversion, (conversionSyn.Parent?.Kind() is SyntaxKind.CheckedExpression or SyntaxKind.UncheckedExpression ? conversionSyn.Parent : conversionSyn).GetLocation(), conversionOp.IsChecked ? DiagnosticSeverity.Error : DiagnosticSeverity.Warning, additionalLocations: null, properties: null, messageArgs: isToDecimal ? [ $"new decimal({(isDoublePrecision ? "double" : "float")})", // "checked" "static NumberExtensions.ConvertToMoneyTruncated", // "unchecked" ] : [ $"decimal.{(isDoublePrecision ? "ConvertToF64" : "ConvertToF32")} ext. (from NumberExtensions)", // "checked" $"static Decimal.{(isDoublePrecision ? "ToDouble" : "ToSingle")}", // "unchecked" ])); }, OperationKind.Conversion); }); } }