mirror of
https://github.com/RPCS3/discord-bot.git
synced 2026-01-31 01:25:22 +01:00
add roslyn analyzer for command description attributes
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -259,7 +259,6 @@ paket-files/
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
launchSettings.json
|
||||
.vscode/
|
||||
*.db
|
||||
*.db-journal
|
||||
|
||||
@@ -14,6 +14,7 @@ internal sealed class BotMath
|
||||
}
|
||||
|
||||
[Command("calculate"), TextAlias("calc"), DefaultGroupCommand]
|
||||
[Description("12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901")]
|
||||
public async ValueTask Calc(CommandContext ctx, [RemainingText, Description("Math expression")] string expression)
|
||||
{
|
||||
if (string.IsNullOrEmpty(expression))
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
|
||||
<Nullable>enable</Nullable>
|
||||
<EmitCompilerGeneratedFiles>false</EmitCompilerGeneratedFiles>
|
||||
<RunCodeAnalysis>True</RunCodeAnalysis>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<CompilerVisibleProperty Include="RootNamespace" />
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
|
||||
namespace SourceGenerators;
|
||||
|
||||
@@ -9,21 +12,30 @@ public class AttributeUsageAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
// You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat.
|
||||
// See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Localizing%20Analyzers.md for more on localization
|
||||
private const string Category = "Usage";
|
||||
private const string DiagnosticId = "DSharpPlusAttributeUsage";
|
||||
|
||||
private static readonly DiagnosticDescriptor AccessCheckAttributeOnGroupCommandRule = new DiagnosticDescriptor(
|
||||
DiagnosticId,
|
||||
private static readonly DiagnosticDescriptor AccessCheckAttributeOnGroupCommandRule = new(
|
||||
"DSP0001",
|
||||
"Access check attributes are ignored",
|
||||
"Attribute {0} will be ignored for GroupCommand",
|
||||
Category,
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
isEnabledByDefault: true,
|
||||
description: "GroupCommand methods will silently ignore any access check attributes, so instead create an instance of the required check attribute and call it explicitly inside the method."
|
||||
);
|
||||
private static readonly DiagnosticDescriptor DescriptionLengthRule = new(
|
||||
"DSP0002",
|
||||
"Description is too long",
|
||||
"Description is {0} characters long, which is {1} characters longer than allowed",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
isEnabledByDefault: true,
|
||||
description: "Description must be less than or equal to 100 characters."
|
||||
);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
|
||||
[AccessCheckAttributeOnGroupCommandRule];
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = [
|
||||
AccessCheckAttributeOnGroupCommandRule,
|
||||
DescriptionLengthRule,
|
||||
];
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
@@ -32,10 +44,11 @@ public class AttributeUsageAnalyzer : DiagnosticAnalyzer
|
||||
|
||||
// TODO: Consider registering other actions that act on syntax instead of or in addition to symbols
|
||||
// See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Analyzer%20Actions%20Semantics.md for more information
|
||||
context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Method);
|
||||
context.RegisterSymbolAction(AnalyzeMethod, SymbolKind.Method);
|
||||
context.RegisterOperationAction(AnalyzeDescriptionAttribute, OperationKind.Attribute);
|
||||
}
|
||||
|
||||
private static void AnalyzeSymbol(SymbolAnalysisContext context)
|
||||
private static void AnalyzeMethod(SymbolAnalysisContext context)
|
||||
{
|
||||
var methodSymbol = (IMethodSymbol)context.Symbol;
|
||||
var methodAttributes = methodSymbol.GetAttributes();
|
||||
@@ -62,6 +75,67 @@ public class AttributeUsageAnalyzer : DiagnosticAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
private void AnalyzeDescriptionAttribute(OperationAnalysisContext context)
|
||||
{
|
||||
// The Roslyn architecture is based on inheritance.
|
||||
// To get the required metadata, we should match the 'Operation' and 'Syntax' objects to the particular types,
|
||||
// which are based on the 'OperationKind' parameter specified in the 'Register...' method.
|
||||
if (context.Operation is not IAttributeOperation attributeOperation
|
||||
|| context.Operation.Syntax is not AttributeSyntax attributeSyntax)
|
||||
return;
|
||||
|
||||
if (attributeOperation.Kind != OperationKind.Attribute
|
||||
|| attributeOperation.Operation is not IObjectCreationOperation
|
||||
{
|
||||
Kind: OperationKind.ObjectCreation,
|
||||
Type:
|
||||
{
|
||||
ContainingNamespace:
|
||||
{
|
||||
ContainingNamespace.Name: "System",
|
||||
Name: "ComponentModel"
|
||||
},
|
||||
Name: "DescriptionAttribute"
|
||||
}
|
||||
} attrCtorOp)
|
||||
return;
|
||||
|
||||
if (attrCtorOp.Arguments.Length is 0
|
||||
|| attrCtorOp.Arguments.FirstOrDefault(arg => arg is
|
||||
{
|
||||
Parameter:
|
||||
{
|
||||
Name: "description",
|
||||
Type: {ContainingNamespace.Name: "System", Name: "String"}
|
||||
}
|
||||
}) is not
|
||||
{
|
||||
Value: ILiteralOperation
|
||||
{
|
||||
ConstantValue:
|
||||
{
|
||||
HasValue: true,
|
||||
Value: string actualDescription
|
||||
}
|
||||
}
|
||||
})
|
||||
return;
|
||||
|
||||
const int maxDescriptionLength = 100;
|
||||
if (actualDescription.Length <= maxDescriptionLength)
|
||||
return;
|
||||
|
||||
var diagnostic = Diagnostic.Create(DescriptionLengthRule,
|
||||
// The highlighted area in the analyzed source code. Keep it as specific as possible.
|
||||
attributeSyntax.GetLocation(),
|
||||
// The value is passed to the 'MessageFormat' argument of your rule.
|
||||
actualDescription.Length, actualDescription.Length - maxDescriptionLength
|
||||
);
|
||||
|
||||
// Reporting a diagnostic is the primary outcome of analyzers.
|
||||
context.ReportDiagnostic(diagnostic);
|
||||
}
|
||||
|
||||
private static bool IsDescendantOfAttribute(AttributeData attributeData, string baseAttributeClassNameWithNamespace)
|
||||
{
|
||||
var attrClass = attributeData.AttributeClass;
|
||||
|
||||
8
SourceGenerators/Properties/launchSettings.json
Normal file
8
SourceGenerators/Properties/launchSettings.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"profiles": {
|
||||
"RoslynAnalyzers": {
|
||||
"commandName": "DebugRoslynComponent",
|
||||
"targetProject": "..\\CompatBot\\CompatBot.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,12 @@
|
||||
<LangVersion>latest</LangVersion>
|
||||
<NoWarn>1701;1702;RS2008;RS1036</NoWarn>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsRoslynComponent>true</IsRoslynComponent>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user