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.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution(); context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(initContext => context.RegisterOperationAction(
{ oac =>
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)
{ {
var conversionOp = (IConversionOperation) oac.Operation; if (typeInput is SpecialType.System_Double) isDoublePrecision = true;
var typeOutput = conversionOp.Type?.SpecialType ?? SpecialType.None; else if (typeInput is SpecialType.System_Single) isDoublePrecision = false;
var typeInput = conversionOp.Operand.Type?.SpecialType ?? SpecialType.None; else return;
bool isToDecimal; isToDecimal = true;
bool isDoublePrecision; }
if (typeOutput is SpecialType.System_Decimal) else if (typeInput is SpecialType.System_Decimal)
{ {
if (typeInput is SpecialType.System_Double) isDoublePrecision = true; if (typeOutput is SpecialType.System_Double) isDoublePrecision = true;
else if (typeInput is SpecialType.System_Single) isDoublePrecision = false; else if (typeOutput is SpecialType.System_Single) isDoublePrecision = false;
else return; else return;
isToDecimal = true; isToDecimal = false;
} }
else if (typeInput is SpecialType.System_Decimal) else
{ {
if (typeOutput is SpecialType.System_Double) isDoublePrecision = true; return;
else if (typeOutput is SpecialType.System_Single) isDoublePrecision = false; }
else return; var conversionSyn = conversionOp.Syntax;
isToDecimal = false; //TODO check the suggested methods are accessible (i.e. BizHawk.Common is referenced)
} oac.ReportDiagnostic(Diagnostic.Create(
else DiagAmbiguousMoneyToFloatConversion,
{ (conversionSyn.Parent?.Kind() is SyntaxKind.CheckedExpression or SyntaxKind.UncheckedExpression
return; ? conversionSyn.Parent
} : conversionSyn).GetLocation(),
var conversionSyn = conversionOp.Syntax; conversionOp.IsChecked ? DiagnosticSeverity.Error : DiagnosticSeverity.Warning,
//TODO check the suggested methods are accessible (i.e. BizHawk.Common is referenced) additionalLocations: null,
oac.ReportDiagnostic(Diagnostic.Create( properties: null,
DiagAmbiguousMoneyToFloatConversion, messageArgs: isToDecimal
(conversionSyn.Parent?.Kind() is SyntaxKind.CheckedExpression or SyntaxKind.UncheckedExpression ? [
? conversionSyn.Parent $"new decimal({(isDoublePrecision ? "double" : "float")})", // "checked"
: conversionSyn).GetLocation(), "static NumberExtensions.ConvertToMoneyTruncated", // "unchecked"
conversionOp.IsChecked ? DiagnosticSeverity.Error : DiagnosticSeverity.Warning, ]
additionalLocations: null, : [
properties: null, $"decimal.{(isDoublePrecision ? "ConvertToF64" : "ConvertToF32")} ext. (from NumberExtensions)", // "checked"
messageArgs: isToDecimal $"static Decimal.{(isDoublePrecision ? "ToDouble" : "ToSingle")}", // "unchecked"
? [ ]));
$"new decimal({(isDoublePrecision ? "double" : "float")})", // "checked" },
"static NumberExtensions.ConvertToMoneyTruncated", // "unchecked" OperationKind.Conversion);
]
: [
$"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.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution(); context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(initContext => var ARROW_ONE_LINE = (' ', ' ');
{ // var ARROW_POST_SIG = (' ', '\n');
var ARROW_ONE_LINE = (' ', ' '); var ARROW_PRE_BODY = ('\n', ' ');
// var ARROW_POST_SIG = (' ', '\n'); context.RegisterSyntaxNodeAction(
var ARROW_PRE_BODY = ('\n', ' '); snac =>
initContext.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; snac.ReportDiagnostic(Diagnostic.Create(DiagExprBodiedMemberFlow, aecs.GetLocation(), "Syntax node for expression-bodied member was orphaned?"));
(char Before, char After) expectedWhitespace; return;
string kind; }
var parent = aecs.Parent; void Flag(string message)
if (parent is null) => snac.ReportDiagnostic(Diagnostic.Create(DiagExprBodiedMemberFlow, parent.GetLocation(), message));
{ switch (parent)
snac.ReportDiagnostic(Diagnostic.Create(DiagExprBodiedMemberFlow, aecs.GetLocation(), "Syntax node for expression-bodied member was orphaned?")); {
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; return;
} }
void Flag(string message) static string EscapeChar(char c)
=> snac.ReportDiagnostic(Diagnostic.Create(DiagExprBodiedMemberFlow, parent.GetLocation(), message)); => c is '\n' ? "\\n" : c.ToString();
switch (parent) void Fail()
{ => Flag($"Whitespace around {kind} arrow syntax should be `{EscapeChar(expectedWhitespace.Before)}=>{EscapeChar(expectedWhitespace.After)}`");
case MethodDeclarationSyntax: if ((aecs.ArrowToken.HasLeadingTrivia ? '\n' : ' ') != expectedWhitespace.Before)
expectedWhitespace = ARROW_PRE_BODY; {
kind = "method"; Fail();
break; return;
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;
}
#pragma warning disable BHI3102 // LINQ `Contains(char)` is fine here #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 #pragma warning restore BHI3102
if ((hasLineBreakAfterArrow ? '\n' : ' ') != expectedWhitespace.After) Fail(); if ((hasLineBreakAfterArrow ? '\n' : ' ') != expectedWhitespace.After) Fail();
}, },
SyntaxKind.ArrowExpressionClause); 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: "_" }; => aes.OperatorToken.RawKind is (int) SyntaxKind.EqualsToken && aes.Left is IdentifierNameSyntax { Identifier.Text: "_" };
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution(); context.EnableConcurrentExecution();
INamedTypeSymbol? invalidOperationExceptionSym = null; context.RegisterCompilationStartAction(initContext =>
INamedTypeSymbol? switchExpressionExceptionSym = null; {
context.RegisterSyntaxNodeAction( var invalidOperationExceptionSym = initContext.Compilation.GetTypeByMetadataName("System.InvalidOperationException")!;
snac => var switchExpressionExceptionSym = initContext.Compilation.GetTypeByMetadataName("System.Runtime.CompilerServices.SwitchExpressionException");
{ initContext.RegisterSyntaxNodeAction(
if (invalidOperationExceptionSym is null) snac =>
{ {
invalidOperationExceptionSym = snac.Compilation.GetTypeByMetadataName("System.InvalidOperationException")!; switch (snac.Node)
switchExpressionExceptionSym = snac.Compilation.GetTypeByMetadataName("System.Runtime.CompilerServices.SwitchExpressionException"); {
} case AnonymousMethodExpressionSyntax:
switch (snac.Node) snac.ReportDiagnostic(Diagnostic.Create(DiagNoAnonDelegates, snac.Node.GetLocation()));
{ break;
case AnonymousMethodExpressionSyntax: case AnonymousObjectCreationExpressionSyntax:
snac.ReportDiagnostic(Diagnostic.Create(DiagNoAnonDelegates, snac.Node.GetLocation())); snac.ReportDiagnostic(Diagnostic.Create(DiagNoAnonClasses, snac.Node.GetLocation()));
break; break;
case AnonymousObjectCreationExpressionSyntax: case AssignmentExpressionSyntax aes:
snac.ReportDiagnostic(Diagnostic.Create(DiagNoAnonClasses, snac.Node.GetLocation())); if (!IsDiscard(aes)) break;
break; if (snac.SemanticModel.GetSymbolInfo(aes.Right, snac.CancellationToken).Symbol?.Kind is not SymbolKind.Local) break;
case AssignmentExpressionSyntax aes: snac.ReportDiagnostic(Diagnostic.Create(DiagNoDiscardingLocals, snac.Node.GetLocation()));
if (!IsDiscard(aes)) break; break;
if (snac.SemanticModel.GetSymbolInfo(aes.Right, snac.CancellationToken).Symbol?.Kind is not SymbolKind.Local) break; case CollectionExpressionSyntax ces:
snac.ReportDiagnostic(Diagnostic.Create(DiagNoDiscardingLocals, snac.Node.GetLocation())); var cesError = CheckSpacingInList(ces.Elements, ces.OpenBracketToken, ces.ToString);
break; if (cesError is not null) snac.ReportDiagnostic(Diagnostic.Create(DiagListExprSpacing, ces.GetLocation(), cesError));
case CollectionExpressionSyntax ces: break;
var cesError = CheckSpacingInList(ces.Elements, ces.OpenBracketToken, ces.ToString); case InterpolatedStringExpressionSyntax ises:
if (cesError is not null) snac.ReportDiagnostic(Diagnostic.Create(DiagListExprSpacing, ces.GetLocation(), cesError)); if (ises.StringStartToken.Text[0] is '@') snac.ReportDiagnostic(Diagnostic.Create(DiagInterpStringIsDollarAt, ises.GetLocation()));
break; break;
case InterpolatedStringExpressionSyntax ises: case ListPatternSyntax lps:
if (ises.StringStartToken.Text[0] is '@') snac.ReportDiagnostic(Diagnostic.Create(DiagInterpStringIsDollarAt, ises.GetLocation())); var lpsError = CheckSpacingInList(lps.Patterns, lps.OpenBracketToken, lps.ToString);
break; if (lpsError is not null) snac.ReportDiagnostic(Diagnostic.Create(DiagListExprSpacing, lps.GetLocation(), lpsError));
case ListPatternSyntax lps: break;
var lpsError = CheckSpacingInList(lps.Patterns, lps.OpenBracketToken, lps.ToString); case QueryExpressionSyntax:
if (lpsError is not null) snac.ReportDiagnostic(Diagnostic.Create(DiagListExprSpacing, lps.GetLocation(), lpsError)); snac.ReportDiagnostic(Diagnostic.Create(DiagNoQueryExpression, snac.Node.GetLocation()));
break; break;
case QueryExpressionSyntax: case RecordDeclarationSyntax rds when rds.ClassOrStructKeyword.ToString() is not "class": // `record struct`s don't use this kind
snac.ReportDiagnostic(Diagnostic.Create(DiagNoQueryExpression, snac.Node.GetLocation())); snac.ReportDiagnostic(Diagnostic.Create(DiagRecordImplicitlyRefType, rds.GetLocation()));
break; break;
case RecordDeclarationSyntax rds when rds.ClassOrStructKeyword.ToString() is not "class": // `record struct`s don't use this kind case SwitchExpressionArmSyntax { WhenClause: null, Pattern: DiscardPatternSyntax, Expression: ThrowExpressionSyntax tes }:
snac.ReportDiagnostic(Diagnostic.Create(DiagRecordImplicitlyRefType, rds.GetLocation())); var thrownExceptionType = snac.SemanticModel.GetThrownExceptionType(tes);
break; if (thrownExceptionType is null)
case SwitchExpressionArmSyntax { WhenClause: null, Pattern: DiscardPatternSyntax, Expression: ThrowExpressionSyntax tes }: {
var thrownExceptionType = snac.SemanticModel.GetThrownExceptionType(tes); snac.ReportDiagnostic(Diagnostic.Create(
if (thrownExceptionType is null) DiagSwitchShouldThrowIOE,
{ tes.GetLocation(),
snac.ReportDiagnostic(Diagnostic.Create( DiagnosticSeverity.Warning,
DiagSwitchShouldThrowIOE, additionalLocations: null,
tes.GetLocation(), properties: null,
DiagnosticSeverity.Warning, ERR_MSG_SWITCH_THROWS_UNKNOWN));
additionalLocations: null, }
properties: null, else if (!invalidOperationExceptionSym.Matches(thrownExceptionType) && switchExpressionExceptionSym?.Matches(thrownExceptionType) != true)
ERR_MSG_SWITCH_THROWS_UNKNOWN)); {
} snac.ReportDiagnostic(Diagnostic.Create(DiagSwitchShouldThrowIOE, tes.GetLocation(), ERR_MSG_SWITCH_THROWS_WRONG_TYPE));
else if (!invalidOperationExceptionSym.Matches(thrownExceptionType) && switchExpressionExceptionSym?.Matches(thrownExceptionType) != true) }
{ // else correct usage, do not flag
snac.ReportDiagnostic(Diagnostic.Create(DiagSwitchShouldThrowIOE, tes.GetLocation(), ERR_MSG_SWITCH_THROWS_WRONG_TYPE)); break;
} }
// else correct usage, do not flag },
break; SyntaxKind.AnonymousObjectCreationExpression,
} SyntaxKind.AnonymousMethodExpression,
}, SyntaxKind.CollectionExpression,
SyntaxKind.AnonymousObjectCreationExpression, SyntaxKind.InterpolatedStringExpression,
SyntaxKind.AnonymousMethodExpression, SyntaxKind.ListPattern,
SyntaxKind.CollectionExpression, SyntaxKind.QueryExpression,
SyntaxKind.InterpolatedStringExpression, SyntaxKind.RecordDeclaration,
SyntaxKind.ListPattern, SyntaxKind.SimpleAssignmentExpression,
SyntaxKind.QueryExpression, SyntaxKind.SwitchExpressionArm);
SyntaxKind.RecordDeclaration, });
SyntaxKind.SimpleAssignmentExpression,
SyntaxKind.SwitchExpressionArm);
} }
} }

View File

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

View File

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