File: src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\Formatting\Engine\AbstractTriviaDataFactory.cs
Web Access
Project: src\src\CodeStyle\Core\Analyzers\Microsoft.CodeAnalysis.CodeStyle.csproj (Microsoft.CodeAnalysis.CodeStyle)
// 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.Security.Principal;
using System.Threading;
using Microsoft.CodeAnalysis.Diagnostics;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Formatting;
 
internal abstract partial class AbstractTriviaDataFactory
{
    private const int SpaceCacheSize = 10;
    private const int LineBreakCacheSize = 5;
    private const int IndentationLevelCacheSize = 20;
 
    private static readonly Dictionary<LineFormattingOptions, (Whitespace[] spaces, Whitespace[,] whitespaces)> s_optionsToWhitespace = new();
    private static Tuple<LineFormattingOptions, (Whitespace[] spaces, Whitespace[,] whitespaces)>? s_lastOptionAndWhitespace;
 
    protected readonly TreeData TreeInfo;
    protected readonly LineFormattingOptions Options;
 
    private readonly Whitespace[] _spaces;
    private readonly Whitespace[,] _whitespaces;
 
    protected AbstractTriviaDataFactory(TreeData treeInfo, LineFormattingOptions options)
    {
        Contract.ThrowIfNull(treeInfo);
 
        TreeInfo = treeInfo;
        Options = options;
 
        (_spaces, _whitespaces) = GetSpacesAndWhitespaces(options);
    }
 
    private static (Whitespace[] spaces, Whitespace[,] whitespaces) GetSpacesAndWhitespaces(LineFormattingOptions options)
    {
        // Fast path where we'er asking for the same options as last time
        var lastOptionAndWhitespace = s_lastOptionAndWhitespace;
        if (lastOptionAndWhitespace?.Item1 == options)
            return lastOptionAndWhitespace.Item2;
 
        // Otherwise, get from the dictionary, computing if necessary.
        var (spaces, whitespaces) = ComputeAndCacheSpacesAndWhitespaces(options);
 
        // Cache this result for the next time.
        s_lastOptionAndWhitespace = Tuple.Create(options, (spaces, whitespaces));
        return (spaces, whitespaces);
 
        static (Whitespace[] spaces, Whitespace[,] whitespaces) ComputeAndCacheSpacesAndWhitespaces(LineFormattingOptions options)
        {
            // First check if it's already in the cache.
            lock (s_optionsToWhitespace)
            {
                if (s_optionsToWhitespace.TryGetValue(options, out var result))
                    return result;
            }
 
            // If not, compute it.
            var spaces = new Whitespace[SpaceCacheSize];
            for (var i = 0; i < SpaceCacheSize; i++)
                spaces[i] = new Whitespace(options, space: i, elastic: false);
 
            var whitespaces = new Whitespace[LineBreakCacheSize, IndentationLevelCacheSize];
            for (var lineIndex = 0; lineIndex < LineBreakCacheSize; lineIndex++)
            {
                for (var indentationLevel = 0; indentationLevel < IndentationLevelCacheSize; indentationLevel++)
                {
                    var indentation = indentationLevel * options.IndentationSize;
                    whitespaces[lineIndex, indentationLevel] = new Whitespace(
                        options, lineBreaks: lineIndex + 1, indentation: indentation, elastic: false);
                }
            }
 
            // Attempt to store in cache.  But defer to any other thread that may have already stored it.
            lock (s_optionsToWhitespace)
            {
                if (s_optionsToWhitespace.TryGetValue(options, out var result))
                    return result;
 
                s_optionsToWhitespace[options] = (spaces, whitespaces);
                return (spaces, whitespaces);
            }
        }
    }
 
    protected TriviaData GetSpaceTriviaData(int space, bool elastic = false)
    {
        Contract.ThrowIfFalse(space >= 0);
 
        // if result has elastic trivia in them, never use cache
        if (elastic)
        {
            return new Whitespace(this.Options, space, elastic: true);
        }
 
        if (space < SpaceCacheSize)
        {
            return _spaces[space];
        }
 
        // create a new space
        return new Whitespace(this.Options, space, elastic: false);
    }
 
    protected TriviaData GetWhitespaceTriviaData(int lineBreaks, int indentation, bool useTriviaAsItIs, bool elastic)
    {
        Contract.ThrowIfFalse(lineBreaks >= 0);
        Contract.ThrowIfFalse(indentation >= 0);
 
        // we can use cache
        //  #1. if whitespace trivia don't have any elastic trivia and
        //  #2. analysis (Item1) didn't find anything preventing us from using cache such as trailing whitespace before new line
        //  #3. number of line breaks (Item2) are under cache capacity (line breaks)
        //  #4. indentation (Item3) is aligned to indentation level
        var canUseCache = !elastic &&
                          useTriviaAsItIs &&
                          lineBreaks > 0 &&
                          lineBreaks <= LineBreakCacheSize &&
                          indentation % Options.IndentationSize == 0;
 
        if (canUseCache)
        {
            var indentationLevel = indentation / Options.IndentationSize;
            if (indentationLevel < IndentationLevelCacheSize)
            {
                var lineIndex = lineBreaks - 1;
                return _whitespaces[lineIndex, indentationLevel]!;
            }
        }
 
        return useTriviaAsItIs
            ? new Whitespace(this.Options, lineBreaks, indentation, elastic)
            : new ModifiedWhitespace(this.Options, lineBreaks, indentation, elastic);
    }
 
    public abstract TriviaData CreateLeadingTrivia(SyntaxToken token);
    public abstract TriviaData CreateTrailingTrivia(SyntaxToken token);
    public abstract TriviaData Create(SyntaxToken token1, SyntaxToken token2);
}