diff --git a/src/CodeStyle/CSharp/CodeFixes/CSharpFormattingCodeFixProvider.cs b/src/CodeStyle/CSharp/CodeFixes/CSharpFormattingCodeFixProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..79622d72acf283ad888d098040df2a071030df40 --- /dev/null +++ b/src/CodeStyle/CSharp/CodeFixes/CSharpFormattingCodeFixProvider.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Composition; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Options; +using Microsoft.VisualStudio.CodingConventions; + +namespace Microsoft.CodeAnalysis.CodeStyle +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.FixFormatting)] + [Shared] + internal class CSharpFormattingCodeFixProvider : AbstractFormattingCodeFixProvider + { + private readonly EditorConfigOptionsApplier _editorConfigOptionsApplier = new EditorConfigOptionsApplier(); + + protected override OptionSet ApplyFormattingOptions(OptionSet optionSet, ICodingConventionContext codingConventionContext) + { + return _editorConfigOptionsApplier.ApplyConventions(optionSet, codingConventionContext.CurrentConventions, LanguageNames.CSharp); + } + } +} diff --git a/src/CodeStyle/CSharp/Tests/FormattingAnalyzerTests.cs b/src/CodeStyle/CSharp/Tests/FormattingAnalyzerTests.cs index a966e5fd6e4210ff6e09cf230799df043ee4e55e..c9abd0249e02577df24de888f561bc8bbaae3032 100644 --- a/src/CodeStyle/CSharp/Tests/FormattingAnalyzerTests.cs +++ b/src/CodeStyle/CSharp/Tests/FormattingAnalyzerTests.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.IO; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp.Testing; using Microsoft.CodeAnalysis.Testing.Verifiers; @@ -7,7 +8,7 @@ namespace Microsoft.CodeAnalysis.CodeStyle { - using Verify = CSharpCodeFixVerifier; + using Verify = CSharpCodeFixVerifier; public class FormattingAnalyzerTests { @@ -184,7 +185,7 @@ class MyClass } "; - await new CSharpCodeFixTest + await new CSharpCodeFixTest { TestCode = testCode, FixedCode = fixedCode, @@ -194,5 +195,50 @@ class MyClass NumberOfIncrementalIterations = 2, }.RunAsync(); } + + [Fact] + public async Task TestEditorConfigUsed() + { + var testCode = @" +class MyClass { + void MyMethod()[| |]{ + } +} +"; + var fixedCode = @" +class MyClass { + void MyMethod() + { + } +} +"; + var editorConfig = @" +root = true + +[*.cs] +csharp_new_line_before_open_brace = methods +"; + + var testDirectoryName = Path.GetRandomFileName(); + Directory.CreateDirectory(testDirectoryName); + try + { + File.WriteAllText(Path.Combine(testDirectoryName, ".editorconfig"), editorConfig); + + // The contents of this file are ignored, but the coding conventions library checks for existence before + // .editorconfig is used. + File.WriteAllText(Path.Combine(testDirectoryName, "Test0.cs"), string.Empty); + + await new CSharpCodeFixTest + { + TestState = { Sources = { (Path.GetFullPath(Path.Combine(testDirectoryName, "Test0.cs")), testCode) } }, + FixedState = { Sources = { (Path.GetFullPath(Path.Combine(testDirectoryName, "Test0.cs")), fixedCode) } }, + }.RunAsync(); + } + finally + { + Directory.Delete(testDirectoryName, true); + } + } } } diff --git a/src/CodeStyle/Core/CodeFixes/FormattingCodeFixHelper.cs b/src/CodeStyle/Core/CodeFixes/FormattingCodeFixHelper.cs index 9b500c1b00d5eb0806d73f6a12d69157f108f8e5..ded0aa6661f3b1ca48a133fda5b5923173416ea8 100644 --- a/src/CodeStyle/Core/CodeFixes/FormattingCodeFixHelper.cs +++ b/src/CodeStyle/Core/CodeFixes/FormattingCodeFixHelper.cs @@ -3,13 +3,14 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis { internal static class FormattingCodeFixHelper { - internal static async Task FixOneAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) + internal static async Task FixOneAsync(Document document, OptionSet options, Diagnostic diagnostic, CancellationToken cancellationToken) { var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); @@ -21,7 +22,6 @@ internal static async Task FixOneAsync(Document document, Diagnostic d text.Lines[diagnosticLinePositionSpan.Start.Line].Start, text.Lines[diagnosticLinePositionSpan.End.Line].End); - var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); return await Formatter.FormatAsync(document, spanToFormat, options, cancellationToken).ConfigureAwait(false); } } diff --git a/src/CodeStyle/Core/CodeFixes/FormattingCodeFixProvider.cs b/src/CodeStyle/Core/CodeFixes/FormattingCodeFixProvider.cs index a13f8a76989e43e65f5e36820725e2ed891e4362..19a9d088f068055845b14f768015579e8ce52e68 100644 --- a/src/CodeStyle/Core/CodeFixes/FormattingCodeFixProvider.cs +++ b/src/CodeStyle/Core/CodeFixes/FormattingCodeFixProvider.cs @@ -1,43 +1,67 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Immutable; -using System.Composition; +using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Options; +using Microsoft.VisualStudio.CodingConventions; namespace Microsoft.CodeAnalysis.CodeStyle { - [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic, Name = PredefinedCodeFixProviderNames.FixFormatting)] - [Shared] - internal class FormattingCodeFixProvider : CodeFixProvider + internal abstract class AbstractFormattingCodeFixProvider : CodeFixProvider { - public override ImmutableArray FixableDiagnosticIds + public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(IDEDiagnosticIds.FormattingDiagnosticId); - public override FixAllProvider GetFixAllProvider() + public sealed override FixAllProvider GetFixAllProvider() { - return new FixAll(); + return new FixAll(this); } - public override Task RegisterCodeFixesAsync(CodeFixContext context) + public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) { foreach (var diagnostic in context.Diagnostics) { context.RegisterCodeFix( CodeAction.Create( CodeStyleResources.Fix_formatting, - c => FormattingCodeFixHelper.FixOneAsync(context.Document, diagnostic, c), - nameof(FormattingCodeFixProvider)), + c => FixOneAsync(context, diagnostic, c), + nameof(AbstractFormattingCodeFixProvider)), diagnostic); } return Task.CompletedTask; } + protected abstract OptionSet ApplyFormattingOptions(OptionSet optionSet, ICodingConventionContext codingConventionContext); + + private async Task FixOneAsync(CodeFixContext context, Diagnostic diagnostic, CancellationToken cancellationToken) + { + var options = await GetOptionsAsync(context.Document, cancellationToken).ConfigureAwait(false); + return await FormattingCodeFixHelper.FixOneAsync(context.Document, options, diagnostic, cancellationToken).ConfigureAwait(false); + } + + private async Task GetOptionsAsync(Document document, CancellationToken cancellationToken) + { + OptionSet options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); + + // The in-IDE workspace supports .editorconfig without special handling. However, the AdhocWorkspace used + // in testing requires manual handling of .editorconfig. + if (document.Project.Solution.Workspace is AdhocWorkspace && File.Exists(document.FilePath ?? document.Name)) + { + var codingConventionsManager = CodingConventionsManagerFactory.CreateCodingConventionsManager(); + var codingConventionContext = await codingConventionsManager.GetConventionContextAsync(document.FilePath ?? document.Name, cancellationToken).ConfigureAwait(false); + options = ApplyFormattingOptions(options, codingConventionContext); + } + + return options; + } + /// /// Provide an optimized Fix All implementation that runs /// on the document(s) @@ -45,11 +69,18 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) /// private class FixAll : DocumentBasedFixAllProvider { + private readonly AbstractFormattingCodeFixProvider _formattingCodeFixProvider; + + public FixAll(AbstractFormattingCodeFixProvider formattingCodeFixProvider) + { + _formattingCodeFixProvider = formattingCodeFixProvider; + } + protected override string CodeActionTitle => CodeStyleResources.Fix_formatting; protected override async Task FixAllInDocumentAsync(FixAllContext fixAllContext, Document document, ImmutableArray diagnostics) { - var options = await document.GetOptionsAsync(fixAllContext.CancellationToken).ConfigureAwait(false); + var options = await _formattingCodeFixProvider.GetOptionsAsync(document, fixAllContext.CancellationToken).ConfigureAwait(false); var updatedDocument = await Formatter.FormatAsync(document, options, fixAllContext.CancellationToken).ConfigureAwait(false); return await updatedDocument.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false); } diff --git a/src/CodeStyle/VisualBasic/CodeFixes/VisualBasicFormattingCodeFixProvider.vb b/src/CodeStyle/VisualBasic/CodeFixes/VisualBasicFormattingCodeFixProvider.vb new file mode 100644 index 0000000000000000000000000000000000000000..8726cfa1da728cb0cc93cf09089445f0b03447f6 --- /dev/null +++ b/src/CodeStyle/VisualBasic/CodeFixes/VisualBasicFormattingCodeFixProvider.vb @@ -0,0 +1,19 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Composition +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Options +Imports Microsoft.VisualStudio.CodingConventions + +Namespace Microsoft.CodeAnalysis.CodeStyle + + <[Shared]> + Friend Class VisualBasicFormattingCodeFixProvider + Inherits AbstractFormattingCodeFixProvider + + Protected Overrides Function ApplyFormattingOptions(optionSet As OptionSet, codingConventionContext As ICodingConventionContext) As OptionSet + Return optionSet + End Function + End Class +End Namespace + diff --git a/src/CodeStyle/VisualBasic/Tests/FormattingAnalyzerTests.vb b/src/CodeStyle/VisualBasic/Tests/FormattingAnalyzerTests.vb index 55caaf9117aa9d6de529df032fc95df271d425b3..f9838a32cd41e381376f05b9d1cd67ced9145ce8 100644 --- a/src/CodeStyle/VisualBasic/Tests/FormattingAnalyzerTests.vb +++ b/src/CodeStyle/VisualBasic/Tests/FormattingAnalyzerTests.vb @@ -3,7 +3,7 @@ Imports Xunit Imports VerifyVB = Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicCodeFixVerifier( Of Microsoft.CodeAnalysis.CodeStyle.VisualBasicFormattingAnalyzer, - Microsoft.CodeAnalysis.CodeStyle.FormattingCodeFixProvider, + Microsoft.CodeAnalysis.CodeStyle.VisualBasicFormattingCodeFixProvider, Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier) Namespace Microsoft.CodeAnalysis.CodeStyle diff --git a/src/Features/Core/Portable/Formatting/FormattingCodeFixProvider.cs b/src/Features/Core/Portable/Formatting/FormattingCodeFixProvider.cs index f6bdc1627df1dcd76474c019b7ce790f9541575e..e9efa3d8873269c0870f28951be57fea970e479a 100644 --- a/src/Features/Core/Portable/Formatting/FormattingCodeFixProvider.cs +++ b/src/Features/Core/Portable/Formatting/FormattingCodeFixProvider.cs @@ -24,13 +24,19 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) foreach (var diagnostic in context.Diagnostics) { context.RegisterCodeFix( - new MyCodeAction(c => FormattingCodeFixHelper.FixOneAsync(context.Document, diagnostic, c)), + new MyCodeAction(c => FixOneAsync(context, diagnostic, c)), diagnostic); } return Task.CompletedTask; } + private static async Task FixOneAsync(CodeFixContext context, Diagnostic diagnostic, CancellationToken cancellationToken) + { + var options = await context.Document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); + return await FormattingCodeFixHelper.FixOneAsync(context.Document, options, diagnostic, cancellationToken).ConfigureAwait(false); + } + protected override async Task FixAllAsync(Document document, ImmutableArray diagnostics, SyntaxEditor editor, CancellationToken cancellationToken) { var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);