diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7a634ebe77..6f978a0b94 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,8 +3,9 @@ All changes to the project will be documented in this file.
## [1.35.1] - not yet released
* Fixed not supported exception when trying to decompile a BCL assembly on Mono. For now we do not try to resolve implementation assembly from a ref assembly (PR: [#1767](https://github.com/OmniSharp/omnisharp-roslyn/pull/1767))
-* Added support for generic classes in test runner ([omnisharp-vscode#3722](https://github.com/OmniSharp/omnisharp-vscode/issues/3722), PR: [#1768](https://github.com/OmniSharp/omnisharp-roslyn/pull/1768))
+* Added support for generic classes in test runner ([omnisharp-vscode#3722](https://github.com/OmniSharp/omnisharp-vscode/issues/3722), PR: [#1768](https://github.com/OmniSharp/omnisharp-roslyn/pull/1768))
* Improved autocompletion performance (PR: [#1761](https://github.com/OmniSharp/omnisharp-roslyn/pull/1761))
+* Move to Roslyn's .editorconfig support ([#1657](https://github.com/OmniSharp/omnisharp-roslyn/issues/1657), PR: [#1771](https://github.com/OmniSharp/omnisharp-roslyn/pull/1771))
## [1.35.0] - 2020-04-10
* Support for `` and `` (PR: [#1739](https://github.com/OmniSharp/omnisharp-roslyn/pull/1739))
diff --git a/build/Packages.props b/build/Packages.props
index ebb70cb9a8..8c1bbcf8f1 100644
--- a/build/Packages.props
+++ b/build/Packages.props
@@ -52,7 +52,6 @@
-
diff --git a/src/OmniSharp.Cake/CakeProjectSystem.cs b/src/OmniSharp.Cake/CakeProjectSystem.cs
index 667e21eeb1..d547d77e41 100644
--- a/src/OmniSharp.Cake/CakeProjectSystem.cs
+++ b/src/OmniSharp.Cake/CakeProjectSystem.cs
@@ -1,10 +1,12 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Composition;
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Text;
using System.Threading.Tasks;
using Cake.Scripting.Abstractions.Models;
using Microsoft.CodeAnalysis;
@@ -18,6 +20,7 @@
using OmniSharp.Helpers;
using OmniSharp.Mef;
using OmniSharp.Models.WorkspaceInformation;
+using OmniSharp.Roslyn.EditorConfig;
using OmniSharp.Roslyn.Utilities;
using OmniSharp.Services;
@@ -280,8 +283,21 @@ private ProjectInfo GetProject(CakeScript cakeScript, string filePath)
throw new InvalidOperationException($"Could not get host object type: {cakeScript.Host.TypeName}.");
}
+ var projectId = ProjectId.CreateNewId(Guid.NewGuid().ToString());
+ var analyzerConfigDocuments = _workspace.EditorConfigEnabled
+ ? EditorConfigFinder
+ .GetEditorConfigPaths(filePath)
+ .Select(path =>
+ DocumentInfo.Create(
+ DocumentId.CreateNewId(projectId),
+ name: ".editorconfig",
+ loader: new FileTextLoader(path, Encoding.UTF8),
+ filePath: path))
+ .ToImmutableArray()
+ : ImmutableArray.Empty;
+
return ProjectInfo.Create(
- id: ProjectId.CreateNewId(Guid.NewGuid().ToString()),
+ id: projectId,
version: VersionStamp.Create(),
name: name,
filePath: filePath,
@@ -292,7 +308,8 @@ private ProjectInfo GetProject(CakeScript cakeScript, string filePath)
metadataReferences: GetMetadataReferences(cakeScript.References),
// TODO: projectReferences?
isSubmission: true,
- hostObjectType: hostObjectType);
+ hostObjectType: hostObjectType)
+ .WithAnalyzerConfigDocuments(analyzerConfigDocuments);
}
private IEnumerable GetMetadataReferences(IEnumerable references)
diff --git a/src/OmniSharp.Host/WorkspaceInitializer.cs b/src/OmniSharp.Host/WorkspaceInitializer.cs
index 2a98ae407d..476e7cd78c 100644
--- a/src/OmniSharp.Host/WorkspaceInitializer.cs
+++ b/src/OmniSharp.Host/WorkspaceInitializer.cs
@@ -29,6 +29,8 @@ public static void Initialize(IServiceProvider serviceProvider, CompositionHost
projectEventForwarder.Initialize();
var projectSystems = compositionHost.GetExports();
+ workspace.EditorConfigEnabled = options.CurrentValue.FormattingOptions.EnableEditorConfigSupport;
+
foreach (var projectSystem in projectSystems)
{
try
diff --git a/src/OmniSharp.MSBuild/ProjectFile/ItemNames.cs b/src/OmniSharp.MSBuild/ProjectFile/ItemNames.cs
index 47ad967f30..785f9df460 100644
--- a/src/OmniSharp.MSBuild/ProjectFile/ItemNames.cs
+++ b/src/OmniSharp.MSBuild/ProjectFile/ItemNames.cs
@@ -5,6 +5,7 @@ internal static class ItemNames
public const string Analyzer = nameof(Analyzer);
public const string AdditionalFiles = nameof(AdditionalFiles);
public const string Compile = nameof(Compile);
+ public const string EditorConfigFiles = nameof(EditorConfigFiles);
public const string PackageReference = nameof(PackageReference);
public const string ProjectReference = nameof(ProjectReference);
public const string ReferencePath = nameof(ReferencePath);
diff --git a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.ProjectData.cs b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.ProjectData.cs
index 6602d98f64..643967befc 100644
--- a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.ProjectData.cs
+++ b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.ProjectData.cs
@@ -49,6 +49,8 @@ private class ProjectData
public ImmutableArray PackageReferences { get; }
public ImmutableArray Analyzers { get; }
public ImmutableArray AdditionalFiles { get; }
+ public ImmutableArray AnalyzerConfigFiles { get; }
+
public RuleSet RuleSet { get; }
public ImmutableDictionary ReferenceAliases { get; }
public ImmutableDictionary ProjectReferenceAliases { get; }
@@ -70,6 +72,7 @@ private ProjectData()
PackageReferences = ImmutableArray.Empty;
Analyzers = ImmutableArray.Empty;
AdditionalFiles = ImmutableArray.Empty;
+ AnalyzerConfigFiles = ImmutableArray.Empty;
ReferenceAliases = ImmutableDictionary.Empty;
ProjectReferenceAliases = ImmutableDictionary.Empty;
}
@@ -154,6 +157,7 @@ private ProjectData(
ImmutableArray packageReferences,
ImmutableArray analyzers,
ImmutableArray additionalFiles,
+ ImmutableArray analyzerConfigFiles,
bool treatWarningsAsErrors,
string defaultNamespace,
bool runAnalyzers,
@@ -171,6 +175,7 @@ private ProjectData(
PackageReferences = packageReferences.EmptyIfDefault();
Analyzers = analyzers.EmptyIfDefault();
AdditionalFiles = additionalFiles.EmptyIfDefault();
+ AnalyzerConfigFiles = analyzerConfigFiles.EmptyIfDefault();
ReferenceAliases = referenceAliases;
ProjectReferenceAliases = projectReferenceAliases;
}
@@ -317,13 +322,14 @@ public static ProjectData Create(MSB.Execution.ProjectInstance projectInstance)
var packageReferences = GetPackageReferences(projectInstance.GetItems(ItemNames.PackageReference));
var analyzers = GetFullPaths(projectInstance.GetItems(ItemNames.Analyzer));
var additionalFiles = GetFullPaths(projectInstance.GetItems(ItemNames.AdditionalFiles));
+ var editorConfigFiles = GetFullPaths(projectInstance.GetItems(ItemNames.EditorConfigFiles));
return new ProjectData(guid, name,
assemblyName, targetPath, outputPath, intermediateOutputPath, projectAssetsFile,
configuration, platform, targetFramework, targetFrameworks,
outputKind, languageVersion, nullableContextOptions, allowUnsafeCode, checkForOverflowUnderflow, documentationFile, preprocessorSymbolNames, suppressedDiagnosticIds,
signAssembly, assemblyOriginatorKeyFile,
- sourceFiles, projectReferences.ToImmutable(), references.ToImmutable(), packageReferences, analyzers, additionalFiles, treatWarningsAsErrors, defaultNamespace, runAnalyzers, runAnalyzersDuringLiveAnalysis, ruleset,
+ sourceFiles, projectReferences.ToImmutable(), references.ToImmutable(), packageReferences, analyzers, additionalFiles, editorConfigFiles, treatWarningsAsErrors, defaultNamespace, runAnalyzers, runAnalyzersDuringLiveAnalysis, ruleset,
referenceAliases.ToImmutableDictionary(), projectReferenceAliases.ToImmutable());
}
diff --git a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs
index 2d9651af76..743ff242ce 100644
--- a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs
+++ b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs
@@ -52,6 +52,7 @@ internal partial class ProjectFileInfo
public ImmutableArray PackageReferences => _data.PackageReferences;
public ImmutableArray Analyzers => _data.Analyzers;
public ImmutableArray AdditionalFiles => _data.AdditionalFiles;
+ public ImmutableArray AnalyzerConfigFiles => _data.AnalyzerConfigFiles;
public ImmutableDictionary ReferenceAliases => _data.ReferenceAliases;
public ImmutableDictionary ProjectReferenceAliases => _data.ProjectReferenceAliases;
public bool TreatWarningsAsErrors => _data.TreatWarningsAsErrors;
@@ -77,7 +78,7 @@ internal static ProjectFileInfo CreateEmpty(string filePath)
{
var id = ProjectId.CreateNewId(debugName: filePath);
- return new ProjectFileInfo(new ProjectIdInfo(id, isDefinedInSolution:false), filePath, data: null);
+ return new ProjectFileInfo(new ProjectIdInfo(id, isDefinedInSolution: false), filePath, data: null);
}
internal static ProjectFileInfo CreateNoBuild(string filePath, ProjectLoader loader)
@@ -85,7 +86,7 @@ internal static ProjectFileInfo CreateNoBuild(string filePath, ProjectLoader loa
var id = ProjectId.CreateNewId(debugName: filePath);
var project = loader.EvaluateProjectFile(filePath);
var data = ProjectData.Create(project);
- //we are not reading the solution here
+ //we are not reading the solution here
var projectIdInfo = new ProjectIdInfo(id, isDefinedInSolution: false);
return new ProjectFileInfo(projectIdInfo, filePath, data);
@@ -127,7 +128,7 @@ public static (ProjectFileInfo, ImmutableArray, ProjectLoaded
var data = ProjectData.Create(projectInstance);
var projectFileInfo = new ProjectFileInfo(ProjectIdInfo, FilePath, data);
- var eventArgs = new ProjectLoadedEventArgs(Id, projectInstance, diagnostics, isReload: true, ProjectIdInfo.IsDefinedInSolution,data.References);
+ var eventArgs = new ProjectLoadedEventArgs(Id, projectInstance, diagnostics, isReload: true, ProjectIdInfo.IsDefinedInSolution, data.References);
return (projectFileInfo, diagnostics, eventArgs);
}
diff --git a/src/OmniSharp.MSBuild/ProjectManager.cs b/src/OmniSharp.MSBuild/ProjectManager.cs
index d14b8bd723..c90c564ae4 100644
--- a/src/OmniSharp.MSBuild/ProjectManager.cs
+++ b/src/OmniSharp.MSBuild/ProjectManager.cs
@@ -176,7 +176,7 @@ private async Task ProcessLoopAsync(CancellationToken cancellationToken)
await Task.Delay(LoopDelay, cancellationToken);
ProcessQueue(cancellationToken);
}
- catch(Exception ex)
+ catch (Exception ex)
{
_logger.LogError($"Error occurred while processing project updates: {ex}");
}
@@ -377,6 +377,14 @@ private void WatchProjectFiles(ProjectFileInfo projectFileInfo)
QueueProjectUpdate(projectFileInfo.FilePath, allowAutoRestore: true, projectFileInfo.ProjectIdInfo);
});
+ if (_workspace.EditorConfigEnabled)
+ {
+ _fileSystemWatcher.Watch(".editorconfig", (file, changeType) =>
+ {
+ QueueProjectUpdate(projectFileInfo.FilePath, allowAutoRestore: false, projectFileInfo.ProjectIdInfo);
+ });
+ }
+
if (projectFileInfo.RuleSet?.FilePath != null)
{
_fileSystemWatcher.Watch(projectFileInfo.RuleSet.FilePath, (file, changeType) =>
@@ -436,6 +444,7 @@ private void UpdateProject(string projectFilePath)
UpdateReferences(project, projectFileInfo.ProjectReferences, projectFileInfo.References);
UpdateAnalyzerReferences(project, projectFileInfo);
UpdateAdditionalFiles(project, projectFileInfo.AdditionalFiles);
+ UpdateAnalyzerConfigFiles(project, projectFileInfo.AnalyzerConfigFiles);
UpdateProjectProperties(project, projectFileInfo);
_workspace.TryPromoteMiscellaneousDocumentsToProject(project);
@@ -483,10 +492,32 @@ private void UpdateAdditionalFiles(Project project, IList additionalFile
foreach (var file in additionalFiles)
{
- if (File.Exists(file))
- {
+ if (File.Exists(file))
+ {
_workspace.AddAdditionalDocument(project.Id, file);
- }
+ }
+ }
+ }
+
+ private void UpdateAnalyzerConfigFiles(Project project, IList analyzerConfigFiles)
+ {
+ if (!_workspace.EditorConfigEnabled)
+ {
+ return;
+ }
+
+ var currentAnalyzerConfigDocuments = project.AnalyzerConfigDocuments;
+ foreach (var document in currentAnalyzerConfigDocuments)
+ {
+ _workspace.RemoveAnalyzerConfigDocument(document.Id);
+ }
+
+ foreach (var file in analyzerConfigFiles)
+ {
+ if (File.Exists(file))
+ {
+ _workspace.AddAnalyzerConfigDocument(project.Id, file);
+ }
}
}
diff --git a/src/OmniSharp.Roslyn.CSharp/OmniSharp.Roslyn.CSharp.csproj b/src/OmniSharp.Roslyn.CSharp/OmniSharp.Roslyn.CSharp.csproj
index 2f38e3b5ce..e7787704e8 100644
--- a/src/OmniSharp.Roslyn.CSharp/OmniSharp.Roslyn.CSharp.csproj
+++ b/src/OmniSharp.Roslyn.CSharp/OmniSharp.Roslyn.CSharp.csproj
@@ -20,6 +20,5 @@
-
diff --git a/src/OmniSharp.Roslyn.CSharp/Services/EditorConfigWorkspaceOptionsProvider.cs b/src/OmniSharp.Roslyn.CSharp/Services/EditorConfigWorkspaceOptionsProvider.cs
deleted file mode 100644
index 94a33a7906..0000000000
--- a/src/OmniSharp.Roslyn.CSharp/Services/EditorConfigWorkspaceOptionsProvider.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using System.Composition;
-using System.IO;
-using Microsoft.CodeAnalysis.Options;
-using Microsoft.Extensions.Logging;
-using OmniSharp.Options;
-using OmniSharp.Roslyn.CSharp.Services.Formatting.EditorConfig;
-using OmniSharp.Roslyn.Options;
-
-namespace OmniSharp.Roslyn.CSharp.Services
-{
- [Export(typeof(IWorkspaceOptionsProvider)), Shared]
- public class EditorConfigWorkspaceOptionsProvider : IWorkspaceOptionsProvider
- {
- private readonly ILoggerFactory _loggerFactory;
-
- public int Order => 200;
-
- [ImportingConstructor]
- public EditorConfigWorkspaceOptionsProvider(ILoggerFactory loggerFactory)
- {
- _loggerFactory = loggerFactory;
- }
-
- public OptionSet Process(OptionSet currentOptionSet, OmniSharpOptions omnisharpOptions, IOmniSharpEnvironment omnisharpEnvironment)
- {
- if (!omnisharpOptions.FormattingOptions.EnableEditorConfigSupport) return currentOptionSet;
-
- // this is a dummy file that doesn't exist, but we simply want to tell .editorconfig to load *.cs specific settings
- var filePath = Path.Combine(omnisharpEnvironment.TargetDirectory, "omnisharp.cs");
- var changedOptionSet = currentOptionSet.WithEditorConfigOptions(filePath, _loggerFactory).GetAwaiter().GetResult();
- return changedOptionSet;
- }
- }
-}
diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Formatting/EditorConfig/EditorConfigOptionExtensions.cs b/src/OmniSharp.Roslyn.CSharp/Services/Formatting/EditorConfig/EditorConfigOptionExtensions.cs
deleted file mode 100644
index fbb8de4d76..0000000000
--- a/src/OmniSharp.Roslyn.CSharp/Services/Formatting/EditorConfig/EditorConfigOptionExtensions.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.Options;
-using Microsoft.Extensions.Logging;
-using Microsoft.VisualStudio.CodingConventions;
-
-namespace OmniSharp.Roslyn.CSharp.Services.Formatting.EditorConfig
-{
- internal static class EditorConfigOptionExtensions
- {
- public static async Task WithEditorConfigOptions(this OptionSet optionSet, string path, ILoggerFactory loggerFactory)
- {
- if (!Path.IsPathRooted(path))
- {
- path = Directory.GetCurrentDirectory();
- }
-
- var codingConventionsManager = CodingConventionsManagerFactory.CreateCodingConventionsManager();
- var optionsApplier = new EditorConfigOptionsApplier(loggerFactory);
- var context = await codingConventionsManager.GetConventionContextAsync(path, CancellationToken.None);
-
- if (context != null && context.CurrentConventions != null)
- {
- return optionsApplier.ApplyConventions(optionSet, context.CurrentConventions, LanguageNames.CSharp);
- }
-
-
- return optionSet;
- }
- }
-}
diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Formatting/EditorConfig/EditorConfigOptionsApplier.cs b/src/OmniSharp.Roslyn.CSharp/Services/Formatting/EditorConfig/EditorConfigOptionsApplier.cs
deleted file mode 100644
index 7e1fe5c721..0000000000
--- a/src/OmniSharp.Roslyn.CSharp/Services/Formatting/EditorConfig/EditorConfigOptionsApplier.cs
+++ /dev/null
@@ -1,111 +0,0 @@
-// adapted from https://github.com/dotnet/format/blob/master/src/Utilities/EditorConfigOptionsApplier.cs
-// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using Microsoft.CodeAnalysis.CodeStyle;
-using Microsoft.CodeAnalysis.CSharp.Formatting;
-using Microsoft.CodeAnalysis.Formatting;
-using Microsoft.CodeAnalysis.Options;
-using Microsoft.CodeAnalysis.Simplification;
-using Microsoft.Extensions.Logging;
-using Microsoft.VisualStudio.CodingConventions;
-
-namespace OmniSharp.Roslyn.CSharp.Services.Formatting.EditorConfig
-{
- internal class EditorConfigOptionsApplier
- {
- private static readonly List<(IOption, OptionStorageLocation, MethodInfo)> _optionsWithStorage;
- private readonly ILogger _logger;
-
- static EditorConfigOptionsApplier()
- {
- _optionsWithStorage = new List<(IOption, OptionStorageLocation, MethodInfo)>();
- _optionsWithStorage.AddRange(GetPropertyBasedOptionsWithStorageFromTypes(typeof(FormattingOptions), typeof(CSharpFormattingOptions), typeof(SimplificationOptions), typeof(SimplificationOptions).Assembly.GetType("Microsoft.CodeAnalysis.Simplification.NamingStyleOptions")));
- _optionsWithStorage.AddRange(GetFieldBasedOptionsWithStorageFromTypes(typeof(CodeStyleOptions), typeof(CSharpFormattingOptions).Assembly.GetType("Microsoft.CodeAnalysis.CSharp.CodeStyle.CSharpCodeStyleOptions")));
- }
-
- public EditorConfigOptionsApplier(ILoggerFactory loggerFactory)
- {
- _logger = loggerFactory.CreateLogger();
- }
-
- public OptionSet ApplyConventions(OptionSet optionSet, ICodingConventionsSnapshot codingConventions, string languageName)
- {
- try
- {
- var adjustedConventions = codingConventions.AllRawConventions.ToDictionary(kvp => kvp.Key, kvp => (string)kvp.Value);
- _logger.LogDebug($"All raw discovered .editorconfig options: {string.Join(Environment.NewLine, adjustedConventions.Select(kvp => $"{kvp.Key}={kvp.Value}"))}");
-
- foreach (var optionWithStorage in _optionsWithStorage)
- {
- if (TryGetConventionValue(optionWithStorage, adjustedConventions, out var value))
- {
- var option = optionWithStorage.Item1;
- _logger.LogTrace($"Applying .editorconfig option {option.Name}");
- var optionKey = new OptionKey(option, option.IsPerLanguage ? languageName : null);
- optionSet = optionSet.WithChangedOption(optionKey, value);
- }
- }
- }
- catch (Exception e)
- {
- _logger.LogError(e, "There was an error when applying .editorconfig options.");
- }
-
- return optionSet;
- }
-
- internal static IEnumerable<(IOption, OptionStorageLocation, MethodInfo)> GetPropertyBasedOptionsWithStorageFromTypes(params Type[] types)
- => types
- .SelectMany(t => t.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.GetProperty))
- .Where(p => typeof(IOption).IsAssignableFrom(p.PropertyType)).Select(p => (IOption)p.GetValue(null))
- .Select(GetOptionWithStorage).Where(ows => ows.Item2 != null);
-
- internal static IEnumerable<(IOption, OptionStorageLocation, MethodInfo)> GetFieldBasedOptionsWithStorageFromTypes(params Type[] types)
- => types
- .SelectMany(t => t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static))
- .Where(p => typeof(IOption).IsAssignableFrom(p.FieldType)).Select(p => (IOption)p.GetValue(null))
- .Select(GetOptionWithStorage).Where(ows => ows.Item2 != null);
-
- internal static (IOption, OptionStorageLocation, MethodInfo) GetOptionWithStorage(IOption option)
- {
- var editorConfigStorage = !option.StorageLocations.IsDefaultOrEmpty
- ? option.StorageLocations.FirstOrDefault(IsEditorConfigStorage)
- : null;
-
- var tryGetOptionMethod = editorConfigStorage?.GetType().GetMethod("TryGetOption", new[] { typeof(IReadOnlyDictionary), typeof(Type), typeof(object).MakeByRefType() });
- return (option, editorConfigStorage, tryGetOptionMethod);
- }
-
- internal static bool IsEditorConfigStorage(OptionStorageLocation storageLocation)
- {
- if (storageLocation.GetType().FullName.StartsWith("Microsoft.CodeAnalysis.Options.EditorConfigStorageLocation"))
- {
- return true;
- }
-
- if (storageLocation.GetType().FullName.StartsWith("Microsoft.CodeAnalysis.Options.NamingStylePreferenceEditorConfigStorageLocation"))
- {
- return true;
- }
-
- return false;
- }
-
- internal static bool TryGetConventionValue((IOption, OptionStorageLocation, MethodInfo) optionWithStorage, Dictionary adjustedConventions, out object value)
- {
- var (option, editorConfigStorage, tryGetOptionMethod) = optionWithStorage;
- value = null;
-
- var args = new object[] { adjustedConventions, option.Type, value };
-
- var isOptionPresent = (bool)tryGetOptionMethod.Invoke(editorConfigStorage, args);
- value = args[2];
-
- return isOptionPresent;
- }
- }
-}
diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Formatting/FormattingWorker.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Formatting/FormattingWorker.cs
index 3b32dfd18b..0c7d4e9b02 100644
--- a/src/OmniSharp.Roslyn.CSharp/Workers/Formatting/FormattingWorker.cs
+++ b/src/OmniSharp.Roslyn.CSharp/Workers/Formatting/FormattingWorker.cs
@@ -1,17 +1,13 @@
using System.Collections.Generic;
using System.Linq;
-using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Formatting;
-using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Text;
using Microsoft.Extensions.Logging;
-using Microsoft.VisualStudio.CodingConventions;
using OmniSharp.Models;
using OmniSharp.Options;
-using OmniSharp.Roslyn.CSharp.Services.Formatting.EditorConfig;
using OmniSharp.Roslyn.Utilities;
namespace OmniSharp.Roslyn.CSharp.Workers.Formatting
@@ -103,9 +99,10 @@ public static async Task> GetFormattedTe
private static async Task FormatDocument(Document document, OmniSharpOptions omnisharpOptions, ILoggerFactory loggerFactory, TextSpan? textSpan = null)
{
+ // If we are not using .editorconfig for formatting options then we can avoid any overhead of calculating document options.
var optionSet = omnisharpOptions.FormattingOptions.EnableEditorConfigSupport
- ? await document.Project.Solution.Workspace.Options.WithEditorConfigOptions(document.FilePath, loggerFactory)
- : document.Project.Solution.Workspace.Options;
+ ? await document.GetOptionsAsync()
+ : document.Project.Solution.Options;
var newDocument = textSpan != null ? await Formatter.FormatAsync(document, textSpan.Value, optionSet) : await Formatter.FormatAsync(document, optionSet);
if (omnisharpOptions.FormattingOptions.OrganizeImports)
diff --git a/src/OmniSharp.Roslyn/EditorConfig/EditorConfigFinder.cs b/src/OmniSharp.Roslyn/EditorConfig/EditorConfigFinder.cs
new file mode 100644
index 0000000000..ddb66c4439
--- /dev/null
+++ b/src/OmniSharp.Roslyn/EditorConfig/EditorConfigFinder.cs
@@ -0,0 +1,61 @@
+// adapted from https://github.com/dotnet/format/blob/d8a66bbcc6b6b9e769eb168cb384b44328786f7b/src/Utilities/EditorConfigFinder.cs
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
+
+using System.IO;
+using System.Collections.Immutable;
+using System.Linq;
+
+namespace OmniSharp.Roslyn.EditorConfig
+{
+ public static class EditorConfigFinder
+ {
+ public static ImmutableArray GetEditorConfigPaths(string path)
+ {
+ // If we are passed a filename then try to parse out the path
+ if (!Directory.Exists(path) &&
+ !TryGetDirectoryPath(path, out path))
+ {
+ return ImmutableArray.Empty;
+ }
+
+ if (!Directory.Exists(path))
+ {
+ return ImmutableArray.Empty;
+ }
+
+ var directory = new DirectoryInfo(path);
+
+ var editorConfigPaths = directory.GetFiles(".editorconfig", SearchOption.AllDirectories)
+ .Select(file => file.FullName)
+ .ToList();
+
+ try
+ {
+ while (directory.Parent is object)
+ {
+ directory = directory.Parent;
+ editorConfigPaths.AddRange(
+ directory.GetFiles(".editorconfig", SearchOption.TopDirectoryOnly)
+ .Select(file => file.FullName));
+ }
+ }
+ catch { }
+
+ return editorConfigPaths.ToImmutableArray();
+ }
+
+ private static bool TryGetDirectoryPath(string path, out string directoryPath)
+ {
+ try
+ {
+ directoryPath = Path.GetDirectoryName(path);
+ return true;
+ }
+ catch
+ {
+ directoryPath = default;
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs b/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs
index d8213e92a6..a6d423b453 100644
--- a/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs
+++ b/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs
@@ -16,6 +16,7 @@
using OmniSharp.FileSystem;
using OmniSharp.FileWatching;
using OmniSharp.Roslyn;
+using OmniSharp.Roslyn.EditorConfig;
using OmniSharp.Roslyn.Utilities;
using OmniSharp.Utilities;
@@ -25,6 +26,7 @@ namespace OmniSharp
public class OmniSharpWorkspace : Workspace
{
public bool Initialized { get; set; }
+ public bool EditorConfigEnabled { get; set; }
public BufferManager BufferManager { get; private set; }
private readonly ILogger _logger;
@@ -103,6 +105,21 @@ public DocumentId TryAddMiscellaneousDocument(string filePath, string language)
var projectInfo = miscDocumentsProjectInfos.GetOrAdd(language, (lang) => CreateMiscFilesProject(lang));
var documentId = AddDocument(projectInfo.Id, filePath);
_logger.LogInformation($"Miscellaneous file: {filePath} added to workspace");
+
+ if (!EditorConfigEnabled)
+ {
+ return documentId;
+ }
+
+ var analyzerConfigFiles = projectInfo.AnalyzerConfigDocuments.Select(document => document.FilePath);
+ var newAnalyzerConfigFiles = EditorConfigFinder
+ .GetEditorConfigPaths(filePath)
+ .Except(analyzerConfigFiles);
+ foreach (var analyzerConfigFile in newAnalyzerConfigFiles)
+ {
+ AddAnalyzerConfigDocument(projectInfo.Id, analyzerConfigFile);
+ }
+
return documentId;
}
@@ -153,7 +170,7 @@ public void TryPromoteMiscellaneousDocumentsToProject(Project project)
public void UpdateDiagnosticOptionsForProject(ProjectId projectId, ImmutableDictionary rules)
{
var project = this.CurrentSolution.GetProject(projectId);
- OnCompilationOptionsChanged(projectId, project.CompilationOptions.WithSpecificDiagnosticOptions(rules));
+ OnCompilationOptionsChanged(projectId, project.CompilationOptions.WithSpecificDiagnosticOptions(rules));
}
private ProjectInfo CreateMiscFilesProject(string language)
@@ -222,7 +239,7 @@ internal DocumentId AddDocument(DocumentId documentId, Project project, string f
// folder computation is best effort. in case of exceptions, we back out because it's not essential for core features
try
{
- // find the relative path from project file to our document
+ // find the relative path from project file to our document
var relativeDocumentPath = FileSystemHelper.GetRelativePath(fullPath, basePath);
// only set document's folders if
@@ -449,13 +466,13 @@ public void SetAnalyzerReferences(ProjectId id, ImmutableArray project.AnalyzerReferences.All(oldRef => oldRef.Display != newRef.Display));
var refsToRemove = project.AnalyzerReferences.Where(newRef => analyzerReferences.All(oldRef => oldRef.Display != newRef.Display));
- foreach(var toAdd in refsToAdd)
+ foreach (var toAdd in refsToAdd)
{
_logger.LogInformation($"Adding analyzer reference: {toAdd.FullPath}");
base.OnAnalyzerReferenceAdded(id, toAdd);
}
- foreach(var toRemove in refsToRemove)
+ foreach (var toRemove in refsToRemove)
{
_logger.LogInformation($"Removing analyzer reference: {toRemove.FullPath}");
base.OnAnalyzerReferenceRemoved(id, toRemove);
@@ -470,11 +487,24 @@ public void AddAdditionalDocument(ProjectId projectId, string filePath)
OnAdditionalDocumentAdded(documentInfo);
}
+ public void AddAnalyzerConfigDocument(ProjectId projectId, string filePath)
+ {
+ var documentId = DocumentId.CreateNewId(projectId);
+ var loader = new OmniSharpTextLoader(filePath);
+ var documentInfo = DocumentInfo.Create(documentId, Path.GetFileName(filePath), filePath: filePath, loader: loader);
+ OnAnalyzerConfigDocumentAdded(documentInfo);
+ }
+
public void RemoveAdditionalDocument(DocumentId documentId)
{
OnAdditionalDocumentRemoved(documentId);
}
+ public void RemoveAnalyzerConfigDocument(DocumentId documentId)
+ {
+ OnAnalyzerConfigDocumentRemoved(documentId);
+ }
+
protected override void ApplyProjectChanges(ProjectChanges projectChanges)
{
// since Roslyn currently doesn't handle DefaultNamespace changes via ApplyProjectChanges
diff --git a/src/OmniSharp.Script/ScriptContextProvider.cs b/src/OmniSharp.Script/ScriptContextProvider.cs
index 830af001a4..f4ae9e61b6 100644
--- a/src/OmniSharp.Script/ScriptContextProvider.cs
+++ b/src/OmniSharp.Script/ScriptContextProvider.cs
@@ -70,7 +70,7 @@ public ScriptContextProvider(ILoggerFactory loggerFactory, IOmniSharpEnvironment
});
}
- public ScriptContext CreateScriptContext(ScriptOptions scriptOptions, string[] allCsxFiles)
+ public ScriptContext CreateScriptContext(ScriptOptions scriptOptions, string[] allCsxFiles, bool editorConfigEnabled)
{
var currentDomainAssemblies = AppDomain.CurrentDomain.GetAssemblies();
@@ -134,7 +134,7 @@ public ScriptContext CreateScriptContext(ScriptOptions scriptOptions, string[] a
AddMetadataReference(metadataReferences, inheritedCompileLib.Location);
}
- var scriptProjectProvider = new ScriptProjectProvider(scriptOptions, _env, _loggerFactory, isDesktopClr);
+ var scriptProjectProvider = new ScriptProjectProvider(scriptOptions, _env, _loggerFactory, isDesktopClr, editorConfigEnabled);
return new ScriptContext(scriptProjectProvider, metadataReferences, compilationDependencies, _defaultGlobalsType);
}
diff --git a/src/OmniSharp.Script/ScriptProjectProvider.cs b/src/OmniSharp.Script/ScriptProjectProvider.cs
index 5f8afc0d73..6109b0d461 100644
--- a/src/OmniSharp.Script/ScriptProjectProvider.cs
+++ b/src/OmniSharp.Script/ScriptProjectProvider.cs
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Text;
using Dotnet.Script.DependencyModel.NuGet;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
@@ -10,6 +12,7 @@
using Microsoft.CodeAnalysis.Scripting.Hosting;
using Microsoft.Extensions.Logging;
using OmniSharp.Helpers;
+using OmniSharp.Roslyn.EditorConfig;
using OmniSharp.Roslyn.Utilities;
namespace OmniSharp.Script
@@ -48,8 +51,9 @@ public class ScriptProjectProvider
private readonly IOmniSharpEnvironment _env;
private readonly ILogger _logger;
private readonly bool _isDesktopClr;
+ private readonly bool _editorConfigEnabled;
- public ScriptProjectProvider(ScriptOptions scriptOptions, IOmniSharpEnvironment env, ILoggerFactory loggerFactory, bool isDesktopClr)
+ public ScriptProjectProvider(ScriptOptions scriptOptions, IOmniSharpEnvironment env, ILoggerFactory loggerFactory, bool isDesktopClr, bool editorConfigEnabled)
{
_scriptOptions = scriptOptions ?? throw new ArgumentNullException(nameof(scriptOptions));
_env = env ?? throw new ArgumentNullException(nameof(env));
@@ -58,6 +62,7 @@ public ScriptProjectProvider(ScriptOptions scriptOptions, IOmniSharpEnvironment
_compilationOptions = new Lazy(CreateCompilationOptions);
_commandLineArgs = new Lazy(CreateCommandLineArguments);
_isDesktopClr = isDesktopClr;
+ _editorConfigEnabled = editorConfigEnabled;
}
private CSharpCommandLineArguments CreateCommandLineArguments()
@@ -158,9 +163,22 @@ public ProjectInfo CreateProject(string csxFileName, IEnumerable
+ DocumentInfo.Create(
+ DocumentId.CreateNewId(projectId),
+ name: ".editorconfig",
+ loader: new FileTextLoader(path, Encoding.UTF8),
+ filePath: path))
+ .ToImmutableArray()
+ : ImmutableArray.Empty;
+
var project = ProjectInfo.Create(
filePath: csxFilePath,
- id: ProjectId.CreateNewId(),
+ id: projectId,
version: VersionStamp.Create(),
name: csxFileName,
assemblyName: $"{csxFileName}.dll",
@@ -171,7 +189,8 @@ public ProjectInfo CreateProject(string csxFileName, IEnumerable(() => _scriptContextProvider.CreateScriptContext(_scriptOptions, allCsxFiles));
+ _scriptContext = new Lazy(() => _scriptContextProvider.CreateScriptContext(_scriptOptions, allCsxFiles, _workspace.EditorConfigEnabled));
if (allCsxFiles.Length == 0)
{
diff --git a/test-assets/test-projects/ProjectWithAnalyzersAndEditorConfig/.editorconfig b/test-assets/test-projects/ProjectWithAnalyzersAndEditorConfig/.editorconfig
new file mode 100644
index 0000000000..9bf71d115a
--- /dev/null
+++ b/test-assets/test-projects/ProjectWithAnalyzersAndEditorConfig/.editorconfig
@@ -0,0 +1,5 @@
+root = true
+
+[*.cs]
+# IDE0005: Unnecessary using
+dotnet_diagnostic.IDE0005.severity = error
diff --git a/test-assets/test-projects/ProjectWithAnalyzersAndEditorConfig/Program.cs b/test-assets/test-projects/ProjectWithAnalyzersAndEditorConfig/Program.cs
new file mode 100644
index 0000000000..20f32262ed
--- /dev/null
+++ b/test-assets/test-projects/ProjectWithAnalyzersAndEditorConfig/Program.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace HelloWorld
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+
+ }
+ }
+}
diff --git a/test-assets/test-projects/ProjectWithAnalyzersAndEditorConfig/ProjectWithAnalyzersAndEditorConfig.csproj b/test-assets/test-projects/ProjectWithAnalyzersAndEditorConfig/ProjectWithAnalyzersAndEditorConfig.csproj
new file mode 100644
index 0000000000..d6555490c9
--- /dev/null
+++ b/test-assets/test-projects/ProjectWithAnalyzersAndEditorConfig/ProjectWithAnalyzersAndEditorConfig.csproj
@@ -0,0 +1,7 @@
+
+
+ Exe
+ netcoreapp2.1
+ 7.1
+
+
diff --git a/tests/OmniSharp.MSBuild.Tests/ProjectWithAnalyzersTests.cs b/tests/OmniSharp.MSBuild.Tests/ProjectWithAnalyzersTests.cs
index 9b1d9219f3..1e8e63704a 100644
--- a/tests/OmniSharp.MSBuild.Tests/ProjectWithAnalyzersTests.cs
+++ b/tests/OmniSharp.MSBuild.Tests/ProjectWithAnalyzersTests.cs
@@ -75,6 +75,54 @@ public async Task WhenProjectIsLoadedThenItContainsCustomRulesetsFromCsproj()
}
}
+ [Fact]
+ public async Task WhenProjectIsLoadedThenItContainsAnalyzerConfigurationFromEditorConfig()
+ {
+ using (var testProject = await TestAssets.Instance.GetTestProjectAsync("ProjectWithAnalyzersAndEditorConfig"))
+ using (var host = CreateMSBuildTestHost(testProject.Directory, configurationData: TestHelpers.GetConfigurationDataWithAnalyzerConfig(roslynAnalyzersEnabled: true, editorConfigEnabled: true)))
+ {
+ var diagnostics = await host.RequestCodeCheckAsync(Path.Combine(testProject.Directory, "Program.cs"));
+
+ Assert.NotEmpty(diagnostics.QuickFixes);
+
+ var quickFix = diagnostics.QuickFixes.OfType().Single(x => x.Id == "IDE0005");
+ Assert.Equal("Error", quickFix.LogLevel);
+ }
+ }
+
+ [Fact]
+ public async Task WhenProjectEditorConfigIsChangedThenAnalyzerConfigurationUpdates()
+ {
+ var emitter = new ProjectLoadTestEventEmitter();
+
+ using (var testProject = await TestAssets.Instance.GetTestProjectAsync("ProjectWithAnalyzersAndEditorConfig"))
+ using (var host = CreateMSBuildTestHost(
+ testProject.Directory,
+ emitter.AsExportDescriptionProvider(LoggerFactory),
+ TestHelpers.GetConfigurationDataWithAnalyzerConfig(roslynAnalyzersEnabled: true, editorConfigEnabled: true)))
+ {
+ var initialProject = host.Workspace.CurrentSolution.Projects.Single();
+ var analyzerConfigDocument = initialProject.AnalyzerConfigDocuments.Single();
+
+ File.WriteAllText(analyzerConfigDocument.FilePath, @"
+root = true
+
+[*.cs]
+# IDE0005: Unnecessary using
+dotnet_diagnostic.IDE0005.severity = none
+");
+
+ await NotifyFileChanged(host, analyzerConfigDocument.FilePath);
+
+ emitter.WaitForProjectUpdate();
+
+ var diagnostics = await host.RequestCodeCheckAsync(Path.Combine(testProject.Directory, "Program.cs"));
+
+ Assert.NotEmpty(diagnostics.QuickFixes);
+ Assert.DoesNotContain(diagnostics.QuickFixes.OfType(), x => x.Id == "IDE0005");
+ }
+ }
+
[Theory]
[InlineData("ProjectWithDisabledAnalyzers")]
[InlineData("ProjectWithDisabledAnalyzers2")]
diff --git a/tests/TestUtility/TestHelpers.cs b/tests/TestUtility/TestHelpers.cs
index 4217ff5203..139a7b8bbd 100644
--- a/tests/TestUtility/TestHelpers.cs
+++ b/tests/TestUtility/TestHelpers.cs
@@ -5,6 +5,7 @@
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Text;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
@@ -13,6 +14,7 @@
using OmniSharp;
using OmniSharp.FileWatching;
using OmniSharp.MSBuild.Discovery;
+using OmniSharp.Roslyn.EditorConfig;
using OmniSharp.Script;
using OmniSharp.Services;
@@ -30,7 +32,7 @@ public static OmniSharpWorkspace CreateCsxWorkspace(TestFile testFile)
public static void AddCsxProjectToWorkspace(OmniSharpWorkspace workspace, TestFile testFile)
{
var references = GetReferences();
- var scriptHelper = new ScriptProjectProvider(new ScriptOptions(), new OmniSharpEnvironment(), new LoggerFactory(), true);
+ var scriptHelper = new ScriptProjectProvider(new ScriptOptions(), new OmniSharpEnvironment(), new LoggerFactory(), isDesktopClr: true, editorConfigEnabled: true);
var project = scriptHelper.CreateProject(testFile.FileName, references.Union(new[] { MetadataReference.CreateFromFile(typeof(CommandLineScriptGlobals).GetTypeInfo().Assembly.Location) }), testFile.FileName, typeof(CommandLineScriptGlobals), Enumerable.Empty());
workspace.AddProject(project);
@@ -49,18 +51,30 @@ public static IEnumerable AddProjectToWorkspace(OmniSharpWorkspace wo
var references = GetReferences();
frameworks = frameworks ?? new[] { string.Empty };
var projectsIds = new List();
+ var editorConfigPaths = EditorConfigFinder.GetEditorConfigPaths(filePath);
foreach (var framework in frameworks)
{
+ var projectId = ProjectId.CreateNewId();
+ var analyzerConfigDocuments = editorConfigPaths.Select(path =>
+ DocumentInfo.Create(
+ DocumentId.CreateNewId(projectId),
+ name: ".editorconfig",
+ loader: new FileTextLoader(path, Encoding.UTF8),
+ filePath: path))
+ .ToImmutableArray();
+
var projectInfo = ProjectInfo.Create(
- id: ProjectId.CreateNewId(),
+ id: projectId,
version: versionStamp,
name: "OmniSharp+" + framework,
assemblyName: "AssemblyName",
language: LanguageNames.CSharp,
filePath: filePath,
metadataReferences: references,
- analyzerReferences: analyzerRefs).WithDefaultNamespace("OmniSharpTest");
+ analyzerReferences: analyzerRefs)
+ .WithDefaultNamespace("OmniSharpTest")
+ .WithAnalyzerConfigDocuments(analyzerConfigDocuments);
workspace.AddProject(projectInfo);
@@ -114,15 +128,23 @@ public static MSBuildInstance AddDotNetCoreToFakeInstance(this MSBuildInstance i
return instance;
}
- public static Dictionary GetConfigurationDataWithAnalyzerConfig(bool roslynAnalyzersEnabled = false, Dictionary existingConfiguration = null)
+ public static Dictionary GetConfigurationDataWithAnalyzerConfig(
+ bool roslynAnalyzersEnabled = false,
+ bool editorConfigEnabled = false,
+ Dictionary existingConfiguration = null)
{
if (existingConfiguration == null)
{
- return new Dictionary() { { "RoslynExtensionsOptions:EnableAnalyzersSupport", roslynAnalyzersEnabled.ToString() } };
+ return new Dictionary()
+ {
+ { "RoslynExtensionsOptions:EnableAnalyzersSupport", roslynAnalyzersEnabled.ToString() },
+ { "FormattingOptions:EnableEditorConfigSupport", editorConfigEnabled.ToString() }
+ };
}
var copyOfExistingConfigs = existingConfiguration.ToDictionary(x => x.Key, x => x.Value);
copyOfExistingConfigs.Add("RoslynExtensionsOptions:EnableAnalyzersSupport", roslynAnalyzersEnabled.ToString());
+ copyOfExistingConfigs.Add("FormattingOptions:EnableEditorConfigSupport", editorConfigEnabled.ToString());
return copyOfExistingConfigs;
}