diff --git a/src/OmniSharp.Abstractions/FileSystem/FileSystemHelper.cs b/src/OmniSharp.Abstractions/FileSystem/FileSystemHelper.cs new file mode 100644 index 0000000000..e52a0444b8 --- /dev/null +++ b/src/OmniSharp.Abstractions/FileSystem/FileSystemHelper.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Composition; +using System.Linq; +using Microsoft.Extensions.FileSystemGlobbing; +using OmniSharp.Options; + +namespace OmniSharp.FileSystem +{ + [Export, Shared] + public class FileSystemHelper + { + private readonly OmniSharpOptions _omniSharpOptions; + private readonly IOmniSharpEnvironment _omniSharpEnvironment; + + [ImportingConstructor] + public FileSystemHelper(OmniSharpOptions omniSharpOptions, IOmniSharpEnvironment omniSharpEnvironment) + { + _omniSharpOptions = omniSharpOptions; + _omniSharpEnvironment = omniSharpEnvironment; + } + + public IEnumerable GetFiles(string includePattern) => GetFiles(includePattern, _omniSharpEnvironment.TargetDirectory); + + public IEnumerable GetFiles(string includePattern, string targetDirectory) + { + var matcher = new Matcher(); + matcher.AddInclude(includePattern); + + if (_omniSharpOptions.FileOptions.SystemExcludeSearchPatterns != null && _omniSharpOptions.FileOptions.SystemExcludeSearchPatterns.Any()) + { + matcher.AddExcludePatterns(_omniSharpOptions.FileOptions.SystemExcludeSearchPatterns); + } + + if (_omniSharpOptions.FileOptions.ExcludeSearchPatterns != null && _omniSharpOptions.FileOptions.ExcludeSearchPatterns.Any()) + { + matcher.AddExcludePatterns(_omniSharpOptions.FileOptions.ExcludeSearchPatterns); + } + + return matcher.GetResultsInFullPath(targetDirectory); + } + } +} diff --git a/src/OmniSharp.Abstractions/Options/FileOptions.cs b/src/OmniSharp.Abstractions/Options/FileOptions.cs new file mode 100644 index 0000000000..3295f1ebd3 --- /dev/null +++ b/src/OmniSharp.Abstractions/Options/FileOptions.cs @@ -0,0 +1,11 @@ +using System; + +namespace OmniSharp.Options +{ + public class FileOptions + { + public string[] SystemExcludeSearchPatterns { get; set; } = new[] { "**/node_modules/**/*", "**/bin/**/*", "**/obj/**/*", "**/.git/**/*" }; + + public string[] ExcludeSearchPatterns { get; set; } = Array.Empty(); + } +} diff --git a/src/OmniSharp.Abstractions/Options/OmniSharpOptions.cs b/src/OmniSharp.Abstractions/Options/OmniSharpOptions.cs index d2fb9e8ae8..27c5d078d9 100644 --- a/src/OmniSharp.Abstractions/Options/OmniSharpOptions.cs +++ b/src/OmniSharp.Abstractions/Options/OmniSharpOptions.cs @@ -7,5 +7,7 @@ public class OmniSharpOptions public RoslynExtensionsOptions RoslynExtensionsOptions { get; } = new RoslynExtensionsOptions(); public FormattingOptions FormattingOptions { get; } = new FormattingOptions(); + + public FileOptions FileOptions { get; } = new FileOptions(); } } diff --git a/src/OmniSharp.Cake/CakeProjectSystem.cs b/src/OmniSharp.Cake/CakeProjectSystem.cs index 8f981bb745..3faae0fc49 100644 --- a/src/OmniSharp.Cake/CakeProjectSystem.cs +++ b/src/OmniSharp.Cake/CakeProjectSystem.cs @@ -12,11 +12,10 @@ using Microsoft.CodeAnalysis.Scripting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using OmniSharp.Cake.Configuration; using OmniSharp.Cake.Services; +using OmniSharp.FileSystem; using OmniSharp.FileWatching; using OmniSharp.Helpers; -using OmniSharp.Models.UpdateBuffer; using OmniSharp.Models.WorkspaceInformation; using OmniSharp.Roslyn.Utilities; using OmniSharp.Services; @@ -32,6 +31,7 @@ public class CakeProjectSystem : IProjectSystem private readonly IAssemblyLoader _assemblyLoader; private readonly ICakeScriptService _scriptService; private readonly IFileSystemWatcher _fileSystemWatcher; + private readonly FileSystemHelper _fileSystemHelper; private readonly ILogger _logger; private readonly ConcurrentDictionary _projects; private readonly Lazy _compilationOptions; @@ -50,6 +50,7 @@ public CakeProjectSystem( IAssemblyLoader assemblyLoader, ICakeScriptService scriptService, IFileSystemWatcher fileSystemWatcher, + FileSystemHelper fileSystemHelper, ILoggerFactory loggerFactory) { _workspace = workspace ?? throw new ArgumentNullException(nameof(workspace)); @@ -58,6 +59,7 @@ public CakeProjectSystem( _assemblyLoader = assemblyLoader ?? throw new ArgumentNullException(nameof(assemblyLoader)); _scriptService = scriptService ?? throw new ArgumentNullException(nameof(scriptService)); _fileSystemWatcher = fileSystemWatcher ?? throw new ArgumentNullException(nameof(fileSystemWatcher)); + _fileSystemHelper = fileSystemHelper; _logger = loggerFactory?.CreateLogger() ?? throw new ArgumentNullException(nameof(loggerFactory)); _projects = new ConcurrentDictionary(); @@ -72,7 +74,7 @@ public void Initalize(IConfiguration configuration) _logger.LogInformation($"Detecting Cake files in '{_environment.TargetDirectory}'."); // Nothing to do if there are no Cake files - var allCakeFiles = Directory.GetFiles(_environment.TargetDirectory, "*.cake", SearchOption.AllDirectories); + var allCakeFiles = _fileSystemHelper.GetFiles("**/*.cake").ToArray(); if (allCakeFiles.Length == 0) { _logger.LogInformation("Could not find any Cake files"); diff --git a/src/OmniSharp.MSBuild/ProjectSystem.cs b/src/OmniSharp.MSBuild/ProjectSystem.cs index 4240e85bf4..8310a22852 100644 --- a/src/OmniSharp.MSBuild/ProjectSystem.cs +++ b/src/OmniSharp.MSBuild/ProjectSystem.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using OmniSharp.Eventing; +using OmniSharp.FileSystem; using OmniSharp.FileWatching; using OmniSharp.Models.WorkspaceInformation; using OmniSharp.MSBuild.Discovery; @@ -30,6 +31,7 @@ public class ProjectSystem : IProjectSystem private readonly MetadataFileReferenceCache _metadataFileReferenceCache; private readonly IEventEmitter _eventEmitter; private readonly IFileSystemWatcher _fileSystemWatcher; + private readonly FileSystemHelper _fileSystemHelper; private readonly ILoggerFactory _loggerFactory; private readonly ILogger _logger; @@ -56,6 +58,7 @@ public ProjectSystem( MetadataFileReferenceCache metadataFileReferenceCache, IEventEmitter eventEmitter, IFileSystemWatcher fileSystemWatcher, + FileSystemHelper fileSystemHelper, ILoggerFactory loggerFactory) { _environment = environment; @@ -66,6 +69,7 @@ public ProjectSystem( _metadataFileReferenceCache = metadataFileReferenceCache; _eventEmitter = eventEmitter; _fileSystemWatcher = fileSystemWatcher; + _fileSystemHelper = fileSystemHelper; _loggerFactory = loggerFactory; _projectsToProcess = new Queue(); @@ -121,7 +125,7 @@ private IEnumerable GetInitialProjectPaths() // Finally, if there isn't a single solution immediately available, // Just process all of the projects beneath the root path. _solutionFileOrRootPath = _environment.TargetDirectory; - return Directory.GetFiles(_environment.TargetDirectory, "*.csproj", SearchOption.AllDirectories); + return _fileSystemHelper.GetFiles("**/*.csproj"); } private IEnumerable GetProjectPathsFromSolution(string solutionFilePath) diff --git a/src/OmniSharp.Script/ScriptProjectSystem.cs b/src/OmniSharp.Script/ScriptProjectSystem.cs index 6bfb7ae9f6..b3e51c2c39 100644 --- a/src/OmniSharp.Script/ScriptProjectSystem.cs +++ b/src/OmniSharp.Script/ScriptProjectSystem.cs @@ -13,6 +13,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyModel; using Microsoft.Extensions.Logging; +using OmniSharp.FileSystem; using OmniSharp.FileWatching; using OmniSharp.Models.WorkspaceInformation; using OmniSharp.Services; @@ -36,7 +37,7 @@ public class ScriptProjectSystem : IProjectSystem private readonly IOmniSharpEnvironment _env; private readonly ILogger _logger; private readonly IFileSystemWatcher _fileSystemWatcher; - + private readonly FileSystemHelper _fileSystemHelper; private readonly CompilationDependencyResolver _compilationDependencyResolver; private ScriptOptions _scriptOptions; @@ -45,12 +46,13 @@ public class ScriptProjectSystem : IProjectSystem [ImportingConstructor] public ScriptProjectSystem(OmniSharpWorkspace workspace, IOmniSharpEnvironment env, ILoggerFactory loggerFactory, - MetadataFileReferenceCache metadataFileReferenceCache, IFileSystemWatcher fileSystemWatcher) + MetadataFileReferenceCache metadataFileReferenceCache, IFileSystemWatcher fileSystemWatcher, FileSystemHelper fileSystemHelper) { _metadataFileReferenceCache = metadataFileReferenceCache; _workspace = workspace; _env = env; _fileSystemWatcher = fileSystemWatcher; + _fileSystemHelper = fileSystemHelper; _logger = loggerFactory.CreateLogger(); _projects = new ConcurrentDictionary(); @@ -86,7 +88,7 @@ public void Initalize(IConfiguration configuration) _logger.LogInformation($"Detecting CSX files in '{_env.TargetDirectory}'."); // Nothing to do if there are no CSX files - var allCsxFiles = Directory.GetFiles(_env.TargetDirectory, "*.csx", SearchOption.AllDirectories); + var allCsxFiles = _fileSystemHelper.GetFiles("**/*.csx").ToArray(); if (allCsxFiles.Length == 0) { _logger.LogInformation("Could not find any CSX files"); diff --git a/tests/OmniSharp.Tests/FileSystemHelperFacts.cs b/tests/OmniSharp.Tests/FileSystemHelperFacts.cs new file mode 100644 index 0000000000..502ec63eaa --- /dev/null +++ b/tests/OmniSharp.Tests/FileSystemHelperFacts.cs @@ -0,0 +1,114 @@ +using Microsoft.Extensions.Logging; +using OmniSharp.FileSystem; +using OmniSharp.Options; +using OmniSharp.Services; +using System.Linq; +using TestUtility; +using Xunit; + +namespace OmniSharp.Tests +{ + public class FileSystemHelperFacts + { + [Fact] + public void FileSystemHelperFacts_CanExcludeSearchPath_File() + { + var helper = CreateFileSystemHelper("**/ProjectWithSdkProperty.csproj"); + + var msbuildProjectFiles = helper.GetFiles("**/*.csproj"); + Assert.NotEmpty(msbuildProjectFiles); + + var projectWithSdkProperty = msbuildProjectFiles.FirstOrDefault(p => p.Contains("ProjectWithSdkProperty")); + Assert.Null(projectWithSdkProperty); + } + + [Fact] + public void FileSystemHelperFacts_CanExcludeSearchPath_MultipleFiles() + { + var helper = CreateFileSystemHelper("**/MSTestProject.csproj", "**/NUnitTestProject.csproj"); + + var msbuildProjectFiles = helper.GetFiles("**/*.csproj"); + Assert.NotEmpty(msbuildProjectFiles); + + var msTestProject = msbuildProjectFiles.FirstOrDefault(p => p.Contains("MSTestProject")); + Assert.Null(msTestProject); + + var nunitTestProject = msbuildProjectFiles.FirstOrDefault(p => p.Contains("NUnitTestProject")); + Assert.Null(nunitTestProject); + } + + [Fact] + public void FileSystemHelperFacts_CanExcludeSearchPath_Folder() + { + var helper = CreateFileSystemHelper("**/ProjectWithSdkProperty/**/*"); + + var msbuildProjectFiles = helper.GetFiles("**/*.csproj"); + Assert.NotEmpty(msbuildProjectFiles); + + var projectWithSdkProperty = msbuildProjectFiles.FirstOrDefault(p => p.Contains("ProjectWithSdkProperty")); + Assert.Null(projectWithSdkProperty); + } + + [Fact] + public void FileSystemHelperFacts_CanExcludeSearchPath_MultipleFolders() + { + var helper = CreateFileSystemHelper("**/MSTestProject/**/*", "**/NUnitTestProject/**/*"); + + var msbuildProjectFiles = helper.GetFiles("**/*.csproj"); + Assert.NotEmpty(msbuildProjectFiles); + + var msTestProject = msbuildProjectFiles.FirstOrDefault(p => p.Contains("MSTestProject")); + Assert.Null(msTestProject); + + var nunitTestProject = msbuildProjectFiles.FirstOrDefault(p => p.Contains("NUnitTestProject")); + Assert.Null(nunitTestProject); + } + + [Fact] + public void FileSystemHelperFacts_CanExcludeSearchPath_MultipleFolders_BothSystemAndUserPaths() + { + var helper = CreateFileSystemHelper(new[] { "**/MSTestProject/**/*", "**/NUnitTestProject/**/*" }, systemExcludePatterns: new[] { "**/ProjectWithSdkProperty/**/*" }); + + var msbuildProjectFiles = helper.GetFiles("**/*.csproj"); + Assert.NotEmpty(msbuildProjectFiles); + + var msTestProject = msbuildProjectFiles.FirstOrDefault(p => p.Contains("MSTestProject")); + Assert.Null(msTestProject); + + var nunitTestProject = msbuildProjectFiles.FirstOrDefault(p => p.Contains("NUnitTestProject")); + Assert.Null(nunitTestProject); + + var projectWithSdkProperty = msbuildProjectFiles.FirstOrDefault(p => p.Contains("ProjectWithSdkProperty")); + Assert.Null(projectWithSdkProperty); + } + + [Fact] + public void FileSystemHelperFacts_CanHandleInvalidPath() + { + var helper = CreateFileSystemHelper("!@@#$$@%&&*()_+"); + + var ex = Record.Exception(() => helper.GetFiles("**/*.csproj")); + Assert.Null(ex); + } + + private FileSystemHelper CreateFileSystemHelper(params string[] excludePatterns) + { + var environment = new OmniSharpEnvironment(TestAssets.Instance.TestAssetsFolder, 1000, LogLevel.Information, null); + var options = new OmniSharpOptions(); + options.FileOptions.ExcludeSearchPatterns = excludePatterns; + var helper = new FileSystemHelper(options, environment); + return helper; + } + + private FileSystemHelper CreateFileSystemHelper(string[] excludePatterns, string[] systemExcludePatterns) + { + var environment = new OmniSharpEnvironment(TestAssets.Instance.TestAssetsFolder, 1000, LogLevel.Information, null); + var options = new OmniSharpOptions(); + options.FileOptions.ExcludeSearchPatterns = excludePatterns; + options.FileOptions.SystemExcludeSearchPatterns = systemExcludePatterns; + + var helper = new FileSystemHelper(options, environment); + return helper; + } + } +}