Roslyn による Visual Studio のアドイン

35
COMU+ ここここここ Sep 27, 2013 ここ こここ Roslyn こここ Visual Studio こここここ

description

2013-07-27 新宿, 東京 こみゅぷらす Tech Aid 2013 http://comuplus.net/CLT2013/

Transcript of Roslyn による Visual Studio のアドイン

Page 1: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

Sep 27, 2013小島 富治雄

Roslyn による Visual Studio のアドイン

Page 2: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

自己紹介

• 小島 富治雄• @Fujiwo• 福井コンピュータアーキテクト株式会社

• Microsoft MVP C# (2005-2014)

Page 3: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

本日の資料• http://blog.shos.info/archives/2013/07/addinbyroslyn.html

にて公開します• スライドは

http://www.slideshare.net/Fujiwo/roslyn-visual-studio

Page 4: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

宣伝

• 来週土曜「 Hokuriku.NET Vol.12 in 福井」http://atnd.org/events/40509

※ 個人の体験です。効果には個人差があります。

Page 5: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

Roslyn について

Page 6: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

Roslyn

• C# や Visual Basic のコンパイラーの内部の API 等を公開

• "Compiler as a Service" と表現

http://www.zdnet.com/blog/microsoft/microsofts-roslyn-compiler-as-a-service-support-unlikely-to-be-in-visual-studio-2012/10896 より

Page 7: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

Microsoft “Roslyn” CTP

• http://msdn.microsoft.com/en-us/vstudio/roslyn.aspx

Page 9: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

Roslyn の機能

• C# や Visual Basic のソースコード解析機能• Visual Studio の拡張機能• C# スクリプト機能• 等

http://www.zdnet.com/blog/microsoft/microsofts-roslyn-compiler-as-a-service-support-unlikely-to-be-in-visual-studio-2012/10896 より

Page 10: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

本日のデモ

• 今日は、「ソースコード解析機能」を用いて、簡単な Visual Studio のアドインを

• C# のソースコードを解析して、識別子等を数えて、それを WPF で作成したウィンドウに表示

Page 11: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

「 Visual Studio アドイン」プロジェクトの新規作成

Page 12: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

NuGet から Roslyn をインストール

Page 13: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

Roslyn 追加後のプロジェクトの参照設定

Page 14: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

ViewModel の作成

• 今回 WPF で出すウィンドウ用の ViewModel• ソースコード内の識別子や using 、クラス等

の数を保持

※ 実装の参考 :「 INotifyPropertyChanged の実装に便利なクラスとコードスニペット」

Page 15: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

ViewModelusing System;using System.ComponentModel;using System.Linq.Expressions;using System.Runtime.CompilerServices; namespace AddinByRoslyn{ public static class PropertyChangedEventHandlerExtensions { public static void Raise(this PropertyChangedEventHandler onPropertyChanged, object sender, [CallerMemberName] string propertyName = "") { if (onPropertyChanged != null) onPropertyChanged(sender, new PropertyChangedEventArgs(propertyName)); } public static void Raise<PropertyType>(this PropertyChangedEventHandler onPropertyChanged, object sender, Expression<Func<PropertyType>> propertyExpression) { onPropertyChanged.Raise(sender, propertyExpression.GetMemberName()); } static string GetMemberName<MemberType>(this Expression<Func<MemberType>> expression) { return ((MemberExpression)expression.Body).Member.Name; } }

Page 16: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

ViewModel public class ViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; int identifierCount = 0; public int IdentifierCount { get { return identifierCount; } set { if (value != identifierCount) { identifierCount = value; PropertyChanged.Raise(this); RaiseUpdateData(); } } } int usingCount = 0; public int UsingCount { get { return usingCount; } set { if (value != usingCount) { usingCount = value; PropertyChanged.Raise(this); RaiseUpdateData(); } } }

int classCount = 0;

public int ClassCount { get { return classCount; } set { if (value != classCount) { classCount = value; PropertyChanged.Raise(this); RaiseUpdateData(); } } } int fieldCount = 0; public int FieldCount { get { return fieldCount; } set { if (value != fieldCount) { fieldCount = value; PropertyChanged.Raise(this); RaiseUpdateData(); } } }

Page 17: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

ViewModel

int propertyCount = 0; public int PropertyCount { get { return propertyCount; } set { if (value != propertyCount) { propertyCount = value; PropertyChanged.Raise(this); RaiseUpdateData(); } } } int methodCount = 0; public int MethodCount { get { return methodCount; } set { if (value != methodCount) { methodCount = value; PropertyChanged.Raise(this); RaiseUpdateData(); } } }

int variableCount = 0; public int VariableCount { get { return variableCount; } set { if (value != variableCount) { variableCount = value; PropertyChanged.Raise(this); RaiseUpdateData(); } } } int ifCount = 0; public int IfCount { get { return ifCount; } set { if (value != ifCount) { ifCount = value; PropertyChanged.Raise(this); RaiseUpdateData(); } } }

Page 18: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

ViewModel int lambdaCount = 0; public int LambdaCount { get { return lambdaCount; } set { if (value != lambdaCount) { lambdaCount = value; PropertyChanged.Raise(this); RaiseUpdateData(); } } } public object Data { get { return new [] { new { 名称 = " 識別子の数 " , 結果 = IdentifierCount }, new { 名称 = "using の数 " , 結果 = UsingCount }, new { 名称 = " クラスの数 " , 結果 = ClassCount }, new { 名称 = " フィールドの数 ", 結果 = FieldCount }, new { 名称 = " プロパティの数 ", 結果 = PropertyCount }, new { 名称 = " メソッドの数 " , 結果 = MethodCount }, new { 名称 = " 変数の数 " , 結果 = VariableCount }, new { 名称 = "if の数 " , 結果 = IfCount }, new { 名称 = " ラムダ式の数 " , 結果 = LambdaCount } }; } }

string result = string.Empty; public string Result { get { return result; } set { if (value != result) { result = value; PropertyChanged.Raise(this); } } } public void Clear() { IdentifierCount = UsingCount = ClassCount = FieldCount = PropertyCount = MethodCount = VariableCount = IfCount = LambdaCount = 0; Result = string.Empty; } void RaiseUpdateData() { PropertyChanged.Raise(this, () => Data); } }}

