扩展TypeScript语法:VS Code tmlanguage添加include后自定义语法失效问题
我之前做过好几次VS Code自定义TypeScript语法的项目,刚好遇到过和你一模一样的问题,给你拆解一下原因和解决办法:
问题的核心原因
当你在tmlanguage.json里直接添加"include": "source.ts"时,本质上是把完整的TypeScript语法规则全部引入到你的自定义语法中。而VS Code的语法解析引擎是按照规则的定义顺序和优先级来匹配的——默认情况下,外部引入的语法规则(比如source.ts)的优先级会高于你自定义的规则,导致你的自定义语法模式还没来得及匹配,就被TS的规则先匹配覆盖了,自然就看不到自定义语法的效果。
正确的实践方案
1. 用embed替代顶层include,精准控制TS语法的嵌入范围
不要在顶层直接引入整个TS语法,而是把TS语法嵌入到你自定义语法的特定区域里,同时把你的自定义规则放在最前面(保证优先匹配)。比如你想在自定义的代码块里支持TS,其余部分用自己的语法,可以这么写:
{ "scopeName": "source.ts-custom", "patterns": [ // 先定义你的自定义语法规则,优先级最高 { "name": "custom.syntax.my-special-keyword", "match": "\\b(myCustomKeyword)\\b", "captures": { "1": { "name": "keyword.control.my-custom" } } }, // 然后在指定区域嵌入TS语法 { "name": "embedded.typescript", "begin": "\\{ts\\}", "end": "\\{/ts\\}", "patterns": [ { "include": "source.ts" } ] } ] }
这样你的自定义关键字会被优先识别,而{ts}和{/ts}包裹的区域才会用TS语法解析。
2. 用语法注入而非完全继承
如果你的自定义语法是TS的超集(比如给TS加一些自定义语法糖),更推荐用语法注入的方式:
- 在
tmlanguage.json里设置"scopeName": "source.ts.my-custom" - 在扩展的
package.json的grammars配置里,添加"injectTo": ["source.ts"]
"contributes": { "grammars": [ { "language": "ts-custom", "scopeName": "source.ts.my-custom", "path": "./syntaxes/ts-custom.tmlanguage.json", "injectTo": ["source.ts"] } ] }
这种方式会把你的自定义规则注入到原生TS语法中,不会覆盖原生规则,而是和它共存。
3. 调整规则顺序,确保自定义规则先匹配
如果你一定要用顶层include,那必须把你的自定义规则放在include语句的前面,因为语法解析是从上到下匹配的,先匹配到的规则会生效:
{ "patterns": [ // 自定义规则在前 { "match": "your-pattern", "name": "your.scope" }, // TS规则在后 { "include": "source.ts" } ] }
不过这种方式不推荐,因为TS的规则非常全面,还是会有很多场景下你的自定义规则被覆盖。
踩过的坑分享
我最开始也是直接加了"include": "source.ts",结果自定义的装饰器语法完全不生效,查了VS Code语法解析的文档才明白是优先级的问题。后来用embed精准控制嵌入范围,再调整规则顺序,问题就解决了。另外要注意测试的时候,一定要重启VS Code或者重新加载扩展,有时候缓存会导致修改不生效。
内容的提问来源于stack exchange,提问作者marco burrometo

