diff --git a/App.config b/App.config new file mode 100644 index 0000000..cae7ab5 --- /dev/null +++ b/App.config @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..0d3c718 --- /dev/null +++ b/Program.cs @@ -0,0 +1,550 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace RoslynAnalysisSampleConsole +{ + class Program + { + private const string ARG_HINT_SINGLE_SOURCE = "/singlesource"; + + //private const string RESULT_OUTPUT_DIR_NAME = "Output"; + + private const string TAB = "\t"; + private const string INDENT = TAB; + + private const string TEST_SOURCE_PATH = @""; + private const string TEST_DLL_PATH = @""; + + private static string _executingAsmPath = string.Empty; + + private static readonly string[] _exceptDirs = { @"\obj\", ".designer.cs" }; + private static readonly string[] _exceptNamespacesStartsWith = { "System.", " asmList = new List(); + + asmList.Add(MetadataReference.CreateFromFile(typeof(object).Assembly.Location)); + + foreach (string path in Directory.GetFiles(_dllPath) + .Where(p => p.EndsWith(".dll") || p.EndsWith(".exe"))) + { + var asm = MetadataReference.CreateFromFile(path); + asmList.Add(asm); + } + + foreach (string path in _loadingAssemblyDirs) + { + foreach (string asmPath in Directory.GetFiles(path) + .Where(p => p.EndsWith(".dll") || p.EndsWith(".exe"))) + { + var asm = MetadataReference.CreateFromFile(asmPath); + asmList.Add(asm); + } + } + + if (isSingleSource) + { + SyntaxTree tree; + using (TextReader reader = new StreamReader(_sourceDirPath)) + { + tree = CSharpSyntaxTree.ParseText(reader.ReadToEnd()); + } + + Dictionary dic = new Dictionary {{_sourceDirPath, tree}}; + WriteSingleFileAnalyzeResult(_sourceDirPath, asmList, dic); + } + else + { + var sourceFilePathList = + from s in Directory.GetFiles(_sourceDirPath, "*.cs", SearchOption.AllDirectories) + where !_exceptDirs.Any(s2 => s.ToLower().Contains(s2)) + select s; + + Dictionary treeDic = new Dictionary(); + + var filePathList = sourceFilePathList as string[] ?? sourceFilePathList.ToArray(); + foreach (string tmpPath in filePathList) + { + using (TextReader reader = new StreamReader(tmpPath)) + { + treeDic.Add(tmpPath, CSharpSyntaxTree.ParseText(reader.ReadToEnd())); + } + } + + foreach (string sourcePath in filePathList) + { + WriteSingleFileAnalyzeResult(sourcePath, asmList, treeDic); + } + } + + // 最後に空行入れる + Console.WriteLine(); + } + catch (Exception ex) + { + WriteExceptionLog(ex); + Environment.Exit(-1); + } + } + + /// + /// 一つのソースファイルを解析 + /// + /// ソースファイルパス + /// アセンブリリスト(名前空間・クラス名解決に使用) + /// インデント(省略可能) + private static void WriteSingleFileAnalyzeResult(string sourceFilePath, IEnumerable assemblyList, + Dictionary syntaxTreeDic, string indent = null) + { + try + { + Console.WriteLine($"[Source: {sourceFilePath}]"); + + SyntaxTree target = syntaxTreeDic[sourceFilePath]; + + var compilation = CSharpCompilation.Create("TypeInfo", syntaxTreeDic.Values, assemblyList); + var model = compilation.GetSemanticModel(target); + + var root = target.GetRoot(); + var classes = root.DescendantNodes().OfType(); + + foreach (ClassDeclarationSyntax cls in classes) + { + WriteClassDeclaration(cls, model); + } + } + catch (Exception ex) + { + List errorReport = new List(); + errorReport.Add($"AnalyzingSourceFile: {sourceFilePath}"); + + WriteExceptionLog(new ApplicationException("[" + string.Join(",", errorReport) + "]", ex)); + } + } + + /// + /// 一つのクラスを解析 + /// + /// クラス定義 + /// セマンティックモデル + /// インデント(省略可能) + private static void WriteClassDeclaration(ClassDeclarationSyntax cls, SemanticModel model, string indent = null) + { + string nameSpace = string.Empty; + string className = string.Empty; + + ClassDeclarationSyntax tmp = cls; + while (tmp.Parent is ClassDeclarationSyntax syntax1) + { + className = tmp.Identifier.Text; + nameSpace = string.IsNullOrEmpty(nameSpace) ? className : className + "." + nameSpace; + + tmp = syntax1; + } + + if (cls.Parent is NamespaceDeclarationSyntax syntax2) + { + nameSpace = syntax2.Name.ToString() + nameSpace; + } + else + { + nameSpace = "(名前空間無し)"; + } + + try + { + Console.WriteLine((indent ?? string.Empty) + "Class Declaration: {0}.{1}", nameSpace, cls.Identifier.Text); + + var constructorBlocks = cls.DescendantNodes().OfType(); + foreach (ConstructorDeclarationSyntax constructorBlock in constructorBlocks) + { + WriteConstructorDeclaration(constructorBlock, model, nameSpace, cls.Identifier.Text, + (indent ?? string.Empty) + INDENT); + } + + var methodBlocks = cls.DescendantNodes().OfType(); + foreach (MethodDeclarationSyntax methodBlock in methodBlocks) + { + WriteMethodDeclaration(methodBlock, model, nameSpace, cls.Identifier.Text, + (indent ?? string.Empty) + INDENT); + } + } + catch (Exception ex) + { + List errorReport = new List(); + errorReport.Add($"AnalyzingNamespace: {nameSpace}"); + errorReport.Add($"AnalyzingClass: {className}"); + + WriteExceptionLog(new ApplicationException("[" + string.Join(",", errorReport) + "]", ex)); + } + } + + /// + /// 一つのコンストラクタ定義を解析 + /// + /// コンストラクタ定義 + /// セマンティックモデル + /// 呼び出し元クラスの属する名前空間 + /// 呼び出し元クラス名 + /// インデント(省略可能) + private static void WriteConstructorDeclaration(ConstructorDeclarationSyntax constructorBlock, SemanticModel model, + string nameSpace, string className, string indent = null) + { + StringBuilder str = new StringBuilder(); + + try + { + str.Append("Constructor Declaration: "); + str.Append("\t"); + str.Append(nameSpace + "." + className); + str.Append("\t"); + str.Append(constructorBlock.Identifier.Text); + + List paramTypeStrings = new List(); + foreach (var item in constructorBlock.ParameterList.Parameters) + { + paramTypeStrings.Add(model.GetTypeInfo(item.Type).Type.Name); + } + + str.Append($"({string.Join(",", paramTypeStrings)})"); + str.Append("\t"); + + string modifier = constructorBlock.Modifiers.ToString(); + str.Append(modifier); + str.Append("\t"); + + int lineCount = 0; + using (StringReader sReader = new StringReader(constructorBlock.WithoutTrivia().ToFullString())) + { + string line; + while ((line = sReader.ReadLine()) != null) + { + if (line != string.Empty && + !string.IsNullOrWhiteSpace(line) && + !line.Trim().StartsWith("//")) + { + lineCount++; + } + } + } + + str.Append(lineCount); + Console.WriteLine((indent ?? string.Empty) + str); + + WriteCallingMethods(constructorBlock, model, nameSpace, className, (indent ?? string.Empty) + INDENT); + } + catch (Exception ex) + { + List errorReport = new List(); + errorReport.Add($"AnalyzingNamespace: {nameSpace}"); + errorReport.Add($"AnalyzingClass: {className}"); + errorReport.Add($"AnalyzingMethod: {constructorBlock.Identifier.Text}"); + + WriteExceptionLog(new ApplicationException("[" + string.Join(",", errorReport) + "]", ex)); + } + } + + /// + /// 一つのメソッド定義を解析 + /// + /// メソッド定義 + /// セマンティックモデル + /// 呼び出し元クラスの属する名前空間 + /// 呼び出し元クラス名 + /// インデント(省略可能) + private static void WriteMethodDeclaration(MethodDeclarationSyntax methodBlock, SemanticModel model, + string nameSpace, string className, string indent = null) + { + StringBuilder str = new StringBuilder(); + + try + { + var symbol = model.GetDeclaredSymbol(methodBlock); + + str.Append("Method Declaration: "); + str.Append("\t"); + + if (symbol != null) + { + str.Append(symbol.ContainingType); + } + else + { + str.Append(nameSpace + "." + className); + } + + str.Append("\t"); + str.Append(methodBlock.Identifier.Text); + + str.Append(methodBlock.ParameterList); + str.Append("\t"); + + string modifier = methodBlock.Modifiers.ToString(); + str.Append(modifier); + str.Append("\t"); + + int lineCount = 0; + using (StringReader sReader = new StringReader(methodBlock.WithoutTrivia().ToFullString())) + { + string line; + while ((line = sReader.ReadLine()) != null) + { + if (line != string.Empty && + !string.IsNullOrWhiteSpace(line) && + !line.Trim().StartsWith("//")) + { + lineCount++; + } + } + } + + str.Append(lineCount); + Console.WriteLine((indent ?? string.Empty) + str); + + WriteCallingMethods(methodBlock, model, nameSpace, className, (indent ?? string.Empty) + INDENT); + } + catch (Exception ex) + { + List errorReport = new List(); + errorReport.Add($"AnalyzingNamespace: {nameSpace}"); + errorReport.Add($"AnalyzingClass: {className}"); + errorReport.Add($"AnalyzingMethod: {methodBlock.Identifier.Text}"); + + WriteExceptionLog(new ApplicationException("[" + string.Join(",", errorReport) + "]", ex)); + } + } + + public static void WriteCallingMethods(MethodDeclarationSyntax methodBlock, SemanticModel model, + string thisNameSpace, string thisClassName, string indent = null) + { + var symbol = model.GetDeclaredSymbol(methodBlock); + + var calling = methodBlock.DescendantNodes().OfType(); + WriteCallingMethods(calling, model, thisNameSpace, thisClassName, indent); + } + + public static void WriteCallingMethods(ConstructorDeclarationSyntax constructorBlock, SemanticModel model, + string thisNameSpace, string thisClassName, string indent = null) + { + var calling = constructorBlock.DescendantNodes().OfType(); + WriteCallingMethods(calling, model, thisNameSpace, thisClassName, indent); + } + + /// + /// 一つのメソッド呼び出し結果を出力 + /// + /// 呼び出し元のメソッド定義 + /// セマンティックモデル + /// 呼び出し元クラスの属する名前空間 + /// 呼び出し元クラス名 + /// インデント(省略可能) + private static void WriteCallingMethods(IEnumerable calling, SemanticModel model, string thisNameSpace, string thisClassName, string indent = null) + { + foreach (InvocationExpressionSyntax invoke in calling) + { + string nameSpace = null; + string typeName = null; + string callingMethodName = null; + + try + { + var symbol1 = model.GetSymbolInfo(invoke).Symbol; + + if (symbol1 != null) + { + nameSpace = symbol1.ContainingNamespace.ToString(); + + if (nameSpace == "System") + { + continue; + } + + if (_exceptNamespacesStartsWith.Any(s => nameSpace.StartsWith(s))) + { + continue; + } + + Console.WriteLine((indent ?? string.Empty) + "Specified Syntax MethodCall: " + symbol1.OriginalDefinition); + } + else + { + if (invoke.Expression is IdentifierNameSyntax syn1) + { + var v = model.GetSymbolInfo(syn1).Symbol; + + nameSpace = thisNameSpace; + typeName = thisClassName; + callingMethodName = syn1.ToString(); + } + else if (invoke.Expression is MemberAccessExpressionSyntax syn2) + { + var v = model.GetSymbolInfo(syn2).Symbol; + + nameSpace = model.GetTypeInfo(syn2.Expression).Type.ContainingNamespace.ToString(); + typeName = model.GetTypeInfo(syn2.Expression).Type.Name; + callingMethodName = syn2.Name.ToString(); + } + else + { + // ここに入ってくるパターンがあるかどうか・・・ + } + + List argumentTypesList = new List(); + var symbols = invoke.ArgumentList.Arguments.Select(s => s.Expression); + + foreach (var ar in symbols) + { + if (ar.IsKind(SyntaxKind.NullLiteralExpression)) + { + argumentTypesList.Add("*"); + } + else + { + var info = model.GetTypeInfo(ar); + argumentTypesList.Add(info.ConvertedType?.ToString() ?? "?"); + } + } + + if (nameSpace == "System") + { + continue; + } + + if (_exceptNamespacesStartsWith.Any(s => nameSpace.StartsWith(s))) + { + continue; + } + + string argumentTypes = "(" + string.Join(", ", argumentTypesList) + ")"; + + Console.WriteLine((indent ?? string.Empty) + "Non-specified Syntax MethodCall: " + nameSpace + "." + typeName + "." + + callingMethodName + argumentTypes); + } + } + catch (Exception ex) + { + List errorReport = new List(); + errorReport.Add($"AnalyzingNamespace: {nameSpace}"); + errorReport.Add($"AnalyzingClass: {typeName}"); + errorReport.Add($"AnalyzingMethod: {callingMethodName}"); + + throw new ApplicationException("[" + string.Join(",", errorReport) + "]", ex); + } + } + } + + private static void WriteExceptionLog(Exception ex) + { + DateTime now = DateTime.Now; + + List errorReport = new List(); + errorReport.Add($"出力日付: {now:yyyy/MM/dd}"); + errorReport.Add($"出力時刻: {now:HH:mm:ss}"); + errorReport.Add("詳細情報:"); + + errorReport.Add(ex.ToString()); + + Exception current = ex; + while (current.InnerException != null) + { + current = current.InnerException; + errorReport.Add(current.ToString()); + errorReport.Add(current.Message); + errorReport.Add(current.StackTrace); + } + + Console.Error.WriteLine(string.Join(Environment.NewLine, errorReport)); + + string logOutputPath = Path.Combine(_executingAsmPath, "Exception.log"); + using (StreamWriter writer = new StreamWriter(logOutputPath, true, Encoding.UTF8)) + { + writer.WriteLine("========================================"); + writer.WriteLine(string.Join(Environment.NewLine, errorReport)); + writer.WriteLine(); + } + } + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..21376c0 --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// アセンブリに関する一般情報は以下の属性セットをとおして制御されます。 +// アセンブリに関連付けられている情報を変更するには、 +// これらの属性値を変更してください。 +[assembly: AssemblyTitle("RoslynAnalysisSampleConsole")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("RoslynAnalysisSampleConsole")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// ComVisible を false に設定すると、このアセンブリ内の型は COM コンポーネントから +// 参照できなくなります。COM からこのアセンブリ内の型にアクセスする必要がある場合は、 +// その型の ComVisible 属性を true に設定してください。 +[assembly: ComVisible(false)] + +// このプロジェクトが COM に公開される場合、次の GUID が typelib の ID になります +[assembly: Guid("b6c1ac85-667d-4218-b004-5c45c0e958dc")] + +// アセンブリのバージョン情報は次の 4 つの値で構成されています: +// +// メジャー バージョン +// マイナー バージョン +// ビルド番号 +// Revision +// +// すべての値を指定するか、次を使用してビルド番号とリビジョン番号を既定に設定できます +// 既定値にすることができます: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/RoslynAnalysisSampleConsole.csproj b/RoslynAnalysisSampleConsole.csproj new file mode 100644 index 0000000..3a324fb --- /dev/null +++ b/RoslynAnalysisSampleConsole.csproj @@ -0,0 +1,202 @@ + + + + + + Debug + AnyCPU + {B6C1AC85-667D-4218-B004-5C45C0E958DC} + Exe + RoslynAnalysisSampleConsole + RoslynAnalysisSampleConsole + v4.6.2 + 512 + true + + + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + packages\Microsoft.CodeAnalysis.Common.2.10.0\lib\netstandard1.3\Microsoft.CodeAnalysis.dll + + + packages\Microsoft.CodeAnalysis.CSharp.2.10.0\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.dll + + + packages\Microsoft.CodeAnalysis.CSharp.Scripting.2.10.0\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.Scripting.dll + + + packages\Microsoft.CodeAnalysis.Scripting.Common.2.10.0\lib\netstandard1.3\Microsoft.CodeAnalysis.Scripting.dll + + + + packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll + + + packages\System.Collections.Immutable.1.5.0\lib\netstandard2.0\System.Collections.Immutable.dll + + + + packages\System.Console.4.3.1\lib\net46\System.Console.dll + True + True + + + + packages\System.Diagnostics.FileVersionInfo.4.3.0\lib\net46\System.Diagnostics.FileVersionInfo.dll + True + True + + + packages\System.Diagnostics.StackTrace.4.3.0\lib\net46\System.Diagnostics.StackTrace.dll + True + True + + + packages\System.IO.4.3.0\lib\net462\System.IO.dll + True + True + + + packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + True + True + + + packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll + True + True + + + packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll + True + True + + + + packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll + True + True + + + packages\System.Reflection.Metadata.1.6.0\lib\netstandard2.0\System.Reflection.Metadata.dll + + + packages\System.Runtime.4.3.1\lib\net462\System.Runtime.dll + True + True + + + packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + + packages\System.Runtime.Extensions.4.3.1\lib\net462\System.Runtime.Extensions.dll + True + True + + + packages\System.Runtime.InteropServices.4.3.0\lib\net462\System.Runtime.InteropServices.dll + True + True + + + packages\System.Security.Cryptography.Algorithms.4.3.1\lib\net461\System.Security.Cryptography.Algorithms.dll + True + True + + + packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + True + True + + + packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + True + True + + + packages\System.Security.Cryptography.X509Certificates.4.3.2\lib\net461\System.Security.Cryptography.X509Certificates.dll + True + True + + + packages\System.Text.Encoding.CodePages.4.5.1\lib\net461\System.Text.Encoding.CodePages.dll + + + packages\System.Threading.Tasks.Extensions.4.5.2\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll + + + packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll + True + True + + + packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll + + + + + + + + + packages\System.Xml.ReaderWriter.4.3.1\lib\net46\System.Xml.ReaderWriter.dll + True + True + + + packages\System.Xml.XmlDocument.4.3.0\lib\net46\System.Xml.XmlDocument.dll + True + True + + + packages\System.Xml.XPath.4.3.0\lib\net46\System.Xml.XPath.dll + True + True + + + packages\System.Xml.XPath.XDocument.4.3.0\lib\net46\System.Xml.XPath.XDocument.dll + True + True + + + + + + + + + + + + + + + + + + このプロジェクトは、このコンピューター上にない NuGet パッケージを参照しています。それらのパッケージをダウンロードするには、[NuGet パッケージの復元] を使用します。詳細については、http://go.microsoft.com/fwlink/?LinkID=322105 を参照してください。見つからないファイルは {0} です。 + + + + \ No newline at end of file diff --git a/RoslynAnalysisSampleConsole.sln b/RoslynAnalysisSampleConsole.sln new file mode 100644 index 0000000..588cdc7 --- /dev/null +++ b/RoslynAnalysisSampleConsole.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28010.2050 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RoslynAnalysisSampleConsole", "RoslynAnalysisSampleConsole.csproj", "{B6C1AC85-667D-4218-B004-5C45C0E958DC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B6C1AC85-667D-4218-B004-5C45C0E958DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B6C1AC85-667D-4218-B004-5C45C0E958DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6C1AC85-667D-4218-B004-5C45C0E958DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B6C1AC85-667D-4218-B004-5C45C0E958DC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BE81408E-5F95-4E3E-84D6-D50AEC4936FA} + EndGlobalSection +EndGlobal diff --git a/packages.config b/packages.config new file mode 100644 index 0000000..b40c6c5 --- /dev/null +++ b/packages.config @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file