MailJDTourDownloadDocsQualityTestsCoverage

Which source lines and branches the tests execute. Lines 91.5 %, branches 75.0 % (minimum 85 % and 72 %). main @ 399ffe3d · 21 October 2026 · pipeline 7238.

GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 94.9% 373 / 0 / 393
Functions: 100.0% 23 / 0 / 23
Branches: 71.1% 627 / 0 / 882

presentation/QuickRuleMailFlow.cpp
Line Branch Exec Source
1 // Mail-flow half of QuickRulesBackend (Sprint 95 Phase 4): rule from the
2 // displayed mail (S95.4.1) or the current search (S95.4.4), the live match
3 // preview and "Also apply to existing messages" (S95.4.6, D-11), and the
4 // existing-rule awareness of every creation path (S95.4.9, principle 8).
5 #include "QuickRulesBackend.h"
6
7 #include "RuleConsoleInput.h"
8 #include "RuleDraftFactory.h"
9 #include "RuleEditorFields.h"
10 #include "RuleSentence.h"
11
12 #include <QElapsedTimer>
13 #include <QLoggingCategory>
14 #include <QTimer>
15
16 Q_DECLARE_LOGGING_CATEGORY(lcRulesBackend)
17
18 namespace {
19
20 constexpr int kApplyLimit = 5000;
21 // S106-323: preview and apply evaluate every cached header of the scope on
22 // the GUI thread; a pattern that is slow on the mail text (a :regex that
23 // backtracks up to PCRE2's match limit per value) stops the pass here.
24 constexpr qint64 kEvaluationBudgetMs = 2000;
25
26 // Same rule shape: same field (header list, address part) and match type.
27 13 bool sameShape(const Sieve::Condition &a, const Sieve::Condition &b) {
28 13 QStringList ah, bh;
29
2/2
✓ Branch 10 → 4 taken 13 times.
✓ Branch 10 → 11 taken 13 times.
26 for (const QString &h : a.headers)
30
2/2
✓ Branch 5 → 6 taken 13 times.
✓ Branch 6 → 7 taken 13 times.
13 ah.append(h.toLower());
31
2/2
✓ Branch 19 → 13 taken 13 times.
✓ Branch 19 → 20 taken 13 times.
26 for (const QString &h : b.headers)
32
2/2
✓ Branch 14 → 15 taken 13 times.
✓ Branch 15 → 16 taken 13 times.
13 bh.append(h.toLower());
33
4/7
✓ Branch 21 → 22 taken 12 times.
✓ Branch 22 → 23 taken 12 times.
✗ Branch 22 → 31 not taken.
✓ Branch 24 → 25 taken 12 times.
✗ Branch 24 → 31 not taken.
✓ Branch 26 → 27 taken 12 times.
✗ Branch 26 → 31 not taken.
12 return a.kind == b.kind && ah == bh && a.addressPart == b.addressPart && a.matchType == b.matchType &&
34
4/6
✓ Branch 20 → 21 taken 12 times.
✓ Branch 20 → 31 taken 1 time.
✓ Branch 27 → 28 taken 12 times.
✗ Branch 27 → 31 not taken.
✓ Branch 29 → 30 taken 12 times.
✗ Branch 29 → 31 not taken.
38 a.negated == b.negated && a.comparator == b.comparator;
35 13 }
36
37 } // namespace
38
39 95 QString QuickRulesBackend::sieveNameFor(const QString &imapPath) const {
40
2/2
✓ Branch 2 → 3 taken 95 times.
✓ Branch 3 → 4 taken 95 times.
95 const QString name = mailboxDirectory().sieveName(imapPath);
41
2/2
✓ Branch 6 → 7 taken 8 times.
✓ Branch 6 → 8 taken 87 times.
190 return name.isEmpty() ? imapPath : name;
42 95 }
43
44 7 QString QuickRulesBackend::imapPathFor(const QString &sieveName) const {
45
2/2
✓ Branch 2 → 3 taken 7 times.
✓ Branch 3 → 4 taken 7 times.
7 const QString path = mailboxDirectory().imapPath(sieveName);
46
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 7 times.
14 return path.isEmpty() ? sieveName : path;
47 7 }
48
49 937 RuleMatcher::Message QuickRulesBackend::messageFor(const MailHeader &header,
50 const QMap<QString, QString> &fields) const {
51 937 RuleMatcher::Message message;
52
1/1
✓ Branch 2 → 3 taken 937 times.
937 message.allHeaders = !fields.isEmpty();
53
2/2
✓ Branch 3 → 4 taken 709 times.
✓ Branch 3 → 5 taken 228 times.
937 message.size = header.size > 0 ? header.size : -1;
54
3/5
✓ Branch 11 → 12 taken 937 times.
✓ Branch 16 → 17 taken 937 times.
✓ Branch 16 → 18 taken 937 times.
✗ Branch 111 → 112 not taken.
✗ Branch 111 → 113 not taken.
1874 message.headers.insert(QStringLiteral("from"), {header.from});
55
3/5
✓ Branch 23 → 24 taken 937 times.
✓ Branch 28 → 29 taken 937 times.
✓ Branch 28 → 30 taken 937 times.
✗ Branch 130 → 131 not taken.
✗ Branch 130 → 132 not taken.
1874 message.headers.insert(QStringLiteral("to"), {header.to});
56
3/5
✓ Branch 35 → 36 taken 937 times.
✓ Branch 40 → 41 taken 937 times.
✓ Branch 40 → 42 taken 937 times.
✗ Branch 149 → 150 not taken.
✗ Branch 149 → 151 not taken.
1874 message.headers.insert(QStringLiteral("subject"), {header.subject});
57 // S95.1.11: rows synced since schema v24 also know every Cc address and
58 // the List-Id; an empty list means "known to be absent".
59
2/2
✓ Branch 42 → 43 taken 1 time.
✓ Branch 42 → 83 taken 936 times.
937 if (header.recipientsKnown) {
60
3/11
✓ Branch 44 → 45 taken 1 time.
✗ Branch 44 → 46 not taken.
✓ Branch 51 → 52 taken 1 time.
✗ Branch 55 → 56 not taken.
✓ Branch 55 → 59 taken 1 time.
✗ Branch 57 → 58 not taken.
✗ Branch 57 → 59 not taken.
✗ Branch 167 → 168 not taken.
✗ Branch 167 → 171 not taken.
✗ Branch 169 → 170 not taken.
✗ Branch 169 → 171 not taken.
2 message.headers.insert(QStringLiteral("cc"), header.cc.isEmpty() ? QStringList{} : QStringList{header.cc});
61
1/1
✓ Branch 72 → 73 taken 1 time.
1 message.headers.insert(QStringLiteral("list-id"),
62
6/15
✗ Branch 60 → 61 not taken.
✓ Branch 60 → 62 taken 1 time.
✓ Branch 66 → 67 taken 1 time.
✓ Branch 76 → 77 taken 1 time.
✗ Branch 76 → 80 not taken.
✓ Branch 78 → 79 taken 1 time.
✓ Branch 78 → 80 taken 1 time.
✓ Branch 80 → 81 taken 1 time.
✗ Branch 80 → 82 not taken.
✗ Branch 188 → 189 not taken.
✗ Branch 188 → 192 not taken.
✗ Branch 190 → 191 not taken.
✗ Branch 190 → 192 not taken.
✗ Branch 194 → 195 not taken.
✗ Branch 194 → 196 not taken.
4 header.listId.isEmpty() ? QStringList{} : QStringList{QLatin1Char('<') + header.listId + QLatin1Char('>')});
63 }
64
4/4
✓ Branch 83 → 84 taken 937 times.
✓ Branch 97 → 98 taken 2306 times.
✓ Branch 99 → 85 taken 1369 times.
✓ Branch 99 → 100 taken 937 times.
2306 for (auto it = fields.cbegin(); it != fields.cend(); ++it)
65
4/6
✓ Branch 89 → 90 taken 1369 times.
✓ Branch 90 → 91 taken 1369 times.
✓ Branch 94 → 95 taken 1369 times.
✓ Branch 94 → 96 taken 1369 times.
✗ Branch 215 → 216 not taken.
✗ Branch 215 → 217 not taken.
2738 message.headers.insert(it.key().toLower(), {it.value()});
66 937 return message;
67 4183 }
68
69 // --- rule from the displayed mail -------------------------------------------------
70
71 15 QVariantMap QuickRulesBackend::mailRuleContext() {
72
2/3
✓ Branch 3 → 4 taken 15 times.
✗ Branch 3 → 5 not taken.
✓ Branch 4 → 6 taken 15 times.
15 const auto mail = m_deps.displayedMail ? m_deps.displayedMail() : Deps::DisplayedMail{};
73
2/3
✓ Branch 7 → 8 taken 15 times.
✗ Branch 7 → 9 not taken.
✓ Branch 8 → 10 taken 15 times.
15 const auto account = m_deps.account ? m_deps.account() : std::nullopt;
74 15 QStringList own;
75
1/2
✓ Branch 11 → 12 taken 15 times.
✗ Branch 11 → 21 not taken.
15 if (account)
76
2/4
✓ Branch 18 → 19 taken 30 times.
✓ Branch 18 → 20 taken 15 times.
✗ Branch 240 → 241 not taken.
✗ Branch 240 → 242 not taken.
45 own = {account->email, account->imap.username};
77
1/1
✓ Branch 21 → 22 taken 15 times.
15 const RuleDraftFactory::MailFacts facts = RuleDraftFactory::factsFromHeaders(mail.headers);
78
1/1
✓ Branch 22 → 23 taken 15 times.
15 const QList<RuleDraftFactory::Chip> chips = RuleDraftFactory::chips(facts, own);
79 // Folder: the prediction, else the mail's folder unless it is the inbox.
80 15 QString folder = mail.predictedFolder;
81
4/6
✓ Branch 25 → 26 taken 9 times.
✓ Branch 25 → 30 taken 6 times.
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 30 taken 9 times.
✗ Branch 31 → 32 not taken.
✓ Branch 31 → 33 taken 15 times.
15 if (folder.isEmpty() && mail.folderPath.compare(QLatin1String("INBOX"), Qt::CaseInsensitive) != 0)
82 ✗ folder = mail.folderPath;
83
1/1
✓ Branch 33 → 34 taken 15 times.
15 const SieveSession *s = session();
84
4/7
✓ Branch 38 → 39 taken 15 times.
✗ Branch 38 → 43 not taken.
✓ Branch 39 → 40 taken 15 times.
✗ Branch 39 → 43 not taken.
✓ Branch 40 → 41 taken 15 times.
✓ Branch 41 → 42 taken 15 times.
✗ Branch 41 → 43 not taken.
30 QVariantMap context{{QStringLiteral("available"), available() && s != nullptr && !readOnly()},
85
1/1
✓ Branch 48 → 49 taken 15 times.
30 {QStringLiteral("chips"), RuleDraftFactory::chipMaps(chips)},
86
3/3
✓ Branch 54 → 55 taken 9 times.
✓ Branch 54 → 56 taken 6 times.
✓ Branch 56 → 57 taken 6 times.
30 {QStringLiteral("folder"), folder.isEmpty() ? QString() : sieveNameFor(folder)},
87 30 {QStringLiteral("predicted"), !mail.predictedFolder.isEmpty()},
88
2/6
✗ Branch 67 → 68 not taken.
✓ Branch 67 → 71 taken 15 times.
✗ Branch 126 → 127 not taken.
✓ Branch 126 → 128 taken 15 times.
✗ Branch 296 → 297 not taken.
✗ Branch 296 → 298 not taken.
30 {QStringLiteral("scopePath"), mail.folderPath.isEmpty() ? QStringLiteral("INBOX") : mail.folderPath},
89
3/7
✗ Branch 77 → 78 not taken.
✓ Branch 77 → 81 taken 15 times.
✓ Branch 82 → 83 taken 15 times.
✗ Branch 121 → 122 not taken.
✓ Branch 121 → 123 taken 15 times.
✗ Branch 282 → 283 not taken.
✗ Branch 282 → 284 not taken.
30 {QStringLiteral("scopeName"), sieveNameFor(mail.folderPath.isEmpty() ? QStringLiteral("INBOX") : mail.folderPath)},
90
2/4
✓ Branch 87 → 88 taken 15 times.
✗ Branch 87 → 91 not taken.
✓ Branch 89 → 90 taken 15 times.
✗ Branch 89 → 91 not taken.
30 {QStringLiteral("stop"), s && s->document().style.stopByDefault},
91 30 {QStringLiteral("sender"), facts.sender},
92
5/7
✓ Branch 101 → 102 taken 9 times.
✓ Branch 101 → 106 taken 6 times.
✓ Branch 105 → 107 taken 9 times.
✓ Branch 110 → 111 taken 135 times.
✓ Branch 110 → 112 taken 15 times.
✗ Branch 250 → 251 not taken.
✗ Branch 250 → 252 not taken.
174 {QStringLiteral("name"), facts.listId.isEmpty() ? facts.sender.section(QLatin1Char('@'), 1) : facts.listId}};
93
2/4
✓ Branch 140 → 141 taken 15 times.
✗ Branch 140 → 142 not taken.
✗ Branch 141 → 142 not taken.
✓ Branch 141 → 143 taken 15 times.
15 if (!s || mail.uid < 0)
94 ✗ return context;
95 // (a) Already handled: rules that would act on this mail now.
96 15 MailHeader header;
97
1/1
✓ Branch 148 → 149 taken 15 times.
30 header.from = mail.headers.value(QStringLiteral("from"));
98
1/1
✓ Branch 158 → 159 taken 15 times.
30 header.to = mail.headers.value(QStringLiteral("to"));
99
1/1
✓ Branch 168 → 169 taken 15 times.
30 header.subject = mail.headers.value(QStringLiteral("subject"));
100
2/2
✓ Branch 174 → 175 taken 15 times.
✓ Branch 176 → 177 taken 15 times.
15 const QList<int> hits = RuleMatcher::matchingBlocks(s->document(), messageFor(header, mail.headers));
101
2/2
✓ Branch 228 → 180 taken 5 times.
✓ Branch 228 → 229 taken 10 times.
15 for (const int index : hits) {
102 5 const Sieve::Block &block = s->document().blocks.at(index);
103
2/3
✓ Branch 183 → 184 taken 5 times.
✗ Branch 186 → 187 not taken.
✓ Branch 186 → 188 taken 5 times.
5 if (RuleSentence::target(block.rule).isEmpty())
104 ✗ continue;
105
1/1
✓ Branch 209 → 210 taken 5 times.
10 context.insert(QStringLiteral("handledBy"),
106 15 QVariantMap{{QStringLiteral("blockIndex"), index},
107
1/3
✓ Branch 195 → 196 taken 5 times.
✗ Branch 195 → 197 not taken.
✗ Branch 197 → 198 not taken.
10 {QStringLiteral("name"), block.named ? block.name : RuleSentence::generatedName(block.rule)},
108
3/5
✓ Branch 202 → 203 taken 5 times.
✓ Branch 215 → 216 taken 15 times.
✓ Branch 215 → 217 taken 5 times.
✗ Branch 390 → 391 not taken.
✗ Branch 390 → 392 not taken.
25 {QStringLiteral("target"), RuleSentence::target(block.rule)}});
109 5 break;
110 }
111 15 return context;
112 200 }
113
114 1 QVariantMap QuickRulesBackend::searchRuleContext(const QString &query) {
115 1 QStringList skipped;
116
1/1
✓ Branch 2 → 3 taken 1 time.
1 const SieveSession *s = session();
117
4/8
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 11 not taken.
✓ Branch 9 → 10 taken 1 time.
✗ Branch 9 → 11 not taken.
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 14 not taken.
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 16 not taken.
2 const bool body = s && s->capabilities().supports(QStringLiteral("body"));
118
1/1
✓ Branch 16 → 17 taken 1 time.
1 const QList<QVariantMap> conditions = RuleDraftFactory::conditionsFromSearch(query, body, &skipped);
119 1 QVariantMap draft = RuleDraftFactory::draft(conditions, QString(), false, false,
120
3/5
✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 22 not taken.
✓ Branch 20 → 21 taken 1 time.
✗ Branch 20 → 22 not taken.
✓ Branch 24 → 25 taken 1 time.
2 s && s->document().style.stopByDefault, QString());
121 // A sorting rule is the common case: start with an empty "Move to folder"
122 // the editor focuses (RJ6), so only the folder is left to type.
123
1/1
✓ Branch 46 → 47 taken 1 time.
2 draft.insert(QStringLiteral("actions"),
124
4/8
✓ Branch 52 → 53 taken 1 time.
✓ Branch 52 → 54 taken 1 time.
✓ Branch 56 → 57 taken 2 times.
✓ Branch 56 → 58 taken 1 time.
✗ Branch 110 → 111 not taken.
✗ Branch 110 → 112 not taken.
✗ Branch 116 → 117 not taken.
✗ Branch 116 → 118 not taken.
7 QVariantList{QVariantMap{{QStringLiteral("kind"), QStringLiteral("move")}, {QStringLiteral("target"), QString()}}});
125 3 return {{QStringLiteral("draft"), draft}, {QStringLiteral("skipped"), skipped},
126
2/4
✓ Branch 80 → 81 taken 3 times.
✓ Branch 80 → 82 taken 1 time.
✗ Branch 156 → 157 not taken.
✗ Branch 156 → 158 not taken.
6 {QStringLiteral("usable"), !conditions.isEmpty()}};
127 8 }
128
129 // --- evaluation, awareness, preview --------------------------------------------------
130
131 33 QVariantMap QuickRulesBackend::evaluateDraft(const QVariantMap &draft, const QString &scopePath) {
132 33 QVariantMap result;
133
1/1
✓ Branch 2 → 3 taken 33 times.
33 const SieveSession *s = session();
134
1/1
✓ Branch 3 → 4 taken 33 times.
33 const Sieve::Rule rule = prepareRule(draft);
135
2/2
✓ Branch 4 → 5 taken 33 times.
✓ Branch 9 → 10 taken 33 times.
66 result.insert(QStringLiteral("sentence"), RuleSentence::sentence(rule, RuleSentence::rich));
136
1/2
✓ Branch 14 → 15 taken 33 times.
✗ Branch 14 → 125 not taken.
33 if (s) {
137 33 const Sieve::Document &document = s->document();
138
1/1
✓ Branch 16 → 17 taken 33 times.
33 const QString target = RuleSentence::target(rule);
139
2/2
✓ Branch 122 → 18 taken 270 times.
✓ Branch 122 → 123 taken 33 times.
303 for (int i = 0; i < document.blocks.size(); ++i) {
140 270 const Sieve::Block &block = document.blocks.at(i);
141
4/4
✓ Branch 19 → 20 taken 187 times.
✓ Branch 19 → 21 taken 83 times.
✓ Branch 20 → 21 taken 26 times.
✓ Branch 20 → 22 taken 161 times.
270 if (block.kind != Sieve::BlockKind::Rule || !block.rule.enabled)
142 109 continue;
143
1/3
✓ Branch 22 → 23 taken 161 times.
✗ Branch 22 → 24 not taken.
✗ Branch 24 → 25 not taken.
161 const QString name = block.named ? block.name : RuleSentence::generatedName(block.rule);
144 // (b) A rule that files into the same folder with the same condition shape.
145
7/12
✓ Branch 31 → 32 taken 100 times.
✓ Branch 31 → 44 taken 49 times.
✓ Branch 34 → 35 taken 19 times.
✓ Branch 34 → 44 taken 81 times.
✓ Branch 49 → 50 taken 161 times.
✗ Branch 49 → 51 not taken.
✓ Branch 51 → 52 taken 10 times.
✓ Branch 51 → 78 taken 151 times.
✗ Branch 257 → 258 not taken.
✗ Branch 257 → 259 not taken.
✗ Branch 261 → 262 not taken.
✗ Branch 261 → 263 not taken.
571 if (!result.contains(QStringLiteral("addTo")) && !target.isEmpty() &&
146
5/9
✓ Branch 32 → 33 taken 100 times.
✓ Branch 36 → 37 taken 10 times.
✓ Branch 36 → 44 taken 9 times.
✓ Branch 38 → 39 taken 10 times.
✗ Branch 38 → 44 not taken.
✓ Branch 47 → 48 taken 161 times.
✗ Branch 47 → 49 not taken.
✗ Branch 253 → 254 not taken.
✗ Branch 253 → 255 not taken.
290 RuleSentence::target(block.rule) == target && block.rule.conditions.size() == 1 &&
147
7/8
✓ Branch 28 → 29 taken 161 times.
✓ Branch 29 → 30 taken 149 times.
✓ Branch 29 → 44 taken 12 times.
✓ Branch 41 → 42 taken 10 times.
✓ Branch 42 → 43 taken 10 times.
✗ Branch 42 → 44 not taken.
✓ Branch 45 → 46 taken 100 times.
✓ Branch 45 → 47 taken 61 times.
481 rule.conditions.size() == 1 && sameShape(block.rule.conditions.first(), rule.conditions.first()))
148
1/1
✓ Branch 65 → 66 taken 10 times.
40 result.insert(QStringLiteral("addTo"), QVariantMap{{QStringLiteral("blockIndex"), i},
149
2/4
✓ Branch 71 → 72 taken 20 times.
✓ Branch 71 → 73 taken 10 times.
✗ Branch 276 → 277 not taken.
✗ Branch 276 → 278 not taken.
50 {QStringLiteral("name"), name}});
150 // (c) An earlier rule with stop that catches the new rule's mails.
151
10/16
✓ Branch 81 → 82 taken 161 times.
✓ Branch 82 → 83 taken 125 times.
✓ Branch 82 → 86 taken 36 times.
✓ Branch 83 → 84 taken 125 times.
✓ Branch 84 → 85 taken 11 times.
✓ Branch 84 → 86 taken 114 times.
✓ Branch 87 → 88 taken 161 times.
✗ Branch 87 → 89 not taken.
✓ Branch 89 → 90 taken 161 times.
✗ Branch 89 → 91 not taken.
✓ Branch 91 → 92 taken 11 times.
✓ Branch 91 → 118 taken 150 times.
✗ Branch 299 → 300 not taken.
✗ Branch 299 → 301 not taken.
✗ Branch 303 → 304 not taken.
✗ Branch 303 → 305 not taken.
322 if (!result.contains(QStringLiteral("shadowedBy")) && RuleMatcher::shadows(block.rule, rule))
152
1/1
✓ Branch 105 → 106 taken 11 times.
44 result.insert(QStringLiteral("shadowedBy"), QVariantMap{{QStringLiteral("blockIndex"), i},
153
2/4
✓ Branch 111 → 112 taken 22 times.
✓ Branch 111 → 113 taken 11 times.
✗ Branch 318 → 319 not taken.
✗ Branch 318 → 320 not taken.
55 {QStringLiteral("name"), name}});
154 161 }
155 33 }
156 // Live preview over the cached headers of the scope folder (D-11).
157 33 int count = 0;
158 33 int unknown = 0;
159 33 bool stopped = false;
160
3/6
✓ Branch 126 → 127 taken 33 times.
✗ Branch 126 → 130 not taken.
✓ Branch 128 → 129 taken 33 times.
✗ Branch 128 → 130 not taken.
✓ Branch 131 → 132 taken 33 times.
✗ Branch 131 → 184 not taken.
33 if (m_deps.cachedHeaders && !scopePath.isEmpty()) {
161 33 QElapsedTimer budget;
162 33 budget.start();
163
1/1
✓ Branch 133 → 134 taken 33 times.
33 const QList<MailHeader> headers = m_deps.cachedHeaders(scopePath);
164
2/2
✓ Branch 181 → 135 taken 258 times.
✓ Branch 181 → 182 taken 33 times.
291 for (qsizetype i = 0; i < headers.size(); ++i) {
165
1/2
✗ Branch 136 → 137 not taken.
✓ Branch 136 → 154 taken 258 times.
258 if (budget.elapsed() > kEvaluationBudgetMs) {
166 ✗ unknown += int(headers.size() - i); // not evaluated
167 ✗ stopped = true;
168 ✗ qCInfo(lcRulesBackend) << "Rule preview stopped after" << i << "of" << headers.size() << "messages";
169 ✗ break;
170 }
171 258 const MailHeader &header = headers.at(i);
172 258 QMap<QString, QString> fields;
173
2/2
✓ Branch 155 → 156 taken 258 times.
✓ Branch 156 → 157 taken 258 times.
258 RuleMatcher::Result match = RuleMatcher::evaluate(rule, messageFor(header, fields));
174
5/6
✓ Branch 158 → 159 taken 44 times.
✓ Branch 158 → 162 taken 214 times.
✓ Branch 160 → 161 taken 44 times.
✗ Branch 160 → 162 not taken.
✓ Branch 163 → 164 taken 44 times.
✓ Branch 163 → 173 taken 214 times.
258 if (match == RuleMatcher::Result::Unknown && m_deps.cachedHeaderFields) {
175
1/1
✓ Branch 164 → 165 taken 44 times.
44 fields = m_deps.cachedHeaderFields(header.folderId, header.uid);
176
3/3
✓ Branch 167 → 168 taken 44 times.
✓ Branch 168 → 169 taken 41 times.
✓ Branch 168 → 173 taken 3 times.
44 if (!fields.isEmpty())
177
2/2
✓ Branch 169 → 170 taken 41 times.
✓ Branch 170 → 171 taken 41 times.
41 match = RuleMatcher::evaluate(rule, messageFor(header, fields));
178 }
179
2/2
✓ Branch 173 → 174 taken 38 times.
✓ Branch 173 → 175 taken 220 times.
258 if (match == RuleMatcher::Result::Match)
180 38 ++count;
181
2/2
✓ Branch 175 → 176 taken 3 times.
✓ Branch 175 → 177 taken 217 times.
220 else if (match == RuleMatcher::Result::Unknown)
182 3 ++unknown;
183 258 }
184 33 }
185
1/1
✓ Branch 188 → 189 taken 33 times.
66 result.insert(QStringLiteral("matchCount"), count);
186
1/1
✓ Branch 196 → 197 taken 33 times.
66 result.insert(QStringLiteral("unknownCount"), unknown);
187
1/1
✓ Branch 204 → 205 taken 33 times.
66 result.insert(QStringLiteral("stopped"), stopped);
188
2/2
✓ Branch 208 → 209 taken 33 times.
✓ Branch 213 → 214 taken 33 times.
66 result.insert(QStringLiteral("scopeName"), sieveNameFor(scopePath));
189
2/2
✓ Branch 219 → 220 taken 27 times.
✓ Branch 219 → 225 taken 6 times.
60 const bool movable = !rule.actions.isEmpty() &&
190
3/3
✓ Branch 222 → 223 taken 27 times.
✓ Branch 223 → 224 taken 22 times.
✓ Branch 223 → 225 taken 5 times.
27 std::all_of(rule.actions.cbegin(), rule.actions.cend(), [](const Sieve::Action &a) {
191
2/2
✓ Branch 3 → 4 taken 5 times.
✓ Branch 3 → 6 taken 10 times.
15 return a.kind == Sieve::Action::Kind::FileInto || a.kind == Sieve::Action::Kind::AddFlag ||
192
4/6
✓ Branch 2 → 3 taken 15 times.
✓ Branch 2 → 6 taken 22 times.
✓ Branch 4 → 5 taken 5 times.
✗ Branch 4 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 5 times.
52 a.kind == Sieve::Action::Kind::RemoveFlag || a.kind == Sieve::Action::Kind::Keep;
193 33 });
194
5/5
✓ Branch 226 → 227 taken 22 times.
✓ Branch 226 → 229 taken 11 times.
✓ Branch 227 → 228 taken 20 times.
✓ Branch 227 → 229 taken 2 times.
✓ Branch 234 → 235 taken 33 times.
66 result.insert(QStringLiteral("canApply"), movable && count > 0); // never forward/delete/reject (D-11)
195 33 return result;
196 96 }
197
198 8 QString QuickRulesBackend::applyToExisting(const QVariantMap &draft, const QString &scopePath) {
199
1/1
✓ Branch 2 → 3 taken 8 times.
8 const QString requestId = nextRequestId();
200 8 const bool announceNone = std::exchange(m_announceNextApply, false);
201
1/1
✓ Branch 4 → 5 taken 8 times.
8 const Sieve::Rule rule = prepareRule(draft);
202 8 QList<qint64> uids;
203 8 bool stopped = false;
204 8 QElapsedTimer budget;
205 8 budget.start();
206
1/2
✓ Branch 7 → 8 taken 8 times.
✗ Branch 7 → 43 not taken.
8 if (m_deps.cachedHeaders)
207
5/5
✓ Branch 8 → 9 taken 8 times.
✓ Branch 9 → 10 taken 8 times.
✓ Branch 10 → 11 taken 8 times.
✓ Branch 40 → 12 taken 29 times.
✓ Branch 40 → 41 taken 8 times.
37 for (const MailHeader &header : m_deps.cachedHeaders(scopePath)) {
208
1/2
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 29 times.
29 if (budget.elapsed() > kEvaluationBudgetMs) {
209 ✗ stopped = true; // S106-323: the matches so far are applied
210 ✗ break;
211 }
212
2/2
✓ Branch 17 → 18 taken 29 times.
✓ Branch 18 → 19 taken 29 times.
29 RuleMatcher::Result match = RuleMatcher::evaluate(rule, messageFor(header, {}));
213
5/6
✓ Branch 21 → 22 taken 14 times.
✓ Branch 21 → 25 taken 15 times.
✓ Branch 23 → 24 taken 14 times.
✗ Branch 23 → 25 not taken.
✓ Branch 26 → 27 taken 14 times.
✓ Branch 26 → 33 taken 15 times.
29 if (match == RuleMatcher::Result::Unknown && m_deps.cachedHeaderFields)
214
3/3
✓ Branch 27 → 28 taken 14 times.
✓ Branch 28 → 29 taken 14 times.
✓ Branch 29 → 30 taken 14 times.
14 match = RuleMatcher::evaluate(rule, messageFor(header, m_deps.cachedHeaderFields(header.folderId, header.uid)));
215
2/2
✓ Branch 33 → 34 taken 11 times.
✓ Branch 33 → 35 taken 18 times.
29 if (match == RuleMatcher::Result::Match)
216
1/1
✓ Branch 34 → 35 taken 11 times.
11 uids.append(header.uid);
217
1/2
✗ Branch 36 → 37 not taken.
✓ Branch 36 → 38 taken 29 times.
29 if (uids.size() >= kApplyLimit)
218 ✗ break;
219 8 }
220 8 QString moveTarget;
221 // S106-321: what the run really changed decides the answer — a rule whose
222 // only actions are never applied retroactively (copy, redirect, discard,
223 // reject) or whose target is the scope itself changes nothing.
224 8 bool changed = false;
225
2/2
✓ Branch 102 → 45 taken 9 times.
✓ Branch 102 → 103 taken 8 times.
17 for (const Sieve::Action &action : rule.actions) {
226
3/4
✓ Branch 46 → 47 taken 8 times.
✓ Branch 46 → 49 taken 1 time.
✓ Branch 47 → 48 taken 8 times.
✗ Branch 47 → 49 not taken.
9 if (action.kind == Sieve::Action::Kind::Redirect || action.kind == Sieve::Action::Kind::Discard ||
227
1/2
✗ Branch 48 → 49 not taken.
✓ Branch 48 → 50 taken 8 times.
8 action.kind == Sieve::Action::Kind::Reject)
228 1 continue; // never applied retroactively (D-11, G10)
229
3/4
✓ Branch 50 → 51 taken 7 times.
✓ Branch 50 → 65 taken 1 time.
✓ Branch 51 → 52 taken 7 times.
✗ Branch 51 → 65 not taken.
8 if (action.kind == Sieve::Action::Kind::FileInto && !action.copy) {
230 7 const QString path = !action.specialUse.isEmpty()
231
1/5
✗ Branch 54 → 55 not taken.
✗ Branch 57 → 58 not taken.
✓ Branch 57 → 59 taken 7 times.
✗ Branch 173 → 174 not taken.
✗ Branch 173 → 175 not taken.
7 ? mailboxDirectory().folderForSpecialUse(action.specialUse)
232
2/4
✗ Branch 53 → 54 not taken.
✓ Branch 53 → 56 taken 7 times.
✗ Branch 55 → 57 not taken.
✓ Branch 56 → 57 taken 7 times.
7 : imapPathFor(action.target);
233
1/2
✓ Branch 60 → 61 taken 7 times.
✗ Branch 60 → 62 not taken.
7 if (!path.isEmpty())
234 7 moveTarget = path;
235 7 continue;
236 7 }
237
3/8
✓ Branch 66 → 67 taken 1 time.
✗ Branch 66 → 69 not taken.
✗ Branch 67 → 68 not taken.
✓ Branch 67 → 70 taken 1 time.
✗ Branch 68 → 69 not taken.
✗ Branch 68 → 70 not taken.
✗ Branch 71 → 72 not taken.
✓ Branch 71 → 73 taken 1 time.
1 if (!m_deps.flagMail || (action.kind != Sieve::Action::Kind::AddFlag && action.kind != Sieve::Action::Kind::RemoveFlag))
238 ✗ continue;
239
4/4
✓ Branch 73 → 74 taken 1 time.
✓ Branch 74 → 75 taken 1 time.
✓ Branch 98 → 76 taken 3 times.
✓ Branch 98 → 99 taken 1 time.
4 for (const qint64 uid : uids)
240
2/2
✓ Branch 95 → 79 taken 3 times.
✓ Branch 95 → 96 taken 3 times.
6 for (const QString &flag : action.flags) {
241
4/11
✓ Branch 80 → 81 taken 3 times.
✗ Branch 80 → 84 not taken.
✓ Branch 87 → 88 taken 3 times.
✗ Branch 89 → 90 not taken.
✓ Branch 89 → 91 taken 3 times.
✓ Branch 91 → 92 taken 3 times.
✗ Branch 91 → 93 not taken.
✗ Branch 180 → 181 not taken.
✗ Branch 180 → 182 not taken.
✗ Branch 184 → 185 not taken.
✗ Branch 184 → 186 not taken.
6 m_deps.flagMail(scopePath, uid, action.kind == Sieve::Action::Kind::AddFlag ? QStringLiteral("add") : QStringLiteral("remove"), flag);
242 3 changed = true;
243 }
244 }
245 // Flags first, then one undoable move batch.
246
6/8
✓ Branch 104 → 105 taken 7 times.
✓ Branch 104 → 112 taken 1 time.
✓ Branch 106 → 107 taken 7 times.
✗ Branch 106 → 112 not taken.
✓ Branch 108 → 109 taken 7 times.
✗ Branch 108 → 112 not taken.
✓ Branch 110 → 111 taken 5 times.
✓ Branch 110 → 112 taken 2 times.
8 const bool moving = !moveTarget.isEmpty() && moveTarget != scopePath && m_deps.moveMails && !uids.isEmpty();
247
2/2
✓ Branch 113 → 114 taken 5 times.
✓ Branch 113 → 116 taken 3 times.
8 if (moving) {
248
1/1
✓ Branch 114 → 115 taken 5 times.
5 m_deps.moveMails(scopePath, uids, moveTarget);
249 5 changed = true;
250 }
251
2/4
✓ Branch 117 → 118 taken 8 times.
✗ Branch 117 → 119 not taken.
✗ Branch 118 → 119 not taken.
✓ Branch 118 → 120 taken 8 times.
8 const bool truncated = uids.size() >= kApplyLimit || stopped;
252
2/2
✓ Branch 121 → 122 taken 5 times.
✓ Branch 121 → 124 taken 3 times.
8 const int applied = changed ? int(uids.size()) : 0;
253
9/9
✓ Branch 125 → 126 taken 8 times.
✓ Branch 129 → 130 taken 8 times.
✓ Branch 130 → 131 taken 8 times.
✓ Branch 131 → 132 taken 8 times.
✓ Branch 132 → 133 taken 8 times.
✓ Branch 134 → 135 taken 8 times.
✓ Branch 135 → 136 taken 8 times.
✓ Branch 143 → 127 taken 8 times.
✓ Branch 143 → 144 taken 8 times.
16 qCInfo(lcRulesBackend) << "Applied a rule to" << applied << "existing messages of" << uids.size() << "matches;"
254
2/3
✗ Branch 136 → 137 not taken.
✓ Branch 136 → 138 taken 8 times.
✓ Branch 139 → 140 taken 8 times.
8 << (truncated ? "stopped at the limit" : "all");
255
4/4
✓ Branch 145 → 146 taken 6 times.
✓ Branch 145 → 148 taken 2 times.
✓ Branch 146 → 147 taken 1 time.
✓ Branch 146 → 148 taken 5 times.
8 const bool nothingToDo = !uids.isEmpty() && !changed;
256
1/5
✓ Branch 151 → 152 taken 8 times.
✗ Branch 199 → 200 not taken.
✗ Branch 199 → 201 not taken.
✗ Branch 202 → 203 not taken.
✗ Branch 202 → 204 not taken.
8 QTimer::singleShot(0, this, [this, requestId, applied, moving, moveTarget, announceNone, truncated, nothingToDo] {
257 7 QString text = nothingToDo ? tr("This rule has no action that can be applied to messages already received.")
258
6/10
✓ Branch 4 → 5 taken 4 times.
✓ Branch 4 → 9 taken 2 times.
✓ Branch 5 → 6 taken 4 times.
✓ Branch 7 → 8 taken 4 times.
✓ Branch 10 → 11 taken 4 times.
✓ Branch 10 → 12 taken 3 times.
✗ Branch 60 → 61 not taken.
✗ Branch 60 → 62 not taken.
✗ Branch 65 → 66 not taken.
✗ Branch 65 → 67 not taken.
21 : moving ? tr("Existing messages moved to %1: %n", nullptr, applied).arg(sieveNameFor(moveTarget))
259
7/7
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 6 times.
✓ Branch 3 → 10 taken 1 time.
✓ Branch 8 → 10 taken 4 times.
✓ Branch 9 → 10 taken 2 times.
✓ Branch 12 → 13 taken 4 times.
✓ Branch 12 → 14 taken 3 times.
24 : tr("Applied to existing messages: %n", nullptr, applied);
260
1/4
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 24 taken 7 times.
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 24 not taken.
7 if (truncated && applied > 0)
261 ✗ text += QLatin1Char(' ') + tr("Only the first %n were changed; apply the rule again for the rest.", nullptr,
262 ✗ applied);
263
3/5
✓ Branch 37 → 38 taken 7 times.
✓ Branch 40 → 41 taken 21 times.
✓ Branch 40 → 42 taken 7 times.
✗ Branch 80 → 81 not taken.
✗ Branch 80 → 82 not taken.
49 emit editFinished(requestId, {{QStringLiteral("ok"), !nothingToDo}, {QStringLiteral("count"), applied},
264 14 {QStringLiteral("message"), text}});
265
6/6
✓ Branch 48 → 49 taken 3 times.
✓ Branch 48 → 51 taken 4 times.
✓ Branch 49 → 50 taken 2 times.
✓ Branch 49 → 51 taken 1 time.
✓ Branch 50 → 51 taken 1 time.
✓ Branch 50 → 58 taken 1 time.
7 if (applied > 0 || nothingToDo || announceNone)
266
6/6
✓ Branch 51 → 52 taken 2 times.
✓ Branch 51 → 53 taken 4 times.
✓ Branch 52 → 53 taken 1 time.
✓ Branch 52 → 54 taken 1 time.
✓ Branch 54 → 55 taken 1 time.
✓ Branch 55 → 56 taken 6 times.
6 emit toast(applied > 0 || nothingToDo ? text : tr("No message in the inbox matches this rule."), false);
267
1/5
✓ Branch 36 → 37 taken 7 times.
✗ Branch 102 → 103 not taken.
✗ Branch 102 → 106 not taken.
✗ Branch 104 → 105 not taken.
✗ Branch 104 → 106 not taken.
35 });
268 8 return requestId;
269 8 }
270
271 // Incoming-mail rules act on the inbox; a run without matches is announced
272 // too, so the click always gets an answer.
273 3 QString QuickRulesBackend::applyRuleNow(int blockIndex) {
274 3 SieveSession *s = session();
275
6/8
✓ Branch 3 → 4 taken 3 times.
✗ Branch 3 → 11 not taken.
✓ Branch 4 → 5 taken 3 times.
✗ Branch 4 → 11 not taken.
✓ Branch 7 → 8 taken 2 times.
✓ Branch 7 → 11 taken 1 time.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 19 taken 2 times.
5 if (!s || blockIndex < 0 || blockIndex >= s->document().blocks.size() ||
276
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 2 times.
2 s->document().blocks.at(blockIndex).kind != Sieve::BlockKind::Rule)
277
2/2
✓ Branch 14 → 15 taken 1 time.
✓ Branch 15 → 16 taken 1 time.
1 return refuseEdit(tr("This rule no longer exists."));
278
6/6
✓ Branch 19 → 20 taken 2 times.
✓ Branch 23 → 24 taken 2 times.
✓ Branch 24 → 25 taken 2 times.
✓ Branch 25 → 26 taken 2 times.
✓ Branch 29 → 21 taken 2 times.
✓ Branch 29 → 30 taken 2 times.
4 qCInfo(lcRulesBackend) << "Apply rule now: block" << blockIndex;
279 2 m_announceNextApply = true;
280
2/2
✓ Branch 33 → 34 taken 2 times.
✓ Branch 34 → 35 taken 2 times.
2 return applyToExisting(draftFor(blockIndex), QStringLiteral("INBOX"));
281 }
282
283 4 QString QuickRulesBackend::addToRule(int blockIndex, const QVariantMap &condition) {
284
1/1
✓ Branch 2 → 3 taken 4 times.
4 SieveSession *s = session();
285
6/8
✓ Branch 3 → 4 taken 4 times.
✗ Branch 3 → 11 not taken.
✓ Branch 4 → 5 taken 4 times.
✗ Branch 4 → 11 not taken.
✓ Branch 7 → 8 taken 3 times.
✓ Branch 7 → 11 taken 1 time.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 19 taken 3 times.
7 if (!s || blockIndex < 0 || blockIndex >= s->document().blocks.size() ||
286
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 3 times.
3 s->document().blocks.at(blockIndex).kind != Sieve::BlockKind::Rule)
287
2/2
✓ Branch 14 → 15 taken 1 time.
✓ Branch 15 → 16 taken 1 time.
1 return refuseEdit(tr("This rule no longer exists."));
288 3 Sieve::Document document = s->document();
289
1/1
✓ Branch 21 → 22 taken 3 times.
3 Sieve::Block &block = document.blocks[blockIndex];
290
1/1
✓ Branch 22 → 23 taken 3 times.
3 const Sieve::Condition added = RuleEditorFields::toCondition(condition);
291
7/8
✓ Branch 24 → 25 taken 3 times.
✗ Branch 24 → 28 not taken.
✓ Branch 25 → 26 taken 3 times.
✓ Branch 26 → 27 taken 3 times.
✓ Branch 27 → 28 taken 1 time.
✓ Branch 27 → 29 taken 2 times.
✓ Branch 30 → 31 taken 1 time.
✓ Branch 30 → 36 taken 2 times.
3 if (block.rule.conditions.size() != 1 || !sameShape(block.rule.conditions.first(), added))
292
2/2
✓ Branch 31 → 32 taken 1 time.
✓ Branch 32 → 33 taken 1 time.
1 return refuseEdit(tr("The rule checks something else; create a new rule instead."));
293
2/2
✓ Branch 46 → 38 taken 2 times.
✓ Branch 46 → 47 taken 2 times.
4 for (const QString &value : added.values)
294
2/3
✓ Branch 39 → 40 taken 2 times.
✓ Branch 41 → 42 taken 2 times.
✗ Branch 41 → 44 not taken.
2 if (!block.rule.conditions.first().values.contains(value))
295
2/2
✓ Branch 42 → 43 taken 2 times.
✓ Branch 43 → 44 taken 2 times.
2 block.rule.conditions.first().values.append(value);
296 2 block.dirty = true;
297
1/2
✗ Branch 47 → 48 not taken.
✓ Branch 47 → 52 taken 2 times.
2 if (!block.named) {
298 ✗ block.name = RuleSentence::generatedName(block.rule);
299 ✗ block.named = true;
300 }
301 2 block.rule.name = block.name;
302 4 return submit({Sieve::encode(document), QStringLiteral("extend"), tr("Rule „%1“ extended").arg(block.name), true});
303 11 }
304
305 // S95.4.8: which rules match the displayed mail, in script order.
306 199 QVariantList QuickRulesBackend::matchingRulesForMail() {
307 199 QVariantList result;
308
1/1
✓ Branch 2 → 3 taken 199 times.
199 const SieveSession *s = session();
309
2/3
✓ Branch 4 → 5 taken 199 times.
✗ Branch 4 → 6 not taken.
✓ Branch 5 → 7 taken 199 times.
199 const auto mail = m_deps.displayedMail ? m_deps.displayedMail() : Deps::DisplayedMail{};
310
4/4
✓ Branch 7 → 8 taken 181 times.
✓ Branch 7 → 9 taken 18 times.
✓ Branch 8 → 9 taken 18 times.
✓ Branch 8 → 10 taken 163 times.
199 if (!s || mail.uid < 0)
311 36 return result;
312 163 MailHeader header;
313
1/1
✓ Branch 15 → 16 taken 163 times.
326 header.from = mail.headers.value(QStringLiteral("from"));
314
1/1
✓ Branch 25 → 26 taken 163 times.
326 header.to = mail.headers.value(QStringLiteral("to"));
315
1/1
✓ Branch 35 → 36 taken 163 times.
326 header.subject = mail.headers.value(QStringLiteral("subject"));
316 163 int skipped = 0;
317
6/6
✓ Branch 41 → 42 taken 163 times.
✓ Branch 43 → 44 taken 163 times.
✓ Branch 45 → 46 taken 163 times.
✓ Branch 46 → 47 taken 163 times.
✓ Branch 92 → 48 taken 93 times.
✓ Branch 92 → 93 taken 163 times.
256 for (const int index : RuleMatcher::matchingBlocks(s->document(), messageFor(header, mail.headers), &skipped)) {
318 93 const Sieve::Block &block = s->document().blocks.at(index);
319
1/1
✓ Branch 74 → 75 taken 93 times.
279 result.append(QVariantMap{{QStringLiteral("blockIndex"), index},
320
1/3
✓ Branch 58 → 59 taken 93 times.
✗ Branch 58 → 60 not taken.
✗ Branch 60 → 61 not taken.
186 {QStringLiteral("name"), block.named ? block.name : RuleSentence::generatedName(block.rule)},
321
1/1
✓ Branch 65 → 66 taken 93 times.
186 {QStringLiteral("sentence"), RuleSentence::sentence(block.rule, RuleSentence::rich)},
322
3/5
✓ Branch 70 → 71 taken 93 times.
✓ Branch 78 → 79 taken 372 times.
✓ Branch 78 → 80 taken 93 times.
✗ Branch 152 → 153 not taken.
✗ Branch 152 → 154 not taken.
651 {QStringLiteral("position"), positionOf(index)}});
323 163 }
324
2/2
✓ Branch 94 → 95 taken 59 times.
✓ Branch 94 → 110 taken 104 times.
163 if (skipped > 0)
325
3/5
✓ Branch 101 → 102 taken 59 times.
✓ Branch 105 → 106 taken 59 times.
✓ Branch 105 → 107 taken 59 times.
✗ Branch 202 → 203 not taken.
✗ Branch 202 → 204 not taken.
177 result.append(QVariantMap{{QStringLiteral("advanced"), skipped}});
326 163 return result;
327 782 }
328
329 // --- console rule mode (S95.4.3) -------------------------------------------------------
330
331 8 QString QuickRulesBackend::consoleSeed() {
332
2/3
✓ Branch 3 → 4 taken 8 times.
✗ Branch 3 → 5 not taken.
✓ Branch 4 → 6 taken 8 times.
8 const auto mail = m_deps.displayedMail ? m_deps.displayedMail() : Deps::DisplayedMail{};
333
2/2
✓ Branch 6 → 7 taken 8 times.
✓ Branch 7 → 8 taken 8 times.
16 return RuleConsoleInput::seedFromSender(RuleDraftFactory::factsFromHeaders(mail.headers).sender);
334 8 }
335
336 1 QString QuickRulesBackend::completeConsoleRule(const QString &text) const {
337
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 3 → 4 taken 1 time.
1 return RuleConsoleInput::completeFolder(text, folderChoices());
338 }
339
340 23 QVariantMap QuickRulesBackend::parseConsoleRule(const QString &text) {
341
1/1
✓ Branch 2 → 3 taken 23 times.
23 const SieveSession *s = session();
342
3/5
✓ Branch 3 → 4 taken 23 times.
✗ Branch 3 → 7 not taken.
✓ Branch 5 → 6 taken 23 times.
✗ Branch 5 → 7 not taken.
✓ Branch 8 → 9 taken 23 times.
23 const RuleConsoleInput::Result parsed = RuleConsoleInput::parse(text, s && s->document().style.stopByDefault);
343 69 QVariantMap result{{QStringLiteral("ok"), parsed.ok}, {QStringLiteral("applyNow"), parsed.applyNow},
344
2/4
✓ Branch 23 → 24 taken 69 times.
✓ Branch 23 → 25 taken 23 times.
✗ Branch 260 → 261 not taken.
✗ Branch 260 → 262 not taken.
115 {QStringLiteral("forwardTargets"), parsed.forwardTargets}};
345
2/2
✓ Branch 31 → 32 taken 8 times.
✓ Branch 31 → 41 taken 15 times.
23 if (!parsed.ok) {
346
1/1
✓ Branch 36 → 37 taken 8 times.
16 result.insert(QStringLiteral("error"), parsed.error);
347 8 return result;
348 }
349
1/1
✓ Branch 41 → 42 taken 15 times.
15 const Sieve::Rule rule = prepareRule(parsed.draft);
350
2/3
✓ Branch 43 → 44 taken 15 times.
✗ Branch 43 → 45 not taken.
✓ Branch 44 → 46 taken 15 times.
15 const auto mail = m_deps.displayedMail ? m_deps.displayedMail() : Deps::DisplayedMail{};
351
2/4
✗ Branch 47 → 48 not taken.
✓ Branch 47 → 51 taken 15 times.
✗ Branch 52 → 53 not taken.
✓ Branch 52 → 54 taken 15 times.
15 const QString scope = mail.folderPath.isEmpty() ? QStringLiteral("INBOX") : mail.folderPath;
352
1/1
✓ Branch 54 → 55 taken 15 times.
15 const QVariantMap evaluation = evaluateDraft(parsed.draft, scope);
353 15 QStringList preview;
354
4/4
✓ Branch 55 → 56 taken 15 times.
✓ Branch 57 → 58 taken 15 times.
✓ Branch 58 → 59 taken 15 times.
✓ Branch 59 → 60 taken 15 times.
30 preview.append(tr("When: %1").arg(RuleSentence::conditions(rule)));
355
4/4
✓ Branch 63 → 64 taken 15 times.
✓ Branch 65 → 66 taken 15 times.
✓ Branch 66 → 67 taken 15 times.
✓ Branch 67 → 68 taken 15 times.
30 preview.append(tr("Then: %1").arg(RuleSentence::actions(rule)));
356
2/2
✓ Branch 75 → 76 taken 15 times.
✓ Branch 76 → 77 taken 15 times.
30 const int count = evaluation.value(QStringLiteral("matchCount")).toInt();
357
2/2
✓ Branch 81 → 82 taken 2 times.
✓ Branch 81 → 91 taken 13 times.
15 if (parsed.applyNow)
358
4/4
✓ Branch 82 → 83 taken 2 times.
✓ Branch 84 → 85 taken 2 times.
✓ Branch 85 → 86 taken 2 times.
✓ Branch 86 → 87 taken 2 times.
4 preview.append(tr("Also sort the matching messages already in %1 (%n)", nullptr, count).arg(sieveNameFor(scope)));
359 else
360
2/2
✓ Branch 91 → 92 taken 13 times.
✓ Branch 95 → 96 taken 13 times.
26 preview.append(tr("Existing matches in %1: %n (+now applies the rule to them)", nullptr, count)
361
2/2
✓ Branch 93 → 94 taken 13 times.
✓ Branch 94 → 95 taken 13 times.
26 .arg(sieveNameFor(scope)));
362
3/3
✓ Branch 103 → 104 taken 15 times.
✓ Branch 106 → 107 taken 8 times.
✓ Branch 106 → 136 taken 7 times.
15 if (evaluation.contains(QStringLiteral("shadowedBy")))
363
2/2
✓ Branch 107 → 108 taken 8 times.
✓ Branch 122 → 123 taken 8 times.
16 preview.append(tr("Rule „%1“ above catches these messages first and stops.")
364
5/5
✓ Branch 113 → 114 taken 8 times.
✓ Branch 114 → 115 taken 8 times.
✓ Branch 119 → 120 taken 8 times.
✓ Branch 120 → 121 taken 8 times.
✓ Branch 121 → 122 taken 8 times.
32 .arg(evaluation.value(QStringLiteral("shadowedBy")).toMap().value(QStringLiteral("name")).toString()));
365
2/2
✓ Branch 147 → 138 taken 4 times.
✓ Branch 147 → 148 taken 15 times.
19 for (const QString &target : parsed.forwardTargets)
366
3/3
✓ Branch 139 → 140 taken 4 times.
✓ Branch 141 → 142 taken 4 times.
✓ Branch 142 → 143 taken 4 times.
8 preview.append(tr("Mails matching this rule will be sent to %1. They leave this mailbox.").arg(target));
367
2/3
✓ Branch 149 → 150 taken 15 times.
✗ Branch 149 → 151 not taken.
✓ Branch 150 → 152 taken 15 times.
15 const auto account = m_deps.account ? m_deps.account() : std::nullopt;
368
6/7
✓ Branch 152 → 153 taken 15 times.
✓ Branch 155 → 156 taken 15 times.
✗ Branch 155 → 158 not taken.
✓ Branch 159 → 160 taken 15 times.
✓ Branch 161 → 162 taken 15 times.
✓ Branch 162 → 163 taken 15 times.
✓ Branch 163 → 164 taken 15 times.
45 preview.append(tr("Account: %1 · rule %2 of %2").arg(account ? account->email : QString()).arg(ruleCount() + 1));
369
3/5
✓ Branch 173 → 174 taken 15 times.
✓ Branch 176 → 177 taken 15 times.
✓ Branch 176 → 178 taken 15 times.
✗ Branch 416 → 417 not taken.
✗ Branch 416 → 418 not taken.
45 const QVariantList issues = draftIssues(-1, parsed.draft, {{QStringLiteral("forwardTargets"), parsed.forwardTargets}});
370
2/2
✓ Branch 224 → 182 taken 1 time.
✓ Branch 224 → 225 taken 14 times.
15 for (const QVariant &issue : issues)
371
4/5
✓ Branch 183 → 184 taken 1 time.
✓ Branch 188 → 189 taken 1 time.
✓ Branch 189 → 190 taken 1 time.
✓ Branch 195 → 196 taken 1 time.
✗ Branch 195 → 222 not taken.
2 if (issue.toMap().value(QStringLiteral("blocking")).toBool()) {
372
1/1
✓ Branch 200 → 201 taken 1 time.
2 result.insert(QStringLiteral("ok"), false);
373
3/3
✓ Branch 204 → 205 taken 1 time.
✓ Branch 209 → 210 taken 1 time.
✓ Branch 213 → 214 taken 1 time.
3 result.insert(QStringLiteral("error"), issue.toMap().value(QStringLiteral("message")));
374 1 break;
375 }
376
1/1
✓ Branch 229 → 230 taken 15 times.
30 result.insert(QStringLiteral("preview"), preview);
377
1/1
✓ Branch 237 → 238 taken 15 times.
30 result.insert(QStringLiteral("draft"), parsed.draft);
378
1/1
✓ Branch 245 → 246 taken 15 times.
30 result.insert(QStringLiteral("scopePath"), scope);
379 15 return result;
380 145 }
381
382 5 QVariantMap QuickRulesBackend::saveConsoleRule(const QString &text, bool confirmed) {
383
1/1
✓ Branch 2 → 3 taken 5 times.
5 const QVariantMap parsed = parseConsoleRule(text);
384
4/4
✓ Branch 7 → 8 taken 5 times.
✓ Branch 8 → 9 taken 5 times.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 34 taken 4 times.
10 if (!parsed.value(QStringLiteral("ok")).toBool())
385
3/5
✓ Branch 21 → 22 taken 1 time.
✓ Branch 25 → 26 taken 1 time.
✓ Branch 25 → 27 taken 1 time.
✗ Branch 173 → 174 not taken.
✗ Branch 173 → 175 not taken.
4 return {{QStringLiteral("error"), parsed.value(QStringLiteral("error"))}};
386
2/2
✓ Branch 38 → 39 taken 4 times.
✓ Branch 39 → 40 taken 4 times.
8 const QStringList forwards = parsed.value(QStringLiteral("forwardTargets")).toStringList();
387
6/6
✓ Branch 45 → 46 taken 2 times.
✓ Branch 45 → 48 taken 2 times.
✓ Branch 46 → 47 taken 1 time.
✓ Branch 46 → 48 taken 1 time.
✓ Branch 49 → 50 taken 1 time.
✓ Branch 49 → 79 taken 3 times.
4 if (!forwards.isEmpty() && !confirmed)
388 2 return {{QStringLiteral("needsConfirmation"), true},
389
1/1
✓ Branch 57 → 58 taken 1 time.
2 {QStringLiteral("message"), tr("Mails matching this rule will be sent to %1. Press Enter again to confirm.")
390
4/6
✓ Branch 62 → 63 taken 1 time.
✓ Branch 63 → 64 taken 1 time.
✓ Branch 67 → 68 taken 2 times.
✓ Branch 67 → 69 taken 1 time.
✗ Branch 213 → 214 not taken.
✗ Branch 213 → 215 not taken.
5 .arg(forwards.join(QStringLiteral(", ")))}};
391
2/2
✓ Branch 83 → 84 taken 3 times.
✓ Branch 84 → 85 taken 3 times.
6 const QVariantMap draft = parsed.value(QStringLiteral("draft")).toMap();
392
2/4
✓ Branch 101 → 102 taken 6 times.
✓ Branch 101 → 103 taken 3 times.
✗ Branch 266 → 267 not taken.
✗ Branch 266 → 268 not taken.
15 const QString requestId = saveDraft(-1, draft, {{QStringLiteral("forwardTargets"), forwards},
393
1/1
✓ Branch 98 → 99 taken 3 times.
9 {QStringLiteral("everyMessage"), confirmed}});
394
4/4
✓ Branch 111 → 112 taken 3 times.
✓ Branch 112 → 113 taken 3 times.
✓ Branch 117 → 118 taken 1 time.
✓ Branch 117 → 144 taken 2 times.
6 if (parsed.value(QStringLiteral("applyNow")).toBool()) {
395
2/2
✓ Branch 122 → 123 taken 1 time.
✓ Branch 123 → 124 taken 1 time.
2 const QString scope = parsed.value(QStringLiteral("scopePath")).toString();
396
1/1
✓ Branch 128 → 129 taken 1 time.
1 QPointer<QuickRulesBackend> guard(this);
397
1/1
✓ Branch 129 → 130 taken 1 time.
1 auto connection = std::make_shared<QMetaObject::Connection>();
398 2 *connection = connect(this, &QuickRulesBackend::editFinished, this,
399
1/9
✓ Branch 135 → 136 taken 1 time.
✗ Branch 314 → 315 not taken.
✗ Branch 314 → 316 not taken.
✗ Branch 317 → 318 not taken.
✗ Branch 317 → 319 not taken.
✗ Branch 320 → 321 not taken.
✗ Branch 320 → 322 not taken.
✗ Branch 323 → 324 not taken.
✗ Branch 323 → 325 not taken.
2 [this, guard, requestId, draft, scope, connection](const QString &id, const QVariantMap &result) {
400
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1 if (id != requestId)
401 ✗ return;
402 1 disconnect(*connection);
403
9/24
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 17 not taken.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 17 not taken.
✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 20 not taken.
✓ Branch 20 → 21 taken 1 time.
✗ Branch 20 → 22 not taken.
✓ Branch 22 → 23 taken 1 time.
✗ Branch 22 → 24 not taken.
✓ Branch 24 → 25 taken 1 time.
✗ Branch 24 → 26 not taken.
✓ Branch 26 → 27 taken 1 time.
✗ Branch 26 → 30 not taken.
✗ Branch 31 → 32 not taken.
✗ Branch 31 → 33 not taken.
✗ Branch 35 → 36 not taken.
✗ Branch 35 → 37 not taken.
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
✗ Branch 43 → 44 not taken.
✗ Branch 43 → 45 not taken.
2 if (guard && result.value(QStringLiteral("ok")).toBool())
404
1/1
✓ Branch 27 → 28 taken 1 time.
1 applyToExisting(draft, scope);
405 1 });
406 1 }
407
2/4
✓ Branch 150 → 151 taken 3 times.
✓ Branch 150 → 152 taken 3 times.
✗ Branch 336 → 337 not taken.
✗ Branch 336 → 338 not taken.
9 return {{QStringLiteral("requestId"), requestId}};
408 26 }
409
410 // --- folders keep their rules (S95.4.7, SR-10) ----------------------------------------
411
412 namespace {
413
414 18 bool targetsFolder(const QString &target, const QString &folder, const QString &delimiter) {
415
8/12
✓ Branch 3 → 4 taken 14 times.
✓ Branch 3 → 9 taken 4 times.
✓ Branch 5 → 6 taken 14 times.
✗ Branch 5 → 10 not taken.
✓ Branch 6 → 7 taken 14 times.
✓ Branch 7 → 8 taken 14 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 14 times.
✓ Branch 12 → 13 taken 14 times.
✓ Branch 12 → 14 taken 4 times.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
18 return target == folder || (!delimiter.isEmpty() && target.startsWith(folder + delimiter));
416 }
417
418 } // namespace
419
420 3 void QuickRulesBackend::folderRenamed(const QString &oldImapPath, const QString &newImapPath) {
421
1/1
✓ Branch 2 → 3 taken 3 times.
3 SieveSession *s = session();
422
5/9
✓ Branch 3 → 4 taken 3 times.
✗ Branch 3 → 8 not taken.
✓ Branch 4 → 5 taken 3 times.
✓ Branch 5 → 6 taken 3 times.
✗ Branch 5 → 8 not taken.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 3 times.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 3 times.
3 if (!s || readOnly() || oldImapPath == newImapPath)
423 1 return;
424
2/3
✓ Branch 13 → 14 taken 3 times.
✗ Branch 13 → 15 not taken.
✓ Branch 14 → 16 taken 3 times.
3 const QString oldName = m_deps.decodeMailbox ? m_deps.decodeMailbox(oldImapPath) : oldImapPath;
425
2/3
✓ Branch 17 → 18 taken 3 times.
✗ Branch 17 → 19 not taken.
✓ Branch 18 → 20 taken 3 times.
3 const QString newName = m_deps.decodeMailbox ? m_deps.decodeMailbox(newImapPath) : newImapPath;
426
2/2
✓ Branch 20 → 21 taken 3 times.
✓ Branch 21 → 22 taken 3 times.
3 const QString delimiter = mailboxDirectory().delimiter();
427 3 Sieve::Document document = s->document();
428
2/3
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 29 taken 3 times.
✓ Branch 30 → 31 taken 3 times.
3 const int changed = Sieve::renameMailbox(&document, oldName, newName, delimiter.isEmpty() ? QLatin1Char('/') : delimiter.at(0));
429
7/7
✓ Branch 31 → 32 taken 3 times.
✓ Branch 35 → 36 taken 3 times.
✓ Branch 36 → 37 taken 3 times.
✓ Branch 37 → 38 taken 3 times.
✓ Branch 38 → 39 taken 3 times.
✓ Branch 42 → 33 taken 3 times.
✓ Branch 42 → 43 taken 3 times.
6 qCInfo(lcRulesBackend) << "Folder rename touches" << changed << "rule blocks";
430
2/2
✓ Branch 43 → 44 taken 1 time.
✓ Branch 43 → 45 taken 2 times.
3 if (changed == 0)
431 1 return;
432 2 submit({Sieve::encode(document), QStringLiteral("folder-rename"),
433 tr("Rules updated for the renamed folder: %n", nullptr, changed), true});
434 10 }
435
436 3 QStringList QuickRulesBackend::rulesUsingFolder(const QString &imapPath) const {
437 3 QStringList names;
438
1/1
✓ Branch 2 → 3 taken 3 times.
3 const SieveSession *s = session();
439
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 3 times.
3 if (!s)
440 ✗ return names;
441
1/1
✓ Branch 5 → 6 taken 3 times.
3 const QString folder = sieveNameFor(imapPath);
442
2/2
✓ Branch 6 → 7 taken 3 times.
✓ Branch 7 → 8 taken 3 times.
3 const QString delimiter = mailboxDirectory().delimiter();
443 // S106-322: the script holds the name as a Sieve string — '\' and '"'
444 // escaped — so the advanced-block probe looks for that spelling. A hit
445 // inside another string only adds a warning line (safe side).
446 6 const auto sieveEscaped = [](QString text) {
447
3/3
✓ Branch 5 → 6 taken 6 times.
✓ Branch 9 → 10 taken 6 times.
✓ Branch 10 → 11 taken 6 times.
6 return text.replace(QLatin1Char('\\'), QLatin1String("\\\\")).replace(QLatin1Char('"'), QLatin1String("\\\"")).toUtf8();
448 };
449
1/1
✓ Branch 10 → 11 taken 3 times.
3 const QByteArray quoted = sieveEscaped(folder);
450
2/2
✓ Branch 12 → 13 taken 3 times.
✓ Branch 13 → 14 taken 3 times.
3 const QByteArray quotedPrefix = sieveEscaped(folder + delimiter);
451
2/2
✓ Branch 67 → 18 taken 14 times.
✓ Branch 67 → 68 taken 3 times.
17 for (const Sieve::Block &block : s->document().blocks) {
452
2/2
✓ Branch 19 → 20 taken 9 times.
✓ Branch 19 → 39 taken 5 times.
14 if (block.kind == Sieve::BlockKind::Rule) {
453
2/2
✓ Branch 37 → 22 taken 9 times.
✓ Branch 37 → 38 taken 7 times.
16 for (const Sieve::Action &action : block.rule.actions)
454
6/7
✓ Branch 23 → 24 taken 9 times.
✗ Branch 23 → 27 not taken.
✓ Branch 24 → 25 taken 9 times.
✓ Branch 25 → 26 taken 2 times.
✓ Branch 25 → 27 taken 7 times.
✓ Branch 28 → 29 taken 2 times.
✓ Branch 28 → 35 taken 7 times.
9 if (action.kind == Sieve::Action::Kind::FileInto && targetsFolder(action.target, folder, delimiter)) {
455
2/4
✓ Branch 29 → 30 taken 2 times.
✗ Branch 29 → 31 not taken.
✗ Branch 31 → 32 not taken.
✓ Branch 32 → 33 taken 2 times.
2 names.append(block.named ? block.name : RuleSentence::generatedName(block.rule));
456 2 break;
457 }
458
4/4
✓ Branch 39 → 40 taken 2 times.
✓ Branch 39 → 50 taken 3 times.
✓ Branch 44 → 45 taken 1 time.
✓ Branch 44 → 49 taken 1 time.
8 } else if (block.kind == Sieve::BlockKind::Advanced &&
459
12/19
✓ Branch 40 → 41 taken 2 times.
✓ Branch 41 → 42 taken 2 times.
✓ Branch 45 → 46 taken 1 time.
✓ Branch 48 → 49 taken 1 time.
✗ Branch 48 → 50 not taken.
✓ Branch 51 → 52 taken 1 time.
✓ Branch 51 → 53 taken 4 times.
✓ Branch 53 → 54 taken 2 times.
✓ Branch 53 → 55 taken 3 times.
✓ Branch 55 → 56 taken 2 times.
✓ Branch 55 → 57 taken 3 times.
✓ Branch 57 → 58 taken 2 times.
✓ Branch 57 → 65 taken 3 times.
✗ Branch 88 → 89 not taken.
✗ Branch 88 → 90 not taken.
✗ Branch 93 → 94 not taken.
✗ Branch 93 → 95 not taken.
✗ Branch 97 → 98 not taken.
✗ Branch 97 → 99 not taken.
8 (block.original.contains("\"" + quoted + "\"") || block.original.contains("\"" + quotedPrefix))) {
460
3/3
✓ Branch 58 → 59 taken 2 times.
✓ Branch 60 → 61 taken 2 times.
✓ Branch 61 → 62 taken 2 times.
4 names.append(tr("Advanced block (line %1)").arg(block.line));
461 }
462 }
463 3 return names;
464 3 }
465
466 3 QString QuickRulesBackend::disableRulesForFolder(const QString &imapPath) {
467
1/1
✓ Branch 2 → 3 taken 3 times.
3 SieveSession *s = session();
468
4/7
✓ Branch 3 → 4 taken 3 times.
✗ Branch 3 → 6 not taken.
✓ Branch 4 → 5 taken 3 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 3 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 14 taken 3 times.
3 if (!s || readOnly())
469 ✗ return refuseEdit(tr("Rules can't be changed right now. Reload them and try again."));
470
1/1
✓ Branch 14 → 15 taken 3 times.
3 const QString folder = sieveNameFor(imapPath);
471
2/2
✓ Branch 15 → 16 taken 3 times.
✓ Branch 16 → 17 taken 3 times.
3 const QString delimiter = mailboxDirectory().delimiter();
472 3 Sieve::Document document = s->document();
473 3 int changed = 0;
474
4/4
✓ Branch 20 → 21 taken 3 times.
✓ Branch 21 → 22 taken 3 times.
✓ Branch 50 → 23 taken 13 times.
✓ Branch 50 → 51 taken 3 times.
16 for (Sieve::Block &block : document.blocks) {
475
3/4
✓ Branch 24 → 25 taken 9 times.
✓ Branch 24 → 26 taken 4 times.
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 27 taken 9 times.
13 if (block.kind != Sieve::BlockKind::Rule || !block.rule.enabled)
476 4 continue;
477
4/4
✓ Branch 27 → 28 taken 9 times.
✓ Branch 28 → 29 taken 9 times.
✓ Branch 46 → 30 taken 9 times.
✓ Branch 46 → 47 taken 7 times.
16 for (const Sieve::Action &action : block.rule.actions)
478
6/7
✓ Branch 31 → 32 taken 9 times.
✗ Branch 31 → 35 not taken.
✓ Branch 32 → 33 taken 9 times.
✓ Branch 33 → 34 taken 2 times.
✓ Branch 33 → 35 taken 7 times.
✓ Branch 36 → 37 taken 2 times.
✓ Branch 36 → 44 taken 7 times.
9 if (action.kind == Sieve::Action::Kind::FileInto && targetsFolder(action.target, folder, delimiter)) {
479 2 block.rule.enabled = false;
480
1/2
✗ Branch 37 → 38 not taken.
✓ Branch 37 → 42 taken 2 times.
2 if (!block.named) {
481 ✗ block.name = RuleSentence::generatedName(block.rule);
482 ✗ block.named = true;
483 }
484 2 block.rule.name = block.name;
485 2 block.dirty = true;
486 2 ++changed;
487 2 break;
488 }
489 }
490
2/2
✓ Branch 51 → 52 taken 1 time.
✓ Branch 51 → 57 taken 2 times.
3 if (changed == 0)
491
2/2
✓ Branch 52 → 53 taken 1 time.
✓ Branch 53 → 54 taken 1 time.
1 return refuseEdit(tr("No rule files into this folder."));
492 2 return submit({Sieve::encode(document), QStringLiteral("folder-delete"),
493 4 tr("Rules turned off for the deleted folder: %n", nullptr, changed), true});
494 7 }
495