Qt自带的QML文件格式工具qmlformat的最新更新带来了两项实用的改进:可配置的换行设置和自动导入排序功能。这些功能提高了代码的可读性和可维护性,是值得关注的新增功能。
指定长度后换行
冗长的代码行可能导致难以阅读和浏览,尤其是在处理具有众多属性的复杂QML组件时尤为明显。
从 Qt 6.9 起,qmlformat 可以自动对超过指定字符长度的代码行进行换行 。
选项
说明
-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 文件中强制保持一致的结构。
选项
说明
-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 分号。新标志支持两种模式:always ,即会在所有JavaScript语句添加分号;essential ,即删除分号,除非因JavaScript的自动分号插入 (ASI) 机制,删除分号会导致歧义。
模式
说明
always
在所有JavaScript语句中添加分号(默认)
essential
删除分号,除非ASI(自动分号插入)机制可能引发歧义
注意: 该选项只影响JavaScript语句末尾的分号。QML元素末尾的分号总是会被qmlformat移除,与-semicolon-rule设置无关。
作为分号规则改进的一部分,对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在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=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 在这些情况下会保留分号。
我们很高兴地宣布,qmlformat现已直接集成至Qt Creator 17中,从而使QML 代码格式化在各项目之间更易访问且保持一致。
当您在Preference>Qt Quick>Code Style中选择qmlformat作为QML代码格式器时,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 中设置的格式规则不一致的情况,请检查您的项目目录树(directory tree )中是否存在 .qmlformat.ini 文件。此类文件的优先级高于全局配置,可能会覆盖您预期的样式化配置。
在Qt Creator 17中,我们新增了一个用于配置 QML 格式化选项的偏好设置板块 。要在QML文件中使用 qmlformat,您需要明确将其设置为首选格式化器 。
导航至Preference>Qt Quick>Code Sytle
在Formatter Selection 下拉菜单中,选择QmlFormat[LSP]
在Global qmlformat configuration 中,根据您的偏好调整列宽或导入排序等其他选项
自定义全局qml格式配置后,右侧的预览窗口将实时展示格式化后的 QML 文件效果。
配置完成后,对QML文件进行格式化操作就非常简单了:
在编辑器中打开任意QML文件
右键点击打开上下文菜单
选择Reformat Document
或者,您也可以通过Tools>QQml/JS>Reformat Document 进行 重格式化操作。
请注意:如果您此前使用的是Qt Creator自带的内置格式化器 ,切换到qmlformat可能会产生不同的格式化结果。这是因为两者使用了不同的格式化算法和代码风格规则。建议在首次格式化后检查代码,以确保其符合您的预期。
如果您 想使用特定版本的qmlformat或自己的分支版本,可以选择 Custom Formatter(自定义格式化器) 选项 。您 需要在参数部分设置路径并指定选项。有关所有可用选项,请参阅qmlformat官方文档 。
您还可以使用shell 脚本对qmlformat 进行封装,从而加快工作流程。以下是一个简单的shell 包装示例:
#!/bin/sh # qmlformat-wrapper.sh /path/to/your/qmlformat --indent-width 12 "$@"
chmod +x qmlformat-wrapper.sh
然后您可以将该脚本路径设置为 Custom Formatter,参数区域可以留空,或者根据需要添加额外选项。您也可以直接将Custom Formatter path 指向qmlformat 可执行文件,并在参数区域填写相关选项:
注意: Qt Creator目前仍默认使用旧版格式化器,以保持兼容性和用户已有的使用习惯。由于旧版格式化器与 qmlformat 的输出并不完全一致,因此在Qt Creator中默认使用qmlformat可能会导致用户的代码风格造成意外影响 ,尤其是在已有项目或多人协作的项目中。在qmlformat的集成更加成熟和统一之前,Qt Creator 将选择权交由用户自行决定 。
下一步是什么?
随着qmlformat集成进Qt Creator 17,QML开发工作变得更加轻松。 新功能(如自动换行和导入排序)有助于保持代码的整洁、可读性并且风格统一。而且格式化操作就集成在Qt Creator 中,仅需点击几下即可完成。
接下来我们将致力于开发对整个项目进行批量格式化的功能。
欢迎您亲自体验这些新功能,并反馈您的使用感受。您的意见将帮助我们进一步优化QML 工具,为所有开发者带来更好的体验。
感谢您成为Qt社区的一员--祝您在使用Qt Creator 17和qmlformat时编码愉快!