Skip to content

Refactor parameter extraction into a ParameterListBuilder module#1180

Merged
christianhelle merged 5 commits into
mainfrom
refactor-parameter-extraction
Jul 16, 2026
Merged

Refactor parameter extraction into a ParameterListBuilder module#1180
christianhelle merged 5 commits into
mainfrom
refactor-parameter-extraction

Conversation

@christianhelle

@christianhelle christianhelle commented Jun 30, 2026

Copy link
Copy Markdown
Owner

Summary

Deepens the parameter-extraction subsystem in Refitter.Core into a single deep module, ParameterListBuilder, replacing a cluster of shallow modules and a 322-line static god-helper.

Previously, building a generated Refit method's parameter list spanned MethodSignatureGeneratorParameterAggregator (IParameterExtractor) → five IParameterTypeExtractor adapters → the static ParameterShared, with the query path threading state through mutable properties and an out parameter. Each seam had exactly one implementation and was never substituted (the tests constructed the concretes directly), so the indirection added no leverage.

What changed

  • New deep module: ParameterListBuilder(settings).Build(operationModel, operation, dynamicQuerystringParameterType) → ParameterList. Settings are injected once via the constructor and the result is a record (Parameters, DynamicQuerystringCode) — no more out parameter or mutable QueryParameterExtractor state.
  • Removed the single-implementation IParameterExtractor and IParameterTypeExtractor interfaces and ParameterAggregator. The five per-kind extractors remain as internal collaborators.
  • Dissolved ParameterShared (322 lines) into four cohesive internal helpers: ParameterNaming, ParameterDefaultValueFormatter, ParameterAttributeFormatter, ParameterTypeResolver. AppendXmlDocComment moved next to its only caller.
  • Generated output is unchanged — verified by the existing black-box generation suites.

Tests

  • Existing black-box coverage (ParameterExtractorDeepCoverageTests, ParameterExtractorEdgeCaseTests) unchanged.
  • The 48 white-box cases were retargeted to the new helpers.
  • Added ParameterListBuilderTests covering the dynamic-querystring result, optional-parameter reordering, and the request-options / cancellation-token append through the new Build interface.
  • Full fast suite: 2133 passing, build clean (0 warnings), dotnet format clean.

Notes

  • Adds a root CONTEXT.md seeding the Parameter list domain term.
  • First candidate from an architecture review focused on turning shallow modules into deep ones; a follow-up fix: commit corrects a MethodSignatureGenerator test that had never actually exercised the dynamic-querystring path.

Summary by CodeRabbit

  • New Features

    • Improved generated API client method signatures, including better handling of query, body, header, and form parameters.
    • Added support for dynamic querystring parameters and cleaner optional parameter ordering.
    • Enhanced generated parameter naming, aliases, default values, and type mapping for more consistent output.
  • Documentation

    • Added introductory project documentation and clearer guidance for generated method parameter naming.
  • Tests

    • Expanded automated coverage for parameter generation, dynamic querystrings, and optional parameter handling.

@christianhelle christianhelle added enhancement New feature, bug fix, or request .NET Pull requests that contain changes to .NET code labels Jun 30, 2026
@christianhelle christianhelle self-assigned this Jun 30, 2026
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR refactors parameter extraction in Refitter.Core, removing IParameterExtractor, IParameterTypeExtractor, ParameterAggregator, and ParameterShared, replacing them with static helpers (ParameterNaming, ParameterAttributeFormatter, ParameterDefaultValueFormatter, ParameterTypeResolver), a ParameterList record, and ParameterListBuilder. Extractors, generators, and tests are updated accordingly; CONTEXT.md documentation is added.

Changes

Parameter extraction refactor

