As مطوّرو Qtنحن ننتج العديد من أسطر كود QML يوميًا. بالطبع، ندرك جميعًا أهمية الأجزاء القابلة للصيانة والمنظمة جيدًا. نحن نعرف أفضل الممارسات، ونحاول اتباعها في كل مرة نضيف فيها كودًا جديدًا إلى القاعدة الحالية. ومع ذلك، مع تزايد تعقيد مشروعنا، قد يكون من الصعب تتبع جميع أفضل الممارسات وضمان أن الكود يلبي المعايير المطلوبة. وهذا أحد الأسباب التي تجعلك تفكر في qmllint كمرشح ليكون صديقك الجديد.

في هذه المقالة، أريد أن أريكم كيف أعمل مع qmllint وكيف يمكنكم دمج فحوصات qmllint في مهامكم اليومية.

ما هو qmllint؟

باختصار، qmllint هي أداة linter قوية مصممة خصيصًا لـ QML. غرضها الرئيسي هو التحقق من الصحة النحوية لملفات QML والتحذير من بعض الأنماط المضادة في QML. قابلة للتكوين بسهولة بحيث يمكننا تمكين التحقق وطباعة التحذيرات التي تهمنا فقط. أنا دائمًا أفعّل جميعها للتأكد من كتابة كود QML دون أي أخطاء.

على سبيل المثال، يمكن لهذه الأداة أن تنبهك إلى:

  • وصول غير مُقيّد إلى الخصائص
  • المشكلات المتعلقة بتجميع كود QML
  • كود مهمل
  • وغيرها الكثير…

بالتأكيد ستسهّل الأداة تطبيق أفضل الممارسات للكود النظيف والحفاظ على الكود في أفضل حالة ممكنة.

من أين تبدأ

لقد أعددت مقتطف كود قصير لهذه المقالة. بالطبع، الأخطاء في الكود مقصودة لإظهار استخدام هذه الأداة.

إليك كود بدء التشغيل:

import QtQuick import QtQuick.Window import QtQuick.Controls Window { width: 640 height: 480 visible: true title: qsTr("Hello World") readonly property int delegateWidth: 640 readonly property int delegateHeight: 30 ListModel { id: items ListElement { name: "red" textColor: "red" } ListElement { name: "blue" textColor: "blue" } ListElement { name: "green" textColor: "green" } } ListView { id: listView anchors.fill: parent model: items delegate: Label { text: model.name color: model.textColor width: delegateWidth height: delegateHeight horizontalAlignment: Qt.AlignHCenter verticalAlignment: Qt.AlignVCenter } } }

الآن، يمكننا فحص الكود باستخدام هذه الأداة:

1. تأكد من إضافة المسار إلى الدليل الذي يوجد فيه qmllint.exe إلى متغيرات البيئة.

2. ما عليك سوى تشغيل الأمر.

3. qmllint <pathToFile>

4. تحقق من مخرجات وحدة التحكم.

qmllint output in the terminal showing unqualified access warnings in a QML file.

كما ترى، يمنحنا qmllint بعض التحذيرات حتى في هذه القطعة القصيرة من الكود. أعلم أنك لن ترتكب أخطاءً كهذه أبداً، لكن ثق بي، قد يرتكبها زملاؤك في الفريق، أو حتى قد تمر أنت بيوم سيئ وتنسى اتباع أفضل الممارسات.

لنلقِ نظرة فاحصة على المخرجات. لدينا بعض التحذيرات المتعلقة بالوصول غير المؤهل ومعلومات تفيد بأن النموذج تم حقنه ضمنيًا في المفوّض. لا يكتفي بطباعة التحذيرات فحسب، بل يقدم لنا أيضًا معلومات حول كيفية التخلص منها. لذا دعونا نتبع الاقتراحات.

5. أصلح التحذيرات المطبوعة وفقًا للمخرجات.

