File: SolutionExplorer\DiagnosticItem\CpsUtilities.cs
Web Access
Project: src\src\VisualStudio\Core\Impl\Microsoft.VisualStudio.LanguageServices.Implementation.csproj (Microsoft.VisualStudio.LanguageServices.Implementation)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
 
namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer;
 
internal static class CpsUtilities
{
    /// <summary>
    /// Given the canonical name of a node representing an analyzer assembly in the
    /// CPS-based project system extracts out the full path to the assembly.
    /// </summary>
    /// <param name="projectDirectoryPath">The full path to the project directory</param>
    /// <param name="analyzerNodeCanonicalName">The canonical name of the analyzer node in the hierarchy</param>
    /// <returns>The full path to the analyzer assembly on disk, or null if <paramref name="analyzerNodeCanonicalName"/>
    /// cannot be parsed.</returns>
    /// <remarks>
    /// The canonical name takes the following form:
    /// 
    ///   [{path to project directory}\]{target framework}\analyzerdependency\{path to assembly}
    ///   
    /// e.g.:
    /// 
    ///   C:\projects\solutions\MyProj\netstandard2.0\analyzerdependency\C:\users\me\.packages\somePackage\lib\someAnalyzer.dll
    ///   
    /// This method exists solely to extract out the "path to assembly" part, i.e.
    /// "C:\users\me\.packages\somePackage\lib\someAnalyzer.dll". We don't need the
    /// other parts.
    /// 
    /// Note that the path to the project directory is optional.
    /// </remarks>
    public static string? ExtractAnalyzerFilePath(string projectDirectoryPath, string analyzerNodeCanonicalName)
    {
        // The canonical name may or may not start with the path to the project's directory.
        if (!projectDirectoryPath.EndsWith("\\"))
        {
            projectDirectoryPath += '\\';
        }
 
        if (analyzerNodeCanonicalName.StartsWith(projectDirectoryPath, StringComparison.OrdinalIgnoreCase))
        {
            // Extract the rest of the string
            analyzerNodeCanonicalName = analyzerNodeCanonicalName[projectDirectoryPath.Length..];
        }
 
        // Find the slash after the target framework
        var backslashIndex = analyzerNodeCanonicalName.IndexOf('\\');
        if (backslashIndex < 0)
        {
            return null;
        }
 
        // If the path does not contain "analyzerdependency\" immediately after the first slash, it
        // is a newer form of the analyzer tree item's file path (VS16.7) which requires no processing.
        //
        // It is theoretically possible that this incorrectly identifies an analyzer assembly
        // defined under "c:\analyzerdependency\..." as data in the old format, however this is very
        // unlikely. The side effect of such a problem is that analyzer's diagnostics would not
        // populate in the tree.
        if (analyzerNodeCanonicalName.IndexOf(@"analyzerdependency\", backslashIndex + 1, @"analyzerdependency\".Length, StringComparison.OrdinalIgnoreCase) != backslashIndex + 1)
        {
            return analyzerNodeCanonicalName;
        }
 
        // Find the slash after "analyzerdependency"
        backslashIndex = analyzerNodeCanonicalName.IndexOf('\\', backslashIndex + 1);
        if (backslashIndex < 0)
        {
            return null;
        }
 
        // The rest of the string is the path.
        return analyzerNodeCanonicalName[(backslashIndex + 1)..];
    }
}