diff --git a/packages/queryLanguage/src/tokens.ts b/packages/queryLanguage/src/tokens.ts index 1bb69f531..043462200 100644 --- a/packages/queryLanguage/src/tokens.ts +++ b/packages/queryLanguage/src/tokens.ts @@ -673,27 +673,12 @@ export const negateToken = new ExternalTokenizer((input, stack) => { } } - // Check if followed by a prefix keyword (by checking for keyword followed by colon) - let foundColon = false; - let peekOffset = offset; - - while (true) { - const ch = input.peek(peekOffset); - if (ch === EOF) break; - - if (ch === COLON) { - foundColon = true; - break; - } - // Hit a delimiter (whitespace, paren, or quote) - not a prefix keyword - if (isWhitespace(ch) || ch === OPEN_PAREN || ch === CLOSE_PAREN || ch === QUOTE) { - break; - } - peekOffset++; - } - - if (foundColon) { - // It's a prefix keyword, accept as negate + // Only accept as negate when the dash is immediately followed by a known + // prefix keyword (e.g. `-file:`). A bare word that merely contains a colon + // (e.g. `-foo:bar`, `-http://x`) is not a prefix and must be left for + // wordToken; otherwise the grammar has no PrefixExpr to follow the negate + // token and the (strict) parser throws a SyntaxError. + if (startsWithPrefixAt(input, offset)) { input.advance(); input.acceptToken(negate); return; diff --git a/packages/queryLanguage/test/negation.txt b/packages/queryLanguage/test/negation.txt index 105c96347..b1d8755aa 100644 --- a/packages/queryLanguage/test/negation.txt +++ b/packages/queryLanguage/test/negation.txt @@ -277,3 +277,27 @@ chat lang:TypeScript -file:(test|spec) ==> Program(AndExpr(Term,PrefixExpr(LangExpr),NegateExpr(PrefixExpr(FileExpr)))) + +# Dash term with non-prefix colon word (e.g. a URL) stays a single Term + +-http://example.com + +==> + +Program(Term) + +# Dash term with unknown colon key stays a single Term + +-time:12 + +==> + +Program(Term) + +# Prefix combined with a negated non-prefix colon word + +repo:x -foo:bar + +==> + +Program(AndExpr(PrefixExpr(RepoExpr),Term))