如何在 .NET 代码分析器中获取解决方案路径

How to get solution path in .NET code analyzer

如何访问 Roslyn 代码分析器中正在编译的 project/solution 的文件路径?我需要根据相对于代码存储的一些规范文件来验证代码。不起作用的东西:

SyntaxTreeAnalysisContext.Tree.FilePath
Assembly.GetExecutingAssembly().Location
AppDomain.CurrentDomain.BaseDirectory
Environment.CurrentDirectory
Path.GetFullPath(relativePath)

分析器存在于工作区级别之下(它们 运行 直接由编译器提供),因此解决方案可能不存在。

由于复杂的原因,它们不是由 MEF 创建的,因此即使它确实存在,也没有简单的方法来获取它。

从 VS 中,您可以找到全局服务提供者(例如,ServiceProvider.GlobalProvider),然后获取 SComponentModel(VS 自己的 MEF 图的根)并获取 Roslyn 的 VisualStudioWorkspace从那个。请注意,这是一种有点脆弱的方法,在 VS 之外根本不起作用。

即使在 VS 中,这也会以奇怪的方式中断预览窗格、杂项文件和其他不属于全局解决方案的上下文中的分析。

这是另一种无需 VS 即可工作的方法;也很脆,但出于不同的原因。 :)

通过从当前语法树的源文件路径开始向上搜索文件夹层次结构,在文件级别查找 csproj。

当然在某些情况下它不会工作(如果源文件在 csproj 文件夹的子树之外,例如链接文件;或者周围有其他陈旧的 csproj 文件等)唯一我能想到的安全网是检查找到的 csproj 是否真的与当前 SemanticModel.Compilation.AssemblyName 引用的程序集名称相同,这样我们就不会得到其他随机项目的 csproj。

这是代码,请参阅名为 FindProjectFile 的方法:https://nsdepcop.codeplex.com/SourceControl/changeset/view/75896#VS2015/source/NsDepCop.VisualStudioIntegration/ProjectAnalyzerRepository.cs

不经过反射不可能从分析器或修复器得到解决方案。

使用additional files存储设置。

在项目中:

<ItemGroup>
  <AdditionalFiles Include="MyConfig.config" />
</ItemGroup>

在分析器中:

private const string ConfigFileName = "MyConfig.config";

private static string LoadConfig(ImmutableArray<AdditionalText> additionalFiles, CancellationToken cancellationToken)
{
    var file = additionalFiles.SingleOrDefault(f => string.Compare(Path.GetFileName(f.Path), ConfigFileName, StringComparison.OrdinalIgnoreCase) == 0);
    if (file == null)
    {
        return null;
    }

    var fileText = file.GetText(cancellationToken);

    using (var stream = new MemoryStream())
    {
        using (StreamWriter writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
        {
            fileText.Write(writer, cancellationToken);
        }

        stream.Position = 0;

        using (var reader = new StreamReader(stream))
        {
            return reader.ReadToEnd();
        }
    }
}

private static void HandleCompilationStart(CompilationStartAnalysisContext context)
{
    var config = LoadConfig(context.Options.AdditionalFiles, context.CancellationToken);
}

我想出了一个通过反射来做到这一点的方法,我只在 windows 环境中测试过这个。

public static class RoslynExtensions
{
    public static Solution GetSolution(this SyntaxNodeAnalysisContext context)
    {
        var workspace = context.Options.GetPrivatePropertyValue<object>("Workspace");
        return workspace.GetPrivatePropertyValue<Solution>("CurrentSolution");
    }

    public static T GetPrivatePropertyValue<T>(this object obj, string propName)
    {
        if (obj == null)
        {
            throw new ArgumentNullException(nameof(obj));
        }

        var pi = obj.GetType().GetRuntimeProperty(propName);

        if (pi == null)
        {
            throw new ArgumentOutOfRangeException(nameof(propName), $"Property {propName} was not found in Type {obj.GetType().FullName}");
        }

        return (T)pi.GetValue(obj, null);
    }
}

像这样从分析器调用:

public override void Initialize(AnalysisContext context)
{
    context.RegisterSyntaxNodeAction(AnalyzeConstDeclaration, SyntaxKind.FieldDeclaration);
}

public static void AnalyzeConstDeclaration(SyntaxNodeAnalysisContext context)
{
     var solution = context.GetSolution();
}