Skip to content

Latest commit

 

History

History
284 lines (207 loc) · 7.48 KB

presentation.md

File metadata and controls

284 lines (207 loc) · 7.48 KB
marp theme _class style backgroundImage
true
gaia
lead
section{ color: #002030; }
url('background.jpg')

bg left:40% 80%

Code with Roslyn

Write C# code analyzers

slides at: https://github.com/tomuxmon/code-with-roslyn


What is Roslyn

  • Before Roslyn (System.CodeDom)
  • Roslyn development (starting 2010 - go prod 2015)
  • Compiler SDK vs C# language

What are the possibilities

  • Analyzers - inspect code and emit warnings in your IDE
  • Fixers - offer code change suggestions to your IDE
  • Code generation - generate code based on your code AST.

Analyzer / Fixer Powers

[*.cs]
dotnet_diagnostic.CA1822.severity = error

[*.MyGenerated.cs]
generated_code = true

dotnet new analyzer

  • minimal analyzer
  • minimal fixer

Required nuget packages

  <ItemGroup>

    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
    </PackageReference>

    <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
    </PackageReference>

    <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.4.0" />
    
  </ItemGroup>

Minimal analyzer - outer view

using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace min_analyzer;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class SomeAnalyzer : DiagnosticAnalyzer {
    // ...snip... 
}

Minimal analyzer - diagnostic descriptor

private static readonly DiagnosticDescriptor someDescriptor = new(
    "S01", "Method body token count",
    "Method body contains '{0}' tokens. Reduce it to 40!",
    "Security", DiagnosticSeverity.Warning, true);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
    ImmutableArray.Create(someDescriptor);

Minimal analyzer - register action

public override void Initialize(AnalysisContext ctx) {
    ctx.EnableConcurrentExecution();
    ctx.ConfigureGeneratedCodeAnalysis(
        GeneratedCodeAnalysisFlags.Analyze |
        GeneratedCodeAnalysisFlags.ReportDiagnostics);
    ctx.RegisterSyntaxNodeAction(
        AnalyzeMethodSyntaxNode,
        SyntaxKind.MethodDeclaration);
}

Minimal analyzer - report diagnostic

private void AnalyzeMethodSyntaxNode(SyntaxNodeAnalysisContext ctx) {
    if (ctx.Node is MethodDeclarationSyntax myx) {
        var node_count = myx.Body.DescendantNodes().Count();
        if (node_count > 40) {
            ctx.ReportDiagnostic(
                Diagnostic.Create(
                    someDescriptor,
                    myx.Body.GetLocation(),
                    node_count));
        }
    }
}

Minimal fixer - outer view

using System.Collections.Immutable;
using System.Threading.Tasks;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CSharp;

namespace min_fixer;

[ExportCodeFixProvider(LanguageNames.CSharp)]
public class SomeFixer : CodeFixProvider {
    // ...snip...
}

Minimal fixer - fix what?

// The name as it will appear in the light bulb menu
private const string FixTitle = "Reduce method size (start it from scratch)";

public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create("S01");

public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

Minimal fixer - register fix

public override async Task RegisterCodeFixesAsync(CodeFixContext ctx) {
    var root = await ctx.Document.GetSyntaxRootAsync(ctx.CancellationToken);
    var diagnostic = ctx.Diagnostics.First();
    var methodSyntax = root.
        FindToken(diagnostic.Location.SourceSpan.Start).
        Parent.
        AncestorsAndSelf().
        OfType<MethodDeclarationSyntax>().
        First();

    ctx.RegisterCodeFix(
        CodeAction.Create(
            title: FixTitle,
            createChangedDocument: c => FixAsync(ctx.Document, methodSyntax, c),
            equivalenceKey: FixTitle),
        diagnostic);
}

Minimal fixer - replace operation

private static async Task<Document> FixAsync(
    Document doc, 
    MethodDeclarationSyntax methodSyntax, 
    CancellationToken ct
) {
    var oldRoot = await doc.GetSyntaxRootAsync(ct);
    var newRoot = oldRoot.ReplaceNode(methodSyntax.Body, SyntaxFactory.Block());
    return doc.WithSyntaxRoot(newRoot);
}

Usefull tools


Demo time


Articles about Roslyn


Ready to use Analyzers


Thank you for listening 🎇

Now Questions!