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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1,222 changes: 614 additions & 608 deletions .editorconfig

Large diffs are not rendered by default.

78 changes: 62 additions & 16 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,19 +1,65 @@
# Auto detect text files and perform LF normalization
* text=auto
# Auto-detect text files and force LF in the repository AND the working tree, on every
# platform. eol=lf is set explicitly on each pattern: a more specific line only overrides
# the attributes it names, and a bare `text` would otherwise fall back to core.autocrlf
# and check out CRLF on Windows.
* text=auto eol=lf

# Custom for Visual Studio
*.cs diff=csharp
# Source code
*.cs text eol=lf diff=csharp
*.xaml text eol=lf
*.slnx text eol=lf
*.sln text eol=lf
*.csproj text eol=lf
*.props text eol=lf
*.targets text eol=lf
*.json text eol=lf
*.xml text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
*.md text eol=lf
*.txt text eol=lf
*.sh text eol=lf
*.ps1 text eol=lf
*.cmd text eol=lf
*.bat text eol=lf
*.config text eol=lf
*.editorconfig text eol=lf

# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
# Binary files (do not normalise or diff). `binary` expands to `-text -diff`, which
# overrides the `* text=auto eol=lf` above. Every extension here previously carried
# `-text` only by virtue of its Git LFS line, so all of them must be listed explicitly
# now that LFS is gone, or `text=auto` would be left guessing.

.idea/
# Images
*.png binary
*.jpg binary
*.gif binary
*.ico binary
*.bmp binary
*.webp binary
*.psd binary

# Fonts
*.ttf binary
*.otf binary
*.woff binary
*.woff2 binary

# Archives
*.zip binary
*.gz binary
*.7z binary
*.tar binary

# Compiled output and signing material
*.dll binary
*.exe binary
*.pdb binary
*.lib binary
*.so binary
*.winmd binary
*.pfx binary
*.snk binary

# Documents
*.pdf binary
4 changes: 2 additions & 2 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<TUnitVersion>1.61.38</TUnitVersion>

<!-- StyleSharp and PerformanceSharp ship from the same repo on a single version line. -->
<SharpAnalyzersVersion>3.38.1</SharpAnalyzersVersion>
<SharpAnalyzersVersion>3.41.14</SharpAnalyzersVersion>
</PropertyGroup>

<PropertyGroup Label="Framework-Aligned Versions">
Expand Down Expand Up @@ -50,7 +50,7 @@
<PackageVersion Include="Roslynator.Analyzers" Version="4.15.0"/>
<PackageVersion Include="SonarAnalyzer.CSharp" Version="10.31.0.145097"/>
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="$(MicrosoftCodeAnalysisAnalyzersVersion)"/>
<PackageVersion Include="PublicApiSharp.Analyzers" Version="1.0.1"/>
<PackageVersion Include="PublicApiSharp.Analyzers" Version="1.0.7"/>
</ItemGroup>

<!-- Roslyn toolchain. Versions are intentionally held at their current bands:
Expand Down
4 changes: 2 additions & 2 deletions src/InterfaceStubGenerator.Shared/Emitter.Constraints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
/// The number of keyword constraints (<c>class</c>, <c>unmanaged</c>, <c>struct</c>, <c>notnull</c>, <c>new()</c>)
/// that can be emitted alongside a type parameter's declared type constraints.
/// </summary>
private const int KeywordConstraintCount = 5;

Check warning on line 14 in src/InterfaceStubGenerator.Shared/Emitter.Constraints.cs

View workflow job for this annotation

GitHub Actions / sonarcloud / sonarcloud

Remove the unused private field 'KeywordConstraintCount'.

/// <summary>Appends the generic type constraint clauses for the given type parameters.</summary>
/// <param name="builder">The buffer accumulating the interface source.</param>
Expand Down Expand Up @@ -74,8 +74,8 @@
|| ((knownConstraints & KnownTypeConstraint.Unmanaged) != 0 && !isOverrideOrExplicitImplementation)
|| (knownConstraints & KnownTypeConstraint.Struct) != 0
|| ((knownConstraints & KnownTypeConstraint.NotNull) != 0 && !isOverrideOrExplicitImplementation)
|| (!isOverrideOrExplicitImplementation && (typeParameter.Constraints.Count > 0 ||
(knownConstraints & KnownTypeConstraint.New) != 0));
|| (!isOverrideOrExplicitImplementation && (typeParameter.Constraints.Count > 0
|| (knownConstraints & KnownTypeConstraint.New) != 0));
}

