From ca46de07024c9bd9ce951432321f23d117adc922 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Fri, 12 Apr 2024 21:36:50 +0100 Subject: [PATCH 1/2] feat: Fix any missing spaces around Boolean operators This is easy to do now that we have the simplified instruction line. For example this line: (path includes a)AND NOT(path includes b) Becomes this: (f1)AND NOT(f2) Which we now convert to this, which just works: (f1) AND NOT (f2) --- src/Query/Filter/BooleanPreprocessor.ts | 8 + ...ry_-_exhaustive_tests_explain.approved.txt | 230 ++++-------------- ...ve_tests_preprocess_-_rewrite.approved.txt | 24 +- tests/Query/Filter/BooleanField.test.ts | 70 ++---- .../Query/Filter/BooleanPreprocessor.test.ts | 36 ++- 5 files changed, 126 insertions(+), 242 deletions(-) diff --git a/src/Query/Filter/BooleanPreprocessor.ts b/src/Query/Filter/BooleanPreprocessor.ts index ba516c2eb3..d82fff7886 100644 --- a/src/Query/Filter/BooleanPreprocessor.ts +++ b/src/Query/Filter/BooleanPreprocessor.ts @@ -88,6 +88,14 @@ export class BooleanPreprocessor { } }); + // boon-js requires at least one space around each operator. + // Be nice to the user and add any missing spaces around operators: + const operatorMissingPrecedingSpace = new RegExp(`(${delimiters.closeFilter})([A-Z])`, 'g'); + simplifiedLine = simplifiedLine.replace(operatorMissingPrecedingSpace, '$1 $2'); + + const operatorMissingFollowingSpace = new RegExp(`([A-Z])(${delimiters.openFilter})`, 'g'); + simplifiedLine = simplifiedLine.replace(operatorMissingFollowingSpace, '$1 $2'); + // convert any non-standard delimiters to standard ones: const openChars = delimiters.openFilterChars; if (openChars != '"' && openChars != '(') { diff --git a/tests/Query/Filter/BooleanField.test.boolean_query_-_exhaustive_tests_explain.approved.txt b/tests/Query/Filter/BooleanField.test.boolean_query_-_exhaustive_tests_explain.approved.txt index cac5cd1f73..a9c9de9ac6 100644 --- a/tests/Query/Filter/BooleanField.test.boolean_query_-_exhaustive_tests_explain.approved.txt +++ b/tests/Query/Filter/BooleanField.test.boolean_query_-_exhaustive_tests_explain.approved.txt @@ -990,21 +990,10 @@ Input: '(description includes SHOULD NOT BE RECOGNISED AS A BOOLEAN)AND(description includes BECAUSE THERE ARE NO SPACES AROUND THE 'AND' OPERATOR)' => Result: -Could not interpret the following instruction as a Boolean combination: - (description includes SHOULD NOT BE RECOGNISED AS A BOOLEAN)AND(description includes BECAUSE THERE ARE NO SPACES AROUND THE 'AND' OPERATOR) - -The error message is: - malformed boolean query -- Unexpected character: A. A closing parenthesis should be followed by another closing parenthesis or whitespace (check the documentation for guidelines) - -The instruction was converted to the following simplified line: - (f1)AND(f2) - -Where the sub-expressions in the simplified line are: - 'f1': 'description includes SHOULD NOT BE RECOGNISED AS A BOOLEAN' - 'f2': 'description includes BECAUSE THERE ARE NO SPACES AROUND THE 'AND' OPERATOR' - -For help, see: - https://publish.obsidian.md/tasks/Queries/Combining+Filters + (description includes SHOULD NOT BE RECOGNISED AS A BOOLEAN)AND(description includes BECAUSE THERE ARE NO SPACES AROUND THE 'AND' OPERATOR) => + AND (All of): + description includes SHOULD NOT BE RECOGNISED AS A BOOLEAN + description includes BECAUSE THERE ARE NO SPACES AROUND THE 'AND' OPERATOR -------------------------------------------------------- @@ -2348,21 +2337,11 @@ Input: '(path includes a) AND NOT(path includes b)' => Result: -Could not interpret the following instruction as a Boolean combination: - (path includes a) AND NOT(path includes b) - -The error message is: - malformed boolean query -- Unexpected character: (. Operators should be separated using whitespace (check the documentation for guidelines) - -The instruction was converted to the following simplified line: - (f1) AND NOT(f2) - -Where the sub-expressions in the simplified line are: - 'f1': 'path includes a' - 'f2': 'path includes b' - -For help, see: - https://publish.obsidian.md/tasks/Queries/Combining+Filters + (path includes a) AND NOT(path includes b) => + AND (All of): + path includes a + NOT: + path includes b -------------------------------------------------------- @@ -2372,21 +2351,10 @@ Input: '(path includes a) AND(path includes b)' => Result: -Could not interpret the following instruction as a Boolean combination: - (path includes a) AND(path includes b) - -The error message is: - malformed boolean query -- Unexpected character: (. Operators should be separated using whitespace (check the documentation for guidelines) - -The instruction was converted to the following simplified line: - (f1) AND(f2) - -Where the sub-expressions in the simplified line are: - 'f1': 'path includes a' - 'f2': 'path includes b' - -For help, see: - https://publish.obsidian.md/tasks/Queries/Combining+Filters + (path includes a) AND(path includes b) => + AND (All of): + path includes a + path includes b -------------------------------------------------------- @@ -2396,21 +2364,11 @@ Input: '(path includes a) OR NOT(path includes b)' => Result: -Could not interpret the following instruction as a Boolean combination: - (path includes a) OR NOT(path includes b) - -The error message is: - malformed boolean query -- Unexpected character: (. Operators should be separated using whitespace (check the documentation for guidelines) - -The instruction was converted to the following simplified line: - (f1) OR NOT(f2) - -Where the sub-expressions in the simplified line are: - 'f1': 'path includes a' - 'f2': 'path includes b' - -For help, see: - https://publish.obsidian.md/tasks/Queries/Combining+Filters + (path includes a) OR NOT(path includes b) => + OR (At least one of): + path includes a + NOT: + path includes b -------------------------------------------------------- @@ -2420,21 +2378,10 @@ Input: '(path includes a) OR(path includes b)' => Result: -Could not interpret the following instruction as a Boolean combination: - (path includes a) OR(path includes b) - -The error message is: - malformed boolean query -- Unexpected character: (. Operators should be separated using whitespace (check the documentation for guidelines) - -The instruction was converted to the following simplified line: - (f1) OR(f2) - -Where the sub-expressions in the simplified line are: - 'f1': 'path includes a' - 'f2': 'path includes b' - -For help, see: - https://publish.obsidian.md/tasks/Queries/Combining+Filters + (path includes a) OR(path includes b) => + OR (At least one of): + path includes a + path includes b -------------------------------------------------------- @@ -2444,21 +2391,10 @@ Input: '(path includes a) XOR(path includes b)' => Result: -Could not interpret the following instruction as a Boolean combination: - (path includes a) XOR(path includes b) - -The error message is: - malformed boolean query -- Unexpected character: (. Operators should be separated using whitespace (check the documentation for guidelines) - -The instruction was converted to the following simplified line: - (f1) XOR(f2) - -Where the sub-expressions in the simplified line are: - 'f1': 'path includes a' - 'f2': 'path includes b' - -For help, see: - https://publish.obsidian.md/tasks/Queries/Combining+Filters + (path includes a) XOR(path includes b) => + XOR (Exactly one of): + path includes a + path includes b -------------------------------------------------------- @@ -2468,21 +2404,10 @@ Input: '(path includes a)AND (path includes b)' => Result: -Could not interpret the following instruction as a Boolean combination: - (path includes a)AND (path includes b) - -The error message is: - malformed boolean query -- Unexpected character: A. A closing parenthesis should be followed by another closing parenthesis or whitespace (check the documentation for guidelines) - -The instruction was converted to the following simplified line: - (f1)AND (f2) - -Where the sub-expressions in the simplified line are: - 'f1': 'path includes a' - 'f2': 'path includes b' - -For help, see: - https://publish.obsidian.md/tasks/Queries/Combining+Filters + (path includes a)AND (path includes b) => + AND (All of): + path includes a + path includes b -------------------------------------------------------- @@ -2492,21 +2417,11 @@ Input: '(path includes a)AND NOT(path includes b)' => Result: -Could not interpret the following instruction as a Boolean combination: - (path includes a)AND NOT(path includes b) - -The error message is: - malformed boolean query -- Unexpected character: A. A closing parenthesis should be followed by another closing parenthesis or whitespace (check the documentation for guidelines) - -The instruction was converted to the following simplified line: - (f1)AND NOT(f2) - -Where the sub-expressions in the simplified line are: - 'f1': 'path includes a' - 'f2': 'path includes b' - -For help, see: - https://publish.obsidian.md/tasks/Queries/Combining+Filters + (path includes a)AND NOT(path includes b) => + AND (All of): + path includes a + NOT: + path includes b -------------------------------------------------------- @@ -2516,21 +2431,10 @@ Input: '(path includes a)OR (path includes b)' => Result: -Could not interpret the following instruction as a Boolean combination: - (path includes a)OR (path includes b) - -The error message is: - malformed boolean query -- Unexpected character: O. A closing parenthesis should be followed by another closing parenthesis or whitespace (check the documentation for guidelines) - -The instruction was converted to the following simplified line: - (f1)OR (f2) - -Where the sub-expressions in the simplified line are: - 'f1': 'path includes a' - 'f2': 'path includes b' - -For help, see: - https://publish.obsidian.md/tasks/Queries/Combining+Filters + (path includes a)OR (path includes b) => + OR (At least one of): + path includes a + path includes b -------------------------------------------------------- @@ -2540,21 +2444,11 @@ Input: '(path includes a)OR NOT (path includes b)' => Result: -Could not interpret the following instruction as a Boolean combination: - (path includes a)OR NOT (path includes b) - -The error message is: - malformed boolean query -- Unexpected character: O. A closing parenthesis should be followed by another closing parenthesis or whitespace (check the documentation for guidelines) - -The instruction was converted to the following simplified line: - (f1)OR NOT (f2) - -Where the sub-expressions in the simplified line are: - 'f1': 'path includes a' - 'f2': 'path includes b' - -For help, see: - https://publish.obsidian.md/tasks/Queries/Combining+Filters + (path includes a)OR NOT (path includes b) => + OR (At least one of): + path includes a + NOT: + path includes b -------------------------------------------------------- @@ -2564,21 +2458,10 @@ Input: '(path includes a)XOR (path includes b)' => Result: -Could not interpret the following instruction as a Boolean combination: - (path includes a)XOR (path includes b) - -The error message is: - malformed boolean query -- Unexpected character: X. A closing parenthesis should be followed by another closing parenthesis or whitespace (check the documentation for guidelines) - -The instruction was converted to the following simplified line: - (f1)XOR (f2) - -Where the sub-expressions in the simplified line are: - 'f1': 'path includes a' - 'f2': 'path includes b' - -For help, see: - https://publish.obsidian.md/tasks/Queries/Combining+Filters + (path includes a)XOR (path includes b) => + XOR (Exactly one of): + path includes a + path includes b -------------------------------------------------------- @@ -3370,20 +3253,9 @@ Input: 'NOT(path includes b)' => Result: -Could not interpret the following instruction as a Boolean combination: - NOT(path includes b) - -The error message is: - malformed boolean query -- Unexpected character: (. Operators should be separated using whitespace (check the documentation for guidelines) - -The instruction was converted to the following simplified line: - NOT(f1) - -Where the sub-expressions in the simplified line are: - 'f1': 'path includes b' - -For help, see: - https://publish.obsidian.md/tasks/Queries/Combining+Filters + NOT(path includes b) => + NOT: + path includes b -------------------------------------------------------- diff --git a/tests/Query/Filter/BooleanField.test.boolean_query_-_exhaustive_tests_preprocess_-_rewrite.approved.txt b/tests/Query/Filter/BooleanField.test.boolean_query_-_exhaustive_tests_preprocess_-_rewrite.approved.txt index b44bfb27c4..7d17c9b523 100644 --- a/tests/Query/Filter/BooleanField.test.boolean_query_-_exhaustive_tests_preprocess_-_rewrite.approved.txt +++ b/tests/Query/Filter/BooleanField.test.boolean_query_-_exhaustive_tests_preprocess_-_rewrite.approved.txt @@ -965,7 +965,7 @@ Input: => Result: { - "simplifiedLine": "(f1)AND(f2)", + "simplifiedLine": "(f1) AND (f2)", "filters": { "f1": "description includes SHOULD NOT BE RECOGNISED AS A BOOLEAN", "f2": "description includes BECAUSE THERE ARE NO SPACES AROUND THE 'AND' OPERATOR" @@ -2316,7 +2316,7 @@ Input: => Result: { - "simplifiedLine": "(f1) AND NOT(f2)", + "simplifiedLine": "(f1) AND NOT (f2)", "filters": { "f1": "path includes a", "f2": "path includes b" @@ -2331,7 +2331,7 @@ Input: => Result: { - "simplifiedLine": "(f1) AND(f2)", + "simplifiedLine": "(f1) AND (f2)", "filters": { "f1": "path includes a", "f2": "path includes b" @@ -2346,7 +2346,7 @@ Input: => Result: { - "simplifiedLine": "(f1) OR NOT(f2)", + "simplifiedLine": "(f1) OR NOT (f2)", "filters": { "f1": "path includes a", "f2": "path includes b" @@ -2361,7 +2361,7 @@ Input: => Result: { - "simplifiedLine": "(f1) OR(f2)", + "simplifiedLine": "(f1) OR (f2)", "filters": { "f1": "path includes a", "f2": "path includes b" @@ -2376,7 +2376,7 @@ Input: => Result: { - "simplifiedLine": "(f1) XOR(f2)", + "simplifiedLine": "(f1) XOR (f2)", "filters": { "f1": "path includes a", "f2": "path includes b" @@ -2391,7 +2391,7 @@ Input: => Result: { - "simplifiedLine": "(f1)AND (f2)", + "simplifiedLine": "(f1) AND (f2)", "filters": { "f1": "path includes a", "f2": "path includes b" @@ -2406,7 +2406,7 @@ Input: => Result: { - "simplifiedLine": "(f1)AND NOT(f2)", + "simplifiedLine": "(f1) AND NOT (f2)", "filters": { "f1": "path includes a", "f2": "path includes b" @@ -2421,7 +2421,7 @@ Input: => Result: { - "simplifiedLine": "(f1)OR (f2)", + "simplifiedLine": "(f1) OR (f2)", "filters": { "f1": "path includes a", "f2": "path includes b" @@ -2436,7 +2436,7 @@ Input: => Result: { - "simplifiedLine": "(f1)OR NOT (f2)", + "simplifiedLine": "(f1) OR NOT (f2)", "filters": { "f1": "path includes a", "f2": "path includes b" @@ -2451,7 +2451,7 @@ Input: => Result: { - "simplifiedLine": "(f1)XOR (f2)", + "simplifiedLine": "(f1) XOR (f2)", "filters": { "f1": "path includes a", "f2": "path includes b" @@ -3222,7 +3222,7 @@ Input: => Result: { - "simplifiedLine": "NOT(f1)", + "simplifiedLine": "NOT (f1)", "filters": { "f1": "path includes b" } diff --git a/tests/Query/Filter/BooleanField.test.ts b/tests/Query/Filter/BooleanField.test.ts index 63a57d79cc..9795131ccb 100644 --- a/tests/Query/Filter/BooleanField.test.ts +++ b/tests/Query/Filter/BooleanField.test.ts @@ -231,54 +231,6 @@ describe('boolean query - filter', () => { 'NOT (happens before blahblahblah)', "couldn't parse sub-expression 'happens before blahblahblah': do not understand happens date", ], - - // Missing spaces before operator - [ - '(path includes a)AND (path includes b)', - 'malformed boolean query -- Unexpected character: A. A closing parenthesis should be followed by another closing parenthesis or whitespace (check the documentation for guidelines)', - ], - [ - '(path includes a)AND NOT(path includes b)', - 'malformed boolean query -- Unexpected character: A. A closing parenthesis should be followed by another closing parenthesis or whitespace (check the documentation for guidelines)', - ], - [ - '(path includes a)OR (path includes b)', - 'malformed boolean query -- Unexpected character: O. A closing parenthesis should be followed by another closing parenthesis or whitespace (check the documentation for guidelines)', - ], - [ - '(path includes a)OR NOT (path includes b)', - 'malformed boolean query -- Unexpected character: O. A closing parenthesis should be followed by another closing parenthesis or whitespace (check the documentation for guidelines)', - ], - [ - '(path includes a)XOR (path includes b)', - 'malformed boolean query -- Unexpected character: X. A closing parenthesis should be followed by another closing parenthesis or whitespace (check the documentation for guidelines)', - ], - - // Missing spaces after operator - [ - '(path includes a) AND(path includes b)', - 'malformed boolean query -- Unexpected character: (. Operators should be separated using whitespace (check the documentation for guidelines)', - ], - [ - '(path includes a) AND NOT(path includes b)', - 'malformed boolean query -- Unexpected character: (. Operators should be separated using whitespace (check the documentation for guidelines)', - ], - [ - '(path includes a) OR(path includes b)', - 'malformed boolean query -- Unexpected character: (. Operators should be separated using whitespace (check the documentation for guidelines)', - ], - [ - '(path includes a) OR NOT(path includes b)', - 'malformed boolean query -- Unexpected character: (. Operators should be separated using whitespace (check the documentation for guidelines)', - ], - [ - '(path includes a) XOR(path includes b)', - 'malformed boolean query -- Unexpected character: (. Operators should be separated using whitespace (check the documentation for guidelines)', - ], - [ - 'NOT(path includes b)', - 'malformed boolean query -- Unexpected character: (. Operators should be separated using whitespace (check the documentation for guidelines)', - ], ])( 'should report expected error message: on "%s" - expected "%s"', (instruction: string, expectedError: string) => { @@ -291,6 +243,28 @@ describe('boolean query - filter', () => { // result.error = 'empty operator in boolean query'; // result.error = `unknown boolean operator '${token.value}'`; }); + + describe('former error cases, that now work', () => { + it.each([ + // Missing spaces before operator + ['(path includes a)AND (path includes b)'], + ['(path includes a)AND NOT(path includes b)'], + ['(path includes a)OR (path includes b)'], + ['(path includes a)OR NOT (path includes b)'], + ['(path includes a)XOR (path includes b)'], + + // Missing spaces after operator + ['(path includes a) AND(path includes b)'], + ['(path includes a) AND NOT(path includes b)'], + ['(path includes a) OR(path includes b)'], + ['(path includes a) OR NOT(path includes b)'], + ['(path includes a) XOR(path includes b)'], + ['NOT(path includes b)'], + ])('should report expected error message: on "%s" - expected "%s"', (instruction: string) => { + const filter = new BooleanField().createFilterOrErrorMessage(instruction); + expect(filter.error).toBeUndefined(); + }); + }); }); describe('boolean query - explain', () => { diff --git a/tests/Query/Filter/BooleanPreprocessor.test.ts b/tests/Query/Filter/BooleanPreprocessor.test.ts index a7cee47185..3e9c810bb0 100644 --- a/tests/Query/Filter/BooleanPreprocessor.test.ts +++ b/tests/Query/Filter/BooleanPreprocessor.test.ts @@ -110,7 +110,7 @@ describe('BooleanPreprocessor', () => { "f1": "done", "f2": "has done date", }, - "simplifiedLine": "(f1)AND(f2)", + "simplifiedLine": "(f1) AND (f2)", } `); }); @@ -121,7 +121,37 @@ describe('BooleanPreprocessor', () => { "filters": { "f1": "not done", }, - "simplifiedLine": "NOT(f1)", + "simplifiedLine": "NOT (f1)", + } + `); + }); + + it('AND NOT missing surrounding spaces', () => { + expect(preprocess('(path includes a)AND NOT(path includes b)')).toMatchInlineSnapshot(` + { + "filters": { + "f1": "path includes a", + "f2": "path includes b", + }, + "simplifiedLine": "(f1) AND NOT (f2)", + } + `); + }); + + it('one of each operator, missing surrounding spaces', () => { + expect(preprocess('(done)XOR(done)AND(done)OR(done)NOT(done)AND NOT(done)OR NOT(done)')) + .toMatchInlineSnapshot(` + { + "filters": { + "f1": "done", + "f2": "done", + "f3": "done", + "f4": "done", + "f5": "done", + "f6": "done", + "f7": "done", + }, + "simplifiedLine": "(f1) XOR (f2) AND (f3) OR (f4) NOT (f5) AND NOT (f6) OR NOT (f7)", } `); }); @@ -159,7 +189,7 @@ describe('BooleanPreprocessor', () => { "filters": { "f1": "description includes d1", }, - "simplifiedLine": """"""NOT " f1 """"""", + "simplifiedLine": """""" NOT " f1 """"""", } `); }); From a8d1e89058046653a3c1b01c58a8283a09656b3e Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Fri, 12 Apr 2024 21:42:23 +0100 Subject: [PATCH 2/2] test: Confirm spaces added for all supported delimiter types --- tests/Query/Filter/BooleanField.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Query/Filter/BooleanField.test.ts b/tests/Query/Filter/BooleanField.test.ts index 9795131ccb..a69fa3527b 100644 --- a/tests/Query/Filter/BooleanField.test.ts +++ b/tests/Query/Filter/BooleanField.test.ts @@ -30,9 +30,13 @@ describe('boolean query - filter', () => { describe('basic operators', () => { it.each([ '(description includes d1) AND (description includes d2)', + '(description includes d1)AND(description includes d2)', '[description includes d1] AND [description includes d2]', + '[description includes d1]AND[description includes d2]', '{description includes d1} AND {description includes d2}', + '{description includes d1}AND{description includes d2}', '"description includes d1" AND "description includes d2"', + '"description includes d1"AND"description includes d2"', ])('instruction: "%s"', (line: string) => { // Arrange const filter = createValidFilter(line);