diff --git a/src/Workspaces/CSharp/Portable/Formatting/Engine/Trivia/TriviaDataFactory.Analyzer.cs b/src/Workspaces/CSharp/Portable/Formatting/Engine/Trivia/TriviaDataFactory.Analyzer.cs index 583b0da40c545f2043dd98c24e815f2c555267d8..c05c0d238a8eb0bf45adca7f6ee94f1e420108ce 100644 --- a/src/Workspaces/CSharp/Portable/Formatting/Engine/Trivia/TriviaDataFactory.Analyzer.cs +++ b/src/Workspaces/CSharp/Portable/Formatting/Engine/Trivia/TriviaDataFactory.Analyzer.cs @@ -59,11 +59,29 @@ public static AnalysisResult Between(SyntaxToken token1, SyntaxToken token2) // newline. If so, we keep track of that so we'll appropriately indent later // on. - var previousNonMissingToken = token1.GetPreviousToken(); - if (previousNonMissingToken.TrailingTrivia.Count > 0 && - previousNonMissingToken.TrailingTrivia.Last().Kind() == SyntaxKind.EndOfLineTrivia) + // Keep walking backward until we hit a token whose *full width* is greater than + // 0. See if this token has an end of line trivia at the end of it. Note: + // we need to "includeZeroWidth" tokens because we can have zero width tokens + // that still have a full width that is non-zero. i.e. a missing token that + // still has trailing trivia on it. + + for (var currentToken = token1; !currentToken.IsKind(SyntaxKind.None); ) { - result.LineBreaks = 1; + var previousToken = currentToken.GetPreviousToken(includeSkipped: false, includeZeroWidth: true); + if (previousToken.FullWidth() == 0) + { + currentToken = previousToken; + continue; + } + + // Finally hit the first previous token with non-zero full width. + if (previousToken.TrailingTrivia.Count > 0 && + previousToken.TrailingTrivia.Last().Kind() == SyntaxKind.EndOfLineTrivia) + { + result.LineBreaks = 1; + } + + break; } } else diff --git a/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs b/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs index 21f902020ee1d3afbbcc808a1ff4a0fd3d11667c..2eb7e8e29f59150667b4df67dba2b169f01aa4c5 100644 --- a/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs +++ b/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs @@ -6950,6 +6950,29 @@ void DoSomething() }", changedOptionSet: optionSet); } + [WorkItem(4014, "https://github.com/dotnet/roslyn/issues/4014")] + [Fact, Trait(Traits.Feature, Traits.Features.Formatting)] + public async Task FormattingCodeWithBrokenInterpolatedStringShouldRespectFormatTabsOption() + { + var optionSet = new Dictionary { { new OptionKey(FormattingOptions.UseTabs, LanguageNames.CSharp), true } }; + + await AssertFormatAsync(@"class AClass +{ + void Main() + { + Test($""\""_{\""""); + Console.WriteLine(args); + } +}", @"class AClass +{ + void Main() + { + Test($""\""_{\""""); + Console.WriteLine(args); + } +}", changedOptionSet: optionSet); + } + [Fact, Trait(Traits.Feature, Traits.Features.Formatting)] [WorkItem(84, "https://github.com/dotnet/roslyn/issues/84")] [WorkItem(849870, "DevDiv")]