Skip to content
Closed
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
36 changes: 27 additions & 9 deletions src/Refitter.SourceGenerator/RefitterSourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,19 +145,37 @@ private static GeneratedCode GenerateCode(
var refit = generator.Generate();

cancellationToken.ThrowIfCancellationRequested();
var filename = settings.OutputFilename ?? Path.GetFileName(file.Path).Replace(".refitter", ".g.cs");
if (filename == ".g.cs")

string hintName;
if (!string.IsNullOrEmpty(settings.OutputFilename))
{
filename = "Refitter.g.cs";
// Honor the user-specified output filename as the hint name
hintName = settings.OutputFilename!;
if (!hintName.EndsWith(".cs", StringComparison.OrdinalIgnoreCase))
{
hintName += ".g.cs";
}
}

// Create unique hint name based on the .refitter file path to avoid collisions
var hintName = Path.GetFileNameWithoutExtension(file.Path);
if (string.IsNullOrEmpty(hintName) || hintName == ".")
else
{
hintName = "Refitter";
// Disambiguate using the full directory path to avoid collisions when multiple
// .refitter files share the same filename in different directories (e.g. src/ApiA/petstore.refitter
// and src/ApiB/petstore.refitter would both produce "petstore.g.cs" without this).
var fileNameWithoutExt = Path.GetFileNameWithoutExtension(file.Path);
if (string.IsNullOrEmpty(fileNameWithoutExt) || fileNameWithoutExt == ".")
{
fileNameWithoutExt = "Refitter";
}

var dir = Path.GetDirectoryName(file.Path) ?? string.Empty;
var safeDir = dir
.Replace(Path.DirectorySeparatorChar, '_')
.Replace(Path.AltDirectorySeparatorChar, '_')
.Replace(':', '_');
hintName = string.IsNullOrEmpty(safeDir)
? $"{fileNameWithoutExt}.g.cs"
: $"{safeDir}_{fileNameWithoutExt}.g.cs";
}
hintName = hintName + ".g.cs";

return new GeneratedCode(diagnostics, refit, hintName);
}
Expand Down
46 changes: 46 additions & 0 deletions src/Refitter.Tests/SourceGeneratorFileIOTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,50 @@ public async Task Test_SourceGenerator_OutputFilename_NotCreatedOnDisk()

generatedCode.Should().NotBeNullOrWhiteSpace();
}

[Test]
public void Test_HintName_IsUnique_For_SameFilename_InDifferentDirectories()
{
// Regression test for: hint-name collisions when two .refitter files share a filename
// Two .refitter files with the same filename in different directories must produce different hint names.
static string ComputeHintName(string filePath)
{
var fileNameWithoutExt = Path.GetFileNameWithoutExtension(filePath);
if (string.IsNullOrEmpty(fileNameWithoutExt) || fileNameWithoutExt == ".")
fileNameWithoutExt = "Refitter";

var dir = Path.GetDirectoryName(filePath) ?? string.Empty;
var safeDir = dir
.Replace(Path.DirectorySeparatorChar, '_')
.Replace(Path.AltDirectorySeparatorChar, '_')
.Replace(':', '_');

return string.IsNullOrEmpty(safeDir)
? $"{fileNameWithoutExt}.g.cs"
: $"{safeDir}_{fileNameWithoutExt}.g.cs";
}

var pathA = Path.Combine("src", "ApiA", "petstore.refitter");
var pathB = Path.Combine("src", "ApiB", "petstore.refitter");

var hintA = ComputeHintName(pathA);
var hintB = ComputeHintName(pathB);

hintA.Should().NotBe(hintB,
"two .refitter files with the same name in different directories must produce different hint names");
}

[Test]
public void Test_HintName_UsesOutputFilename_WhenSpecified()
{
// When settings.OutputFilename is set, it should be used as the hint name directly.
var outputFilename = "MyCustomOutput.g.cs";

var hintName = outputFilename.EndsWith(".cs", StringComparison.OrdinalIgnoreCase)
? outputFilename
: outputFilename + ".g.cs";

hintName.Should().Be("MyCustomOutput.g.cs",
"OutputFilename should be used as-is when it already ends with .cs");
}
}
Loading