QML 정적 분석 2 - 사용자 정의 분석 단계

이 장에서는 지난 장에서 만든 플러그인을 확장하여 qmllint에 사용자 정의 분석 패스를 추가하는 방법을 설명합니다. 데모 목적으로, Text 요소의 text 속성에 "Hello world!"가 할당되어 있는지 확인하는 플러그인을 만들어 보겠습니다.

이를 위해 ElementPass 을 상속받은 새로운 클래스를 생성합니다.

참고: 플러그인이 등록할 수 있는 패스에는 ElementPasses 과 PropertyPasses 의 두 가지 유형이있습니다 . 이 튜토리얼에서는 더 간단한 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;
};

우리의 ` HelloWorldElementPass `은 ` Text ` 요소를 분석해야 하므로, ` Text ` 타입에 대한 참조가 필요합니다. ` resolveType ` 함수를 사용하여 이를 얻을 수 있습니다. 타입을 매번 새로 해결하는 것을 피하기 위해, 생성자에서 한 번만 이 작업을 수행하고 타입을 멤버 변수에 저장합니다.

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

이 패스의 실제 로직은 shouldRun 와 run 라는 두 함수에서 처리됩니다. 이 함수들은 qmllint가 분석하는 파일 내의 모든 Element에 대해 실행됩니다.

shouldRun 메서드에서는 현재 Element가 Text에서 파생되었는지 확인하고, 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;
}

이곳에서 검사를 통과한 요소들만 pass의 run 메서드를 통해 분석됩니다. run 내부에서 모든 검사를 수행할 수도 있지만, 성능 측면과 코드 가독성 향상을 위해 일반적으로 관심사를 분리하는 것이 바람직합니다.

run 함수에서는 text 속성에 대한 바인딩을 가져옵니다. 바인딩된 값이 문자열 리터럴인 경우, 예상한 인사말과 일치하는지 확인합니다.

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 `가 모두 할당되어 있을 수도 있습니다.

마지막으로, 패스(pass)의 인스턴스를 생성하고 이를 ` 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 ` 요소의 리터럴 바인딩에 대해서는 잘못된 인사말에 대해 올바르게 경고를 표시합니다.
  • NotText 는 Text 를 상속받지 않으므로, inherits 검사에서 이를 제외하게 되어 분석 과정에서 건너뛰게 됩니다.
  • 사용자 정의 MyText 요소는 Text 를 상속받으므로, 결과적으로 예상한 대로 경고가 표시됩니다.

요약하자면, 사용자 정의 패스를 통해 qmllint 를 확장하는 데 필요한 단계를 살펴보았으며, 정적 검사(static checks)의 한계도 파악했습니다.

© 2026 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.