Repository: obsidian-tasks-group/obsidian-tasks (ts)

Issue:
For invalid regular expressions, give user meaningful explanation of the problem
              Here is an example of the kind of error message about invalid regex patterns shown by the Apply Patterns plugin:

![image](https://user-images.githubusercontent.com/4840096/187770618-8a30a839-90ea-43d7-928b-3ca4fa06ad4c.png)

The bit that is useful here is 'unterminated group' and the fact that it includes the problem regex pattern itself (which would be useful if the user had multiple regexes in one tasks block).

Assuming it uses the same library, then reviewing its source code to find out how it obtains the description of the error could be very illuminating.

_Originally posted by @claremacrae in https://github.com/obsidian-tasks-group/obsidian-tasks/issues/1039#issuecomment-1233362575_
            

Pull request:
fix: Display reason for any failure to read regex
# Description

Prior to this change, any errors in regular expressions that made it past the checking in `RegexMatcher.validateAndConstruct()` resulted in a blank Tasks code block, with no information for the user at all about the nature of the problem.

Now, an error message is displayed, showing what the problem is.

For example:

```
Error: Parsing regular expression. The error message was: "SyntaxError: Invalid regular expression: /hello(/: Unterminated group"
description regex matches /hello(/
```

## Motivation and Context

Fixes #2129.

## How has this been tested?

- With new tests
- Manually in the sample vault

<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, tests ran to see how -->
<!--- your change affects other areas of the code, etc. -->

## Screenshots (if appropriate)

## 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)

## 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`.
- [ ] 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 07203a3c..e3ec978c 100644
--- a/src/Query/Filter/TextField.ts
+++ b/src/Query/Filter/TextField.ts
@@ -4,6 +4,7 @@ import { RegexMatcher } from '../Matchers/RegexMatcher';
 import type { IStringMatcher } from '../Matchers/IStringMatcher';
 import type { Comparator } from '../Sorter';
 import type { GrouperFunction } from '../Grouper';
+import { errorMessageForException } from '../../lib/ExceptionTools';
 import { Field } from './Field';
 import type { FilterFunction } from './Filter';
 import { Filter } from './Filter';
@@ -30,7 +31,11 @@ export abstract class TextField extends Field {
         if (filterOperator.includes('include')) {
             matcher = new SubstringMatcher(filterValue);
         } else if (filterOperator.includes('regex')) {
-            matcher = RegexMatcher.validateAndConstruct(filterValue);
+            try {
+                matcher = RegexMatcher.validateAndConstruct(filterValue);
+            } catch (e) {
+                return FilterOrErrorMessage.fromError(line, errorMessageForException('Parsing regular expression', e));
+            }
             if (matcher === null) {
                 return FilterOrErrorMessage.fromError(
                     line,
diff --git a/src/Query/Matchers/RegexMatcher.ts b/src/Query/Matchers/RegexMatcher.ts
index 8ef99499..3e77c308 100644
--- a/src/Query/Matchers/RegexMatcher.ts
+++ b/src/Query/Matchers/RegexMatcher.ts
@@ -24,6 +24,7 @@ export class RegexMatcher extends IStringMatcher {
      * @param {string} regexInput - A string that can be converted to a regular expression.
      *                              It must begin with a /, and end either with / and optionally any
      *                              valid flags.
+     * @throws {SyntaxError} Throws an exception if there was an error in {@link regexInput}.
      */
     public static validateAndConstruct(regexInput: string): RegexMatcher | null {
         // Courtesy of https://stackoverflow.com/questions/17843691/javascript-regex-to-match-a-regex


Test patch:
diff --git a/tests/Query/Filter/TextField.test.ts b/tests/Query/Filter/TextField.test.ts
index f264f02c..d51e4fc8 100644
--- a/tests/Query/Filter/TextField.test.ts
+++ b/tests/Query/Filter/TextField.test.ts
@@ -2,6 +2,16 @@ import { verify } from 'approvals/lib/Providers/Jest/JestApprovals';
 import { DescriptionField } from '../../../src/Query/Filter/DescriptionField';
 import { Query } from '../../../src/Query/Query';
 
+describe('should report regular expression errors to user ', () => {
+    it('should report mismatched parenthesis', () => {
+        const instruction = 'description regex matches /hello(/';
+        const filterOrError = new DescriptionField().createFilterOrErrorMessage(instruction);
+        expect(filterOrError.error).toEqual(
+            'Error: Parsing regular expression. The error message was: "SyntaxError: Invalid regular expression: /hello(/: Unterminated group"',
+        );
+    });
+});
+
 describe('explains regular sub-string searches', () => {
     it('should explain simple string search', () => {
         const instruction = 'description includes hello';