QML 정적 분석 2 - 사용자 지정 패스

이 장에서는 지난 장에서 만든 플러그인을 확장하여 사용자 정의 분석 패스를 qmllint에 추가하는 방법을 보여줍니다. 데모를 위해 Text 요소에 텍스트 속성에 "Hello world!"가 할당되어 있는지 확인하는 플러그인을 만들겠습니다.

이를 위해 ElementPass 에서 파생된 새 클래스를 만듭니다.

참고: 플러그인이 등록할 수 있는 패스에는 ElementPassesPropertyPasses 의 두 가지 유형이 있습니다. 이 튜토리얼에서는 더 간단한 ElementPass 만 고려하겠습니다.

class HelloWorldElementPass : public QQmlSA::ElementPass
{
public:
    HelloWorldElementPass(QQmlSA::PassManager *manager);
    bool shouldRun(const QQmlSA::Element &element) override;
    void run(const QQmlSA::Element &element) override;
private:
    QQmlSA::Element m_textType;
};

HelloWorldElementPassText 요소를 분석해야 하므로 Text 유형에 대한 참조가 필요합니다. resolveType 함수를 사용하여 이를 얻을 수 있습니다. 유형을 계속 재확인하고 싶지 않으므로 생성자에서 이 작업을 한 번 수행하고 유형을 멤버 변수에 저장합니다.

HelloWorldElementPass::HelloWorldElementPass(QQmlSA::PassManager *manager)
    : QQmlSA::ElementPass(manager)
{
    m_textType = resolveType("QtQuick", "Text");
}

패스의 실제 로직은 shouldRunrun 의 두 함수에서 발생합니다. 이 두 함수는 qmllint가 분석하는 파일의 모든 엘리먼트에서 실행됩니다.

shouldRun 메서드에서는 현재 엘리먼트가 Text에서 파생되었는지 확인하고 텍스트 프로퍼티에 바인딩이 있는지 확인합니다.

bool HelloWorldElementPass::shouldRun(const QQmlSA::Element &element)
{
    if (!element.inherits(m_textType))
        return false;
    if (!element.hasOwnPropertyBindings(u"text"_s))
        return false;
    return true;
}

여기서 검사를 통과한 요소만 run 메서드를 통해 분석됩니다. run 자체 내에서 모든 검사를 수행할 수도 있지만 일반적으로 성능과 코드 가독성 향상을 위해 분리하는 것이 바람직합니다.

run 함수에서는 텍스트 속성에 대한 바인딩을 검색합니다. 바인딩된 값이 문자열 리터럴인 경우 예상한 인사말인지 확인합니다.

void HelloWorldElementPass::run(const QQmlSA::Element &element)
{
    auto textBindings = element.ownPropertyBindings(u"text"_s);
    for (const auto &textBinding: textBindings) {
        if (textBinding.bindingType() != QQmlSA::BindingType::StringLiteral)
            continue;
        if (textBinding.stringValue() != u"Hello world!"_s)
            emitWarning("Incorrect greeting", helloWorld, textBinding.sourceLocation());
    }
}

참고: 대부분의 경우 속성에는 바인딩이 하나만 할당됩니다. 그러나 예를 들어 같은 프로퍼티에 리터럴 바인딩과 Behavior 바인딩이 할당될 수 있습니다.

마지막으로, 패스의 인스턴스를 생성하고 PassManager 에 등록해야 합니다. 이 작업은 플러그인의 함수에

    manager->registerElementPass(std::make_unique<HelloWorldElementPass>(manager));

를 플러그인의 registerPasses 함수에 추가하면 됩니다.

다음을 통해 예제 파일에서 qmllint를 호출하여 플러그인을 테스트할 수 있습니다.

qmllint -P /path/to/the/directory/containing/the/plugin --Plugin.HelloWorld.hello-world info test.qml

test.qml 가 다음과 같으면

import QtQuick

Item {
     id: root

     property string greeting: "Hello"

     component MyText : Text {}

     component NotText : Item {
         property string text
     }

     Text { text: "Hello world!" }
     Text { text: root.greeting }
     Text { text: "Goodbye world!" }
     NotText {
         text: "Does not trigger"
          MyText { text: "Goodbye world!" }
     }
}

처럼 보이면

Info: test.qml:22:26: Incorrect greeting [Plugin.HelloWorld.hello-world]
          MyText { text: "Goodbye world!" }
                         ^^^^^^^^^^^^^^^^
Info: test.qml:19:19: Incorrect greeting [Plugin.HelloWorld.hello-world]
     Text { text: "Goodbye world!" }

를 출력합니다. 여기서 몇 가지 관찰할 수 있습니다:

  • 첫 번째 Text 에는 예상되는 인사말이 포함되어 있으므로 경고가 없습니다.
  • 두 번째 Text 는 런타임에 잘못된 경고가 표시됩니다( "Hello world" 대신"Hello". 그러나 이것은 리터럴 바인딩이 아니라 다른 속성에 대한 바인딩이기 때문에 (일반적으로) qmllint에서 감지할 수 없습니다. 리터럴 바인딩만 검사하므로 이 바인딩을 건너뛰기만 하면 됩니다.
  • 세 번째 Text 요소의 리터럴 바인딩의 경우 잘못된 인사말에 대해 올바르게 경고합니다.
  • NotTextText 에서 파생되지 않으므로 분석에서는 inherits 검사를 통해 이를 건너뛰게 됩니다.
  • 사용자 정의 MyText 요소는 Text 에서 상속되므로 결과적으로 예상되는 경고가 표시됩니다.

요약하면, 사용자 지정 패스로 qmllint 을 확장하는 데 필요한 단계를 살펴보고 정적 검사의 한계도 알게 되었습니다.

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.