File: Formatter\FormatterTests.cs
Web Access
Project: src\src\Workspaces\CoreTest\Microsoft.CodeAnalysis.Workspaces.UnitTests.csproj (Microsoft.CodeAnalysis.Workspaces.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Formatting;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.OrganizeImports;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.UnitTests;
using Microsoft.CodeAnalysis.VisualBasic.Formatting;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.Editor.UnitTests.Formating;
 
[UseExportProvider]
[Trait(Traits.Feature, Traits.Features.Formatting)]
public class FormatterTests
{
    private static readonly TestComposition s_composition = FeaturesTestCompositions.Features;
 
    [ExportLanguageService(typeof(IFormattingService), language: NoCompilationConstants.LanguageName), Shared, PartNotDiscoverable]
    internal class TestFormattingService : IFormattingService
    {
        [ImportingConstructor]
        [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
        public TestFormattingService()
        {
        }
 
        public Task<Document> FormatAsync(Document document, IEnumerable<TextSpan>? spans, LineFormattingOptions lineFormattingOptions, SyntaxFormattingOptions? syntaxFormattingOptions, CancellationToken cancellationToken)
            => Task.FromResult(document.WithText(SourceText.From($"Formatted with options: {lineFormattingOptions.ToString().Replace("\r", "\\r").Replace("\n", "\\n")}")));
    }
 
    [Fact]
    public async Task FormatAsync_ForeignLanguageWithFormattingSupport()
    {
        var hostServices = s_composition.AddParts([typeof(NoCompilationLanguageService), typeof(TestFormattingService)]).GetHostServices();
        using var workspace = new AdhocWorkspace(hostServices);
 
        var project = workspace.AddProject("Dummy", NoCompilationConstants.LanguageName);
        var document = workspace.AddDocument(project.Id, "File.dummy", SourceText.From("dummy"));
 
#pragma warning disable RS0030 // Do not used banned APIs
        var formattedDocument = await Formatter.FormatAsync(document, spans: null, options: null, CancellationToken.None);
#pragma warning restore RS0030 // Do not used banned APIs
 
        var formattedText = await formattedDocument.GetTextAsync();
        AssertEx.Equal(@"Formatted with options: LineFormattingOptions { UseTabs = False, TabSize = 4, IndentationSize = 4, NewLine = \r\n }", formattedText.ToString());
    }
 
    [Theory, CombinatorialData]
    public async Task FormatAsync_ForeignLanguageWithFormattingSupport_Options(bool passExplicitOptions)
    {
        var hostServices = s_composition.AddParts([typeof(NoCompilationLanguageService), typeof(TestFormattingService)]).GetHostServices();
 
        using var workspace = new AdhocWorkspace(hostServices);
 
        var project = workspace.AddProject("Dummy", NoCompilationConstants.LanguageName);
        var document = workspace.AddDocument(project.Id, "File.dummy", SourceText.From("dummy"));
 
        var solutionOptions = workspace.CurrentSolution.Options.
            WithChangedOption(new OptionKey(FormattingOptions.UseTabs, NoCompilationConstants.LanguageName), false).
            WithChangedOption(new OptionKey(FormattingOptions.TabSize, NoCompilationConstants.LanguageName), 1).
            WithChangedOption(new OptionKey(FormattingOptions.IndentationSize, NoCompilationConstants.LanguageName), 7).
            WithChangedOption(new OptionKey(FormattingOptions.NewLine, NoCompilationConstants.LanguageName), "\n");
 
        document = document.Project.Solution.WithOptions(solutionOptions).GetRequiredDocument(document.Id);
 
#pragma warning disable RS0030 // Do not used banned APIs
        var documentOptions = await document.GetOptionsAsync();
        Assert.Equal(7, documentOptions.GetOption(FormattingOptions.IndentationSize));
#pragma warning restore
 
        var options = passExplicitOptions ? new TestOptionSet(ImmutableDictionary<OptionKey, object?>.Empty.
            Add(new OptionKey(FormattingOptions.UseTabs, NoCompilationConstants.LanguageName), true).
            Add(new OptionKey(FormattingOptions.TabSize, NoCompilationConstants.LanguageName), 5).
            Add(new OptionKey(FormattingOptions.IndentationSize, NoCompilationConstants.LanguageName), 6).
            Add(new OptionKey(FormattingOptions.NewLine, NoCompilationConstants.LanguageName), "\r")) : null;
 
#pragma warning disable RS0030 // Do not used banned APIs
        var formattedDocument = await Formatter.FormatAsync(document, spans: null, options, CancellationToken.None);
#pragma warning restore
 
        var formattedText = await formattedDocument.GetTextAsync();
 
        if (passExplicitOptions)
        {
            // explicit options override solution and document options:
            AssertEx.Equal(@"Formatted with options: LineFormattingOptions { UseTabs = True, TabSize = 5, IndentationSize = 6, NewLine = \r }", formattedText.ToString());
        }
        else
        {
            // document options override solution options:
            AssertEx.Equal(@"Formatted with options: LineFormattingOptions { UseTabs = False, TabSize = 1, IndentationSize = 7, NewLine = \n }", formattedText.ToString());
        }
    }
 
    [Fact]
    public async Task PublicOptions()
    {
        using var workspace = new AdhocWorkspace();
        var csProject = workspace.AddProject("CS", LanguageNames.CSharp);
        var vbProject = workspace.AddProject("VB", LanguageNames.VisualBasic);
        var csDocument = workspace.AddDocument(csProject.Id, "File.cs", SourceText.From("class C { }"));
        var vbDocument = workspace.AddDocument(vbProject.Id, "File.vb", SourceText.From("Class C : End Class"));
 
        // Validate that options are read from specified OptionSet:
 
        var updatedOptions = OptionsTestHelpers.GetOptionSetWithChangedOptions(TestOptionSet.Empty, OptionsTestHelpers.PublicFormattingOptionsWithNonDefaultValues);
        ValidateCSharpOptions((CSharpSyntaxFormattingOptions)(await Formatter.GetFormattingOptionsAsync(csDocument, updatedOptions, CancellationToken.None)).Syntax!);
        ValidateVisualBasicOptions((VisualBasicSyntaxFormattingOptions)(await Formatter.GetFormattingOptionsAsync(vbDocument, updatedOptions, CancellationToken.None)).Syntax!);
 
        // Validate that options are read from solution snapshot as a fallback (we have no editorconfig file, so all options should fall back):
 
        var updatedSolutionOptions = OptionsTestHelpers.GetOptionSetWithChangedOptions(workspace.CurrentSolution.Options, OptionsTestHelpers.PublicFormattingOptionsWithNonDefaultValues);
        var solutionWithUpdatedOptions = workspace.CurrentSolution.WithOptions(updatedSolutionOptions);
        var csDocumentWithUpdatedOptions = solutionWithUpdatedOptions.GetRequiredDocument(csDocument.Id);
        var vbDocumentWithUpdatedOptions = solutionWithUpdatedOptions.GetRequiredDocument(vbDocument.Id);
 
        ValidateCSharpOptions((CSharpSyntaxFormattingOptions)(await Formatter.GetFormattingOptionsAsync(csDocumentWithUpdatedOptions, optionSet: null, CancellationToken.None)).Syntax!);
        ValidateVisualBasicOptions((VisualBasicSyntaxFormattingOptions)(await Formatter.GetFormattingOptionsAsync(vbDocumentWithUpdatedOptions, optionSet: null, CancellationToken.None)).Syntax!);
        ValidateOrganizeImportsOptions(await Formatter.GetOrganizeImportsOptionsAsync(csDocumentWithUpdatedOptions, CancellationToken.None));
        ValidateOrganizeImportsOptions(await Formatter.GetOrganizeImportsOptionsAsync(vbDocumentWithUpdatedOptions, CancellationToken.None));
 
        static void ValidateCommonOptions(SyntaxFormattingOptions formattingOptions)
        {
            Assert.True(formattingOptions.UseTabs);
            Assert.Equal(5, formattingOptions.TabSize);
            Assert.Equal(7, formattingOptions.IndentationSize);
            Assert.Equal("\r", formattingOptions.NewLine);
        }
 
        static void ValidateCSharpOptions(CSharpSyntaxFormattingOptions formattingOptions)
        {
            ValidateCommonOptions(formattingOptions);
 
            Assert.False(formattingOptions.Spacing.HasFlag(SpacePlacement.AfterControlFlowStatementKeyword));
            Assert.False(formattingOptions.Spacing.HasFlag(SpacePlacement.AfterSemicolonsInForStatement));
            Assert.False(formattingOptions.Spacing.HasFlag(SpacePlacement.AfterColonInBaseTypeDeclaration));
            Assert.False(formattingOptions.Spacing.HasFlag(SpacePlacement.BeforeColonInBaseTypeDeclaration));
            Assert.False(formattingOptions.Spacing.HasFlag(SpacePlacement.AfterComma));
            Assert.True(formattingOptions.Spacing.HasFlag(SpacePlacement.IgnoreAroundVariableDeclaration));
            Assert.True(formattingOptions.Spacing.HasFlag(SpacePlacement.AfterMethodDeclarationName));
            Assert.True(formattingOptions.Spacing.HasFlag(SpacePlacement.BetweenEmptyMethodDeclarationParentheses));
            Assert.True(formattingOptions.Spacing.HasFlag(SpacePlacement.WithinMethodDeclarationParenthesis));
            Assert.True(formattingOptions.Spacing.HasFlag(SpacePlacement.AfterMethodCallName));
            Assert.True(formattingOptions.Spacing.HasFlag(SpacePlacement.BetweenEmptyMethodCallParentheses));
            Assert.True(formattingOptions.Spacing.HasFlag(SpacePlacement.WithinMethodCallParentheses));
            Assert.True(formattingOptions.Spacing.HasFlag(SpacePlacement.WithinExpressionParentheses));
            Assert.True(formattingOptions.Spacing.HasFlag(SpacePlacement.WithinCastParentheses));
            Assert.True(formattingOptions.Spacing.HasFlag(SpacePlacement.BeforeSemicolonsInForStatement));
            Assert.True(formattingOptions.Spacing.HasFlag(SpacePlacement.WithinOtherParentheses));
            Assert.True(formattingOptions.Spacing.HasFlag(SpacePlacement.AfterCast));
            Assert.True(formattingOptions.Spacing.HasFlag(SpacePlacement.BeforeOpenSquareBracket));
            Assert.True(formattingOptions.Spacing.HasFlag(SpacePlacement.BetweenEmptySquareBrackets));
            Assert.True(formattingOptions.Spacing.HasFlag(SpacePlacement.WithinSquareBrackets));
            Assert.True(formattingOptions.Spacing.HasFlag(SpacePlacement.BeforeComma));
            Assert.True(formattingOptions.Spacing.HasFlag(SpacePlacement.AfterDot));
            Assert.True(formattingOptions.Spacing.HasFlag(SpacePlacement.BeforeDot));
 
            Assert.Equal(BinaryOperatorSpacingOptions.Remove, formattingOptions.SpacingAroundBinaryOperator);
 
            Assert.False(formattingOptions.NewLines.HasFlag(NewLinePlacement.BeforeMembersInObjectInitializers));
            Assert.False(formattingOptions.NewLines.HasFlag(NewLinePlacement.BeforeMembersInAnonymousTypes));
            Assert.False(formattingOptions.NewLines.HasFlag(NewLinePlacement.BeforeElse));
            Assert.False(formattingOptions.NewLines.HasFlag(NewLinePlacement.BeforeCatch));
            Assert.False(formattingOptions.NewLines.HasFlag(NewLinePlacement.BeforeFinally));
            Assert.False(formattingOptions.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInTypes));
            Assert.False(formattingOptions.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInAnonymousTypes));
            Assert.False(formattingOptions.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInObjectCollectionArrayInitializers));
            Assert.False(formattingOptions.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInProperties));
            Assert.False(formattingOptions.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInMethods));
            Assert.False(formattingOptions.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInAccessors));
            Assert.False(formattingOptions.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInAnonymousMethods));
            Assert.False(formattingOptions.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInLambdaExpressionBody));
            Assert.False(formattingOptions.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInControlBlocks));
            Assert.False(formattingOptions.NewLines.HasFlag(NewLinePlacement.BetweenQueryExpressionClauses));
 
            Assert.Equal(LabelPositionOptions.LeftMost, formattingOptions.LabelPositioning);
 
            Assert.True(formattingOptions.Indentation.HasFlag(IndentationPlacement.Braces));
            Assert.False(formattingOptions.Indentation.HasFlag(IndentationPlacement.BlockContents));
            Assert.False(formattingOptions.Indentation.HasFlag(IndentationPlacement.SwitchCaseContents));
            Assert.False(formattingOptions.Indentation.HasFlag(IndentationPlacement.SwitchCaseContentsWhenBlock));
            Assert.False(formattingOptions.Indentation.HasFlag(IndentationPlacement.SwitchSection));
 
            Assert.False(formattingOptions.WrappingPreserveSingleLine);
            Assert.False(formattingOptions.WrappingKeepStatementsOnSingleLine);
        }
 
        static void ValidateVisualBasicOptions(VisualBasicSyntaxFormattingOptions simplifierOptions)
        {
            ValidateCommonOptions(simplifierOptions);
        }
 
        static void ValidateOrganizeImportsOptions(OrganizeImportsOptions options)
        {
            Assert.Equal("\r", options.NewLine);
        }
    }
}