QMLフォーマッターの新機能

本稿は「What's New in QML formatting」の抄訳です

QMLファイルのフォーマットを行うQtの公式ツールであるqmlformatの最新アップデートにより、実用的な2つの改善点がもたらされました。それは、設定可能な改行と自動インポートソートです。これらの機能はコードの可読性と保守性を向上させるため、注目に値する貴重な追加機能と言えます。

指定した長さで改行

長いコード行は、特に多数のプロパティを持つ複雑なQMLコンポーネントを扱う場合、読みにくく操作しにくいことがあります。Qt 6.9以降、qmlformatは指定された文字長を超える行を自動的に改行できるようになりました。

Option Description
-W, --column-width < width > 指定された幅を超える行を改行します。行の折り返しを無効にするには -1 を使用します(デフォルト)。

改行の動作

最大行長を指定すると、qmlformatはその制限を超える行を自動的に改行します。例えば次のようになります:

// main.qml
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Templates as T

T.ItemDelegate {
id: control
highlighted: control.pressed || control.hovered || (control.ListView.view && control.ListView.view.currentIndex === index && control.ListView.view.highlightFollowsCurrentItem && control.enabled }

改行オプションを使用しない qmlformat だと、可読性が低下します:

import QtQuick
import QtQuick.Controls.Material
import QtQuick.Templates as T

T.ItemDelegate {
id: control
highlighted: control.pressed || control.hovered || (control.ListView.view && control.ListView.view.currentIndex === index && control.ListView.view.highlightFollowsCurrentItem && control.enabled
}

qmlformat -W 80 main.qml を使用すると、より読みやすい結果が得られます:


import QtQuick
import QtQuick.Controls.Material
import QtQuick.Templates as T

T.ItemDelegate {
            id: control
            highlighted: control.pressed || control.hovered || (
                                     control.ListView.view
                                     && control.ListView.view.currentIndex
                                     === index
                                     && control.ListView.view.highlightFollowsCurrentItem
                                     && control.enabled

インポート文のソート

雑然とした、あるいは一貫性のない順序のインポート文を管理するのは困難な場合があります。これは、複数のコントリビューターが同じファイルを変更する際に、不必要なマージコンフリクトを引き起こすことがよくあります。

これらの問題に対処するため、Qt 6.10ではqmlformatに --sort-imports オプションが導入されました。インポートをソートすることで、使用されているモジュールが明確になり可読性が向上します。また、不要な差分を最小限に抑えることでマージコンフリクトを減らし、すべてのQMLファイルで一貫した構造を強制します。

Option Description
-S, --sort-imports インポートをアルファベット順にソートします(警告:指定された名前が複数のモジュールの型を識別する場合、これはセマンティクスを変更する可能性があります!)

 

⚠️ 警告: --sort-imports を使用してインポートをアルファベット順にソートすると、複数のモジュールに同じ型名が存在する場合、QMLファイルのセマンティクスが変わる可能性があります。アプリケーションが期待どおりに動作することを確認するため、必ず結果を確認してください。

インポートのソートの動作


// Original main.qml
import QtQuick.Templates
import MyCustomModule

Button {
}

qmlformat -S main.qml を実行すると、次のようになります:

import MyCustomModule
import QtQuick.Templates

Button {
}

この例では、元の main.qml は MyCustomModule の Button を使用しています。--sort-imports を使ってフォーマットすると、セマンティクスが変更され、QtQuick.Templates の非視覚的な Button が使われるようになります。

今後の予定:カスタマイズ可能なセミコロンルール

Qt 6.10以降、新しい --semicolon-rule オプションが導入され、フォーマット時にJavaScriptのセミコロンがどのように扱われるかについて、ユーザーはより詳細に制御できるようになりました。この新しいフラグは2つのモードをサポートしています。一つは "always" で、これはすべてのJSステートメントにセミコロンを付加します。もう一つは "essential" で、JavaScriptの自動セミコロン挿入 (ASI) ルールにより、セミコロンを省略すると曖昧さにつながる場合を除き、これらを削除します。

Mode Description
always JavaScriptのすべてのステートメントにセミコロンを追加します(デフォルト)
essential ASI(自動セミコロン挿入)によって安全でなくなる場合を除き、セミコロンを削除します

 

: このオプションは、JavaScriptステートメントの末尾にあるセミコロンにのみ影響します。QML要素の末尾にあるセミコロンは、--semicolon-rule の設定に関わらず、qmlformat によって常に削除されます。

これらのセミコロンルールの改善の一環として、EmptyStatements(空のステートメント)の扱いも洗練されました。 if、for、whileなどの本体を持たない制御構造に続くセミコロンは、新しいインデントされた行ではなく、閉じ括弧の直後に表示されるようになりました。加えて、連続する複数の空のステートメントは、よりすっきりとした結果を得るために単一のセミコロンにまとめられます。


import QtQuick
Item {
    Component.onCompleted: {
        for(;;);;
        ;;
        if (true);;;
        
        ;
        while (true);
        ;
        ;;
        var a = [1, 2, 3];;;;
        for (var i in a);;;;
    }
}

// qmlformat output prior to 6.10
import QtQuick

Item {
    Component.onCompleted: {
        for (; ; )
            ;
        ;
        ;
        ;
        if (true)
            ;
        ;
        ;

        ;
        while (true)
            ;
        ;
        ;
        ;
        var a = [1, 2, 3];
        ;
        ;
        ;
        for (var i in a)
            ;
        ;
        ;
        ;
    }
}

// qmlformat output as of 6.10
import QtQuick
Item {
    Component.onCompleted: {
        for (; ; );
        if (true);
        while (true);
        var a = [1, 2, 3];
        for (var i in a);
    }
}

セミコロンのカスタマイズの動作

// original test.js
let v = a;;;;;;
[1, 2, 3].forEach(x => console.log(x));;;;;
;;;
;;;
let x = v;;;
x = 10;;;
;
;;;
;
let y = x;;;
(function() { })();;
let a = b
+ c
;
let xx = y;
-x;

qmlformat --semicolon-rule=always test.js を実行すると、JavaScript ステートメントの最後にセミコロンが追加されます。

let v = a;
[1, 2, 3].forEach(x => console.log(x));
let x = v;
x = 10;
let y = x;
(function () {})();
let a = b + c;
let xx = y;
-x;

qmlformat --semicolon-rule=essential test.js を実行すると、意図しない動作につながる可能性のあるセミコロンを除き、すべてのセミコロンが削除されます。

たとえば、let v = a; の後のセミコロンは削除されません。これは、次の行が [ トークンで始まるためです:

let v = a;
[1, 2, 3].forEach(x => console.log(x))
let x = v
x = 10
let y = x;
(function () {})()
let a = b + c
let xx = y;
-x

JavaScript では、行の先頭にある (, [, +, - といった特定のトークンが、自動セミコロン挿入(ASI)が期待通りに機能するのを妨げることがあります。コードのセマンティクスが変わるのを避けるため、qmlformat はこのような場合にセミコロンを保持します。

Qt Creator 17へのqmlformat統合

qmlformat がQt Creator 17に直接統合されたことをお知らせします。これにより、QMLコードのフォーマットがよりアクセスしやすく、プロジェクト全体で一貫したものになりました。

Qt Creatorの「設定」にある「Qt Quick」>「コードスタイル」で、QMLコードフォーマッターとして qmlformat を選択すると、Qt Creatorはシステム上で利用可能な最新バージョンの qmlformat を自動的に検出します。そして、qmlformat --write-defaults コマンドを実行してデフォルト設定を生成します。この設定は、あなたのフォーマットの好みに合わせてカスタマイズできます。追加情報はこちらで確認できます。グローバル設定は QStandardPaths::GenericConfigLocation/.qmlformat.ini に保存されます。

qmlformat が設定をどのように探し出すかを理解することは重要です。フォーマット中、まずソースファイルのあるディレクトリで .qmlformat.ini ファイルを探します。もしローカル設定が見つからない場合、適切な設定ファイルが見つかるか、ルートディレクトリに到達するまで、ディレクトリ階層を上にたどって検索します。それでもローカル設定が見つからない場合は、一般的な設定場所に保存されているグローバル設定が使用されます。

このフローチャートは、qmlformat が設定ファイルをどのように検索するかを示しています:

[Start: QML file directory]
↓
[Is .qmlformat.ini here?] → Yes → [Use it]
↓ No
[Go to parent directory]
↓
[Repeat until root]
↓
[Still not found?] → [Use global config in GenericConfigLocation]

Qt Creator の設定で定義した内容と異なる予期せぬフォーマットの動作に気づいたら、プロジェクトのディレクトリツリー内に .qmlformat.ini ファイルが存在しないか確認してみてください。このファイルはグローバル設定よりも優先されるため、意図したスタイル設定が上書きされている可能性があります。

Qt Creator 17でのqmlformatの動作

Qt Creator 17では、QMLフォーマットオプションを設定するための新しい設定セクションが導入されました。QMLファイルでqmlformatを使用するには、明示的にそれを優先フォーマッターとして選択する必要があります。

pref

Qt Creatorでqmlformatを設定する

  1. Preferences > Qt Quick > Code Style に移動します
  2. Formatter Selection ドロップダウンで, QmlFormat[LSP] を選択します
  3. Global qmlformat configuration で、行の幅やインポートのソートなどの追加オプションを好みに合わせて調整します

グローバル qmlformat 設定をカスタマイズすると、右側のプレビューが更新され、QMLファイルをフォーマットしたときに設定がどのように適用されるかが表示されます。

QMLファイルでqmlformatを使用する

設定が終わればQMLファイルのフォーマットは簡単です:

  1. Qt CreatorのエディタでQMLファイルを開きます
  2. 右クリックしてコンテキストメニューを開きます
  3. Reformat Document を選択します

または、 Tools > QQml/JS > Reformat Document から再フォーマットアクションにアクセスすることもできます。

なお、以前にQt Creatorの内蔵フォーマッターを使用していた場合、qmlformatに切り替えると異なるフォーマット結果になることがあります。これは、2つのフォーマッターが異なるアルゴリズムとアプローチでコードスタイルを処理するため、想定される動作です。最初のフォーマット後にコードを確認し、期待どおりになっているか確認することをお勧めします。

別のバージョンのqmlformatを使用する

特定のバージョンのqmlformatや、独自のフォークを使用したい場合は、Custom Formatter オプションが利用できます。パスを設定し、引数セクションでオプションを指定する必要があります。利用可能なすべてのオプションについては、公式のqmlformatドキュメントを参照してください。

さらに、qmlformatを独自のスクリプトでラップすると、ワークフローを高速化できます。シンプルなシェルラッパーを使えば、ニーズに合わせてqmlformatの動作をカスタマイズできます。例えば次の通りです:


#!/bin/sh
# qmlformat-wrapper.sh

/path/to/your/qmlformat --indent-width 12 "$@"

chmod +x qmlformat-wrapper.sh
その後、このスクリプトをカスタムフォーマッターとして使用できます。スクリプトのパスを設定し、引数セクションを空にするか(または必要に応じて追加オプションを加えるか)、あるいは、カスタムフォーマッターのパスを直接 qmlformat 実行ファイルに指定し、必要なオプションを引数セクションに渡すことも可能です:

 

customformatter

: Qt Creator は現在、互換性とユーザーの期待を維持するため、デフォルトで従来のフォーマッタを使用しています。これら2つのフォーマッタは同一の出力を生成する保証がないため、Qt Creator で qmlformat をデフォルトで使用すると、特に共同プロジェクトや既存のコードベースにおいて、ユーザーのコードスタイルに予期せぬ、または意図しない変更が生じる可能性があります。統合がより成熟し、連携がとれるようになるまで、Qt Creator はユーザーに選択を委ねています。

次の展開は?

qmlformat がQt Creator 17に統合されたことで、QMLでの作業が少し楽になりました。自動改行やインポートのソートといった新機能は、コードをクリーンで読みやすく、一貫性のあるものに保つのに役立ちます。そして、フォーマット機能がQt Creatorに直接組み込まれているため、ほんの数クリックで利用できます。

今後は、プロジェクト全体の一括フォーマット機能に取り組む予定です。

ぜひお試しいただき、ご意見をお聞かせください。皆さんのフィードバックが、QMLツールの改善に役立ちます。

Qtコミュニティの一員として、いつもありがとうございます。Qt Creator 17とqmlformatで、楽しいコーディングを!


Blog Topics:

Comments