Clean up registration in `BizHawk.Analyzer`

will rebuild in later commit
This commit is contained in:
YoshiRulz 2025-02-22 14:33:53 +10:00
parent 57921a9206
commit 03bf156a91
No known key found for this signature in database
GPG Key ID: C4DE31C245353FB7
5 changed files with 290 additions and 297 deletions

View File

@ -19,54 +19,52 @@ public sealed class AmbiguousMoneyToFloatConversionAnalyzer : DiagnosticAnalyzer
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(initContext =>
{
initContext.RegisterOperationAction(oac =>
context.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)
{
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);
});
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);
}
}

View File

@ -20,102 +20,99 @@ public sealed class ExprBodiedMemberFlowAnalyzer : DiagnosticAnalyzer
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(initContext =>
{
var ARROW_ONE_LINE = (' ', ' ');
// var ARROW_POST_SIG = (' ', '\n');
var ARROW_PRE_BODY = ('\n', ' ');
initContext.RegisterSyntaxNodeAction(
snac =>
var ARROW_ONE_LINE = (' ', ' ');
// var ARROW_POST_SIG = (' ', '\n');
var ARROW_PRE_BODY = ('\n', ' ');
context.RegisterSyntaxNodeAction(
snac =>
{
var aecs = (ArrowExpressionClauseSyntax) snac.Node;
(char Before, char After) expectedWhitespace;
string kind;
var parent = aecs.Parent;
if (parent is null)
{
var aecs = (ArrowExpressionClauseSyntax) snac.Node;
(char Before, char After) expectedWhitespace;
string kind;
var parent = aecs.Parent;
if (parent is null)
{
snac.ReportDiagnostic(Diagnostic.Create(DiagExprBodiedMemberFlow, aecs.GetLocation(), "Syntax node for expression-bodied member was orphaned?"));
snac.ReportDiagnostic(Diagnostic.Create(DiagExprBodiedMemberFlow, aecs.GetLocation(), "Syntax node for expression-bodied member was orphaned?"));
return;
}
void Flag(string message)
=> snac.ReportDiagnostic(Diagnostic.Create(DiagExprBodiedMemberFlow, parent.GetLocation(), message));
switch (parent)
{
case MethodDeclarationSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "method";
break;
case PropertyDeclarationSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "get-only prop";
break;
case AccessorDeclarationSyntax ads:
expectedWhitespace = ARROW_ONE_LINE;
switch (ads.Keyword.Text)
{
case "get":
kind = ads.Parent?.Parent is IndexerDeclarationSyntax ? "get-indexer" : "getter";
break;
case "set":
kind = ads.Parent?.Parent is IndexerDeclarationSyntax ? "set-indexer" : "setter";
break;
case "init":
kind = "setter";
break;
case "add":
kind = "event sub";
break;
case "remove":
kind = "event unsub";
break;
default:
Flag($"Expression-bodied accessor was of an unexpected kind: {ads.Parent!.Parent!.GetType().FullName}");
return;
}
break;
case ConstructorDeclarationSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "constructor";
break;
case LocalFunctionStatementSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "local method";
break;
case IndexerDeclarationSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "get-only indexer";
break;
case OperatorDeclarationSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "overloaded operator";
break;
case ConversionOperatorDeclarationSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "overloaded cast operator";
break;
case DestructorDeclarationSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "finalizer";
break;
default:
Flag($"Expression-bodied member was of an unexpected kind: {parent.GetType().FullName}");
return;
}
void Flag(string message)
=> snac.ReportDiagnostic(Diagnostic.Create(DiagExprBodiedMemberFlow, parent.GetLocation(), message));
switch (parent)
{
case MethodDeclarationSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "method";
break;
case PropertyDeclarationSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "get-only prop";
break;
case AccessorDeclarationSyntax ads:
expectedWhitespace = ARROW_ONE_LINE;
switch (ads.Keyword.Text)
{
case "get":
kind = ads.Parent?.Parent is IndexerDeclarationSyntax ? "get-indexer" : "getter";
break;
case "set":
kind = ads.Parent?.Parent is IndexerDeclarationSyntax ? "set-indexer" : "setter";
break;
case "init":
kind = "setter";
break;
case "add":
kind = "event sub";
break;
case "remove":
kind = "event unsub";
break;
default:
Flag($"Expression-bodied accessor was of an unexpected kind: {ads.Parent!.Parent!.GetType().FullName}");
return;
}
break;
case ConstructorDeclarationSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "constructor";
break;
case LocalFunctionStatementSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "local method";
break;
case IndexerDeclarationSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "get-only indexer";
break;
case OperatorDeclarationSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "overloaded operator";
break;
case ConversionOperatorDeclarationSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "overloaded cast operator";
break;
case DestructorDeclarationSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "finalizer";
break;
default:
Flag($"Expression-bodied member was of an unexpected kind: {parent.GetType().FullName}");
return;
}
static string EscapeChar(char c)
=> c is '\n' ? "\\n" : c.ToString();
void Fail()
=> Flag($"Whitespace around {kind} arrow syntax should be `{EscapeChar(expectedWhitespace.Before)}=>{EscapeChar(expectedWhitespace.After)}`");
if ((aecs.ArrowToken.HasLeadingTrivia ? '\n' : ' ') != expectedWhitespace.Before)
{
Fail();
return;
}
}
static string EscapeChar(char c)
=> c is '\n' ? "\\n" : c.ToString();
void Fail()
=> Flag($"Whitespace around {kind} arrow syntax should be `{EscapeChar(expectedWhitespace.Before)}=>{EscapeChar(expectedWhitespace.After)}`");
if ((aecs.ArrowToken.HasLeadingTrivia ? '\n' : ' ') != expectedWhitespace.Before)
{
Fail();
return;
}
#pragma warning disable BHI3102 // LINQ `Contains(char)` is fine here
var hasLineBreakAfterArrow = aecs.ArrowToken.HasTrailingTrivia && aecs.ArrowToken.TrailingTrivia.ToFullString().Contains('\n');
var hasLineBreakAfterArrow = aecs.ArrowToken.HasTrailingTrivia && aecs.ArrowToken.TrailingTrivia.ToFullString().Contains('\n');
#pragma warning restore BHI3102
if ((hasLineBreakAfterArrow ? '\n' : ' ') != expectedWhitespace.After) Fail();
},
SyntaxKind.ArrowExpressionClause);
});
if ((hasLineBreakAfterArrow ? '\n' : ' ') != expectedWhitespace.After) Fail();
},
SyntaxKind.ArrowExpressionClause);
}
}

