通过 Roslyn 检查 Automapper 使用情况

Checking Automapper usage via Roslyn

我有一个定义如下的通用方法,它包装了对 AutoMapper 的调用。

public TOut CreateRequest<TOut, TModelIn>(TModelIn data)
{
  ...
  return Mapper.Map<TModelIn, TOut>();
}

如果我调用 Mapper.AssertConfigurationIsValid,那么我可以检查我的地图是否设置正确,但是我无法检查是否有人在没有定义地图时添加了一行代码来尝试创建地图。

所以我希望能够扫描我的程序集并找到对上述方法的所有调用,提取正在使用的泛型类型,然后将这些类型连接到 Mapper.Map<Type1, Type2>(); 调用中。然后我可以调用 Mapper.AssertConfigurationIsValid 方法,并确保我代码中的所有 Maps 确实已映射并且有效。

我的想法是将其添加到单元测试中,这样我就可以在让用户去测试它以查看会发生什么之前确定映射。

[更新] 我一直在考虑在我的单元测试中使用 Roslyn 来做到这一点。有谁知道如何通过 Roslyn(直接调用和参数列表)找到方法调用?

我设法通过 Rosyln 实现了这一点,但这并不是一件容易的事。 我最终将所有文档加载到我的解决方案中,在它们中搜索 MemberAccessExpressionSyntax 类型,然后提取带有 GenericNameSyntax 且标识符为 CreateRequest.

的那些

然后我可以从 TypeListArgument 中获取每个参数,我知道其中可能有 1、2 或 3。我只想要带有 3 的实例,因此可以将它们读为 IdentifixNameSyntax 对象,并使用 Identifier 为我提供 AutoMapper 地图所需的 class 名称。

然后我不得不使用反射从程序集内查找 类 或枚举的名称,以便给我可以传递给 AutoMapperType

测试设置代码:

var slnPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "MySolution.sln"));
var workspace = MSBuildWorkspace.Create();
_solution = workspace.OpenSolutionAsync(slnPath).Result;
_project = _solution.Projects.First(p => p.Name == "MyProject");

foreach (var documentId in _project.DocumentIds) {
    var document = _solution.GetDocument(documentId);
    if (document.SupportsSyntaxTree) {
        _documents.Add(document);
    }
}

主要代码:

foreach (var document in _documents) {
    var methods = document.GetSyntaxRootAsync().Result.DescendantNodes().OfType<MemberAccessExpressionSyntax>();

    foreach (var m in methods.Where(x => x.Name is GenericNameSyntax)) {
        var genSyntax = m.Name as GenericNameSyntax;
        if (genSyntax?.Identifier.Text == "CreateRequest") {
            var args = genSyntax.TypeArgumentList.Arguments;

            if (args.Count == 3) {
                var item1 = args[0] as IdentifierNameSyntax;
                var item2 = args[1] as IdentifierNameSyntax;

                if (item1 != null && item2 != null) {

                    var c1 = ReflectionTestHelper.GetClassesWithKeyword(item1.Identifier.Text).SingleOrDefault(x => x.Name == item1.Identifier.Text)
                        ?? ReflectionTestHelper.GetEnumsWithKeyword(item1.Identifier.Text).SingleOrDefault(x => x.Name == item1.Identifier.Text);

                    var c2 = ReflectionTestHelper.GetClassesWithKeyword(item2.Identifier.Text).SingleOrDefault(x => x.Name == item2.Identifier.Text)
                        ?? ReflectionTestHelper.GetEnumsWithKeyword(item2.Identifier.Text).SingleOrDefault(x => x.Name == item2.Identifier.Text);

                    if (c1 == null)
                        errors.Add("Unable to find Class for mapping :: " + item1.Identifier.Text);

                    if (c2 == null)
                        errors.Add("Unable to find Class for mapping :: " + item2.Identifier.Text);

                    if (c1 != null && c2 != null) {

                        var map = Mapper.Configuration.FindTypeMapFor(c1, c2);

                        if (map == null) {

                            var location = genSyntax.GetLocation().GetMappedLineSpan();
                            var line = location.Span.Start.Line + 1;

                            var errormessage = new StringBuilder();
                            errormessage.AppendLine("No AutoMapper map found for :: " + item1.Identifier.Text + " -> " + item2.Identifier.Text);
                            errormessage.AppendLine("\tLocation: " + document.FilePath + "[Line:" + line + "]");
                            errormessage.AppendLine("\tMethod: " + genSyntax.Parent);
                            errors.Add(errormessage.ToString());
                        }
                    }
                }
            }
        }
    }
}

就像我说的,不是那么好,但它完成了工作。