diff --git a/.changeset/gentle-moles-jump.md b/.changeset/gentle-moles-jump.md new file mode 100644 index 0000000..d13ff89 --- /dev/null +++ b/.changeset/gentle-moles-jump.md @@ -0,0 +1,5 @@ +--- +"@guardian/cql": patch +--- + +Fix `queryChange`'s serialised `queryStr` dropping quotes from a plain phrase containing a reserved character (`:`, `(`, `)`, or `"`) with no whitespace, e.g. `"hello:world"`. Previously only whitespace triggered re-quoting for plain phrases, while chip keys/values already correctly checked reserved characters too — the two now use the same predicate (`shouldQuoteFieldValue`). Without this fix, a quoted phrase containing a colon is silently corrupted into an unquoted string, which then gets mis-parsed as a `key:value` chip the next time it's read. diff --git a/lib/cql/src/lang/interpreter.spec.ts b/lib/cql/src/lang/interpreter.spec.ts index 9389751..bd7045e 100644 --- a/lib/cql/src/lang/interpreter.spec.ts +++ b/lib/cql/src/lang/interpreter.spec.ts @@ -27,6 +27,33 @@ describe("interpreter", () => { expect(str).toBe(queryStr); }); + it("should quote a plain string containing a reserved char (colon)", () => { + const queryStr = `"hello:world"`; + const query = parser(queryStr).queryAst!; + + const str = cqlQueryStrFromQueryAst(query); + + expect(str).toBe(queryStr); + }); + + it("should quote a plain string containing a reserved char (parens)", () => { + const queryStr = `"(parenthetical)"`; + const query = parser(queryStr).queryAst!; + + const str = cqlQueryStrFromQueryAst(query); + + expect(str).toBe(queryStr); + }); + + it("should not quote a plain string with no reserved chars or whitespace", () => { + const queryStr = `hello`; + const query = parser(queryStr).queryAst!; + + const str = cqlQueryStrFromQueryAst(query); + + expect(str).toBe(queryStr); + }); + it("should escape reserved characters in chip keys and values", () => { const queryStr = `key:"\\"value\\""`; const query = parser(queryStr).queryAst!; diff --git a/lib/cql/src/lang/interpreter.ts b/lib/cql/src/lang/interpreter.ts index 92fa1ab..bcc6070 100644 --- a/lib/cql/src/lang/interpreter.ts +++ b/lib/cql/src/lang/interpreter.ts @@ -1,5 +1,5 @@ import { CqlBinary, CqlExpr, CqlField, CqlQuery } from "./ast"; -import { hasWhitespace, shouldQuoteFieldValue } from "./utils"; +import { shouldQuoteFieldValue } from "./utils"; export const cqlQueryStrFromQueryAst = (query: CqlQuery): string => { const { content } = query; @@ -17,7 +17,7 @@ const strFromExpr = (queryExpr: CqlExpr): string | undefined => { const renderedContent = (() => { switch (content.type) { case "CqlStr": - return hasWhitespace(content.searchExpr) + return shouldQuoteFieldValue(content.searchExpr) ? `"${content.searchExpr}"` : content.searchExpr; case "CqlGroup":