View File

@ -113,74 +113,72 @@ public class HawkSourceAnalyzer : DiagnosticAnalyzer
=> aes.OperatorToken.RawKind is (int) SyntaxKind.EqualsToken && aes.Left is IdentifierNameSyntax { Identifier.Text: "_" };
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
INamedTypeSymbol? invalidOperationExceptionSym = null;
INamedTypeSymbol? switchExpressionExceptionSym = null;
context.RegisterSyntaxNodeAction(
snac =>
{
if (invalidOperationExceptionSym is null)
context.RegisterCompilationStartAction(initContext =>
{
var invalidOperationExceptionSym = initContext.Compilation.GetTypeByMetadataName("System.InvalidOperationException")!;
var switchExpressionExceptionSym = initContext.Compilation.GetTypeByMetadataName("System.Runtime.CompilerServices.SwitchExpressionException");
initContext.RegisterSyntaxNodeAction(
snac =>
{
invalidOperationExceptionSym = snac.Compilation.GetTypeByMetadataName("System.InvalidOperationException")!;
switchExpressionExceptionSym = snac.Compilation.GetTypeByMetadataName("System.Runtime.CompilerServices.SwitchExpressionException");
}
switch (snac.Node)
{
case AnonymousMethodExpressionSyntax:
snac.ReportDiagnostic(Diagnostic.Create(DiagNoAnonDelegates, snac.Node.GetLocation()));
break;
case AnonymousObjectCreationExpressionSyntax:
snac.ReportDiagnostic(Diagnostic.Create(DiagNoAnonClasses, snac.Node.GetLocation()));
break;
case AssignmentExpressionSyntax aes:
if (!IsDiscard(aes)) break;
if (snac.SemanticModel.GetSymbolInfo(aes.Right, snac.CancellationToken).Symbol?.Kind is not SymbolKind.Local) break;
snac.ReportDiagnostic(Diagnostic.Create(DiagNoDiscardingLocals, snac.Node.GetLocation()));
break;
case CollectionExpressionSyntax ces:
var cesError = CheckSpacingInList(ces.Elements, ces.OpenBracketToken, ces.ToString);
if (cesError is not null) snac.ReportDiagnostic(Diagnostic.Create(DiagListExprSpacing, ces.GetLocation(), cesError));
break;
case InterpolatedStringExpressionSyntax ises:
if (ises.StringStartToken.Text[0] is '@') snac.ReportDiagnostic(Diagnostic.Create(DiagInterpStringIsDollarAt, ises.GetLocation()));
break;
case ListPatternSyntax lps:
var lpsError = CheckSpacingInList(lps.Patterns, lps.OpenBracketToken, lps.ToString);
if (lpsError is not null) snac.ReportDiagnostic(Diagnostic.Create(DiagListExprSpacing, lps.GetLocation(), lpsError));
break;
case QueryExpressionSyntax:
snac.ReportDiagnostic(Diagnostic.Create(DiagNoQueryExpression, snac.Node.GetLocation()));
break;
case RecordDeclarationSyntax rds when rds.ClassOrStructKeyword.ToString() is not "class": // `record struct`s don't use this kind
snac.ReportDiagnostic(Diagnostic.Create(DiagRecordImplicitlyRefType, rds.GetLocation()));
break;
case SwitchExpressionArmSyntax { WhenClause: null, Pattern: DiscardPatternSyntax, Expression: ThrowExpressionSyntax tes }:
var thrownExceptionType = snac.SemanticModel.GetThrownExceptionType(tes);
if (thrownExceptionType is null)
{
snac.ReportDiagnostic(Diagnostic.Create(
DiagSwitchShouldThrowIOE,
tes.GetLocation(),
DiagnosticSeverity.Warning,
additionalLocations: null,
properties: null,
ERR_MSG_SWITCH_THROWS_UNKNOWN));
}
else if (!invalidOperationExceptionSym.Matches(thrownExceptionType) && switchExpressionExceptionSym?.Matches(thrownExceptionType) != true)
{
snac.ReportDiagnostic(Diagnostic.Create(DiagSwitchShouldThrowIOE, tes.GetLocation(), ERR_MSG_SWITCH_THROWS_WRONG_TYPE));
}
// else correct usage, do not flag
break;
}
},
SyntaxKind.AnonymousObjectCreationExpression,
SyntaxKind.AnonymousMethodExpression,
SyntaxKind.CollectionExpression,
SyntaxKind.InterpolatedStringExpression,
SyntaxKind.ListPattern,
SyntaxKind.QueryExpression,
SyntaxKind.RecordDeclaration,
SyntaxKind.SimpleAssignmentExpression,
SyntaxKind.SwitchExpressionArm);
switch (snac.Node)
{
case AnonymousMethodExpressionSyntax:
snac.ReportDiagnostic(Diagnostic.Create(DiagNoAnonDelegates, snac.Node.GetLocation()));
break;
case AnonymousObjectCreationExpressionSyntax:
snac.ReportDiagnostic(Diagnostic.Create(DiagNoAnonClasses, snac.Node.GetLocation()));
break;
case AssignmentExpressionSyntax aes:
if (!IsDiscard(aes)) break;
if (snac.SemanticModel.GetSymbolInfo(aes.Right, snac.CancellationToken).Symbol?.Kind is not SymbolKind.Local) break;
snac.ReportDiagnostic(Diagnostic.Create(DiagNoDiscardingLocals, snac.Node.GetLocation()));
break;
case CollectionExpressionSyntax ces:
var cesError = CheckSpacingInList(ces.Elements, ces.OpenBracketToken, ces.ToString);
if (cesError is not null) snac.ReportDiagnostic(Diagnostic.Create(DiagListExprSpacing, ces.GetLocation(), cesError));
break;
case InterpolatedStringExpressionSyntax ises:
if (ises.StringStartToken.Text[0] is '@') snac.ReportDiagnostic(Diagnostic.Create(DiagInterpStringIsDollarAt, ises.GetLocation()));
break;
case ListPatternSyntax lps:
var lpsError = CheckSpacingInList(lps.Patterns, lps.OpenBracketToken, lps.ToString);
if (lpsError is not null) snac.ReportDiagnostic(Diagnostic.Create(DiagListExprSpacing, lps.GetLocation(), lpsError));
break;
case QueryExpressionSyntax:
snac.ReportDiagnostic(Diagnostic.Create(DiagNoQueryExpression, snac.Node.GetLocation()));
break;
case RecordDeclarationSyntax rds when rds.ClassOrStructKeyword.ToString() is not "class": // `record struct`s don't use this kind
snac.ReportDiagnostic(Diagnostic.Create(DiagRecordImplicitlyRefType, rds.GetLocation()));
break;
case SwitchExpressionArmSyntax { WhenClause: null, Pattern: DiscardPatternSyntax, Expression: ThrowExpressionSyntax tes }:
var thrownExceptionType = snac.SemanticModel.GetThrownExceptionType(tes);
if (thrownExceptionType is null)
{
snac.ReportDiagnostic(Diagnostic.Create(
DiagSwitchShouldThrowIOE,
tes.GetLocation(),
DiagnosticSeverity.Warning,
additionalLocations: null,
properties: null,
ERR_MSG_SWITCH_THROWS_UNKNOWN));
}
else if (!invalidOperationExceptionSym.Matches(thrownExceptionType) && switchExpressionExceptionSym?.Matches(thrownExceptionType) != true)
{
snac.ReportDiagnostic(Diagnostic.Create(DiagSwitchShouldThrowIOE, tes.GetLocation(), ERR_MSG_SWITCH_THROWS_WRONG_TYPE));
}
// else correct usage, do not flag
break;
}
},
SyntaxKind.AnonymousObjectCreationExpression,
SyntaxKind.AnonymousMethodExpression,
SyntaxKind.CollectionExpression,
SyntaxKind.InterpolatedStringExpression,
SyntaxKind.ListPattern,
SyntaxKind.QueryExpression,
SyntaxKind.RecordDeclaration,
SyntaxKind.SimpleAssignmentExpression,
SyntaxKind.SwitchExpressionArm);
});
}
}

