1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

コードから{クラス名.メソッド名}を抽出する

Posted at

はじめに

  • 新規にメソッドを作る際、既存のメソッド名(英単語)を参考にしたい
  • コードの中から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;
        }
    }
}

デモ

demo.gif

Git Hub

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?