Skip to content

Commit

Permalink
Add option for specifying extension methods from referenced assemblies
Browse files Browse the repository at this point in the history
  • Loading branch information
maca88 committed Sep 14, 2022
1 parent 1dd3d94 commit 8e1af67
Show file tree
Hide file tree
Showing 11 changed files with 201 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,13 @@ public interface IFluentProjectAsyncExtensionMethodsConfiguration
/// </summary>
/// <param name="projectName">Name of the project where async extension methods are located</param>
/// <param name="fileName">Name of the file which contains the async extension methods</param>
/// <returns></returns>
IFluentProjectAsyncExtensionMethodsConfiguration ProjectFile(string projectName, string fileName);

// TODO
///// <summary>
///// Add an external type that contains async extension methods
///// </summary>
///// <param name="assemblyName">Name of the assembly where async extension methods are located</param>
///// <param name="type">Full name of the type which contains the async extension methods</param>
///// <returns></returns>
//IFluentProjectExtensionMethodsConfiguration ExternalType(string assemblyName, string type);
/// <summary>
/// Add an external type that contains async extension methods
/// </summary>
/// <param name="assemblyName">Name of the assembly where async extension methods are located</param>
/// <param name="fullTypeName">Full name of the type which contains the async extension methods</param>
IFluentProjectAsyncExtensionMethodsConfiguration ExternalType(string assemblyName, string fullTypeName);
}
}
15 changes: 11 additions & 4 deletions Source/AsyncGenerator.Core/Extensions/SymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,23 @@ public static bool IsAsyncCounterpart(this IMethodSymbol syncMethod, ITypeSymbol
syncMethod = syncMethod.ReducedFrom ?? syncMethod;
}

var candidateIndexOffset = 0;
if (candidateAsyncMethod.IsExtensionMethod && !syncMethod.IsExtensionMethod)
{
candidateIndexOffset = 1;
}

if (syncMethod.OverriddenMethod != null && candidateAsyncMethod.EqualTo(syncMethod.OverriddenMethod))
{
return false;
}

var candidateParameterLength = candidateAsyncMethod.Parameters.Length - candidateIndexOffset;
// Check if the length of the parameters matches
if (syncMethod.Parameters.Length != candidateAsyncMethod.Parameters.Length)
if (syncMethod.Parameters.Length != candidateParameterLength)
{
// For symplicity, we suppose that the sync method does not have a cancellation token as a parameter
if (!hasCancellationToken || syncMethod.Parameters.Length + 1 != candidateAsyncMethod.Parameters.Length)
if (!hasCancellationToken || syncMethod.Parameters.Length + 1 != candidateParameterLength)
{
return false;
}
Expand Down Expand Up @@ -93,7 +100,7 @@ public static bool IsAsyncCounterpart(this IMethodSymbol syncMethod, ITypeSymbol
for (var i = 0; i < syncMethod.Parameters.Length; i++)
{
var param = syncMethod.Parameters[i];
var candidateParam = candidateAsyncMethod.Parameters[i];
var candidateParam = candidateAsyncMethod.Parameters[i + candidateIndexOffset];
if (param.IsOptional != candidateParam.IsOptional ||
param.IsParams != candidateParam.IsParams ||
param.RefKind != candidateParam.RefKind)
Expand Down Expand Up @@ -152,7 +159,7 @@ public static bool IsAsyncCounterpart(this IMethodSymbol syncMethod, ITypeSymbol
}
result = true;
}
if (syncMethod.Parameters.Length >= candidateAsyncMethod.Parameters.Length)
if (syncMethod.Parameters.Length >= candidateParameterLength)
{
return result;
}
Expand Down
19 changes: 19 additions & 0 deletions Source/AsyncGenerator.Core/FileConfiguration/FileConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,13 @@ public class AsyncExtensionMethods
[XmlArrayItem("ProjectFile", IsNullable = false)]
public List<ProjectFile> ProjectFiles { get; set; }

[XmlArrayItem("AssemblyType", IsNullable = false)]
public List<AssemblyType> AssemblyTypes { get; set; }

public AsyncExtensionMethods()
{
ProjectFiles = new List<ProjectFile>();
AssemblyTypes = new List<AssemblyType>();
}
}

Expand Down Expand Up @@ -379,6 +383,21 @@ public class ProjectFile
[XmlAttribute(AttributeName = "projectName")]
public string ProjectName { get; set; }
}

[Serializable]
[DebuggerStepThrough]
[DesignerCategory("code")]
[XmlType(Namespace = "https://github.com/maca88/AsyncGenerator")]
[XmlRoot("AssemblyType")]
[EditorBrowsable(EditorBrowsableState.Never)]
public class AssemblyType
{
[XmlAttribute(AttributeName = "assemblyName")]
public string AssemblyName { get; set; }

[XmlAttribute(AttributeName = "fullTypeName")]
public string FullTypeName { get; set; }
}

