diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..1ff0c423 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..3a2238d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,245 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +[Xx]64/ +[Xx]86/ +[Bb]uild/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml + +# TODO: Un-comment the next line if you do not want to checkin +# your web deploy settings because they may include unencrypted +# passwords +#*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Microsoft Azure ApplicationInsights config file +ApplicationInsights.config + +# Windows Store app package directory +AppPackages/ +BundleArtifacts/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# LightSwitch generated files +GeneratedArtifacts/ +ModelManifest.xml + +# Paket dependency manager +.paket/paket.exe + +# FAKE - F# Make +.fake/ \ No newline at end of file diff --git a/Source/AsyncGenerator/AsyncGenerator.Tests/AsyncGenerator.Tests.csproj b/Source/AsyncGenerator/AsyncGenerator.Tests/AsyncGenerator.Tests.csproj new file mode 100644 index 00000000..0c2d9715 --- /dev/null +++ b/Source/AsyncGenerator/AsyncGenerator.Tests/AsyncGenerator.Tests.csproj @@ -0,0 +1,273 @@ + + + + Debug + AnyCPU + {0A3D852A-5C81-4E74-AC16-85BA2EBF3581} + Library + Properties + AsyncGenerator.Tests + AsyncGenerator.Tests + v4.6.1 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\ManagedEsent.1.9.4\lib\net40\Esent.Interop.dll + True + + + ..\packages\Microsoft.CodeAnalysis.Common.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.dll + True + + + ..\packages\Microsoft.CodeAnalysis.CSharp.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.dll + True + + + ..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.Workspaces.dll + True + + + ..\packages\Microsoft.CodeAnalysis.Elfie.0.10.6\lib\net46\Microsoft.CodeAnalysis.Elfie.dll + True + + + ..\packages\Microsoft.CodeAnalysis.Workspaces.Common.2.0.0\lib\net46\Microsoft.CodeAnalysis.Workspaces.dll + True + + + ..\packages\Microsoft.CodeAnalysis.Workspaces.Common.2.0.0\lib\net46\Microsoft.CodeAnalysis.Workspaces.Desktop.dll + True + + + + ..\packages\Microsoft.Web.Xdt.2.1.1\lib\net40\Microsoft.Web.XmlTransform.dll + True + + + ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll + True + + + ..\packages\NuGet.Common.4.0.0\lib\net45\NuGet.Common.dll + True + + + ..\packages\NuGet.ContentModel.4.0.0\lib\net45\NuGet.ContentModel.dll + True + + + ..\packages\NuGet.Core.2.14.0\lib\net40-Client\NuGet.Core.dll + True + + + ..\packages\NuGet.Frameworks.4.0.0\lib\net45\NuGet.Frameworks.dll + True + + + ..\packages\NuGet.Packaging.4.0.0\lib\net45\NuGet.Packaging.dll + True + + + ..\packages\NuGet.Packaging.Core.4.0.0\lib\net45\NuGet.Packaging.Core.dll + True + + + ..\packages\NuGet.Packaging.Core.Types.4.0.0\lib\net45\NuGet.Packaging.Core.Types.dll + True + + + ..\packages\NuGet.Repositories.4.0.0\lib\net45\NuGet.Repositories.dll + True + + + ..\packages\NuGet.RuntimeModel.4.0.0\lib\net45\NuGet.RuntimeModel.dll + True + + + ..\packages\NuGet.Versioning.4.0.0\lib\net45\NuGet.Versioning.dll + True + + + ..\packages\NUnit.3.6.1\lib\net45\nunit.framework.dll + True + + + + ..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll + True + + + ..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + True + + + + ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll + True + + + ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll + True + + + ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll + True + + + ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll + True + + + ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll + True + + + ..\packages\System.Console.4.3.0\lib\net46\System.Console.dll + True + + + ..\packages\System.Diagnostics.FileVersionInfo.4.3.0\lib\net46\System.Diagnostics.FileVersionInfo.dll + True + + + ..\packages\System.Diagnostics.StackTrace.4.3.0\lib\net46\System.Diagnostics.StackTrace.dll + True + + + ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + True + + + ..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll + True + + + ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll + True + + + + ..\packages\System.Reflection.Metadata.1.4.2\lib\portable-net45+win8\System.Reflection.Metadata.dll + True + + + ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll + True + + + ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + True + + + ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + True + + + ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll + True + + + ..\packages\System.Text.Encoding.CodePages.4.3.0\lib\net46\System.Text.Encoding.CodePages.dll + True + + + ..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll + True + + + ..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll + True + + + + + ..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll + True + + + ..\packages\System.Xml.XmlDocument.4.3.0\lib\net46\System.Xml.XmlDocument.dll + True + + + ..\packages\System.Xml.XPath.4.3.0\lib\net46\System.Xml.XPath.dll + True + + + ..\packages\System.Xml.XPath.XDocument.4.3.0\lib\net46\System.Xml.XPath.XDocument.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + + False + + + False + + + False + + + False + + + + + + + + \ No newline at end of file diff --git a/Source/AsyncGenerator/AsyncGenerator.Tests/Properties/AssemblyInfo.cs b/Source/AsyncGenerator/AsyncGenerator.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..d1627383 --- /dev/null +++ b/Source/AsyncGenerator/AsyncGenerator.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AsyncGenerator.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AsyncGenerator.Tests")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("0a3d852a-5c81-4e74-ac16-85ba2ebf3581")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Source/AsyncGenerator/AsyncGenerator.Tests/UnitTest1.cs b/Source/AsyncGenerator/AsyncGenerator.Tests/UnitTest1.cs new file mode 100644 index 00000000..5b8ad875 --- /dev/null +++ b/Source/AsyncGenerator/AsyncGenerator.Tests/UnitTest1.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NuGet; +using NUnit.Framework; + +namespace AsyncGenerator.Tests +{ + public class UnitTest1 + { + [Test] + public void TestMethod1() + { + //ID of the package to be looked up + string packageID = "EntityFramework"; + + //Connect to the official package repository + IPackageRepository repo = PackageRepositoryFactory.Default.CreateRepository("https://packages.nuget.org/api/v2"); + + //Get the list of all NuGet packages with ID 'EntityFramework' + var package = repo.FindPackagesById(packageID) + .First(o => o.IsLatestVersion); + //.First(o => o.Version.ToFullString() == "5.0.0"); + + + //Initialize the package manager + var path = @"C:\Workspace\Git\AsyncGenerator\Source\AsyncGenerator\packages"; + var packageManager = new PackageManager(repo, path); + + //Download and unzip the package + packageManager.InstallPackage(package, false, false); + } + + } +} diff --git a/Source/AsyncGenerator/AsyncGenerator.Tests/app.config b/Source/AsyncGenerator/AsyncGenerator.Tests/app.config new file mode 100644 index 00000000..0f29e48a --- /dev/null +++ b/Source/AsyncGenerator/AsyncGenerator.Tests/app.config @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/AsyncGenerator/AsyncGenerator.Tests/packages.config b/Source/AsyncGenerator/AsyncGenerator.Tests/packages.config new file mode 100644 index 00000000..f61cece1 --- /dev/null +++ b/Source/AsyncGenerator/AsyncGenerator.Tests/packages.config @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/AsyncGenerator/AsyncGenerator.sln b/Source/AsyncGenerator/AsyncGenerator.sln new file mode 100644 index 00000000..68e2bdb1 --- /dev/null +++ b/Source/AsyncGenerator/AsyncGenerator.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsyncGenerator", "AsyncGenerator\AsyncGenerator.csproj", "{9D321EA8-54AE-4741-86A8-12198551AD67}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsyncGenerator.Tests", "AsyncGenerator.Tests\AsyncGenerator.Tests.csproj", "{0A3D852A-5C81-4E74-AC16-85BA2EBF3581}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9D321EA8-54AE-4741-86A8-12198551AD67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D321EA8-54AE-4741-86A8-12198551AD67}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D321EA8-54AE-4741-86A8-12198551AD67}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D321EA8-54AE-4741-86A8-12198551AD67}.Release|Any CPU.Build.0 = Release|Any CPU + {0A3D852A-5C81-4E74-AC16-85BA2EBF3581}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0A3D852A-5C81-4E74-AC16-85BA2EBF3581}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0A3D852A-5C81-4E74-AC16-85BA2EBF3581}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0A3D852A-5C81-4E74-AC16-85BA2EBF3581}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Source/AsyncGenerator/AsyncGenerator/AsyncCodeGenerator.cs b/Source/AsyncGenerator/AsyncGenerator/AsyncCodeGenerator.cs new file mode 100644 index 00000000..75e55dae --- /dev/null +++ b/Source/AsyncGenerator/AsyncGenerator/AsyncCodeGenerator.cs @@ -0,0 +1,439 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using AsyncGenerator.Configuration; +using AsyncGenerator.Internal; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.MSBuild; + +namespace AsyncGenerator +{ + public class MethodData + { + public MethodData(TypeData typeData, IMethodSymbol symbol, MethodDeclarationSyntax node) + { + TypeData = typeData; + Symbol = symbol; + Node = node; + } + + public TypeData TypeData { get; } + + public IMethodSymbol Symbol { get; } + + public MethodDeclarationSyntax Node { get; } + } + + public class TypeData + { + public TypeData(NamespaceData namespaceData, INamedTypeSymbol symbol, TypeDeclarationSyntax node, TypeData parentTypeData = null) + { + NamespaceData = namespaceData; + ParentTypeData = parentTypeData; + Symbol = symbol; + Node = node; + } + + public HashSet References { get; } = new HashSet(); + + public TypeData ParentTypeData { get; } + + public NamespaceData NamespaceData { get; } + + public INamedTypeSymbol Symbol { get; } + + public TypeDeclarationSyntax Node { get; } + + public ConcurrentDictionary MethodData { get; } = new ConcurrentDictionary(); + + public ConcurrentDictionary NestedTypeData { get; } = new ConcurrentDictionary(); + } + + public class NamespaceData + { + public NamespaceData(DocumentData documentData, INamespaceSymbol symbol, NamespaceDeclarationSyntax node) + { + DocumentData = documentData; + Symbol = symbol; + Node = node; + } + + public DocumentData DocumentData { get; } + + public INamespaceSymbol Symbol { get; } + + public NamespaceDeclarationSyntax Node { get; } + + public bool IsGlobal => Node == null; + + public ConcurrentDictionary TypeData { get; } = new ConcurrentDictionary(); + + public TypeData GetTypeData(TypeDeclarationSyntax node, bool create = false) + { + var typeSymbol = DocumentData.SemanticModel.GetDeclaredSymbol(node); + return GetTypeData(typeSymbol, create); + } + + public TypeData GetTypeData(MethodDeclarationSyntax node, bool create = false) + { + var typeSymbol = DocumentData.SemanticModel.GetDeclaredSymbol(node).ContainingType; + return GetTypeData(typeSymbol, create); + } + + public TypeData GetTypeData(IMethodSymbol symbol, bool create = false) + { + return GetTypeData(symbol.ContainingType, create); + } + + public TypeData GetTypeData(INamedTypeSymbol type, bool create = false) + { + var nestedTypes = new Stack(); + while (type != null) + { + nestedTypes.Push(type); + type = type.ContainingType; + } + TypeData currentTypeData = null; + var path = DocumentData.FilePath; + while (nestedTypes.Count > 0) + { + var typeSymbol = nestedTypes.Pop().OriginalDefinition; + var location = typeSymbol.Locations.Single(o => o.SourceTree.FilePath == path); + var namespaceNode = Node ?? (SyntaxNode)DocumentData.RootNode; // Global namespace + var node = namespaceNode.DescendantNodes() + .OfType() + .First(o => o.ChildTokens().First(t => t.IsKind(SyntaxKind.IdentifierToken)).Span == location.SourceSpan); + + var typeDataDict = currentTypeData?.NestedTypeData ?? TypeData; + TypeData typeData; + if (typeDataDict.TryGetValue(node, out typeData)) + { + currentTypeData = typeData; + continue; + } + if (!create) + { + return null; + } + currentTypeData = typeDataDict.GetOrAdd(node, syntax => new TypeData(this, typeSymbol, node, currentTypeData)); + } + return currentTypeData; + } + } + + public class DocumentData + { + public DocumentData(ProjectData projectData, Document document, CompilationUnitSyntax rootNode, SemanticModel semanticModel) + { + ProjectData = projectData; + Document = document; + RootNode = rootNode; + SemanticModel = semanticModel; + GlobalNamespaceData = new NamespaceData(this, SemanticModel.Compilation.GlobalNamespace, null); + } + + public Document Document { get; } + + public string FilePath => Document.FilePath; + + public ProjectData ProjectData { get; } + + public CompilationUnitSyntax RootNode { get; } + + public SemanticModel SemanticModel { get; } + + public NamespaceData GlobalNamespaceData { get; } + + public ConcurrentDictionary NamespaceData { get; } = new ConcurrentDictionary(); + + public TypeData GetOrCreateTypeData(TypeDeclarationSyntax node) + { + return GetNamespaceData(node, true).GetTypeData(node, true); + } + + private NamespaceData GetNamespaceData(SyntaxNode node, bool create = false) + { + if (node == null) + { + return GlobalNamespaceData; + } + var namespaceNode = node.AncestorsAndSelf().OfType().FirstOrDefault(); + if (namespaceNode == null) + { + return GlobalNamespaceData; + } + var namespaceSymbol = (INamespaceSymbol)ModelExtensions.GetDeclaredSymbol(SemanticModel, namespaceNode); + return GetNamespaceData(namespaceNode, namespaceSymbol, create); + } + + private NamespaceData GetNamespaceData(NamespaceDeclarationSyntax namespaceNode, INamespaceSymbol namespaceSymbol, bool create = false) + { + NamespaceData namespaceData; + if (NamespaceData.TryGetValue(namespaceNode, out namespaceData)) + { + return namespaceData; + } + return !create ? null : NamespaceData.GetOrAdd(namespaceNode, syntax => new NamespaceData(this, namespaceSymbol, namespaceNode)); + } + } + + public class ProjectData + { + private readonly SolutionData _solutionData; + + public ProjectData(SolutionData solutionData, ProjectId projectId, ProjectConfiguration configuration) + { + _solutionData = solutionData; + Configuration = configuration; + ProjectId = projectId; + DirectoryPath = Path.GetDirectoryName(Project.FilePath) + @"\"; + } + + internal readonly ProjectConfiguration Configuration; + + public string DirectoryPath { get; } + + public ProjectId ProjectId { get; } + + public Project Project + { + get { return _solutionData.Solution.GetProject(ProjectId); } + set { _solutionData.Solution = value.Solution; } + } + + public ConcurrentDictionary DocumentData { get; } = new ConcurrentDictionary(); + + + public DocumentData GetDocumentData(Document document) + { + DocumentData documentData; + if (!DocumentData.TryGetValue(document.FilePath, out documentData)) + { + throw new InvalidOperationException($"Document {document.FilePath} was not found in the project {Project.Name}"); + } + return documentData; + } + + public async Task CreateDocumentData(Document document) + { + if (document.Project != Project) + { + throw new InvalidOperationException($"Document {document.FilePath} does not belong to project {Project.Name}"); + } + var rootNode = (CompilationUnitSyntax)await document.GetSyntaxRootAsync().ConfigureAwait(false); + var semanticModel = await document.GetSemanticModelAsync().ConfigureAwait(false); + var documentData = new DocumentData(this, document, rootNode, semanticModel); + return DocumentData.AddOrUpdate(document.FilePath, documentData, (s, data) => documentData); + } + + } + + public class SolutionData + { + public SolutionData(Solution solution, MSBuildWorkspace buildWorkspace, SolutionConfiguration configuration) + { + Configuration = configuration; + Workspace = buildWorkspace; + Solution = solution; + } + + public MSBuildWorkspace Workspace { get; } + + public readonly SolutionConfiguration Configuration; + + public Solution Solution { get; set; } + + internal ConcurrentDictionary ProjectData { get; } = new ConcurrentDictionary(); + + } + + public class ProjectAnalyzer + { + private IImmutableSet _analyzeDocuments; + private ProjectAnalyzeConfiguration _configuration; + + public ProjectAnalyzer(ProjectData projectData) + { + ProjectData = projectData; + } + + public ProjectData ProjectData { get; } + + public async Task Analyze() + { + Setup(); + var documentData = await Task.WhenAll(_analyzeDocuments + .Select(o => ProjectData.CreateDocumentData(o))) + .ConfigureAwait(false); + + await Task.WhenAll(documentData + .Select(AnalyzeDocumentData)) + .ConfigureAwait(false); + } + + + public async Task AnalyzeDocumentData(DocumentData documentData) + { + foreach (var typeDeclaration in documentData.RootNode + .DescendantNodes() + .OfType()) + { + var typeData = documentData.GetOrCreateTypeData(typeDeclaration); + if (!_configuration.TypeSelectionPredicate(typeData.Symbol)) + { + continue; + } + var typeTransform = _configuration.TypeConversionFunction(typeData.Symbol); + // If the type have to be defined as a new type then we need to find all references to that type + if (typeTransform == TypeConversion.NewType) + { + await ScanForTypeReferences(typeData).ConfigureAwait(false); + } + } + } + + /// + /// When a type needs to be defined as a new type we need to find all references to them. + /// Reference can point to a variable, field, base type, argument definition + /// + private async Task ScanForTypeReferences(TypeData typeData) + { + // References for ctor of the type and the type itself wont have any locations + var references = await SymbolFinder.FindReferencesAsync(typeData.Symbol, ProjectData.Project.Solution, _analyzeDocuments).ConfigureAwait(false); + foreach (var refLocation in references.SelectMany(o => o.Locations)) + { + var documentData = ProjectData.GetDocumentData(refLocation.Document); + typeData.References.Add(refLocation); + + // we need to find the type where the reference location is + var node = documentData.RootNode.DescendantNodes(descendIntoTrivia: true) + .First( + o => + { + if (o.IsKind(SyntaxKind.GenericName)) + { + return o.ChildTokens().First(t => t.IsKind(SyntaxKind.IdentifierToken)).Span == + refLocation.Location.SourceSpan; + } + return o.Span == refLocation.Location.SourceSpan; + }); + + var methodNode = node.Ancestors().OfType().FirstOrDefault(); + if (methodNode != null) + { + var methodInfo = documentData.GetOrCreateMethodInfo(methodNode, true); + if (methodInfo.TypeReferences.Contains(refLocation)) + { + continue; + } + methodInfo.TypeReferences.Add(refLocation); + } + else + { + var type = node.Ancestors().OfType().FirstOrDefault(); + if (type != null) + { + var refTypeInfo = documentData.GetOrCreateTypeInfo(type); + if (refTypeInfo.TypeReferences.Contains(refLocation)) + { + continue; + } + refTypeInfo.TypeReferences.Add(refLocation); + } + else // can happen when declaring a Name in a using statement + { + var namespaceNode = node.Ancestors().OfType().FirstOrDefault(); + var namespaceInfo = documentData.GetNamespaceInfo(namespaceNode, true); + if (namespaceInfo.TypeReferences.Contains(refLocation)) + { + continue; + } + namespaceInfo.TypeReferences.Add(refLocation); + } + } + } + } + + private void Setup() + { + _configuration = ProjectData.Configuration.AnalyzeConfiguration; + + // Documents that can be analyzed + _analyzeDocuments = ProjectData.Project.Documents.Where(o => _configuration.DocumentSelectionPredicate(o)).ToImmutableHashSet(); + } + } + + + public class AsyncCodeGenerator + { + public async Task GenerateAsync(IAsyncCodeConfiguration configuration) + { + if (configuration == null) + { + throw new ArgumentNullException(nameof(configuration)); + } + var conf = configuration.Build(); + + foreach (var config in conf.SolutionConfigurations) + { + var solutionData = await CreateSolutionData(config).ConfigureAwait(false); + foreach (var projectData in solutionData.ProjectData.Values) + { + await AnalyzeProject(projectData).ConfigureAwait(false); + } + } + + //conf.SolutionConfigurations.First().ProjectConfigurations.First().TransformConfiguration. + } + + private Task AnalyzeProject(ProjectData projectData) + { + var analyzer = new ProjectAnalyzer(projectData); + return analyzer.Analyze(); + } + + + private async Task CreateSolutionData(SolutionConfiguration configuration) + { + var workspace = MSBuildWorkspace.Create(); + var solution = await workspace.OpenSolutionAsync(configuration.Path).ConfigureAwait(false); + var solutionData = new SolutionData(solution, workspace, configuration); + + var projectConfigs = configuration.ProjectConfigurations.ToDictionary(o => o.Name); + foreach (var project in solution.Projects.Where(o => projectConfigs.ContainsKey(o.Name))) + { + var config = projectConfigs[project.Name]; + var projectData = new ProjectData(solutionData, project.Id, config); + RemoveGeneratedDocuments(projectData); + solutionData.ProjectData.AddOrUpdate(project.Id, projectData, (id, data) => projectData); + } + return solutionData; + } + + private void RemoveGeneratedDocuments(ProjectData projectData) + { + var project = projectData.Project; + var asyncFolder = projectData.Configuration.TransformConfiguration.AsyncFolder; + if (string.IsNullOrEmpty(asyncFolder)) + { + return; + } + var asyncProjectFolder = Path.Combine(projectData.DirectoryPath, asyncFolder) + @"\"; + // remove all generated documents + var toRemove = project.Documents.Where(o => o.FilePath.StartsWith(asyncProjectFolder)).Select(doc => doc.Id).ToList(); + foreach (var docId in toRemove) + { + project = project.RemoveDocument(docId); + } + projectData.Project = project; + } + } +} diff --git a/Source/AsyncGenerator/AsyncGenerator/AsyncGenerator.csproj b/Source/AsyncGenerator/AsyncGenerator/AsyncGenerator.csproj new file mode 100644 index 00000000..7e3f5a10 --- /dev/null +++ b/Source/AsyncGenerator/AsyncGenerator/AsyncGenerator.csproj @@ -0,0 +1,213 @@ + + + + + Debug + AnyCPU + {9D321EA8-54AE-4741-86A8-12198551AD67} + Library + Properties + AsyncGenerator + AsyncGenerator + v4.6.1 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\ManagedEsent.1.9.4\lib\net40\Esent.Interop.dll + True + + + ..\packages\Microsoft.CodeAnalysis.Common.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.dll + True + + + ..\packages\Microsoft.CodeAnalysis.CSharp.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.dll + True + + + ..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.Workspaces.dll + True + + + ..\packages\Microsoft.CodeAnalysis.Elfie.0.10.6\lib\net46\Microsoft.CodeAnalysis.Elfie.dll + True + + + ..\packages\Microsoft.CodeAnalysis.Workspaces.Common.2.0.0\lib\net46\Microsoft.CodeAnalysis.Workspaces.dll + True + + + ..\packages\Microsoft.CodeAnalysis.Workspaces.Common.2.0.0\lib\net46\Microsoft.CodeAnalysis.Workspaces.Desktop.dll + True + + + ..\packages\Microsoft.Web.Xdt.2.1.1\lib\net40\Microsoft.Web.XmlTransform.dll + True + + + ..\packages\NuGet.Core.2.14.0\lib\net40-Client\NuGet.Core.dll + True + + + + ..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll + True + + + ..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + True + + + + ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll + True + + + ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll + True + + + ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll + True + + + ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll + True + + + ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll + True + + + ..\packages\System.Console.4.3.0\lib\net46\System.Console.dll + True + + + + ..\packages\System.Diagnostics.FileVersionInfo.4.3.0\lib\net46\System.Diagnostics.FileVersionInfo.dll + True + + + ..\packages\System.Diagnostics.StackTrace.4.3.0\lib\net46\System.Diagnostics.StackTrace.dll + True + + + ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + True + + + ..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll + True + + + ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll + True + + + + ..\packages\System.Reflection.Metadata.1.4.2\lib\portable-net45+win8\System.Reflection.Metadata.dll + True + + + ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll + True + + + ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + True + + + ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + True + + + ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll + True + + + ..\packages\System.Text.Encoding.CodePages.4.3.0\lib\net46\System.Text.Encoding.CodePages.dll + True + + + ..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll + True + + + ..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll + True + + + + + + + + + ..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll + True + + + ..\packages\System.Xml.XmlDocument.4.3.0\lib\net46\System.Xml.XmlDocument.dll + True + + + ..\packages\System.Xml.XPath.4.3.0\lib\net46\System.Xml.XPath.dll + True + + + ..\packages\System.Xml.XPath.XDocument.4.3.0\lib\net46\System.Xml.XPath.XDocument.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/AsyncGenerator/AsyncGenerator/Configuration/AsyncCodeConfiguration.cs b/Source/AsyncGenerator/AsyncGenerator/Configuration/AsyncCodeConfiguration.cs new file mode 100644 index 00000000..8f87292e --- /dev/null +++ b/Source/AsyncGenerator/AsyncGenerator/Configuration/AsyncCodeConfiguration.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace AsyncGenerator.Configuration +{ + public interface ITransformPlugin + { + void Configure(Project project, ProjectTransformConfiguration configuration); + } + + public interface IAnalyzePlugin + { + void Configure(Project project, ProjectAnalyzeConfiguration configuration); + } + + public interface IProjectConfiguration + { + IProjectConfiguration ConfigureAnalyzation(Action action); + + IProjectConfiguration ConfigureTransformation(Action action); + + IProjectConfiguration ConfigureCompilation(string outputPath, Action action); + } + + public class ProjectConfiguration : IProjectConfiguration + { + public ProjectConfiguration(string name) + { + Name = name; + AnalyzeConfiguration = new ProjectAnalyzeConfiguration(); + TransformConfiguration = new ProjectTransformConfiguration(); + } + + public string Name { get; } + + public ProjectAnalyzeConfiguration AnalyzeConfiguration { get; } + + public ProjectTransformConfiguration TransformConfiguration { get; } + + public ProjectCompileConfiguration CompileConfiguration { get; private set; } + + public IProjectConfiguration ConfigureAnalyzation(Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + action(AnalyzeConfiguration); + return this; + } + + public IProjectConfiguration ConfigureTransformation(Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + action(TransformConfiguration); + return this; + } + + public IProjectConfiguration ConfigureCompilation(string outputPath, Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + CompileConfiguration = new ProjectCompileConfiguration(outputPath); + action(CompileConfiguration); + return this; + } + } + + public interface ISolutionConfiguration + { + ISolutionConfiguration ConfigureProject(string projectName, Action action); + + /// + /// Set if changes to projects and documents should be applied at the end of the transformation process + /// + ISolutionConfiguration ApplyChanges(bool value); + } + + public class SolutionConfiguration : ISolutionConfiguration + { + public SolutionConfiguration(string path) + { + Path = path; + } + + public List ProjectConfigurations { get; } = new List(); + + public string Path { get; } + + public bool ApplyChanges { get; private set; } + + public ISolutionConfiguration ConfigureProject(string projectName, Action action) + { + if (projectName == null) + { + throw new ArgumentNullException(nameof(projectName)); + } + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + var projectConfig = new ProjectConfiguration(projectName); + ProjectConfigurations.Add(projectConfig); + action(projectConfig); + return this; + } + + ISolutionConfiguration ISolutionConfiguration.ApplyChanges(bool value) + { + ApplyChanges = value; + return this; + } + } + + + public interface IAsyncCodeConfiguration + { + IAsyncCodeConfiguration ConfigureSolution(string solutionFilePath, Action action); + + AsyncCodeConfiguration Build(); + } + + public class AsyncCodeConfiguration : IAsyncCodeConfiguration + { + public static IAsyncCodeConfiguration Create() + { + return new AsyncCodeConfiguration(); + } + + public List SolutionConfigurations { get; } = new List(); + + public IAsyncCodeConfiguration ConfigureSolution(string solutionFilePath, Action action) + { + if (solutionFilePath == null) + { + throw new ArgumentNullException(nameof(solutionFilePath)); + } + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + if (!File.Exists(solutionFilePath)) + { + throw new FileNotFoundException($"Solution not found. Path:'{solutionFilePath}'"); + } + var solutionConfig = new SolutionConfiguration(solutionFilePath); + SolutionConfigurations.Add(solutionConfig); + action(solutionConfig); + return this; + } + + public AsyncCodeConfiguration Build() + { + return this; + } + } +} diff --git a/Source/AsyncGenerator/AsyncGenerator/Configuration/IProjectAnalyzeConfiguration.cs b/Source/AsyncGenerator/AsyncGenerator/Configuration/IProjectAnalyzeConfiguration.cs new file mode 100644 index 00000000..7ae659e6 --- /dev/null +++ b/Source/AsyncGenerator/AsyncGenerator/Configuration/IProjectAnalyzeConfiguration.cs @@ -0,0 +1,59 @@ +using System; +using Microsoft.CodeAnalysis; + +namespace AsyncGenerator.Configuration +{ + public interface IProjectAnalyzeConfiguration + { + /// + /// Set a function that will decide what type of conversion to apply for a given method + /// + IProjectAnalyzeConfiguration MethodConversionFunction(Func func); + + /// + /// Set a function that will decide what type of conversion to apply for a given type + /// + IProjectAnalyzeConfiguration TypeConversionFunction(Func func); + + /// + /// Set a predicate that will decide if the document will be analyzed + /// + IProjectAnalyzeConfiguration DocumentSelectionPredicate(Predicate predicate); + + /// + /// Set a predicate that will decide if the method will be analyzed + /// + IProjectAnalyzeConfiguration MethodSelectionPredicate(Predicate predicate); + + /// + /// Set a predicate that will decide if the type will be analyzed + /// + IProjectAnalyzeConfiguration TypeSelectionPredicate(Predicate predicate); + + /// + /// Set a predicate that will decide if the method that can be converted to async should be converted + /// + IProjectAnalyzeConfiguration ConvertMethodPredicate(Predicate predicate); + + /// + /// Append a function that will try to find an async counterpart for the given method + /// + IProjectAnalyzeConfiguration AppendFindAsyncCounterpartDelegate(FindAsyncCounterpart func); + + /// + /// Prepend a function that will try to find an async counterpart for the given method + /// + IProjectAnalyzeConfiguration PrependFindAsyncCounterpartDelegate(FindAsyncCounterpart func); + + /// + /// Enable or disable scanning for async counterparts within a method body + /// + IProjectAnalyzeConfiguration ScanMethodBody(bool value); + + /// + /// Enable or disable scanning for missing async counterparts + /// + IProjectAnalyzeConfiguration ScanForMissingAsyncMembers(bool value); + + } +} diff --git a/Source/AsyncGenerator/AsyncGenerator/Configuration/IProjectCompileConfiguration.cs b/Source/AsyncGenerator/AsyncGenerator/Configuration/IProjectCompileConfiguration.cs new file mode 100644 index 00000000..b6607c46 --- /dev/null +++ b/Source/AsyncGenerator/AsyncGenerator/Configuration/IProjectCompileConfiguration.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AsyncGenerator.Configuration +{ + public interface IProjectCompileConfiguration + { + /// + /// Set the path where the symbols will be placed + /// + IProjectCompileConfiguration SymbolsPath(string path); + + /// + /// Set the path where the xml documentation will be placed + /// + IProjectCompileConfiguration XmlDocumentationPath(string path); + } +} diff --git a/Source/AsyncGenerator/AsyncGenerator/Configuration/IProjectTransformConfiguration.cs b/Source/AsyncGenerator/AsyncGenerator/Configuration/IProjectTransformConfiguration.cs new file mode 100644 index 00000000..767dede3 --- /dev/null +++ b/Source/AsyncGenerator/AsyncGenerator/Configuration/IProjectTransformConfiguration.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace AsyncGenerator.Configuration +{ + public interface IProjectTransformConfiguration + { + /// + /// Set the name of the folder where all newly generated files will be stored + /// + IProjectTransformConfiguration AsyncFolder(string folderName); + + /// + /// Set a function that can return a number of namespaces to import in a given document + /// + IProjectTransformConfiguration AdditionalDocumentNamespacesFunction(Func> func); + + /// + /// Add a assembly reference to the project + /// + IProjectTransformConfiguration AddAssemblyReference(string assemblyPath); + + /// + /// Set the parse options of the project + /// + IProjectTransformConfiguration ParseOptions(ParseOptions parseOptions); + + /// + /// Wraps all generated code within the provided directive + /// + IProjectTransformConfiguration DirectiveForGeneratedCode(string directiveName); + + /// + /// Indent all generated code using the provided indentation + /// + IProjectTransformConfiguration IndentationForGeneratedCode(string indentation); + + + } +} diff --git a/Source/AsyncGenerator/AsyncGenerator/Configuration/ProjectAnalyzeConfiguration.cs b/Source/AsyncGenerator/AsyncGenerator/Configuration/ProjectAnalyzeConfiguration.cs new file mode 100644 index 00000000..e32ab6a7 --- /dev/null +++ b/Source/AsyncGenerator/AsyncGenerator/Configuration/ProjectAnalyzeConfiguration.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; + +namespace AsyncGenerator.Configuration +{ + public delegate Task FindAsyncCounterpart(Project project, IMethodSymbol syncMethodSymbol, bool searchInheritedTypes); + + public class ProjectAnalyzeConfiguration : IProjectAnalyzeConfiguration + { + public Func MethodConversionFunction { get; private set; } = m => MethodConversion.None; + + public Func TypeConversionFunction { get; private set; } = m => TypeConversion.Unknown; + + public Predicate DocumentSelectionPredicate { get; private set; } = m => true; + + public Predicate MethodSelectionPredicate { get; private set; } = m => true; + + public Predicate TypeSelectionPredicate { get; private set; } = m => true; + + public Predicate ConvertMethodPredicate { get; private set; } = m => true; + + public List FindAsyncCounterpartDelegates { get; } = new List(); + + public bool ScanMethodBody { get; private set; } + + public bool ScanForMissingAsyncMembers { get; private set; } + + IProjectAnalyzeConfiguration IProjectAnalyzeConfiguration.MethodConversionFunction(Func func) + { + if (func == null) + { + throw new ArgumentNullException(nameof(func)); + } + MethodConversionFunction = func; + return this; + } + + IProjectAnalyzeConfiguration IProjectAnalyzeConfiguration.TypeConversionFunction(Func func) + { + if (func == null) + { + throw new ArgumentNullException(nameof(func)); + } + TypeConversionFunction = func; + return this; + } + + IProjectAnalyzeConfiguration IProjectAnalyzeConfiguration.DocumentSelectionPredicate(Predicate predicate) + { + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + DocumentSelectionPredicate = predicate; + return this; + } + + IProjectAnalyzeConfiguration IProjectAnalyzeConfiguration.MethodSelectionPredicate(Predicate predicate) + { + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + MethodSelectionPredicate = predicate; + return this; + } + + IProjectAnalyzeConfiguration IProjectAnalyzeConfiguration.TypeSelectionPredicate(Predicate predicate) + { + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + TypeSelectionPredicate = predicate; + return this; + } + + IProjectAnalyzeConfiguration IProjectAnalyzeConfiguration.ConvertMethodPredicate(Predicate predicate) + { + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + ConvertMethodPredicate = predicate; + return this; + } + + IProjectAnalyzeConfiguration IProjectAnalyzeConfiguration.AppendFindAsyncCounterpartDelegate(FindAsyncCounterpart func) + { + if (func == null) + { + throw new ArgumentNullException(nameof(func)); + } + FindAsyncCounterpartDelegates.Add(func); + return this; + } + + IProjectAnalyzeConfiguration IProjectAnalyzeConfiguration.PrependFindAsyncCounterpartDelegate(FindAsyncCounterpart func) + { + if (func == null) + { + throw new ArgumentNullException(nameof(func)); + } + FindAsyncCounterpartDelegates.Insert(0, func); + return this; + } + + IProjectAnalyzeConfiguration IProjectAnalyzeConfiguration.ScanMethodBody(bool value) + { + ScanMethodBody = value; + return this; + } + + IProjectAnalyzeConfiguration IProjectAnalyzeConfiguration.ScanForMissingAsyncMembers(bool value) + { + ScanForMissingAsyncMembers = value; + return this; + } + + } +} diff --git a/Source/AsyncGenerator/AsyncGenerator/Configuration/ProjectCompileConfiguration.cs b/Source/AsyncGenerator/AsyncGenerator/Configuration/ProjectCompileConfiguration.cs new file mode 100644 index 00000000..f18e9147 --- /dev/null +++ b/Source/AsyncGenerator/AsyncGenerator/Configuration/ProjectCompileConfiguration.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AsyncGenerator.Configuration +{ + public class ProjectCompileConfiguration : IProjectCompileConfiguration + { + public ProjectCompileConfiguration(string outputPath) + { + OutputPath = outputPath; + } + + public string OutputPath { get; } + + public string SymbolsPath { get; private set; } + + public string XmlDocumentationPath { get; private set; } + + IProjectCompileConfiguration IProjectCompileConfiguration.SymbolsPath(string path) + { + if (path == null) + { + throw new ArgumentNullException(nameof(path)); + } + SymbolsPath = path; + return this; + } + + IProjectCompileConfiguration IProjectCompileConfiguration.XmlDocumentationPath(string path) + { + if (path == null) + { + throw new ArgumentNullException(nameof(path)); + } + XmlDocumentationPath = path; + return this; + } + } +} diff --git a/Source/AsyncGenerator/AsyncGenerator/Configuration/ProjectTransformConfiguration.cs b/Source/AsyncGenerator/AsyncGenerator/Configuration/ProjectTransformConfiguration.cs new file mode 100644 index 00000000..1c5b1c16 --- /dev/null +++ b/Source/AsyncGenerator/AsyncGenerator/Configuration/ProjectTransformConfiguration.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace AsyncGenerator.Configuration +{ + public class ProjectTransformConfiguration : IProjectTransformConfiguration + { + public string AsyncFolder { get; private set; } = "Async"; + + public Func> AdditionalDocumentNamespacesFunction { get; private set; } + + public HashSet AssemblyReferences { get; } = new HashSet(); + + public ParseOptions ParseOptions { get; private set; } + + public string DirectiveForGeneratedCode { get; private set; } + + public string IndentationForGeneratedCode { get; private set; } + + IProjectTransformConfiguration IProjectTransformConfiguration.AsyncFolder(string folderName) + { + if (folderName == null) + { + throw new ArgumentNullException(nameof(folderName)); + } + AsyncFolder = folderName; + return this; + } + + IProjectTransformConfiguration IProjectTransformConfiguration.AdditionalDocumentNamespacesFunction( + Func> func) + { + if (func == null) + { + throw new ArgumentNullException(nameof(func)); + } + AdditionalDocumentNamespacesFunction = func; + return this; + } + + IProjectTransformConfiguration IProjectTransformConfiguration.AddAssemblyReference(string assemblyPath) + { + if (assemblyPath == null) + { + throw new ArgumentNullException(nameof(assemblyPath)); + } + if (!File.Exists(assemblyPath)) + { + throw new FileNotFoundException(assemblyPath); + } + AssemblyReferences.Add(assemblyPath); + return this; + } + + IProjectTransformConfiguration IProjectTransformConfiguration.ParseOptions(ParseOptions parseOptions) + { + if (parseOptions == null) + { + throw new ArgumentNullException(nameof(parseOptions)); + } + ParseOptions = parseOptions; + return this; + } + + IProjectTransformConfiguration IProjectTransformConfiguration.DirectiveForGeneratedCode(string directiveName) + { + if (directiveName == null) + { + throw new ArgumentNullException(nameof(directiveName)); + } + DirectiveForGeneratedCode = directiveName; + return this; + } + + IProjectTransformConfiguration IProjectTransformConfiguration.IndentationForGeneratedCode(string indentation) + { + if (indentation == null) + { + throw new ArgumentNullException(nameof(indentation)); + } + IndentationForGeneratedCode = indentation; + return this; + } + } +} diff --git a/Source/AsyncGenerator/AsyncGenerator/Internal/AsyncLock.cs b/Source/AsyncGenerator/AsyncGenerator/Internal/AsyncLock.cs new file mode 100644 index 00000000..f029832b --- /dev/null +++ b/Source/AsyncGenerator/AsyncGenerator/Internal/AsyncLock.cs @@ -0,0 +1,46 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace AsyncGenerator.Internal +{ + internal class AsyncLock + { + private readonly AsyncSemaphore _semaphore; + private readonly Task _releaser; + + public AsyncLock() + { + _semaphore = new AsyncSemaphore(1); + _releaser = Task.FromResult(new Releaser(this)); + } + + public Task LockAsync() + { + var wait = _semaphore.WaitAsync(); + return wait.IsCompleted + ? _releaser + : wait.ContinueWith( + (_, state) => new Releaser((AsyncLock)state), + this, + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Default); + } + + public struct Releaser : IDisposable + { + private readonly AsyncLock _toRelease; + + internal Releaser(AsyncLock toRelease) + { + _toRelease = toRelease; + } + + public void Dispose() + { + _toRelease?._semaphore.Release(); + } + } + } +} diff --git a/Source/AsyncGenerator/AsyncGenerator/Internal/AsyncSemaphore.cs b/Source/AsyncGenerator/AsyncGenerator/Internal/AsyncSemaphore.cs new file mode 100644 index 00000000..b96c5cbe --- /dev/null +++ b/Source/AsyncGenerator/AsyncGenerator/Internal/AsyncSemaphore.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace AsyncGenerator.Internal +{ + internal class AsyncSemaphore + { + private static readonly Task Completed = Task.FromResult(true); + private readonly Queue> _waiters = new Queue>(); + private int _currentCount; + + public AsyncSemaphore(int initialCount) + { + if (initialCount < 0) + { + throw new ArgumentOutOfRangeException(nameof(initialCount)); + } + _currentCount = initialCount; + } + public Task WaitAsync() + { + lock (_waiters) + { + if (_currentCount > 0) + { + --_currentCount; + return Completed; + } + var waiter = new TaskCompletionSource(); + _waiters.Enqueue(waiter); + return waiter.Task; + } + } + + public void Release() + { + TaskCompletionSource toRelease = null; + lock (_waiters) + { + if (_waiters.Count > 0) + toRelease = _waiters.Dequeue(); + else + ++_currentCount; + } + toRelease?.SetResult(true); + } + } +} diff --git a/Source/AsyncGenerator/AsyncGenerator/MethodConversion.cs b/Source/AsyncGenerator/AsyncGenerator/MethodConversion.cs new file mode 100644 index 00000000..1432b724 --- /dev/null +++ b/Source/AsyncGenerator/AsyncGenerator/MethodConversion.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AsyncGenerator +{ + public enum MethodConversion + { + /// + /// The method wont be changed + /// + None = 0, + /// + /// The method will be converted to async + /// + ToAsync = 1, + /// + /// The method will be converted to async only if there is at least one method within the method body that has an async counterpart + /// + Smart = 3 + } +} diff --git a/Source/AsyncGenerator/AsyncGenerator/Properties/AssemblyInfo.cs b/Source/AsyncGenerator/AsyncGenerator/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..ca3bc98a --- /dev/null +++ b/Source/AsyncGenerator/AsyncGenerator/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AsyncGenerator")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AsyncGenerator")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("9d321ea8-54ae-4741-86a8-12198551ad67")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Source/AsyncGenerator/AsyncGenerator/TypeConversion.cs b/Source/AsyncGenerator/AsyncGenerator/TypeConversion.cs new file mode 100644 index 00000000..c8b5adf8 --- /dev/null +++ b/Source/AsyncGenerator/AsyncGenerator/TypeConversion.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AsyncGenerator +{ + public enum TypeConversion + { + /// + /// The type conversion will be decided by the analyzer + /// + Unknown = 0, + /// + /// A partial type will be created that will contains the async counterparts + /// + Partial = 1, + /// + /// A new type will be created with an Async postfix that will contains the async counterparts + /// + NewType = 2 + } +} diff --git a/Source/AsyncGenerator/AsyncGenerator/app.config b/Source/AsyncGenerator/AsyncGenerator/app.config new file mode 100644 index 00000000..0f29e48a --- /dev/null +++ b/Source/AsyncGenerator/AsyncGenerator/app.config @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/AsyncGenerator/AsyncGenerator/packages.config b/Source/AsyncGenerator/AsyncGenerator/packages.config new file mode 100644 index 00000000..ed89b6ff --- /dev/null +++ b/Source/AsyncGenerator/AsyncGenerator/packages.config @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file