File: MetadataAsSource\AbstractMetadataAsSourceService.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// 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.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.DocumentationComments;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Formatting.Rules;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Simplification;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.MetadataAsSource;
 
internal abstract partial class AbstractMetadataAsSourceService : IMetadataAsSourceService
{
    public async Task<Document> AddSourceToAsync(
        Document document,
        Compilation symbolCompilation,
        ISymbol symbol,
        SyntaxFormattingOptions? formattingOptions,
        CancellationToken cancellationToken)
    {
        var newSemanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
        var rootNamespace = newSemanticModel.GetEnclosingNamespace(position: 0, cancellationToken);
        Contract.ThrowIfNull(rootNamespace);
 
        var context = new CodeGenerationSolutionContext(
            document.Project.Solution,
            new CodeGenerationContext(
                contextLocation: newSemanticModel.SyntaxTree.GetLocation(new TextSpan()),
                generateMethodBodies: false,
                generateDocumentationComments: true,
                mergeAttributes: false,
                autoInsertionLocation: false));
 
        // Add the interface of the symbol to the top of the root namespace
        document = await CodeGenerator.AddNamespaceOrTypeDeclarationAsync(
            context,
            rootNamespace,
            CreateCodeGenerationSymbol(document, symbol),
            cancellationToken).ConfigureAwait(false);
 
        document = await AddNullableRegionsAsync(document, cancellationToken).ConfigureAwait(false);
 
        var docCommentFormattingService = document.GetRequiredLanguageService<IDocumentationCommentFormattingService>();
        var docWithDocComments = await ConvertDocCommentsToRegularCommentsAsync(document, docCommentFormattingService, cancellationToken).ConfigureAwait(false);
 
        var docWithAssemblyInfo = await AddAssemblyInfoRegionAsync(docWithDocComments, symbolCompilation, symbol.GetOriginalUnreducedDefinition(), cancellationToken).ConfigureAwait(false);
        var node = await docWithAssemblyInfo.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
 
        var formattedDoc = await Formatter.FormatAsync(
            docWithAssemblyInfo,
            [node.FullSpan],
            formattingOptions,
            GetFormattingRules(docWithAssemblyInfo),
            cancellationToken).ConfigureAwait(false);
 
        var reducers = GetReducers();
        return await Simplifier.ReduceAsync(formattedDoc, reducers, cancellationToken).ConfigureAwait(false);
    }
 
    protected abstract Task<Document> AddNullableRegionsAsync(Document document, CancellationToken cancellationToken);
 
    /// <summary>
    /// provide formatting rules to be used when formatting MAS file
    /// </summary>
    protected abstract ImmutableArray<AbstractFormattingRule> GetFormattingRules(Document document);
 
    /// <summary>
    /// Prepends a region directive at the top of the document with a name containing
    /// information about the assembly and a comment inside containing the path to the
    /// referenced assembly.  The containing assembly may not have a path on disk, in which case
    /// a string similar to "location unknown" will be placed in the comment inside the region
    /// instead of the path.
    /// </summary>
    /// <param name="document">The document to generate source into</param>
    /// <param name="symbolCompilation">The <see cref="Compilation"/> in which symbol is resolved.</param>
    /// <param name="symbol">The symbol to generate source for</param>
    /// <param name="cancellationToken">To cancel document operations</param>
    /// <returns>The updated document</returns>
    protected abstract Task<Document> AddAssemblyInfoRegionAsync(Document document, Compilation symbolCompilation, ISymbol symbol, CancellationToken cancellationToken);
 
    protected abstract Task<Document> ConvertDocCommentsToRegularCommentsAsync(Document document, IDocumentationCommentFormattingService docCommentFormattingService, CancellationToken cancellationToken);
 
    protected abstract ImmutableArray<AbstractReducer> GetReducers();
 
    private static INamespaceOrTypeSymbol CreateCodeGenerationSymbol(Document document, ISymbol symbol)
    {
        symbol = symbol.GetOriginalUnreducedDefinition();
        var topLevelNamespaceSymbol = symbol.ContainingNamespace;
        var topLevelNamedType = MetadataAsSourceHelpers.GetTopLevelContainingNamedType(symbol);
 
        var canImplementImplicitly = document.GetRequiredLanguageService<ISemanticFactsService>().SupportsImplicitInterfaceImplementation;
        var docCommentFormattingService = document.GetLanguageService<IDocumentationCommentFormattingService>();
 
        INamespaceOrTypeSymbol wrappedType = new WrappedNamedTypeSymbol(topLevelNamedType, canImplementImplicitly, docCommentFormattingService);
 
        return topLevelNamespaceSymbol.IsGlobalNamespace
            ? wrappedType
            : CodeGenerationSymbolFactory.CreateNamespaceSymbol(
                topLevelNamespaceSymbol.ToDisplayString(SymbolDisplayFormats.NameFormat),
                null,
                [wrappedType]);
    }
}