diff --git a/resources/sample_vaults/Tasks-Demo/Functions/Custom Filters - Demo.md b/resources/sample_vaults/Tasks-Demo/Functions/Custom Filters - Demo.md index f6c5d5b7ef..ce08ca65b7 100644 --- a/resources/sample_vaults/Tasks-Demo/Functions/Custom Filters - Demo.md +++ b/resources/sample_vaults/Tasks-Demo/Functions/Custom Filters - Demo.md @@ -43,3 +43,34 @@ filename includes Custom Filters - Demo # Infer tag from heading filter by function task.heading.includes('#context/home') || task.tags.find( (tag) => tag === '#context/home' ) && true || false ``` + +## Demonstrate Error Handling + +### Parsing Errors + +This section demonstrate how Tasks handles errors when reading `filter by function` instructions. + +#### SyntaxError + +```tasks +filter by function \ + task.due.formatAsDate( +``` + +### Evaluation Errors + +This section demonstrate how Tasks handles when evaluating `filter by function` instructions during searches. + +#### ReferenceError + +```tasks +filter by function \ + hello +``` + +#### Non-existent task field + +```tasks +filter by function \ + task.nonExistentField +``` diff --git a/src/Query/Query.ts b/src/Query/Query.ts index b3ff5c2799..ef1d41787a 100644 --- a/src/Query/Query.ts +++ b/src/Query/Query.ts @@ -246,11 +246,15 @@ ${source}`; } private setError(message: string, statement: Statement) { + this._error = Query.generateErrorMessage(statement, message); + } + + private static generateErrorMessage(statement: Statement, message: string) { if (statement.allLinesIdentical()) { - this._error = `${message} + return `${message} Problem line: "${statement.rawInstruction}"`; } else { - this._error = `${message} + return `${message} Problem statement: ${statement.explainStatement(' ')} `; @@ -265,10 +269,16 @@ ${statement.explainStatement(' ')} this.debug(`Executing query: ${this.formatQueryForLogging()}`); const searchInfo = new SearchInfo(this.tasksFile, tasks); + + // Custom filter (filter by function) does not report the instruction line in any exceptions, + // for performance reasons. So we keep track of it here. + let possiblyBrokenStatement: Statement | undefined = undefined; try { this.filters.forEach((filter) => { + possiblyBrokenStatement = filter.statement; tasks = tasks.filter((task) => filter.filterFunction(task, searchInfo)); }); + possiblyBrokenStatement = undefined; const { debugSettings } = getSettings(); const tasksSorted = debugSettings.ignoreSortInstructions ? tasks : Sort.by(this.sorting, tasks, searchInfo); @@ -283,7 +293,12 @@ ${statement.explainStatement(' ')} return new QueryResult(taskGroups, tasksSorted.length); } catch (e) { const description = 'Search failed'; - return QueryResult.fromError(errorMessageForException(description, e)); + let message = errorMessageForException(description, e); + + if (possiblyBrokenStatement) { + message = Query.generateErrorMessage(possiblyBrokenStatement, message); + } + return QueryResult.fromError(message); } } diff --git a/tests/Query/Query.test.ts b/tests/Query/Query.test.ts index 74fa93a035..8e9551d2fe 100644 --- a/tests/Query/Query.test.ts +++ b/tests/Query/Query.test.ts @@ -1542,7 +1542,8 @@ describe('Query', () => { describe('error handling', () => { it('should catch an exception that occurs during searching', () => { // Arrange - const source = 'filter by function wibble'; + const source = `filter by function \\ +wibble`; const query = new Query(source); const queryUpper = new Query(source.toUpperCase()); const task = TaskBuilder.createFullyPopulatedTask(); @@ -1553,10 +1554,26 @@ describe('Query', () => { // Assert expect(queryResult.searchErrorMessage).toEqual( - 'Error: Search failed.\nThe error message was:\n "ReferenceError: wibble is not defined"', + `Error: Search failed. +The error message was: + "ReferenceError: wibble is not defined" +Problem statement: + filter by function \\ + wibble + => + filter by function wibble +`, ); expect(queryResultUpper.searchErrorMessage).toEqual( - 'Error: Search failed.\nThe error message was:\n "ReferenceError: WIBBLE is not defined"', + `Error: Search failed. +The error message was: + "ReferenceError: WIBBLE is not defined" +Problem statement: + FILTER BY FUNCTION \\ + WIBBLE + => + FILTER BY FUNCTION WIBBLE +`, ); }); });