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:
@@ -1,49 +0,0 @@
|
|||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using VerifyCS = MakeConst.Test.CSharpCodeFixVerifier<
|
|
||||||
ScriptCoreGenerator.StyleChecker,
|
|
||||||
ScriptCoreGenerator.MakeConstCodeFixProvider>;
|
|
||||||
|
|
||||||
namespace MakeConst.Test
|
|
||||||
{
|
|
||||||
[TestClass]
|
|
||||||
public class MakeConstUnitTest
|
|
||||||
{
|
|
||||||
//No diagnostics expected to show up
|
|
||||||
[TestMethod]
|
|
||||||
public async Task TestEmpty()
|
|
||||||
{
|
|
||||||
var test = @"";
|
|
||||||
|
|
||||||
await VerifyCS.VerifyAnalyzerAsync(test);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task LocalIntCouldBeConstant_Diagnostic()
|
|
||||||
{
|
|
||||||
await VerifyCS.VerifyCodeFixAsync(@"
|
|
||||||
using System;
|
|
||||||
|
|
||||||
class Program
|
|
||||||
{
|
|
||||||
static void Main()
|
|
||||||
{
|
|
||||||
[|int i = 0;|]
|
|
||||||
Console.WriteLine(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
", @"
|
|
||||||
using System;
|
|
||||||
|
|
||||||
class Program
|
|
||||||
{
|
|
||||||
static void Main()
|
|
||||||
{
|
|
||||||
const int i = 0;
|
|
||||||
Console.WriteLine(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using VerifyCS = MakeConst.Test.CSharpCodeFixVerifier<
|
||||||
|
ScriptCoreGenerator.StyleCheckers.CatchUnmanagedCallersOnlyAnalyzer,
|
||||||
|
ScriptCoreGenerator.StyleCheckers.CatchUnmanagedCallersOnlyFixProvider>;
|
||||||
|
|
||||||
|
namespace ScriptCoreGenerator.Test.StyleCheckers
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public class CatchUnmanagedCallersOnlyTest
|
||||||
|
{
|
||||||
|
//No diagnostics expected to show up
|
||||||
|
[TestMethod]
|
||||||
|
public async Task TestEmpty()
|
||||||
|
{
|
||||||
|
var test = @"";
|
||||||
|
|
||||||
|
await VerifyCS.VerifyAnalyzerAsync(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task TestNoDiagnosticsMethodWithStatementBlock()
|
||||||
|
{
|
||||||
|
var test = @"""
|
||||||
|
using System;
|
||||||
|
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static void Main()
|
||||||
|
{
|
||||||
|
Console.WriteLine(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
await VerifyCS.VerifyAnalyzerAsync(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task TestNoDiagnosticsWithArrowFunction()
|
||||||
|
{
|
||||||
|
var test = @"""
|
||||||
|
using System;
|
||||||
|
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static void Main() => Console.WriteLine(i);
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
await VerifyCS.VerifyAnalyzerAsync(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task TestNoDiagnosticWithCorrectMethod()
|
||||||
|
{
|
||||||
|
await VerifyCS.VerifyAnalyzerAsync(@"
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
[UnmanagedCallersOnly]
|
||||||
|
static void Main()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const int i = 0;
|
||||||
|
Console.WriteLine(i);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task LocalIntCouldBeConstant_Diagnostic()
|
||||||
|
{
|
||||||
|
await VerifyCS.VerifyCodeFixAsync(
|
||||||
|
@"""
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
[[|UnmanagedCallersOnly|]]
|
||||||
|
static void Main()
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
Console.WriteLine(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
@"""
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
[UnmanagedCallersOnly]
|
||||||
|
static void Main()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const int i = 0;
|
||||||
|
Console.WriteLine(i);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
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.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.CodeAnalysis.Formatting;
|
|
||||||
|
|
||||||
namespace ScriptCoreGenerator
|
|
||||||
{
|
|
||||||
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(MakeConstCodeFixProvider)), Shared]
|
|
||||||
public class MakeConstCodeFixProvider : CodeFixProvider
|
|
||||||
{
|
|
||||||
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(StyleChecker.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);
|
|
||||||
|
|
||||||
// TODO: Replace the following code with your own analysis, generating a CodeAction for each fix to suggest
|
|
||||||
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<LocalDeclarationStatementSyntax>().First();
|
|
||||||
|
|
||||||
// Register a code action that will invoke the fix.
|
|
||||||
context.RegisterCodeFix(
|
|
||||||
CodeAction.Create(
|
|
||||||
title: Strings.CodeFixTitle,
|
|
||||||
createChangedDocument: c => MakeConstAsync(context.Document, declaration, c),
|
|
||||||
equivalenceKey: nameof(Strings.CodeFixTitle)),
|
|
||||||
diagnostic);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<Document> MakeConstAsync(Document contextDocument, LocalDeclarationStatementSyntax declaration, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
// Remove the leading trivia from the local declaration.
|
|
||||||
SyntaxToken firstToken = declaration.GetFirstToken();
|
|
||||||
SyntaxTriviaList leadingTrivia = firstToken.LeadingTrivia;
|
|
||||||
LocalDeclarationStatementSyntax trimmedLocal =
|
|
||||||
declaration.ReplaceToken(firstToken, firstToken.WithLeadingTrivia(SyntaxTriviaList.Empty));
|
|
||||||
|
|
||||||
// Create a const token with the leading trivia.
|
|
||||||
SyntaxToken constToken =
|
|
||||||
SyntaxFactory.Token(leadingTrivia, SyntaxKind.ConstKeyword, SyntaxFactory.TriviaList(SyntaxFactory.ElasticMarker));
|
|
||||||
|
|
||||||
// Insert the const token into the modifier list, creating a new modifiers list.
|
|
||||||
SyntaxTokenList newModifiers = trimmedLocal.Modifiers.Insert(0, constToken);
|
|
||||||
// Produce the new local dclaration.
|
|
||||||
LocalDeclarationStatementSyntax newLocal =
|
|
||||||
trimmedLocal.WithModifiers(newModifiers).WithDeclaration(declaration.Declaration);
|
|
||||||
|
|
||||||
// Add an annotation to format the new local declaration.
|
|
||||||
LocalDeclarationStatementSyntax formattedLocal = newLocal.WithAdditionalAnnotations(Formatter.Annotation);
|
|
||||||
|
|
||||||
// Replace the old local declaration with the new local declaration.
|
|
||||||
SyntaxNode oldRoot = await contextDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
SyntaxNode newRoot = oldRoot.ReplaceNode(declaration, formattedLocal);
|
|
||||||
|
|
||||||
// Return document with transformed tree.
|
|
||||||
return contextDocument.WithSyntaxRoot(newRoot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Generated
+36
@@ -87,6 +87,42 @@ namespace ScriptCoreGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Methods marked with UnmanagedCallersOnlyAttribute must wrap their logic in a try-catch statement, to ensure that no exception is leaks out of the C# runtime..
|
||||||
|
/// </summary>
|
||||||
|
internal static string CatchUnmanagedCallers_Description {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("CatchUnmanagedCallers_Description", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Wrap in try-catch-Statement.
|
||||||
|
/// </summary>
|
||||||
|
internal static string CatchUnmanagedCallers_FixTitle {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("CatchUnmanagedCallers_FixTitle", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Method '{0}' must wrap it's logic in a try-catch-Statement.
|
||||||
|
/// </summary>
|
||||||
|
internal static string CatchUnmanagedCallers_MessageFormat {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("CatchUnmanagedCallers_MessageFormat", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to UnmanagedCallersOnly-Methods must catch all exceptions..
|
||||||
|
/// </summary>
|
||||||
|
internal static string CatchUnmanagedCallers_Title {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("CatchUnmanagedCallers_Title", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Make constant.
|
/// Looks up a localized string similar to Make constant.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -126,6 +126,18 @@
|
|||||||
<data name="AnalyzerTitle" xml:space="preserve">
|
<data name="AnalyzerTitle" xml:space="preserve">
|
||||||
<value>Variable can be made const</value>
|
<value>Variable can be made const</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="CatchUnmanagedCallers_Description" xml:space="preserve">
|
||||||
|
<value>Methods marked with UnmanagedCallersOnlyAttribute must wrap their logic in a try-catch statement, to ensure that no exception is leaks out of the C# runtime.</value>
|
||||||
|
</data>
|
||||||
|
<data name="CatchUnmanagedCallers_FixTitle" xml:space="preserve">
|
||||||
|
<value>Wrap in try-catch-Statement</value>
|
||||||
|
</data>
|
||||||
|
<data name="CatchUnmanagedCallers_MessageFormat" xml:space="preserve">
|
||||||
|
<value>Method '{0}' must wrap it's logic in a try-catch-Statement</value>
|
||||||
|
</data>
|
||||||
|
<data name="CatchUnmanagedCallers_Title" xml:space="preserve">
|
||||||
|
<value>UnmanagedCallersOnly-Methods must catch all exceptions.</value>
|
||||||
|
</data>
|
||||||
<data name="CodeFixTitle" xml:space="preserve">
|
<data name="CodeFixTitle" xml:space="preserve">
|
||||||
<value>Make constant</value>
|
<value>Make constant</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
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;
|
|
||||||
|
|
||||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
|
||||||
public class StyleChecker : DiagnosticAnalyzer
|
|
||||||
{
|
|
||||||
public const string DiagnosticId = "MakeConst";
|
|
||||||
|
|
||||||
private static readonly LocalizableString Title =
|
|
||||||
new LocalizableResourceString(nameof(Strings.AnalyzerTitle), Strings.ResourceManager, typeof(Strings));
|
|
||||||
private static readonly LocalizableString MessageFormat =
|
|
||||||
new LocalizableResourceString(nameof(Strings.AnalyzerMessageFormat), Strings.ResourceManager, typeof(Strings));
|
|
||||||
private static readonly LocalizableString Description =
|
|
||||||
new LocalizableResourceString(nameof(Strings.AnalyzerDescription), Strings.ResourceManager, typeof(Strings));
|
|
||||||
|
|
||||||
private const string Category = "Usage";
|
|
||||||
|
|
||||||
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat,
|
|
||||||
Category, DiagnosticSeverity.Warning, 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.LocalDeclarationStatement);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
|
|
||||||
{
|
|
||||||
var localDeclaration = (LocalDeclarationStatementSyntax)context.Node;
|
|
||||||
|
|
||||||
if (localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform data flow analysis on the local declaration.
|
|
||||||
DataFlowAnalysis dataFlowAnalysis = context.SemanticModel.AnalyzeDataFlow(localDeclaration);
|
|
||||||
|
|
||||||
// Retrieve the local symbol for each variable in the local declaration
|
|
||||||
// and ensure that it is not written outside of the data flow analysis region.
|
|
||||||
VariableDeclaratorSyntax variable = localDeclaration.Declaration.Variables.Single();
|
|
||||||
ISymbol variableSymbol = context.SemanticModel.GetDeclaredSymbol(variable, context.CancellationToken);
|
|
||||||
if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation(), localDeclaration.Declaration.Variables.First().Identifier.ValueText));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,16 @@ namespace ScriptCoreGenerator;
|
|||||||
|
|
||||||
public static class SyntaxNodeExtensions
|
public static class SyntaxNodeExtensions
|
||||||
{
|
{
|
||||||
|
public static string? ExtractName(NameSyntax? name)
|
||||||
|
{
|
||||||
|
return name switch
|
||||||
|
{
|
||||||
|
SimpleNameSyntax ins => ins.Identifier.Text,
|
||||||
|
QualifiedNameSyntax qns => qns.Right.Identifier.Text,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public static T GetParent<T>(this SyntaxNode node)
|
public static T GetParent<T>(this SyntaxNode node)
|
||||||
{
|
{
|
||||||
var parent = node.Parent;
|
var parent = node.Parent;
|
||||||
@@ -27,6 +37,25 @@ public static class SyntaxNodeExtensions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static T? GetParentOrNull<T>(this SyntaxNode node) where T : SyntaxNode
|
||||||
|
{
|
||||||
|
SyntaxNode? parent = node.Parent;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
switch (parent)
|
||||||
|
{
|
||||||
|
case null:
|
||||||
|
return null;
|
||||||
|
case T t:
|
||||||
|
return t;
|
||||||
|
default:
|
||||||
|
parent = parent.Parent;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// determine the namespace the class/enum/struct is declared in, if any
|
/// determine the namespace the class/enum/struct is declared in, if any
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user