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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ The library consists of a single static class `Fuzzy` in `FuzzySearch/Fuzzy.cs`

The scoring algorithm rewards:
- Adjacent/consecutive character matches (`adjacentMatchBonus`)
- Matches after separators like `_` or space (`matchAfterSeparatorBonus`)
- Matches after separators: whitespace, `_`, `-`, `.`, `/` or `\` (`matchAfterSeparatorBonus`)
- Matches at camelCase boundaries (`camelCaseMatchBonus`)

And penalizes:
Expand Down
52 changes: 52 additions & 0 deletions FuzzySearch.Test/FuzzyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -938,4 +938,56 @@ public void Score_RepeatedLettersAfterSeparatorMatch_AreCharged()
}

#endregion

#region Separator Tests

[TestMethod]
public void Score_PathSegmentMatch_OutranksMidWordMatch()
{
Assert.IsGreaterThan(ScoreOf("domain.cs", "main"), ScoreOf("src/main.cs", "main"), "A match starting a path segment should outrank one buried in a word.");
}

[TestMethod]
public void Score_HyphenatedWordMatch_OutranksJoinedWordMatch()
{
Assert.IsGreaterThan(ScoreOf("getlist", "list"), ScoreOf("get-list", "list"), "A match after a hyphen should outrank one in the middle of a word.");
}

[TestMethod]
[DataRow("src-main.cs")]
[DataRow("src.main.cs")]
[DataRow("src/main.cs")]
[DataRow("src\\main.cs")]
[DataRow("src\tmain.cs")]
[DataRow("src main.cs")]
public void Score_EverySeparator_EarnsTheSameBonusAsUnderscore(string subject)
{
Assert.AreEqual(ScoreOf("src_main.cs", "main"), ScoreOf(subject, "main"), $"'{subject}' should score like 'src_main.cs'.");
}

[TestMethod]
[DataRow('_')]
[DataRow('-')]
[DataRow('.')]
[DataRow('/')]
[DataRow('\\')]
[DataRow(' ')]
[DataRow('\t')]
[DataRow('\n')]
public void IsSeparator_SeparatorCharacters_ReturnTrue(char value)
{
Assert.IsTrue(Fuzzy.IsSeparator(value));
}

[TestMethod]
[DataRow('a')]
[DataRow('Z')]
[DataRow('0')]
[DataRow(':')]
public void IsSeparator_OtherCharacters_ReturnFalse(char value)
{
Assert.IsFalse(Fuzzy.IsSeparator(value));
}

#endregion
}
19 changes: 17 additions & 2 deletions FuzzySearch/Fuzzy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@
/// <summary>The bonus score awarded for adjacent character matches.</summary>
internal const int adjacentMatchBonus = 5;

/// <summary>The bonus score awarded for matches that occur after a separator character ('_' or space).</summary>
/// <summary>
/// The bonus score awarded for matches that occur after a separator character: whitespace, '_', '-', '.', '/' or
/// '\'.
/// </summary>
internal const int matchAfterSeparatorBonus = 10;

/// <summary>The bonus score awarded for matches that occur at camelCase boundaries.</summary>
Expand Down Expand Up @@ -162,7 +165,7 @@
/// or the pattern is empty and the subject is not; otherwise, <c>false</c>.
/// </param>
/// <returns>A score representing the quality of the match. Higher scores indicate better matches.</returns>
internal static int CalculateScoreCore(ReadOnlySpan<char> subject, ReadOnlySpan<char> pattern, out bool wholePatternIsPresent)

Check warning on line 168 in FuzzySearch/Fuzzy.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Refactor this method to reduce its Cognitive Complexity from 35 to the 15 allowed.

Check warning on line 168 in FuzzySearch/Fuzzy.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Refactor this method to reduce its Cognitive Complexity from 35 to the 15 allowed.

Check warning on line 168 in FuzzySearch/Fuzzy.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Refactor this method to reduce its Cognitive Complexity from 35 to the 15 allowed.

Check warning on line 168 in FuzzySearch/Fuzzy.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Refactor this method to reduce its Cognitive Complexity from 35 to the 15 allowed.

Check warning on line 168 in FuzzySearch/Fuzzy.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Refactor this method to reduce its Cognitive Complexity from 35 to the 15 allowed.

Check warning on line 168 in FuzzySearch/Fuzzy.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Refactor this method to reduce its Cognitive Complexity from 35 to the 15 allowed.
{
if (pattern.IsEmpty)
{
Expand Down Expand Up @@ -283,7 +286,7 @@
bool isLetter = strLower != strUpper;

prevLower = strChar == strLower && isLetter;
prevSeparator = strChar is '_' or ' ';
prevSeparator = IsSeparator(strChar);

strIdx += strCharLength;
strCodepointIdx++;
Expand All @@ -299,6 +302,18 @@
return score;
}

/// <summary>
/// Determines whether a character separates words, so that a match right after it earns
/// <see cref="matchAfterSeparatorBonus"/>.
/// </summary>
/// <param name="value">The character to test.</param>
/// <returns>
/// <c>true</c> for whitespace and for the identifier and path separators '_', '-', '.', '/' and '\'; otherwise,
/// <c>false</c>.
/// </returns>
internal static bool IsSeparator(char value) =>
char.IsWhiteSpace(value) || value is '_' or '-' or '.' or '/' or '\\';

/// <summary>
/// Gets the number of UTF-16 code units occupied by the Unicode codepoint starting at the given index.
/// </summary>
Expand Down Expand Up @@ -448,7 +463,7 @@

if (char.IsHighSurrogate(c) && i + 1 < value.Length && char.IsLowSurrogate(value[i + 1]))
{
i++;

Check warning on line 466 in FuzzySearch/Fuzzy.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Do not update the stop condition variable 'i' in the body of the for loop.

Check warning on line 466 in FuzzySearch/Fuzzy.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Do not update the stop condition variable 'i' in the body of the for loop.

Check warning on line 466 in FuzzySearch/Fuzzy.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Do not update the stop condition variable 'i' in the body of the for loop.

Check warning on line 466 in FuzzySearch/Fuzzy.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Do not update the stop condition variable 'i' in the body of the for loop.
continue;
}

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ Both overloads take `ReadOnlySpan<char>`, so a `string` argument is passed strai
| Rule | Effect |
|------|--------|
| Match adjacent to the previous match | `+5` |
| Match after a `_` or space separator | `+10` |
| Match after whitespace or a `_`, `-`, `.`, `/` or `\` separator | `+10` |
| Match at a camelCase boundary | `+10` |
| Each unmatched character | `-1` |
| Unmatched characters before the first match | `-1` each, capped at `-5` |
Expand Down
Loading