View File

@ -19,64 +19,62 @@ public sealed class TernaryInferredTypeMismatchAnalyzer : DiagnosticAnalyzer
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(initContext =>
{
initContext.RegisterOperationAction(oac =>
{
var ifelseOrTernaryOp = (IConditionalOperation) oac.Operation;
if (ifelseOrTernaryOp.WhenFalse is null) return;
var parent = ifelseOrTernaryOp.Parent!;
if (parent.Kind is OperationKind.Conversion) parent = parent.Parent!;
if (parent.Kind is not OperationKind.Interpolation) return;
var ternaryOp = ifelseOrTernaryOp;
var typeTernary = ternaryOp.Type!;
context.RegisterOperationAction(
oac =>
{
var ifelseOrTernaryOp = (IConditionalOperation) oac.Operation;
if (ifelseOrTernaryOp.WhenFalse is null) return;
var parent = ifelseOrTernaryOp.Parent!;
if (parent.Kind is OperationKind.Conversion) parent = parent.Parent!;
if (parent.Kind is not OperationKind.Interpolation) return;
var ternaryOp = ifelseOrTernaryOp;
var typeTernary = ternaryOp.Type!;
#if false // never hit; either both branches are string and there are no conversions, or conversions are necessary
if (typeTernary.SpecialType is SpecialType.System_String) return;
if (typeTernary.SpecialType is SpecialType.System_String) return;
#endif
var lhs = ternaryOp.WhenTrue;
var rhs = ternaryOp.WhenFalse;
var lhs = ternaryOp.WhenTrue;
var rhs = ternaryOp.WhenFalse;
static IOperation TrimImplicitCast(IOperation op)
=> op is IConversionOperation { Conversion.IsImplicit: true } implCastOp ? implCastOp.Operand : op;
var typeLHS = TrimImplicitCast(lhs).Type!;
var typeRHS = TrimImplicitCast(rhs).Type!;
if (typeLHS.Matches(typeRHS)) return; // unnecessary conversion operators on each branch? seen with `? this : this`
static IOperation TrimImplicitCast(IOperation op)
=> op is IConversionOperation { Conversion.IsImplicit: true } implCastOp ? implCastOp.Operand : op;
var typeLHS = TrimImplicitCast(lhs).Type!;
var typeRHS = TrimImplicitCast(rhs).Type!;
if (typeLHS.Matches(typeRHS)) return; // unnecessary conversion operators on each branch? seen with `? this : this`
const string ERR_MSG_OBJECT = "missing ToString means ternary branches are upcast to object";
var fatal = false;
IOperation flaggedOp = ternaryOp;
string message;
if (typeLHS.SpecialType is SpecialType.System_String)
{
flaggedOp = rhs;
message = ERR_MSG_OBJECT;
}
else if (typeRHS.SpecialType is SpecialType.System_String)
{
flaggedOp = lhs;
message = ERR_MSG_OBJECT;
}
else if (typeTernary.SpecialType is SpecialType.System_Object)
{
fatal = true;
message = "ternary branches are upcast to object! add ToString calls, or convert one to the other's type";
}
else
{
// if one's already an e.g. int literal, flag the e.g. char literal
if (typeTernary.Matches(typeLHS)) flaggedOp = rhs;
else if (typeTernary.Matches(typeRHS)) flaggedOp = lhs;
message = $"ternary branches are converted to {typeTernary} before serialisation, possibly unintended";
}
oac.ReportDiagnostic(Diagnostic.Create(
DiagTernaryInferredTypeMismatch,
flaggedOp.Syntax.GetLocation(),
fatal ? DiagnosticSeverity.Error : DiagnosticSeverity.Warning,
additionalLocations: null,
properties: null,
messageArgs: message));
},
OperationKind.Conditional);
});
const string ERR_MSG_OBJECT = "missing ToString means ternary branches are upcast to object";
var fatal = false;
IOperation flaggedOp = ternaryOp;
string message;
if (typeLHS.SpecialType is SpecialType.System_String)
{
flaggedOp = rhs;
message = ERR_MSG_OBJECT;
}
else if (typeRHS.SpecialType is SpecialType.System_String)
{
flaggedOp = lhs;
message = ERR_MSG_OBJECT;
}
else if (typeTernary.SpecialType is SpecialType.System_Object)
{
fatal = true;
message = "ternary branches are upcast to object! add ToString calls, or convert one to the other's type";
}
else
{
// if one's already an e.g. int literal, flag the e.g. char literal
if (typeTernary.Matches(typeLHS)) flaggedOp = rhs;
else if (typeTernary.Matches(typeRHS)) flaggedOp = lhs;
message = $"ternary branches are converted to {typeTernary} before serialisation, possibly unintended";
}
oac.ReportDiagnostic(Diagnostic.Create(
DiagTernaryInferredTypeMismatch,
flaggedOp.Syntax.GetLocation(),
fatal ? DiagnosticSeverity.Error : DiagnosticSeverity.Warning,
additionalLocations: null,
properties: null,
messageArgs: message));
},
OperationKind.Conditional);
}
}

