@pilllesss/yorn 1.0.182 → 1.0.183
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @pilllesss/yorn might be problematic. Click here for more details.
- package/README.md +1 -1
- package/dist/providers/data/.manifest.json +1 -1
- package/dist/skills/code-review/LICENSE +21 -0
- package/dist/skills/code-review/SKILL.md +233 -0
- package/dist/skills/code-review/assets/pr-review-template.md +137 -0
- package/dist/skills/code-review/assets/review-checklist.md +123 -0
- package/dist/skills/code-review/reference/angular.md +768 -0
- package/dist/skills/code-review/reference/architecture-review-guide.md +472 -0
- package/dist/skills/code-review/reference/c.md +890 -0
- package/dist/skills/code-review/reference/code-quality-universal.md +488 -0
- package/dist/skills/code-review/reference/code-review-best-practices.md +136 -0
- package/dist/skills/code-review/reference/common-bugs-checklist.md +302 -0
- package/dist/skills/code-review/reference/cpp.md +893 -0
- package/dist/skills/code-review/reference/cross-cutting/async-concurrency-patterns.md +515 -0
- package/dist/skills/code-review/reference/cross-cutting/error-handling-principles.md +492 -0
- package/dist/skills/code-review/reference/cross-cutting/n-plus-one-queries.md +309 -0
- package/dist/skills/code-review/reference/cross-cutting/sql-injection-prevention.md +308 -0
- package/dist/skills/code-review/reference/cross-cutting/xss-prevention.md +264 -0
- package/dist/skills/code-review/reference/csharp.md +525 -0
- package/dist/skills/code-review/reference/css-less-sass.md +661 -0
- package/dist/skills/code-review/reference/dart.md +670 -0
- package/dist/skills/code-review/reference/django.md +985 -0
- package/dist/skills/code-review/reference/fastapi.md +580 -0
- package/dist/skills/code-review/reference/go.md +993 -0
- package/dist/skills/code-review/reference/java.md +409 -0
- package/dist/skills/code-review/reference/java8.md +586 -0
- package/dist/skills/code-review/reference/kotlin.md +1018 -0
- package/dist/skills/code-review/reference/nestjs.md +593 -0
- package/dist/skills/code-review/reference/performance-review-guide.md +816 -0
- package/dist/skills/code-review/reference/php.md +684 -0
- package/dist/skills/code-review/reference/python.md +1073 -0
- package/dist/skills/code-review/reference/qt.md +757 -0
- package/dist/skills/code-review/reference/react.md +871 -0
- package/dist/skills/code-review/reference/ruby.md +964 -0
- package/dist/skills/code-review/reference/rust.md +846 -0
- package/dist/skills/code-review/reference/security-review-guide.md +494 -0
- package/dist/skills/code-review/reference/svelte.md +1064 -0
- package/dist/skills/code-review/reference/swift.md +936 -0
- package/dist/skills/code-review/reference/typescript.md +1016 -0
- package/dist/skills/code-review/reference/vue.md +924 -0
- package/dist/skills/code-review/reference/zig.md +440 -0
- package/dist/skills/code-review/scripts/pr-analyzer.py +435 -0
- package/dist/skills/code-review/scripts/test_pr_analyzer.py +380 -0
- package/dist/yorn.cjs +628 -628
- package/package.json +2 -2
|
@@ -0,0 +1,757 @@
|
|
|
1
|
+
# Qt Code Review Guide
|
|
2
|
+
|
|
3
|
+
> Code review guidelines focusing on object model, signals/slots, Model/View, QML, Qt6 migration, event loop, testing, and GUI performance. Examples based on Qt 5.15 / Qt 6.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Object Model & Memory Management](#object-model--memory-management)
|
|
8
|
+
- [Signals & Slots](#signals--slots)
|
|
9
|
+
- [Containers & Strings](#containers--strings)
|
|
10
|
+
- [Threads & Concurrency](#threads--concurrency)
|
|
11
|
+
- [GUI & Widgets](#gui--widgets)
|
|
12
|
+
- [Model/View Architecture](#modelview-architecture)
|
|
13
|
+
- [Meta-Object System](#meta-object-system)
|
|
14
|
+
- [QML / Qt Quick](#qml--qt-quick)
|
|
15
|
+
- [Qt5 → Qt6 Migration](#qt5--qt6-migration)
|
|
16
|
+
- [Testing](#testing)
|
|
17
|
+
- [Review Checklist](#review-checklist)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Object Model & Memory Management
|
|
22
|
+
|
|
23
|
+
### Use Parent-Child Ownership Mechanism
|
|
24
|
+
Qt's `QObject` hierarchy automatically manages memory. For `QObject`, prefer setting a parent object over manual `delete` or smart pointers.
|
|
25
|
+
|
|
26
|
+
```cpp
|
|
27
|
+
// ❌ Manual management prone to memory leaks
|
|
28
|
+
QWidget* w = new QWidget();
|
|
29
|
+
QLabel* l = new QLabel();
|
|
30
|
+
l->setParent(w);
|
|
31
|
+
// ... If w is deleted, l is automatically deleted. But if w leaks, l also leaks.
|
|
32
|
+
|
|
33
|
+
// ✅ Specify parent in constructor
|
|
34
|
+
QWidget* w = new QWidget(this); // Owned by 'this'
|
|
35
|
+
QLabel* l = new QLabel(w); // Owned by 'w'
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Use Smart Pointers with QObject
|
|
39
|
+
If a `QObject` has no parent, use `QScopedPointer` or `std::unique_ptr` with a custom deleter (use `deleteLater` if cross-thread). Avoid `std::shared_ptr` for `QObject` unless necessary, as it confuses the parent-child ownership system.
|
|
40
|
+
|
|
41
|
+
```cpp
|
|
42
|
+
// ✅ Scoped pointer for local/member QObject without parent
|
|
43
|
+
QScopedPointer<MyObject> obj(new MyObject());
|
|
44
|
+
|
|
45
|
+
// ✅ Safe pointer to prevent dangling pointers
|
|
46
|
+
QPointer<MyObject> safePtr = obj.data();
|
|
47
|
+
if (safePtr) {
|
|
48
|
+
safePtr->doSomething();
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Use `deleteLater()`
|
|
53
|
+
For asynchronous deletion, especially in slots or event handlers, use `deleteLater()` instead of `delete` to ensure pending events in the event loop are processed.
|
|
54
|
+
|
|
55
|
+
```cpp
|
|
56
|
+
// ❌ Bad: delete in a slot may invalidate sender during signal emission
|
|
57
|
+
void MyWidget::onFinished() {
|
|
58
|
+
delete this; // UB: may be called from within a signal chain
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ✅ Good: safe deferred deletion
|
|
62
|
+
void MyWidget::onFinished() {
|
|
63
|
+
deleteLater();
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Avoid double ownership
|
|
68
|
+
|
|
69
|
+
```cpp
|
|
70
|
+
// ❌ Bad: parent owns the dialog, but we also store it in unique_ptr
|
|
71
|
+
auto dialog = std::make_unique<QDialog>(this); // 'this' is parent AND unique_ptr owns it
|
|
72
|
+
|
|
73
|
+
// ✅ Good: parent owns it, raw pointer for access
|
|
74
|
+
auto* dialog = new QDialog(this);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Signals & Slots
|
|
80
|
+
|
|
81
|
+
### Prefer Function Pointer Syntax
|
|
82
|
+
Use compile-time checked syntax (Qt 5+).
|
|
83
|
+
|
|
84
|
+
```cpp
|
|
85
|
+
// ❌ String-based (runtime check only, slower)
|
|
86
|
+
connect(sender, SIGNAL(valueChanged(int)), receiver, SLOT(updateValue(int)));
|
|
87
|
+
|
|
88
|
+
// ✅ Compile-time check
|
|
89
|
+
connect(sender, &Sender::valueChanged, receiver, &Receiver::updateValue);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Lambda connections — specify context object
|
|
93
|
+
|
|
94
|
+
```cpp
|
|
95
|
+
// ❌ Bad: lambda captures `this` raw; crashes if object is deleted
|
|
96
|
+
connect(timer, &QTimer::timeout, [this]() {
|
|
97
|
+
update(); // crashes if 'this' was destroyed
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// ✅ Good: context object disconnects automatically on destruction
|
|
101
|
+
connect(timer, &QTimer::timeout, this, [this]() {
|
|
102
|
+
update();
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Connection Types
|
|
107
|
+
Be explicit or aware of connection types when crossing threads.
|
|
108
|
+
- `Qt::AutoConnection` (Default): Direct if same thread, Queued if different thread.
|
|
109
|
+
- `Qt::QueuedConnection`: Always posts event (thread-safe across threads).
|
|
110
|
+
- `Qt::DirectConnection`: Immediate call (dangerous if accessing non-thread-safe data across threads).
|
|
111
|
+
|
|
112
|
+
### Avoid Loops
|
|
113
|
+
Check logic that might cause infinite signal loops (e.g., `valueChanged` -> `setValue` -> `valueChanged`). Block signals or check for equality before setting values.
|
|
114
|
+
|
|
115
|
+
```cpp
|
|
116
|
+
void MyClass::setValue(int v) {
|
|
117
|
+
if (m_value == v) return; // ✅ Good: Break loop
|
|
118
|
+
m_value = v;
|
|
119
|
+
emit valueChanged(v);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Disconnect when appropriate
|
|
124
|
+
|
|
125
|
+
```cpp
|
|
126
|
+
// ✅ Good: explicit disconnect before changing target
|
|
127
|
+
disconnect(oldSource, &Source::data, this, &Receiver::onData);
|
|
128
|
+
connect(newSource, &Source::data, this, &Receiver::onData);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Containers & Strings
|
|
134
|
+
|
|
135
|
+
### QString Efficiency
|
|
136
|
+
- Use `QStringLiteral("...")` for compile-time string creation to avoid runtime allocation.
|
|
137
|
+
- Use `QLatin1String` for comparison with ASCII literals (in Qt 5).
|
|
138
|
+
- Prefer `arg()` for formatting (or `QStringBuilder`'s `%` operator).
|
|
139
|
+
|
|
140
|
+
```cpp
|
|
141
|
+
// ❌ Runtime conversion
|
|
142
|
+
if (str == "test") ...
|
|
143
|
+
|
|
144
|
+
// ✅ Prefer QLatin1String for comparison with ASCII literals (in Qt 5)
|
|
145
|
+
if (str == QLatin1String("test")) ... // Qt 5
|
|
146
|
+
if (str == u"test"_s) ... // Qt 6
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Container Selection
|
|
150
|
+
- **Qt 6**: `QList` is now the default choice (unified with `QVector`).
|
|
151
|
+
- **Qt 5**: Prefer `QVector` over `QList` for contiguous memory and cache performance, unless stable references are needed.
|
|
152
|
+
- Be aware of Implicit Sharing (Copy-on-Write). Passing containers by value is cheap *until* modified. Use `const &` for read-only access.
|
|
153
|
+
|
|
154
|
+
```cpp
|
|
155
|
+
// ❌ Forces deep copy if function modifies 'list'
|
|
156
|
+
void process(QVector<int> list) {
|
|
157
|
+
list[0] = 1;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ✅ Read-only reference
|
|
161
|
+
void process(const QVector<int>& list) { ... }
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Use constBegin/constEnd for read-only iteration
|
|
165
|
+
|
|
166
|
+
```cpp
|
|
167
|
+
// ❌ Bad: begin()/end() may trigger detach
|
|
168
|
+
for (auto it = list.begin(); it != list.end(); ++it) {
|
|
169
|
+
qDebug() << *it;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ✅ Good: const iteration avoids detach
|
|
173
|
+
for (auto it = list.constBegin(); it != list.constEnd(); ++it) {
|
|
174
|
+
qDebug() << *it;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ✅ Best: range-based for with const ref (Qt 5.7+)
|
|
178
|
+
for (const auto& item : list) {
|
|
179
|
+
qDebug() << item;
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Threads & Concurrency
|
|
186
|
+
|
|
187
|
+
### Subclassing QThread vs Worker Object
|
|
188
|
+
Prefer the "Worker Object" pattern over subclassing `QThread` implementation details.
|
|
189
|
+
|
|
190
|
+
```cpp
|
|
191
|
+
// ❌ Business logic inside QThread::run()
|
|
192
|
+
class MyThread : public QThread {
|
|
193
|
+
void run() override { ... }
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// ✅ Worker object moved to thread
|
|
197
|
+
QThread* thread = new QThread;
|
|
198
|
+
Worker* worker = new Worker;
|
|
199
|
+
worker->moveToThread(thread);
|
|
200
|
+
connect(thread, &QThread::started, worker, &Worker::process);
|
|
201
|
+
connect(thread, &QThread::finished, worker, &QObject::deleteLater);
|
|
202
|
+
thread->start();
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### GUI Thread Safety
|
|
206
|
+
**NEVER** access UI widgets (`QWidget` and subclasses) from a background thread. Use signals/slots to communicate updates to the main thread.
|
|
207
|
+
|
|
208
|
+
```cpp
|
|
209
|
+
// ❌ Bad: accessing widget from worker thread
|
|
210
|
+
void Worker::onResult(Data data) {
|
|
211
|
+
label->setText(data.toString()); // CRASH: not in GUI thread
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ✅ Good: signal to GUI thread
|
|
215
|
+
void Worker::onResult(Data data) {
|
|
216
|
+
emit resultReady(data); // connected via QueuedConnection
|
|
217
|
+
}
|
|
218
|
+
// In main thread:
|
|
219
|
+
connect(worker, &Worker::resultReady, this, [this](const Data& d) {
|
|
220
|
+
label->setText(d.toString());
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### QtConcurrent for simple parallelism
|
|
225
|
+
|
|
226
|
+
```cpp
|
|
227
|
+
// ✅ Good: simple parallel computation
|
|
228
|
+
auto future = QtConcurrent::run([data]() {
|
|
229
|
+
return heavyComputation(data);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// ✅ Good: map-reduce pattern
|
|
233
|
+
auto results = QtConcurrent::mappedReduced(
|
|
234
|
+
inputList,
|
|
235
|
+
[](const Item& item) { return process(item); },
|
|
236
|
+
[](int& result, int value) { result += value; }
|
|
237
|
+
);
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## GUI & Widgets
|
|
243
|
+
|
|
244
|
+
### Logic Separation
|
|
245
|
+
Keep business logic out of UI classes (`MainWindow`, `Dialog`). UI classes should only handle display and user input forwarding.
|
|
246
|
+
|
|
247
|
+
### Layouts
|
|
248
|
+
Avoid fixed sizes (`setGeometry`, `resize`). Use layouts (`QVBoxLayout`, `QGridLayout`) to handle different DPIs and window resizing gracefully.
|
|
249
|
+
|
|
250
|
+
### Blocking Event Loop
|
|
251
|
+
Never execute long-running operations on the main thread (freezes GUI).
|
|
252
|
+
- **Bad**: `Sleep()`, `while(busy)`, synchronous network calls.
|
|
253
|
+
- **Good**: `QProcess`, `QThread`, `QtConcurrent`, or asynchronous APIs (`QNetworkAccessManager`).
|
|
254
|
+
|
|
255
|
+
### High-DPI scaling
|
|
256
|
+
|
|
257
|
+
```cpp
|
|
258
|
+
// ✅ Qt 5: enable high-DPI scaling
|
|
259
|
+
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
|
260
|
+
|
|
261
|
+
// ✅ Qt 6: enabled by default, but verify icons and custom painting scale correctly
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Model/View Architecture
|
|
267
|
+
|
|
268
|
+
### Subclass QAbstractItemModel correctly
|
|
269
|
+
|
|
270
|
+
When implementing a custom model, the following methods are **required**:
|
|
271
|
+
|
|
272
|
+
```cpp
|
|
273
|
+
class TaskModel : public QAbstractTableModel {
|
|
274
|
+
Q_OBJECT
|
|
275
|
+
public:
|
|
276
|
+
int rowCount(const QModelIndex& parent = {}) const override {
|
|
277
|
+
if (parent.isValid()) return 0; // table model has no tree
|
|
278
|
+
return m_tasks.size();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
int columnCount(const QModelIndex& parent = {}) const override {
|
|
282
|
+
if (parent.isValid()) return 0;
|
|
283
|
+
return 3; // title, priority, status
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
QVariant data(const QModelIndex& index, int role) const override {
|
|
287
|
+
if (!index.isValid() || index.row() >= m_tasks.size())
|
|
288
|
+
return {};
|
|
289
|
+
|
|
290
|
+
if (role == Qt::DisplayRole) {
|
|
291
|
+
switch (index.column()) {
|
|
292
|
+
case 0: return m_tasks[index.row()].title;
|
|
293
|
+
case 1: return m_tasks[index.row()].priority;
|
|
294
|
+
case 2: return m_tasks[index.row()].status;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return {};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Required for headers
|
|
301
|
+
QVariant headerData(int section, Qt::Orientation orientation, int role) const override {
|
|
302
|
+
if (role != Qt::DisplayRole) return {};
|
|
303
|
+
if (orientation == Qt::Horizontal) {
|
|
304
|
+
switch (section) {
|
|
305
|
+
case 0: return "Title";
|
|
306
|
+
case 1: return "Priority";
|
|
307
|
+
case 2: return "Status";
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return section + 1; // row numbers
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private:
|
|
314
|
+
QVector<Task> m_tasks;
|
|
315
|
+
};
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Notify the view of changes
|
|
319
|
+
|
|
320
|
+
```cpp
|
|
321
|
+
// ❌ Bad: modifying data without notifying the view
|
|
322
|
+
void TaskModel::addTask(const Task& task) {
|
|
323
|
+
m_tasks.append(task); // view doesn't know about the change
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// ✅ Good: emit proper signals
|
|
327
|
+
void TaskModel::addTask(const Task& task) {
|
|
328
|
+
beginInsertRows({}, m_tasks.size(), m_tasks.size());
|
|
329
|
+
m_tasks.append(task);
|
|
330
|
+
endInsertRows();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
void TaskModel::updateStatus(int row, const QString& status) {
|
|
334
|
+
m_tasks[row].status = status;
|
|
335
|
+
emit dataChanged(index(row, 2), index(row, 2), {Qt::DisplayRole});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
void TaskModel::clearAll() {
|
|
339
|
+
beginResetModel();
|
|
340
|
+
m_tasks.clear();
|
|
341
|
+
endResetModel();
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Delegate pattern for custom rendering
|
|
346
|
+
|
|
347
|
+
```cpp
|
|
348
|
+
class PriorityDelegate : public QStyledItemDelegate {
|
|
349
|
+
Q_OBJECT
|
|
350
|
+
public:
|
|
351
|
+
void paint(QPainter* painter, const QStyleOptionViewItem& option,
|
|
352
|
+
const QModelIndex& index) const override {
|
|
353
|
+
QStyleOptionViewItem opt = option;
|
|
354
|
+
initStyleOption(&opt, index);
|
|
355
|
+
|
|
356
|
+
// Color-code by priority
|
|
357
|
+
QString priority = index.data().toString();
|
|
358
|
+
if (priority == "High") {
|
|
359
|
+
opt.backgroundBrush = QColor("#ffcccc");
|
|
360
|
+
} else if (priority == "Low") {
|
|
361
|
+
opt.backgroundBrush = QColor("#ccffcc");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
QStyledItemDelegate::paint(painter, opt, index);
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
// Usage:
|
|
369
|
+
tableView->setItemDelegateForColumn(1, new PriorityDelegate(this));
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Performance with large datasets
|
|
373
|
+
|
|
374
|
+
- Use `beginInsertRows`/`endInsertRows` for batch inserts, not one row at a time.
|
|
375
|
+
- For 100K+ rows, consider `QSortFilterProxyModel` for filtering instead of re-querying.
|
|
376
|
+
- Use `model()->fetchMore()` for lazy loading / pagination.
|
|
377
|
+
- Avoid `Qt::UserRole + N` with heavy objects; use a lightweight key and look up externally.
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## Meta-Object System
|
|
382
|
+
|
|
383
|
+
### Properties & Enums
|
|
384
|
+
Use `Q_PROPERTY` for values exposed to QML or needing introspection.
|
|
385
|
+
Use `Q_ENUM` to enable string conversion for enums.
|
|
386
|
+
|
|
387
|
+
```cpp
|
|
388
|
+
class MyObject : public QObject {
|
|
389
|
+
Q_OBJECT
|
|
390
|
+
Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)
|
|
391
|
+
public:
|
|
392
|
+
enum State { Idle, Running };
|
|
393
|
+
Q_ENUM(State)
|
|
394
|
+
// ...
|
|
395
|
+
};
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### qobject_cast
|
|
399
|
+
Use `qobject_cast<T*>` for QObjects instead of `dynamic_cast`. It is faster and doesn't require RTTI.
|
|
400
|
+
|
|
401
|
+
### Q_GADGET for value types
|
|
402
|
+
|
|
403
|
+
```cpp
|
|
404
|
+
// ✅ Good: introspection without QObject overhead
|
|
405
|
+
struct Coordinate {
|
|
406
|
+
Q_GADGET
|
|
407
|
+
Q_PROPERTY(double x MEMBER x)
|
|
408
|
+
Q_PROPERTY(double y MEMBER y)
|
|
409
|
+
public:
|
|
410
|
+
double x = 0.0;
|
|
411
|
+
double y = 0.0;
|
|
412
|
+
};
|
|
413
|
+
Q_DECLARE_METATYPE(Coordinate)
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## QML / Qt Quick
|
|
419
|
+
|
|
420
|
+
### C++/QML boundary design
|
|
421
|
+
|
|
422
|
+
```cpp
|
|
423
|
+
// ✅ Good: expose C++ model to QML via context property (Qt 5) or QML_SINGLETON (Qt 6)
|
|
424
|
+
|
|
425
|
+
// Qt 6: QML_ELEMENT + QML_SINGLETON
|
|
426
|
+
class AppSettings : public QObject {
|
|
427
|
+
Q_OBJECT
|
|
428
|
+
QML_ELEMENT
|
|
429
|
+
QML_SINGLETON
|
|
430
|
+
Q_PROPERTY(QString theme READ theme WRITE setTheme NOTIFY themeChanged)
|
|
431
|
+
public:
|
|
432
|
+
QString theme() const { return m_theme; }
|
|
433
|
+
void setTheme(const QString& t) {
|
|
434
|
+
if (m_theme != t) {
|
|
435
|
+
m_theme = t;
|
|
436
|
+
emit themeChanged();
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
signals:
|
|
440
|
+
void themeChanged();
|
|
441
|
+
private:
|
|
442
|
+
QString m_theme;
|
|
443
|
+
};
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### QML performance best practices
|
|
447
|
+
|
|
448
|
+
```qml
|
|
449
|
+
// ❌ Bad: JavaScript in onCompleted blocks UI thread
|
|
450
|
+
Component.onCompleted: {
|
|
451
|
+
for (var i = 0; i < 10000; i++) {
|
|
452
|
+
model.append({"value": i}); // slow, blocks rendering
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// ✅ Good: use C++ model, or WorkerScript for heavy JS
|
|
457
|
+
// Prefer C++ QAbstractListModel for large datasets
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
```qml
|
|
461
|
+
// ❌ Bad: frequent property bindings cause re-evaluation
|
|
462
|
+
Rectangle {
|
|
463
|
+
width: parent.width * 0.8 + someComplexCalc()
|
|
464
|
+
height: parent.height * 0.6 + anotherCalc()
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// ✅ Good: minimize binding complexity
|
|
468
|
+
Rectangle {
|
|
469
|
+
width: parent.width * 0.8
|
|
470
|
+
height: parent.height * 0.6
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### QML object lifecycle
|
|
475
|
+
|
|
476
|
+
- QML-created objects are owned by the QML engine.
|
|
477
|
+
- `Qt.createComponent()` + `createObject()` — caller manages lifetime.
|
|
478
|
+
- Use `Loader` for lazy instantiation of heavy components.
|
|
479
|
+
- `property var myObj: QtObject {}` — the QML engine owns it.
|
|
480
|
+
|
|
481
|
+
```qml
|
|
482
|
+
// ✅ Good: Loader for conditional heavy UI
|
|
483
|
+
Loader {
|
|
484
|
+
id: detailLoader
|
|
485
|
+
active: selectedItem !== null
|
|
486
|
+
sourceComponent: active ? detailComponent : null
|
|
487
|
+
}
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
|
|
492
|
+
## Qt5 → Qt6 Migration
|
|
493
|
+
|
|
494
|
+
### Key breaking changes
|
|
495
|
+
|
|
496
|
+
| Qt 5 | Qt 6 | Notes |
|
|
497
|
+
|------|------|-------|
|
|
498
|
+
| `QList` ≠ `QVector` | `QList` = `QVector` | Unified; QList is now QVector internally |
|
|
499
|
+
| `QStringRef` | `QStringView` | QStringView is non-owning, more like string_view |
|
|
500
|
+
| `QLatin1String` | `QLatin1StringView` | Or use `u"..."_s` string literals |
|
|
501
|
+
| `QTextStream(stream)` | `QTextStream(&string)` | Constructor changes |
|
|
502
|
+
| `QMouseEvent::pos()` | `QMouseEvent::position()` | Returns QPointF instead of QPoint |
|
|
503
|
+
| `QWheelEvent::delta()` | `QWheelEvent::angleDelta()` | Already deprecated in Qt 5 |
|
|
504
|
+
| `QComboBox::activated(int)` | `QComboBox::textActivated(QString)` | Overload disambiguation |
|
|
505
|
+
|
|
506
|
+
### CMake replaces qmake
|
|
507
|
+
|
|
508
|
+
```cmake
|
|
509
|
+
# ✅ Qt 6 CMakeLists.txt
|
|
510
|
+
cmake_minimum_required(VERSION 3.16)
|
|
511
|
+
project(MyApp LANGUAGES CXX)
|
|
512
|
+
|
|
513
|
+
set(CMAKE_CXX_STANDARD 17)
|
|
514
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
515
|
+
set(CMAKE_AUTOMOC ON)
|
|
516
|
+
set(CMAKE_AUTORCC ON)
|
|
517
|
+
|
|
518
|
+
find_package(Qt6 REQUIRED COMPONENTS Widgets Quick Core)
|
|
519
|
+
|
|
520
|
+
qt_add_executable(MyApp
|
|
521
|
+
main.cpp
|
|
522
|
+
MainWindow.cpp
|
|
523
|
+
MainWindow.h
|
|
524
|
+
resources.qrc
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
target_link_libraries(MyApp PRIVATE
|
|
528
|
+
Qt6::Widgets
|
|
529
|
+
Qt6::Quick
|
|
530
|
+
Qt6::Core
|
|
531
|
+
)
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### Qt6 new APIs and improvements
|
|
535
|
+
|
|
536
|
+
```cpp
|
|
537
|
+
// ✅ Qt 6: QStringView instead of QStringRef
|
|
538
|
+
void process(QStringView sv); // non-owning, efficient
|
|
539
|
+
|
|
540
|
+
// ✅ Qt 6: QCalendar API for date handling
|
|
541
|
+
QCalendar cal(QCalendar::System::Gregorian);
|
|
542
|
+
QDate date = cal.dateFromParts(2024, 3, 15);
|
|
543
|
+
|
|
544
|
+
// ✅ Qt 6: Qt Concurrent improvements
|
|
545
|
+
auto future = QtConcurrent::run(QThreadPool::globalInstance(),
|
|
546
|
+
[]() { return heavyWork(); });
|
|
547
|
+
|
|
548
|
+
// ✅ Qt 6: Compare API for containers
|
|
549
|
+
QList<int> a = {1, 2, 3};
|
|
550
|
+
QList<int> b = {1, 2, 3};
|
|
551
|
+
bool eq = a == b; // works correctly in Qt 6
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### Migration checklist
|
|
555
|
+
|
|
556
|
+
- [ ] Replace `qmake` with `CMake` (or use `qt-cmake`)
|
|
557
|
+
- [ ] Replace `QStringRef` with `QStringView`
|
|
558
|
+
- [ ] Replace deprecated event accessors (`pos()` → `position()`)
|
|
559
|
+
- [ ] Update signal/slot connections for overloaded signals (use `qOverload`)
|
|
560
|
+
- [ ] Verify `QList`/`QVector` interchangeability
|
|
561
|
+
- [ ] Test with Qt 6 compatibility module: `find_package(Qt6 COMPONENTS Core5Compat)`
|
|
562
|
+
|
|
563
|
+
---
|
|
564
|
+
|
|
565
|
+
## Testing
|
|
566
|
+
|
|
567
|
+
### QTest framework
|
|
568
|
+
|
|
569
|
+
```cpp
|
|
570
|
+
#include <QtTest>
|
|
571
|
+
#include "parser.h"
|
|
572
|
+
|
|
573
|
+
class TestParser : public QObject {
|
|
574
|
+
Q_OBJECT
|
|
575
|
+
private slots:
|
|
576
|
+
void testEmptyInput() {
|
|
577
|
+
Parser p("");
|
|
578
|
+
QVERIFY(p.nextToken().isNull());
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
void testIntegerToken() {
|
|
582
|
+
Parser p("42");
|
|
583
|
+
auto token = p.nextToken();
|
|
584
|
+
QCOMPARE(token.type(), Token::Integer);
|
|
585
|
+
QCOMPARE(token.value().toInt(), 42);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
void testNegativeNumber() {
|
|
589
|
+
Parser p("-7");
|
|
590
|
+
auto token = p.nextToken();
|
|
591
|
+
QCOMPARE(token.value().toInt(), -7);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Data-driven test
|
|
595
|
+
void testValidTokens_data() {
|
|
596
|
+
QTest::addColumn<QString>("input");
|
|
597
|
+
QTest::addColumn<int>("expectedType");
|
|
598
|
+
|
|
599
|
+
QTest::newRow("integer") << "42" << static_cast<int>(Token::Integer);
|
|
600
|
+
QTest::newRow("string") << "\"hello\"" << static_cast<int>(Token::String);
|
|
601
|
+
QTest::newRow("operator") << "+" << static_cast<int>(Token::Operator);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
void testValidTokens() {
|
|
605
|
+
QFETCH(QString, input);
|
|
606
|
+
QFETCH(int, expectedType);
|
|
607
|
+
|
|
608
|
+
Parser p(input);
|
|
609
|
+
auto token = p.nextToken();
|
|
610
|
+
QCOMPARE(token.type(), static_cast<Token::Type>(expectedType));
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
QTEST_MAIN(TestParser)
|
|
615
|
+
#include "test_parser.moc"
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
### GUI testing with QTest
|
|
619
|
+
|
|
620
|
+
```cpp
|
|
621
|
+
class TestLoginDialog : public QObject {
|
|
622
|
+
Q_OBJECT
|
|
623
|
+
private slots:
|
|
624
|
+
void testLoginButtonDisabledWhenEmpty() {
|
|
625
|
+
LoginDialog dialog;
|
|
626
|
+
dialog.show();
|
|
627
|
+
QVERIFY(QTest::qWaitForWindowExposed(&dialog));
|
|
628
|
+
|
|
629
|
+
// Initially, login button should be disabled
|
|
630
|
+
QPushButton* loginBtn = dialog.findChild<QPushButton*>("loginButton");
|
|
631
|
+
QVERIFY(loginBtn != nullptr);
|
|
632
|
+
QVERIFY(!loginBtn->isEnabled());
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
void testLoginEnabledAfterInput() {
|
|
636
|
+
LoginDialog dialog;
|
|
637
|
+
dialog.show();
|
|
638
|
+
QVERIFY(QTest::qWaitForWindowExposed(&dialog));
|
|
639
|
+
|
|
640
|
+
QLineEdit* userField = dialog.findChild<QLineEdit*>("usernameField");
|
|
641
|
+
QLineEdit* passField = dialog.findChild<QLineEdit*>("passwordField");
|
|
642
|
+
|
|
643
|
+
QTest::keyClicks(userField, "alice");
|
|
644
|
+
QTest::keyClicks(passField, "secret123");
|
|
645
|
+
|
|
646
|
+
QPushButton* loginBtn = dialog.findChild<QPushButton*>("loginButton");
|
|
647
|
+
QVERIFY(loginBtn->isEnabled());
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
void testSubmitOnEnter() {
|
|
651
|
+
LoginDialog dialog;
|
|
652
|
+
dialog.show();
|
|
653
|
+
QVERIFY(QTest::qWaitForWindowExposed(&dialog));
|
|
654
|
+
|
|
655
|
+
QLineEdit* userField = dialog.findChild<QLineEdit*>("usernameField");
|
|
656
|
+
QTest::keyClicks(userField, "alice");
|
|
657
|
+
QTest::keyClick(userField, Qt::Key_Return);
|
|
658
|
+
|
|
659
|
+
// Verify the dialog emitted the accepted signal
|
|
660
|
+
QTRY_COMPARE(dialog.result(), static_cast<int>(QDialog::Accepted));
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
### Mock Qt objects for unit testing
|
|
666
|
+
|
|
667
|
+
```cpp
|
|
668
|
+
// ✅ Good: inject dependencies for testability
|
|
669
|
+
class NetworkService {
|
|
670
|
+
public:
|
|
671
|
+
virtual ~NetworkService() = default;
|
|
672
|
+
virtual QJsonObject fetchUser(int id) = 0;
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
class MockNetworkService : public NetworkService {
|
|
676
|
+
public:
|
|
677
|
+
QJsonObject fetchUser(int id) override {
|
|
678
|
+
return m_responses.value(id, {});
|
|
679
|
+
}
|
|
680
|
+
void setResponse(int id, const QJsonObject& json) {
|
|
681
|
+
m_responses[id] = json;
|
|
682
|
+
}
|
|
683
|
+
private:
|
|
684
|
+
QHash<int, QJsonObject> m_responses;
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
// In test:
|
|
688
|
+
void testProfileDisplay() {
|
|
689
|
+
MockNetworkService mock;
|
|
690
|
+
mock.setResponse(1, {{"name", "Alice"}, {"role", "admin"}});
|
|
691
|
+
|
|
692
|
+
ProfileController controller(&mock);
|
|
693
|
+
controller.loadProfile(1);
|
|
694
|
+
QCOMPARE(controller.name(), "Alice");
|
|
695
|
+
QCOMPARE(controller.role(), "admin");
|
|
696
|
+
}
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
### CI integration
|
|
700
|
+
|
|
701
|
+
```bash
|
|
702
|
+
# Qt 6 test runner
|
|
703
|
+
mkdir build && cd build
|
|
704
|
+
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON
|
|
705
|
+
cmake --build .
|
|
706
|
+
ctest --output-on-failure
|
|
707
|
+
|
|
708
|
+
# With Xvfb for GUI tests on headless CI
|
|
709
|
+
xvfb-run -a ctest --output-on-failure
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
---
|
|
713
|
+
|
|
714
|
+
## Review Checklist
|
|
715
|
+
|
|
716
|
+
### Memory
|
|
717
|
+
- [ ] Is parent-child relationship correct? Are dangling pointers avoided (using `QPointer`)?
|
|
718
|
+
- [ ] No double ownership (parent + smart pointer)
|
|
719
|
+
- [ ] `deleteLater()` used instead of `delete` in slots
|
|
720
|
+
|
|
721
|
+
### Signals & Slots
|
|
722
|
+
- [ ] Function pointer syntax used (compile-time checked)
|
|
723
|
+
- [ ] Lambda connections have context object for auto-disconnect
|
|
724
|
+
- [ ] No signal loops (guard with equality check or blockSignals)
|
|
725
|
+
- [ ] Proper disconnect when changing signal sources
|
|
726
|
+
|
|
727
|
+
### Threads
|
|
728
|
+
- [ ] Is UI accessed only from main thread?
|
|
729
|
+
- [ ] Are long tasks offloaded (QThread worker, QtConcurrent)?
|
|
730
|
+
- [ ] Worker object pattern preferred over QThread subclassing
|
|
731
|
+
- [ ] Proper cleanup: thread quit + wait before delete
|
|
732
|
+
|
|
733
|
+
### Strings & Containers
|
|
734
|
+
- [ ] `QStringLiteral` or `u"..."_s` used for compile-time strings
|
|
735
|
+
- [ ] `const &` used for read-only container access
|
|
736
|
+
- [ ] No implicit detach in loops (use const iterators or range-for with const ref)
|
|
737
|
+
|
|
738
|
+
### Model/View
|
|
739
|
+
- [ ] begin/end Insert/Remove/Reset signals emitted correctly
|
|
740
|
+
- [ ] dataChanged emitted for individual item updates
|
|
741
|
+
- [ ] Delegate used for custom rendering (not subclassing view)
|
|
742
|
+
|
|
743
|
+
### QML
|
|
744
|
+
- [ ] C++/QML boundary uses QML_ELEMENT (Qt 6) or registered types
|
|
745
|
+
- [ ] Heavy computation not in QML JS
|
|
746
|
+
- [ ] Loader used for conditional/lazy component instantiation
|
|
747
|
+
|
|
748
|
+
### Testing
|
|
749
|
+
- [ ] QTest unit tests for core logic
|
|
750
|
+
- [ ] GUI tests use QTest::keyClicks / qWaitForWindowExposed
|
|
751
|
+
- [ ] Dependencies injected for mockability
|
|
752
|
+
- [ ] Tests run in CI (with Xvfb for GUI tests)
|
|
753
|
+
|
|
754
|
+
### Style
|
|
755
|
+
- [ ] Naming conventions (camelCase for methods, PascalCase for classes)
|
|
756
|
+
- [ ] Resources loaded from `.qrc`
|
|
757
|
+
- [ ] `Q_OBJECT` macro present in all QObject subclasses
|