Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<IsRoslynComponent>true</IsRoslynComponent>
<Nullable>enable</Nullable>
<MsCACSharpVersion>3.8.0</MsCACSharpVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<Nullable>enable</Nullable>
<DefineConstants>$(DefineConstants);ROSLYN_4</DefineConstants>
<MsCACSharpVersion>4.1.0</MsCACSharpVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
Expand Down
42 changes: 19 additions & 23 deletions InterfaceStubGenerator.Shared/Emitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{{

Expand All @@ -108,8 +106,7 @@ partial class {model.Ns}{model.ClassDeclaration}
{{
Client = client;
this.requestBuilder = requestBuilder;
}}
"
}}"
);

var uniqueNames = new UniqueNameBuilder();
Expand Down Expand Up @@ -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();
}

/// <summary>
Expand All @@ -158,7 +154,7 @@ partial class {model.Ns}{model.ClassDeclaration}
/// <param name="isTopLevel">True if directly from the type we're generating for, false for methods found on base interfaces</param>
/// <param name="uniqueNames">Contains the unique member names in the interface scope.</param>
private static void WriteRefitMethod(
StringBuilder source,
SourceWriter source,
MethodModel methodModel,
bool isTopLevel,
UniqueNameBuilder uniqueNames
Expand Down Expand Up @@ -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(
"""
Expand All @@ -252,7 +248,7 @@ private static void WriteDisposableMethod(StringBuilder source)
}

private static string GenerateTypeParameterExpression(
StringBuilder source,
SourceWriter source,
MethodModel methodModel,
UniqueNameBuilder uniqueNames
)
Expand Down Expand Up @@ -283,7 +279,7 @@ UniqueNameBuilder uniqueNames
}

private static void WriteMethodOpening(
StringBuilder source,
SourceWriter source,
MethodModel methodModel,
bool isExplicitInterface,
bool isAsync = false
Expand Down Expand Up @@ -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<TypeConstraint> typeParameters,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ IncrementalValuesProvider<InterfaceModel> model
static (spc, model) =>
{
var mapperText = Emitter.EmitInterface(model);
spc.AddSource(model.FileName, SourceText.From(mapperText, Encoding.UTF8));
spc.AddSource(model.FileName, mapperText);
}
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Models\TypeConstraint.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Models\WellKnownTYpes.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parser.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SourceWriter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UniqueNameBuilder.cs" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ out var refitInternalNamespace
var interfaceText = Emitter.EmitInterface(interfaceModel);
context.AddSource(
interfaceModel.FileName,
SourceText.From(interfaceText, Encoding.UTF8)
interfaceText
);
}

Expand Down
142 changes: 142 additions & 0 deletions InterfaceStubGenerator.Shared/SourceWriter.cs
Original file line number Diff line number Diff line change
@@ -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<char> remainingText = text.AsSpan();
do
{
ReadOnlySpan<char> 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<char> remainingText = text.AsSpan();
do
{
ReadOnlySpan<char> 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<char> GetNextLine(ref ReadOnlySpan<char> remainingText, out bool isFinalLine)
{
if (remainingText.IsEmpty)
{
isFinalLine = true;
return default;
}

ReadOnlySpan<char> next;
ReadOnlySpan<char> 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<char> span)
{
fixed (char* ptr = span)
{
builder.Append(ptr, span.Length);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public RefitGeneratorTestIGeneratedClient(global::System.Net.Http.HttpClient cli
/// <inheritdoc />
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.");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public RefitGeneratorTestIGeneratedInterface(global::System.Net.Http.HttpClient
/// <inheritdoc />
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.");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
}
}
}
Expand Down
Loading