Layer / File(s) Summary
New formatter/resolver helpers
src/Refitter.Core/ParameterExtraction/ParameterNaming.cs, ParameterAttributeFormatter.cs, ParameterDefaultValueFormatter.cs, ParameterTypeResolver.cs, ParameterList.cs
New static classes provide identifier naming/escaping, attribute string building, default-value/numeric formatting, and JSON-schema-to-C# type resolution; ParameterList record holds generated parameter strings and dynamic querystring code.
ParameterListBuilder and generator wiring
ParameterListBuilder.cs, Generation/MethodSignatureGenerator.cs, Generation/InterfaceGenerator.cs, ParameterAggregator.cs (removed), IParameterExtractor.cs (removed)
ParameterListBuilder.Build orchestrates route/query/body/header/form extraction, reordering, and request-options/cancellation-token appending; MethodSignatureGenerator and InterfaceGenerator now use it directly instead of the removed IParameterExtractor/ParameterAggregator.
Extractors updated to new helpers
RouteParameterExtractor.cs, QueryParameterExtractor.cs, BodyParameterExtractor.cs, HeaderParameterExtractor.cs, FormParameterExtractor.cs, DynamicQuerystringParameterBuilder.cs, OptionalParameterReorderer.cs, IParameterTypeExtractor.cs (removed), ParameterShared.cs (removed)
Extractors call the new naming/attribute/type-resolution helpers instead of ParameterShared/IParameterTypeExtractor; QueryParameterExtractor.Extract now returns a tuple with dynamic querystring code instead of mutating instance state; classes marked sealed.
Tests and docs
src/Refitter.Tests/Parameters/ParameterExtractorPrivateCoverageTests.cs, ParameterListBuilderTests.cs (new), MethodSignatureGeneratorTests.cs, CONTEXT.md
Existing tests retargeted to new helper classes; new ParameterListBuilderTests cover dynamic querystring, cancellation tokens, request options, and optional parameter ordering; CONTEXT.md adds Refitter overview and language documentation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

A rabbit hopped through ParameterShared,
Found tangled helpers, frayed and bared.
Now Naming, Resolver, Formatter align,
A tidy Builder builds each line. 🐇
Hop, refactor, ship — all fine!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main refactor: parameter extraction moved into ParameterListBuilder.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor-parameter-extraction

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sonarqubecloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (8)
src/Refitter.Core/ParameterExtraction/ParameterDefaultValueFormatter.cs (2)

29-30: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid var per repo convention.

As per coding guidelines, "Discourage use of var keyword in C# (csharp_style_var_* = false:silent per .editorconfig)".

Also applies to: 50-50

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Refitter.Core/ParameterExtraction/ParameterDefaultValueFormatter.cs`
around lines 29 - 30, Replace the inferred local declaration in
ParameterDefaultValueFormatter with an explicit string type to match the repo’s
C# style convention. Update the numericString assignment in the default value
formatting logic to use string rather than var, keeping the existing
IFormattable/CultureInfo.InvariantCulture behavior unchanged.

Source: Coding guidelines


11-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the repeated "default" literal into a constant.

SonarCloud flags 6 occurrences of the "default" literal in this file; consolidating reduces typo risk if behavior ever needs to change.

♻️ Suggested fix
 internal static class ParameterDefaultValueFormatter
 {
+    private const string DefaultLiteral = "default";
+
     public static bool IsNumericType(string type)

then replace each "default" return with DefaultLiteral.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Refitter.Core/ParameterExtraction/ParameterDefaultValueFormatter.cs`
around lines 11 - 79, The ParameterDefaultValueFormatter methods repeat the same
"default" literal in several return paths, so extract it into a shared constant
and use that everywhere instead. Add a single constant near the existing helpers
in ParameterDefaultValueFormatter, then update FormatNumericValue,
FormatDefaultValue, and GetDefaultValueForParameter to return that constant
instead of the inline string.

Source: Linters/SAST tools

src/Refitter.Core/ParameterExtraction/ParameterAttributeFormatter.cs (2)

34-35: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid var per repo convention.

