diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/WorkspaceProjectFactoryServiceTests.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/WorkspaceProjectFactoryServiceTests.cs index e575d0879e230..2b5aac043fcc2 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/WorkspaceProjectFactoryServiceTests.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/WorkspaceProjectFactoryServiceTests.cs @@ -4,7 +4,6 @@ using Microsoft.CodeAnalysis.LanguageServer.BrokeredServices; using Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; -using Microsoft.CodeAnalysis.LanguageServer.Logging; using Microsoft.CodeAnalysis.Remote.ProjectSystem; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.Shell.ServiceBroker; @@ -37,14 +36,20 @@ public async Task CreateProjectAndBatch() var sourceFilePath = MakeAbsolutePath("SourceFile.cs"); var additionalFilePath = MakeAbsolutePath("AdditionalFile.txt"); - await workspaceProject.AddSourceFilesAsync(new[] { new SourceFileInfo(sourceFilePath, Array.Empty()) }, CancellationToken.None); - await workspaceProject.AddAdditionalFilesAsync(new[] { additionalFilePath }, CancellationToken.None); + await workspaceProject.AddSourceFilesAsync([new SourceFileInfo(sourceFilePath, ["Folder"])], CancellationToken.None); + await workspaceProject.AddAdditionalFilesAsync([new SourceFileInfo(additionalFilePath, FolderNames: ["Folder"])], CancellationToken.None); await batch.ApplyAsync(CancellationToken.None); // Verify it actually did something; we won't exclusively test each method since those are tested at lower layers var project = workspaceFactory.Workspace.CurrentSolution.Projects.Single(); - Assert.Equal(sourceFilePath, project.Documents.Single().FilePath); - Assert.Equal(additionalFilePath, project.AdditionalDocuments.Single().FilePath); + + var document = Assert.Single(project.Documents); + Assert.Equal(sourceFilePath, document.FilePath); + Assert.Equal("Folder", Assert.Single(document.Folders)); + + var additionalDocument = Assert.Single(project.AdditionalDocuments); + Assert.Equal(additionalFilePath, additionalDocument.FilePath); + Assert.Equal("Folder", Assert.Single(additionalDocument.Folders)); } private static string MakeAbsolutePath(string relativePath) diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LoadedProject.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LoadedProject.cs index cabc2672e68fb..c2c9db2e26269 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LoadedProject.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LoadedProject.cs @@ -3,10 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; -using System.Linq; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageServer.Handler.DebugConfiguration; -using Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.ProjectTelemetry; using Microsoft.CodeAnalysis.MSBuild; using Microsoft.CodeAnalysis.ProjectSystem; using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; @@ -163,7 +161,7 @@ public void Dispose() newProjectInfo.AdditionalDocuments, _mostRecentFileInfo?.AdditionalDocuments, DocumentFileInfoComparer.Instance, - document => _projectSystemProject.AddAdditionalFile(document.FilePath), + document => _projectSystemProject.AddAdditionalFile(document.FilePath, folders: document.Folders), document => _projectSystemProject.RemoveAdditionalFile(document.FilePath), "Project {0} now has {1} additional file(s)."); diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProject.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProject.cs index 5367e67e18719..ba1da2fa0461d 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProject.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProject.cs @@ -24,6 +24,7 @@ public WorkspaceProject(ProjectSystemProject project, SolutionServices solutionS _targetFrameworkManager = targetFrameworkManager; } + [Obsolete($"Call the {nameof(AddAdditionalFilesAsync)} overload that takes {nameof(SourceFileInfo)}.")] public async Task AddAdditionalFilesAsync(IReadOnlyList additionalFilePaths, CancellationToken _) { await using var batchScope = _project.CreateBatchScope(); @@ -32,6 +33,14 @@ public async Task AddAdditionalFilesAsync(IReadOnlyList additionalFilePa _project.AddAdditionalFile(additionalFilePath); } + public async Task AddAdditionalFilesAsync(IReadOnlyList additionalFiles, CancellationToken cancellationToken) + { + await using var batchScope = _project.CreateBatchScope(); + + foreach (var additionalFile in additionalFiles) + _project.AddAdditionalFile(additionalFile.FilePath, folders: additionalFile.FolderNames.ToImmutableArray()); + } + public async Task AddAnalyzerConfigFilesAsync(IReadOnlyList analyzerConfigPaths, CancellationToken _) { await using var batchScope = _project.CreateBatchScope(); diff --git a/src/Tools/ExternalAccess/FSharp/VS/IFSharpWorkspaceProjectContextFactory.cs b/src/Tools/ExternalAccess/FSharp/VS/IFSharpWorkspaceProjectContextFactory.cs index f7042f933c403..67c5294f273c8 100644 --- a/src/Tools/ExternalAccess/FSharp/VS/IFSharpWorkspaceProjectContextFactory.cs +++ b/src/Tools/ExternalAccess/FSharp/VS/IFSharpWorkspaceProjectContextFactory.cs @@ -126,7 +126,7 @@ public string DisplayName public string BinOutputPath { - get => _vsProjectContext.BinOutputPath; + get => _vsProjectContext.BinOutputPath!; set => _vsProjectContext.BinOutputPath = value; } @@ -134,7 +134,7 @@ public ProjectId Id => _vsProjectContext.Id; public string FilePath - => _vsProjectContext.ProjectFilePath; + => _vsProjectContext.ProjectFilePath!; public int ProjectReferenceCount => _projectReferences.Count; diff --git a/src/VisualStudio/Core/Def/ProjectSystem/BrokeredService/WorkspaceProject.cs b/src/VisualStudio/Core/Def/ProjectSystem/BrokeredService/WorkspaceProject.cs index f789f0eacd8b6..2a028acc93c15 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/BrokeredService/WorkspaceProject.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/BrokeredService/WorkspaceProject.cs @@ -29,6 +29,7 @@ public void Dispose() _project.Dispose(); } + [Obsolete($"Call the {nameof(AddAdditionalFilesAsync)} overload that takes {nameof(SourceFileInfo)}.")] public async Task AddAdditionalFilesAsync(IReadOnlyList additionalFilePaths, CancellationToken cancellationToken) { await using var batch = _project.CreateBatchScope().ConfigureAwait(false); @@ -37,6 +38,14 @@ public async Task AddAdditionalFilesAsync(IReadOnlyList additionalFilePa _project.AddAdditionalFile(additionalFilePath); } + public async Task AddAdditionalFilesAsync(IReadOnlyList additionalFiles, CancellationToken cancellationToken) + { + await using var batchScope = _project.CreateBatchScope().ConfigureAwait(false); + + foreach (var additionalFile in additionalFiles) + _project.AddAdditionalFile(additionalFile.FilePath, folderNames: additionalFile.FolderNames.ToImmutableArray()); + } + public async Task RemoveAdditionalFilesAsync(IReadOnlyList additionalFilePaths, CancellationToken cancellationToken) { await using var batch = _project.CreateBatchScope().ConfigureAwait(false); diff --git a/src/VisualStudio/Core/Def/ProjectSystem/CPS/IWorkspaceProjectContext.cs b/src/VisualStudio/Core/Def/ProjectSystem/CPS/IWorkspaceProjectContext.cs index 83e92eb485a32..a9daef45804a7 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/CPS/IWorkspaceProjectContext.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/CPS/IWorkspaceProjectContext.cs @@ -2,14 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading.Tasks; using Microsoft.CodeAnalysis; -using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; namespace Microsoft.VisualStudio.LanguageServices.ProjectSystem { @@ -23,10 +20,10 @@ internal interface IWorkspaceProjectContext : IDisposable { // Project properties. string DisplayName { get; set; } - string ProjectFilePath { get; set; } + string? ProjectFilePath { get; set; } Guid Guid { get; set; } bool LastDesignTimeBuildSucceeded { get; set; } - string BinOutputPath { get; set; } + string? BinOutputPath { get; set; } /// /// When this project is one of a multi-targeting group of projects, this value indicates whether or not this @@ -55,11 +52,13 @@ internal interface IWorkspaceProjectContext : IDisposable void RemoveAnalyzerReference(string referencePath); // Files. - void AddSourceFile(string filePath, bool isInCurrentContext = true, IEnumerable folderNames = null, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular); + void AddSourceFile(string filePath, bool isInCurrentContext = true, IEnumerable? folderNames = null, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular); void RemoveSourceFile(string filePath); + [Obsolete($"Call the {nameof(AddAdditionalFile)} method that takes folder names.")] void AddAdditionalFile(string filePath, bool isInCurrentContext = true); + void AddAdditionalFile(string filePath, IEnumerable folderNames, bool isInCurrentContext = true); void RemoveAdditionalFile(string filePath); - void AddDynamicFile(string filePath, IEnumerable folderNames = null); + void AddDynamicFile(string filePath, IEnumerable? folderNames = null); void RemoveDynamicFile(string filePath); /// diff --git a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject.cs b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject.cs index 3bfa9acab151c..b5369fcf98154 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject.cs @@ -183,13 +183,7 @@ protected void AddFile( return; } - ImmutableArray folders = default; - - var itemid = Hierarchy.TryGetItemId(filename); - if (itemid != VSConstants.VSITEMID_NIL) - { - folders = GetFolderNamesForDocument(itemid); - } + var folders = GetFolderNamesForDocument(filename); ProjectSystemProject.AddSourceFile(filename, sourceCodeKind, folders); } @@ -309,6 +303,17 @@ private static Guid GetProjectIDGuid(IVsHierarchy hierarchy) /// IVsHierarchy providers, but this code (which is fairly old) still makes the assumptions anyways. private readonly Dictionary> _folderNameMap = new(); + private ImmutableArray GetFolderNamesForDocument(string filename) + { + var itemid = Hierarchy.TryGetItemId(filename); + if (itemid != VSConstants.VSITEMID_NIL) + { + return GetFolderNamesForDocument(itemid); + } + + return default; + } + private ImmutableArray GetFolderNamesForDocument(uint documentItemID) { AssertIsForeground(); diff --git a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject_IAnalyzerHost.cs b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject_IAnalyzerHost.cs index e67eed2bf0896..c04b42c506840 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject_IAnalyzerHost.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject_IAnalyzerHost.cs @@ -36,7 +36,7 @@ void IAnalyzerHost.SetRuleSetFile(string ruleSetFileFullPath) } void IAnalyzerHost.AddAdditionalFile(string additionalFilePath) - => ProjectSystemProject.AddAdditionalFile(additionalFilePath); + => ProjectSystemProject.AddAdditionalFile(additionalFilePath, folders: GetFolderNamesForDocument(additionalFilePath)); void IAnalyzerHost.RemoveAdditionalFile(string additionalFilePath) => ProjectSystemProject.RemoveAdditionalFile(additionalFilePath); diff --git a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs index 97aef6ae8ff3b..55ae78713d580 100644 --- a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs +++ b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs @@ -235,6 +235,9 @@ public void RemoveSourceFile(string filePath) public void AddAdditionalFile(string filePath, bool isInCurrentContext = true) => _projectSystemProject.AddAdditionalFile(filePath); + public void AddAdditionalFile(string filePath, IEnumerable folderNames, bool isInCurrentContext = true) + => _projectSystemProject.AddAdditionalFile(filePath, folders: folderNames.ToImmutableArray()); + public void Dispose() { _projectCodeModel?.OnProjectClosed(); diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs index 51cf73f5f7936..008d6065f2571 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs @@ -719,8 +719,8 @@ public void RemoveSourceTextContainer(SourceTextContainer textContainer) #region Additional File Addition/Removal // TODO: should AdditionalFiles have source code kinds? - public void AddAdditionalFile(string fullPath, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular) - => _additionalFiles.AddFile(fullPath, sourceCodeKind, folders: default); + public void AddAdditionalFile(string fullPath, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular, ImmutableArray folders = default) + => _additionalFiles.AddFile(fullPath, sourceCodeKind, folders); public bool ContainsAdditionalFile(string fullPath) => _additionalFiles.ContainsFile(fullPath); diff --git a/src/Workspaces/Remote/Core/ProjectSystem/IWorkspaceProject.cs b/src/Workspaces/Remote/Core/ProjectSystem/IWorkspaceProject.cs index 4e6ab5f9fbefc..87f60be46f2a5 100644 --- a/src/Workspaces/Remote/Core/ProjectSystem/IWorkspaceProject.cs +++ b/src/Workspaces/Remote/Core/ProjectSystem/IWorkspaceProject.cs @@ -24,7 +24,9 @@ internal interface IWorkspaceProject : IDisposable Task AddMetadataReferencesAsync(IReadOnlyList metadataReferences, CancellationToken cancellationToken); Task RemoveMetadataReferencesAsync(IReadOnlyList metadataReferences, CancellationToken cancellationToken); + [Obsolete($"Call the {nameof(AddAdditionalFilesAsync)} overload that takes {nameof(SourceFileInfo)}.")] Task AddAdditionalFilesAsync(IReadOnlyList additionalFilePaths, CancellationToken cancellationToken); + Task AddAdditionalFilesAsync(IReadOnlyList additionalFiles, CancellationToken cancellationToken); Task RemoveAdditionalFilesAsync(IReadOnlyList additionalFilePaths, CancellationToken cancellationToken); Task AddAnalyzerReferencesAsync(IReadOnlyList analyzerPaths, CancellationToken cancellationToken);