Add analyzer (currently disabled) to enforce a newline policy for `=>`
This commit is contained in:
parent
479f151bbb
commit
2ffb897b11
|
@ -24,6 +24,8 @@ dotnet_diagnostic.BHI1102.severity = error
|
||||||
dotnet_diagnostic.BHI1103.severity = error
|
dotnet_diagnostic.BHI1103.severity = error
|
||||||
# Brackets of collection expression should be separated with spaces
|
# Brackets of collection expression should be separated with spaces
|
||||||
dotnet_diagnostic.BHI1110.severity = warning
|
dotnet_diagnostic.BHI1110.severity = warning
|
||||||
|
# Expression-bodied member should be flowed to next line correctly
|
||||||
|
dotnet_diagnostic.BHI1120.severity = warning
|
||||||
|
|
||||||
# Check result of IDictionary.TryGetValue, or discard it if default(T) is desired
|
# Check result of IDictionary.TryGetValue, or discard it if default(T) is desired
|
||||||
dotnet_diagnostic.BHI1200.severity = error
|
dotnet_diagnostic.BHI1200.severity = error
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
namespace BizHawk.Analyzers;
|
||||||
|
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public sealed class ExprBodiedMemberFlowAnalyzer : DiagnosticAnalyzer
|
||||||
|
{
|
||||||
|
private static readonly DiagnosticDescriptor DiagExprBodiedMemberFlow = new(
|
||||||
|
id: "BHI1120",
|
||||||
|
title: "Expression-bodied member should be flowed to next line correctly",
|
||||||
|
messageFormat: "{0}",
|
||||||
|
category: "Usage",
|
||||||
|
defaultSeverity: DiagnosticSeverity.Warning,
|
||||||
|
isEnabledByDefault: true);
|
||||||
|
|
||||||
|
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(DiagExprBodiedMemberFlow);
|
||||||
|
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
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 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?"));
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
var hasLineBreakAfterArrow = aecs.ArrowToken.HasTrailingTrivia && aecs.ArrowToken.TrailingTrivia.ToFullString().Contains('\n');
|
||||||
|
if ((hasLineBreakAfterArrow ? '\n' : ' ') != expectedWhitespace.After) Fail();
|
||||||
|
},
|
||||||
|
SyntaxKind.ArrowExpressionClause);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
namespace BizHawk.Tests.Analyzers;
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
using Verify = Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<
|
||||||
|
BizHawk.Analyzers.ExprBodiedMemberFlowAnalyzer,
|
||||||
|
Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public sealed class ExprBodiedMemberFlowAnalyzerTests
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public Task CheckMisuseOfExpressionBodies()
|
||||||
|
=> Verify.VerifyAnalyzerAsync("""
|
||||||
|
public sealed class Cases {
|
||||||
|
private int GetOnlyProp
|
||||||
|
=> default;
|
||||||
|
{|BHI1120:private int BadGetOnlyProp => default;|}
|
||||||
|
private int GetSetProp {
|
||||||
|
get => default;
|
||||||
|
set => _ = value;
|
||||||
|
}
|
||||||
|
private int BadGetSetProp {
|
||||||
|
{|BHI1120:get =>
|
||||||
|
default;|}
|
||||||
|
{|BHI1120:set
|
||||||
|
=> _ = value;|}
|
||||||
|
}
|
||||||
|
private int GetInitProp {
|
||||||
|
get => default;
|
||||||
|
init => _ = value;
|
||||||
|
}
|
||||||
|
private int BadGetInitProp {
|
||||||
|
{|BHI1120:get
|
||||||
|
=> default;|}
|
||||||
|
{|BHI1120:init =>
|
||||||
|
_ = value;|}
|
||||||
|
}
|
||||||
|
private event System.EventHandler Event {
|
||||||
|
add => DummyMethod();
|
||||||
|
remove => _ = value;
|
||||||
|
}
|
||||||
|
private event System.EventHandler BadEvent {
|
||||||
|
{|BHI1120:add =>
|
||||||
|
DummyMethod();|}
|
||||||
|
{|BHI1120:remove
|
||||||
|
=> _ = value;|}
|
||||||
|
}
|
||||||
|
{|BHI1120:public Cases() => DummyMethod();|}
|
||||||
|
{|BHI1120:~Cases() => DummyMethod();|}
|
||||||
|
private int this[char good] {
|
||||||
|
get => default;
|
||||||
|
set => _ = value;
|
||||||
|
}
|
||||||
|
private int this[int bad] {
|
||||||
|
{|BHI1120:get
|
||||||
|
=> default;|}
|
||||||
|
{|BHI1120:set =>
|
||||||
|
_ = value;|}
|
||||||
|
}
|
||||||
|
private int ExprBodyMethod()
|
||||||
|
=> default;
|
||||||
|
{|BHI1120:private int BadExprBodyMethod() => default;|}
|
||||||
|
private void DummyMethod() {
|
||||||
|
int LocalMethod()
|
||||||
|
=> default;
|
||||||
|
{|BHI1120:int BadLocalMethod() => default;|}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public sealed class GoodCtorDtor {
|
||||||
|
public GoodCtorDtor()
|
||||||
|
=> DummyMethod();
|
||||||
|
~GoodCtorDtor()
|
||||||
|
=> DummyMethod();
|
||||||
|
private void DummyMethod() {}
|
||||||
|
}
|
||||||
|
namespace System.Runtime.CompilerServices {
|
||||||
|
public static class IsExternalInit {} // this sample is compiled for lowest-common-denominator of `netstandard2.0`, so `init` accessor gives an error without this
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
}
|
Binary file not shown.
Loading…
Reference in New Issue