6. import QtQuick 7. import QtQuick.Window 8. import QtQuick.Controls 9. 10. Window { 11. id: root //تمت إضافة id إلى المكوّن الجذري 12. width: 640 13. height: 480 14. visible: true 15. title: qsTr("Hello World") 16. 17. readonly property int delegateWidth: 640 18. readonly property int delegateHeight: 30 19. 20. ListModel { 21. id: items 22. 23. ListElement { 24. name: "red" 25. textColor: "red" 26. } 27. ListElement { 28. name: "blue" 29. textColor: "blue" 30. } 31. ListElement { 32. name: "green" 33. textColor: "green" 34. } 35. } 36. 37. ListView { 38. id: listView 39. anchors.fill: parent 40. model: items 41. 42. delegate: Label { 43. //تمت إضافة الخاصية المطلوبة إلى المفوّض 44. required property string name 45. required property string textColor 46. 47. text: name 48. color: textColor 49. 50. //تمت إضافة id للوصول مباشرةً إلى الخصائص من الجذر. 51. width: root.delegateWidth 52. height: root.delegateHeight 53. 54. horizontalAlignment: Qt.AlignHCenter 55. verticalAlignment: Qt.AlignVCenter 56. } 57. } 58. }

6. أعد تشغيل qmllint.

qmllint warnings with suggestion to use ComponentBehavior: Bound in QML.

وجد تحذيرًا آخر وزودنا بمعلومات حول كيفية إصلاحه. لنقم بالتغيير.

//تمت إضافة pragma اللازمة في أعلى الملف
pragma ComponentBehavior: Bound

import QtQuick
import QtQuick.Window
import QtQuick.Controls

Window {
    id: root
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")

    readonly property int delegateWidth: 640
    readonly property int delegateHeight: 30

    ListModel {
        id: items
        ListElement { name: "red"; textColor: "red" }
        ListElement { name: "blue"; textColor: "blue" }
        ListElement { name: "green"; textColor: "green" }
    }

    ListView {
        id: listView
        anchors.fill: parent
        model: items
        delegate: Label {
            required property string name
            required property string textColor
            text: name
            color: textColor
            width: root.delegateWidth
            height: root.delegateHeight
            horizontalAlignment: Qt.AlignHCenter
            verticalAlignment: Qt.AlignVCenter
        }
    }
}

لن تطبع الجولة التالية من qmllint أي شيء. لقد أصلحنا الملف بالكامل.

حسنًا، لقد أكملنا أول فحص qmllint. ماذا الآن؟

سيكون من الرائع تكوين إعدادات qmllint لتتمكن من فحص جميع الملفات بأمر واحد، وجعل qmllint يدرك الوحدات الخارجية، وتخزين المخرجات في مكان ما، وربما يمكننا منحه المزيد من القوة ليتمكن من حل تحذيراتنا تلقائياً.

تكوين qmllint

يمكن تكوين qmllint بشكل منفصل لكل مشروع. قد يكون هذا مفيداً إذا كنت ترغب في تعطيل بعض التحذيرات أو توفير مسار إلى وحدات إضافية.

من الممكن تحقيق النتيجة نفسها عن طريق تشغيل الأمر qmllint مع خيارات إضافية، لكن من الأفضل بكثير تكوين qmllint مرة واحدة في ملف منفصل. يمكنه إنشاء ملف التكوين الافتراضي لنا.

qmllint –write-defaults

يتم إنشاء ملف .qmllint.ini في الدليل الذي نفّذنا منه الأمر. يكون qmllint على علم بهذه الإعدادات إذا قام بفحص ملف موجود في نفس دليل الإعدادات أو في الأدلة الفرعية. يمكنك تجاهل هذه الإعدادات إذا أردت أو تجاوز بعض الخيارات من سطر الأوامر.

//ignore settings file qmllint --ignore-settings //overwrite CompilerWarnings option qmllint --compiler warning

الآن يمكننا تحديد الفئة التي ينبغي تعطيلها أو معاملتها كرسالة معلومات. في حالتنا، أوصي بتمكين CompilerWarnings. ستحذرك من أجزاء كود qml التي لا يمكن تجميعها بواسطة qmlsc.

[Warnings]
ImportFailure=warning
ReadOnlyProperty=warning
BadSignalHandlerParameters=warning
UnusedImports=info
DuplicatedName=warning
PrefixedImportType=warning
AccessSingletonViaObject=warning
Deprecated=warning
ControlsSanity=disable
UnresolvedType=warning
LintPluginWarnings=disable
MultilineStrings=info
RestrictedType=warning
PropertyAliasCycles=warning
VarUsedBeforeDeclaration=warning
AttachedPropertyReuse=disable
RequiredProperty=warning
WithStatement=warning
InheritanceCycle=warning
UnqualifiedAccess=warning
UncreatableType=warning
MissingProperty=warning
InvalidLintDirective=warning
**CompilerWarnings=warning**
UseProperFunction=warning
NonListProperty=warning
IncompatibleType=warning
TopLevelComponent=warning
MissingType=warning
DuplicatePropertyBinding=warning

[General]
DisableDefaultImports=false
AdditionalQmlImportPaths=
OverwriteImportTypes=
DisablePlugins=
ResourcePath=

ضمان الإلمام بالوحدات

لنوسّع تطبيقنا قليلًا بحيث يستخدم بعض الوحدات. على سبيل المثال، نريد إنشاء زر مخصص يُستخدم كمفوَّض في ListView الخاصة بنا. يمكن أيضًا استخدام الزر في أجزاء مختلفة من التطبيق، لذا لا بأس في وضعه في وحدة منفصلة.

هيكل الملفات:

QML project structure with qmllint configuration and custom imports module.
//Main.qml
pragma ComponentBehavior: Bound

import QtQuick
import QtQuick.Window
import QtQuick.Controls
import CustomControls

Window {
    id: root
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")

    readonly property int delegateWidth: 640
    readonly property int delegateHeight: 30

    ListModel {
        id: items
        ListElement {
            name: "red"
            textColor: "red"
        }
        ListElement {
            name: "blue"
            textColor: "blue"
        }
        ListElement {
            name: "green"
            textColor: "green"
        }
        ListElement {
            name: "black"
            textColor: "black"
        }
    }

    ListView {
        id: listView
        anchors.fill: parent
        model: items
        delegate: CustomButton {
            required property string name
            required property string textColor
            text: name
            color: textColor
        }
    }
}

//CustomButton.qml
import QtQuick
import QtQuick.Controls.Fusion as Fusion

Fusion.Button {
    id: control
    property alias color: content.color
    implicitWidth: 640
    implicitHeight: 30

    contentItem: Fusion.Label {
        id: content
        text: control.text
        verticalAlignment: Text.AlignVCenter
        horizontalAlignment: Text.AlignHCenter
    }
}

لنقم بفحص Main.qml.

qmllint output showing import errors and unresolved types in a QML project.

لماذا توجد كل هذه التحذيرات؟ لقد أجرينا تغييرات طفيفة فقط على الملف، مثل إضافة وحدة وتغيير التسمية إلى CustomButton. السبب هو أن الأداة لا تدرك الوحدات التي أنشأناها. فكيف يمكننا جعلها تدرك هذه الوحدات؟

هناك طريقتان:

1. يمكننا تشغيل qmllint مع خيار إضافي.

qmllint -I imports Main.qml

2. أضف المسار إلى الدليل الأصلي للوحدة في ملف الإعدادات.

AdditionalQmlImportPaths=imports

لنقم بتشغيل qmllint مرة أخرى.

Running qmllint from the command line to analyse QML files.

أصبح المكوّن الآن يُقرأ بشكل صحيح، واختفت جميع التحذيرات دون أي تغييرات في الكود. لم تتبقَّ سوى رسالة واحدة تفيد بأن الملف يحتوي على استيراد غير مستخدم. وهذا متبقٍّ من الإصدار السابق من الكود عندما كنا نستخدم Label. من السهل نسيان إزالة الاستيرادات التي لم تعد مطلوبة. لذلك، في كل مرة يتم فيها إنشاء مكوّن جديد، نحتاج إلى التأكد من تمرير المسار بشكل صحيح إلى إعدادات qmllint.

تخزين المخرجات في ملف JSON

في بعض الحالات، لا يكفي عرض المخرجات مباشرة في وحدة التحكم. على سبيل المثال، قد تكون المخرجات طويلة جدًا لدرجة أن قراءة التحذيرات لم تعد واضحة، أو قد تحتاج التقرير إلى تخزينه في مكان ما لمزيد من التحليل. فهو يتيح لنا تخزين المخرجات بسهولة بصيغة JSON.

qmllint –json report.json Main.qml

ينشئ الأمر أعلاه ملف report.json. يتم تخزين نتائج فحص ملف Main.qml في هذا الملف.

//report.json { "files": [ { "filename": "C:/Users/kaj/Documents/qmllint_test/Main.qml", "success": true, "warnings": [ { "charOffset": 74, "column": 1, "id": "unused-imports", "length": 6, "line": 5, "message": "Unused import", "suggestions": [], "type": "info" } ] } ], "revision": 3 }

التحقق من جميع ملفات QML في التطبيق

حسناً، نحن نعرف كيفية فحص ملف واحد. ومع ذلك، بالنسبة للمشاريع الأكبر، سيكون من الأفضل بكثير أن نتمكن من فحص جميع الملفات في المشروع بأمر qmllint واحد. من الممكن تمرير عدة ملفات كوسائط. سيقوم QMLllint بفحصها جميعاً. ومع ذلك، لا يزال هذا ليس الحل الأفضل.

qmllint Main.qml imports/CustomControl/CustomButton.qml

أوصي بإخفاء تنفيذ الأمر في نص برمجي. في حالتي، أكتب نصاً بسيطاً بلغة Python يتحقق من جميع ملفات qml في الدليل والأدلة الفرعية ويشغّل qmllint عليها. تُخزَّن النتيجة في ملف report.json.

import os import subprocess import sys command = ["qmllint"] json_file = "report.json" directory = os.getcwd() qml_files = [] command += ["--json", json_file] for root, dirs, files in os.walk(directory): for file in files: if file.endswith(".qml"): path = os.path.join(root, file) qml_files.append(path) if not qml_files: print(f"No .qml files found in {directory}") sys.exit(1) command += qml_files print(f"Linting {len(qml_files)} QML files in {directory}") with open(json_file, "w") as f: subprocess.run(command, stdout=f, stderr=subprocess.PIPE)

qmllint مكتفٍ ذاتيًا

في مشروع كبير، قد تكون لدينا قائمة طويلة جدًا من التحذيرات في عدة ملفات. قد يكون معظمها متعلقًا بـ"الوصول غير المؤهل". الإصلاح بسيط، لكنه سيتطلب بالتأكيد بعض الوقت لتطبيقه. في هذه الحالة، يمكننا تفويض qmllint للقيام بذلك نيابة عنا. لنجربه على الإصدار السابق من الكود مع Label كمفوَّض لقائمة ListView الخاصة بنا:

delegate: Label { required property string name required property string textColor text: name color: textColor width: delegateWidth height: delegateHeight }

مخرجات qmllint:

Corrected QML code after resolving issues reported by qmllint.

الآن نحن واثقون من أن التحذيرات سهلة الإصلاح، وأن qmllint يعرف بالضبط كيفية إصلاحها. لذا، يمكننا السماح له بإصلاحها تلقائيًا. الشيء الوحيد الذي نحتاج إلى فعله هو تشغيل الأمر نفسه مع خيار إضافي "-f"

qmllint -f Main.qml

يتم تطبيق الإصلاح على Main.qml ويتم تخزين النسخة السابقة من الملف في ملف Main.qml.bak، حتى نتمكن من الاحتفاظ بنسخة احتياطية منه في حال حدوث أي خطأ. Main.qml بعد الإصلاح:

delegate: Label { required property string name required property string textColor text: name color: textColor width: root.delegateWidth height: root.delegateHeight }

كما نرى، فإنه يقوم بالضبط بنفس ما نقوم به، لكن يمكننا توفير بعض الوقت من خلال تفويض العمل إلى qmllint.

الملخص

qmllint أداة مفيدة جداً لمطوري Qt/QML. إذا تم استخدامها بشكل متكرر وبالطريقة الصحيحة، فإنها يمكن أن تضمن كتابة ملفات qml الخاصة بك دون أخطاء.

آمل أن أكون قد أوضحت لكم لماذا ينبغي عليكم استخدام qmllint في مشاريعكم وكيفية استخدامه. بالطبع، هناك العديد من الطرق الأخرى لاستخدامه، لكن الطرق التي وصفتها في المقال هي، في رأيي، الأكثر أهمية. خطوتكم التالية هي دمج فحوصات qmllint في خط أنابيب CI الخاص بكم وتروا كم من الأخطاء فاتكم. تذكروا أن qmllint صديقكم وسيخبركم دائمًا عندما ترتكبون خطأ.

سيساعدك خبراؤنا في بناء تطبيقات عالية الجودة وغنية بالميزات وسهلة الاستخدام باستخدام Qt. قم بزيارة صفحة عروض Qt لمعرفة المزيد.

الأسئلة الشائعة

qmllint هي أداة تحليل ثابتة لـ QML تساعد في اكتشاف الأخطاء والتحذيرات والمشكلات المحتملة في تطبيقات Qt. وهي تتيح للمطورين تحديد المشكلات مبكرًا، قبل تشغيل التطبيق.

يساعد استخدام qmllint في تحسين جودة الكود وتقليل أخطاء وقت التشغيل. فهو يبرز الاستخدام غير الصحيح للخصائص وعدم تطابق الأنواع والمشكلات الأخرى التي قد لا تكون مرئية على الفور أثناء التطوير.

يمكن تشغيل qmllint من سطر الأوامر من خلال توجيهه إلى ملف QML أو دليل مشروع. يقوم بتحليل الكود وإرجاع قائمة بالتحذيرات والأخطاء التي تحتاج إلى معالجة.

يمكن لـ qmllint اكتشاف أخطاء الصياغة وارتباطات الخصائص غير الصالحة وتناقضات الأنواع والمشكلات الشائعة الأخرى في كود QML. وهي مفيدة بشكل خاص لاكتشاف الأخطاء مبكرًا في عملية التطوير.