/// <summary>Appends the comma-separated constraint list for a type parameter.</summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved.
// ReactiveUI and Contributors licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.
using System.Runtime.CompilerServices;

namespace Refit.Generator;

Expand All @@ -11,6 +12,7 @@ internal static partial class Emitter
/// <param name="parameterName">The source parameter name.</param>
/// <param name="uniqueNames">The unique member name builder for the interface scope.</param>
/// <returns>The unique generated field name.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static string GetParameterInfoFieldName(string parameterName, UniqueNameBuilder uniqueNames) =>
uniqueNames.New($"______{parameterName}AttributeProvider");

Expand All @@ -27,6 +29,7 @@ internal static string GetParameterInfoFieldName(string parameterName, UniqueNam
/// <param name="sb">The target builder.</param>
/// <param name="separator">The separator to append before the value.</param>
/// <returns>The same builder for chaining.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static PooledStringBuilder AppendJoining(string value, int i, PooledStringBuilder sb, string separator = ", ") => AppendSeparator(i, sb, separator).Append(value);

/// <summary>Appends a C# attribute construction expression to the builder.</summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved.
// ReactiveUI and Contributors licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.
using System.Runtime.CompilerServices;

namespace Refit.Generator;

Expand Down Expand Up @@ -78,9 +79,7 @@ internal static void AppendObjectPropertyList(
var keyExpression = scope.ParentKeyExpr is { } parentKey
? BuildNestedKeyExpression(property, parentKey, scope.Delimiter, emission, context)
: BuildQueryObjectKeyExpression(property, emission);

var pairCall = context.PreEscapedKeys ? ".AddPreEscapedKey(" : AddQueryPairCall;
var site = new QueryPropertySite(valueLocal, keyExpression, context.PreEncoded, $"{scope.Indentation} ", pairCall);
var site = new QueryPropertySite(valueLocal, keyExpression, context.PreEncoded, $"{scope.Indentation} ", context.PreEscapedKeys ? ".AddPreEscapedKey(" : AddQueryPairCall);

if (property.Nested is { } children)
{
Expand Down Expand Up @@ -604,6 +603,7 @@ internal static void AppendFormattedObjectQueryProperty(
/// <param name="providerField">The cached attribute-provider field name.</param>
/// <param name="emission">The shared emission locals and helper state.</param>
/// <returns>The formatter call expression.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static string BuildUrlFormatterCall(
string valueExpression,
string parameterTypeName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,8 @@ internal static string BuildFormattedValueExpression(
string providerField,
in InlineValueEmission emission)
{
// TreatAsString stringifies the raw value before the formatter runs, mirroring the reflection builder.
var customValue = query.TreatAsString ? valueExpression + ToStringCall : valueExpression;
var customExpression =
EmitFormatUrlParameter(customValue, providerField, $"typeof({parameterTypeName})", emission);
EmitFormatUrlParameter(query.TreatAsString ? valueExpression + ToStringCall : valueExpression, providerField, $"typeof({parameterTypeName})", emission);

var fastExpression = query.TreatAsString
? valueExpression + ToStringCall
Expand Down
2 changes: 2 additions & 0 deletions src/InterfaceStubGenerator.Shared/Emitter.Inline.Query.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for full license information.

using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace Refit.Generator;

Expand Down Expand Up @@ -386,6 +387,7 @@ internal static void AppendScalarAddCall(
/// <returns>The C# string literal, URI-data-escaped unless <paramref name="preEncoded"/>.</returns>
/// <remarks>Escaping here rather than on every request matches the reflection builder's output because
/// <c>Uri.EscapeDataString</c> follows RFC 3986 consistently across the supported target frameworks.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static string BuildPreEscapedQueryKeyLiteral(string key, bool preEncoded) =>
ToCSharpStringLiteral(preEncoded ? key : System.Uri.EscapeDataString(key));

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved.
// ReactiveUI and Contributors licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.
using System.Runtime.CompilerServices;

namespace Refit.Generator;

Expand Down Expand Up @@ -86,20 +87,24 @@ internal static void AppendInlineRequestProperties(

foreach (var property in interfaceModel.Properties)
{
if (property.RequestPropertyKey.Length != 0 && property.HasGetter)
if (property.RequestPropertyKey.Length == 0 || !property.HasGetter)
{
var key = ToCSharpStringLiteral(property.RequestPropertyKey);
AppendRequestProperty(sb, bodyIndent, requestLocal, property.Type, key, BuildPropertyAccessExpression(property));
continue;
}

var key = ToCSharpStringLiteral(property.RequestPropertyKey);
AppendRequestProperty(sb, bodyIndent, requestLocal, property.Type, key, BuildPropertyAccessExpression(property));
}

foreach (var parameter in request.Parameters)
{
if (parameter.Kind == RequestParameterKind.Property)
if (parameter.Kind != RequestParameterKind.Property)
{
var key = ToCSharpStringLiteral(parameter.PropertyKey);
AppendRequestProperty(sb, bodyIndent, requestLocal, parameter.Type, key, $"@{parameter.Name}");
continue;
}

var key = ToCSharpStringLiteral(parameter.PropertyKey);
AppendRequestProperty(sb, bodyIndent, requestLocal, parameter.Type, key, $"@{parameter.Name}");
}
}

Expand All @@ -110,6 +115,7 @@ internal static void AppendInlineRequestProperties(
/// <param name="typeArgument">The request-property value type argument.</param>
/// <param name="keyExpression">The property-key expression.</param>
/// <param name="valueExpression">The property-value expression.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void AppendRequestProperty(
PooledStringBuilder sb,
string bodyIndent,
Expand Down
19 changes: 12 additions & 7 deletions src/InterfaceStubGenerator.Shared/Emitter.Inline.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved.
// ReactiveUI and Contributors licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.
using System.Runtime.CompilerServices;

namespace Refit.Generator;

Expand Down Expand Up @@ -352,6 +353,7 @@ internal static string BuildInlineObservableReturn(
/// <summary>Appends the return statement for a <c>Task&lt;HttpRequestMessage&gt;</c> method that returns the built request.</summary>
/// <param name="builder">The buffer accumulating the interface's generated method source.</param>
/// <param name="requestLocal">The generated request message local name.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void AppendInlineRequestMessageReturn(PooledStringBuilder builder, string requestLocal) =>
builder.Append(Indent(MethodBodyIndentation))
.Append("return global::System.Threading.Tasks.Task.FromResult(").Append(requestLocal).AppendLine(");");
Expand Down Expand Up @@ -416,6 +418,7 @@ internal static void AppendInlineReturn(
/// <param name="bodyIndent">The method-body indentation.</param>
/// <remarks>The adapter receives a deferred send: the request is built eagerly and captured, so the deferred call is
/// single-use (a second invocation would send a disposed request).</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void AppendInlineAdapterReturn(
PooledStringBuilder builder,
in RequestModel request,
Expand Down Expand Up @@ -542,6 +545,7 @@ internal static void AppendInlineParameterHeaders(
/// <param name="nameExpression">The header-name expression.</param>
/// <param name="valueExpression">The header-value expression.</param>
/// <param name="settingsLocal">The generated settings local name, read for the header validation flag.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void AppendSetHeader(
PooledStringBuilder sb,
string bodyIndent,
Expand Down Expand Up @@ -646,13 +650,14 @@ internal static string BuildCancellationTokenExpression(in RequestModel request)
/// <summary>Builds the body serialization enum expression for an inline generated method.</summary>
/// <param name="bodyParameter">The parsed body parameter.</param>
/// <returns>The serialization method expression.</returns>
internal static string BuildBodySerializationMethodExpression(in RequestParameterModel bodyParameter)
{
var serializationMethod = bodyParameter.BodySerializationMethod == "Json"
? "Serialized"
: bodyParameter.BodySerializationMethod;
return $"global::Refit.BodySerializationMethod.{serializationMethod}";
}
internal static string BuildBodySerializationMethodExpression(in RequestParameterModel bodyParameter) =>
$"global::Refit.BodySerializationMethod.{BodySerializationMemberName(bodyParameter.BodySerializationMethod)}";

/// <summary>Maps the obsolete <c>Json</c> member onto the name it was replaced by.</summary>
/// <param name="serializationMethod">The serialization method named on the body parameter.</param>
/// <returns>The member name to emit.</returns>
private static string BodySerializationMemberName(string serializationMethod) =>
serializationMethod == "Json" ? "Serialized" : serializationMethod;

/// <summary>The shared locals and rendered fragments used to emit one unrolled form body.</summary>
/// <param name="BodyExpr">The body value expression.</param>
Expand Down
7 changes: 2 additions & 5 deletions src/InterfaceStubGenerator.Shared/Emitter.SharedCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,12 @@ internal static void EmitPreserveAttribute(
+ "global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | "
+ "global::System.AttributeTargets.Event | global::System.AttributeTargets.Interface | "
+ "global::System.AttributeTargets.Delegate)]";

var generatedCodeAttribute = GeneratedCodeAttribute;
var attributeText = $$"""
{{generatedFileHeader}}
namespace {{model.RefitInternalNamespace}}
{
/// <summary>Identifies generated members that should be preserved by tools that honor this attribute.</summary>
{{generatedCodeAttribute}}
{{GeneratedCodeAttribute}}
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
{{attributeUsageLine}}
Expand Down Expand Up @@ -61,13 +59,12 @@ internal static void EmitGeneratedFactoryModule(
string generatedFileHeader,
Action<string, SourceText> addSource)
{
var generatedCodeAttribute = GeneratedCodeAttribute;
var generatedSource = $$"""
{{generatedFileHeader}}
namespace Refit.Implementation
{
/// <summary>Registers generated Refit factories for interfaces discovered at compile time.</summary>
{{generatedCodeAttribute}}
{{GeneratedCodeAttribute}}
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[global::System.Diagnostics.DebuggerNonUserCode]
[{{model.PreserveAttributeDisplayName}}]
Expand Down
7 changes: 7 additions & 0 deletions src/InterfaceStubGenerator.Shared/Emitter.Testing.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved.
// ReactiveUI and Contributors licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.
using System.Runtime.CompilerServices;

namespace Refit.Generator;

/// <summary>Provides internal accessors for focused generator tests.</summary>
Expand All @@ -10,31 +12,36 @@ internal static partial class Emitter
/// <summary>Escapes text for generated XML documentation comments.</summary>
/// <param name="value">The text to escape.</param>
/// <returns>The escaped XML documentation text.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static string ToXmlDocumentationTextForTesting(string value) => ToXmlDocumentationText(value);

/// <summary>Builds the generated file header for an interface implementation.</summary>
/// <param name="nullability">The nullable context for the generated source.</param>
/// <param name="emitGeneratedCodeMarkers">Whether generated-code markers should be emitted.</param>
/// <returns>The generated file header.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static string BuildGeneratedFileHeaderForTesting(Nullability nullability, bool emitGeneratedCodeMarkers) =>
BuildGeneratedFileHeader(nullability, emitGeneratedCodeMarkers);

/// <summary>Determines whether an interface can be constructed without a reflection request builder.</summary>
/// <param name="model">The interface model being emitted.</param>
/// <returns><see langword="true"/> when all Refit methods use generated request construction.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool CanUseGeneratedSettingsFactoryForTesting(InterfaceModel model) =>
CanUseGeneratedSettingsFactory(model);

/// <summary>Builds the generated <c>typeof(...)</c> argument list for method parameters.</summary>
/// <param name="parameters">The parameter models to emit.</param>
/// <returns>The generated parameter type list.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static string BuildParameterTypeListForTesting(ImmutableEquatableArray<ParameterModel> parameters) =>
BuildParameterTypeList(parameters);

/// <summary>Determines whether a type parameter has constraints that should be emitted.</summary>
/// <param name="typeParameter">The type parameter constraint to inspect.</param>
/// <param name="isOverrideOrExplicitImplementation">True if emitting for an override or explicit implementation.</param>
/// <returns><see langword="true"/> when at least one constraint should be emitted.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool HasConstraintKeywordsForTesting(in TypeConstraint typeParameter, bool isOverrideOrExplicitImplementation) =>
HasConstraintKeywords(typeParameter, isOverrideOrExplicitImplementation);

Expand Down
Loading
Loading