Repository: obsidian-tasks-group/obsidian-tasks
Pull request #2139 (2023-07-17 09:28:46)

feat: Show help message for invalid regular expressions
<!--- Provide a general summary of your changes in the Title above -->

# Description

Give a help message if a regular expression is invalid.


Documentation will be updated in a later PR.

## Motivation and Context

Fixes https://github.com/obsidian-tasks-group/obsidian-tasks/issues/1038

This closes the last of the usability issues around regular expression searching.

The previous message of something like 'check your slashes' could have been clearer.


## How has this been tested?

- Running and updating tests
- Repeated exploratory testing, until I was happy enough with the message.

## Screenshots (if appropriate)

![image](https://github.com/obsidian-tasks-group/obsidian-tasks/assets/4840096/e97a1d05-6a52-4291-8512-fb39b2d675f8)


## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

Changes visible to users:

- [x] **New feature** (prefix: `feat` - non-breaking change which adds functionality)

Internal changes:

- [x] **Tests** (prefix: `test` - additions and improvements to unit tests and the smoke tests)

## Checklist

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [x] My code follows the code style of this project and passes `yarn run lint`.
- [x] My change requires a change to the documentation.
- [ ] I have [updated the documentation](https://publish.obsidian.md/tasks-contributing/Documentation/About+Documentation) accordingly.
- [x] My change has adequate [Unit Test coverage](https://publish.obsidian.md/tasks-contributing/Testing/About+Testing).

## Terms

<!--
By submitting this pull request, you must agree to follow our
[contributing guide](https://publish.obsidian.md/tasks-contributing) and
[Code of Conduct](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CODE_OF_CONDUCT.md).
Put an x in the boxes to confirm you agree.
-->

- [x] My contribution follow this project's [contributing guide](https://publish.obsidian.md/tasks-contributing)
- [x] I agree to follow this project's [Code of Conduct](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CODE_OF_CONDUCT.md)



Patch:
diff --git a/src/Query/Filter/TextField.ts b/src/Query/Filter/TextField.ts
index e3ec978c..a6670597 100644
--- a/src/Query/Filter/TextField.ts
+++ b/src/Query/Filter/TextField.ts
@@ -39,7 +39,7 @@ export abstract class TextField extends Field {
             if (matcher === null) {
                 return FilterOrErrorMessage.fromError(
                     line,
-                    `cannot parse regex (${this.fieldName()}); check your leading and trailing slashes for your query`,
+                    `Invalid instruction: '${line}'\n\n${RegexMatcher.helpMessage()}`,
                 );
             }
         }
diff --git a/src/Query/Matchers/RegexMatcher.ts b/src/Query/Matchers/RegexMatcher.ts
index 51447856..441ba7d9 100644
--- a/src/Query/Matchers/RegexMatcher.ts
+++ b/src/Query/Matchers/RegexMatcher.ts
@@ -47,7 +47,37 @@ export class RegexMatcher extends IStringMatcher {
         return stringToSearch.match(this.regex) !== null;
     }
 
-    explanation(instruction: string): Explanation {
+    public static helpMessage(): string {
+        return String.raw`See https://publish.obsidian.md/tasks/Queries/Regular+Expressions
+
+Regular expressions must look like this:
+    /pattern/
+or this:
+    /pattern/flags
+
+Where:
+- pattern: The 'regular expression' pattern to search for.
+- flags:   Optional characters that modify the search.
+           i => make the search case-insensitive
+           u => add Unicode support
+
+Examples:  /^Log/
+           /^Log/i
+           /File Name\.md/
+           /waiting|waits|waited/i
+           /\d\d:\d\d/
+
+The following characters have special meaning in the pattern:
+to find them literally, you must add a \ before them:
+    [\^$.|?*+()
+
+CAUTION! Regular expression (or 'regex') searching is a powerful
+but advanced feature that requires thorough knowledge in order to
+use successfully, and not miss intended search results.
+`;
+    }
+
+    public explanation(instruction: string): Explanation {
         const intro = 'using regex: ';
         const explanationText = alignRegexWithOriginalInstruction(instruction, intro, this.regexAsString());
         return new Explanation(explanationText);
diff --git a/src/Query/Query.ts b/src/Query/Query.ts
index a1fd0ba8..53a992a8 100644
--- a/src/Query/Query.ts
+++ b/src/Query/Query.ts
@@ -173,7 +173,8 @@ export class Query implements IQuery {
     }
 
     private setError(message: string, line: string) {
-        this._error = `${message}:\n${line}`;
+        this._error = `${message}
+Problem line: "${line}"`;
     }
 
     public applyQueryToTasks(tasks: Task[]): QueryResult {


Test patch:
diff --git a/tests/Query.test.ts b/tests/Query.test.ts
index a370d80e..c5fa063c 100644
--- a/tests/Query.test.ts
+++ b/tests/Query.test.ts
@@ -445,31 +445,64 @@ describe('Query parsing', () => {
             return new Query({ source: source }).error;
         }
 
-        it('for invalid filter', () => {
+        it('for invalid regular expression filter', () => {
             const source = 'description regex matches apple sauce';
             expect(getQueryError(source)).toEqual(
-                'cannot parse regex (description); check your leading and trailing slashes for your query:\n' + source,
+                String.raw`Invalid instruction: 'description regex matches apple sauce'
+
+See https://publish.obsidian.md/tasks/Queries/Regular+Expressions
+
+Regular expressions must look like this:
+    /pattern/
+or this:
+    /pattern/flags
+
+Where:
+- pattern: The 'regular expression' pattern to search for.
+- flags:   Optional characters that modify the search.
+           i => make the search case-insensitive
+           u => add Unicode support
+
+Examples:  /^Log/
+           /^Log/i
+           /File Name\.md/
+           /waiting|waits|waited/i
+           /\d\d:\d\d/
+
+The following characters have special meaning in the pattern:
+to find them literally, you must add a \ before them:
+    [\^$.|?*+()
+
+CAUTION! Regular expression (or 'regex') searching is a powerful
+but advanced feature that requires thorough knowledge in order to
+use successfully, and not miss intended search results.
+
+Problem line: "${source}"`,
             );
         });
 
         it('for invalid sort by', () => {
             const source = 'sort by nonsense';
-            expect(getQueryError(source)).toEqual('do not understand query:\n' + source);
+            expect(getQueryError(source)).toEqual(`do not understand query
+Problem line: "${source}"`);
         });
 
         it('for invalid group by', () => {
             const source = 'group by nonsense';
-            expect(getQueryError(source)).toEqual('do not understand query:\n' + source);
+            expect(getQueryError(source)).toEqual(`do not understand query
+Problem line: "${source}"`);
         });
 
         it('for invalid hide', () => {
             const source = 'hide nonsense';
-            expect(getQueryError(source)).toEqual('do not understand query:\n' + source);
+            expect(getQueryError(source)).toEqual(`do not understand query
+Problem line: "${source}"`);
         });
 
         it('for unknown instruction', () => {
             const source = 'spaghetti';
-            expect(getQueryError(source)).toEqual('do not understand query:\n' + source);
+            expect(getQueryError(source)).toEqual(`do not understand query
+Problem line: "${source}"`);
         });
     });
 });