As per coding guidelines, "Discourage use of var keyword in C# (csharp_style_var_* = false:silent per .editorconfig)".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Refitter.Core/ParameterExtraction/ParameterAttributeFormatter.cs` around
lines 34 - 35, The ParameterAttributeFormatter logic uses var against the repo’s
C# style convention. Update the local declarations in the formatter path,
especially the values assigned in ParameterAttributeFormatter, to use explicit
types instead of var, keeping the existing behavior unchanged while matching the
editorconfig rule.

Source: Coding guidelines


10-18: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider consolidating the two GetAliasAsAttribute overloads.

Both overloads implement the same comparison logic; the first can delegate to the second.

♻️ Suggested consolidation
 public static string GetAliasAsAttribute(CSharpParameterModel parameterModel) =>
-    string.Equals(parameterModel.Name, parameterModel.VariableName)
-        ? string.Empty
-        : $"AliasAs(\"{ParameterNaming.EscapeString(parameterModel.Name)}\")";
+    GetAliasAsAttribute(parameterModel.Name, parameterModel.VariableName);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Refitter.Core/ParameterExtraction/ParameterAttributeFormatter.cs` around
lines 10 - 18, The two GetAliasAsAttribute overloads in
ParameterAttributeFormatter duplicate the same comparison and formatting logic.
Update the CSharpParameterModel overload to delegate to the string-based
GetAliasAsAttribute(originalName, variableName) overload using
parameterModel.Name and parameterModel.VariableName, and keep the Ordinal
comparison and AliasAs formatting in only one place.
src/Refitter.Core/ParameterExtraction/ParameterTypeResolver.cs (2)

42-45: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Use the char overload of EndsWith.

EndsWith(string) performs a culture-aware comparison by default; for a single-character check, EndsWith(char) is faster and avoids ambiguity.

