diff --git a/src/Integration.UnitTests/LocalServices/SolutionWorkspaceServiceTests.cs b/src/Integration.UnitTests/LocalServices/SolutionWorkspaceServiceTests.cs index 2bff374c8..62ea6a7cb 100644 --- a/src/Integration.UnitTests/LocalServices/SolutionWorkspaceServiceTests.cs +++ b/src/Integration.UnitTests/LocalServices/SolutionWorkspaceServiceTests.cs @@ -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 @@ -37,7 +38,7 @@ public void MefCtor_CheckIsExported() MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); + MefTestHelpers.CreateExport()); } [TestMethod] @@ -54,11 +55,7 @@ public void IsSolutionWorkSpace_ShouldBeOppsiteOfFolderWorkSpace(bool isFolderSp var solutionInfoProvider = Substitute.For(); solutionInfoProvider.IsFolderWorkspace().Returns(isFolderSpace); - var serviceProvider = Substitute.For(); - - var threadHandler = new NoOpThreadHandler(); - - var testSubject = new SolutionWorkspaceService(solutionInfoProvider, new TestLogger(), serviceProvider, threadHandler); + var testSubject = new SolutionWorkspaceService(solutionInfoProvider, new TestLogger(), Substitute.For()); var result = testSubject.IsSolutionWorkSpace(); diff --git a/src/Integration/LocalServices/SolutionWorkspaceService.cs b/src/Integration/LocalServices/SolutionWorkspaceService.cs index 96c87ffd9..aa236ac5a 100644 --- a/src/Integration/LocalServices/SolutionWorkspaceService.cs +++ b/src/Integration/LocalServices/SolutionWorkspaceService.cs @@ -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 ListFiles() => + IsSolutionWorkSpace() + ? vsUiServiceOperation.Execute>(GetAllFilesInSolution) + : []; + + [ExcludeFromCodeCoverage] + private IReadOnlyCollection 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 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 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 ListFiles() - { - if (!IsSolutionWorkSpace()) { return Array.Empty(); } + [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(); + return path; + } - return GetAllFilesInSolution(solution); - } + [ExcludeFromCodeCoverage] + private IEnumerable ChildrenOf(IVsHierarchy hierarchy, VSConstants.VSITEMID rootID) + { + var result = new List(); - [ExcludeFromCodeCoverage] - private IReadOnlyCollection GetAllFilesInSolution(IVsSolution solution) + for (var itemID = FirstChild(hierarchy, rootID); + itemID != VSConstants.VSITEMID.Nil; + itemID = NextSibling(hierarchy, itemID)) { - IReadOnlyCollection 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 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 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 ChildrenOf(IVsHierarchy hierarchy, VSConstants.VSITEMID rootID) - { - var result = new List(); - - 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; } }