mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
StyleChecker works, tests dont
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
|
||||
namespace ScriptCoreGenerator.StyleCheckers;
|
||||
|
||||
/// <summary>
|
||||
/// Checks that methods with UnmanagedCallersOnly attribute always have a try-catch statement to ensure that exceptions never escape out of the C# code - as this would crash the application.
|
||||
/// </summary>
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public class CatchUnmanagedCallersOnlyAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
public const string DiagnosticId = "GE0001";
|
||||
|
||||
private static readonly LocalizableString Title =
|
||||
new LocalizableResourceString(nameof(Strings.CatchUnmanagedCallers_Title), Strings.ResourceManager,
|
||||
typeof(Strings));
|
||||
|
||||
private static readonly LocalizableString MessageFormat =
|
||||
new LocalizableResourceString(nameof(Strings.CatchUnmanagedCallers_MessageFormat), Strings.ResourceManager,
|
||||
typeof(Strings));
|
||||
|
||||
private static readonly LocalizableString Description =
|
||||
new LocalizableResourceString(nameof(Strings.CatchUnmanagedCallers_Description), Strings.ResourceManager,
|
||||
typeof(Strings));
|
||||
|
||||
private const string Category = "Usage";
|
||||
|
||||
private static readonly DiagnosticDescriptor Rule = new(DiagnosticId, Title, MessageFormat,
|
||||
Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||
context.EnableConcurrentExecution();
|
||||
|
||||
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.Attribute);
|
||||
}
|
||||
|
||||
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
var attributeNode = (AttributeSyntax)context.Node;
|
||||
|
||||
if (SyntaxNodeExtensions.ExtractName(attributeNode.Name) is not ("UnmanagedCallersOnly" or "UnmanagedCallersOnlyAttribute"))
|
||||
return;
|
||||
|
||||
MethodDeclarationSyntax? method = attributeNode.GetParentOrNull<MethodDeclarationSyntax>();
|
||||
|
||||
|
||||
if (method is null)
|
||||
return;
|
||||
|
||||
void ReportDiagnostic()
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(Rule, attributeNode.GetLocation(), method.Identifier.ValueText));
|
||||
}
|
||||
|
||||
if (method.Body is not null)
|
||||
{
|
||||
// Check if the outer most statement is a try-catch block.
|
||||
var tryStatement = method.Body.Statements.FirstOrDefault(s => s is TryStatementSyntax) as TryStatementSyntax;
|
||||
if (tryStatement is null)
|
||||
{
|
||||
// If there is no try-catch block, report a diagnostic.
|
||||
ReportDiagnostic();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check if the try block has a catch clause.
|
||||
if (tryStatement.Catches.Count == 0)
|
||||
{
|
||||
ReportDiagnostic();
|
||||
return;
|
||||
}
|
||||
|
||||
// Report a diagnostic if there is no catch block that catches System.Exception.
|
||||
if (tryStatement.Catches.All(c =>
|
||||
{
|
||||
TypeSyntax? typeSyntax = c.Declaration?.Type;
|
||||
|
||||
if (typeSyntax is null)
|
||||
return true;
|
||||
|
||||
TypeInfo typeInfo = context.SemanticModel.GetTypeInfo(typeSyntax);
|
||||
return typeInfo.Type?.Name != "Exception";
|
||||
}))
|
||||
{
|
||||
ReportDiagnostic();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (method.ExpressionBody is not null)
|
||||
{
|
||||
// Expression-bodied methods cannot have an outer-most try-catch block.
|
||||
ReportDiagnostic();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CodeActions;
|
||||
using Microsoft.CodeAnalysis.CodeFixes;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Rename;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Composition;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.Formatting;
|
||||
|
||||
namespace ScriptCoreGenerator.StyleCheckers
|
||||
{
|
||||
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CatchUnmanagedCallersOnlyAnalyzer)), Shared]
|
||||
public class CatchUnmanagedCallersOnlyFixProvider : CodeFixProvider
|
||||
{
|
||||
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(CatchUnmanagedCallersOnlyAnalyzer.DiagnosticId);
|
||||
|
||||
public sealed override FixAllProvider GetFixAllProvider()
|
||||
{
|
||||
// See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/FixAllProvider.md for more information on Fix All Providers
|
||||
return WellKnownFixAllProviders.BatchFixer;
|
||||
}
|
||||
|
||||
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
|
||||
{
|
||||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
|
||||
|
||||
var diagnostic = context.Diagnostics.First();
|
||||
var diagnosticSpan = diagnostic.Location.SourceSpan;
|
||||
|
||||
// Find the type declaration identified by the diagnostic.
|
||||
var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<MethodDeclarationSyntax>().First();
|
||||
|
||||
// Register a code action that will invoke the fix.
|
||||
context.RegisterCodeFix(
|
||||
CodeAction.Create(
|
||||
title: Strings.CatchUnmanagedCallers_FixTitle,
|
||||
createChangedDocument: c => AddTryCatchAsync(context.Document, declaration, c),
|
||||
equivalenceKey: nameof(Strings.CatchUnmanagedCallers_FixTitle)),
|
||||
diagnostic);
|
||||
}
|
||||
|
||||
private async Task<Document> AddTryCatchAsync(Document contextDocument, MethodDeclarationSyntax method, CancellationToken cancellationToken)
|
||||
{
|
||||
if (method.Body is not null)
|
||||
{
|
||||
// Create a try-catch block
|
||||
var tryBlock = SyntaxFactory.Block(method.Body.Statements);
|
||||
var catchClause = SyntaxFactory.CatchClause()
|
||||
.WithDeclaration(SyntaxFactory.CatchDeclaration(SyntaxFactory.IdentifierName("Exception"))
|
||||
.WithIdentifier(SyntaxFactory.Identifier("ex")))
|
||||
.WithBlock(SyntaxFactory.Block(
|
||||
SyntaxFactory.SingletonList<StatementSyntax>(
|
||||
SyntaxFactory.ExpressionStatement(
|
||||
SyntaxFactory.InvocationExpression(
|
||||
SyntaxFactory.IdentifierName("Console.WriteLine"))
|
||||
.WithArgumentList(
|
||||
SyntaxFactory.ArgumentList(
|
||||
SyntaxFactory.SingletonSeparatedList(
|
||||
SyntaxFactory.Argument(
|
||||
SyntaxFactory.IdentifierName("ex.Message")))))))));
|
||||
|
||||
var tryStatement = SyntaxFactory.TryStatement()
|
||||
.WithBlock(tryBlock)
|
||||
.WithCatches(SyntaxFactory.SingletonList(catchClause));
|
||||
|
||||
// Replace the method body with the new try-catch block
|
||||
var newMethodBody = method.Body.WithStatements(SyntaxFactory.SingletonList<StatementSyntax>(tryStatement));
|
||||
var newMethod = method.WithBody(newMethodBody);
|
||||
|
||||
// Update the syntax tree
|
||||
var oldRoot = await contextDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
|
||||
var newRoot = oldRoot.ReplaceNode(method, newMethod);
|
||||
|
||||
return contextDocument.WithSyntaxRoot(newRoot);
|
||||
}
|
||||
|
||||
return contextDocument;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user