⚡ Suggested fix
-        if (settings.OptionalParameters &&
-            !type.EndsWith("?") &&
+        if (settings.OptionalParameters &&
+            !type.EndsWith('?') &&
             (parameterModel.IsNullable || parameterModel.IsOptional || !parameterModel.IsRequired))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Refitter.Core/ParameterExtraction/ParameterTypeResolver.cs` around lines
42 - 45, The nullable-type suffix check in ParameterTypeResolver should use the
char-based EndsWith overload instead of the string overload. Update the
conditional in the parameter type formatting logic so the type check uses the
single-character '?' overload alongside the existing OptionalParameters and
parameterModel nullability/optionality conditions, keeping the same behavior
while avoiding the culture-aware string comparison.

Source: Linters/SAST tools


38-38: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid var per repo convention.

As per coding guidelines, "Discourage use of var keyword in C# (csharp_style_var_* = false:silent per .editorconfig)".

Also applies to: 65-65, 91-91, 99-99

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Refitter.Core/ParameterExtraction/ParameterTypeResolver.cs` at line 38,
The ParameterTypeResolver code uses var in multiple spots against the repo’s C#
style convention. Replace the inferred declarations in ParameterTypeResolver
(including the TrimImportedNamespaces call result and the other flagged local
variables in the same class) with explicit types so the code matches the
editorconfig guidance.

Source: Coding guidelines

src/Refitter.Core/ParameterExtraction/ParameterNaming.cs (1)

18-18: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid var per repo convention.

csharp_style_var_* = false:silent in .editorconfig discourages var; use explicit types (StringBuilder, string) here.

As per coding guidelines, "Discourage use of var keyword in C# (csharp_style_var_* = false:silent per .editorconfig)".

Also applies to: 64-64

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Refitter.Core/ParameterExtraction/ParameterNaming.cs` at line 18, The
issue is use of `var` in `ParameterNaming`, which conflicts with the repo’s
explicit-type convention. Update the local declarations in `ParameterNaming` to
use concrete types instead of inferred types, including the `StringBuilder`
initialization and the other `var` usage mentioned in the comment, so the
implementation matches the `.editorconfig` rule and the surrounding C# style.

Source: Coding guidelines

src/Refitter.Tests/Parameters/ParameterListBuilderTests.cs (1)

129-140: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Reordering test doesn't actually exercise the reorder logic.

page (required) is declared before limit (optional) in RequiredAndOptionalQuerySpec, so the natural extraction order already places the optional parameter last. The assertion would pass even if OptionalParameterReorderer.Reorder were a no-op, so this test doesn't meaningfully verify reordering behavior. Declare limit before page in the spec so the reorderer actually has to move something.

♻️ Proposed fix
     private const string RequiredAndOptionalQuerySpec = """
         openapi: 3.0.0
         info:
           title: Test
           version: "1.0"
         paths:
           /test:
             get:
               operationId: getTest
               parameters:
-                - name: page
-                  in: query
-                  required: true
-                  schema:
-                    type: integer
                 - name: limit
                   in: query
                   schema:
                     type: integer
+                - name: page
+                  in: query
+                  required: true
+                  schema:
+                    type: integer
               responses:
                 '200':
                   description: Success
         """;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Refitter.Tests/Parameters/ParameterListBuilderTests.cs` around lines 129
- 140, The test in ParameterListBuilderTests for
Build_Orders_Optional_Parameters_After_Required currently doesn’t verify
OptionalParameterReorderer.Reorder because RequiredAndOptionalQuerySpec already
yields the optional parameter last. Update the spec used by SetupAsync so the
optional parameter (limit) is declared before the required one (page), then keep
the assertion on ParameterListBuilder.Build to confirm the reorder logic
actually moves optional parameters after required ones.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/Refitter.Core/ParameterExtraction/ParameterAttributeFormatter.cs`:
- Around line 34-35: The ParameterAttributeFormatter logic uses var against the
repo’s C# style convention. Update the local declarations in the formatter path,
especially the values assigned in ParameterAttributeFormatter, to use explicit
types instead of var, keeping the existing behavior unchanged while matching the
editorconfig rule.
- Around line 10-18: The two GetAliasAsAttribute overloads in
ParameterAttributeFormatter duplicate the same comparison and formatting logic.
Update the CSharpParameterModel overload to delegate to the string-based
GetAliasAsAttribute(originalName, variableName) overload using
parameterModel.Name and parameterModel.VariableName, and keep the Ordinal
comparison and AliasAs formatting in only one place.

In `@src/Refitter.Core/ParameterExtraction/ParameterDefaultValueFormatter.cs`:
- Around line 29-30: Replace the inferred local declaration in
ParameterDefaultValueFormatter with an explicit string type to match the repo’s
C# style convention. Update the numericString assignment in the default value
formatting logic to use string rather than var, keeping the existing
IFormattable/CultureInfo.InvariantCulture behavior unchanged.
- Around line 11-79: The ParameterDefaultValueFormatter methods repeat the same
"default" literal in several return paths, so extract it into a shared constant
and use that everywhere instead. Add a single constant near the existing helpers
in ParameterDefaultValueFormatter, then update FormatNumericValue,
FormatDefaultValue, and GetDefaultValueForParameter to return that constant
instead of the inline string.

In `@src/Refitter.Core/ParameterExtraction/ParameterNaming.cs`:
- Line 18: The issue is use of `var` in `ParameterNaming`, which conflicts with
the repo’s explicit-type convention. Update the local declarations in
`ParameterNaming` to use concrete types instead of inferred types, including the
`StringBuilder` initialization and the other `var` usage mentioned in the
comment, so the implementation matches the `.editorconfig` rule and the
surrounding C# style.

In `@src/Refitter.Core/ParameterExtraction/ParameterTypeResolver.cs`:
- Around line 42-45: The nullable-type suffix check in ParameterTypeResolver
should use the char-based EndsWith overload instead of the string overload.
Update the conditional in the parameter type formatting logic so the type check
uses the single-character '?' overload alongside the existing OptionalParameters
and parameterModel nullability/optionality conditions, keeping the same behavior
while avoiding the culture-aware string comparison.
- Line 38: The ParameterTypeResolver code uses var in multiple spots against the
repo’s C# style convention. Replace the inferred declarations in
ParameterTypeResolver (including the TrimImportedNamespaces call result and the
other flagged local variables in the same class) with explicit types so the code
matches the editorconfig guidance.

In `@src/Refitter.Tests/Parameters/ParameterListBuilderTests.cs`:
- Around line 129-140: The test in ParameterListBuilderTests for
Build_Orders_Optional_Parameters_After_Required currently doesn’t verify
OptionalParameterReorderer.Reorder because RequiredAndOptionalQuerySpec already
yields the optional parameter last. Update the spec used by SetupAsync so the
optional parameter (limit) is declared before the required one (page), then keep
the assertion on ParameterListBuilder.Build to confirm the reorder logic
actually moves optional parameters after required ones.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5bcab9ef-4aac-4876-b2cb-02f25e9b99b7

📥 Commits

Reviewing files that changed from the base of the PR and between 6e35a03 and 47406f4.

📒 Files selected for processing (23)
  • CONTEXT.md
  • src/Refitter.Core/Generation/InterfaceGenerator.cs
  • src/Refitter.Core/Generation/MethodSignatureGenerator.cs
  • src/Refitter.Core/ParameterExtraction/BodyParameterExtractor.cs
  • src/Refitter.Core/ParameterExtraction/DynamicQuerystringParameterBuilder.cs
  • src/Refitter.Core/ParameterExtraction/FormParameterExtractor.cs
  • src/Refitter.Core/ParameterExtraction/HeaderParameterExtractor.cs
  • src/Refitter.Core/ParameterExtraction/IParameterExtractor.cs
  • src/Refitter.Core/ParameterExtraction/IParameterTypeExtractor.cs
  • src/Refitter.Core/ParameterExtraction/OptionalParameterReorderer.cs
  • src/Refitter.Core/ParameterExtraction/ParameterAggregator.cs
  • src/Refitter.Core/ParameterExtraction/ParameterAttributeFormatter.cs
  • src/Refitter.Core/ParameterExtraction/ParameterDefaultValueFormatter.cs
  • src/Refitter.Core/ParameterExtraction/ParameterList.cs
  • src/Refitter.Core/ParameterExtraction/ParameterListBuilder.cs
  • src/Refitter.Core/ParameterExtraction/ParameterNaming.cs
  • src/Refitter.Core/ParameterExtraction/ParameterShared.cs
  • src/Refitter.Core/ParameterExtraction/ParameterTypeResolver.cs
  • src/Refitter.Core/ParameterExtraction/QueryParameterExtractor.cs
  • src/Refitter.Core/ParameterExtraction/RouteParameterExtractor.cs
  • src/Refitter.Tests/MethodSignatureGeneratorTests.cs
  • src/Refitter.Tests/Parameters/ParameterExtractorPrivateCoverageTests.cs
  • src/Refitter.Tests/Parameters/ParameterListBuilderTests.cs
💤 Files with no reviewable changes (4)
  • src/Refitter.Core/ParameterExtraction/IParameterExtractor.cs
  • src/Refitter.Core/ParameterExtraction/ParameterAggregator.cs
  • src/Refitter.Core/ParameterExtraction/ParameterShared.cs
  • src/Refitter.Core/ParameterExtraction/IParameterTypeExtractor.cs

@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.45148% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.85%. Comparing base (6162f4c) to head (47406f4).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...ameterExtraction/ParameterDefaultValueFormatter.cs 53.84% 0 Missing and 18 partials ⚠️
...ParameterExtraction/ParameterAttributeFormatter.cs 90.62% 3 Missing ⚠️
....Core/ParameterExtraction/ParameterTypeResolver.cs 95.74% 0 Missing and 2 partials ⚠️
...erExtraction/DynamicQuerystringParameterBuilder.cs 96.00% 0 Missing and 1 partial ⚠️
...ore/ParameterExtraction/QueryParameterExtractor.cs 90.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1180      +/-   ##
==========================================
+ Coverage   94.83%   94.85%   +0.01%     
==========================================
  Files          81       85       +4     
  Lines        3585     3578       -7     
==========================================
- Hits         3400     3394       -6     
  Misses         76       76              
+ Partials      109      108       -1     
Flag Coverage Δ
unittests 94.85% <89.45%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@christianhelle christianhelle changed the title refactor: deepen parameter extraction into a ParameterListBuilder module Refactor parameter extraction into a ParameterListBuilder module Jul 1, 2026
@christianhelle
christianhelle merged commit 01d45b2 into main Jul 16, 2026
13 of 14 checks passed
@christianhelle
christianhelle deleted the refactor-parameter-extraction branch July 16, 2026 20:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature, bug fix, or request .NET Pull requests that contain changes to .NET code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant