Skip to content

Commit 37c11c0

Browse files
9swampyasbjornu
authored andcommitted
Sanitize Participant
1 parent f8a95ab commit 37c11c0

File tree

4 files changed

+182
-3
lines changed

4 files changed

+182
-3
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using GitVersion.Testing.Helpers;
2+
3+
namespace GitVersion.Core.Tests.Helpers;
4+
5+
[TestFixture]
6+
public class ParticipantSanitizerTests
7+
{
8+
[TestCase("feature/1234-is-id-with-something-kebab", "feature_1234_IsIdWithSomethingKebab")]
9+
[TestCase("feature/1234-IsSomethingPascalCase", "feature_1234_IsSomethingPascalCase")]
10+
[TestCase("feature/Caps-lower-something-kebab", "feature_CapsLowerSomethingKebab")]
11+
[TestCase("feature/Caps-lower-is-kebab", "feature_CapsLowerIsKebab")]
12+
[TestCase("kebab-folder/1234-is-id-with-something-kebab", "KebabFolder_1234_IsIdWithSomethingKebab")]
13+
[TestCase("kebab-folder/1234-IsSomethingPascalCase", "KebabFolder_1234_IsSomethingPascalCase")]
14+
[TestCase("kebab-folder/Caps-lower-something-kebab", "KebabFolder_CapsLowerSomethingKebab")]
15+
[TestCase("kebab-folder/Caps-lower-is-kebab", "KebabFolder_CapsLowerIsKebab")]
16+
[TestCase("PascalCaseFolder/1234-is-id-with-something-kebab", "PascalCaseFolder_1234_IsIdWithSomethingKebab")]
17+
[TestCase("PascalCaseFolder/1234-IsSomethingPascalCase", "PascalCaseFolder_1234_IsSomethingPascalCase")]
18+
[TestCase("PascalCaseFolder/Caps-lower-something-kebab", "PascalCaseFolder_CapsLowerSomethingKebab")]
19+
[TestCase("PascalCaseFolder/Caps-lower-is-kebab", "PascalCaseFolder_CapsLowerIsKebab")]
20+
[TestCase("1234-is-id-with-something-kebab", "1234_IsIdWithSomethingKebab")]
21+
[TestCase("1234-IsSomethingPascalCase", "1234_IsSomethingPascalCase")]
22+
[TestCase("Caps-lower-something-kebab", "CapsLowerSomethingKebab")]
23+
[TestCase("Caps-lower-is-kebab", "CapsLowerIsKebab")]
24+
[TestCase("feature/all-lower-is-kebab", "feature_AllLowerIsKebab")]
25+
[TestCase("feature/24321-Upperjustoneword", "feature_24321_Upperjustoneword")]
26+
[TestCase("feature/justoneword", "feature_Justoneword")]
27+
[TestCase("feature/PascalCase", "feature_PascalCase")]
28+
[TestCase("feature/PascalCase-with-kebab", "feature_PascalCaseWithKebab")]
29+
[TestCase("feature/12414", "feature_12414")]
30+
[TestCase("feature/12414/12342-FeatureStoryTaskWithShortDescription", "feature_12414_12342_FeatureStoryTaskWithShortDescription")]
31+
[TestCase("feature/12414/12342-Short-description", "feature_12414_12342_ShortDescription")]
32+
[TestCase("feature/12414/12342-short-description", "feature_12414_12342_ShortDescription")]
33+
[TestCase("feature/12414/12342-Short-Description", "feature_12414_12342_ShortDescription")]
34+
[TestCase("release/1.0.0", "release_1.0.0")]
35+
[TestCase("releases", "releases")]
36+
[TestCase("feature", "feature")]
37+
[TestCase("feature/tfs1-Short-description", "feature_tfs1_ShortDescription")]
38+
[TestCase("feature/f2-Short-description", "feature_f2_ShortDescription")]
39+
[TestCase("feature/bug1", "feature_bug1")]
40+
[TestCase("f2", "f2")]
41+
[TestCase("feature/f2", "feature_f2")]
42+
[TestCase("feature/story2", "feature_story2")]
43+
[TestCase("master", "master")]
44+
[TestCase("develop", "develop")]
45+
[TestCase("main", "main")]
46+
public void SanitizeValidParticipant_ShouldReturnExpectedResult(string input, string expected)
47+
{
48+
var actual = ParticipantSanitizer.SanitizeParticipant(input);
49+
actual.ShouldBe(expected);
50+
}
51+
52+
[TestCase("")]
53+
[TestCase(" ")]
54+
public void SanitizeEmptyOrWhitespaceParticipant_ShouldThrow(string value)
55+
{
56+
var exception = Should.Throw<ArgumentException>(() => ParticipantSanitizer.SanitizeParticipant(value));
57+
exception.Message.ShouldBe("The value cannot be an empty string or composed entirely of whitespace. (Parameter 'participant')");
58+
}
59+
60+
[TestCase("feature/")]
61+
[TestCase("/")]
62+
public void SanitizeInvalidParticipant_ShouldThrow(string value)
63+
{
64+
var exception = Should.Throw<ArgumentException>(() => ParticipantSanitizer.SanitizeParticipant(value));
65+
exception.Message.ShouldBe("The value cannot end with a folder separator ('/'). (Parameter 'participant')");
66+
}
67+
}

