Skip to content

Commit

Permalink
Share SourceWriter between JSON & config binding generators (#89150)
Browse files Browse the repository at this point in the history
* Share SourceWriter between JSON & config binding generators

* Fix failing test, clean up impl, and address feedback
  • Loading branch information
layomia authored Jul 19, 2023
1 parent 50ba405 commit 7036cba
Show file tree
Hide file tree
Showing 32 changed files with 298 additions and 286 deletions.
Original file line number Diff line number Diff line change
@@ -1,42 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.CodeAnalysis.Text;
using System;
using System.Text;
using System.Diagnostics;
using Microsoft.CodeAnalysis.Text;

namespace System.Text.Json.SourceGeneration
namespace SourceGenerators
{
internal sealed class SourceWriter
{
private const char IndentationChar = ' ';
private const int CharsPerIndentation = 4;

private readonly StringBuilder _sb = new();
private int _indentation;

public SourceWriter()
{
IndentationChar = ' ';
CharsPerIndentation = 4;
}

public SourceWriter(char indentationChar, int charsPerIndentation)
{
if (!char.IsWhiteSpace(indentationChar))
{
throw new ArgumentOutOfRangeException(nameof(indentationChar));
}

if (charsPerIndentation < 1)
{
throw new ArgumentOutOfRangeException(nameof(charsPerIndentation));
}

IndentationChar = indentationChar;
CharsPerIndentation = charsPerIndentation;
}

public char IndentationChar { get; }
public int CharsPerIndentation { get; }

public int Length => _sb.Length;
public int Indentation
{
get => _indentation;
Expand Down Expand Up @@ -88,6 +67,12 @@ public SourceText ToSourceText()
return SourceText.From(_sb.ToString(), Encoding.UTF8);
}

public void Reset()
{
_sb.Clear();
_indentation = 0;
}

private void AddIndentation()
=> _sb.Append(IndentationChar, CharsPerIndentation * _indentation);

Expand Down
6 changes: 6 additions & 0 deletions src/libraries/Common/tests/Common.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
Link="Common\Interop\Linux\Interop.ProcFsStat.TryReadStatusFile.cs" />
<Compile Include="$(CommonPath)Interop\Linux\os-release\Interop.OSReleaseFile.cs"
Link="Common\Interop\Linux\os-release\Interop.OSReleaseFile.cs" />
<Compile Include="$(CommonPath)SourceGenerators\SourceWriter.cs"
Link="Common\SourceGenerators\SourceWriter.cs" />
<Compile Include="$(CommonPath)System\CharArrayHelpers.cs"
Link="Common\System\CharArrayHelpers.cs" />
<Compile Include="$(CommonPath)System\StringExtensions.cs"
Expand Down Expand Up @@ -81,6 +83,7 @@
<Compile Include="Tests\Interop\cgroupsTests.cs" />
<Compile Include="Tests\Interop\procfsTests.cs" />
<Compile Include="Tests\Interop\OSReleaseTests.cs" />
<Compile Include="Tests\SourceGenerators\SourceWriterTests.cs" />
<Compile Include="Tests\System\IO\PathInternal.Tests.cs" />
<Compile Include="Tests\System\IO\StringParserTests.cs" />
<Compile Include="Tests\System\Net\HttpDateParserTests.cs" />
Expand Down Expand Up @@ -155,6 +158,9 @@
<Folder Include="System\Net\Sockets\" />
<Folder Include="System\Net\VirtualNetwork\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(MicrosoftCodeAnalysisVersion_LatestVS)" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(CommonTestPath)StreamConformanceTests\StreamConformanceTests.csproj" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using SourceGenerators;
using Xunit;

namespace Common.Tests
{
public sealed class SourceWriterTests
{
[Fact]
public void CanHandleVariousLineEndings()
{
string testTemplate = "public static void Main(){0}{{{1}\tConsole.WriteLine(\"Hello, world\");{2}}}";
SourceWriter writer = new();

CheckCanWrite(string.Format(testTemplate, "\n", "\n", "\n"));
CheckCanWrite(string.Format(testTemplate, "\r\n", "\r\n", "\r\n"));
CheckCanWrite(string.Format(testTemplate, "\n", "\r\n", "\n"));

// Regression test for https://github.com/dotnet/runtime/issues/88918.
CheckCanWrite(" global::Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.AddOptions(services);\r\n global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton<global::Microsoft.Extensions.Options.IOptionsChangeTokenSource<TOptions>>(services, new global::Microsoft.Extensions.Options.ConfigurationChangeTokenSource<TOptions>(name, configuration));\r\n return global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton<global::Microsoft.Extensions.Options.IConfigureOptions<TOptions>>(services, new global::Microsoft.Extensions.Options.ConfigureNamedOptions<TOptions>(name, obj => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCoreUntyped(configuration, obj, typeof(TOptions), configureOptions)));\r\n}");

void CheckCanWrite(string source)
{
// No exception expected.
writer.WriteLine(source);
writer.Reset();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Diagnostics;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;
using SourceGenerators;

namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{
Expand Down Expand Up @@ -36,17 +37,17 @@ public void Emit()
return;
}

_writer.WriteBlock("""
_writer.WriteLine("""
// <auto-generated/>
#nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
""");
_writer.WriteBlankLine();
_writer.WriteLine();

_useFullyQualifiedNames = true;
EmitBinder_ConfigurationBinder();
EmitBinder_Extensions_IConfiguration();
EmitBinder_Extensions_OptionsBuilder();
EmitBinder_Extensions_ServiceCollection();
EmitBinder_Extensions_IServiceCollection();

_useFullyQualifiedNames = false;
Emit_CoreBindingHelper();
Expand Down Expand Up @@ -131,14 +132,16 @@ private void EmitBindLogicFromString(

if (!checkForNullSectionValue)
{
writeOnSuccess?.Invoke(parsedValueExpr);
InvokeWriteOnSuccess();
}
else
{
_writer.WriteBlockStart($"if ({sectionValueExpr} is string {nonNull_StringValue_Identifier})");
writeOnSuccess?.Invoke(parsedValueExpr);
_writer.WriteBlockEnd();
EmitStartBlock($"if ({sectionValueExpr} is string {nonNull_StringValue_Identifier})");
InvokeWriteOnSuccess();
EmitEndBlock();
}

void InvokeWriteOnSuccess() => writeOnSuccess?.Invoke(parsedValueExpr);
}

private bool EmitObjectInit(TypeSpec type, string memberAccessExpr, InitializationKind initKind, string configArgExpr)
Expand Down Expand Up @@ -222,7 +225,7 @@ private void EmitCastToIConfigurationSection()
exceptionTypeDisplayString = nameof(InvalidOperationException);
}

_writer.WriteBlock($$"""
_writer.WriteLine($$"""
if ({{Identifier.configuration}} is not {{sectionTypeDisplayString}} {{Identifier.section}})
{
throw new {{exceptionTypeDisplayString}}();
Expand All @@ -235,13 +238,13 @@ private void EmitIConfigurationHasValueOrChildrenCheck(bool voidReturn)
string returnPostfix = voidReturn ? string.Empty : " null";
string methodDisplayString = GetHelperMethodDisplayString(Identifier.HasValueOrChildren);

_writer.WriteBlock($$"""
_writer.WriteLine($$"""
if (!{{methodDisplayString}}({{Identifier.configuration}}))
{
return{{returnPostfix}};
}
""");
_writer.WriteBlankLine();
_writer.WriteLine();
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ private sealed partial class Emitter
{
private bool ShouldEmitMethods(MethodsToGen_ConfigurationBinder methods) => (_sourceGenSpec.MethodsToGen_ConfigurationBinder & methods) != 0;

private void EmitBinder_ConfigurationBinder()
private void EmitBinder_Extensions_IConfiguration()
{
Debug.Assert(_sourceGenSpec.TypesForGen_ConfigurationBinder_BindMethods.Count <= 3 &&
!_sourceGenSpec.TypesForGen_ConfigurationBinder_BindMethods.Keys.Any(overload => (overload & MethodsToGen_ConfigurationBinder.Bind) is 0));
Expand All @@ -25,13 +25,13 @@ private void EmitBinder_ConfigurationBinder()
}

_emitBlankLineBeforeNextStatement = false;
EmitRootBindingClassBlockStart(Identifier.GeneratedConfigurationBinder);
EmitRootBindingClassStartBlock(Identifier.GeneratedConfigurationBinder);

EmitGetMethods();
EmitGetValueMethods();
EmitBindMethods_ConfigurationBinder();

_writer.WriteBlockEnd();
EmitEndBlock();
_emitBlankLineBeforeNextStatement = true;
}

Expand Down
Loading

0 comments on commit 7036cba

Please sign in to comment.