SonarAnalyzer.CSharp でサイクロマティック複雑度を算出

C# ソースファイルのサイクロマティック複雑度(循環的複雑度)を算出するサンプルを SonarC# (SonarAnalyzer.CSharp)API を利用して作ってみました。

今回、使用した環境は以下の通りです。

ソースは http://github.com/fits/try_samples/tree/master/blog/20190422/

準備

dotnet コマンドを使ってプロジェクトを作成します。

プロジェクトの作成
> dotnet new console

C# のソースを構文解析する必要があるので Microsoft.CodeAnalysis.CSharp パッケージを追加します。

Microsoft.CodeAnalysis.CSharp の追加
> dotnet add package Microsoft.CodeAnalysis.CSharp

次に、SonarAnalyzer.CSharp パッケージを追加しますが、これは IDE(VisualStudio)用パッケージのようなので、単に add package してもプロジェクトで参照できるようにはなりません。(analyzers ディレクトリへ .dll が配置されているため)

そこで、以下のように指定のディレクトリへパッケージを配置し ※、.csproj を編集する事で対応してみました。

 ※ 普通に add package して .nuget/packages ディレクトリへ
    配置された dll のパスを設定する方法も考えられる
SonarAnalyzer.CSharp の追加(pkg ディレクトリへ配置)
> dotnet add package SonarAnalyzer.CSharp --package-directory pkg

上記コマンドで追加された PackageReference 要素をコメントアウトし、代わりに Reference 要素を追加します。(HintPath で SonarAnalyzer.CSharp.dll のパスを指定)

sonar_sample.csproj の編集
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.0.0" />
    <!-- 以下をコメントアウト
    <PackageReference Include="SonarAnalyzer.CSharp" Version="7.13.0.8313">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    -->
    <!-- 以下を追加 -->
    <Reference Include="SonarAnalyzer.CSharp">
      <HintPath>./pkg/sonaranalyzer.csharp/7.13.0.8313/analyzers/SonarAnalyzer.CSharp.dll</HintPath>
    </Reference>
  </ItemGroup>

</Project>

実装

C#ソースコードをパースして MethodDeclarationSyntax を取り出し、CSharpCyclomaticComplexityMetric.GetComplexity メソッドへ渡す事でサイクロマティック複雑度を算出します。

Program.cs
using System;
using System.Linq;
using System.IO;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using SonarAnalyzer.Metrics.CSharp;

namespace CyclomaticComplexity
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var reader = new StreamReader(args[0]))
            {
                // ソースコードのパース
                var tree = CSharpSyntaxTree.ParseText(reader.ReadToEnd());
                var root = tree.GetCompilationUnitRoot();

                // MethodDeclarationSyntax の取得
                var methods = root.DescendantNodes()
                                    .OfType<MethodDeclarationSyntax>();

                foreach(var m in methods)
                {
                    var c = CSharpCyclomaticComplexityMetric.GetComplexity(m);

                    Console.WriteLine("{0},{1}", m.Identifier, c.Complexity);
                }
            }
        }
    }
}

実行

ビルドして実行してみます。

ビルド
> dotnet build

・・・
ビルドに成功しました。
    0 個の警告
    0 エラー

Program.cs の複雑度を算出してみます。

実行1
> dotnet run Program.cs

Main,2

SonarC# のソースで試してみます。

実行2
> cd ..
> git clone https://github.com/SonarSource/sonar-dotnet.git
・・・

> cd sonar_sample
> dotnet run ../sonar-dotnet/sonaranalyzer-dotnet/src/SonarAnalyzer.CSharp/Metrics/CSharpMetrics.cs

GetCognitiveComplexity,1
GetCyclomaticComplexity,1
IsClass,4
IsCommentTrivia,1
IsDocumentationCommentTrivia,4
IsEndOfFile,1
IsFunction,16
IsNoneToken,1
IsStatement,28