diff --git a/InterfaceStubGenerator.Roslyn38/InterfaceStubGenerator.Roslyn38.csproj b/InterfaceStubGenerator.Roslyn38/InterfaceStubGenerator.Roslyn38.csproj
index fa3e54b88..1a3a97d7f 100644
--- a/InterfaceStubGenerator.Roslyn38/InterfaceStubGenerator.Roslyn38.csproj
+++ b/InterfaceStubGenerator.Roslyn38/InterfaceStubGenerator.Roslyn38.csproj
@@ -10,6 +10,7 @@
true
enable
3.8.0
+ true
diff --git a/InterfaceStubGenerator.Roslyn41/InterfaceStubGenerator.Roslyn41.csproj b/InterfaceStubGenerator.Roslyn41/InterfaceStubGenerator.Roslyn41.csproj
index 8911bd287..192b8ebb1 100644
--- a/InterfaceStubGenerator.Roslyn41/InterfaceStubGenerator.Roslyn41.csproj
+++ b/InterfaceStubGenerator.Roslyn41/InterfaceStubGenerator.Roslyn41.csproj
@@ -11,6 +11,7 @@
enable
$(DefineConstants);ROSLYN_4
4.1.0
+ true
diff --git a/InterfaceStubGenerator.Shared/Emitter.cs b/InterfaceStubGenerator.Shared/Emitter.cs
index a1305fb48..cc6280128 100644
--- a/InterfaceStubGenerator.Shared/Emitter.cs
+++ b/InterfaceStubGenerator.Shared/Emitter.cs
@@ -69,20 +69,18 @@ public static void Initialize()
addSource("Generated.g.cs", SourceText.From(generatedClassText, Encoding.UTF8));
}
- public static string EmitInterface(InterfaceModel model)
+ public static SourceText EmitInterface(InterfaceModel model)
{
- var source = new StringBuilder();
+ var source = new SourceWriter();
// if nullability is supported emit the nullable directive
if (model.Nullability != Nullability.None)
{
- source.Append("#nullable ");
- source.Append(model.Nullability == Nullability.Enabled ? "enable" : "disable");
+ source.WriteLine("#nullable " + (model.Nullability == Nullability.Enabled ? "enable" : "disable"));
}
- source.Append(
- $@"
-#pragma warning disable
+ source.WriteLine(
+ $@"#pragma warning disable
namespace Refit.Implementation
{{
@@ -108,8 +106,7 @@ partial class {model.Ns}{model.ClassDeclaration}
{{
Client = client;
this.requestBuilder = requestBuilder;
- }}
-"
+ }}"
);
var uniqueNames = new UniqueNameBuilder();
@@ -138,16 +135,15 @@ partial class {model.Ns}{model.ClassDeclaration}
WriteDisposableMethod(source);
}
- source.Append(
+ source.WriteLine(
@"
}
}
}
-#pragma warning restore
-"
+#pragma warning restore"
);
- return source.ToString();
+ return source.ToSourceText();
}
///
@@ -158,7 +154,7 @@ partial class {model.Ns}{model.ClassDeclaration}
/// True if directly from the type we're generating for, false for methods found on base interfaces
/// Contains the unique member names in the interface scope.
private static void WriteRefitMethod(
- StringBuilder source,
+ SourceWriter source,
MethodModel methodModel,
bool isTopLevel,
UniqueNameBuilder uniqueNames
@@ -220,23 +216,23 @@ UniqueNameBuilder uniqueNames
WriteMethodClosing(source);
}
- private static void WriteNonRefitMethod(StringBuilder source, MethodModel methodModel)
+ private static void WriteNonRefitMethod(SourceWriter source, MethodModel methodModel)
{
WriteMethodOpening(source, methodModel, true);
- source.Append(
+ source.WriteLine(
@"
- throw new global::System.NotImplementedException(""Either this method has no Refit HTTP method attribute or you've used something other than a string literal for the 'path' argument."");
- "
- );
+ throw new global::System.NotImplementedException(""Either this method has no Refit HTTP method attribute or you've used something other than a string literal for the 'path' argument."");");
+ source.Indentation += 1;
WriteMethodClosing(source);
+ source.Indentation -= 1;
}
// TODO: This assumes that the Dispose method is a void that takes no parameters.
// The previous version did not.
// Does the bool overload cause an issue here.
- private static void WriteDisposableMethod(StringBuilder source)
+ private static void WriteDisposableMethod(SourceWriter source)
{
source.Append(
"""
@@ -252,7 +248,7 @@ private static void WriteDisposableMethod(StringBuilder source)
}
private static string GenerateTypeParameterExpression(
- StringBuilder source,
+ SourceWriter source,
MethodModel methodModel,
UniqueNameBuilder uniqueNames
)
@@ -283,7 +279,7 @@ UniqueNameBuilder uniqueNames
}
private static void WriteMethodOpening(
- StringBuilder source,
+ SourceWriter source,
MethodModel methodModel,
bool isExplicitInterface,
bool isAsync = false
@@ -324,7 +320,7 @@ private static void WriteMethodOpening(
);
}
- private static void WriteMethodClosing(StringBuilder source) => source.Append(@" }");
+ private static void WriteMethodClosing(SourceWriter source) => source.Append(@" }");
private static string GenerateConstraints(
ImmutableEquatableArray typeParameters,
diff --git a/InterfaceStubGenerator.Shared/IncrementalValuesProviderExtensions.cs b/InterfaceStubGenerator.Shared/IncrementalValuesProviderExtensions.cs
index 0766c4c93..ef4bd070b 100644
--- a/InterfaceStubGenerator.Shared/IncrementalValuesProviderExtensions.cs
+++ b/InterfaceStubGenerator.Shared/IncrementalValuesProviderExtensions.cs
@@ -60,7 +60,7 @@ IncrementalValuesProvider model
static (spc, model) =>
{
var mapperText = Emitter.EmitInterface(model);
- spc.AddSource(model.FileName, SourceText.From(mapperText, Encoding.UTF8));
+ spc.AddSource(model.FileName, mapperText);
}
);
}
diff --git a/InterfaceStubGenerator.Shared/InterfaceStubGenerator.Shared.projitems b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.Shared.projitems
index f4cfc6944..7e8f0ebb5 100644
--- a/InterfaceStubGenerator.Shared/InterfaceStubGenerator.Shared.projitems
+++ b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.Shared.projitems
@@ -23,6 +23,7 @@
+
\ No newline at end of file
diff --git a/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs
index d33ffc17d..bd6e91cde 100644
--- a/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs
+++ b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs
@@ -63,7 +63,7 @@ out var refitInternalNamespace
var interfaceText = Emitter.EmitInterface(interfaceModel);
context.AddSource(
interfaceModel.FileName,
- SourceText.From(interfaceText, Encoding.UTF8)
+ interfaceText
);
}
diff --git a/InterfaceStubGenerator.Shared/SourceWriter.cs b/InterfaceStubGenerator.Shared/SourceWriter.cs
new file mode 100644
index 000000000..ac93b28e9
--- /dev/null
+++ b/InterfaceStubGenerator.Shared/SourceWriter.cs
@@ -0,0 +1,142 @@
+using System.Diagnostics;
+using System.Text;
+
+using Microsoft.CodeAnalysis.Text;
+
+namespace Refit.Generator;
+
+// From https://github.com/dotnet/runtime/blob/233826c88d2100263fb9e9535d96f75824ba0aea/src/libraries/Common/src/SourceGenerators/SourceWriter.cs#L11
+internal sealed class SourceWriter
+{
+ private const char IndentationChar = ' ';
+ private const int CharsPerIndentation = 4;
+
+ private readonly StringBuilder _sb = new();
+ private int _indentation;
+
+ public int Indentation
+ {
+ get => _indentation;
+ set
+ {
+ if (value < 0)
+ {
+ Throw();
+ static void Throw() => throw new ArgumentOutOfRangeException(nameof(value));
+ }
+
+ _indentation = value;
+ }
+ }
+
+ public void Append(string text)
+ {
+ if (_indentation == 0)
+ {
+ _sb.Append(text);
+ return;
+ }
+
+ bool isFinalLine;
+ ReadOnlySpan remainingText = text.AsSpan();
+ do
+ {
+ ReadOnlySpan nextLine = GetNextLine(ref remainingText, out isFinalLine);
+
+ AddIndentation();
+ AppendSpan(_sb, nextLine);
+ if (!isFinalLine)
+ {
+ _sb.AppendLine();
+ }
+ }
+ while (!isFinalLine);
+ }
+
+ public void WriteLine(char value)
+ {
+ AddIndentation();
+ _sb.Append(value);
+ _sb.AppendLine();
+ }
+
+ public void WriteLine(string text)
+ {
+ if (_indentation == 0)
+ {
+ _sb.AppendLine(text);
+ return;
+ }
+
+ bool isFinalLine;
+ ReadOnlySpan remainingText = text.AsSpan();
+ do
+ {
+ ReadOnlySpan nextLine = GetNextLine(ref remainingText, out isFinalLine);
+
+ AddIndentation();
+ AppendSpan(_sb, nextLine);
+ _sb.AppendLine();
+ }
+ while (!isFinalLine);
+ }
+
+ public void WriteLine() => _sb.AppendLine();
+
+ public SourceText ToSourceText()
+ {
+ Debug.Assert(_indentation == 0 && _sb.Length > 0);
+ return SourceText.From(_sb.ToString(), Encoding.UTF8);
+ }
+
+ public void Reset()
+ {
+ _sb.Clear();
+ _indentation = 0;
+ }
+
+ private void AddIndentation()
+ => _sb.Append(IndentationChar, CharsPerIndentation * _indentation);
+
+ private static ReadOnlySpan GetNextLine(ref ReadOnlySpan remainingText, out bool isFinalLine)
+ {
+ if (remainingText.IsEmpty)
+ {
+ isFinalLine = true;
+ return default;
+ }
+
+ ReadOnlySpan next;
+ ReadOnlySpan rest;
+
+ int lineLength = remainingText.IndexOf('\n');
+ if (lineLength == -1)
+ {
+ lineLength = remainingText.Length;
+ isFinalLine = true;
+ rest = default;
+ }
+ else
+ {
+ rest = remainingText.Slice(lineLength + 1);
+ isFinalLine = false;
+ }
+
+ if ((uint)lineLength > 0 && remainingText[lineLength - 1] == '\r')
+ {
+ lineLength--;
+ }
+
+ next = remainingText.Slice(0, lineLength);
+ remainingText = rest;
+ return next;
+ }
+
+ private static unsafe void AppendSpan(StringBuilder builder, ReadOnlySpan span)
+ {
+ fixed (char* ptr = span)
+ {
+ builder.Append(ptr, span.Length);
+ }
+ }
+}
diff --git a/Refit.GeneratorTests/_snapshots/InterfaceTests.NonRefitMethodShouldRaiseDiagnostic#IGeneratedClient.g.verified.cs b/Refit.GeneratorTests/_snapshots/InterfaceTests.NonRefitMethodShouldRaiseDiagnostic#IGeneratedClient.g.verified.cs
index 1b01c0762..bd40b5712 100644
--- a/Refit.GeneratorTests/_snapshots/InterfaceTests.NonRefitMethodShouldRaiseDiagnostic#IGeneratedClient.g.verified.cs
+++ b/Refit.GeneratorTests/_snapshots/InterfaceTests.NonRefitMethodShouldRaiseDiagnostic#IGeneratedClient.g.verified.cs
@@ -50,7 +50,7 @@ public RefitGeneratorTestIGeneratedClient(global::System.Net.Http.HttpClient cli
///
void global::RefitGeneratorTest.IGeneratedClient.NonRefitMethod()
{
- throw new global::System.NotImplementedException("Either this method has no Refit HTTP method attribute or you've used something other than a string literal for the 'path' argument.");
+ throw new global::System.NotImplementedException("Either this method has no Refit HTTP method attribute or you've used something other than a string literal for the 'path' argument.");
}
}
}
diff --git a/Refit.GeneratorTests/_snapshots/InterfaceTests.RefitInterfaceDerivedFromBaseTest#IGeneratedInterface.g.verified.cs b/Refit.GeneratorTests/_snapshots/InterfaceTests.RefitInterfaceDerivedFromBaseTest#IGeneratedInterface.g.verified.cs
index e4f43fc5c..ad553c934 100644
--- a/Refit.GeneratorTests/_snapshots/InterfaceTests.RefitInterfaceDerivedFromBaseTest#IGeneratedInterface.g.verified.cs
+++ b/Refit.GeneratorTests/_snapshots/InterfaceTests.RefitInterfaceDerivedFromBaseTest#IGeneratedInterface.g.verified.cs
@@ -50,7 +50,7 @@ public RefitGeneratorTestIGeneratedInterface(global::System.Net.Http.HttpClient
///
void global::RefitGeneratorTest.IBaseInterface.NonRefitMethod()
{
- throw new global::System.NotImplementedException("Either this method has no Refit HTTP method attribute or you've used something other than a string literal for the 'path' argument.");
+ throw new global::System.NotImplementedException("Either this method has no Refit HTTP method attribute or you've used something other than a string literal for the 'path' argument.");
}
}
}
diff --git a/Refit.GeneratorTests/_snapshots/MethodTests.MethodsWithGenericConstraints#IGeneratedClient.g.verified.cs b/Refit.GeneratorTests/_snapshots/MethodTests.MethodsWithGenericConstraints#IGeneratedClient.g.verified.cs
index 3b8ce9976..349e3867c 100644
--- a/Refit.GeneratorTests/_snapshots/MethodTests.MethodsWithGenericConstraints#IGeneratedClient.g.verified.cs
+++ b/Refit.GeneratorTests/_snapshots/MethodTests.MethodsWithGenericConstraints#IGeneratedClient.g.verified.cs
@@ -61,7 +61,7 @@ public RefitGeneratorTestIGeneratedClient(global::System.Net.Http.HttpClient cli
where T3 : struct
where T5 : class
{
- throw new global::System.NotImplementedException("Either this method has no Refit HTTP method attribute or you've used something other than a string literal for the 'path' argument.");
+ throw new global::System.NotImplementedException("Either this method has no Refit HTTP method attribute or you've used something other than a string literal for the 'path' argument.");
}
}
}