src/GitVersion.Testing/Fixtures/SequenceDiagram.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using GitVersion.Testing.Helpers;
12
using GitVersion.Testing.Internal;
23

34
namespace GitVersion.Testing;
@@ -39,11 +40,12 @@ public SequenceDiagram()
3940
/// </summary>
4041
public void Participant(string participant, string? @as = null)
4142
{
42-
this.participants.Add(participant, @as ?? participant);
43-
if (@as == null)
43+
var cleanParticipant = ParticipantSanitizer.SanitizeParticipant(@as ?? participant);
44+
this.participants.Add(participant, cleanParticipant);
45+
if (participant == cleanParticipant)
4446
this.diagramBuilder.AppendLineFormat("participant {0}", participant);
4547
else
46-
this.diagramBuilder.AppendLineFormat("participant \"{0}\" as {1}", participant, @as);
48+
this.diagramBuilder.AppendLineFormat("participant \"{0}\" as {1}", participant, cleanParticipant);
4749
}
4850

4951
/// <summary>
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
namespace GitVersion.Testing.Helpers;
2+
3+
public static class ParticipantSanitizer
4+
{
5+
/// <summary>
6+
/// Converts a participant identifier to a standardized format.
7+
/// </summary>
8+
/// <param name="participant">The participant identifier to convert. This value cannot be null, empty, or consist only of whitespace.</param>
9+
/// <returns>A string representing the converted participant identifier. If the input contains a folder separator ('/'), the
10+
/// portion after the separator is processed recursively. If the input is in kebab-case, it is converted to
11+
/// PascalCase. Otherwise, the input is returned unchanged.</returns>
12+
public static string SanitizeParticipant(string participant)
13+
{
14+
ArgumentException.ThrowIfNullOrWhiteSpace(participant);
15+
if (participant.EndsWith('/'))
16+
{
17+
throw new ArgumentException("The value cannot end with a folder separator ('/').", nameof(participant));
18+
}
19+
20+
var folderIndex = participant.IndexOf('/');
21+
if (folderIndex > -1)
22+
{
23+
return SplitOnFolderAndRecurseSuffix(folderIndex, participant);
24+
}
25+
26+
if (IsKebabCase(participant))
27+
{
28+
return EnsurePascalCase(participant);
29+
}
30+
31+
return participant;
32+
}
33+
34+
private static string SplitOnFolderAndRecurseSuffix(int folderIndex, string input)
35+
{
36+
var folder = input[..folderIndex];
37+
if (IsKebabCase(folder))
38+
{
39+
var parts = folder.Split('-');
40+
if (parts[0].IsAnId())
41+
{
42+
folder = parts[0] + EnsurePascalCase(string.Join("-", parts.Skip(1)));
43+
}
44+
else
45+
{
46+
folder = EnsurePascalCase(folder);
47+
}
48+
}
49+
50+
var suffix = input[(folderIndex + 1)..];
51+
var nextFolderIndex = suffix.IndexOf('/');
52+
if (ThereIsAnotherFolder(nextFolderIndex))
53+
{
54+
return $"{folder}_{SplitOnFolderAndRecurseSuffix(nextFolderIndex, suffix)}";
55+
}
56+
57+
return $"{folder}_{EnsurePascalCase(suffix)}";
58+
}
59+
60+
private static string EnsureFirstCharUpper(string input) => char.ToUpperInvariant(input[0]) + input[1..];
61+
62+
private static bool IsKebabCase(string value) => value.Contains('-');
63+
64+
private static bool ThereIsAnotherFolder(int folderIndex) => folderIndex > -1;
65+
66+
private static string EnsurePascalCase(string input)
67+
{
68+
if (string.IsNullOrEmpty(input)) return input;
69+
if (input.Length == 1) return input.ToUpperInvariant();
70+
if (IsKebabCase(input))
71+
{
72+
var parts = input.Split('-');
73+
if (parts[0].IsAnId())
74+
{
75+
return parts.Length == 1
76+
? parts[0]
77+
: parts[0] + "_" + EnsurePascalCase(string.Join("-", parts.Skip(1)));
78+
}
79+
80+
return ToPascalCase(parts);
81+
}
82+
83+
return input.IsAnId()
84+
? input
85+
: EnsureFirstCharUpper(input);
86+
}
87+
88+
private static string ToPascalCase(string[] parts)
89+
{
90+
var sb = new StringBuilder();
91+
foreach (var part in parts)
92+
{
93+
sb.Append(EnsurePascalCase(part));
94+
}
95+
96+
return sb.ToString();
97+
}
98+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace GitVersion.Testing.Helpers;
2+
3+
internal static class StringExtensions
4+
{
5+
public static bool EndsWithAnIntId(this string value) =>
6+
value.Length > 0 && char.IsDigit(value[^1]);
7+
8+
public static bool IsAnIntId(this string value) =>
9+
int.TryParse(value, out _);
10+
11+
public static bool IsAnId(this string value) => IsAnIntId(value) || EndsWithAnIntId(value);
12+
}

0 commit comments

Comments
 (0)