fix(search): reject empty phrases and phrases glued to other text

The lexer now keeps token offsets and rejects what the grammar never allowed
but the token rules accepted:
- an empty phrase: "" (matched every card), tag:""
- a phrase touching a word or another phrase: a"b", "a"b, "a""b"
- whitespace between a qualifier and its phrase value: tag: "x"

key:"value" stays the only legal contact; parentheses still delimit phrases.
Listed in PLAN 6.1/6.2 and USAGE.
This commit is contained in:
doyaGu committed 2026-10-03 08:20:30 -04:00
1 parent 9277e2a331
commit 1d744ba504
4 files changed
+124 -12

No files matched your search

@@ -296,6 +296,42 @@ public class SearchQueryParserTests
AssertSyntaxError("tag: bug");
}
[TestMethod]
[DataRow("tag: \"x\"")]
[DataRow("title: \"a b\"")]
public void SpacedQualifierPhraseValueIsAnError(string text)
{
AssertSyntaxError(text);
}
[TestMethod]
[DataRow("a\"b\"")]
[DataRow("\"a\"b")]
[DataRow("\"a\"\"b\"")]
[DataRow("tag:\"x\"y")]
[DataRow("tag:\"x\"\"y\"")]
public void PhraseGluedToOtherTextIsAnError(string text)
{
AssertSyntaxError(text);
}
[TestMethod]
[DataRow("\"\"")]
[DataRow("a \"\"")]
[DataRow("tag:\"\"")]
public void EmptyPhraseIsAnError(string text)
{
AssertSyntaxError(text);
}
[TestMethod]
public void PhraseMayTouchParentheses()
{
AssertParsesTo(
new SearchNode.And([new SearchNode.FreeText("a b"), new SearchNode.FreeText("c")]),
"(\"a b\")(c)");
}
[TestMethod]
[DataRow("id:12x")]
[DataRow("id:abc")]