From e021a2c723fbe3c25d4fe353dd272f668887302b Mon Sep 17 00:00:00 2001 From: Matt Parker <61717342+MattParkerDev@users.noreply.github.com> Date: Tue, 18 Nov 2025 23:16:13 +1000 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Support=20Multi-TFM=20Projects=20(#?= =?UTF-8?q?13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Features/Analysis/RoslynAnalysis.cs | 36 +++++++++++++++++-- .../VsPersistence/IntermediateMapper.cs | 6 ++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs b/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs index bd6dfe5..9c72cd9 100644 --- a/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs +++ b/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs @@ -23,6 +23,7 @@ using Microsoft.CodeAnalysis.Rename; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.Extensions.Logging; +using NuGet.Frameworks; using Roslyn.LanguageServer.Protocol; using SharpIDE.Application.Features.Analysis.FixLoaders; using SharpIDE.Application.Features.Analysis.Razor; @@ -233,6 +234,7 @@ public class RoslynAnalysis(ILogger logger, BuildService buildSe // The ProjectIds will not match however, so we need to match on FilePath // Since the ProjectIds don't match, we also need to remap all ProjectReferences to the existing ProjectIds // same for documents + // TODO: Handle multiple TFMs - loadedProjectInfos would contain multiple projects with the same FilePath var projectInfosToUpdateWith = loadedProjectInfos.Select(loadedProjectInfo => { var existingProject = _workspace.CurrentSolution.Projects.Single(p => p.FilePath == loadedProjectInfo.FilePath); @@ -1008,7 +1010,37 @@ public class RoslynAnalysis(ILogger logger, BuildService buildSe private static Project GetProjectForSharpIdeProjectModel(SharpIdeProjectModel projectModel) { - var project = _workspace!.CurrentSolution.Projects.Single(s => s.FilePath == projectModel.FilePath); - return project; + var projectsForProjectPath = _workspace!.CurrentSolution.Projects.Where(s => s.FilePath == projectModel.FilePath).ToList(); + if (projectsForProjectPath.Count is 0) throw new InvalidOperationException($"No project found in workspace for project path '{projectModel.FilePath}'"); + if (projectsForProjectPath.Count is 1) + { + return projectsForProjectPath[0]; + } + + // Multiple projects with same path, different TFMs + var projectAndFrameworkList = projectsForProjectPath + .Select(s => + { + var flavor = s.State.NameAndFlavor.flavor!; + var framework = NuGetFramework.Parse(flavor); + return (Project: s, Framework: framework); + }) + .Where(s => s.Framework.IsDesktop() is false) // Exclude .NET Framework projects + .ToList(); + + if (projectAndFrameworkList.Any(s => s.Framework.Framework == FrameworkConstants.FrameworkIdentifiers.NetCoreApp)) // .NET Core project // I would prefer to use Framework.IsNet5Era + { + // remove .net standard projects + projectAndFrameworkList = projectAndFrameworkList + .Where(s => s.Framework.Framework == FrameworkConstants.FrameworkIdentifiers.NetCoreApp) + .ToList(); + } + + var selectedProject = projectAndFrameworkList + .OrderByDescending(s => s.Framework, NuGetFrameworkSorter.Instance) + .Select(s => s.Project) + .First(); + + return selectedProject; } } diff --git a/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/IntermediateMapper.cs b/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/IntermediateMapper.cs index c93cef8..cb7ffee 100644 --- a/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/IntermediateMapper.cs +++ b/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/IntermediateMapper.cs @@ -14,6 +14,12 @@ public static class IntermediateMapper var serializer = SolutionSerializers.GetSerializerByMoniker(solutionFilePath); Guard.Against.Null(serializer, nameof(serializer)); var vsSolution = await serializer.OpenAsync(solutionFilePath, cancellationToken); + + // Remove any projects that aren't csproj, TODO: Instead of removing, display in the solution explorer that the project type isn't supported + foreach (var vsSolutionSolutionProject in vsSolution.SolutionProjects.Where(s => s.Extension is not ".csproj").ToList()) + { + vsSolution.RemoveProject(vsSolutionSolutionProject); + } var rootFolders = vsSolution.SolutionFolders .Where(f => f.Parent is null)