Repository: obsidian-tasks-group/obsidian-tasks
Pull request #2094 (2023-07-03 21:21:54)

feat: Add 'task.descriptionWithoutTags' for 'group by function'
<!--- Provide a general summary of your changes in the Title above -->

# Description

This takes advantage of code that @ilandikov created in #2076, and makes it available to users for custom grouping with the property `task.descriptionWithoutTags` such as:

```
group by function task.descriptionWithoutTags
```

## Motivation and Context

I was first going to add this to this docs:

```
group by function task.description.replace(/(^|\s)#[^ !@#$%^&*(),.?":{}|<>]*/g, '')
```

and then decided it was kinder to users to provide `task.descriptionWithoutTags` instead!

## How has this been tested?

<!--- 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. -->

- By adding tests (at the `TaskProperties` level, for inclusion in the docs)
- By testing manually in the demo vault

## Screenshots (if appropriate)

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

Changes visible to users:

- [x] **New feature** (prefix: `feat` - non-breaking change which adds functionality)
- [x] **Documentation** (prefix: `docs` - improvements to any documentation content **for users**)

Internal changes:

- [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/Queries/Grouping.md b/docs/Queries/Grouping.md
index 54bcbf3f..d7f4ce60 100644
--- a/docs/Queries/Grouping.md
+++ b/docs/Queries/Grouping.md
@@ -314,7 +314,7 @@ As well as the date-related groups above, groups can be created from properties
 
 ### Description
 
-There is no built-in instruction to group by desciption.
+There is no built-in instruction to group by description.
 
 Since Tasks 4.0.0, **[[Custom Grouping|custom grouping]] by description** is now possible.
 
@@ -332,6 +332,20 @@ Since Tasks 4.0.0, **[[Custom Grouping|custom grouping]] by description** is now
 
 <!-- placeholder to force blank line after included text --> <!-- endInclude -->
 
+### Description without tags
+
+Since Tasks X.Y.Z, it is possible to remove tags from the descriptions in custom groups, for use in **[[Custom Grouping|custom grouping]]**.
+
+The value `task.descriptionWithoutTags` returns a copy of the description with all the tags removed, so that you can group together any tasks whose descriptions differ only by their tags.
+
+<!-- placeholder to force blank line before included text --> <!-- include: TaskPropertyExamples.test.custom_grouping_by_task.descriptionWithoutTags_docs.approved.md -->
+
+- ```group by function task.descriptionWithoutTags```
+  - Like `group by description`, but it removes any tags from the group headings.
+  - This might be useful for finding completed recurrences of the same task, even if the tags differ in some recurrences.
+
+<!-- placeholder to force blank line after included text --> <!-- endInclude -->
+
 ### Priority
 
 - `group by priority`
diff --git a/docs/Scripting/Task Properties.md b/docs/Scripting/Task Properties.md
index fd0109bc..4ef7c32d 100644
--- a/docs/Scripting/Task Properties.md
+++ b/docs/Scripting/Task Properties.md
@@ -65,6 +65,7 @@ For more information, including adding your own customised statuses, see [[Statu
 | Field | Type 1 | Example 1 | Type 2 | Example 2 |
 | ----- | ----- | ----- | ----- | ----- |
 | `task.description` | `string` | `'Do exercises #todo #health'` | `string` | `'minimal task'` |
+| `task.descriptionWithoutTags` | `string` | `'Do exercises'` | `string` | `'minimal task'` |
 | `task.priorityNumber` | `number` | `2` | `number` | `3` |
 | `task.priorityName` | `string` | `'Medium'` | `string` | `'Normal'` |
 | `task.urgency` | `number` | `3.3000000000000007` | `number` | `1.9500000000000002` |
diff --git a/src/Task.ts b/src/Task.ts
index 06160dde..e8f39477 100644
--- a/src/Task.ts
+++ b/src/Task.ts
@@ -478,6 +478,16 @@ export class Task {
         return Number.parseInt(this.priority);
     }
 
+    /**
+     * Return a copy of the description, with any tags removed.
+     *
+     * Note that this removes tags recognised by Tasks (including removing #123, for example),
+     * as opposed to tags recognised by Obsidian, which does not treat numbers-only as valid tags.
+     */
+    public get descriptionWithoutTags(): string {
+        return this.description.replace(TaskRegularExpressions.hashTags, '').trim();
+    }
+
     /**
      * Return the name of the Task's priority.
      *


Test patch:
diff --git a/tests/Scripting/TaskProperties.test.task_other_fields.approved.md b/tests/Scripting/TaskProperties.test.task_other_fields.approved.md
index a8981896..2814aaed 100644
--- a/tests/Scripting/TaskProperties.test.task_other_fields.approved.md
+++ b/tests/Scripting/TaskProperties.test.task_other_fields.approved.md
@@ -3,6 +3,7 @@
 | Field | Type 1 | Example 1 | Type 2 | Example 2 |
 | ----- | ----- | ----- | ----- | ----- |
 | `task.description` | `string` | `'Do exercises #todo #health'` | `string` | `'minimal task'` |
+| `task.descriptionWithoutTags` | `string` | `'Do exercises'` | `string` | `'minimal task'` |
 | `task.priorityNumber` | `number` | `2` | `number` | `3` |
 | `task.priorityName` | `string` | `'Medium'` | `string` | `'Normal'` |
 | `task.urgency` | `number` | `3.3000000000000007` | `number` | `1.9500000000000002` |
diff --git a/tests/Scripting/TaskProperties.test.ts b/tests/Scripting/TaskProperties.test.ts
index 80178177..d0a92c9c 100644
--- a/tests/Scripting/TaskProperties.test.ts
+++ b/tests/Scripting/TaskProperties.test.ts
@@ -67,6 +67,7 @@ describe('task', () => {
     it('other fields', () => {
         verifyFieldDataForReferenceDocs([
             'task.description',
+            'task.descriptionWithoutTags',
             'task.priorityNumber',
             'task.priorityName',
             'task.urgency',
diff --git a/tests/Scripting/TaskPropertyExamples.test.custom_grouping_by_task.descriptionWithoutTags_docs.approved.md b/tests/Scripting/TaskPropertyExamples.test.custom_grouping_by_task.descriptionWithoutTags_docs.approved.md
new file mode 100644
index 00000000..b9f4133f
--- /dev/null
+++ b/tests/Scripting/TaskPropertyExamples.test.custom_grouping_by_task.descriptionWithoutTags_docs.approved.md
@@ -0,0 +1,8 @@
+<!-- placeholder to force blank line before included text -->
+
+- ```group by function task.descriptionWithoutTags```
+    - Like `group by description`, but it removes any tags from the group headings.
+    - This might be useful for finding completed recurrences of the same task, even if the tags differ in some recurrences.
+
+
+<!-- placeholder to force blank line after included text -->
diff --git a/tests/Scripting/TaskPropertyExamples.test.custom_grouping_by_task.descriptionWithoutTags_results.approved.txt b/tests/Scripting/TaskPropertyExamples.test.custom_grouping_by_task.descriptionWithoutTags_results.approved.txt
new file mode 100644
index 00000000..266eb2f4
--- /dev/null
+++ b/tests/Scripting/TaskPropertyExamples.test.custom_grouping_by_task.descriptionWithoutTags_results.approved.txt
@@ -0,0 +1,13 @@
+Results of custom groupers
+
+
+
+group by function task.descriptionWithoutTags
+Like `group by description`, but it removes any tags from the group headings.
+This might be useful for finding completed recurrences of the same task, even if the tags differ in some recurrences
+=>
+long description Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce quam ipsum, consectetur ut dolor nec, fringilla lobortis mi. Vestibulum gravida tincidunt urna nec ornare. Cras sit amet sagittis sapien, vitae mattis velit. Vestibulum sem tortor, blandit at ultrices eget, ultrices eget odio. Donec efficitur purus massa, vel molestie turpis tincidunt id.
+my description
+short description
+====================================================================================
+
diff --git a/tests/Scripting/TaskPropertyExamples.test.custom_grouping_by_task.description_results.approved.txt b/tests/Scripting/TaskPropertyExamples.test.custom_grouping_by_task.description_results.approved.txt
index cf90c09d..9112ccff 100644
--- a/tests/Scripting/TaskPropertyExamples.test.custom_grouping_by_task.description_results.approved.txt
+++ b/tests/Scripting/TaskPropertyExamples.test.custom_grouping_by_task.description_results.approved.txt
@@ -7,6 +7,14 @@ group by description.
 This might be useful for finding completed recurrences of the same task
 =>
 long description Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce quam ipsum, consectetur ut dolor nec, fringilla lobortis mi. Vestibulum gravida tincidunt urna nec ornare. Cras sit amet sagittis sapien, vitae mattis velit. Vestibulum sem tortor, blandit at ultrices eget, ultrices eget odio. Donec efficitur purus massa, vel molestie turpis tincidunt id. 
+my description
+my description #context/home
+my description #context/work
+my description #multiple-tags1 #multiple-tags2
+my description #project/project1
+my description #tag
+my description #tag/subtag
+my description #tag/subtag/sub-sub-tag
 short description
 ====================================================================================
 
@@ -15,6 +23,14 @@ group by function task.description.toUpperCase()
 Convert the description to capitals
 =>
 LONG DESCRIPTION LOREM IPSUM DOLOR SIT AMET, CONSECTETUR ADIPISCING ELIT. FUSCE QUAM IPSUM, CONSECTETUR UT DOLOR NEC, FRINGILLA LOBORTIS MI. VESTIBULUM GRAVIDA TINCIDUNT URNA NEC ORNARE. CRAS SIT AMET SAGITTIS SAPIEN, VITAE MATTIS VELIT. VESTIBULUM SEM TORTOR, BLANDIT AT ULTRICES EGET, ULTRICES EGET ODIO. DONEC EFFICITUR PURUS MASSA, VEL MOLESTIE TURPIS TINCIDUNT ID. 
+MY DESCRIPTION
+MY DESCRIPTION #CONTEXT/HOME
+MY DESCRIPTION #CONTEXT/WORK
+MY DESCRIPTION #MULTIPLE-TAGS1 #MULTIPLE-TAGS2
+MY DESCRIPTION #PROJECT/PROJECT1
+MY DESCRIPTION #TAG
+MY DESCRIPTION #TAG/SUBTAG
+MY DESCRIPTION #TAG/SUBTAG/SUB-SUB-TAG
 SHORT DESCRIPTION
 ====================================================================================
 
@@ -23,6 +39,13 @@ group by function task.description.slice(0, 25)
 Truncate descriptions to at most their first 25 characters, and group by that string
 =>
 long description Lorem ip
+my description
+my description #context/h
+my description #context/w
+my description #multiple-
+my description #project/p
+my description #tag
+my description #tag/subta
 short description
 ====================================================================================
 
@@ -32,5 +55,13 @@ Highlight the word "short" in any group descriptions
 =>
 ==short== description
 long description Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce quam ipsum, consectetur ut dolor nec, fringilla lobortis mi. Vestibulum gravida tincidunt urna nec ornare. Cras sit amet sagittis sapien, vitae mattis velit. Vestibulum sem tortor, blandit at ultrices eget, ultrices eget odio. Donec efficitur purus massa, vel molestie turpis tincidunt id. 
+my description
+my description #context/home
+my description #context/work
+my description #multiple-tags1 #multiple-tags2
+my description #project/project1
+my description #tag
+my description #tag/subtag
+my description #tag/subtag/sub-sub-tag
 ====================================================================================
 
diff --git a/tests/Scripting/TaskPropertyExamples.test.ts b/tests/Scripting/TaskPropertyExamples.test.ts
index 3024eb89..0b9386b7 100644
--- a/tests/Scripting/TaskPropertyExamples.test.ts
+++ b/tests/Scripting/TaskPropertyExamples.test.ts
@@ -280,7 +280,19 @@ describe('custom grouping by', () => {
                     'Highlight the word "short" in any group descriptions',
                 ],
             ],
-            SampleTasks.withAllRepresentativeDescriptions(),
+            SampleTasks.withAllRepresentativeDescriptions().concat(SampleTasks.withRepresentativeTags()),
+        ],
+
+        [
+            'task.descriptionWithoutTags',
+            [
+                [
+                    'group by function task.descriptionWithoutTags',
+                    'Like `group by description`, but it removes any tags from the group headings.',
+                    'This might be useful for finding completed recurrences of the same task, even if the tags differ in some recurrences',
+                ],
+            ],
+            SampleTasks.withAllRepresentativeDescriptions().concat(SampleTasks.withRepresentativeTags()),
         ],
 
         // [