我平时会接触一些 CFML 项目。CFML 本身并不是只有一种语法,一个 .cfm 文件里经常同时出现 CFTag、CFScript、SQL、HTML、CSS 和 JavaScript。代码可以正常运行,但文件写久以后,缩进和标签结构很容易变乱。
市面上当然有各种代码格式化工具,不过直接拿通用 Formatter 处理 CFML,结果经常不太理想。它可能认识 HTML,却不认识 cfquery、cfloop 和 cfoutput;也可能能整理 JavaScript,却会把 CFML 中的表达式或混合内容改坏。
我需要的其实不是一个会把所有代码重写成固定风格的工具,而是一个更保守的 Formatter:把缩进和标签结构整理好,同时尽量不修改原来的代码风格。
所以我用 TypeScript 开发了 CFML Auto Formatter,并把它作为 VS Code 扩展发布了出来。
项目使用 MIT License,目前插件可以直接从 Visual Studio Marketplace 或 Open VSX 安装。
这个插件解决什么问题
CFML 文件比较特殊。下面这种代码在实际项目里很常见:
<cfquery name="userList" datasource="mainDb">
SELECT id,name,email FROM users WHERE status = <cfqueryparam value="#status#">
</cfquery>
这里既有 CFML 标签,也有 SQL 和 CFML 表达式。再复杂一点的页面还会继续混入 HTML、CSS、JavaScript 和 cfscript。
如果只按照 HTML 的规则处理,SQL 不会得到正确缩进;如果把整个文件当成 JavaScript,CFML 标签又会被破坏。格式化 CFML 的难点不只是增加几个空格,而是先判断当前处理的是哪一种内容,再把对应范围交给合适的逻辑。
因此这个插件把格式化入口拆成了几种类型:
| 格式化类型 | 处理内容 |
|---|---|
| CFTag Syntax | CFML 标签、嵌套结构和缩进 |
| Script Syntax | cfscript 和 JavaScript 风格代码 |
| SQL Syntax | cfquery 或选中范围中的 SQL |
| HTML Syntax | .cfm 中的 HTML 内容 |
| CSS Syntax | .cfm 中的 CSS 内容 |
我可以只选择某个代码块进行格式化,不必每次都处理整个文件。对于混合内容很多的 .cfm 文件,这种方式更容易控制结果。
主要功能
目前插件实现了这些功能:
.cfm和.cfc文件的 CFML 缩进整理;- CFML 标签自动换行;
- 按块级标签进行局部格式化;
- 单独格式化 CFTag、CFScript、SQL、HTML 和 CSS;
- 支持 VS Code 手动格式化和保存时格式化;
- 尽量保留已有换行和原本的代码风格;
- 支持 Tab、空格数、换行保留、括号风格等配置;
- SQL 可以选择 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等方言;
- 提供中文、英文和日文提示信息。
插件不依赖本机的 ColdFusion 或 Lucee 运行环境。安装扩展以后,格式化逻辑直接在 VS Code 的 Node.js Extension Host 中运行。
技术栈
| 部分 | 使用的技术 |
|---|---|
| 主要语言 | TypeScript |
| 编辑器接口 | VS Code Extension API |
| CFML 标签处理 | 自定义解析与正则预处理 |
| Script、HTML、CSS 美化 | js-beautify |
| SQL 格式化 | sql-formatter + 自定义兼容逻辑 |
| 构建 | Webpack、ts-loader |
| 生产压缩 | TerserPlugin |
| 扩展打包 | VSCE |
| 发布平台 | Visual Studio Marketplace、Open VSX |
我选择 TypeScript 的原因很直接:VS Code Extension API 本身有完整的类型定义,TextDocument、Range、TextEdit、FormattingOptions 这些对象如果只用 JavaScript 开发,很容易在格式化范围和返回值上写错。
格式化工具又属于对代码原文进行修改的程序。一个行号算错、一个 Range 边界不对,就可能替换掉不该替换的内容。TypeScript 不能解决全部问题,但至少能提前挡住一部分参数和返回类型错误。
VS Code 格式化接口怎么接入
插件激活后,会给 cfml、cfm 和 cfc 注册两类 Provider:
DocumentFormattingEditProvider:处理整个文件;DocumentRangeFormattingEditProvider:只处理选中的范围。
核心思路不是直接修改编辑器文本,而是计算一组 TextEdit,再交给 VS Code 应用。这样可以继续使用编辑器原生的撤销、保存时格式化和 Format Document 流程。
简化以后,结构大致是这样:
const provider: vscode.DocumentFormattingEditProvider = {
async provideDocumentFormattingEdits(document, options, token) {
const manager = new FormatterManager();
return manager.formatDocument(document, options, token);
},
};
vscode.languages.registerDocumentFormattingEditProvider(
"cfml",
provider
);
局部格式化也使用相同思路,只是多接收一个 Range。插件会先确认选区是不是完整的 CFML 块级标签范围,再决定是否继续处理。这样可以避免用户只选中了半个标签时直接修改代码。
CFML 标签自动换行
这个插件里有一层 CFML 标签预处理。它会先找出一行中的 CFML 标签,把标签、标签前的普通文本和标签后的内容拆开,再生成对应的 TextEdit。
例如一行里同时写了多个标签:
<cfif isLogin><cfoutput>#userName#</cfoutput></cfif>
预处理会先把标签结构拆清楚,然后再进入缩进阶段。
这里不能简单使用 <.*?>。CFML 标签属性的字符串中也可能包含 >,如果正则在字符串内部提前结束,就会把一个完整标签切坏。因此我的匹配逻辑会分别处理单引号、双引号、转义字符、开始标签、结束标签和自闭合标签。
插件还会判断 cfif、cfloop、cfoutput 等块级标签是否前后对应。做局部格式化时,只有选区能够构成完整代码块才会继续执行。
这套实现目前没有假装自己是完整的 CFML AST。它更接近一套为实际格式化需求设计的轻量解析和预处理逻辑。这样部署简单,也不需要用户额外安装 Lucee 或 ColdFusion。
为什么不是所有内容都用同一个 Formatter
CFML 的混合语法决定了一个 Formatter 很难把全部内容都处理好。
对于 cfscript、JavaScript、HTML 和 CSS,我使用 js-beautify 作为基础能力,再把 VS Code 中的设置转换成对应选项。用户可以控制:
- 使用 Tab 还是空格;
- 空格缩进宽度;
- 是否保留原有换行;
- 最多保留多少连续空行;
- 大括号使用
collapse、expand还是其他风格; - 是否拆分链式调用;
- 文件末尾是否保留换行。
SQL 部分使用 sql-formatter,并允许选择 SQL 方言。但是 cfquery 中可能存在 cfloop、cfif 或 #expression#,这些内容不是普通 SQL。遇到这种情况,单纯调用第三方库并不够,所以插件还加入了自己的兼容处理。
这也是我开发这个插件过程中感受最明显的一点:引入一个 Formatter 库并不等于功能已经完成。真正花时间的是确定边界、保护 CFML 内容,以及处理各种混合语法。
处理 Auto Close Tag 冲突
开发过程中还遇到过一个比较隐蔽的问题:格式化标签时,Auto Close Tag 或 CFML 自动闭合功能可能认为我正在输入新的开始标签,于是自动插入结束标签。
最后结果就会变成,Formatter 正常整理了一次代码,另一个插件又在中间追加了标签。
现在执行手动格式化时,插件会:
- 读取 Auto Close Tag 和 CFML Auto Close 的原始设置;
- 临时关闭相关自动闭合功能;
- 执行本次格式化;
- 无论成功还是失败,都在
finally中恢复用户原来的设置。
这里的重点是恢复“原值”,而不是格式化完成后一律打开。因为用户原本可能就关闭了这些选项,插件不应该替用户修改长期设置。
命令和快捷键
安装以后,可以在编辑器右键菜单的 Format Code (satt) 中选择不同格式化方式,也可以使用命令面板。
当前提供的快捷键如下:
| 功能 | 快捷键 |
|---|---|
| CFTag Syntax | Shift + Alt + T |
| Script Syntax | Shift + Alt + S |
| HTML Syntax | Shift + Alt + H |
| CSS Syntax | Shift + Alt + C |
| SQL Syntax | Shift + Alt + Q |
如果选择了代码范围,插件只处理选中内容;没有选择内容时,CFTag 格式化可以处理整个文件。
构建和打包
源码使用 TypeScript 编写,入口文件是 src/extension.ts。构建时通过 ts-loader 交给 Webpack,最终输出 VS Code 加载的 dist/extension.js。
生产构建还使用 TerserPlugin 删除 console 和 debugger,并生成不包含源码正文的 Source Map。扩展本身运行在 Node.js 环境中,vscode 模块由 VS Code 提供,所以不会被打进 Bundle。
主要命令比较简单:
npm run build
npm run package
build 负责生成生产代码,package 通过 VSCE 生成可以安装和发布的 .vsix 文件。
发布到两个插件市场
目前插件已经发布到两个平台:
Visual Studio Marketplace
在 Visual Studio Marketplace 安装 CFML Auto Formatter
这是 VS Code 默认使用的扩展市场。普通 VS Code 用户直接在 Extensions 中搜索 CFML Auto Formatter 就可以安装。
Open VSX Registry
在 Open VSX 安装 CFML Auto Formatter
Open VSX 主要服务于使用开放扩展注册表的编辑器和发行版。发布到这里以后,不只标准版 VS Code 用户可以获取插件,使用 Open VSX 生态的用户也能安装。
我选择同时发布两个市场,是因为插件本身没有必要被某一个编辑器分发渠道限制。代码仓库也是公开的,安装页面、源码和 Issue 都可以互相对应。
目前的限制
这个项目现在已经能解决我日常遇到的大部分 .cfc 格式化问题,但我不想把它描述成已经支持所有 CFML 代码。
目前 .cfc 的自动缩进相对稳定;.cfm 因为经常混合 HTML、XML、CSS、JavaScript 和 SQL,整份文件一次性格式化仍有可能出现不理想的结果。因此插件在对 .cfm 执行全文格式化前会显示警告。
对于复杂页面,我更建议:
- 先选中完整的 CFML 块再格式化;
- SQL、HTML、CSS 和 Script 分别处理;
- 格式化后通过 Git Diff 确认改动;
- 特别复杂的
cfscript字符串、XML 或动态 HTML 不要直接批量处理。
自动标签换行对于属性很多的标签比较有用,但很短的内联标签也可能被拆行。这部分之后还可以继续增加更细的配置。
我从这个项目里学到的东西
这个插件看起来只是“整理一下代码”,真正实现以后才发现,它同时涉及编辑器 API、文本范围计算、混合语言解析、第三方 Formatter 的边界、插件冲突和扩展发布。
我没有重新实现所有格式化算法。能交给 js-beautify 和 sql-formatter 的部分就继续使用成熟库,CFML 特有的标签、块范围和混合语法再由自己的逻辑处理。
对我来说,这种拆分比从零写一个完整 Parser 更符合当前项目的实际需求:用户不需要准备 CFML 运行环境,插件安装后就能使用,我也可以根据真实项目中的问题逐步改进规则。
后面我会继续改善 .cfm 混合内容的处理、复杂 CFML 模板的兼容性,以及格式化结果的稳定性。如果你也在维护 ColdFusion 或 Lucee 项目,可以直接安装试用;遇到有代表性的代码结构,也可以在 GitHub 提交 Issue。
项目链接
- GitHub:satt-hri/vscode-plugin-cf-formatter
- Visual Studio Marketplace:hri.cfml-auto-formatter
- Open VSX Registry:hri/cfml-auto-formatter
