From 4ecc19484e3a0d2f69cf89b581c0cbf3b3e394ff Mon Sep 17 00:00:00 2001 From: Paul DeVito Date: Fri, 9 Oct 2026 23:51:08 +0300 Subject: [PATCH] fix(config): keep a query name in a nested path in alias replacement The public method QueryKitPropertyMappings.ReplaceAliasesWithPropertyPaths replaced a query name anywhere in front of an operator, also after a dot. The segment after a dot belongs to another type, so the old match gave a wrong path. The method now keeps a query name after a dot. It also does not throw InvalidOperationException for a query name after a dot when the property has PreventFilter and PreventSort. Behavior change: only code that calls ReplaceAliasesWithPropertyPaths directly sees a new result. Examples (Title has HasQueryName("name")): ReplaceAliasesWithPropertyPaths("Author.Name == \"x\" && name == \"y\"") before: Author.Title == "x" && Title == "y" after: Author.Name == "x" && Title == "y" --- QueryKit.UnitTests/PropertyResolverTests.cs | 17 +++++++++++++++-- QueryKit/QueryKitPropertyMappings.cs | 5 +++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/QueryKit.UnitTests/PropertyResolverTests.cs b/QueryKit.UnitTests/PropertyResolverTests.cs index 3305c06..3aa9ab6 100644 --- a/QueryKit.UnitTests/PropertyResolverTests.cs +++ b/QueryKit.UnitTests/PropertyResolverTests.cs @@ -857,7 +857,7 @@ public void property_prevented_for_filter_and_sort_is_removed_by_its_member_name } [Fact] - public void alias_replacement_replaces_a_query_name_in_a_nested_path() + public void alias_replacement_does_not_replace_a_query_name_in_a_nested_path() { var config = new QueryKitConfiguration(config => { @@ -866,7 +866,20 @@ public void alias_replacement_replaces_a_query_name_in_a_nested_path() var input = config.PropertyMappings.ReplaceAliasesWithPropertyPaths("""Author.Name == "x" && name == "y" """); - input.Should().Be("""Author.Title == "x" && Title == "y" """); + input.Should().Be("""Author.Name == "x" && Title == "y" """); + } + + [Fact] + public void filter_with_a_query_name_in_a_nested_path_resolves_the_nested_path() + { + var config = new QueryKitConfiguration(config => + { + config.Property(x => x.Title).HasQueryName("name"); + }); + + var filterExpression = FilterParser.ParseFilter("""Author.Name == "x" """, config); + + filterExpression.ToDisplayString().Should().Be("""x => (x.Author.Name == "x")"""); } [Fact] diff --git a/QueryKit/QueryKitPropertyMappings.cs b/QueryKit/QueryKitPropertyMappings.cs index 16ecdf8..e537449 100644 --- a/QueryKit/QueryKitPropertyMappings.cs +++ b/QueryKit/QueryKitPropertyMappings.cs @@ -150,8 +150,9 @@ public string ReplaceAliasesWithPropertyPaths(string input) { // Use regular expression to isolate left side of the expression. Query names and // operators are matched literally, so escape any regex metacharacters they contain - // (e.g. the `^` in `^^` would otherwise be read as a start-of-line anchor). - var regex = AliasRegexCache.Get($@"\b{Regex.Escape(queryKitPropertyInfo.QueryName!)}\b(?=\s*{Regex.Escape(op)})"); + // (e.g. the `^` in `^^` would otherwise be read as a start-of-line anchor). A query name + // after a dot is a segment of a nested path, so it stays. + var regex = AliasRegexCache.Get($@"(?