Skip to content

Commit

Permalink
SLVS-1653 Use IVsUIServiceOperation in SolutionWorkspaceService
Browse files Browse the repository at this point in the history
  • Loading branch information
georgii-borovinskikh-sonarsource committed Jan 7, 2025
1 parent 5b48c85 commit fa552cd
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 137 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NSubstitute;
using SonarLint.VisualStudio.Core;
using SonarLint.VisualStudio.Infrastructure.VS;
using SonarLint.VisualStudio.TestInfrastructure;

namespace SonarLint.VisualStudio.Integration.UnitTests.LocalServices
Expand All @@ -37,7 +38,7 @@ public void MefCtor_CheckIsExported()
MefTestHelpers.CheckTypeCanBeImported<SolutionWorkspaceService, ISolutionWorkspaceService>(
MefTestHelpers.CreateExport<ISolutionInfoProvider>(),
MefTestHelpers.CreateExport<ILogger>(),
MefTestHelpers.CreateExport<SVsServiceProvider>());
MefTestHelpers.CreateExport<IVsUIServiceOperation>());
}

[TestMethod]
Expand All @@ -54,11 +55,7 @@ public void IsSolutionWorkSpace_ShouldBeOppsiteOfFolderWorkSpace(bool isFolderSp
var solutionInfoProvider = Substitute.For<ISolutionInfoProvider>();
solutionInfoProvider.IsFolderWorkspace().Returns(isFolderSpace);

var serviceProvider = Substitute.For<IServiceProvider>();

var threadHandler = new NoOpThreadHandler();

var testSubject = new SolutionWorkspaceService(solutionInfoProvider, new TestLogger(), serviceProvider, threadHandler);
var testSubject = new SolutionWorkspaceService(solutionInfoProvider, new TestLogger(), Substitute.For<IVsUIServiceOperation>());

var result = testSubject.IsSolutionWorkSpace();

Expand Down
232 changes: 101 additions & 131 deletions src/Integration/LocalServices/SolutionWorkspaceService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,172 +18,142 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using SonarLint.VisualStudio.Core;
using SonarLint.VisualStudio.Infrastructure.VS;

namespace SonarLint.VisualStudio.Integration
namespace SonarLint.VisualStudio.Integration;

[Export(typeof(ISolutionWorkspaceService))]
[PartCreationPolicy(CreationPolicy.Shared)]
[method: ImportingConstructor]
public class SolutionWorkspaceService(ISolutionInfoProvider solutionInfoProvider, ILogger log, IVsUIServiceOperation vsUiServiceOperation)
: ISolutionWorkspaceService
{
[Export(typeof(ISolutionWorkspaceService))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class SolutionWorkspaceService : ISolutionWorkspaceService
public bool IsSolutionWorkSpace() => !solutionInfoProvider.IsFolderWorkspace();

[ExcludeFromCodeCoverage]
public IReadOnlyCollection<string> ListFiles() =>
IsSolutionWorkSpace()
? vsUiServiceOperation.Execute<SVsSolution, IVsSolution, IReadOnlyCollection<string>>(GetAllFilesInSolution)
: [];

[ExcludeFromCodeCoverage]
private IReadOnlyCollection<string> GetAllFilesInSolution(IVsSolution solution) =>
GetLoadedProjects(solution)
.SelectMany(AllItemsInProject)
.Where(x => x != null)
.Where(x => x.Contains("\\"))
.Where(x => !x.EndsWith("\\"))
.Where(x => !x.Contains("\\.nuget\\"))
.Where(x => !x.Contains("\\node_modules\\"))
.ToHashSet(StringComparer.InvariantCultureIgnoreCase); // move filtering closer to path extraction to avoid processing unnecessary items)

[ExcludeFromCodeCoverage]
private IEnumerable<IVsProject> GetLoadedProjects(IVsSolution solution)
{
private readonly ISolutionInfoProvider solutionInfoProvider;
private readonly ILogger log;
private readonly IServiceProvider serviceProvider;
private readonly IThreadHandling threadHandling;

[ImportingConstructor]
public SolutionWorkspaceService(ISolutionInfoProvider solutionInfoProvider, ILogger log, [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider)
: this(solutionInfoProvider, log, serviceProvider, ThreadHandling.Instance) { }
var guid = Guid.Empty;
solution.GetProjectEnum((uint)__VSENUMPROJFLAGS.EPF_LOADEDINSOLUTION, ref guid, out var enumerator);
var hierarchy = new IVsHierarchy[1] { null };
for (enumerator.Reset();
enumerator.Next(1, hierarchy, out var fetched) == VSConstants.S_OK && fetched == 1; /*nothing*/)
{
yield return (IVsProject)hierarchy[0];
}
}

internal SolutionWorkspaceService(ISolutionInfoProvider solutionInfoProvider, ILogger log, IServiceProvider serviceProvider, IThreadHandling threadHandling)
[ExcludeFromCodeCoverage]
private IEnumerable<string> AllItemsInProject(IVsProject project)
{
if (project is null)
{
this.solutionInfoProvider = solutionInfoProvider;
this.log = log;
this.serviceProvider = serviceProvider;
this.threadHandling = threadHandling;
throw new ArgumentNullException(nameof(project));
}

public bool IsSolutionWorkSpace() => !solutionInfoProvider.IsFolderWorkspace();
var projectDir = Path.GetDirectoryName(GetProjectFilePath(project));
var hierarchy = project as IVsHierarchy;

return
ChildrenOf(hierarchy, VSConstants.VSITEMID.Root)
.Select(
id =>
{
project.GetMkDocument((uint)id, out var name);
if (name != null && projectDir != null && !name.StartsWith(projectDir))
{// sometimes random sdk files are included as parts of project items
return null;
}
if (name != null && name.Length > 0 && !Path.IsPathRooted(name))
{
name = Path.Combine(projectDir, name);
}
return name;
});
}

[ExcludeFromCodeCoverage]
public IReadOnlyCollection<string> ListFiles()
{
if (!IsSolutionWorkSpace()) { return Array.Empty<string>(); }
[ExcludeFromCodeCoverage]
private string GetProjectFilePath(IVsProject project)
{
var path = string.Empty;
var hr = project.GetMkDocument((uint)VSConstants.VSITEMID.Root, out path);
Debug.Assert(hr == VSConstants.S_OK || hr == VSConstants.E_NOTIMPL, "GetMkDocument failed for project.");

IVsSolution solution = this.serviceProvider.GetService<SVsSolution, IVsSolution>();
return path;
}

return GetAllFilesInSolution(solution);
}
[ExcludeFromCodeCoverage]
private IEnumerable<VSConstants.VSITEMID> ChildrenOf(IVsHierarchy hierarchy, VSConstants.VSITEMID rootID)
{
var result = new List<VSConstants.VSITEMID>();

[ExcludeFromCodeCoverage]
private IReadOnlyCollection<string> GetAllFilesInSolution(IVsSolution solution)
for (var itemID = FirstChild(hierarchy, rootID);
itemID != VSConstants.VSITEMID.Nil;
itemID = NextSibling(hierarchy, itemID))
{
IReadOnlyCollection<string> result = null;
threadHandling.RunOnUIThread(() => result = GetLoadedProjects(solution)
.SelectMany(AllItemsInProject)
.Where(x => x != null)
.Where(x => x.Contains("\\"))
.Where(x => !x.EndsWith("\\"))
.Where(x => !x.Contains("\\.nuget\\"))
.Where(x => !x.Contains("\\node_modules\\"))
.ToHashSet(StringComparer.InvariantCultureIgnoreCase)); // move filtering closer to path extraction to avoid processing unnecessary items)

return result;
result.Add(itemID);
result.AddRange(ChildrenOf(hierarchy, itemID));
}

[ExcludeFromCodeCoverage]
private IEnumerable<IVsProject> GetLoadedProjects(IVsSolution solution)
{
var guid = Guid.Empty;
solution.GetProjectEnum((uint)__VSENUMPROJFLAGS.EPF_LOADEDINSOLUTION, ref guid, out var enumerator);
var hierarchy = new IVsHierarchy[1] { null };
for (enumerator.Reset();
enumerator.Next(1, hierarchy, out var fetched) == VSConstants.S_OK && fetched == 1; /*nothing*/)
{
yield return (IVsProject)hierarchy[0];
}
}
return result;
}

[ExcludeFromCodeCoverage]
private IEnumerable<string> AllItemsInProject(IVsProject project)
[ExcludeFromCodeCoverage]
private VSConstants.VSITEMID FirstChild(IVsHierarchy hierarchy, VSConstants.VSITEMID rootID)
{
try
{
if (project is null)
if (hierarchy.GetProperty((uint)rootID, (int)__VSHPROPID.VSHPROPID_FirstChild, out var childIDObj) == VSConstants.S_OK && childIDObj != null)
{
throw new ArgumentNullException(nameof(project));
return (VSConstants.VSITEMID)(int)childIDObj;
}

var projectDir = Path.GetDirectoryName(GetProjectFilePath(project));
var hierarchy = project as IVsHierarchy;

return
ChildrenOf(hierarchy, VSConstants.VSITEMID.Root)
.Select(
id =>
{
project.GetMkDocument((uint)id, out var name);
if (name != null && projectDir != null && !name.StartsWith(projectDir))
{// sometimes random sdk files are included as parts of project items
return null;
}
if (name != null && name.Length > 0 && !Path.IsPathRooted(name))
{
name = Path.Combine(projectDir, name);
}
return name;
});
}

[ExcludeFromCodeCoverage]
private string GetProjectFilePath(IVsProject project)
catch (Exception e)
{
var path = string.Empty;
var hr = project.GetMkDocument((uint)VSConstants.VSITEMID.Root, out path);
Debug.Assert(hr == VSConstants.S_OK || hr == VSConstants.E_NOTIMPL, "GetMkDocument failed for project.");

return path;
log.LogVerbose(e.ToString());
}

[ExcludeFromCodeCoverage]
private IEnumerable<VSConstants.VSITEMID> ChildrenOf(IVsHierarchy hierarchy, VSConstants.VSITEMID rootID)
{
var result = new List<VSConstants.VSITEMID>();

for (var itemID = FirstChild(hierarchy, rootID);
itemID != VSConstants.VSITEMID.Nil;
itemID = NextSibling(hierarchy, itemID))
{
result.Add(itemID);
result.AddRange(ChildrenOf(hierarchy, itemID));
}

return result;
}
return VSConstants.VSITEMID.Nil;
}

[ExcludeFromCodeCoverage]
private VSConstants.VSITEMID FirstChild(IVsHierarchy hierarchy, VSConstants.VSITEMID rootID)
[ExcludeFromCodeCoverage]
private VSConstants.VSITEMID NextSibling(IVsHierarchy hierarchy, VSConstants.VSITEMID firstID)
{
try
{
try
if (hierarchy.GetProperty((uint)firstID, (int)__VSHPROPID.VSHPROPID_NextSibling, out var siblingIDObj) == VSConstants.S_OK && siblingIDObj != null)
{
if (hierarchy.GetProperty((uint)rootID, (int)__VSHPROPID.VSHPROPID_FirstChild, out var childIDObj) == VSConstants.S_OK && childIDObj != null)
{
return (VSConstants.VSITEMID)(int)childIDObj;
}
return (VSConstants.VSITEMID)(int)siblingIDObj;
}
catch (Exception e)
{
log.LogVerbose(e.ToString());
}

return VSConstants.VSITEMID.Nil;
}

[ExcludeFromCodeCoverage]
private VSConstants.VSITEMID NextSibling(IVsHierarchy hierarchy, VSConstants.VSITEMID firstID)
catch (Exception e)
{
try
{
if (hierarchy.GetProperty((uint)firstID, (int)__VSHPROPID.VSHPROPID_NextSibling, out var siblingIDObj) == VSConstants.S_OK && siblingIDObj != null)
{
return (VSConstants.VSITEMID)(int)siblingIDObj;
}
}
catch (Exception e)
{
log.LogVerbose(e.ToString());
}

return VSConstants.VSITEMID.Nil;
log.LogVerbose(e.ToString());
}

return VSConstants.VSITEMID.Nil;
}
}

0 comments on commit fa552cd

Please sign in to comment.