From f03b0c023128d3cda7cbbc2193dcef5ab1043579 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Tue, 27 Oct 2026 01:59:14 +0100 Subject: [PATCH] Find a resource name that comes before a longer one on the same line `GetAllIndexesAsync` took the longest of the names that occur anywhere in the rest of a line and carried on after its first occurrence. A shorter name that stood before a longer one on the same line was skipped, so its text was not shown. In `Strings.A + Resources.B` only `Resources.B` was found. `IndexOfAnyAsync` now returns the earliest occurrence of any of the names, which is what its name says, so every name on a line is found, from left to right. A line that uses one name, or the longer names first, gives the same indexes as before. Added tests for both methods. Four of them fail without the change. Co-Authored-By: Claude Sonnet 5.5 --- src/StringExtensions.cs | 16 ++- .../StringExtensionTests.cs | 133 ++++++++++++++++++ .../StringResourceVisualizer.Tests.csproj | 1 + 3 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 src/StringResourceVisualizer.Tests/StringExtensionTests.cs diff --git a/src/StringExtensions.cs b/src/StringExtensions.cs index b4d5665..861e49a 100644 --- a/src/StringExtensions.cs +++ b/src/StringExtensions.cs @@ -11,6 +11,12 @@ namespace StringResourceVisualizer { public static class StringExtensions { + /// + /// Finds where the first of the values occurs in a text. + /// + /// The text to search. + /// The values to look for. A value can be in the array more than once. + /// The index of the earliest occurrence of any of the values, or -1 if none of them occurs. public static async Task IndexOfAnyAsync(this string source, params string[] values) { try @@ -25,7 +31,9 @@ public static async Task IndexOfAnyAsync(this string source, params string[ if (valuePositions.Any(v => v.Value > -1)) { - var result = valuePositions.Where(v => v.Value > -1).OrderByDescending(v => v.Key.Length).FirstOrDefault().Value; + // The earliest occurrence counts, whatever the length of the value. + // Using the longest value that occurs anywhere in the text would skip a shorter one that comes before it. + var result = valuePositions.Where(v => v.Value > -1).Min(v => v.Value); return result; } @@ -43,6 +51,12 @@ public static async Task IndexOfAnyAsync(this string source, params string[ return -1; } + /// + /// Finds where each of the values occurs in a text. + /// + /// The text to search. + /// The values to look for. + /// The index of every occurrence of any of the values, from left to right. public static async Task> GetAllIndexesAsync(this string source, params string[] values) { var result = new List(); diff --git a/src/StringResourceVisualizer.Tests/StringExtensionTests.cs b/src/StringResourceVisualizer.Tests/StringExtensionTests.cs new file mode 100644 index 0000000..9b87e32 --- /dev/null +++ b/src/StringResourceVisualizer.Tests/StringExtensionTests.cs @@ -0,0 +1,133 @@ +// +// Copyright (c) Matt Lacey. All rights reserved. +// + +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace StringResourceVisualizer.Tests +{ + [TestClass] + public class StringExtensionTests + { + [TestMethod] + public async Task IndexOfAny_NoValues_FindsNothing() + { + var actual = await "Resources.Greeting".IndexOfAnyAsync(); + + Assert.AreEqual(-1, actual); + } + + [TestMethod] + public async Task IndexOfAny_ValueThatIsNotThere_FindsNothing() + { + var actual = await "var x = 1;".IndexOfAnyAsync("Resources."); + + Assert.AreEqual(-1, actual); + } + + [TestMethod] + public async Task IndexOfAny_SearchIsCaseSensitive() + { + var actual = await "var x = resources.Greeting;".IndexOfAnyAsync("Resources."); + + Assert.AreEqual(-1, actual); + } + + [TestMethod] + public async Task IndexOfAny_SingleValue_FindsItsFirstOccurrence() + { + var actual = await "var x = Resources.A + Resources.B;".IndexOfAnyAsync("Resources."); + + Assert.AreEqual(8, actual); + } + + [TestMethod] + public async Task IndexOfAny_SeveralValues_FindsTheEarliestOne() + { + var actual = await "Strings.A + Resources.B".IndexOfAnyAsync("Resources.", "Strings."); + + Assert.AreEqual(0, actual); + } + + [TestMethod] + public async Task IndexOfAny_SeveralValues_FindsTheEarliestOneWhateverTheOrderOfTheValues() + { + var actual = await "Strings.A + Resources.B".IndexOfAnyAsync("Strings.", "Resources."); + + Assert.AreEqual(0, actual); + } + + [TestMethod] + public async Task IndexOfAny_SeveralValues_SkipsTheOnesThatAreNotThere() + { + var actual = await "x = Strings.A".IndexOfAnyAsync("Resources.", "Strings."); + + Assert.AreEqual(4, actual); + } + + [TestMethod] + public async Task IndexOfAny_DuplicateValues_DoNotMatter() + { + var actual = await "x = Resources.A".IndexOfAnyAsync("Resources.", "Resources."); + + Assert.AreEqual(4, actual); + } + + [TestMethod] + public async Task GetAllIndexes_NameBeforeALongerName_IsFound() + { + var actual = await "Strings.A + Resources.B".GetAllIndexesAsync("Strings.", "Resources."); + + CollectionAssert.AreEqual(new[] { 0, 12 }, actual); + } + + [TestMethod] + public async Task GetAllIndexes_NameAfterALongerName_IsFound() + { + var actual = await "Resources.A + Strings.B".GetAllIndexesAsync("Strings.", "Resources."); + + CollectionAssert.AreEqual(new[] { 0, 14 }, actual); + } + + [TestMethod] + public async Task GetAllIndexes_NamesOfDifferentLengthsInAnyOrder_AreAllFoundFromLeftToRight() + { + var actual = await "Strings.A, Resources.B, Strings.C".GetAllIndexesAsync("Strings.", "Resources."); + + CollectionAssert.AreEqual(new[] { 0, 11, 24 }, actual); + } + + [TestMethod] + public async Task GetAllIndexes_EveryUseOfTheSameName_IsFound() + { + var actual = await "Resources.A + Resources.B".GetAllIndexesAsync("Resources."); + + CollectionAssert.AreEqual(new[] { 0, 14 }, actual); + } + + [TestMethod] + public async Task GetAllIndexes_NameThatEndsWithAnotherName_FindsBoth() + { + var actual = await "MyResources.A".GetAllIndexesAsync("Resources.", "MyResources."); + + CollectionAssert.AreEqual(new[] { 0, 2 }, actual); + } + + [TestMethod] + public async Task GetAllIndexes_NothingFound_IsEmpty() + { + var actual = await "var x = 1;".GetAllIndexesAsync("Resources."); + + Assert.AreEqual(0, actual.Count); + } + + [TestMethod] + public async Task GetAllIndexes_NoValues_IsEmpty() + { + var actual = await "Resources.A".GetAllIndexesAsync(); + + Assert.AreEqual(0, actual.Count); + } + } +} diff --git a/src/StringResourceVisualizer.Tests/StringResourceVisualizer.Tests.csproj b/src/StringResourceVisualizer.Tests/StringResourceVisualizer.Tests.csproj index 82e550c..e47a819 100644 --- a/src/StringResourceVisualizer.Tests/StringResourceVisualizer.Tests.csproj +++ b/src/StringResourceVisualizer.Tests/StringResourceVisualizer.Tests.csproj @@ -44,6 +44,7 @@ +