Clean up registration in `BizHawk.Analyzer`
will rebuild in later commit
This commit is contained in:
parent
57921a9206
commit
03bf156a91
|
@ -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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue