Repository: obsidian-tasks-group/obsidian-tasks
Pull request #2377 (2023-10-29 08:51:11)

feat: Custom grouping accesses Query Properties directly, without Placeholders
<!--- Provide a general summary of your changes in the Title above -->

# Description

`group by function` lines can now use `query.file.path` directly just like `task.file.path`.

In custom groups, there is no longer a requirement to use quotes and placeholders to access query properties.

## Motivation and Context

To simplify the use of query properties in custom grouping code.



## How has this been tested?

- Running existing tests
- Adding new tests
- Manually testing in the same vault, with various queries such as:

~~~
```tasks
limit groups 1

group by root
group by folder
group by function task.file.folder.replace(query.file.root, '')
```
~~~

<!--- 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)

The bits in red on the left have been removed, as `query.file.path`  etc can all now be used as variables directly in `filter by function`.

![image](https://github.com/obsidian-tasks-group/obsidian-tasks/assets/4840096/c52778b9-b957-4db1-a550-89d295976903)


## 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)
- [x] **Documentation** (prefix: `docs` - improvements to any documentation content **for users**)

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`.
- [x] My change requires a change to the documentation.
- [x] 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/docs/Introduction.md b/docs/Introduction.md
index c9a77d49..a0f24eef 100644
--- a/docs/Introduction.md
+++ b/docs/Introduction.md
@@ -13,13 +13,14 @@ _In recent [releases](https://github.com/obsidian-tasks-group/obsidian-tasks/rel
     Move the older ones down to the top of the comment block below...
 -->
 
+- X.Y.Z: 🔥 Enable [[Custom Grouping|custom grouping]] to use [[Query Properties|query properties]] directly - no placeholders required.
+
+<!--
 - 5.0.0: 🔥 Add [[Line Continuations|line continuations]].
   - **Warning**: This is a [[Line Continuations#Appendix Updating pre-5.0.0 searches with trailing backslashes|potentially breaking change]] if you search for backslash (`\`) characters.
 - 5.0.0: 🔥 Document [[Comments#Inline comments|inline comments]]
 - 5.0.0: 🔥 Document [[Recurring Tasks and Custom Statuses|recurring tasks and custom statuses]]
 - 5.0.0: 🔥 Add new Help pages [[Known Limitations]] and [[Breaking Changes]].
-
-<!--
 - 4.9.0: 🔥 Add [[Task Properties|task properties]] `task.priorityNameGroupText` and `task.status.typeGroupText`, for example:
   - `group by function task.priorityNameGroupText + ': ' + task.status.typeGroupText`
 - 4.9.0: 🔥 Add [[Task Properties#Values in TasksDate Properties|task date properties]] for categorising dates, for example:
diff --git a/docs/Queries/Grouping.md b/docs/Queries/Grouping.md
index 7d2735e1..84133960 100644
--- a/docs/Queries/Grouping.md
+++ b/docs/Queries/Grouping.md
@@ -747,23 +747,20 @@ group by function task.file.path
 - Like 'group by path' but includes the file extension.
 
 ```javascript
-group by function task.file.path.replace('{{query.file.folder}}', '')
+group by function task.file.path.replace(query.file.folder, '')
 ```
 
 - Group by the task's file path, but remove the query's folder from the group.
 - For tasks in the query's folder or a sub-folder, this is a nice way of seeing shortened paths.
-- Note that the placeholder text is expanded to a raw string, so needs to be inside quotes.
-- This is provided to give ideas: it's a bit of a lazy implementation, as it doesn't check that `'{{query.file.folder}}'` is at the start of the line.
+- This is provided to give ideas: it's a bit of a lazy implementation, as it doesn't check that `query.file.folder` is at the start of the line.
 
 <!-- placeholder to force blank line after included text --><!-- endInclude -->
 
-Since Tasks 4.7.0, the query's file path can be used in custom groups.
+Since Tasks X.Y.Z, the query's file path can be used conveniently in custom groups.
 
-- It must be quoted:
-  - `'{{query.file.path}}'` or
-  - `'{{query.file.pathWithoutExtension}}'` (since Tasks 4.8.0)
-- Beware if using placeholder text in regular expressions: Any special characters in filenames would need to be escaped.
-- Useful reading: [[Query Properties]] and [[Placeholders]].
+- `query.file.path` or
+- `query.file.pathWithoutExtension`
+- Useful reading: [[Query Properties]].
 
 ### Root
 
@@ -784,11 +781,10 @@ group by function task.file.root
 
 <!-- placeholder to force blank line after included text --><!-- endInclude -->
 
-Since Tasks 4.7.0, the query's file root can be used in custom groups.
+Since Tasks X.Y.Z, the query's file root can be used conveniently in custom groups.
 
-- It must be quoted: `'{{query.file.root}}'`
-- Beware if using placeholder text in regular expressions: Any special characters in filenames would need to be escaped.
-- Useful reading: [[Query Properties]] and [[Placeholders]].
+- `query.file.root`
+- Useful reading: [[Query Properties]].
 
 ### Folder
 
@@ -817,11 +813,10 @@ group by function task.file.folder.slice(0, -1).split('/').pop() + '/'
 
 <!-- placeholder to force blank line after included text --><!-- endInclude -->
 
-Since Tasks 4.7.0, the query's folder can be used in custom groups.
+Since Tasks X.Y.Z, the query's folder can be used conveniently in custom groups.
 
-- It must be quoted: `'{{query.file.folder}}'`
-- Beware if using placeholder text in regular expressions: Any special characters in filenames would need to be escaped.
-- Useful reading: [[Query Properties]] and [[Placeholders]].
+- `query.file.folder`
+- Useful reading: [[Query Properties]].
 
 ### File Name
 
@@ -846,13 +841,11 @@ group by function task.file.filenameWithoutExtension + (task.hasHeading ? (' > '
 
 <!-- placeholder to force blank line after included text --><!-- endInclude -->
 
-Since Tasks 4.7.0, the query's file name can be used in custom groups.
+Since Tasks X.Y.Z, the query's file name can be used conveniently in custom groups.
 
-- It must be quoted:
-  - `'{{query.file.filename}}'` or
-  - `'{{query.file.filenameWithoutExtension}}'` (since Tasks 4.8.0)
-- Beware if using placeholder text in regular expressions: Any special characters in filenames would need to be escaped.
-- Useful reading: [[Query Properties]] and [[Placeholders]].
+- `query.file.filename` or
+- `query.file.filenameWithoutExtension`
+- Useful reading: [[Query Properties]].
 
 ### Backlink
 
diff --git a/docs/Quick Reference.md b/docs/Quick Reference.md
index 83076970..3e8b04e9 100644
--- a/docs/Quick Reference.md
+++ b/docs/Quick Reference.md
@@ -8,57 +8,57 @@ aliases:
 
 This table summarizes the filters and other options available inside a `tasks` block.
 
-| [[Filters]]                                                                                                                                                                                                                                                                                                                                           | [[Sorting\|Sort]]                           | [[Grouping\|Group]]    | [[Layout\|Display]]    | [[About Scripting\|Scripting]]                                               |
-| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | ---------------------- | ---------------------- | ---------------------------------------------------------------------------- |
-| **[[Filters#Filters for Dates in Tasks\|Status]]**                                                                                                                                                                                                                                                                                                    |                                             |                        |                        |                                                                              |
-| `done`<br>`not done`                                                                                                                                                                                                                                                                                                                                  | `sort by status`                            | `group by status`      |                        | `task.isDone`                                                                |
-| `status.name (includes, does not include) <string>`<br>`status.name (regex matches, regex does not match) /regex/i`                                                                                                                                                                                                                                   | `sort by status.name`                       | `group by status.name` |                        | `task.status.name`                                                           |
-| `status.type (is, is not) (TODO, DONE, IN_PROGRESS, CANCELLED, NON_TASK)`                                                                                                                                                                                                                                                                             | `sort by status.type`                       | `group by status.type` |                        | `task.status.type`<br>`task.status.typeGroupText`                            |
-|                                                                                                                                                                                                                                                                                                                                                       |                                             |                        |                        | `task.status.symbol`                                                         |
-|                                                                                                                                                                                                                                                                                                                                                       |                                             |                        |                        | `task.status.nextSymbol`                                                     |
-| **[[Filters#Filters for Dates in Tasks\|Dates]]**                                                                                                                                                                                                                                                                                                     |                                             |                        |                        |                                                                              |
-| `done (on, before, after, on or before, on or after) <date>`<br>`done (in, before, after, in or before, in or after) ...`<br>`... YYYY-MM-DD YYYY-MM-DD`<br>`... (last, this, next) (week, month, quarter, year)`<br>`... (YYYY-Www,YYYY-mm, YYYY-Qq, YYYY)`<br>`has done date`<br>`no  done date`<br>`done date is invalid`                          | `sort by done`                              | `group by done`        | `hide done date`       | `task.done`                                                                  |
-| `created (on, before, after, on or before, on or after) <date>`<br>`created (in, before, after, in or before, in or after) ...`<br>`... YYYY-MM-DD YYYY-MM-DD`<br>`... (last, this, next) (week, month, quarter, year)`<br>`... (YYYY-Www,YYYY-mm, YYYY-Qq, YYYY)`<br>`has created date`<br>`no  created date`<br>`created date is invalid`           | `sort by created`                           | `group by created`     | `hide created date`    | `task.created`                                                               |
-| `starts (on, before, after, on or before, on or after) <date>`<br>`starts (in, before, after, in or before, in or after) ...`<br>`... YYYY-MM-DD YYYY-MM-DD`<br>`... (last, this, next) (week, month, quarter, year)`<br>`... (YYYY-Www,YYYY-mm, YYYY-Qq, YYYY)`<br>`has start date`<br>`no  start date`<br>`start date is invalid`                   | `sort by start`                             | `group by start`       | `hide start date`      | `task.start`                                                                 |
-| `scheduled (on, before, after, on or before, on or after) <date>`<br>`scheduled (in, before, after, in or before, in or after) ...`<br>`... YYYY-MM-DD YYYY-MM-DD`<br>`... (last, this, next) (week, month, quarter, year)`<br>`... (YYYY-Www,YYYY-mm, YYYY-Qq, YYYY)`<br>`has scheduled date`<br>`no  scheduled date`<br>`scheduled date is invalid` | `sort by scheduled`                         | `group by scheduled`   | `hide scheduled date`  | `task.scheduled`                                                             |
-| `due (on, before, after, on or before, on or after) <date>`<br>`due (in, before, after, in or before, in or after) ...`<br>`... YYYY-MM-DD YYYY-MM-DD`<br>`... (last, this, next) (week, month, quarter, year)`<br>`... (YYYY-Www,YYYY-mm, YYYY-Qq, YYYY)`<br>`has due date`<br>`no  due date`<br>`due date is invalid`                               | `sort by due`                               | `group by due`         | `hide due date`        | `task.due`                                                                   |
-| `happens (on, before, after, on or before, on or after) <date>`<br>`happens (in, before, after, in or before, in or after) ...`<br>`... YYYY-MM-DD YYYY-MM-DD`<br>`... (last, this, next) (week, month, quarter, year)`<br>`... (YYYY-Www,YYYY-mm, YYYY-Qq, YYYY)`<br>`has happens date`<br>`no  happens date`                                        | `sort by happens`                           | `group by happens`     |                        | `task.happens`                                                               |
-| **[[Filters#Recurrence\|Recurrence]]**                                                                                                                                                                                                                                                                                                                |                                             |                        |                        |                                                                              |
-| `is recurring`<br>`is not recurring`                                                                                                                                                                                                                                                                                                                  | `sort by recurring`                         | `group by recurring`   |                        | `task.isRecurring`                                                           |
-| `recurrence (includes, does not include) <string>`<br>`recurrence (regex matches, regex does not match) /regex/i`                                                                                                                                                                                                                                     |                                             | `group by recurrence`  | `hide recurrence rule` | `task.recurrenceRule`                                                        |
-| **[[Filters#Priority\|Priority]]** and **[[Urgency\|urgency]]**                                                                                                                                                                                                                                                                                       |                                             |                        |                        |                                                                              |
-| `priority is (above, below, not)? (lowest, low, none, medium, high, highest)`                                                                                                                                                                                                                                                                         | `sort by priority`                          | `group by priority`    | `hide priority`        | `task.priorityName`<br>`task.priorityNameGroupText`<br>`task.priorityNumber` |
-|                                                                                                                                                                                                                                                                                                                                                       | `sort by urgency`                           | `group by urgency`     | `show urgency`         | `task.urgency`                                                               |
-| **[[Filters#Filters for File Properties\|File properties]]**                                                                                                                                                                                                                                                                                          |                                             |                        |                        |                                                                              |
-| `path (includes, does not include) <path>`<br>`path (regex matches, regex does not match) /regex/i`<br>`path includes {{query.file.path}}`<br>`path includes {{query.file.pathWithoutExtension}}`                                                                                                                                                     | `sort by path`                              | `group by path`        |                        | `task.file.path`<br>`task.file.pathWithoutExtension`                         |
-| `root (includes, does not include) <root>`<br>`root (regex matches, regex does not match) /regex/i`<br>`root includes {{query.file.root}}`                                                                                                                                                                                                            |                                             | `group by root`        |                        | `task.file.root`                                                             |
-| `folder (includes, does not include) <folder>`<br>`folder (regex matches, regex does not match) /regex/i`<br>`folder includes {{query.file.folder}}`                                                                                                                                                                                                  |                                             | `group by folder`      |                        | `task.file.folder`                                                           |
-| `filename (includes, does not include) <filename>`<br>`filename (regex matches, regex does not match) /regex/i`<br>`filename includes {{query.file.filename}}`<br>`filename includes {{query.file.filenameWithoutExtension}}`                                                                                                                         | `sort by filename`                          | `group by filename`    |                        | `task.file.filename`<br>`task.file.filenameWithoutExtension`                 |
-| `heading (includes, does not include) <string>`<br>`heading (regex matches, regex does not match) /regex/i`                                                                                                                                                                                                                                           | `sort by heading`                           | `group by heading`     |                        | `task.hasHeading`<br>`task.heading`                                          |
-|                                                                                                                                                                                                                                                                                                                                                       |                                             | `group by backlink`    | `hide backlink`        |                                                                              |
-| **[[Filters#Description\|Description]]**, **[[Filters#Tags\|Tags]]** and other odds and ends                                                                                                                                                                                                                                                          |                                             |                        |                        |                                                                              |
-| `description (includes, does not include) <string>`<br>`description (regex matches, regex does not match) /regex/i`                                                                                                                                                                                                                                   | `sort by description`                       |                        |                        | `task.description`<br>`task.descriptionWithoutTags`                          |
-| `has tags`<br>`no tags`<br>`tag (includes, does not include) <tag>`<br>`tags (include, do not include) <tag>`<br>`tag (regex matches, regex does not match) /regex/i`<br>`tags (regex matches, regex does not match) /regex/i`                                                                                                                        | `sort by tag`<br>`sort by tag <tag_number>` | `group by tags`        | `hide tags`            | `task.tags`                                                                  |
-|                                                                                                                                                                                                                                                                                                                                                       |                                             |                        |                        | `task.originalMarkdown`                                                      |
-| **[[About Scripting\|Scripting]]**                                                                                                                                                                                                                                                                                                                    |                                             |                        |                        |                                                                              |
-| `filter by function`                                                                                                                                                                                                                                                                                                                                  |                                             | `group by function`    |                        |                                                                              |
-| **[[Combining Filters]]**                                                                                                                                                                                                                                                                                                                             |                                             |                        |                        |                                                                              |
-| `(filter 1) AND (filter 2)`                                                                                                                                                                                                                                                                                                                           |                                             |                        |                        |                                                                              |
-| `(filter 1) OR (filter 2)`                                                                                                                                                                                                                                                                                                                            |                                             |                        |                        |                                                                              |
-| `NOT (filter 1)`                                                                                                                                                                                                                                                                                                                                      |                                             |                        |                        |                                                                              |
-| `(filter 1) XOR (filter 2)`                                                                                                                                                                                                                                                                                                                           |                                             |                        |                        |                                                                              |
-| `(filter 1) AND NOT (filter 2)`                                                                                                                                                                                                                                                                                                                       |                                             |                        |                        |                                                                              |
-| `(filter 1) OR NOT (filter 2)`                                                                                                                                                                                                                                                                                                                        |                                             |                        |                        |                                                                              |
-| `(filter 1) AND ((filter 2) OR (filter 3))`                                                                                                                                                                                                                                                                                                           |                                             |                        |                        |                                                                              |
-| **Other Filter Options**                                                                                                                                                                                                                                                                                                                              |                                             |                        |                        |                                                                              |
-| `exclude sub-items`                                                                                                                                                                                                                                                                                                                                   |                                             |                        |                        |                                                                              |
-| `limit to <number> tasks`<br>`limit <number>`                                                                                                                                                                                                                                                                                                         |                                             |                        |                        |                                                                              |
-| `limit groups to <number> tasks`<br>`limit groups <number>`                                                                                                                                                                                                                                                                                           |                                             |                        |                        |                                                                              |
-| **Other Layout Options**                                                                                                                                                                                                                                                                                                                              |                                             |                        |                        |                                                                              |
-| `hide edit button`                                                                                                                                                                                                                                                                                                                                    |                                             |                        |                        |                                                                              |
-| `hide task count`                                                                                                                                                                                                                                                                                                                                     |                                             |                        |                        |                                                                              |
-| `short mode`                                                                                                                                                                                                                                                                                                                                          |                                             |                        |                        |                                                                              |
-| **Other Instructions**                                                                                                                                                                                                                                                                                                                                |                                             |                        |                        |                                                                              |
-| `ignore global query`                                                                                                                                                                                                                                                                                                                                 |                                             |                        |                        |                                                                              |
-| `explain`                                                                                                                                                                                                                                                                                                                                             |                                             |                        |                        |                                                                              |
-| `# comment`                                                                                                                                                                                                                                                                                                                                           |                                             |                        |                        |                                                                              |
+| [[Filters]]                                                                                                                                                                                                                                                                                                                                           | [[Sorting\|Sort]]                           | [[Grouping\|Group]]    | [[Layout\|Display]]    | [[About Scripting\|Scripting]]                                                                                                 |
+| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | ---------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
+| **[[Filters#Filters for Dates in Tasks\|Status]]**                                                                                                                                                                                                                                                                                                    |                                             |                        |                        |                                                                                                                                |
+| `done`<br>`not done`                                                                                                                                                                                                                                                                                                                                  | `sort by status`                            | `group by status`      |                        | `task.isDone`                                                                                                                  |
+| `status.name (includes, does not include) <string>`<br>`status.name (regex matches, regex does not match) /regex/i`                                                                                                                                                                                                                                   | `sort by status.name`                       | `group by status.name` |                        | `task.status.name`                                                                                                             |
+| `status.type (is, is not) (TODO, DONE, IN_PROGRESS, CANCELLED, NON_TASK)`                                                                                                                                                                                                                                                                             | `sort by status.type`                       | `group by status.type` |                        | `task.status.type`<br>`task.status.typeGroupText`                                                                              |
+|                                                                                                                                                                                                                                                                                                                                                       |                                             |                        |                        | `task.status.symbol`                                                                                                           |
+|                                                                                                                                                                                                                                                                                                                                                       |                                             |                        |                        | `task.status.nextSymbol`                                                                                                       |
+| **[[Filters#Filters for Dates in Tasks\|Dates]]**                                                                                                                                                                                                                                                                                                     |                                             |                        |                        |                                                                                                                                |
+| `done (on, before, after, on or before, on or after) <date>`<br>`done (in, before, after, in or before, in or after) ...`<br>`... YYYY-MM-DD YYYY-MM-DD`<br>`... (last, this, next) (week, month, quarter, year)`<br>`... (YYYY-Www,YYYY-mm, YYYY-Qq, YYYY)`<br>`has done date`<br>`no  done date`<br>`done date is invalid`                          | `sort by done`                              | `group by done`        | `hide done date`       | `task.done`                                                                                                                    |
+| `created (on, before, after, on or before, on or after) <date>`<br>`created (in, before, after, in or before, in or after) ...`<br>`... YYYY-MM-DD YYYY-MM-DD`<br>`... (last, this, next) (week, month, quarter, year)`<br>`... (YYYY-Www,YYYY-mm, YYYY-Qq, YYYY)`<br>`has created date`<br>`no  created date`<br>`created date is invalid`           | `sort by created`                           | `group by created`     | `hide created date`    | `task.created`                                                                                                                 |
+| `starts (on, before, after, on or before, on or after) <date>`<br>`starts (in, before, after, in or before, in or after) ...`<br>`... YYYY-MM-DD YYYY-MM-DD`<br>`... (last, this, next) (week, month, quarter, year)`<br>`... (YYYY-Www,YYYY-mm, YYYY-Qq, YYYY)`<br>`has start date`<br>`no  start date`<br>`start date is invalid`                   | `sort by start`                             | `group by start`       | `hide start date`      | `task.start`                                                                                                                   |
+| `scheduled (on, before, after, on or before, on or after) <date>`<br>`scheduled (in, before, after, in or before, in or after) ...`<br>`... YYYY-MM-DD YYYY-MM-DD`<br>`... (last, this, next) (week, month, quarter, year)`<br>`... (YYYY-Www,YYYY-mm, YYYY-Qq, YYYY)`<br>`has scheduled date`<br>`no  scheduled date`<br>`scheduled date is invalid` | `sort by scheduled`                         | `group by scheduled`   | `hide scheduled date`  | `task.scheduled`                                                                                                               |
+| `due (on, before, after, on or before, on or after) <date>`<br>`due (in, before, after, in or before, in or after) ...`<br>`... YYYY-MM-DD YYYY-MM-DD`<br>`... (last, this, next) (week, month, quarter, year)`<br>`... (YYYY-Www,YYYY-mm, YYYY-Qq, YYYY)`<br>`has due date`<br>`no  due date`<br>`due date is invalid`                               | `sort by due`                               | `group by due`         | `hide due date`        | `task.due`                                                                                                                     |
+| `happens (on, before, after, on or before, on or after) <date>`<br>`happens (in, before, after, in or before, in or after) ...`<br>`... YYYY-MM-DD YYYY-MM-DD`<br>`... (last, this, next) (week, month, quarter, year)`<br>`... (YYYY-Www,YYYY-mm, YYYY-Qq, YYYY)`<br>`has happens date`<br>`no  happens date`                                        | `sort by happens`                           | `group by happens`     |                        | `task.happens`                                                                                                                 |
+| **[[Filters#Recurrence\|Recurrence]]**                                                                                                                                                                                                                                                                                                                |                                             |                        |                        |                                                                                                                                |
+| `is recurring`<br>`is not recurring`                                                                                                                                                                                                                                                                                                                  | `sort by recurring`                         | `group by recurring`   |                        | `task.isRecurring`                                                                                                             |
+| `recurrence (includes, does not include) <string>`<br>`recurrence (regex matches, regex does not match) /regex/i`                                                                                                                                                                                                                                     |                                             | `group by recurrence`  | `hide recurrence rule` | `task.recurrenceRule`                                                                                                          |
+| **[[Filters#Priority\|Priority]]** and **[[Urgency\|urgency]]**                                                                                                                                                                                                                                                                                       |                                             |                        |                        |                                                                                                                                |
+| `priority is (above, below, not)? (lowest, low, none, medium, high, highest)`                                                                                                                                                                                                                                                                         | `sort by priority`                          | `group by priority`    | `hide priority`        | `task.priorityName`<br>`task.priorityNameGroupText`<br>`task.priorityNumber`                                                   |
+|                                                                                                                                                                                                                                                                                                                                                       | `sort by urgency`                           | `group by urgency`     | `show urgency`         | `task.urgency`                                                                                                                 |
+| **[[Filters#Filters for File Properties\|File properties]]**                                                                                                                                                                                                                                                                                          |                                             |                        |                        |                                                                                                                                |
+| `path (includes, does not include) <path>`<br>`path (regex matches, regex does not match) /regex/i`<br>`path includes {{query.file.path}}`<br>`path includes {{query.file.pathWithoutExtension}}`                                                                                                                                                     | `sort by path`                              | `group by path`        |                        | `task.file.path`<br>`task.file.pathWithoutExtension`<br>`query.file.path`<br>`query.file.pathWithoutExtension`                 |
+| `root (includes, does not include) <root>`<br>`root (regex matches, regex does not match) /regex/i`<br>`root includes {{query.file.root}}`                                                                                                                                                                                                            |                                             | `group by root`        |                        | `task.file.root`<br>`query.file.root`                                                                                          |
+| `folder (includes, does not include) <folder>`<br>`folder (regex matches, regex does not match) /regex/i`<br>`folder includes {{query.file.folder}}`                                                                                                                                                                                                  |                                             | `group by folder`      |                        | `task.file.folder`<br>`query.file.folder`                                                                                      |
+| `filename (includes, does not include) <filename>`<br>`filename (regex matches, regex does not match) /regex/i`<br>`filename includes {{query.file.filename}}`<br>`filename includes {{query.file.filenameWithoutExtension}}`                                                                                                                         | `sort by filename`                          | `group by filename`    |                        | `task.file.filename`<br>`task.file.filenameWithoutExtension`<br>`query.file.filename`<br>`query.file.filenameWithoutExtension` |
+| `heading (includes, does not include) <string>`<br>`heading (regex matches, regex does not match) /regex/i`                                                                                                                                                                                                                                           | `sort by heading`                           | `group by heading`     |                        | `task.hasHeading`<br>`task.heading`                                                                                            |
+|                                                                                                                                                                                                                                                                                                                                                       |                                             | `group by backlink`    | `hide backlink`        |                                                                                                                                |
+| **[[Filters#Description\|Description]]**, **[[Filters#Tags\|Tags]]** and other odds and ends                                                                                                                                                                                                                                                          |                                             |                        |                        |                                                                                                                                |
+| `description (includes, does not include) <string>`<br>`description (regex matches, regex does not match) /regex/i`                                                                                                                                                                                                                                   | `sort by description`                       |                        |                        | `task.description`<br>`task.descriptionWithoutTags`                                                                            |
+| `has tags`<br>`no tags`<br>`tag (includes, does not include) <tag>`<br>`tags (include, do not include) <tag>`<br>`tag (regex matches, regex does not match) /regex/i`<br>`tags (regex matches, regex does not match) /regex/i`                                                                                                                        | `sort by tag`<br>`sort by tag <tag_number>` | `group by tags`        | `hide tags`            | `task.tags`                                                                                                                    |
+|                                                                                                                                                                                                                                                                                                                                                       |                                             |                        |                        | `task.originalMarkdown`                                                                                                        |
+| **[[About Scripting\|Scripting]]**                                                                                                                                                                                                                                                                                                                    |                                             |                        |                        |                                                                                                                                |
+| `filter by function`                                                                                                                                                                                                                                                                                                                                  |                                             | `group by function`    |                        |                                                                                                                                |
+| **[[Combining Filters]]**                                                                                                                                                                                                                                                                                                                             |                                             |                        |                        |                                                                                                                                |
+| `(filter 1) AND (filter 2)`                                                                                                                                                                                                                                                                                                                           |                                             |                        |                        |                                                                                                                                |
+| `(filter 1) OR (filter 2)`                                                                                                                                                                                                                                                                                                                            |                                             |                        |                        |                                                                                                                                |
+| `NOT (filter 1)`                                                                                                                                                                                                                                                                                                                                      |                                             |                        |                        |                                                                                                                                |
+| `(filter 1) XOR (filter 2)`                                                                                                                                                                                                                                                                                                                           |                                             |                        |                        |                                                                                                                                |
+| `(filter 1) AND NOT (filter 2)`                                                                                                                                                                                                                                                                                                                       |                                             |                        |                        |                                                                                                                                |
+| `(filter 1) OR NOT (filter 2)`                                                                                                                                                                                                                                                                                                                        |                                             |                        |                        |                                                                                                                                |
+| `(filter 1) AND ((filter 2) OR (filter 3))`                                                                                                                                                                                                                                                                                                           |                                             |                        |                        |                                                                                                                                |
+| **Other Filter Options**                                                                                                                                                                                                                                                                                                                              |                                             |                        |                        |                                                                                                                                |
+| `exclude sub-items`                                                                                                                                                                                                                                                                                                                                   |                                             |                        |                        |                                                                                                                                |
+| `limit to <number> tasks`<br>`limit <number>`                                                                                                                                                                                                                                                                                                         |                                             |                        |                        |                                                                                                                                |
+| `limit groups to <number> tasks`<br>`limit groups <number>`                                                                                                                                                                                                                                                                                           |                                             |                        |                        |                                                                                                                                |
+| **Other Layout Options**                                                                                                                                                                                                                                                                                                                              |                                             |                        |                        |                                                                                                                                |
+| `hide edit button`                                                                                                                                                                                                                                                                                                                                    |                                             |                        |                        |                                                                                                                                |
+| `hide task count`                                                                                                                                                                                                                                                                                                                                     |                                             |                        |                        |                                                                                                                                |
+| `short mode`                                                                                                                                                                                                                                                                                                                                          |                                             |                        |                        |                                                                                                                                |
+| **Other Instructions**                                                                                                                                                                                                                                                                                                                                |                                             |                        |                        |                                                                                                                                |
+| `ignore global query`                                                                                                                                                                                                                                                                                                                                 |                                             |                        |                        |                                                                                                                                |
+| `explain`                                                                                                                                                                                                                                                                                                                                             |                                             |                        |                        |                                                                                                                                |
+| `# comment`                                                                                                                                                                                                                                                                                                                                           |                                             |                        |                        |                                                                                                                                |
diff --git a/docs/Scripting/Custom Grouping.md b/docs/Scripting/Custom Grouping.md
index ba7c5e2e..d7566862 100644
--- a/docs/Scripting/Custom Grouping.md
+++ b/docs/Scripting/Custom Grouping.md
@@ -15,8 +15,9 @@ publish: true
   - `group by function task.urgency.toFixed(3)`
 - There are loads of examples in [[Grouping]].
   - Search for `group by function` in that file.
-- Find all the supported tasks properties in [[Task Properties]] and [[Quick Reference]].
+- Find all the **supported tasks properties** in [[Task Properties]] and [[Quick Reference]].
   - A number of properties are only available for custom grouping and filters, and not for built-in grouping instructions.
+- Find all the **supported query properties** in [[Query Properties]].
 - Learn a bit about how expressions work in [[Expressions]].
 
 ## Custom grouping introduction
@@ -280,7 +281,9 @@ Tasks with custom date groups, including formatting.
   - `group by function 6 * 7`
   - `group by function undefined`
 - See [[Expressions]] for more examples to try out.
-- A task, whose data you can access via all the [[Task Properties]].
+- You can use:
+  - A task, whose data you can access via all the [[Task Properties]].
+  - Some information about the file containing the query, which you can access via all the [[Query Properties]].
 - The generated text is rendered by Obsidian, so you can insert markdown characters to add formatting to your headings.
 
 ## Troubleshooting
diff --git a/docs/Scripting/Query Properties.md b/docs/Scripting/Query Properties.md
index 014bd2a3..d46a33cf 100644
--- a/docs/Scripting/Query Properties.md
+++ b/docs/Scripting/Query Properties.md
@@ -7,21 +7,23 @@ publish: true
 <span class="related-pages">#feature/scripting</span>
 
 > [!released]
-> Query Properties were introduced in Tasks 4.7.0.
+>
+> - Query Properties were introduced in Tasks 4.7.0.
+> - Their direct use in Custom Groups, without the use of Placeholders, was introduced in Tasks X.Y.Z.
 
 ## Introduction
 
 In a growing number of locations, Tasks allows programmatic/scripting access to properties of the file containing the search query:
 
 - [[Placeholders]]
+- [[Custom Grouping]]
 
 This page documents all the available pieces of information in Queries that you can access.
 
 > [!warning]
 >
-> - These properties can currently only be used in [[Placeholders]].
-> - Placeholders can be in [[Custom Filters]] and [[Custom Grouping]], but must be surrounded by quotes. For example: `'{{query.file.folder}}'`.
-> - In a future release, we will allow expressions such as `query.file.folder` to be used directly in custom filters and groups.
+> - Placeholders can be in [[Custom Filters]], but must be surrounded by quotes. For example: `'{{query.file.folder}}'`.
+> - In a future release, we will allow expressions such as `query.file.folder` to be used directly in custom filters.
 
 ## Values for Query File Properties
 
diff --git a/src/Query/Filter/FunctionField.ts b/src/Query/Filter/FunctionField.ts
index 374dc2b6..c4f63d03 100644
--- a/src/Query/Filter/FunctionField.ts
+++ b/src/Query/Filter/FunctionField.ts
@@ -3,6 +3,8 @@ import type { GrouperFunction } from '../Grouper';
 import { Grouper } from '../Grouper';
 import { Explanation } from '../Explain/Explanation';
 import { TaskExpression, parseAndEvaluateExpression } from '../../Scripting/TaskExpression';
+import type { QueryContext } from '../../Scripting/QueryContext';
+import type { SearchInfo } from '../SearchInfo';
 import { Field } from './Field';
 import { Filter, type FilterFunction } from './Filter';
 import { FilterOrErrorMessage } from './FilterOrErrorMessage';
@@ -20,6 +22,7 @@ export class FunctionField extends Field {
         }
 
         const expression = match[1];
+        // TODO When filters are allowed to start using the QueryContext, will need to pass in actual QueryContext
         const taskExpression = new TaskExpression(expression);
         if (!taskExpression.isValid()) {
             return FilterOrErrorMessage.fromError(line, taskExpression.parseError!);
@@ -85,7 +88,7 @@ function createFilterFunctionFromLine(expression: TaskExpression): FilterFunctio
 export function filterByFunction(expression: TaskExpression, task: Task): boolean {
     // Allow exceptions to propagate to caller, since this will be called in a tight loop.
     // In searches, it will be caught by Query.applyQueryToTasks().
-    const result = expression.evaluate(task);
+    const result = expression.evaluate(task, undefined); // TODO when supporting query.file in filtering, add a QueryContext parameter
 
     // We insist that 'filter by function' returns booleans,
     // to avoid users having to understand truthy and falsey values.
@@ -103,14 +106,15 @@ export function filterByFunction(expression: TaskExpression, task: Task): boolea
 type GroupingArg = string;
 
 function createGrouperFunctionFromLine(line: string): GrouperFunction {
-    return (task: Task) => {
-        return groupByFunction(task, line);
+    return (task: Task, searchInfo: SearchInfo) => {
+        const queryContext = searchInfo.queryContext();
+        return groupByFunction(task, line, queryContext);
     };
 }
 
-export function groupByFunction(task: Task, arg: GroupingArg): string[] {
+export function groupByFunction(task: Task, arg: GroupingArg, queryContext?: QueryContext): string[] {
     try {
-        const result = parseAndEvaluateExpression(task, arg);
+        const result = parseAndEvaluateExpression(task, arg, queryContext);
 
         if (Array.isArray(result)) {
             return result.map((h) => h.toString());
diff --git a/src/Query/SearchInfo.ts b/src/Query/SearchInfo.ts
index d38e4073..eb9a6e7e 100644
--- a/src/Query/SearchInfo.ts
+++ b/src/Query/SearchInfo.ts
@@ -1,4 +1,5 @@
 import type { Task } from '../Task';
+import { type QueryContext, makeQueryContext } from '../Scripting/QueryContext';
 
 /**
  * SearchInfo contains selected data passed in from the {@link Query} being executed.
@@ -21,4 +22,14 @@ export class SearchInfo {
     public static fromAllTasks(tasks: Task[]): SearchInfo {
         return new SearchInfo(undefined, tasks);
     }
+
+    /**
+     * Construct a {@link QueryContext} from this, for use in the placeholder
+     * facility and scripting code.
+     *
+     * @return A QueryContext, or undefined if the path to the query file is unknown.
+     */
+    public queryContext(): QueryContext | undefined {
+        return this.queryPath ? makeQueryContext(this.queryPath) : undefined;
+    }
 }
diff --git a/src/Scripting/QueryContext.ts b/src/Scripting/QueryContext.ts
index d0ee9867..7542f466 100644
--- a/src/Scripting/QueryContext.ts
+++ b/src/Scripting/QueryContext.ts
@@ -1,14 +1,19 @@
 import { TasksFile } from './TasksFile';
 
 /**
- * This interface is part of the implementation of placeholders.
- * Use {@link makeQueryContext} to make a QueryContext.
+ * This interface is part of the implementation of placeholders and scripting.
  *
- * QueryContext is a 'view' to pass in to {@link expandPlaceholders}.
+ * - Use {@link makeQueryContext} to make a {@link QueryContext}.
+ * - Or more commonly, if you have a {@link SearchInfo}, use {@link SearchInfo.queryContext}
+ *
+ * QueryContext is a 'view' to pass in to {@link expandPlaceholders} and
+ * to various methods of {@link TaskExpression}.
  *
  * It provides the following:
- * `queryContext.query.file` - where query.file is a {@link TasksFile} object.
- * So it supplies `query.file.path`, `query.file.folder`.
+ * - `queryContext.query.file` - where `query.file` is a {@link TasksFile} object.
+ *    So it supplies `query.file.path`, `query.file.folder`.
+ *
+ * @see SearchInfo
  */
 export interface QueryContext {
     query: {
@@ -19,6 +24,8 @@ export interface QueryContext {
 /**
  * Create a {@link QueryContext} to represent a query in note at the give path.
  * @param path
+ *
+ * @see SearchInfo.queryContext
  */
 export function makeQueryContext(path: string): QueryContext {
     const tasksFile = new TasksFile(path);
diff --git a/src/Scripting/TaskExpression.ts b/src/Scripting/TaskExpression.ts
index 6cf53d9b..54d0c381 100644
--- a/src/Scripting/TaskExpression.ts
+++ b/src/Scripting/TaskExpression.ts
@@ -1,16 +1,18 @@
 import type { Task } from '../Task';
 import { FunctionOrError, evaluateExpression, evaluateExpressionOrCatch, parseExpression } from './Expression';
+import type { QueryContext } from './QueryContext';
 
 /**
  *  From: https://www.educative.io/answers/parameter-vs-argument
  *      A parameter is a variable in a function definition. It is a placeholder and hence does not have a concrete value.
  *      An argument is a value passed during function invocation.
  * @param task - during parsing, this can be null. During evaluation, it must be a Task
+ * @param queryContext - during parsing, this can be null. During evaluation, it must be a QueryContext or undefined.
  */
-export function constructArguments(task: Task | null) {
+export function constructArguments(task: Task | null, queryContext: QueryContext | null) {
     const paramsArgs: [string, any][] = [
-        // TODO Later, pass in the Query too, for access to file properties
         ['task', task],
+        ['query', queryContext ? queryContext.query : null],
     ];
     return paramsArgs;
 }
@@ -19,6 +21,7 @@ export function constructArguments(task: Task | null) {
  * Evaluate an arbitrary JavaScript expression on a Task object
  * @param task - a {@link Task} object
  * @param arg - a string, such as `task.path.startsWith("journal/") ? "journal/" : task.path`
+ * @param queryContext - an optional {@link QueryContext} object
  *
  * Currently any errors are returned as string error messages, starting with the word 'Error'.
  *
@@ -26,8 +29,8 @@ export function constructArguments(task: Task | null) {
  *
  * See also {@link FunctionField} which exposes this facility to users.
  */
-export function parseAndEvaluateExpression(task: Task, arg: string) {
-    const paramsArgs = constructArguments(task);
+export function parseAndEvaluateExpression(task: Task, arg: string, queryContext?: QueryContext) {
+    const paramsArgs = constructArguments(task, queryContext ? queryContext : null);
 
     const functionOrError = parseExpression(paramsArgs, arg);
     if (functionOrError.error) {
@@ -46,7 +49,7 @@ export class TaskExpression {
 
     public constructor(line: string) {
         this.line = line;
-        this.functionOrError = parseExpression(constructArguments(null), line);
+        this.functionOrError = parseExpression(constructArguments(null, null), line);
     }
 
     public isValid() {
@@ -60,28 +63,38 @@ export class TaskExpression {
     /**
      * Evaluate the expression on this task, or throw an exception if the calculation failed
      * @param task
+     * @param queryContext - optional. If not supplied, query properties will be unavailable.
      *
      * @see evaluateOrCatch
      */
-    public evaluate(task: Task) {
+    public evaluate(task: Task, queryContext?: QueryContext) {
+        // TODO When 'filter by function' supports query properties, make queryContext non-optional and simplify its use below.
         if (!this.isValid()) {
             throw Error(
                 `Error: Cannot evaluate an expression which is not valid: "${this.line}" gave error: "${this.parseError}"`,
             );
         }
-        return evaluateExpression(this.functionOrError.queryComponent!, constructArguments(task));
+        return evaluateExpression(
+            this.functionOrError.queryComponent!,
+            constructArguments(task, queryContext ? queryContext : null),
+        );
     }
 
     /**
      * Evaluate the expression on this task, or return error text if the calculation failed
      * @param task
+     * @param queryContext
      *
      * @see evaluate
      */
-    public evaluateOrCatch(task: Task) {
+    public evaluateOrCatch(task: Task, queryContext: QueryContext) {
         if (!this.isValid()) {
             return `Error: Cannot evaluate an expression which is not valid: "${this.line}" gave error: "${this.parseError}"`;
         }
-        return evaluateExpressionOrCatch(this.functionOrError.queryComponent!, constructArguments(task), this.line);
+        return evaluateExpressionOrCatch(
+            this.functionOrError.queryComponent!,
+            constructArguments(task, queryContext),
+            this.line,
+        );
     }
 }


Test patch:
diff --git a/tests/CustomMatchers/CustomMatchersForGrouping.ts b/tests/CustomMatchers/CustomMatchersForGrouping.ts
index b0e2406d..db760578 100644
--- a/tests/CustomMatchers/CustomMatchersForGrouping.ts
+++ b/tests/CustomMatchers/CustomMatchersForGrouping.ts
@@ -53,9 +53,10 @@ export function toSupportGroupingWithProperty(field: Field, property: string) {
  * Collate all the headings obtained when grouping the tasks by the grouper
  * @param grouper
  * @param tasks
+ * @param searchInfo
  */
-export function groupHeadingsForTask(grouper: Grouper, tasks: Task[]) {
-    const groups = new TaskGroups([grouper], tasks, SearchInfo.fromAllTasks(tasks));
+export function groupHeadingsForTask(grouper: Grouper, tasks: Task[], searchInfo: SearchInfo) {
+    const groups = new TaskGroups([grouper], tasks, searchInfo);
 
     const headings: string[] = [];
     groups.groups.forEach((taskGroup) => {
@@ -71,7 +72,7 @@ export function groupHeadingsToBe(
     expectedGroupHeadings: string[],
 ): jest.CustomMatcherResult {
     tasks.sort(() => Math.random() - 0.5);
-    const groupHeadings = groupHeadingsForTask(grouper, tasks);
+    const groupHeadings = groupHeadingsForTask(grouper, tasks, SearchInfo.fromAllTasks(tasks));
 
     const pass: boolean = groupHeadings.join() === expectedGroupHeadings.join();
     const message: () => string = () =>
@@ -92,6 +93,27 @@ export function groupHeadingsToBe(
  * @param expectedGroupNames
  */
 export function toGroupTask(grouper: Grouper | null, task: Task, expectedGroupNames: string[]) {
+    const tasks = [task];
+    const searchInfo = SearchInfo.fromAllTasks(tasks);
+    return toGroupTaskUsingSearchInfo(grouper, task, searchInfo, expectedGroupNames);
+}
+
+/**
+ * Test that applying the grouper to the task, with the given SearchInfo, generates the expected group names.
+ *
+ * Unless the grouper needs access to the path to the query, use {@link toGroupTask} instead.
+ *
+ * @param grouper
+ * @param task
+ * @param searchInfo
+ * @param expectedGroupNames
+ */
+export function toGroupTaskUsingSearchInfo(
+    grouper: Grouper | null,
+    task: Task,
+    searchInfo: SearchInfo,
+    expectedGroupNames: string[],
+) {
     if (grouper === undefined) {
         return {
             message: () => 'unexpected null grouper: check your instruction matches your filter class.',
@@ -99,7 +121,7 @@ export function toGroupTask(grouper: Grouper | null, task: Task, expectedGroupNa
         };
     }
 
-    expect(grouper!.grouper(task, SearchInfo.fromAllTasks([task]))).toEqual(expectedGroupNames);
+    expect(grouper!.grouper(task, searchInfo)).toEqual(expectedGroupNames);
 }
 
 /**
diff --git a/tests/Query.test.ts b/tests/Query.test.ts
index ccbf1eaa..0188ef6d 100644
--- a/tests/Query.test.ts
+++ b/tests/Query.test.ts
@@ -304,6 +304,7 @@ describe('Query parsing', () => {
             'group by folder',
             'group by folder reverse',
             'group by function reverse task.status.symbol.replace(" ", "space")',
+            'group by function task.file.path.replace(query.file.folder, "")',
             'group by function task.status.symbol.replace(" ", "space")',
             'group by happens',
             'group by happens reverse',
@@ -1248,6 +1249,20 @@ At most 8 tasks per group (if any "group by" options are supplied).
             expect(query.grouping.length).toEqual(1);
         });
 
+        it('should work with a custom group that uses query information', () => {
+            // Arrange
+            const source = 'group by function query.file.path';
+            const query = new Query(source, 'hello.md');
+
+            // Act
+            const results = query.applyQueryToTasks([new TaskBuilder().build()]);
+
+            // Assert
+            const groups = results.taskGroups;
+            expect(groups.groups.length).toEqual(1);
+            expect(groups.groups[0].groups).toEqual(['hello.md']);
+        });
+
         it('should log meaningful error for supported group type', () => {
             // Arrange
             const source = 'group by xxxx';
diff --git a/tests/Query/Filter/FunctionField.test.ts b/tests/Query/Filter/FunctionField.test.ts
index 122a14be..6379df39 100644
--- a/tests/Query/Filter/FunctionField.test.ts
+++ b/tests/Query/Filter/FunctionField.test.ts
@@ -6,7 +6,11 @@ import moment from 'moment';
 import { FunctionField } from '../../../src/Query/Filter/FunctionField';
 import { Status } from '../../../src/Status';
 import { Priority } from '../../../src/Task';
-import { toGroupTaskFromBuilder, toGroupTaskWithPath } from '../../CustomMatchers/CustomMatchersForGrouping';
+import {
+    toGroupTaskFromBuilder,
+    toGroupTaskUsingSearchInfo,
+    toGroupTaskWithPath,
+} from '../../CustomMatchers/CustomMatchersForGrouping';
 import { TaskBuilder } from '../../TestingTools/TaskBuilder';
 import { SearchInfo } from '../../../src/Query/SearchInfo';
 
@@ -236,4 +240,11 @@ describe('FunctionField - grouping - example functions', () => {
             '#context/pc_home',
         ]);
     });
+
+    it('group by a query property', () => {
+        const line = 'group by function query.file.filename';
+        const grouper = createGrouper(line);
+        const task = new TaskBuilder().build();
+        toGroupTaskUsingSearchInfo(grouper, task, new SearchInfo('queries/query file.md', [task]), ['query file.md']);
+    });
 });
diff --git a/tests/Query/SearchInfo.test.ts b/tests/Query/SearchInfo.test.ts
index f8806c9f..1fbe498c 100644
--- a/tests/Query/SearchInfo.test.ts
+++ b/tests/Query/SearchInfo.test.ts
@@ -29,4 +29,22 @@ describe('SearchInfo', () => {
 
         expect(searchInfo.queryPath).toEqual(path);
     });
+
+    it('should create a QueryContext from a known path', () => {
+        const path = 'a/b/c.md';
+        const searchInfo = new SearchInfo(path, []);
+
+        const queryContext = searchInfo.queryContext();
+
+        expect(queryContext).not.toBeUndefined();
+        expect(queryContext!.query.file.path).toEqual(path);
+    });
+
+    it('should not create a QueryContext from unknown path', () => {
+        const searchInfo = new SearchInfo(undefined, []);
+
+        const queryContext = searchInfo.queryContext();
+
+        expect(queryContext).toBeUndefined();
+    });
 });
diff --git a/tests/Scripting/Expression.test.ts b/tests/Scripting/Expression.test.ts
index 857a7943..f1fd5dc1 100644
--- a/tests/Scripting/Expression.test.ts
+++ b/tests/Scripting/Expression.test.ts
@@ -8,6 +8,7 @@ import { TaskBuilder } from '../TestingTools/TaskBuilder';
 import { verifyMarkdownForDocs } from '../TestingTools/VerifyMarkdownTable';
 import { continue_lines } from '../../src/Query/Scanner';
 import { constructArguments, parseAndEvaluateExpression } from '../../src/Scripting/TaskExpression';
+import { makeQueryContext } from '../../src/Scripting/QueryContext';
 import { formatToRepresentType } from './ScriptingTestHelpers';
 
 window.moment = moment;
@@ -53,6 +54,7 @@ describe('Expression', () => {
     });
 
     const task = TaskBuilder.createFullyPopulatedTask();
+    const queryContext = makeQueryContext('temp.md');
 
     describe('detect errors at parse stage', () => {
         it('should report meaningful error message for parentheses too few parentheses', () => {
@@ -70,7 +72,7 @@ describe('Expression', () => {
 
     describe('detect errors at evaluation time', () => {
         const line = 'nonExistentVariable';
-        const paramsArgs = constructArguments(task);
+        const paramsArgs = constructArguments(task, queryContext);
         const expression = parseExpression(paramsArgs, line);
         it('evaluateExpressionAndCatch() should report meaningful error message for invalid variable', () => {
             expect(expression.error).toBeUndefined();
@@ -90,7 +92,7 @@ describe('Expression', () => {
         });
 
         it('should report unknown for invalid task property', () => {
-            expect(parseAndEvaluateExpression(task, 'task.iAmNotAKnownTaskProperty')).toEqual(undefined);
+            expect(parseAndEvaluateExpression(task, 'task.iAmNotAKnownTaskProperty', queryContext)).toEqual(undefined);
         });
     });
 
@@ -110,7 +112,7 @@ describe('Expression', () => {
         markdown +=
             expressions
                 .map((expression) => {
-                    const result = parseAndEvaluateExpression(task, continue_lines(expression));
+                    const result = parseAndEvaluateExpression(task, continue_lines(expression), queryContext);
                     return `${expression}${resultSeparator}=> ${formatToRepresentType(result)}`;
                 })
                 .join(separator) + '\n';
diff --git a/tests/Scripting/ScriptingReference/CustomGrouping/CustomGroupingExamples.test.file_properties_task.file.path_docs.approved.md b/tests/Scripting/ScriptingReference/CustomGrouping/CustomGroupingExamples.test.file_properties_task.file.path_docs.approved.md
index 1da7cd1b..b1f46a1d 100644
--- a/tests/Scripting/ScriptingReference/CustomGrouping/CustomGroupingExamples.test.file_properties_task.file.path_docs.approved.md
+++ b/tests/Scripting/ScriptingReference/CustomGrouping/CustomGroupingExamples.test.file_properties_task.file.path_docs.approved.md
@@ -8,13 +8,12 @@ group by function task.file.path
 - Like 'group by path' but includes the file extension.
 
 ```javascript
-group by function task.file.path.replace('{{query.file.folder}}', '')
+group by function task.file.path.replace(query.file.folder, '')
 ```
 
 - Group by the task's file path, but remove the query's folder from the group.
 - For tasks in the query's folder or a sub-folder, this is a nice way of seeing shortened paths.
-- Note that the placeholder text is expanded to a raw string, so needs to be inside quotes.
-- This is provided to give ideas: it's a bit of a lazy implementation, as it doesn't check that `'{{query.file.folder}}'` is at the start of the line.
+- This is provided to give ideas: it's a bit of a lazy implementation, as it doesn't check that `query.file.folder` is at the start of the line.
 
 
 <!-- placeholder to force blank line after included text -->
diff --git a/tests/Scripting/ScriptingReference/CustomGrouping/CustomGroupingExamples.test.file_properties_task.file.path_results.approved.txt b/tests/Scripting/ScriptingReference/CustomGrouping/CustomGroupingExamples.test.file_properties_task.file.path_results.approved.txt
index 435747ff..09d7e633 100644
--- a/tests/Scripting/ScriptingReference/CustomGrouping/CustomGroupingExamples.test.file_properties_task.file.path_results.approved.txt
+++ b/tests/Scripting/ScriptingReference/CustomGrouping/CustomGroupingExamples.test.file_properties_task.file.path_results.approved.txt
@@ -14,11 +14,10 @@ e/d/c.md
 ====================================================================================
 
 
-group by function task.file.path.replace('{{query.file.folder}}', '')
+group by function task.file.path.replace(query.file.folder, '')
 Group by the task's file path, but remove the query's folder from the group.
 For tasks in the query's folder or a sub-folder, this is a nice way of seeing shortened paths.
-Note that the placeholder text is expanded to a raw string, so needs to be inside quotes.
-This is provided to give ideas: it's a bit of a lazy implementation, as it doesn't check that `'{{query.file.folder}}'` is at the start of the line.
+This is provided to give ideas: it's a bit of a lazy implementation, as it doesn't check that `query.file.folder` is at the start of the line.
 =>
 a_b_c.md
 b.md
diff --git a/tests/Scripting/ScriptingReference/CustomGrouping/CustomGroupingExamples.test.ts b/tests/Scripting/ScriptingReference/CustomGrouping/CustomGroupingExamples.test.ts
index 516fad57..66c61c02 100644
--- a/tests/Scripting/ScriptingReference/CustomGrouping/CustomGroupingExamples.test.ts
+++ b/tests/Scripting/ScriptingReference/CustomGrouping/CustomGroupingExamples.test.ts
@@ -226,11 +226,10 @@ describe('file properties', () => {
                 // comment to force line break
                 ['group by function task.file.path', "Like 'group by path' but includes the file extension"],
                 [
-                    "group by function task.file.path.replace('{{query.file.folder}}', '')",
+                    "group by function task.file.path.replace(query.file.folder, '')",
                     "Group by the task's file path, but remove the query's folder from the group.",
                     "For tasks in the query's folder or a sub-folder, this is a nice way of seeing shortened paths.",
-                    'Note that the placeholder text is expanded to a raw string, so needs to be inside quotes.',
-                    "This is provided to give ideas: it's a bit of a lazy implementation, as it doesn't check that `'{{query.file.folder}}'` is at the start of the line.",
+                    "This is provided to give ideas: it's a bit of a lazy implementation, as it doesn't check that `query.file.folder` is at the start of the line.",
                 ],
             ],
             SampleTasks.withAllRootsPathsHeadings(),
diff --git a/tests/Scripting/ScriptingReference/VerifyFunctionFieldSamples.ts b/tests/Scripting/ScriptingReference/VerifyFunctionFieldSamples.ts
index c61d108e..293fd568 100644
--- a/tests/Scripting/ScriptingReference/VerifyFunctionFieldSamples.ts
+++ b/tests/Scripting/ScriptingReference/VerifyFunctionFieldSamples.ts
@@ -129,11 +129,12 @@ export function verifyFunctionFieldGrouperSamplesOnTasks(
         const instruction = group[0];
         const comment = group.slice(1);
 
-        const expandedInstruction = preprocessSingleInstruction(instruction, 'a/b.md');
+        const path = 'a/b.md';
+        const expandedInstruction = preprocessSingleInstruction(instruction, path);
         const grouper = new FunctionField().createGrouperFromLine(expandedInstruction);
         expect(grouper).not.toBeNull();
 
-        const headings = groupHeadingsForTask(grouper!, tasks);
+        const headings = groupHeadingsForTask(grouper!, tasks, new SearchInfo(path, tasks));
         return formatQueryAndResultsForApproving(instruction, comment, headings);
     });
 }
diff --git a/tests/Scripting/TaskExpression.test.ts b/tests/Scripting/TaskExpression.test.ts
index 1cbba1c4..95b7e5a4 100644
--- a/tests/Scripting/TaskExpression.test.ts
+++ b/tests/Scripting/TaskExpression.test.ts
@@ -1,7 +1,33 @@
-import { TaskExpression } from '../../src/Scripting/TaskExpression';
+import { TaskExpression, constructArguments, parseAndEvaluateExpression } from '../../src/Scripting/TaskExpression';
 import { TaskBuilder } from '../TestingTools/TaskBuilder';
+import { makeQueryContext } from '../../src/Scripting/QueryContext';
 
 describe('TaskExpression', () => {
+    describe('low level functions', () => {
+        it('should allow passing QueryContext or null to constructArguments()', () => {
+            const task = new TaskBuilder().build();
+            constructArguments(task, null);
+            constructArguments(task, makeQueryContext('dummy.md'));
+        });
+
+        it('should calculate an expression value from a QueryContext', () => {
+            const queryContext = makeQueryContext('test.md');
+            const task = new TaskBuilder().build();
+            const result = parseAndEvaluateExpression(task, 'query.file.path', queryContext);
+            expect(result).toEqual('test.md');
+        });
+
+        it('should behave predictably if no QueryContext supplied', () => {
+            const task = new TaskBuilder().build();
+            const result = parseAndEvaluateExpression(task, 'query.file.path', undefined);
+            expect(result).toMatchInlineSnapshot(`
+                "Error: Failed calculating expression "query.file.path".
+                The error message was:
+                    "TypeError: Cannot read properties of null (reading 'file')""
+            `);
+        });
+    });
+
     describe('parsing', () => {
         it('should report that a parsable line is valid', () => {
             const line = 'task.description';
@@ -31,15 +57,25 @@ describe('TaskExpression', () => {
     });
 
     describe('evaluating', () => {
-        it('should evaluate a valid line and give correct result', () => {
+        const queryContext = makeQueryContext('dummy.md');
+        it('should evaluate a valid task property and give correct result', () => {
             // Arrange
             const taskExpression = new TaskExpression('task.description');
+            const task = new TaskBuilder().description('hello').build();
 
-            // Act
-            const result = taskExpression.evaluateOrCatch(new TaskBuilder().description('hello').build());
+            // Act, Assert
+            expect(taskExpression.evaluate(task, queryContext)).toEqual('hello');
+            expect(taskExpression.evaluateOrCatch(task, queryContext)).toEqual('hello');
+        });
 
-            // Assert
-            expect(result).toEqual('hello');
+        it('should evaluate a valid query property and give correct result', () => {
+            // Arrange
+            const taskExpression = new TaskExpression('query.file.path');
+            const task = new TaskBuilder().build();
+
+            // Act, Assert
+            expect(taskExpression.evaluate(task, queryContext)).toEqual('dummy.md');
+            expect(taskExpression.evaluateOrCatch(task, queryContext)).toEqual('dummy.md');
         });
 
         it('should return error string as output if evaluating an expression that parsed OK fails at execution', () => {
@@ -47,7 +83,7 @@ describe('TaskExpression', () => {
             const taskExpression = new TaskExpression('wibble');
 
             // Act
-            const result = taskExpression.evaluateOrCatch(new TaskBuilder().build());
+            const result = taskExpression.evaluateOrCatch(new TaskBuilder().build(), queryContext);
 
             // Assert
             expect(result).toEqual(
@@ -56,7 +92,7 @@ describe('TaskExpression', () => {
 
             // Try again, using evaluate()
             const t = () => {
-                taskExpression.evaluate(new TaskBuilder().build());
+                taskExpression.evaluate(new TaskBuilder().build(), queryContext);
             };
             expect(t).toThrow(ReferenceError);
             expect(t).toThrowError('wibble is not defined');
@@ -68,7 +104,7 @@ describe('TaskExpression', () => {
             expect(taskExpression.isValid()).toEqual(false);
 
             // Act
-            const result = taskExpression.evaluateOrCatch(new TaskBuilder().build());
+            const result = taskExpression.evaluateOrCatch(new TaskBuilder().build(), queryContext);
 
             // Assert
             const expectedErrorMessage =
@@ -77,7 +113,7 @@ describe('TaskExpression', () => {
 
             // Try again, using evaluate()
             const t = () => {
-                taskExpression.evaluate(new TaskBuilder().build());
+                taskExpression.evaluate(new TaskBuilder().build(), queryContext);
             };
             expect(t).toThrow(Error);
             expect(t).toThrowError(expectedErrorMessage);
diff --git a/tests/Scripting/TaskProperties.test.ts b/tests/Scripting/TaskProperties.test.ts
index bcc6920c..d3bb1e46 100644
--- a/tests/Scripting/TaskProperties.test.ts
+++ b/tests/Scripting/TaskProperties.test.ts
@@ -20,8 +20,8 @@ describe('task', () => {
         const task1 = TaskBuilder.createFullyPopulatedTask();
         const task2 = new TaskBuilder().description('minimal task').status(Status.makeInProgress()).build();
         for (const field of fields) {
-            const value1 = parseAndEvaluateExpression(task1, field);
-            const value2 = parseAndEvaluateExpression(task2, field);
+            const value1 = parseAndEvaluateExpression(task1, field, undefined);
+            const value2 = parseAndEvaluateExpression(task2, field, undefined);
             const cells = [
                 addBackticks(field),
                 addBackticks(determineExpressionType(value1)),
diff --git a/tests/TaskGroups.test.ts b/tests/TaskGroups.test.ts
index 9c37ecd9..5af9cc12 100644
--- a/tests/TaskGroups.test.ts
+++ b/tests/TaskGroups.test.ts
@@ -14,6 +14,7 @@ import { HappensDateField } from '../src/Query/Filter/HappensDateField';
 import { DueDateField } from '../src/Query/Filter/DueDateField';
 import { SearchInfo } from '../src/Query/SearchInfo';
 import { fromLine } from './TestHelpers';
+import { TaskBuilder } from './TestingTools/TaskBuilder';
 
 window.moment = moment;
 
@@ -99,6 +100,25 @@ describe('Grouping tasks', () => {
         expect(groups.groups[0].tasks.length).toEqual(0);
     });
 
+    it('should provide access to SearchInfo', () => {
+        // Arrange
+        const groupByQueryPath: GrouperFunction = (_task: Task, searchInfo: SearchInfo) => {
+            return [searchInfo.queryPath ? searchInfo.queryPath : 'No SearchInfo'];
+        };
+        const grouper: Grouper = new Grouper('test', groupByQueryPath, false);
+
+        const filename = 'somewhere/anything.md';
+        const tasks = [new TaskBuilder().build()];
+        const searchInfo = new SearchInfo(filename, tasks);
+
+        // Act
+        const groups = new TaskGroups([grouper], tasks, searchInfo);
+
+        // Assert
+        expect(groups.groups.length).toEqual(1);
+        expect(groups.groups[0].groups).toEqual([filename]);
+    });
+
     it('sorts group names correctly', () => {
         const a = fromLine({
             line: '- [ ] third file path',