View File

@ -27,39 +27,41 @@ public sealed class UseNameofOperatorAnalyzer : DiagnosticAnalyzer
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
ISymbol? memberInfoDotNameSym = null;
ISymbol? typeDotToStringSym = null;
context.RegisterSyntaxNodeAction(
snac =>
{
memberInfoDotNameSym ??= snac.Compilation.GetTypeByMetadataName("System.Reflection.MemberInfo")!.GetMembers("Name")[0];
typeDotToStringSym ??= snac.Compilation.GetTypeByMetadataName("System.Type")!
.GetMembers(WellKnownMemberNames.ObjectToString)[0];
var toes = (TypeOfExpressionSyntax) snac.Node;
switch (toes.Parent)
context.RegisterCompilationStartAction(initContext =>
{
var memberInfoDotNameSym = initContext.Compilation.GetTypeByMetadataName("System.Reflection.MemberInfo")!
.GetMembers("Name")[0];
var typeDotToStringSym = initContext.Compilation.GetTypeByMetadataName("System.Type")!
.GetMembers(WellKnownMemberNames.ObjectToString)[0];
initContext.RegisterSyntaxNodeAction(
snac =>
{
case BinaryExpressionSyntax bes:
if ((ReferenceEquals(toes, bes.Left) ? bes.Right : bes.Left) is LiteralExpressionSyntax { Token.RawKind: (int) SyntaxKind.StringLiteralToken })
{
snac.ReportDiagnostic(Diagnostic.Create(DiagNoToStringOnType, toes.GetLocation(), toes.Type.GetText(), " in string concatenation"));
}
break;
case InterpolationSyntax:
snac.ReportDiagnostic(Diagnostic.Create(DiagNoToStringOnType, toes.GetLocation(), toes.Type.GetText(), " in string interpolation"));
break;
case MemberAccessExpressionSyntax maes1:
var accessed = snac.SemanticModel.GetSymbolInfo(maes1.Name, snac.CancellationToken).Symbol;
if (memberInfoDotNameSym.Matches(accessed))
{
snac.ReportDiagnostic(Diagnostic.Create(DiagUseNameof, maes1.GetLocation(), toes.Type.GetText()));
}
else if (typeDotToStringSym.Matches(accessed))
{
snac.ReportDiagnostic(Diagnostic.Create(DiagNoToStringOnType, maes1.GetLocation(), toes.Type.GetText(), ".ToString()"));
}
break;
}
},
SyntaxKind.TypeOfExpression);
var toes = (TypeOfExpressionSyntax) snac.Node;
switch (toes.Parent)
{
case BinaryExpressionSyntax bes:
if ((ReferenceEquals(toes, bes.Left) ? bes.Right : bes.Left) is LiteralExpressionSyntax { Token.RawKind: (int) SyntaxKind.StringLiteralToken })
{
snac.ReportDiagnostic(Diagnostic.Create(DiagNoToStringOnType, toes.GetLocation(), toes.Type.GetText(), " in string concatenation"));
}
break;
case InterpolationSyntax:
snac.ReportDiagnostic(Diagnostic.Create(DiagNoToStringOnType, toes.GetLocation(), toes.Type.GetText(), " in string interpolation"));
break;
case MemberAccessExpressionSyntax maes1:
var accessed = snac.SemanticModel.GetSymbolInfo(maes1.Name, snac.CancellationToken).Symbol;
if (memberInfoDotNameSym.Matches(accessed))
{
snac.ReportDiagnostic(Diagnostic.Create(DiagUseNameof, maes1.GetLocation(), toes.Type.GetText()));
}
else if (typeDotToStringSym.Matches(accessed))
{
snac.ReportDiagnostic(Diagnostic.Create(DiagNoToStringOnType, maes1.GetLocation(), toes.Type.GetText(), ".ToString()"));
}
break;
}
},
SyntaxKind.TypeOfExpression);
});
}
}