feat: tighten search grammar and use unicode case folding

This commit is contained in:
doyaGu committed 2026-10-03 02:50:52 -04:00
1 parent 632339ea42
commit 27b507e439
6 files changed
+204 -79

No files matched your search

+64 -7
View File
@@ -147,9 +147,10 @@ public class SearchQueryParserTests
}
[TestMethod]
public void QualifierValueMayContainColons()
public void QuotedQualifierValueMayContainColonsAndOperators()
{
AssertParsesTo(new SearchNode.TagName("a:b"), "tag:a:b");
AssertParsesTo(new SearchNode.TagName("a:b"), "tag:\"a:b\"");
AssertParsesTo(new SearchNode.TagName("AND"), "tag:\"AND\"");
}
[TestMethod]
@@ -169,13 +170,20 @@ public class SearchQueryParserTests
public void IdQualifierParsesNumber()
{
AssertParsesTo(new SearchNode.CardId(42), "id:42");
AssertParsesTo(new SearchNode.CardId(12), "id:\"12\"");
AssertParsesTo(new SearchNode.CardId(61), "id:#61");
}
[TestMethod]
public void ColonWithoutKeyPrefixIsFreeText()
public void QuotedTextWithColonIsFreeText()
{
AssertParsesTo(new SearchNode.FreeText(":foo"), ":foo");
AssertParsesTo(new SearchNode.FreeText("http://x"), "\"http://x\"");
}
[TestMethod]
public void WordMayContainHashAndPunctuation()
{
AssertParsesTo(new SearchNode.FreeText("#61"), "#61");
AssertParsesTo(new SearchNode.FreeText("c#/.net"), "c#/.net");
}
#endregion
@@ -262,9 +270,58 @@ public class SearchQueryParserTests
}
[TestMethod]
public void NonNumericIdIsAnError()
[DataRow("id:12x")]
[DataRow("id:abc")]
[DataRow("id:0")]
[DataRow("id:-1")]
[DataRow("id:+1")]
[DataRow("id:#")]
[DataRow("id:##1")]
[DataRow("id:1.5")]
[DataRow("id:٣")]
[DataRow("id:99999999999999999999")]
public void InvalidIdValueIsAnError(string text)
{
AssertSyntaxError("id:12x");
AssertSyntaxError(text);
}
[TestMethod]
public void QuotedIdValueIsAnError()
{
AssertSyntaxError("id:\"61\"");
}
[TestMethod]
[DataRow("tag:AND")]
[DataRow("tag:OR")]
[DataRow("title:AND")]
public void BareOperatorAsQualifierValueIsAnError(string text)
{
AssertSyntaxError(text);
}
[TestMethod]
public void UnquotedQualifierValueWithColonIsAnError()
{
AssertSyntaxError("tag:a:b");
}
[TestMethod]
[DataRow(":foo")]
[DataRow("http://x")]
[DataRow("a:")]
public void ColonAfterNonKeyIsAnError(string text)
{
AssertSyntaxError(text);
}
[TestMethod]
[DataRow("a\\b")]
[DataRow("\\")]
[DataRow("tag:a\\b")]
public void BackslashInBareWordIsAnError(string text)
{
AssertSyntaxError(text);
}
[TestMethod]
+19 -4
View File
@@ -169,10 +169,25 @@ public class SearchSqlCompilerTests
{
CompiledSearch compiled = SearchSqlCompiler.Compile(new SearchNode.FreeText("x"));
// One folded parameter shared by the title and content tests.
StringAssert.Contains(compiled.Predicate, "$p0");
StringAssert.Contains(compiled.Predicate, "$p1");
Assert.AreEqual(2, compiled.Parameters.Count);
Assert.AreEqual("%x%", compiled.Parameters[0].Value);
Assert.AreEqual("%x%", compiled.Parameters[1].Value);
Assert.AreEqual(1, compiled.Parameters.Count);
Assert.AreEqual("x", compiled.Parameters[0].Value);
}
[TestMethod]
public void MatchingFoldsNonAsciiCase()
{
using var fixture = new SearchFixture();
SqliteTestHelper.Exec(fixture.Connection, "INSERT INTO columns (title, description, created_at, updated_at) VALUES ('Érable', '', 1, 1);");
SqliteTestHelper.Exec(fixture.Connection, "INSERT INTO cards (column_id, title, content, created_at, updated_at) VALUES (3, 'Ärger', 'Straße ΣΟΦΙΑ', 1, 1);");
SqliteTestHelper.Exec(fixture.Connection, "INSERT INTO tags (name, color, description) VALUES ('Äpfel', '#123456', '');");
SqliteTestHelper.Exec(fixture.Connection, "INSERT INTO card_tags (card_id, tag_id) VALUES (5, 4);");
CollectionAssert.AreEqual(new[] { 5L }, fixture.Search("ärger"));
CollectionAssert.AreEqual(new[] { 5L }, fixture.Search("title:ÄRGER"));
CollectionAssert.AreEqual(new[] { 5L }, fixture.Search("content:σοφια"));
CollectionAssert.AreEqual(new[] { 5L }, fixture.Search("tag:äpfel"));
CollectionAssert.AreEqual(new[] { 5L }, fixture.Search("column:érable"));
}
}