diff --git a/src/EditorFeatures/CSharpTest/Formatting/FormattingAnalyzerTests.cs b/src/EditorFeatures/CSharpTest/Formatting/FormattingAnalyzerTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..922b8884ce8c76a07e4fd92bf207197782f5464c
--- /dev/null
+++ b/src/EditorFeatures/CSharpTest/Formatting/FormattingAnalyzerTests.cs
@@ -0,0 +1,58 @@
+// 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;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics;
+using Microsoft.CodeAnalysis.Formatting;
+using Microsoft.CodeAnalysis.Test.Utilities;
+using Xunit;
+
+namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Formatting
+{
+ public class FormattingAnalyzerTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest
+ {
+ internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace)
+ => (new FormattingDiagnosticAnalyzer(), new FormattingCodeFixProvider());
+
+ [Fact, Trait(Traits.Feature, Traits.Features.Formatting)]
+ public async Task TrailingWhitespace()
+ {
+ var testCode =
+ "class X[| |]" + Environment.NewLine +
+ "{" + Environment.NewLine +
+ "}" + Environment.NewLine;
+ var expected =
+ "class X" + Environment.NewLine +
+ "{" + Environment.NewLine +
+ "}" + Environment.NewLine;
+ await TestInRegularAndScriptAsync(testCode, expected);
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.Formatting)]
+ public async Task TestMissingSpace()
+ {
+ var testCode = @"
+class TypeName
+{
+ void Method()
+ {
+ if$$(true)return;
+ }
+}
+";
+ var expected = @"
+class TypeName
+{
+ void Method()
+ {
+ if (true)return;
+ }
+}
+";
+
+ await TestInRegularAndScriptAsync(testCode, expected);
+ }
+ }
+}
diff --git a/src/Features/Core/Portable/CodeFixes/PredefinedCodeFixProviderNames.cs b/src/Features/Core/Portable/CodeFixes/PredefinedCodeFixProviderNames.cs
index 9744103ff5f0aada23398f65416a71c8939e5e90..e62a530915c850fd12e1d3604d378b02bacbd705 100644
--- a/src/Features/Core/Portable/CodeFixes/PredefinedCodeFixProviderNames.cs
+++ b/src/Features/Core/Portable/CodeFixes/PredefinedCodeFixProviderNames.cs
@@ -21,6 +21,7 @@ internal static class PredefinedCodeFixProviderNames
public const string AddMissingReference = nameof(AddMissingReference);
public const string AddImport = nameof(AddImport);
public const string FullyQualify = nameof(FullyQualify);
+ public const string FixFormatting = nameof(FixFormatting);
public const string FixIncorrectFunctionReturnType = nameof(FixIncorrectFunctionReturnType);
public const string FixIncorrectExitContinue = nameof(FixIncorrectExitContinue);
public const string GenerateConstructor = nameof(GenerateConstructor);
diff --git a/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs b/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs
index 0c642546038291ac9efd52342287661214f0fb15..e4c9a43d68e5de9c8f9e90ccab2aa1be77e40758 100644
--- a/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs
+++ b/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs
@@ -84,6 +84,8 @@ internal static class IDEDiagnosticIds
public const string UseExpressionBodyForLambdaExpressionsDiagnosticId = "IDE0053";
+ public const string FormattingDiagnosticId = "IDE0054";
+
// Analyzer error Ids
public const string AnalyzerChangedId = "IDE1001";
public const string AnalyzerDependencyConflictId = "IDE1002";
diff --git a/src/Features/Core/Portable/FeaturesResources.Designer.cs b/src/Features/Core/Portable/FeaturesResources.Designer.cs
index 215b6e91a65a977c750ed42ab6dad16db1a71bef..73d0f87558e9964815b056b494209e0f80d1a88e 100644
--- a/src/Features/Core/Portable/FeaturesResources.Designer.cs
+++ b/src/Features/Core/Portable/FeaturesResources.Designer.cs
@@ -1369,6 +1369,33 @@ internal class FeaturesResources {
}
}
+ ///
+ /// Looks up a localized string similar to Fix formatting.
+ ///
+ internal static string Formatting_analyzer_code_fix {
+ get {
+ return ResourceManager.GetString("Formatting_analyzer_code_fix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Fix formatting.
+ ///
+ internal static string Formatting_analyzer_message {
+ get {
+ return ResourceManager.GetString("Formatting_analyzer_message", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Fix formatting.
+ ///
+ internal static string Formatting_analyzer_title {
+ get {
+ return ResourceManager.GetString("Formatting_analyzer_title", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Formatting document.
///
diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx
index 6b374a45bfc19d144770bc609f127feed2d97a1e..b5e03eb48f1248c955dab9d6fddd60c0e24fbc39 100644
--- a/src/Features/Core/Portable/FeaturesResources.resx
+++ b/src/Features/Core/Portable/FeaturesResources.resx
@@ -1430,4 +1430,13 @@ This version used in: {2}
Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.
+
+ Fix formatting
+
+
+ Fix formatting
+
+
+ Fix formatting
+
\ No newline at end of file
diff --git a/src/Features/Core/Portable/Formatting/FormattingCodeFixProvider.cs b/src/Features/Core/Portable/Formatting/FormattingCodeFixProvider.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f43831e23dee21b1b08de6a2f3dcbcf035d6b87c
--- /dev/null
+++ b/src/Features/Core/Portable/Formatting/FormattingCodeFixProvider.cs
@@ -0,0 +1,69 @@
+// 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;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Composition;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editing;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis.Formatting
+{
+ [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic, Name = PredefinedCodeFixProviderNames.FixFormatting)]
+ [Shared]
+ internal class FormattingCodeFixProvider : SyntaxEditorBasedCodeFixProvider
+ {
+ public override ImmutableArray FixableDiagnosticIds
+ => ImmutableArray.Create(IDEDiagnosticIds.FormattingDiagnosticId);
+
+ public override Task RegisterCodeFixesAsync(CodeFixContext context)
+ {
+ context.RegisterCodeFix(
+ new MyCodeAction(c => FixOneAsync(context.Document, context.Diagnostics, c)),
+ context.Diagnostics);
+
+ return Task.CompletedTask;
+ }
+
+ protected async Task FixOneAsync(Document document, ImmutableArray diagnostics, CancellationToken cancellationToken)
+ {
+ var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
+ var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
+ var changes = new List();
+ foreach (var diagnostic in diagnostics)
+ {
+ if (!tree.Equals(diagnostic.Location.SourceTree)
+ || !diagnostic.Properties.TryGetValue(FormattingDiagnosticAnalyzer.ReplaceTextKey, out var replacement))
+ {
+ continue;
+ }
+
+ changes.Add(new TextChange(diagnostic.Location.SourceSpan, replacement));
+ }
+
+ changes.Sort((left, right) => left.Span.Start.CompareTo(right.Span.Start));
+
+ return document.WithText(text.WithChanges(changes));
+ }
+
+ protected override async Task FixAllAsync(Document document, ImmutableArray diagnostics, SyntaxEditor editor, CancellationToken cancellationToken)
+ {
+ var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
+ var updatedDocument = await Formatter.FormatAsync(document, options, cancellationToken).ConfigureAwait(false);
+ editor.ReplaceNode(editor.OriginalRoot, await updatedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false));
+ }
+
+ private sealed class MyCodeAction : CodeAction.DocumentChangeAction
+ {
+ public MyCodeAction(Func> createChangedDocument)
+ : base(FeaturesResources.Formatting_analyzer_code_fix, createChangedDocument, FeaturesResources.Formatting_analyzer_code_fix)
+ {
+ }
+ }
+ }
+}
diff --git a/src/Features/Core/Portable/Formatting/FormattingDiagnosticAnalyzer.cs b/src/Features/Core/Portable/Formatting/FormattingDiagnosticAnalyzer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1f19d8f55a2892399967f8eb9d9106267d7bfc8d
--- /dev/null
+++ b/src/Features/Core/Portable/Formatting/FormattingDiagnosticAnalyzer.cs
@@ -0,0 +1,101 @@
+// 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 Microsoft.CodeAnalysis.CodeStyle;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis.Formatting
+{
+ [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
+ internal class FormattingDiagnosticAnalyzer
+ : AbstractCodeStyleDiagnosticAnalyzer
+ {
+ public static readonly string ReplaceTextKey = nameof(ReplaceTextKey);
+
+ public static readonly ImmutableDictionary RemoveTextProperties =
+ ImmutableDictionary.Create().Add(ReplaceTextKey, "");
+
+ public FormattingDiagnosticAnalyzer()
+ : base(
+ IDEDiagnosticIds.FormattingDiagnosticId,
+ new LocalizableResourceString(nameof(FeaturesResources.Formatting_analyzer_title), FeaturesResources.ResourceManager, typeof(FeaturesResources)),
+ new LocalizableResourceString(nameof(FeaturesResources.Formatting_analyzer_message), FeaturesResources.ResourceManager, typeof(FeaturesResources)))
+ {
+ }
+
+ public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
+ => DiagnosticAnalyzerCategory.SyntaxAnalysis;
+
+ public override bool OpenFileOnly(Workspace workspace)
+ => false;
+
+ protected override void InitializeWorker(AnalysisContext context)
+ => context.RegisterSyntaxTreeAction(AnalyzeSyntaxTree);
+
+ private void AnalyzeSyntaxTree(SyntaxTreeAnalysisContext context)
+ {
+ if (!(context.Options is WorkspaceAnalyzerOptions workspaceAnalyzerOptions))
+ {
+ return;
+ }
+
+ var options = context.Options.GetDocumentOptionSetAsync(context.Tree, context.CancellationToken).GetAwaiter().GetResult();
+ if (options == null)
+ {
+ return;
+ }
+
+ var workspace = workspaceAnalyzerOptions.Services.Workspace;
+ var formattingChanges = Formatter.GetFormattedTextChanges(context.Tree.GetRoot(context.CancellationToken), workspace, options, context.CancellationToken);
+ foreach (var formattingChange in formattingChanges)
+ {
+ var change = formattingChange;
+ if (change.NewText.Length > 0 && !change.Span.IsEmpty)
+ {
+ var oldText = context.Tree.GetText(context.CancellationToken);
+
+ // Handle cases where the change is a substring removal from the beginning
+ var offset = change.Span.Length - change.NewText.Length;
+ if (offset >= 0 && oldText.GetSubText(new TextSpan(change.Span.Start + offset, change.NewText.Length)).ContentEquals(SourceText.From(change.NewText)))
+ {
+ change = new TextChange(new TextSpan(change.Span.Start, offset), "");
+ }
+ else
+ {
+ // Handle cases where the change is a substring removal from the end
+ if (change.NewText.Length < change.Span.Length
+ && oldText.GetSubText(new TextSpan(change.Span.Start, change.NewText.Length)).ContentEquals(SourceText.From(change.NewText)))
+ {
+ change = new TextChange(new TextSpan(change.Span.Start + change.NewText.Length, change.Span.Length - change.NewText.Length), "");
+ }
+ }
+ }
+
+ if (change.NewText.Length == 0 && change.Span.IsEmpty)
+ {
+ // No actual change
+ continue;
+ }
+
+ ImmutableDictionary properties;
+ if (change.NewText.Length == 0)
+ {
+ properties = RemoveTextProperties;
+ }
+ else
+ {
+ properties = ImmutableDictionary.Create().Add(ReplaceTextKey, change.NewText);
+ }
+
+ var location = Location.Create(context.Tree, change.Span);
+ context.ReportDiagnostic(DiagnosticHelper.Create(
+ Descriptor,
+ location,
+ ReportDiagnostic.Default,
+ additionalLocations: null,
+ properties));
+ }
+ }
+ }
+}
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf
index 089dfd5fee28c3c05ff2a0cbf7ee577fff79633a..c895ec2a0b8d1434bd12c9e4d7229d335ecbc4d0 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf
@@ -62,6 +62,21 @@
Opravit překlep {0}
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
Formátuje se dokument.
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf
index 77a5b454eb105fc40122318ba27ae57a5b6735a5..0da8017d26d35e268e3978e64e871a77441bf68e 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf
@@ -62,6 +62,21 @@
Tippfehler "{0}" korrigieren
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
Dokument wird formatiert
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf
index aaa7bd169fb106cf3063fe5347ea1618025eb3d7..d8f88c6205ff1e2a4b5e465ca297fade1c6d0c18 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf
@@ -62,6 +62,21 @@
Corregir error de escritura "{0}"
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
Aplicando formato al documento
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf
index 4fd55ad860bf5212c86cca7a4da557e1d5c1f728..887c572e7570d30a927a25ea60d1697048312ae2 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf
@@ -62,6 +62,21 @@
Corriger la faute de frappe '{0}'
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
Mise en forme du document
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf
index 9aed9dc638808e65ec2dcfd590ead74326a8e3e5..378965d4eefa51417d7e0a8d0da161408ff7a357 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf
@@ -62,6 +62,21 @@
Correggere l'errore di ortografia '{0}'
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
Formattazione del documento
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf
index 8009b5e33d1993f41bb14ec7577f51821769ee18..f150d1b8bd77143a8243f9c15d47c51f4562515b 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf
@@ -62,6 +62,21 @@
'{0}' の入力ミスを修正します
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
ドキュメントの書式設定
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf
index b03ab463232e8c9afdf190f736cdba4c3dccc32d..9519547453e87c68c287ea8c4e576f37fdefa761 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf
@@ -62,6 +62,21 @@
오타 '{0}' 수정
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
문서 서식 지정
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf
index d6a2615bef8c9e5240565b2162d7e1cbd4ee0fb0..2bfd6534ac63001d2ff66775cfe0befa095200c8 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf
@@ -62,6 +62,21 @@
Popraw błąd pisowni „{0}”
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
Formatowanie dokumentu
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf
index 7c3d9aed55c5a2843b65205dd3de4554e1e4e53b..fa3a51336e90f1472c914a3fbdf02cfccde803fb 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf
@@ -62,6 +62,21 @@
Corrigir erro de digitação '{0}'
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
Formatando documento
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf
index 7dd2e43c518abd25c9cfb45125a6d9036f43caf6..16b87ab4fa3db81666b17c6dfc6774e878b54032 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf
@@ -62,6 +62,21 @@
Исправьте опечатку "{0}"
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
Форматирование документа
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf
index b4d87e6f3451c7464a68e990d6731b048cec1bab..ccd4f7a40db4570ab2d171d72209bafe11d09ff0 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf
@@ -62,6 +62,21 @@
'{0}' yazım hatasını düzeltin
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
Belge biçimlendiriliyor
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf
index c729b2e2d69152eeae5116a86ec4cd288117da9f..a1233d9f88429ff85a5305e94c23f9f903c66087 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf
@@ -62,6 +62,21 @@
修正笔误“{0}”
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
设置文档格式
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf
index 1c99e47d2096892c5c6068c85a369a26a6635a7d..17400e64cba5112f186bcd5ef00147611fd50f33 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf
@@ -62,6 +62,21 @@
修正錯字 '{0}'
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
+
+
+ Fix formatting
+
+
正在將文件格式化
diff --git a/src/Workspaces/Core/Portable/Formatting/Engine/AbstractFormatEngine.cs b/src/Workspaces/Core/Portable/Formatting/Engine/AbstractFormatEngine.cs
index 482fc7a0488fa4457c427aae56bbe6c60d111ff6..c5e158c672c637ffc574a3bcfdbbecd1ee469e80 100644
--- a/src/Workspaces/Core/Portable/Formatting/Engine/AbstractFormatEngine.cs
+++ b/src/Workspaces/Core/Portable/Formatting/Engine/AbstractFormatEngine.cs
@@ -92,9 +92,13 @@ internal abstract partial class AbstractFormatEngine
_language = token1.Language;
}
- // set synchronous task executor if it is debug mode or if there is not many things to format
- this.TaskExecutor = optionSet.GetOption(FormattingOptions.DebugMode, _language) ? TaskExecutor.Synchronous :
- (SpanToFormat.Length < ConcurrentThreshold) ? TaskExecutor.Synchronous : executor;
+ // set synchronous task executor if it is enabled (explicitly or as part of debug mode) or if there is not
+ // many things to format
+ var synchronousExecutorAllowed =
+ !optionSet.GetOption(FormattingOptions.AllowConcurrent)
+ || optionSet.GetOption(FormattingOptions.DebugMode, _language);
+ var useSynchronousExecutor = synchronousExecutorAllowed || SpanToFormat.Length < ConcurrentThreshold;
+ TaskExecutor = useSynchronousExecutor ? TaskExecutor.Synchronous : executor;
}
protected abstract AbstractTriviaDataFactory CreateTriviaFactory();
diff --git a/src/Workspaces/Core/Portable/Formatting/Formatter.cs b/src/Workspaces/Core/Portable/Formatting/Formatter.cs
index 2a26fe9af4a6affb3d2f29c2df579114c2f27821..3fd87091971139bb4561d2e0c92ae12b0949f768 100644
--- a/src/Workspaces/Core/Portable/Formatting/Formatter.cs
+++ b/src/Workspaces/Core/Portable/Formatting/Formatter.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -281,7 +282,13 @@ internal static Task GetFormattingResult(SyntaxNode node, IEn
/// An optional cancellation token.
/// The changes necessary to format the tree.
public static IList GetFormattedTextChanges(SyntaxNode node, Workspace workspace, OptionSet options = null, CancellationToken cancellationToken = default)
- => GetFormattedTextChangesAsync(node, workspace, options, cancellationToken).WaitAndGetResult_CanCallOnBackground(cancellationToken);
+ {
+ options = options ?? workspace.Options;
+ options = options.WithChangedOption(FormattingOptions.AllowConcurrent, false);
+ var resultTask = GetFormattedTextChangesAsync(node, workspace, options, cancellationToken);
+ Debug.Assert(resultTask.IsCompleted);
+ return resultTask.WaitAndGetResult_CanCallOnBackground(cancellationToken);
+ }
internal static Task> GetFormattedTextChangesAsync(SyntaxNode node, Workspace workspace, OptionSet options = null, CancellationToken cancellationToken = default)
=> GetFormattedTextChangesAsync(node, SpecializedCollections.SingletonEnumerable(node.FullSpan), workspace, options, rules: null, cancellationToken: cancellationToken);
diff --git a/src/Workspaces/Core/Portable/Formatting/FormattingOptions.cs b/src/Workspaces/Core/Portable/Formatting/FormattingOptions.cs
index a56a7b233f8be16c57d0e4435f11e9732856ac0e..2686547d2322c4e738a50ee0bdda85930dee13c2 100644
--- a/src/Workspaces/Core/Portable/Formatting/FormattingOptions.cs
+++ b/src/Workspaces/Core/Portable/Formatting/FormattingOptions.cs
@@ -45,6 +45,7 @@ private static Optional ParseEditorConfigEndOfLine(string endOfLineValue
}
internal static PerLanguageOption DebugMode { get; } = new PerLanguageOption(nameof(FormattingOptions), nameof(DebugMode), defaultValue: false);
+ internal static Option AllowConcurrent { get; } = new Option(nameof(FormattingOptions), nameof(AllowConcurrent), defaultValue: true);
internal static Option AllowDisjointSpanMerging { get; } = new Option(nameof(FormattingOptions), nameof(AllowDisjointSpanMerging), defaultValue: false);