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)
|
# Python Tools for Visual Studio (PTVS)
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
launchSettings.json
|
|
||||||
.vscode/
|
.vscode/
|
||||||
*.db
|
*.db
|
||||||
*.db-journal
|
*.db-journal
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ internal sealed class BotMath
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Command("calculate"), TextAlias("calc"), DefaultGroupCommand]
|
[Command("calculate"), TextAlias("calc"), DefaultGroupCommand]
|
||||||
|
[Description("12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901")]
|
||||||
public async ValueTask Calc(CommandContext ctx, [RemainingText, Description("Math expression")] string expression)
|
public async ValueTask Calc(CommandContext ctx, [RemainingText, Description("Math expression")] string expression)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(expression))
|
if (string.IsNullOrEmpty(expression))
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
|
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<EmitCompilerGeneratedFiles>false</EmitCompilerGeneratedFiles>
|
<EmitCompilerGeneratedFiles>false</EmitCompilerGeneratedFiles>
|
||||||
|
<RunCodeAnalysis>True</RunCodeAnalysis>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<CompilerVisibleProperty Include="RootNamespace" />
|
<CompilerVisibleProperty Include="RootNamespace" />
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
using Microsoft.CodeAnalysis.Operations;
|
||||||
|
|
||||||
namespace SourceGenerators;
|
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.
|
// 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
|
// 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(
|
private static readonly DiagnosticDescriptor AccessCheckAttributeOnGroupCommandRule = new(
|
||||||
DiagnosticId,
|
"DSP0001",
|
||||||
"Access check attributes are ignored",
|
"Access check attributes are ignored",
|
||||||
"Attribute {0} will be ignored for GroupCommand",
|
"Attribute {0} will be ignored for GroupCommand",
|
||||||
Category,
|
"Usage",
|
||||||
DiagnosticSeverity.Error,
|
DiagnosticSeverity.Error,
|
||||||
isEnabledByDefault: true,
|
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."
|
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; } =
|
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = [
|
||||||
[AccessCheckAttributeOnGroupCommandRule];
|
AccessCheckAttributeOnGroupCommandRule,
|
||||||
|
DescriptionLengthRule,
|
||||||
|
];
|
||||||
|
|
||||||
public override void Initialize(AnalysisContext context)
|
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
|
// 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
|
// 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 methodSymbol = (IMethodSymbol)context.Symbol;
|
||||||
var methodAttributes = methodSymbol.GetAttributes();
|
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)
|
private static bool IsDescendantOfAttribute(AttributeData attributeData, string baseAttributeClassNameWithNamespace)
|
||||||
{
|
{
|
||||||
var attrClass = attributeData.AttributeClass;
|
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>
|
<LangVersion>latest</LangVersion>
|
||||||
<NoWarn>1701;1702;RS2008;RS1036</NoWarn>
|
<NoWarn>1701;1702;RS2008;RS1036</NoWarn>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<IsRoslynComponent>true</IsRoslynComponent>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<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.Analyzers" Version="3.11.0" PrivateAssets="all" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" PrivateAssets="all" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
Reference in New Issue
Block a user