From 426ba087907c16d464c95b3841a11172d3123e7e Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Mon, 13 Aug 2018 15:59:09 -0700 Subject: [PATCH] Add support for Miscellaneous Files (#1252) * Basic of the orphan file system * Initialise the project system * add csproj * Test using MSBuildProjectSystem * Orphan file system using the msbuild system * Remove unnecessary change * csproj temp * Get only syntactic diagnostics for misc files * Logic to delete the document * Edit csproj * Rename to misc files * Order the project systems * Ordering failing * Order project systems based on DisplayName * Remove orphan system * Put inside the partial class * Clean up the miscellanous file system * Tests for the misc project system * Make MEF work * Returns only semantic diagnostics running * Add more failing tests * Clean up * Extension Ordering working for the project systems * Clean the solution * Change the target framework * Add references * Rename project system * Add few tests * Refactor the tests to add a method to the test host * Remove roslyn dependency in abstractions * use only one project for all the miscellanous files * Add project system names class * Modifying the files changed facts to use absolute path instead of relative By adding the misc files support, the file that was being added by the test was being treated as a miscellanous file and being added to the workspace. However since the path being passed was relative, an exception was being thrown. * Add the empty project * Add the IUpdates interface * Add absolute paths for the misc file system * Add dotnet project system for the updates * CR feedback * Remove comment * Add project handling in the workspace * Add assembly info * Deal with race condition between project system updates * add property type * CR feedback * Move the check for the Misc files into the workspace * edit comment * Check for the project id for the document * Dont keep track of the documents in the project system * Add test for multiple files * Add test to handle deletion * Call TryRemoveMiscDocument * Naming consistency * Use the default references same as the script system * CR feedback --- OmniSharp.sln | 33 +- .../Mef/ExportProjectSystemAttribute.cs | 18 + .../Mef/ProjectSystemMetadata.cs | 7 + .../ProjectSystemNames.cs | 11 + .../Services/IUpdates.cs | 9 + src/OmniSharp.Cake/CakeProjectSystem.cs | 3 +- src/OmniSharp.DotNet/DotNetProjectSystem.cs | 10 +- src/OmniSharp.Host/WorkspaceInitializer.cs | 9 +- src/OmniSharp.MSBuild/ProjectSystem.cs | 10 +- .../MiscellaneousFilesProjectSystem.cs | 111 ++++++ .../OmniSharp.MiscellaneousFiles.csproj | 17 + .../Helpers/DiagnosticExtensions.cs | 15 +- .../Services/Diagnostics/CodeCheckService.cs | 2 +- .../Refactoring/V2/BaseCodeActionService.cs | 42 +- .../Refactoring/V2/CodeActionsOrder.Graph.cs | 85 ---- .../V2/CodeActionsOrder.ProviderNode.cs | 85 ---- .../Diagnostics/CSharpDiagnosticService.cs | 2 +- src/OmniSharp.Roslyn/OmniSharpWorkspace.cs | 55 +++ .../DefaultMetadataReferencesHelper.cs | 42 ++ .../Utilities/ExtensionOrderer.Graph.cs | 88 ++++ .../Utilities/ExtensionOrderer.Node.cs | 76 ++++ .../Utilities/ExtensionOrderer.cs | 17 + src/OmniSharp.Script/ScriptContextProvider.cs | 27 +- src/OmniSharp.Script/ScriptProjectSystem.cs | 3 +- src/OmniSharp.Stdio/OmniSharp.Stdio.csproj | 1 + .../test-projects/EmptyProject/README.txt | 0 .../AssemblyInfo.cs | 1 + .../EndpointFacts.cs | 376 ++++++++++++++++++ .../OmniSharp.MiscellaneousFiles.Tests.csproj | 27 ++ tests/TestUtility/OmniSharpTestHost.cs | 10 + tests/TestUtility/TestUtility.csproj | 1 + 31 files changed, 945 insertions(+), 248 deletions(-) create mode 100644 src/OmniSharp.Abstractions/Mef/ExportProjectSystemAttribute.cs create mode 100644 src/OmniSharp.Abstractions/Mef/ProjectSystemMetadata.cs create mode 100644 src/OmniSharp.Abstractions/ProjectSystemNames.cs create mode 100644 src/OmniSharp.Abstractions/Services/IUpdates.cs create mode 100644 src/OmniSharp.MiscellaneousFiles/MiscellaneousFilesProjectSystem.cs create mode 100644 src/OmniSharp.MiscellaneousFiles/OmniSharp.MiscellaneousFiles.csproj delete mode 100644 src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeActionsOrder.Graph.cs delete mode 100644 src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeActionsOrder.ProviderNode.cs create mode 100644 src/OmniSharp.Roslyn/Utilities/DefaultMetadataReferencesHelper.cs create mode 100644 src/OmniSharp.Roslyn/Utilities/ExtensionOrderer.Graph.cs create mode 100644 src/OmniSharp.Roslyn/Utilities/ExtensionOrderer.Node.cs create mode 100644 src/OmniSharp.Roslyn/Utilities/ExtensionOrderer.cs create mode 100644 test-assets/test-projects/EmptyProject/README.txt create mode 100644 tests/OmniSharp.MiscellaneousFiles.Tests/AssemblyInfo.cs create mode 100644 tests/OmniSharp.MiscellaneousFiles.Tests/EndpointFacts.cs create mode 100644 tests/OmniSharp.MiscellaneousFiles.Tests/OmniSharp.MiscellaneousFiles.Tests.csproj diff --git a/OmniSharp.sln b/OmniSharp.sln index 7ad964327d..76a7afeab5 100644 --- a/OmniSharp.sln +++ b/OmniSharp.sln @@ -1,3 +1,4 @@ + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27004.2005 @@ -11,7 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution global.json = global.json EndProjectSection ProjectSection(FolderGlobals) = preProject - global_1json__JSONSchema = http://json.schemastore.org/global + global_1json__JSONSchema = http://json.schemastore.org/global EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{35E025BF-BBB2-4FAC-9F4B-37CBA083EE47}" @@ -68,6 +69,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OmniSharp.Stdio.Driver", "s EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OmniSharp.Script.Tests", "tests\OmniSharp.Script.Tests\OmniSharp.Script.Tests.csproj", "{9E4BA68C-7F4B-429A-A0C7-8CE7D41D610F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmniSharp.MiscellaneousFiles", "src\OmniSharp.MiscellaneousFiles\OmniSharp.MiscellaneousFiles.csproj", "{49358F28-883B-4FA0-B853-8774A732E188}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmniSharp.MiscellaneousFiles.Tests", "tests\OmniSharp.MiscellaneousFiles.Tests\OmniSharp.MiscellaneousFiles.Tests.csproj", "{51F1D224-A543-48C5-BD37-139B935E71D8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -390,6 +395,30 @@ Global {9E4BA68C-7F4B-429A-A0C7-8CE7D41D610F}.Release|x64.Build.0 = Release|Any CPU {9E4BA68C-7F4B-429A-A0C7-8CE7D41D610F}.Release|x86.ActiveCfg = Release|Any CPU {9E4BA68C-7F4B-429A-A0C7-8CE7D41D610F}.Release|x86.Build.0 = Release|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Debug|Any CPU.Build.0 = Debug|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Debug|x64.ActiveCfg = Debug|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Debug|x64.Build.0 = Debug|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Debug|x86.ActiveCfg = Debug|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Debug|x86.Build.0 = Debug|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Release|Any CPU.ActiveCfg = Release|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Release|Any CPU.Build.0 = Release|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Release|x64.ActiveCfg = Release|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Release|x64.Build.0 = Release|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Release|x86.ActiveCfg = Release|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Release|x86.Build.0 = Release|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Debug|x64.ActiveCfg = Debug|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Debug|x64.Build.0 = Debug|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Debug|x86.ActiveCfg = Debug|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Debug|x86.Build.0 = Debug|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Release|Any CPU.Build.0 = Release|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Release|x64.ActiveCfg = Release|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Release|x64.Build.0 = Release|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Release|x86.ActiveCfg = Release|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -421,6 +450,8 @@ Global {BC640CBF-F6E2-42EA-9D61-FB6E515AEA44} = {2C348365-A9D8-459E-9276-56FC46AAEE31} {D2A78CEE-B278-476F-AF34-A7D6F792F973} = {2C348365-A9D8-459E-9276-56FC46AAEE31} {9E4BA68C-7F4B-429A-A0C7-8CE7D41D610F} = {35E025BF-BBB2-4FAC-9F4B-37CBA083EE47} + {49358F28-883B-4FA0-B853-8774A732E188} = {2C348365-A9D8-459E-9276-56FC46AAEE31} + {51F1D224-A543-48C5-BD37-139B935E71D8} = {35E025BF-BBB2-4FAC-9F4B-37CBA083EE47} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4DD725CE-B49A-4151-8B77-BB33FE88E46E} diff --git a/src/OmniSharp.Abstractions/Mef/ExportProjectSystemAttribute.cs b/src/OmniSharp.Abstractions/Mef/ExportProjectSystemAttribute.cs new file mode 100644 index 0000000000..b1736d0d1f --- /dev/null +++ b/src/OmniSharp.Abstractions/Mef/ExportProjectSystemAttribute.cs @@ -0,0 +1,18 @@ +using System; +using System.Composition; +using OmniSharp.Services; + +namespace OmniSharp.Mef +{ + [MetadataAttribute] + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class ExportProjectSystemAttribute: ExportAttribute + { + public string Name { get; } + + public ExportProjectSystemAttribute(string name) : base(typeof(IProjectSystem)) + { + Name = name; + } + } +} diff --git a/src/OmniSharp.Abstractions/Mef/ProjectSystemMetadata.cs b/src/OmniSharp.Abstractions/Mef/ProjectSystemMetadata.cs new file mode 100644 index 0000000000..ef2498dea3 --- /dev/null +++ b/src/OmniSharp.Abstractions/Mef/ProjectSystemMetadata.cs @@ -0,0 +1,7 @@ +namespace OmniSharp.Mef +{ + public class ProjectSystemMetadata + { + public string Name { get; set; } + } +} diff --git a/src/OmniSharp.Abstractions/ProjectSystemNames.cs b/src/OmniSharp.Abstractions/ProjectSystemNames.cs new file mode 100644 index 0000000000..ef468dc4d2 --- /dev/null +++ b/src/OmniSharp.Abstractions/ProjectSystemNames.cs @@ -0,0 +1,11 @@ +namespace OmniSharp +{ + public static class ProjectSystemNames + { + public const string MSBuildProjectSystem = "MSBuildProjectSystem"; + public const string CakeProjectSystem = "CakeProjectSystem"; + public const string DotNetProjectSystem = "DotNetProjectSystem"; + public const string ScriptProjectSystem = "ScriptProjectSystem"; + public const string MiscellaneousFilesProjectSystem = "MiscellaneousFilesProjectSystem"; + } +} diff --git a/src/OmniSharp.Abstractions/Services/IUpdates.cs b/src/OmniSharp.Abstractions/Services/IUpdates.cs new file mode 100644 index 0000000000..4c70f27a1d --- /dev/null +++ b/src/OmniSharp.Abstractions/Services/IUpdates.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace OmniSharp.Services +{ + public interface IWaitableProjectSystem: IProjectSystem + { + Task WaitForUpdatesAsync(); + } +} diff --git a/src/OmniSharp.Cake/CakeProjectSystem.cs b/src/OmniSharp.Cake/CakeProjectSystem.cs index 28ff198a7c..63d1b60860 100644 --- a/src/OmniSharp.Cake/CakeProjectSystem.cs +++ b/src/OmniSharp.Cake/CakeProjectSystem.cs @@ -16,13 +16,14 @@ using OmniSharp.FileSystem; using OmniSharp.FileWatching; using OmniSharp.Helpers; +using OmniSharp.Mef; using OmniSharp.Models.WorkspaceInformation; using OmniSharp.Roslyn.Utilities; using OmniSharp.Services; namespace OmniSharp.Cake { - [Export(typeof(IProjectSystem)), Shared] + [ExportProjectSystem(ProjectSystemNames.CakeProjectSystem), Shared] public class CakeProjectSystem : IProjectSystem { private readonly OmniSharpWorkspace _workspace; diff --git a/src/OmniSharp.DotNet/DotNetProjectSystem.cs b/src/OmniSharp.DotNet/DotNetProjectSystem.cs index cb073018a9..cb57bb455c 100644 --- a/src/OmniSharp.DotNet/DotNetProjectSystem.cs +++ b/src/OmniSharp.DotNet/DotNetProjectSystem.cs @@ -17,14 +17,15 @@ using OmniSharp.Eventing; using OmniSharp.FileWatching; using OmniSharp.Helpers; +using OmniSharp.Mef; using OmniSharp.Models.Events; using OmniSharp.Models.WorkspaceInformation; using OmniSharp.Services; namespace OmniSharp.DotNet { - [Export(typeof(IProjectSystem)), Shared] - public class DotNetProjectSystem : IProjectSystem + [ExportProjectSystem(ProjectSystemNames.DotNetProjectSystem), Shared] + public class DotNetProjectSystem : IWaitableProjectSystem { private const string CompilationConfiguration = "Debug"; @@ -423,5 +424,10 @@ private static LanguageVersion ParseLanguageVersion(string value) return languageVersion; } + + async Task IWaitableProjectSystem.WaitForUpdatesAsync() + { + await ((IProjectSystem)this).GetWorkspaceModelAsync(new WorkspaceInformationRequest()); + } } } diff --git a/src/OmniSharp.Host/WorkspaceInitializer.cs b/src/OmniSharp.Host/WorkspaceInitializer.cs index ffe3c0b766..2e4b82f130 100644 --- a/src/OmniSharp.Host/WorkspaceInitializer.cs +++ b/src/OmniSharp.Host/WorkspaceInitializer.cs @@ -1,13 +1,16 @@ using System; using System.Composition.Hosting; +using System.Linq; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using OmniSharp.Mef; using OmniSharp.Options; using OmniSharp.Roslyn; using OmniSharp.Roslyn.Options; using OmniSharp.Services; +using OmniSharp.Utilities; namespace OmniSharp { @@ -24,9 +27,11 @@ public static void Initialize( var projectEventForwarder = compositionHost.GetExport(); projectEventForwarder.Initialize(); + var projectSystems = compositionHost.GetExports>(); + var ps = projectSystems.Select(n => n.Value); + var orderedProjectSystems = ExtensionOrderer.GetOrderedOrUnorderedList(ps, eps => eps.Name); - // Initialize all the project systems - foreach (var projectSystem in compositionHost.GetExports()) + foreach (var projectSystem in orderedProjectSystems) { try { diff --git a/src/OmniSharp.MSBuild/ProjectSystem.cs b/src/OmniSharp.MSBuild/ProjectSystem.cs index 4d432acf18..d86f0d5a72 100644 --- a/src/OmniSharp.MSBuild/ProjectSystem.cs +++ b/src/OmniSharp.MSBuild/ProjectSystem.cs @@ -10,6 +10,7 @@ using OmniSharp.Eventing; using OmniSharp.FileSystem; using OmniSharp.FileWatching; +using OmniSharp.Mef; using OmniSharp.Models.WorkspaceInformation; using OmniSharp.MSBuild.Discovery; using OmniSharp.MSBuild.Models; @@ -20,8 +21,8 @@ namespace OmniSharp.MSBuild { - [Export(typeof(IProjectSystem)), Shared] - public class ProjectSystem : IProjectSystem + [ExportProjectSystem(ProjectSystemNames.MSBuildProjectSystem), Shared] + public class ProjectSystem : IWaitableProjectSystem { private readonly IOmniSharpEnvironment _environment; private readonly OmniSharpWorkspace _workspace; @@ -209,5 +210,10 @@ async Task IProjectSystem.GetProjectModelAsync(string filePath) return new MSBuildProjectInfo(projectFileInfo); } + + public async Task WaitForUpdatesAsync() + { + await _manager.WaitForQueueEmptyAsync(); + } } } diff --git a/src/OmniSharp.MiscellaneousFiles/MiscellaneousFilesProjectSystem.cs b/src/OmniSharp.MiscellaneousFiles/MiscellaneousFilesProjectSystem.cs new file mode 100644 index 0000000000..054fb93707 --- /dev/null +++ b/src/OmniSharp.MiscellaneousFiles/MiscellaneousFilesProjectSystem.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.Composition; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using OmniSharp.FileSystem; +using OmniSharp.FileWatching; +using OmniSharp.Mef; +using OmniSharp.Models.WorkspaceInformation; +using OmniSharp.Services; + +namespace OmniSharp.MiscellaneousFile +{ + [ExtensionOrder(After = ProjectSystemNames.MSBuildProjectSystem)] + [ExtensionOrder(After = ProjectSystemNames.DotNetProjectSystem)] + [ExportProjectSystem(ProjectSystemNames.MiscellaneousFilesProjectSystem), Shared] + public class MiscellaneousFilesProjectSystem : IProjectSystem + { + private const string miscFileExtension = ".cs"; + public string Key => ProjectSystemNames.MiscellaneousFilesProjectSystem; + public string Language => LanguageNames.CSharp; + IEnumerable IProjectSystem.Extensions => new[] { miscFileExtension }; + public bool EnabledByDefault { get; } = true; + + private readonly OmniSharpWorkspace _workspace; + private readonly IFileSystemWatcher _fileSystemWatcher; + private readonly FileSystemHelper _fileSystemHelper; + private readonly List _projectSystems; + private readonly ILogger _logger; + + [ImportingConstructor] + public MiscellaneousFilesProjectSystem(OmniSharpWorkspace workspace, IFileSystemWatcher fileSystemWatcher, FileSystemHelper fileSystemHelper, + ILoggerFactory loggerFactory, [ImportMany] IEnumerable> projectSystems) + { + _workspace = workspace; + _fileSystemWatcher = fileSystemWatcher; + _fileSystemHelper = fileSystemHelper; + _logger = loggerFactory.CreateLogger(); + _projectSystems = projectSystems + .Where(ps => ps.Metadata.Name == ProjectSystemNames.MSBuildProjectSystem || + ps.Metadata.Name == ProjectSystemNames.DotNetProjectSystem) + .Select(ps => ps.Value) + .Cast() + .ToList(); + } + + Task IProjectSystem.GetProjectModelAsync(string filePath) + { + return Task.FromResult(null); + } + + Task IProjectSystem.GetWorkspaceModelAsync(WorkspaceInformationRequest request) + { + return Task.FromResult(null); + } + + void IProjectSystem.Initalize(IConfiguration configuration) + { + var allFiles = _fileSystemHelper.GetFiles("**/*.cs"); + foreach (var filePath in allFiles) + TryAddMiscellaneousFile(filePath); + + _fileSystemWatcher.Watch(miscFileExtension, OnMiscellaneousFileChanged); + } + + private async void TryAddMiscellaneousFile(string filePath) + { + //wait for the project systems to finish processing the updates + foreach (var projectSystem in _projectSystems) + { + await projectSystem.WaitForUpdatesAsync(); + } + + var absoluteFilePath = new FileInfo(filePath).FullName; + if (!File.Exists(absoluteFilePath)) + return; + + if (_workspace.TryAddMiscellaneousDocument(absoluteFilePath, Language) != null) + { + _logger.LogInformation($"Successfully added file '{absoluteFilePath}' to workspace"); + } + } + + private void OnMiscellaneousFileChanged(string filePath, FileChangeType changeType) + { + if (changeType == FileChangeType.Unspecified && File.Exists(filePath) || + changeType == FileChangeType.Create) + { + TryAddMiscellaneousFile(filePath); + } + + else if (changeType == FileChangeType.Unspecified && !File.Exists(filePath) || + changeType == FileChangeType.Delete) + { + RemoveFromWorkspace(filePath); + } + } + + private void RemoveFromWorkspace(string filePath) + { + if (_workspace.TryRemoveMiscellaneousDocument(filePath)) + { + _logger.LogDebug($"Removed file '{filePath}' from the workspace."); + } + } + } +} diff --git a/src/OmniSharp.MiscellaneousFiles/OmniSharp.MiscellaneousFiles.csproj b/src/OmniSharp.MiscellaneousFiles/OmniSharp.MiscellaneousFiles.csproj new file mode 100644 index 0000000000..89bb4ca4c4 --- /dev/null +++ b/src/OmniSharp.MiscellaneousFiles/OmniSharp.MiscellaneousFiles.csproj @@ -0,0 +1,17 @@ + + + + net461 + AnyCPU + + + + + + + + + + + + diff --git a/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs b/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs index a2b89de94d..94380327b6 100644 --- a/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs +++ b/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs @@ -24,15 +24,24 @@ internal static DiagnosticLocation ToDiagnosticLocation(this Diagnostic diagnost }; } - internal static async Task> FindDiagnosticLocationsAsync(this IEnumerable documents) + internal static async Task> FindDiagnosticLocationsAsync(this IEnumerable documents, OmniSharpWorkspace workspace) { if (documents == null || !documents.Any()) return Enumerable.Empty(); var items = new List(); foreach (var document in documents) { - var semanticModel = await document.GetSemanticModelAsync(); - IEnumerable diagnostics = semanticModel.GetDiagnostics(); + IEnumerable diagnostics; + if (workspace.IsCapableOfSemanticDiagnostics(document)) + { + var semanticModel = await document.GetSemanticModelAsync(); + diagnostics = semanticModel.GetDiagnostics(); + } + else + { + var syntaxModel = await document.GetSyntaxTreeAsync(); + diagnostics = syntaxModel.GetDiagnostics(); + } foreach (var quickFix in diagnostics.Select(d => d.ToDiagnosticLocation())) { diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index a857343f2a..4a09494f81 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -27,7 +27,7 @@ public async Task Handle(CodeCheckRequest request) ? _workspace.GetDocuments(request.FileName) : _workspace.CurrentSolution.Projects.SelectMany(project => project.Documents); - var quickFixes = await documents.FindDiagnosticLocationsAsync(); + var quickFixes = await documents.FindDiagnosticLocationsAsync(_workspace); return new QuickFixResponse(quickFixes); } } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs index 8c213387b8..9d74803837 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs @@ -151,48 +151,14 @@ private async Task AppendFixesAsync(Document document, TextSpan span, IEnumerabl private List GetSortedCodeFixProviders() { - var nodesList = new List>(); - var providerList = new List(); - - foreach (var provider in this.Providers) - { - foreach (var codeFixProvider in provider.CodeFixProviders) - { - providerList.Add(codeFixProvider); - nodesList.Add(ProviderNode.From(codeFixProvider)); - } - } - - var graph = Graph.GetGraph(nodesList); - if (graph.HasCycles()) - { - return providerList; - } - - return graph.TopologicalSort(); + var providerList = this.Providers.SelectMany(provider => provider.CodeFixProviders); + return ExtensionOrderer.GetOrderedOrUnorderedList(providerList, attribute => attribute.Name).ToList(); } private List GetSortedCodeRefactoringProviders() { - var nodesList = new List>(); - var providerList = new List(); - - foreach (var provider in this.Providers) - { - foreach (var codeRefactoringProvider in provider.CodeRefactoringProviders) - { - providerList.Add(codeRefactoringProvider); - nodesList.Add(ProviderNode.From(codeRefactoringProvider)); - } - } - - var graph = Graph.GetGraph(nodesList); - if (graph.HasCycles()) - { - return providerList; - } - - return graph.TopologicalSort(); + var providerList = this.Providers.SelectMany(provider => provider.CodeRefactoringProviders); + return ExtensionOrderer.GetOrderedOrUnorderedList(providerList, attribute => attribute.Name).ToList(); } private bool HasFix(CodeFixProvider codeFixProvider, string diagnosticId) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeActionsOrder.Graph.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeActionsOrder.Graph.cs deleted file mode 100644 index 79c8a007c5..0000000000 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeActionsOrder.Graph.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Adapted from ExtensionOrderer in Roslyn -using System.Collections.Generic; - -namespace OmniSharp.Roslyn.CSharp.Services.Refactoring.V2 -{ - internal class Graph - { - //Dictionary to map between nodes and the names - private Dictionary> Nodes { get; } - private List> AllNodes { get; } - private Graph(List> nodesList) - { - Nodes = new Dictionary>(); - AllNodes = nodesList; - } - internal static Graph GetGraph(List> nodesList) - { - var graph = new Graph(nodesList); - - foreach (ProviderNode node in graph.AllNodes) - { - graph.Nodes[node.ProviderName] = node; - } - - foreach (ProviderNode node in graph.AllNodes) - { - foreach (var before in node.Before) - { - if (graph.Nodes.ContainsKey(before)) - { - var beforeNode = graph.Nodes[before]; - beforeNode.NodesBeforeMeSet.Add(node); - } - } - - foreach (var after in node.After) - { - if (graph.Nodes.ContainsKey(after)) - { - var afterNode = graph.Nodes[after]; - node.NodesBeforeMeSet.Add(afterNode); - } - } - } - - return graph; - } - - public bool HasCycles() - { - foreach (var node in this.AllNodes) - { - if (node.CheckForCycles()) - return true; - } - return false; - } - - public List TopologicalSort() - { - List result = new List(); - var seenNodes = new HashSet>(); - - foreach (var node in AllNodes) - { - Visit(node, result, seenNodes); - } - - return result; - } - - private void Visit(ProviderNode node, List result, HashSet> seenNodes) - { - if (seenNodes.Add(node)) - { - foreach (var before in node.NodesBeforeMeSet) - { - Visit(before, result, seenNodes); - } - - result.Add(node.Provider); - } - } - } -} diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeActionsOrder.ProviderNode.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeActionsOrder.ProviderNode.cs deleted file mode 100644 index 92be73ac8a..0000000000 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeActionsOrder.ProviderNode.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Adapted from ExtensionOrderer in Roslyn -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CodeRefactorings; - -namespace OmniSharp.Roslyn.CSharp.Services.Refactoring.V2 -{ - internal class ProviderNode - { - public string ProviderName { get; set; } - public List Before { get; set; } - public List After { get; set; } - public TProvider Provider { get; set; } - public HashSet> NodesBeforeMeSet { get; set; } - - public static ProviderNode From(TProvider provider) - { - string providerName = ""; - if (provider is CodeFixProvider) - { - var exportAttribute = provider.GetType().GetCustomAttribute(typeof(ExportCodeFixProviderAttribute)); - if (exportAttribute is ExportCodeFixProviderAttribute fixAttribute && fixAttribute.Name != null) - { - providerName = fixAttribute.Name; - } - } - else - { - var exportAttribute = provider.GetType().GetCustomAttribute(typeof(ExportCodeRefactoringProviderAttribute)); - if (exportAttribute is ExportCodeRefactoringProviderAttribute refactoringAttribute && refactoringAttribute.Name != null) - { - providerName = refactoringAttribute.Name; - } - } - - var orderAttributes = provider.GetType().GetCustomAttributes(typeof(ExtensionOrderAttribute), true).Select(attr => (ExtensionOrderAttribute)attr).ToList(); - return new ProviderNode(provider, providerName, orderAttributes); - } - - private ProviderNode(TProvider provider, string providerName, List orderAttributes) - { - Provider = provider; - ProviderName = providerName; - Before = new List(); - After = new List(); - NodesBeforeMeSet = new HashSet>(); - orderAttributes.ForEach(attr => AddAttribute(attr)); - } - - private void AddAttribute(ExtensionOrderAttribute attribute) - { - if (attribute.Before != null) - Before.Add(attribute.Before); - if (attribute.After != null) - After.Add(attribute.After); - } - - internal bool CheckForCycles() - { - return CheckForCycles(new HashSet>()); - } - - private bool CheckForCycles(HashSet> seenNodes) - { - if (!seenNodes.Add(this)) - { - //Cycle detected - return true; - } - - foreach (var before in this.NodesBeforeMeSet) - { - if (before.CheckForCycles(seenNodes)) - return true; - } - - seenNodes.Remove(this); - return false; - } - } -} diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs index 35fc89fe39..03e733f669 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs @@ -140,7 +140,7 @@ private async Task Dequeue() private async Task ProcessNextItem(string filePath) { var documents = _workspace.GetDocuments(filePath); - var items = await documents.FindDiagnosticLocationsAsync(); + var items = await documents.FindDiagnosticLocationsAsync(_workspace); return new DiagnosticResult() { diff --git a/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs b/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs index d0e371ca5e..495f88a6ca 100644 --- a/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs +++ b/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Composition; using System.IO; @@ -9,6 +10,7 @@ using Microsoft.CodeAnalysis.Text; using Microsoft.Extensions.Logging; using OmniSharp.Roslyn; +using OmniSharp.Roslyn.Utilities; using OmniSharp.Utilities; namespace OmniSharp @@ -21,6 +23,8 @@ public class OmniSharpWorkspace : Workspace private readonly ILogger _logger; + private readonly ConcurrentDictionary miscDocumentsProjectInfos = new ConcurrentDictionary(); + [ImportingConstructor] public OmniSharpWorkspace(HostServicesAggregator aggregator, ILoggerFactory loggerFactory) : base(aggregator.CreateHostServices(), "Custom") @@ -80,9 +84,50 @@ public void RemoveMetadataReference(ProjectId projectId, MetadataReference metad public void AddDocument(DocumentInfo documentInfo) { + // if the file has already been added as a misc file, + // because of a possible race condition between the updates of the project systems, + // remove the misc file and add the document as required + TryRemoveMiscellaneousDocument(documentInfo.FilePath); + OnDocumentAdded(documentInfo); } + public DocumentId TryAddMiscellaneousDocument(string filePath, string language) + { + if (GetDocument(filePath) != null) + return null; //if the workspace already knows about this document then it is not a miscellaneous document + + var projectInfo = miscDocumentsProjectInfos.GetOrAdd(language, CreateMiscFilesProject(language)); + var documentId = AddDocument(projectInfo.Id, filePath); + return documentId; + } + + public bool TryRemoveMiscellaneousDocument(string filePath) + { + var documentId = GetDocumentId(filePath); + if (documentId == null || !IsMiscellaneousDocument(documentId)) + return false; + + RemoveDocument(documentId); + return true; + } + + private ProjectInfo CreateMiscFilesProject(string language) + { + string assemblyName = Guid.NewGuid().ToString("N"); + var projectInfo = ProjectInfo.Create( + id: ProjectId.CreateNewId(), + version: VersionStamp.Create(), + name: "MiscellaneousFiles", + metadataReferences: DefaultMetadataReferenceHelper.GetDefaultMetadataReferenceLocations() + .Select(loc => MetadataReference.CreateFromFile(loc)), + assemblyName: assemblyName, + language: language); + + AddProject(projectInfo); + return projectInfo; + } + public DocumentId AddDocument(ProjectId projectId, string filePath, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular) { var documentId = DocumentId.CreateNewId(projectId); @@ -224,5 +269,15 @@ private void SaveDocumentText(DocumentId id, string fullPath, SourceText newText _logger.LogError(e, $"Error saving document {fullPath}"); } } + + public bool IsCapableOfSemanticDiagnostics(Document document) + { + return !IsMiscellaneousDocument(document.Id); + } + + private bool IsMiscellaneousDocument(DocumentId documentId) + { + return miscDocumentsProjectInfos.Where(p => p.Value.Id == documentId.ProjectId).Any(); + } } } diff --git a/src/OmniSharp.Roslyn/Utilities/DefaultMetadataReferencesHelper.cs b/src/OmniSharp.Roslyn/Utilities/DefaultMetadataReferencesHelper.cs new file mode 100644 index 0000000000..7c4ce122ec --- /dev/null +++ b/src/OmniSharp.Roslyn/Utilities/DefaultMetadataReferencesHelper.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace OmniSharp.Roslyn.Utilities +{ + public static class DefaultMetadataReferenceHelper + { + public static IEnumerable GetDefaultMetadataReferenceLocations() + { + var assemblies = new[] + { + typeof(object).GetTypeInfo().Assembly, + typeof(Enumerable).GetTypeInfo().Assembly, + typeof(Stack<>).GetTypeInfo().Assembly, + typeof(Lazy<,>).GetTypeInfo().Assembly, + FromName("System.Runtime"), + FromName("mscorlib") + }; + + return assemblies + .Where(a => a != null) + .Select(a => a.Location) + .Distinct(); + + Assembly FromName(string assemblyName) + { + try + { + return Assembly.Load(new AssemblyName(assemblyName)); + } + catch + { + return null; + } + } + } + } +} diff --git a/src/OmniSharp.Roslyn/Utilities/ExtensionOrderer.Graph.cs b/src/OmniSharp.Roslyn/Utilities/ExtensionOrderer.Graph.cs new file mode 100644 index 0000000000..4abd245418 --- /dev/null +++ b/src/OmniSharp.Roslyn/Utilities/ExtensionOrderer.Graph.cs @@ -0,0 +1,88 @@ +// Adapted from ExtensionOrderer in Roslyn +using System.Collections.Generic; + +namespace OmniSharp.Utilities +{ + static partial class ExtensionOrderer + { + internal class Graph + { + //Dictionary to map between nodes and the names + private Dictionary> Nodes { get; } + private List> AllNodes { get; } + private Graph(List> nodesList) + { + Nodes = new Dictionary>(); + AllNodes = nodesList; + } + internal static Graph GetGraph(List> nodesList) + { + var graph = new Graph(nodesList); + + foreach (Node node in graph.AllNodes) + { + graph.Nodes[node.Name] = node; + } + + foreach (Node node in graph.AllNodes) + { + foreach (var before in node.Before) + { + if (graph.Nodes.ContainsKey(before)) + { + var beforeNode = graph.Nodes[before]; + beforeNode.NodesBeforeMeSet.Add(node); + } + } + + foreach (var after in node.After) + { + if (graph.Nodes.ContainsKey(after)) + { + var afterNode = graph.Nodes[after]; + node.NodesBeforeMeSet.Add(afterNode); + } + } + } + + return graph; + } + + public bool HasCycles() + { + foreach (var node in this.AllNodes) + { + if (node.CheckForCycles()) + return true; + } + return false; + } + + public List TopologicalSort() + { + List result = new List(); + var seenNodes = new HashSet>(); + + foreach (var node in AllNodes) + { + Visit(node, result, seenNodes); + } + + return result; + } + + private void Visit(Node node, List result, HashSet> seenNodes) + { + if (seenNodes.Add(node)) + { + foreach (var before in node.NodesBeforeMeSet) + { + Visit(before, result, seenNodes); + } + + result.Add(node.Extension); + } + } + } + } +} diff --git a/src/OmniSharp.Roslyn/Utilities/ExtensionOrderer.Node.cs b/src/OmniSharp.Roslyn/Utilities/ExtensionOrderer.Node.cs new file mode 100644 index 0000000000..383570f8f5 --- /dev/null +++ b/src/OmniSharp.Roslyn/Utilities/ExtensionOrderer.Node.cs @@ -0,0 +1,76 @@ +// Adapted from ExtensionOrderer in Roslyn +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.CodeAnalysis; + +namespace OmniSharp.Utilities +{ + static partial class ExtensionOrderer + { + internal class Node + { + public string Name { get; set; } + public List Before { get; set; } + public List After { get; set; } + public TNode Extension { get; set; } + public HashSet> NodesBeforeMeSet { get; set; } + + public static Node From(TNode extension, Func nameExtractor) where TNodeAttribute : Attribute + { + string name = string.Empty; + var attribute = extension.GetType().GetCustomAttribute(); + if (attribute is TNodeAttribute && !string.IsNullOrEmpty(nameExtractor(attribute))) + { + name = nameExtractor(attribute); + } + var orderAttributes = extension.GetType().GetCustomAttributes(true); + return new Node(extension, name, orderAttributes); + } + + private Node(TNode extension, string name, IEnumerable orderAttributes) + { + Extension = extension; + Name = name; + Before = new List(); + After = new List(); + NodesBeforeMeSet = new HashSet>(); + foreach (var attribute in orderAttributes) + { + AddAttribute(attribute); + } + } + + private void AddAttribute(ExtensionOrderAttribute attribute) + { + if (attribute.Before != null) + Before.Add(attribute.Before); + if (attribute.After != null) + After.Add(attribute.After); + } + + internal bool CheckForCycles() + { + return CheckForCycles(new HashSet>()); + } + + private bool CheckForCycles(HashSet> seenNodes) + { + if (!seenNodes.Add(this)) + { + //Cycle detected + return true; + } + + foreach (var before in this.NodesBeforeMeSet) + { + if (before.CheckForCycles(seenNodes)) + return true; + } + + seenNodes.Remove(this); + return false; + } + } + } +} diff --git a/src/OmniSharp.Roslyn/Utilities/ExtensionOrderer.cs b/src/OmniSharp.Roslyn/Utilities/ExtensionOrderer.cs new file mode 100644 index 0000000000..dfc39e9f09 --- /dev/null +++ b/src/OmniSharp.Roslyn/Utilities/ExtensionOrderer.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace OmniSharp.Utilities +{ + public static partial class ExtensionOrderer + { + /* Returns a sorted order of the nodes if such a sorting exists, else returns the unsorted list */ + public static IEnumerable GetOrderedOrUnorderedList(IEnumerable unsortedList, Func nameExtractor) where TAttribute: Attribute + { + var nodesList = unsortedList.Select(elem => Node.From(elem, nameExtractor)); + var graph = Graph.GetGraph(nodesList.ToList()); + return graph.HasCycles() ? unsortedList : graph.TopologicalSort(); + } + } +} diff --git a/src/OmniSharp.Script/ScriptContextProvider.cs b/src/OmniSharp.Script/ScriptContextProvider.cs index a97cd2d648..95b5967f9d 100644 --- a/src/OmniSharp.Script/ScriptContextProvider.cs +++ b/src/OmniSharp.Script/ScriptContextProvider.cs @@ -117,20 +117,7 @@ public ScriptContext CreateScriptContext(ScriptOptions scriptOptions) private void AddDefaultClrMetadataReferences(HashSet commonReferences, HashSet assemblyReferences) { - var assemblies = new[] - { - typeof(object).GetTypeInfo().Assembly, - typeof(Enumerable).GetTypeInfo().Assembly, - typeof(Stack<>).GetTypeInfo().Assembly, - typeof(Lazy<,>).GetTypeInfo().Assembly, - FromName("System.Runtime"), - FromName("mscorlib") - }; - - var references = assemblies - .Where(a => a != null) - .Select(a => a.Location) - .Distinct() + var references = DefaultMetadataReferenceHelper.GetDefaultMetadataReferenceLocations() .Select(l => { assemblyReferences.Add(l); @@ -141,18 +128,6 @@ private void AddDefaultClrMetadataReferences(HashSet commonRe { commonReferences.Add(reference); } - - Assembly FromName(string assemblyName) - { - try - { - return Assembly.Load(new AssemblyName(assemblyName)); - } - catch - { - return null; - } - } } private void AddMetadataReference(ISet referenceCollection, HashSet assemblyReferences, string fileReference) diff --git a/src/OmniSharp.Script/ScriptProjectSystem.cs b/src/OmniSharp.Script/ScriptProjectSystem.cs index 1bf3dacb94..d4b7dd9633 100644 --- a/src/OmniSharp.Script/ScriptProjectSystem.cs +++ b/src/OmniSharp.Script/ScriptProjectSystem.cs @@ -12,12 +12,13 @@ using Microsoft.Extensions.Logging; using OmniSharp.FileSystem; using OmniSharp.FileWatching; +using OmniSharp.Mef; using OmniSharp.Models.WorkspaceInformation; using OmniSharp.Services; namespace OmniSharp.Script { - [Export(typeof(IProjectSystem)), Shared] + [ExportProjectSystem(ProjectSystemNames.ScriptProjectSystem), Shared] public class ScriptProjectSystem : IProjectSystem { private const string CsxExtension = ".csx"; diff --git a/src/OmniSharp.Stdio/OmniSharp.Stdio.csproj b/src/OmniSharp.Stdio/OmniSharp.Stdio.csproj index 29ee1a386c..6b7ca88600 100644 --- a/src/OmniSharp.Stdio/OmniSharp.Stdio.csproj +++ b/src/OmniSharp.Stdio/OmniSharp.Stdio.csproj @@ -8,6 +8,7 @@ + diff --git a/test-assets/test-projects/EmptyProject/README.txt b/test-assets/test-projects/EmptyProject/README.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/OmniSharp.MiscellaneousFiles.Tests/AssemblyInfo.cs b/tests/OmniSharp.MiscellaneousFiles.Tests/AssemblyInfo.cs new file mode 100644 index 0000000000..9933b8fd08 --- /dev/null +++ b/tests/OmniSharp.MiscellaneousFiles.Tests/AssemblyInfo.cs @@ -0,0 +1 @@ +[assembly: Xunit.CollectionBehavior(DisableTestParallelization = true)] diff --git a/tests/OmniSharp.MiscellaneousFiles.Tests/EndpointFacts.cs b/tests/OmniSharp.MiscellaneousFiles.Tests/EndpointFacts.cs new file mode 100644 index 0000000000..9fd6d7647a --- /dev/null +++ b/tests/OmniSharp.MiscellaneousFiles.Tests/EndpointFacts.cs @@ -0,0 +1,376 @@ +using System.Linq; +using System.Threading.Tasks; +using OmniSharp.Models; +using OmniSharp.Models.CodeCheck; +using OmniSharp.Models.FilesChanged; +using OmniSharp.Models.FindImplementations; +using OmniSharp.Models.FindSymbols; +using OmniSharp.Models.FindUsages; +using OmniSharp.Models.FixUsings; +using OmniSharp.Models.SignatureHelp; +using OmniSharp.Models.TypeLookup; +using OmniSharp.Roslyn.CSharp.Services.Files; +using OmniSharp.Roslyn.CSharp.Services.Types; +using TestUtility; +using Xunit; +using Xunit.Abstractions; + +namespace OmniSharp.MiscellaneousFiles.Tests +{ + public class EndpointFacts : AbstractTestFixture + { + public EndpointFacts(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public async Task Returns_only_syntactic_diagnotics() + { + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("EmptyProject")) + { + var testfile = new TestFile("a.cs", "class C { b a = new b(); int n }"); + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var filePath = AddTestFile(testProject, testfile); + await WaitForFileUpdate(filePath, host); + var request = new CodeCheckRequest() { FileName = filePath }; + var actual = await host.GetResponse(OmniSharpEndpoints.CodeCheck, request); + Assert.Single(actual.QuickFixes); + Assert.Equal("; expected", actual.QuickFixes.First().Text); + } + } + } + + [Fact] + public async Task Returns_Signature_help() + { + const string source = +@"class Program +{ + public static void Main(){ + System.Guid.NewGuid($$); + } +}"; + var testfile = new TestFile("a.cs", source); + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("EmptyProject")) + { + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var filePath = AddTestFile(testProject, testfile); + await WaitForFileUpdate(filePath, host); + var point = testfile.Content.GetPointFromPosition(); + var request = new SignatureHelpRequest() + { + FileName = filePath, + Line = point.Line, + Column = point.Offset, + Buffer = testfile.Content.Code + }; + + var actual = await host.GetResponse(OmniSharpEndpoints.SignatureHelp, request); + Assert.Single(actual.Signatures); + Assert.Equal(0, actual.ActiveParameter); + Assert.Equal(0, actual.ActiveSignature); + Assert.Equal("NewGuid", actual.Signatures.ElementAt(0).Name); + Assert.Empty(actual.Signatures.ElementAt(0).Parameters); + } + } + } + + [Fact] + public async Task Returns_Implementations() + { + const string source = @" + public class MyClass + { + public MyClass() { Fo$$o(); } + + public void Foo() {} + }"; + + var testfile = new TestFile("a.cs", source); + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("EmptyProject")) + { + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var filePath = AddTestFile(testProject, testfile); + await WaitForFileUpdate(filePath, host); + var point = testfile.Content.GetPointFromPosition(); + var request = new FindImplementationsRequest() + { + FileName = filePath, + Line = point.Line, + Column = point.Offset, + Buffer = testfile.Content.Code + }; + + var actual = await host.GetResponse(OmniSharpEndpoints.FindImplementations, request); + Assert.Single(actual.QuickFixes); + Assert.Equal("public void Foo() {}", actual.QuickFixes.First().Text.Trim()); + } + } + } + + [Fact] + public async Task Returns_Usages() + { + const string source = @" + public class F$$oo + { + public string prop { get; set; } + } + + public class FooConsumer + { + public FooConsumer() + { + var temp = new Foo(); + var prop = foo.prop; + } + }"; + + var testfile = new TestFile("a.cs", source); + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("EmptyProject")) + { + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var filePath = AddTestFile(testProject, testfile); + await WaitForFileUpdate(filePath, host); + + var point = testfile.Content.GetPointFromPosition(); + var request = new FindUsagesRequest() + { + FileName = filePath, + Line = point.Line, + Column = point.Offset, + Buffer = testfile.Content.Code + }; + + var actual = await host.GetResponse(OmniSharpEndpoints.FindUsages, request); + Assert.Equal(2, actual.QuickFixes.Count()); + } + } + } + + [Fact] + public async Task Returns_Symbols() + { + const string source = @" + namespace Some.Long.Namespace + { + public class Foo + { + private string _field = 0; + private string AutoProperty { get; } + private string Property + { + get { return _field; } + set { _field = value; } + } + private string Method() {} + private string Method(string param) {} + + private class Nested + { + private string NestedMethod() {} + } + } + }"; + + var testfile = new TestFile("a.cs", source); + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("EmptyProject")) + { + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var filePath = AddTestFile(testProject, testfile); + await WaitForFileUpdate(filePath, host); + var actual = await host.GetResponse(OmniSharpEndpoints.FindSymbols, null); + var symbols = actual.QuickFixes.Select(q => q.Text); + + var expected = new[] + { + "Foo", + "_field", + "AutoProperty", + "Property", + "Method()", + "Method(string param)", + "Nested", + "NestedMethod()" + }; + + Assert.Equal(expected, symbols); + } + } + } + + [Fact] + public async Task Returns_FixUsings() + { + const string code = @" +namespace nsA +{ + public class classX{} +} + +namespace OmniSharp +{ + public class class1 + { + public method1() + { + var c1 = new classX(); + } + } +}"; + + const string expectedCode = @" +using nsA; + +namespace nsA +{ + public class classX{} +} + +namespace OmniSharp +{ + public class class1 + { + public method1() + { + var c1 = new classX(); + } + } +}"; + + var testfile = new TestFile("a.cs", code); + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("EmptyProject")) + { + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var filePath = AddTestFile(testProject, testfile); + await WaitForFileUpdate(filePath, host); + var request = new FixUsingsRequest(){ FileName = filePath }; + var actual = await host.GetResponse(OmniSharpEndpoints.FixUsings, request); + Assert.Equal(expectedCode.Replace("\r\n", "\n"), actual.Buffer.Replace("\r\n", "\n")); + } + } + } + + [Fact] + public async Task Returns_TypeLookup() + { + const string code = @"class F$$oo {}"; + var testfile = new TestFile("a.cs", code); + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("EmptyProject")) + { + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var filePath = AddTestFile(testProject, testfile); + await WaitForFileUpdate(filePath, host); + var service = host.GetRequestHandler(OmniSharpEndpoints.TypeLookup); + var point = testfile.Content.GetPointFromPosition(); + var request = new TypeLookupRequest + { + FileName = filePath, + Line = point.Line, + Column = point.Offset, + }; + + var actual = await host.GetResponse(OmniSharpEndpoints.TypeLookup, request); + Assert.Equal("Foo", actual.Type); + } + } + } + + [Fact] + public async Task Adds_Multiple_Misc_Files_To_Same_project() + { + const string source1 = +@"class Program +{ + public static void Main(){ + A a = new A(4, $$5); + } +}"; + + const string source2 = +@"class A +{ + A(int a, int b) + { + } +}"; + var testfile1 = new TestFile("file1.cs", source1); + var testfile2 = new TestFile("file2.cs", source2); + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("EmptyProject")) + { + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var filePath1 = AddTestFile(testProject, testfile1); + var filePath2 = AddTestFile(testProject, testfile2); + await WaitForFileUpdate(filePath1, host); + await WaitForFileUpdate(filePath2, host); + var point = testfile1.Content.GetPointFromPosition(); + var request = new SignatureHelpRequest() + { + FileName = filePath1, + Line = point.Line, + Column = point.Offset, + Buffer = testfile1.Content.Code + }; + + var actual = await host.GetResponse(OmniSharpEndpoints.SignatureHelp, request); + Assert.Single(actual.Signatures); + Assert.Equal(1, actual.ActiveParameter); + Assert.Equal(0, actual.ActiveSignature); + Assert.Equal("A", actual.Signatures.ElementAt(0).Name); + Assert.Equal(2, actual.Signatures.ElementAt(0).Parameters.Count()); + } + } + } + + [Fact] + public async Task Handles_File_Deletion() + { + //When the file is deleted the diagnostics must not be returned + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("EmptyProject")) + { + var testfile = new TestFile("a.cs", "class C { b a = new b(); int n }"); + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var filePath = AddTestFile(testProject, testfile); + await WaitForFileUpdate(filePath, host); + var request = new CodeCheckRequest() { FileName = filePath }; + var actual = await host.GetResponse(OmniSharpEndpoints.CodeCheck, request); + Assert.Single(actual.QuickFixes); + + await WaitForFileUpdate(filePath, host, FileWatching.FileChangeType.Delete); + actual = await host.GetResponse(OmniSharpEndpoints.CodeCheck, request); + Assert.Empty(actual.QuickFixes); + } + } + } + + private string AddTestFile(ITestProject testProject, TestFile testfile) + { + return testProject.AddDisposableFile(testfile.FileName, testfile.Content.Text.ToString()); + } + + private async Task WaitForFileUpdate(string filePath, OmniSharpTestHost host, FileWatching.FileChangeType changeType = FileWatching.FileChangeType.Create) + { + var fileChangedService = host.GetRequestHandler(OmniSharpEndpoints.FilesChanged); + await fileChangedService.Handle(new[] + { + new FilesChangedRequest + { + FileName = filePath, + ChangeType = changeType + } + }); + + await Task.Delay(2000); + } + } +} diff --git a/tests/OmniSharp.MiscellaneousFiles.Tests/OmniSharp.MiscellaneousFiles.Tests.csproj b/tests/OmniSharp.MiscellaneousFiles.Tests/OmniSharp.MiscellaneousFiles.Tests.csproj new file mode 100644 index 0000000000..7837179d01 --- /dev/null +++ b/tests/OmniSharp.MiscellaneousFiles.Tests/OmniSharp.MiscellaneousFiles.Tests.csproj @@ -0,0 +1,27 @@ + + + + net461 + AnyCPU + true + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/TestUtility/OmniSharpTestHost.cs b/tests/TestUtility/OmniSharpTestHost.cs index 22ad764a3b..fb78cc746f 100644 --- a/tests/TestUtility/OmniSharpTestHost.cs +++ b/tests/TestUtility/OmniSharpTestHost.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -14,6 +15,7 @@ using OmniSharp.DotNetTest.Models; using OmniSharp.Eventing; using OmniSharp.Mef; +using OmniSharp.MiscellaneousFile; using OmniSharp.Models.WorkspaceInformation; using OmniSharp.MSBuild; using OmniSharp.Options; @@ -40,6 +42,7 @@ public class OmniSharpTestHost : DisposableObject typeof(OmniSharpWorkspace).GetTypeInfo().Assembly, // OmniSharp.Roslyn typeof(RoslynFeaturesHostServicesProvider).GetTypeInfo().Assembly, // OmniSharp.Roslyn.CSharp typeof(CakeProjectSystem).GetTypeInfo().Assembly, // OmniSharp.Cake + typeof(MiscellaneousFilesProjectSystem).GetTypeInfo().Assembly // OmniSharp.MiscellanousFiles }); private readonly TestServiceProvider _serviceProvider; @@ -214,5 +217,12 @@ public void ClearWorkspace() Workspace.RemoveProject(projectId); } } + + public Task GetResponse( + string endpoint, TRequest request) + { + var service = GetRequestHandler>(endpoint); + return service.Handle(request); + } } } diff --git a/tests/TestUtility/TestUtility.csproj b/tests/TestUtility/TestUtility.csproj index 14de90b483..ede88fae3d 100644 --- a/tests/TestUtility/TestUtility.csproj +++ b/tests/TestUtility/TestUtility.csproj @@ -9,6 +9,7 @@ +