Skip to content

feat(formats): add csproj format handler for .NET project files - #198

Merged
BryanFRD merged 1 commit into
mainfrom
feat/csproj-format
Apr 1, 2026
Merged

BryanFRD merged 1 commit into
mainfrom
feat/csproj-format

Conversation

@BryanFRD

@BryanFRD BryanFRD commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Add a csproj format handler to read and write <Version> tags in .NET project files.

  • New src/formats/csproj.rs implementing the VersionFile trait
  • Targets <Version> elements (capital V), ignoring Version attributes on <PackageReference> elements
  • Only replaces the first <Version> tag (consistent with the xml handler behavior)
  • Covers .csproj (C#), .fsproj (F#), and .vbproj (VB.NET) since they share the same structure
  • Updated FileFormat enum, handler dispatch, schema, and tests

Config usage

{
  "package": [{
    "versionedFiles": [
      { "path": "src/MyApp/MyApp.csproj", "format": "csproj" }
    ]
  }]
}

Closes #30

Copilot AI review requested due to automatic review settings April 1, 2026 07:16
@codecov

codecov Bot commented Apr 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds a new csproj version-file format to FerrFlow so it can read/write .NET project versions from <Version> elements in MSBuild project files.

Changes:

  • Introduces src/formats/csproj.rs implementing the VersionFile trait for <Version>…</Version> handling.
  • Wires the new format into handler dispatch (get_handler) and updates config/schema to allow "csproj".
  • Extends existing format dispatch tests to include the new enum variant.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
src/formats/mod.rs Exposes the new module and dispatches FileFormat::Csproj to the new handler; expands handler dispatch tests.
src/formats/csproj.rs Implements csproj read/write logic and adds unit tests for basic scenarios.
src/config.rs Adds Csproj to the FileFormat enum for config serialization/deserialization.
schema/ferrflow.json Extends the JSON Schema format enum with "csproj".

Comment thread src/formats/csproj.rs
Comment on lines +105 to +109
static VERSION_RE: OnceLock<Regex> = OnceLock::new();

fn version_re() -> &'static Regex {
VERSION_RE.get_or_init(|| Regex::new(r"<Version>([^<]+)</Version>").unwrap())
}

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

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

version_re matches the first <Version>…</Version> anywhere in the file and only when the opening tag has no attributes. This can (a) pick up <Version> elements outside <PropertyGroup> (e.g., a <PackageReference><Version>…</Version></PackageReference> pattern) and (b) fail to match common MSBuild patterns like <Version Condition="…">…</Version>. Consider parsing the XML and selecting the first <PropertyGroup>/<Version> element, or at least tighten the pattern to allow attributes while constraining the match to within a <PropertyGroup> block to avoid updating the wrong version.

Copilot uses AI. Check for mistakes.
Comment thread src/formats/csproj.rs
Comment on lines +126 to +130
let mut count = 0;
let new_content = version_re().replace(&content, |_: &regex::Captures| {
count += 1;
format!("<Version>{version}</Version>")
});

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

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

write_version uses Regex::replace, which replaces all <Version> tags, but the PR description (and the tests in xml.rs) indicate only the first tag should be updated. Update the implementation to replace only the first match (e.g., replacen with a limit of 1 or manual slicing) and add an assertion/test that a second <Version> element remains unchanged.

Copilot uses AI. Check for mistakes.
Comment thread src/config.rs
Comment on lines 219 to 223
pub enum FileFormat {
Csproj,
#[serde(rename = "gomod")]
GoMod,
Gradle,

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

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

A new FileFormat::Csproj variant was added, but the CLI format prompt/parsing logic in this file is still hard-coded to [toml/json/xml/gradle/gomod/txt] and parse_file_format() doesn't map "csproj" (it currently falls back to Toml). This makes it impossible to select csproj via the interactive config flow and can silently misinterpret user input; update ALLOWED_FORMATS, the prompt text/error message, default_version_file(), and parse_file_format() to include csproj (and extend the existing file_format_serde_all_variants test to cover it).

Copilot uses AI. Check for mistakes.
@BryanFRD
BryanFRD merged commit 6b3eb4f into main Apr 1, 2026
15 checks passed
@BryanFRD
BryanFRD deleted the feat/csproj-format branch April 1, 2026 07:20

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Benchmark

Details
Benchmark suite Current: c04728f Previous: e7359fc Ratio
commit_parsing/100 17200 ns/iter (± 109) 18553 ns/iter (± 63) 0.93
commit_parsing/1000 175046 ns/iter (± 918) 187423 ns/iter (± 1447) 0.93
commit_parsing/10000 1809136 ns/iter (± 17254) 1919342 ns/iter (± 49311) 0.94
changelog/build_50 7876 ns/iter (± 34) 7534 ns/iter (± 678) 1.05
changelog/build_500 69300 ns/iter (± 256) 64491 ns/iter (± 629) 1.07
version_files/toml_read 8431 ns/iter (± 46) 9555 ns/iter (± 135) 0.88
version_files/toml_write 137633 ns/iter (± 12740) 100378 ns/iter (± 13320) 1.37
version_files/json_read 5334 ns/iter (± 36) 6345 ns/iter (± 68) 0.84
version_files/json_write 136025 ns/iter (± 21068) 93901 ns/iter (± 17169) 1.45
version_files/xml_read 5107 ns/iter (± 22) 5969 ns/iter (± 131) 0.86
version_files/xml_write 132919 ns/iter (± 9711) 93639 ns/iter (± 4765) 1.42
version_files/gradle_read 4986 ns/iter (± 25) 5849 ns/iter (± 27) 0.85
version_files/gradle_write 133873 ns/iter (± 9248) 94488 ns/iter (± 7716) 1.42
config_loading/single 10365 ns/iter (± 71) 11709 ns/iter (± 271) 0.89
config_loading/mono_10 15500 ns/iter (± 283) 16922 ns/iter (± 127) 0.92
config_loading/mono_50 40767 ns/iter (± 405) 41721 ns/iter (± 821) 0.98

This comment was automatically generated by workflow using github-action-benchmark.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.

Benchmark suite Current: c04728f Previous: e7359fc Ratio
version_files/toml_write 137633 ns/iter (± 12740) 100378 ns/iter (± 13320) 1.37
version_files/json_write 136025 ns/iter (± 21068) 93901 ns/iter (± 17169) 1.45
version_files/xml_write 132919 ns/iter (± 9711) 93639 ns/iter (± 4765) 1.42
version_files/gradle_write 133873 ns/iter (± 9248) 94488 ns/iter (± 7716) 1.42

This comment was automatically generated by workflow using github-action-benchmark.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support .NET project files (.csproj / .fsproj)

2 participants