[Serializable]
[DebuggerStepThrough]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,11 @@ private static void Configure(AsyncExtensionMethods config, IFluentProjectAsyncE
{
fluentConfig.ProjectFile(projectFile.ProjectName, projectFile.FileName);
}

foreach (var assemblyType in config.AssemblyTypes)
{
fluentConfig.ExternalType(assemblyType.AssemblyName, assemblyType.FullTypeName);
}
}

private static void Configure(AsyncGenerator configuration, Diagnostics config, IFluentProjectDiagnosticsConfiguration fluentConfig)
Expand Down
61 changes: 50 additions & 11 deletions Source/AsyncGenerator.Core/Plugins/AsyncExtensionMethodsFinder.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AsyncGenerator.Core.Configuration;
using AsyncGenerator.Core.Extensions;
Expand All @@ -10,6 +9,7 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static AsyncGenerator.Core.AsyncCounterpartsSearchOptions;

namespace AsyncGenerator.Core.Plugins
{
Expand All @@ -19,23 +19,56 @@ public class AsyncExtensionMethodsFinder : IAsyncCounterpartsFinder, IDocumentTr
private ILookup<string, IMethodSymbol> _extensionMethodsLookup;
private readonly string _fileName;
private readonly string _projectName;
private readonly bool _findByReference;

public AsyncExtensionMethodsFinder(string projectName, string fileName)
public AsyncExtensionMethodsFinder(string projectName, string fileName, bool findByReference)
{
_projectName = projectName;
_fileName = fileName;
_findByReference = findByReference;
}

public async Task Initialize(Project project, IProjectConfiguration configuration, Compilation compilation)
{
var extProject = project.Solution.Projects.First(o => o.Name == _projectName);
var doc = extProject.Documents.First(o => o.Name == _fileName);
var rootNode = await doc.GetSyntaxRootAsync().ConfigureAwait(false);
var semanticModel = await doc.GetSemanticModelAsync().ConfigureAwait(false);
_extensionMethods = new HashSet<IMethodSymbol>(rootNode.DescendantNodes()
.OfType<MethodDeclarationSyntax>()
.Where(o => o.Identifier.ValueText.EndsWith("Async"))
.Select(o => semanticModel.GetDeclaredSymbol(o)), SymbolEqualityComparer.Default);
_extensionMethods = new HashSet<IMethodSymbol>(SymbolEqualityComparer.Default);
if (_findByReference)
{
var test = compilation.References
.Select(compilation.GetAssemblyOrModuleSymbol)
.OfType<IAssemblySymbol>()
.FirstOrDefault(o => o.Name == _projectName);
var type = test?.GetTypeByMetadataName(_fileName);
if (type == null)
{
throw new InvalidOperationException($"Type {_fileName} was not found in assembly {_projectName}");
}

foreach (var asyncMethod in type.GetMembers().OfType<IMethodSymbol>()
.Where(o => o.Name.EndsWith("Async") && o.IsExtensionMethod))
{
_extensionMethods.Add(asyncMethod);
}
}
else
{
var extProject = project.Solution.Projects.First(o => o.Name == _projectName);
var docs = extProject.Documents.Where(o => o.Name == _fileName);
foreach (var doc in docs)
{
var rootNode = await doc.GetSyntaxRootAsync().ConfigureAwait(false);
var semanticModel = await doc.GetSemanticModelAsync().ConfigureAwait(false);
var asyncMethods = rootNode.DescendantNodes()
.OfType<MethodDeclarationSyntax>()
.Where(o => o.Identifier.ValueText.EndsWith("Async"))
.Select(o => semanticModel.GetDeclaredSymbol(o))
.Where(o => o?.IsExtensionMethod == true);
foreach (var asyncMethod in asyncMethods)
{
_extensionMethods.Add(asyncMethod);
}
}
}

_extensionMethodsLookup = _extensionMethods.ToLookup(o => o.Name);
}

Expand Down Expand Up @@ -77,10 +110,16 @@ public IEnumerable<IMethodSymbol> FindAsyncCounterparts(IMethodSymbol symbol, IT
var asyncName = symbol.GetAsyncName();
foreach (var asyncCandidate in _extensionMethodsLookup[asyncName])
{
if (!symbol.IsAsyncCounterpart(invokedFromType, asyncCandidate, true, true, false))
if (!symbol.IsAsyncCounterpart(
invokedFromType,
asyncCandidate,
true,
options.HasFlag(HasCancellationToken),
options.HasFlag(IgnoreReturnType)))
{
continue;
}

yield return asyncCandidate;
yield break;
}
Expand Down
22 changes: 22 additions & 0 deletions Source/AsyncGenerator.TestCases/IFileReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Threading.Tasks;

namespace AsyncGenerator.TestCases
{
public class FileResult
{

}

public interface IFileReader
{
FileResult Read(string path);
}

public static class FileReaderExtensions
{
public static Task<FileResult> ReadAsync(this IFileReader reader, string path)
{
return Task.FromResult(new FileResult());
}
}
}
30 changes: 23 additions & 7 deletions Source/AsyncGenerator.Tests/AsyncMethodFinder/Fixture.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using AsyncGenerator.Analyzation;
using System.Threading.Tasks;
using AsyncGenerator.Core;
using AsyncGenerator.Core.Plugins;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using NUnit.Framework;
using AsyncGenerator.Tests.AsyncMethodFinder.Input;

Expand Down Expand Up @@ -161,5 +155,27 @@ public Task TestExtensionMethodsAfterTransformation()
)
);
}

[Test]
public Task TestExternalExtensionMethodsAfterTransformation()
{
return ReadonlyTest(nameof(ExternalExtensionMethods), p => p
.ConfigureAnalyzation(a => a
.MethodConversion(symbol => MethodConversion.Smart)
.AsyncExtensionMethods(o => o
.ExternalType("AsyncGenerator.TestCases", "AsyncGenerator.TestCases.FileReaderExtensions"))
)
.ConfigureTransformation(t => t
.AfterTransformation(result =>
{
AssertValidAnnotations(result);
Assert.AreEqual(1, result.Documents.Count);
var document = result.Documents[0];
Assert.NotNull(document.OriginalModified);
Assert.AreEqual(GetOutputFile(nameof(ExternalExtensionMethods)), document.Transformed.ToFullString());
})
)
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using AsyncGenerator.TestCases;

namespace AsyncGenerator.Tests.AsyncMethodFinder.Input
{
public class ExternalExtensionMethods
{
public void External(IFileReader reader)
{
reader.Read("test");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by AsyncGenerator.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------


using AsyncGenerator.TestCases;

namespace AsyncGenerator.Tests.AsyncMethodFinder.Input
{
using System.Threading.Tasks;
public partial class ExternalExtensionMethods
{
public Task ExternalAsync(IFileReader reader)
{
return reader.ReadAsync("test");
}
}
}
10 changes: 9 additions & 1 deletion Source/AsyncGenerator/AsyncCodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,15 @@ internal static async Task GenerateProject(ProjectData projectData, ILoggerFacto
{
foreach (var fileName in pair.Value)
{
RegisterPlugin(projectData.Configuration, new AsyncExtensionMethodsFinder(pair.Key, fileName));
RegisterPlugin(projectData.Configuration, new AsyncExtensionMethodsFinder(pair.Key, fileName, false));
}
}

foreach (var pair in analyzeConfig.AsyncExtensionMethods.AssemblyTypes)
{
foreach (var fullTypeName in pair.Value)
{
RegisterPlugin(projectData.Configuration, new AsyncExtensionMethodsFinder(pair.Key, fullTypeName, true));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AsyncGenerator.Core.Configuration;

namespace AsyncGenerator.Configuration.Internal
{
internal class ProjectAsyncExtensionMethodsConfiguration : IFluentProjectAsyncExtensionMethodsConfiguration
{
public Dictionary<string, HashSet<string>> ProjectFiles { get; } = new Dictionary<string, HashSet<string>>();

public Dictionary<string, HashSet<string>> AssemblyTypes { get; } = new Dictionary<string, HashSet<string>>();

IFluentProjectAsyncExtensionMethodsConfiguration IFluentProjectAsyncExtensionMethodsConfiguration.ProjectFile(string projectName, string fileName)
{
Expand All @@ -29,5 +28,24 @@ IFluentProjectAsyncExtensionMethodsConfiguration IFluentProjectAsyncExtensionMet
ProjectFiles[projectName].Add(fileName);
return this;
}

IFluentProjectAsyncExtensionMethodsConfiguration IFluentProjectAsyncExtensionMethodsConfiguration.ExternalType(string assemblyName, string fullTypeName)
{
if (assemblyName == null)
{
throw new ArgumentNullException(nameof(assemblyName));
}
if (fullTypeName == null)
{
throw new ArgumentNullException(nameof(fullTypeName));
}

if (!AssemblyTypes.ContainsKey(assemblyName))
{
AssemblyTypes.Add(assemblyName, new HashSet<string>());
}
AssemblyTypes[assemblyName].Add(fullTypeName);
return this;
}
}
}

0 comments on commit 8e1af67

Please sign in to comment.