はじめに
- 新規にメソッドを作る際、既存のメソッド名(英単語)を参考にしたい
- コードの中からUTを抽出したい
- このコードにはどのくらいのメソッドが実装されているんだろう
と思うことが6カ月に1回くらいあると思います。
そんなときのために、メソッド一覧を抽出するプログラム(WPF)を作成しました。
実装
View/MainWindow.xaml
<Window x:Class="ClassMethodAnalyzer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModel="clr-namespace:ClassMethodAnalyzer.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<viewModel:MainViewModel/>
</Window.DataContext>
<Grid>
<Grid>
<Button Content="Select Folder" HorizontalAlignment="Left" Margin="10" VerticalAlignment="Top" Width="100" Command="{Binding SelectFolderCommand}"/>
<Button Content="Clear" HorizontalAlignment="Left" Margin="120,10,0,0" VerticalAlignment="Top" Width="100" Command="{Binding ClearCommand}"/>
<TextBox Name="OutputTextBox" Margin="10,50,10,10" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" TextWrapping="Wrap"
Text="{Binding AnalysisResult}" IsReadOnly="True" AcceptsReturn="True"/>
</Grid>
</Grid>
</Window>
ViewModel/MainViewModel.cs
using System.ComponentModel;
using System.Windows.Input;
using ClassMethodAnalyzer.Commands;
using ClassMethodAnalyzer.Model;
using Microsoft.WindowsAPICodePack.Dialogs;
namespace ClassMethodAnalyzer.ViewModel
{
public class MainViewModel : INotifyPropertyChanged
{
private string _analysisResult;
public string AnalysisResult
{
get => _analysisResult;
set
{
_analysisResult = value;
OnPropertyChanged(nameof(AnalysisResult));
}
}
public ICommand SelectFolderCommand { get; }
public ICommand ClearCommand { get; }
public MainViewModel()
{
SelectFolderCommand = new RelayCommand(SelectFolder);
ClearCommand = new RelayCommand(Clear);
}
private void SelectFolder()
{
var dialog = new CommonOpenFileDialog
{
IsFolderPicker = true
};
if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
{
string folderPath = dialog.FileName;
AnalysisResult = CodeAnalyzer.Analyze(folderPath);
}
}
private void Clear()
{
AnalysisResult = string.Empty;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Commands/RelayCommand.cs
using System.Windows.Input;
namespace ClassMethodAnalyzer.Commands
{
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public RelayCommand(Action execute, Func<bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute();
public void Execute(object parameter) => _execute();
public event EventHandler? CanExecuteChanged;
}
}
Model/ClassMethods.cs
namespace ClassMethodAnalyzer.Model
{
public class ClassMethods
{
public List<string> Methods { get; }
public string Constructor { get; }
public ClassMethods(List<string> methods, string constructor)
{
Methods = methods;
Constructor = constructor;
}
}
}
Model/CodeAnalyzer.cs
using System.IO;
using System.Text.RegularExpressions;
namespace ClassMethodAnalyzer.Model
{
public static class CodeAnalyzer
{
public static string Analyze(string folderPath)
{
var results = new List<string>();
// フォルダ内の全ファイルを再帰的に取得し、bin/objフォルダを除外
foreach (var filePath in GetCSharpFiles(folderPath))
{
var code = File.ReadAllText(filePath);
var classNames = ExtractClassNames(code);
var methodsByClass = ExtractMethodsByClass(code, classNames);
foreach (var className in methodsByClass.Keys)
{
foreach (var methodName in methodsByClass[className].Methods)
{
results.Add($"{className}.{methodName}");
}
if (methodsByClass[className].Constructor != null)
{
results.Add($"{className}.{methodsByClass[className].Constructor}");
}
}
}
return string.Join("\n", results);
}
private static IEnumerable<string> GetCSharpFiles(string folderPath)
{
foreach (var file in Directory.GetFiles(folderPath, "*.cs", SearchOption.TopDirectoryOnly))
{
yield return file;
}
foreach (var directory in Directory.GetDirectories(folderPath))
{
// binまたはobjフォルダをスキップ
if (directory.EndsWith("bin", StringComparison.OrdinalIgnoreCase) ||
directory.EndsWith("obj", StringComparison.OrdinalIgnoreCase))
{
continue;
}
// サブディレクトリを再帰的に取得
foreach (var file in GetCSharpFiles(directory))
{
yield return file;
}
}
}
private static List<string> ExtractClassNames(string code)
{
var classNames = new List<string>();
var classPattern = @"\bclass\s+(\w+)";
var matches = Regex.Matches(code, classPattern);
foreach (Match match in matches)
{
classNames.Add(match.Groups[1].Value);
}
return classNames;
}
private static Dictionary<string, ClassMethods> ExtractMethodsByClass(string code, List<string> classNames)
{
var methodsByClass = new Dictionary<string, ClassMethods>();
foreach (var className in classNames)
{
// クラス全体を取得
var methodPattern = $@"{className}.*?\{{((?:.*?\n)*?)\}}";
var classMatch = Regex.Match(code, methodPattern, RegexOptions.Singleline);
if (classMatch.Success)
{
var methods = new List<string>();
string constructor = null;
var methodPatternInClass = @"\b(?:public|private|protected|internal)?\s*(?:void|int|string|bool|float|double|var)\s+(\w+)\s*\(";
var constructorPattern = $@"\b{className}\s*\(";
var classBody = classMatch.Groups[1].Value;
// メソッドを抽出
var methodMatches = Regex.Matches(classBody, methodPatternInClass);
foreach (Match methodMatch in methodMatches)
{
methods.Add(methodMatch.Groups[1].Value);
}
// コンストラクタを抽出
var constructorMatch = Regex.Match(classBody, constructorPattern);
if (constructorMatch.Success)
{
constructor = className; // コンストラクタ名はクラス名と同じ
}
methodsByClass[className] = new ClassMethods(methods, constructor);
}
}
return methodsByClass;
}
}
}
デモ
Git Hub