diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/IndentCacheTests.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/IndentCacheTests.cs new file mode 100644 index 00000000000..c7f9fa26360 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/IndentCacheTests.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +using IndentCache = Microsoft.AspNetCore.Razor.Language.CodeGeneration.CodeWriter.IndentCache; + +namespace Microsoft.AspNetCore.Razor.Language.Test.CodeGeneration; + +public class IndentCacheTest +{ + [Theory] + [InlineData(0, false, 4, "")] + [InlineData(4, false, 4, " ")] + [InlineData(8, false, 4, " ")] + [InlineData(0, true, 4, "")] + [InlineData(4, true, 4, "\t")] + [InlineData(8, true, 4, "\t\t")] + [InlineData(6, true, 4, "\t ")] + [InlineData(5, true, 4, "\t ")] + [InlineData(3, true, 4, " ")] + public void GetIndentString_ReturnsExpectedString(int size, bool useTabs, int tabSize, string expected) + { + var result = IndentCache.GetIndentString(size, useTabs, tabSize); + Assert.Equal(expected, result.ToString()); + } + + [Fact] + public void GetIndentString_TabSizeOne_UsesOnlyTabs() + { + var result = IndentCache.GetIndentString(size: 5, useTabs: true, tabSize: 1); + Assert.Equal(new string('\t', 5), result.ToString()); + } + + [Fact] + public void GetIndentString_TabSizeGreaterThanSize_UsesSpaces() + { + var result = IndentCache.GetIndentString(size: 3, useTabs: true, tabSize: 10); + Assert.Equal(" ", result.ToString()); + } + + [Fact] + public void GetIndentString_TabsAndSpacesInResultExceedCachedSizes() + { + var spaceCount = IndentCache.MaxSpaceCount + 1; + var tabCount = IndentCache.MaxTabCount + 1; + var tabSize = spaceCount + 1; + + var size = tabSize * tabCount + spaceCount; + var result = IndentCache.GetIndentString(size, useTabs: true, tabSize); + + var expected = new string('\t', tabCount) + new string(' ', spaceCount); + Assert.Equal(expected, result.ToString()); + } +} diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/CodeWriter.IndentCache.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/CodeWriter.IndentCache.cs new file mode 100644 index 00000000000..562e3ce425c --- /dev/null +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/CodeWriter.IndentCache.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration; + +public sealed partial class CodeWriter +{ + internal static class IndentCache + { + internal const int MaxTabCount = 64; + internal const int MaxSpaceCount = 128; + + private static readonly ReadOnlyMemory s_tabs = new string('\t', MaxTabCount).AsMemory(); + private static readonly ReadOnlyMemory s_spaces = new string(' ', MaxSpaceCount).AsMemory(); + + public static ReadOnlyMemory GetIndentString(int size, bool useTabs, int tabSize) + { + if (!useTabs) + { + return SliceOrCreate(size, s_spaces); + } + + var tabCount = size / tabSize; + var spaceCount = size % tabSize; + + if (spaceCount == 0) + { + return SliceOrCreate(tabCount, s_tabs); + } + + return string.Create(length: tabCount + spaceCount, state: tabCount, static (destination, tabCount) => + { + destination[..tabCount].Fill('\t'); + destination[tabCount..].Fill(' '); + }).AsMemory(); + } + + private static ReadOnlyMemory SliceOrCreate(int length, ReadOnlyMemory chars) + { + return (length <= chars.Length) + ? chars[..length] + : new string(chars.Span[0], length).AsMemory(); + } + } +} diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/CodeWriter.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/CodeWriter.cs index 0f5dd3cc547..01e9944bd90 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/CodeWriter.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/CodeWriter.cs @@ -9,7 +9,6 @@ using System.IO; using System.Runtime.CompilerServices; using System.Text; -using Microsoft.AspNetCore.Razor.PooledObjects; using Microsoft.CodeAnalysis.Text; namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration; @@ -118,7 +117,7 @@ public int CurrentIndent if (_indentSize != value) { _indentSize = value; - _indentString = ComputeIndent(value, IndentWithTabs, TabSize); + _indentString = IndentCache.GetIndentString(value, IndentWithTabs, TabSize); } } } @@ -189,7 +188,7 @@ public CodeWriter Indent(int size) var indentString = size == _indentSize ? _indentString - : ComputeIndent(size, IndentWithTabs, TabSize); + : IndentCache.GetIndentString(size, IndentWithTabs, TabSize); AddTextChunk(indentString); @@ -200,30 +199,6 @@ public CodeWriter Indent(int size) return this; } - private static ReadOnlyMemory ComputeIndent(int size, bool useTabs, int tabSize) - { - if (size == 0) - { - return ReadOnlyMemory.Empty; - } - - if (useTabs) - { - var tabCount = size / tabSize; - var spaceCount = size % tabSize; - - using var _ = StringBuilderPool.GetPooledObject(out var builder); - builder.SetCapacityIfLarger(tabCount + spaceCount); - - builder.Append('\t', tabCount); - builder.Append(' ', spaceCount); - - return builder.ToString().AsMemory(); - } - - return new string(' ', size).AsMemory(); - } - public CodeWriter Write(string value) { ArgHelper.ThrowIfNull(value);