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

Issue:
Behavior change in handling `\` at the very end of `tasks` queries [in Live Preview, due to Obsidian unintended change]
### Please check that this issue hasn't been reported before.

- [X] I searched previous [Bug Reports](https://github.com/obsidian-tasks-group/obsidian-tasks/issues?q=is%3Aopen+is%3Aissue+label%3A%22type%3A+bug%22+sort%3Acomments-desc) didn't find any similar reports.

### Steps to reproduce

I recently updated Obsidian Tasks after few months of not updating, and lots of my queries have started returning nearly every task in my vault. Queries which had a final `\`, e.g. in the "leaky" query below, would slow down Obsidian significantly and list all tasks in the vault. (The reason I often have trailing `\` in queries is that I tend to mix/match query snippets and keeping the trailing `\` is robust to changes in that query, e.g. moving that "clause" up or down, or elsewhere).

I found that removing the `\` at the very end of the query restored functionality to what I expected from before updating, properly filtering tasks.

I would say tentatively that this apparent "regression" has cropped up in one of the updates in the past ~six months. If there's an easy methodology for me to quickly "bisect" Tasks plugin versions to narrow down the exact release this happened on, or Obsidian Sync logs I'm unaware of, please let me know. My Sync history only shows in the manifest my recent update to 7.10.2, then updating to 7.11.1, so I can't exactly recall which version I updated *from* when this regression crept in.

#### Simple reproduction

1. Create a new vault
2. Enable community plugins and install Tasks
3. Create two files, `one.md` and `two.md` with the following contents
4. **Switch to Live Preview** (_tested in Obsidian 1.7.4 - this line added by @claremacrae_)
5. Note the difference between the "leaky" and "correct" query in `two.md`. The only difference is the trailing slash. The leaky query "incorrectly" sees the task in `one.md`.
- [ ] (Not yet done by myself) To find the version in which the regression appeared, install progressively older versions of the plugin until slash handling in the "leaky" query is handled the same as in the new "correct" query

##### `one.md`

``````Markdown
# One

## Task

- [ ] one

``````

##### `two.md`

``````Markdown
# Two

## Task

- [ ] two

## Leaky query (trailing slash)

```tasks
( path includes {{ query.file.path }} )\
```

## Correct query (no trailing slash)

```tasks
( path includes {{ query.file.path }} )
```

``````

### Expected Behavior

Queries with trailing `\` should still function properly.

### Current behaviour

Queries with trailing `\` fetch everything in the vault instead of filtering as expected.

### Which Operating Systems are you using?

- [X] Android
- [X] iPhone/iPad
- [ ] Linux
- [ ] macOS
- [X] Windows

### Obsidian Version

1.7.4

### Tasks Plugin Version

7.11.1

### Checks

- [x] I have tried it with all other plugins disabled and the error still occurs

### Possible solution

- [ ] Verify reproduction of this behavior
- [ ] Determine whether this is intended behavior. If so, consider communicating it in docs. If not, proceed...
- [ ] Determine the version in which the behavior change was introduced and find the PR that changed it
- [ ] Consider implementing a "fix" for this "regression" if deemed as such
- [ ] Add a test to the test suite that checks the case of "backslash is the final character in the query before closing backticks"

Pull request:
fix: '\' at end of last line in query no longer discards the instruction
# 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] **Bug fix** (prefix: `fix` - non-breaking change which fixes an issue)
    - Fixes #3137

## Description

This works around an issue found with Obsidian 1.7.4, that in Live Preview, Obsidian eats the final end-of-line character in code blocks.

In the Tasks plugin, the line continuation code depended on there being a final end-of-line character at the end of the query text in order to allow users to place dangling `\` markers on the last line in the query.

That problem was logged in #3137.

This change ensures that there is always a last end-of-line character at the end of every query, regardless of its location. This will be useful if/when Tasks allows users to run searches via its API - as requested in #2459.

## Motivation and Context

Fixez #3137.

## How has this been tested?

- With new tests
- And exploratory testing

## Screenshots (if appropriate)

I used this test file:

~~~
# asdfasdf

- [ ] #task asdfasdfads

```tasks
limit 10
ignore global query
explain
( path includes {{ query.file.path }} )\
```
~~~

And arranged the views as:

**Source Mode | Live Preview | Reading Mode**

### Before the fix


![image](https://github.com/user-attachments/assets/d5023386-0999-4e4a-82dd-d523ff8353b3)

### After the fix

![image](https://github.com/user-attachments/assets/9836ad66-f36f-4e24-9d2f-84e6528e143b)


## 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 follows 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/Scanner.ts b/src/Query/Scanner.ts
index 582fe4c3..092b2c5a 100644
--- a/src/Query/Scanner.ts
+++ b/src/Query/Scanner.ts
@@ -69,7 +69,15 @@ export function continueLines(input: string): Statement[] {
 
     let currentStatementRaw = '';
     let currentStatementProcessed = '';
-    for (const inputLine of input.split('\n')) {
+
+    // See https://github.com/obsidian-tasks-group/obsidian-tasks/issues/3137.
+    //      Issue #3137 revealed that if the last line of the query ended in
+    //      a backslash, this function returned without saved the final
+    //      instruction.
+    //      The simplest way to prevent this is to add an extra end-of-line
+    //      to the end of the query, which will get ignored when not needed:
+    const inputWithGuaranteedFinalEOL = input + '\n';
+    for (const inputLine of inputWithGuaranteedFinalEOL.split('\n')) {
         const adjustedLine = adjustLine(inputLine, continuePreviousLine);
         if (continuePreviousLine) {
             currentStatementRaw += '\n' + inputLine;


Test patch:
diff --git a/tests/Query/Query.test.ts b/tests/Query/Query.test.ts
index 741cf933..86216704 100644
--- a/tests/Query/Query.test.ts
+++ b/tests/Query/Query.test.ts
@@ -556,6 +556,12 @@ describe('Query parsing', () => {
         expect(new Query('short mode\nfull').queryLayoutOptions.shortMode).toEqual(false);
     });
 
+    it('should cope with missing end-of-line character in query ending with continuation character - #3137', () => {
+        const query = new Query('path includes query.md \\');
+        expect(query.filters.length).toEqual(1);
+        expect(query.filters[0].instruction).toEqual('path includes query.md');
+    });
+
     describe('should include instruction in parsing error messages', () => {
         function getQueryError(source: string) {
             return new Query(source, new TasksFile('Example Path.md')).error;
diff --git a/tests/Query/Scanner.test.ts b/tests/Query/Scanner.test.ts
index 1b80e27e..42ba9ac1 100644
--- a/tests/Query/Scanner.test.ts
+++ b/tests/Query/Scanner.test.ts
@@ -150,6 +150,11 @@ describe('continue_lines', () => {
         expect(continueLinesFlattened(text)).toEqual('description includes one two three four five six');
     });
 
+    it('should preserve last instruction in query that ends in a continuation line = #3137', () => {
+        const text = 'due today \\';
+        expect(continueLinesFlattened(text)).toEqual('due today');
+    });
+
     it('visualise continue_lines', () => {
         const output = `
 input: