From 1b70e59bde8a3a3b069f445206dce0857a0fbb74 Mon Sep 17 00:00:00 2001
From: Robert Dyer <rdyer@unl.edu>
Date: Thu, 22 Aug 2024 11:25:16 -0500
Subject: [PATCH] Apply regular expression conditions to imported transactions.
 (#3287)

---
 .../src/server/accounts/rules.test.ts         | 20 +++++++++++++++++++
 .../loot-core/src/server/accounts/rules.ts    | 14 +++++++++++--
 upcoming-release-notes/3287.md                |  6 ++++++
 3 files changed, 38 insertions(+), 2 deletions(-)
 create mode 100644 upcoming-release-notes/3287.md

diff --git a/packages/loot-core/src/server/accounts/rules.test.ts b/packages/loot-core/src/server/accounts/rules.test.ts
index 556230081..392add3ff 100644
--- a/packages/loot-core/src/server/accounts/rules.test.ts
+++ b/packages/loot-core/src/server/accounts/rules.test.ts
@@ -47,6 +47,9 @@ describe('Condition', () => {
     let cond = new Condition('contains', 'name', 'foo', null, fieldTypes);
     expect(cond.eval({ name: null })).toBe(false);
 
+    cond = new Condition('matches', 'name', '^fo*$', null, fieldTypes);
+    expect(cond.eval({ name: null })).toBe(false);
+
     cond = new Condition('oneOf', 'name', ['foo'], null, fieldTypes);
     expect(cond.eval({ name: null })).toBe(false);
 
@@ -69,6 +72,9 @@ describe('Condition', () => {
     cond = new Condition('contains', 'name', 'foo', null, fieldTypes);
     expect(cond.eval({ date: '2020-01-01' })).toBe(false);
 
+    cond = new Condition('matches', 'name', '^fo*$', null, fieldTypes);
+    expect(cond.eval({ date: '2020-01-01' })).toBe(false);
+
     spy.mockRestore();
   });
 
@@ -223,6 +229,20 @@ describe('Condition', () => {
     expect(cond.eval({ name: 'bfoo' })).toBe(true);
     expect(cond.eval({ name: 'bfo' })).toBe(false);
     expect(cond.eval({ name: 'f o o' })).toBe(false);
+
+    cond = new Condition('matches', 'name', '^fo*$', null, fieldTypes);
+    expect(cond.eval({ name: 'bar foo baz' })).toBe(false);
+    expect(cond.eval({ name: 'bar FOOb' })).toBe(false);
+    expect(cond.eval({ name: 'foo' })).toBe(true);
+    expect(cond.eval({ name: 'foob' })).toBe(false);
+    expect(cond.eval({ name: 'bfoo' })).toBe(false);
+    expect(cond.eval({ name: 'bfo' })).toBe(false);
+    expect(cond.eval({ name: 'f o o' })).toBe(false);
+  });
+
+  test('matches handles invalid regex', () => {
+    const cond = new Condition('matches', 'name', 'fo**', null, fieldTypes);
+    expect(cond.eval({ name: 'foo' })).toBe(false);
   });
 
   test('number validates value', () => {
diff --git a/packages/loot-core/src/server/accounts/rules.ts b/packages/loot-core/src/server/accounts/rules.ts
index b5fbde95d..467dc7bc8 100644
--- a/packages/loot-core/src/server/accounts/rules.ts
+++ b/packages/loot-core/src/server/accounts/rules.ts
@@ -162,7 +162,7 @@ const CONDITION_TYPES = {
         assert(
           Array.isArray(value),
           'no-empty-array',
-          `oneOf must have an array value (field: ${fieldName}): ${JSON.stringify(
+          `${op} must have an array value (field: ${fieldName}): ${JSON.stringify(
             value,
           )}`,
         );
@@ -173,7 +173,7 @@ const CONDITION_TYPES = {
         assert(
           typeof value === 'string' && value.length > 0,
           'no-empty-string',
-          `contains must have non-empty string (field: ${fieldName})`,
+          `${op} must have non-empty string (field: ${fieldName})`,
         );
       }
 
@@ -420,6 +420,16 @@ export class Condition {
           );
         }
         return fieldValue <= extractValue(this.value);
+      case 'matches':
+        if (fieldValue === null) {
+          return false;
+        }
+        try {
+          return new RegExp(this.value).test(fieldValue);
+        } catch (e) {
+          console.log('invalid regexp in matches condition', e);
+          return false;
+        }
       default:
     }
 
diff --git a/upcoming-release-notes/3287.md b/upcoming-release-notes/3287.md
new file mode 100644
index 000000000..a1b272403
--- /dev/null
+++ b/upcoming-release-notes/3287.md
@@ -0,0 +1,6 @@
+---
+category: Bugfix
+authors: [psybers, jameshurst]
+---
+
+Apply regular expression conditions to imported transactions.
-- 
GitLab