Page 19: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

Roslyn を用いたコード解析部

• Roslyn.Compilers.CSharp.SyntaxWalkerクラスを継承

• SyntaxWalker クラス– Visitor パターン– Visit メソッドを呼ぶと、全ノードを辿り、

ノードの種類毎の virtual メソッドを呼ぶ• 例えば、識別子のノードの場合に

は、 VisitIdentifierName という virtual メソッドが呼ばれる• それぞれの virtual メソッドをオーバーライドす

れば、そこに処理を書ける• 今日は、呼ばれた回数を数えるだけ

Page 20: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

Roslyn を用いたコード解析部using Roslyn.Compilers.CSharp;using System.IO;using System.Text; namespace AddinByRoslyn{ class SyntaxCounter : SyntaxWalker { readonly ViewModel viewModel; StringBuilder stringBuilder; // viewModel.Result 用 public SyntaxCounter(ViewModel viewModel) { this.viewModel = viewModel; } // ソース ファイルの中を解析 public void AnalyzeSourceFile(string sourceFileName) { using (var reader = new StreamReader(sourceFileName, false)) { var sourceCode = reader.ReadToEnd(); Analyze(sourceCode); } }

Page 21: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

Roslyn を用いたコード解析部 // ソース コードの中を解析 void Analyze(string sourceCode) { // Roslyn.Compilers.CSharp.SyntaxTree クラスによるシンタックス ツリーの取得 var tree = SyntaxTree.ParseText(sourceCode); Analyze(tree.GetRoot()); // シンタックス ツリーのルート要素を解析 } // ルート要素の中を解析 void Analyze(CompilationUnitSyntax root) { viewModel.Clear(); stringBuilder = new StringBuilder(); Visit(root); // Visit メソッドにより、全ノードを辿る (Visitor パターン ) viewModel.Result = stringBuilder.ToString(); } // 全ノードについて public override void DefaultVisit(SyntaxNode node) { base.DefaultVisit(node); // ノードの情報を stringBuilder に追加 stringBuilder.AppendLine(string.Format("NodeType: {0}, Node: {1}", node.GetType().Name, node.GetText())); }

Page 22: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

Roslyn を用いたコード解析部 // 識別子のノードの場合 public override void VisitIdentifierName(IdentifierNameSyntax node) { base.VisitIdentifierName(node); viewModel.IdentifierCount++; // 識別子を数える } // using のノードの場合 public override void VisitUsingDirective(UsingDirectiveSyntax node) { base.VisitUsingDirective(node); viewModel.UsingCount++; // using を数える } // クラスのノードの場合 public override void VisitClassDeclaration(ClassDeclarationSyntax node) { base.VisitClassDeclaration(node); viewModel.ClassCount++; // クラスを数える } // フィールドのノードの場合 public override void VisitFieldDeclaration(FieldDeclarationSyntax node) { base.VisitFieldDeclaration(node); viewModel.FieldCount++; // フィールドを数える }

Page 23: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

Roslyn を用いたコード解析部 // プロパティのノードの場合 public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node) { base.VisitPropertyDeclaration(node); viewModel.PropertyCount++; // プロパティを数える } // メソッドのノードの場合 public override void VisitMethodDeclaration(MethodDeclarationSyntax node) { base.VisitMethodDeclaration(node); viewModel.MethodCount++; // メソッドを数える } // 変数のノードの場合 public override void VisitVariableDeclaration(VariableDeclarationSyntax node) { base.VisitVariableDeclaration(node); viewModel.VariableCount++; // 変数を数える } // if のノードの場合 public override void VisitIfStatement(IfStatementSyntax node) { base.VisitIfStatement(node); viewModel.IfCount++; // if を数える }

Page 24: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

Roslyn を用いたコード解析部 // シンプルなラムダ式のノードの場合 public override void VisitSimpleLambdaExpression(SimpleLambdaExpressionSyntax node) { base.VisitSimpleLambdaExpression(node); viewModel.LambdaCount++; // ラムダ式を数える } // 括弧付きのラムダ式のノードの場合 public override void VisitParenthesizedLambdaExpression(ParenthesizedLambdaExpressionSyntax node) { base.VisitParenthesizedLambdaExpression(node); viewModel.LambdaCount++; // ラムダ式を数える } }}

Page 25: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

View.xaml

• XAML の Grid 内– ViewModel の Result を表示するための

TextBox– ViewModel の Data を表示するための

DataGrid• プロジェクトに System.Xaml への参照設定

を追加

Page 26: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

View.xaml<Window x:Class="AddinByRoslyn.View" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="500" d:DesignWidth="500"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBox TextWrapping="Wrap" Text="{Binding Path=Result}" FontFamily="Meiryo" FontSize="14" IsReadOnlyCaretVisible="True" VerticalScrollBarVisibility="Auto" /> <DataGrid Grid.Row="1" ItemsSource="{Binding Path=Data}" FontFamily="Meiryo" FontSize="14" /> </Grid></Window>

Page 27: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

View.xaml.cs

• ViewModel のインスタンスを保持– DataContext

• コンストラクターで C# のソース ファイル名を受け取る– SyntaxCounter クラスで解析

Page 28: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

View.xaml.csusing System.Windows; namespace AddinByRoslyn{ public partial class View : Window { ViewModel viewModel = new ViewModel(); public View(string sourceFileName) { InitializeComponent(); DataContext = viewModel; // DataContext に ViewModel のインスタンスを設定 if (!string.IsNullOrWhiteSpace(sourceFileName)) // SyntaxCounter クラスを用いたソース ファイルの解析 new SyntaxCounter(viewModel).AnalyzeSourceFile(sourceFileName); } }}

Page 29: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

• ViewModel のインスタンスを保持– DataContext

• コンストラクターで C# のソース ファイル名を受け取る– SyntaxCounter クラスで解析

Page 30: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

Visual Studio アドイン部

• Visual Studio アドイン部のコードをカスタマイズ–プロジェクトの新規作成時に自動生成

された Connect.cs 内–Exec メソッドの中で View を作成し、

ソースコード ファイル名を渡す

Page 31: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

Visual Studio アドイン部 // カスタマイズする Exec メソッド public void Exec(string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled) { if (executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault && commandName == "AddinByRoslyn.Connect.AddinByRoslyn") { var view = new View(GetSourceFileName()); // View のインスタンスにソースファイル名を渡す view.Show(); handled = true; } else { handled = false; } } // ソース ファイル名の取得 ( 追加する private メソッド ) string GetSourceFileName() { var items = _applicationObject.SelectedItems; if (items.Count == 0) return null; var item = items.Item(1); return item.ProjectItem.get_FileNames(1); }

Page 32: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

実行結果

Page 33: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

実行結果

Page 34: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす

まとめ

• 今日は、 Roslyn を使って何種類かの文法要素を数えただけ

• 同様の方法で、より複雑な解析を行ったり、一部を変更できる

• Roslyn の機能は C# や Visual Basic のソースコードの解析だけではない

Page 35: Roslyn による Visual Studio のアドイン

COMU+こみゅぷらす