Repository: obsidian-tasks-group/obsidian-tasks
Pull request #2128 (2023-07-14 21:01:53)

feat: query error messages now show problem line
<!--- Provide a general summary of your changes in the Title above -->

# Description

All error messages from parsing task query blocks now include the actual problem line.

## Motivation and Context

It was previously sometimes hard to figure out which line in a long query block was causing the problem.

## How has this been tested?

- Manually, by testing it with various errors
- Adding new Jest tests

## Screenshots (if appropriate)

The text in red has been added:

![image](https://github.com/obsidian-tasks-group/obsidian-tasks/assets/4840096/19498166-66a2-48e1-bbb9-45459d15f3d0)


## 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] **Refactor** (prefix: `refactor` - non-breaking change which only improves the design or structure of existing code, and making no changes to its external behaviour)
- [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`.
- [ ] 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/Query.ts b/src/Query/Query.ts
index 5550a193..a1fd0ba8 100644
--- a/src/Query/Query.ts
+++ b/src/Query/Query.ts
@@ -62,7 +62,7 @@ export class Query implements IQuery {
                     case this.parseFilter(line):
                         break;
                     default:
-                        this._error = `do not understand query: ${line}`;
+                        this.setError('do not understand query', line);
                 }
             });
     }
@@ -172,6 +172,10 @@ export class Query implements IQuery {
         return this._error;
     }
 
+    private setError(message: string, line: string) {
+        this._error = `${message}:\n${line}`;
+    }
+
     public applyQueryToTasks(tasks: Task[]): QueryResult {
         try {
             this.filters.forEach((filter) => {
@@ -239,7 +243,7 @@ export class Query implements IQuery {
                     this._layoutOptions.hideTags = hide;
                     break;
                 default:
-                    this._error = 'do not understand hide/show option';
+                    this.setError('do not understand hide/show option', line);
             }
         }
     }
@@ -247,8 +251,11 @@ export class Query implements IQuery {
     private parseFilter(line: string) {
         const filterOrError = FilterParser.parseFilter(line);
         if (filterOrError != null) {
-            if (filterOrError.filter) this._filters.push(filterOrError.filter);
-            else this._error = filterOrError.error;
+            if (filterOrError.filter) {
+                this._filters.push(filterOrError.filter);
+            } else {
+                this.setError(filterOrError.error ?? 'Unknown error', line);
+            }
             return true;
         }
         return false;
@@ -257,7 +264,7 @@ export class Query implements IQuery {
     private parseLimit({ line }: { line: string }): void {
         const limitMatch = line.match(this.limitRegexp);
         if (limitMatch === null) {
-            this._error = 'do not understand query limit';
+            this.setError('do not understand query limit', line);
             return;
         }
 


Test patch:
diff --git a/tests/Query.test.ts b/tests/Query.test.ts
index 0a83a97b..a370d80e 100644
--- a/tests/Query.test.ts
+++ b/tests/Query.test.ts
@@ -439,6 +439,39 @@ describe('Query parsing', () => {
         expect(new Query({ source: 'group by status.name' }).grouping[0].property).toEqual('status.name');
         expect(new Query({ source: 'group by status.type' }).grouping[0].property).toEqual('status.type');
     });
+
+    describe('should include instruction in parsing error messages', () => {
+        function getQueryError(source: string) {
+            return new Query({ source: source }).error;
+        }
+
+        it('for invalid 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,
+            );
+        });
+
+        it('for invalid sort by', () => {
+            const source = 'sort by nonsense';
+            expect(getQueryError(source)).toEqual('do not understand query:\n' + source);
+        });
+
+        it('for invalid group by', () => {
+            const source = 'group by nonsense';
+            expect(getQueryError(source)).toEqual('do not understand query:\n' + source);
+        });
+
+        it('for invalid hide', () => {
+            const source = 'hide nonsense';
+            expect(getQueryError(source)).toEqual('do not understand query:\n' + source);
+        });
+
+        it('for unknown instruction', () => {
+            const source = 'spaghetti';
+            expect(getQueryError(source)).toEqual('do not understand query:\n' + source);
+        });
+    });
 });
 
 describe('Query', () => {