SymbolGo
增强前端项目代码跳转能力的 VS Code 扩展。
不自定义快捷键、不自定义 UI,补齐前端样式、组件和脚本跳转。使用方式就是原生的 Ctrl/Cmd + 鼠标左键 或 F12。
支持的跳转
从模板到样式
| 场景 |
示例 |
跳转到 |
| Vue / HTML / JSX 的 class |
class="user-card__title" |
CSS / SCSS / Sass / Less 中的选择器定义 |
| 动态 class 表达式 |
:class="['a', { 'is-active': ok }]"、className={clsx('a')} |
光标所指的那一个类名 |
| id |
id="app-root" |
#app-root 定义 |
| CSS Modules |
styles.title、styles['title']、Vue 的 $style.title |
只在对应模块文件中查找 |
嵌套选择器会解析出完整名称,.user-card { &__title { } } 可以被 user-card__title 命中;@media 等 at-rule 内部仍沿用外层父选择器。
组件
| 场景 |
说明 |
<UserCard />、<user-card /> |
跳转到 UserCard.vue / UserCard.tsx / user-card/index.vue 等组件文件 |
默认策略是 auto:全局组件、自动导入组件按文件名查找;Vue 中已 import 的组件按导入路径查找,未安装 Vue 语言扩展时也能跳转。JSX / TSX 中已 import 的组件仍交给内置 TypeScript 服务,设为 always 可由 SymbolGo 一并提供。安装 Vue 语言扩展时,两者可能同时提供结果。
JavaScript / TypeScript 脚本
- 普通 JS / TS(含 JSX、TSX、mjs、cjs 等)以及 Vue 的
<script>、<script lang="js">、<script setup> 块:函数、变量、参数、可静态解析的对象成员跳转到声明位置;
- 本地
import 别名、命名空间、require 和 re-export:沿模块关系定位源声明;模块路径也可直接跳转;
- 复用
symbolgo.alias、工作区 tsconfig / jsconfig 路径别名;依赖文件已在编辑器打开时优先使用未保存内容;
- CSS Modules 的 import 绑定按语法节点解析,不再受前面的副作用 import、注释或字符串影响。
Vue <script setup> 的模板也支持变量跳转:{{ title }}、@click="onSubmit"、:disabled="loading"、v-model="form.title" 会定位到脚本定义;v-for / v-slot 的局部名按标签作用域解析,避免与脚本中的同名变量混淆。直接从 Vue 导入的 ref / computed 支持模板解包,reactive(useTable()) 的可静态推断成员可以继续定位到 composable 的源声明。
脚本采用随扩展打包的 TypeScript 语义引擎,不要求用户项目安装 TypeScript。脚本符号按需建立语义工程,不计入「查看索引状态」中的样式符号数量。
样式文件内部
| 语言 |
支持的符号 |
| CSS |
class、id、CSS Variables(--x)、@keyframes、@function --x() |
| SCSS / Sass |
$变量、@mixin、@function、%placeholder、class、id、@keyframes |
| Less |
@变量、mixin(.m() / #ns())、detached ruleset、class、id、@keyframes |
| 全部 |
@import / @use / @forward 的路径(支持 _partial 与 index 约定) |
@include mixins.button-base 这类命名空间调用会正确识别为 mixin,不会被当成类选择器;animation: fade-in 的值会识别为 keyframes 名。
悬停提示
命中时在原生 Hover 中追加一行 SymbolGo · <符号> 与定义位置列表,可通过 symbolgo.hover.enabled 关闭。除此之外不注入任何装饰或界面元素。
配置
| 配置项 |
默认值 |
说明 |
symbolgo.enabled |
true |
总开关 |
symbolgo.include |
常见前端文件 glob |
参与索引的文件 |
symbolgo.exclude |
node_modules、dist 等 |
排除索引的文件 |
symbolgo.maxFiles |
20000 |
索引文件数上限 |
symbolgo.maxFileSizeKB |
1024 |
单文件体积上限 |
symbolgo.style.enabled |
true |
class / id 跳转开关 |
symbolgo.style.scope |
smart |
smart / related / workspace,见下 |
symbolgo.component.mode |
auto |
auto / always / off |
symbolgo.hover.enabled |
true |
悬停来源标识 |
symbolgo.alias |
{} |
额外路径别名,如 { "@": "src" } |
symbolgo.trace |
false |
输出详细日志 |
style.scope 的三种策略:
related:只在当前文件与其直接关联的样式(SFC 的 <style> 块、<style src>、@import / @use、脚本里 import 的样式、HTML 的 <link>)中查找;
smart(默认):先按 related 查找,找不到时回退到整个工作区;
workspace:始终返回工作区中所有同名定义。
多个结果会全部返回,由 VS Code 用原生的 Peek Definition 列表展示;排序优先级为「当前文件 → 关联样式 → 同目录 → 其余」。
路径别名
按以下顺序合并:symbolgo.alias > tsconfig.json / jsconfig.json 的 compilerOptions.paths > 存在 src 目录时的 @/ 兜底。同时支持 webpack 风格的 ~ 前缀与 node_modules 内的样式文件。
命令
| 命令 |
说明 |
SymbolGo: 重建索引 |
重新全量扫描 |
SymbolGo: 查看索引状态 |
在输出面板打印文件数、符号数与分类统计 |
SymbolGo: 打开日志输出 |
打开输出面板 |
架构
src/
extension.ts 扩展入口,只做装配与生命周期管理
core/
types.ts 符号种类与纯数据结构(不依赖 vscode)
lineMap.ts 偏移 <-> 位置映射
languages.ts 语言 / 扩展名 / 组件名归一化
analyzer.ts 唯一解析入口:源码 -> 符号 + import 关联
symbolIndex.ts 内存倒排索引(按 种类+名字 O(1) 查询)
indexManager.ts 全量扫描分片、文件监听、编辑防抖
documentCache.ts 当前文档解析结果与关联样式缓存
pathResolver.ts 模块路径 / 别名 / partial / index 解析
config.ts logger.ts
parsers/ 纯文本解析,全部不依赖 vscode,可单测
styleParser.ts CSS / SCSS / Less 单遍扫描解析器
selector.ts 逗号拆分、& 展开、class / id 提取
sassIndented.ts 缩进语法 Sass -> 花括号语法(带位置回映射)
vueParser.ts SFC 块拆分
markupContext.ts 模板中的光标语义识别
styleContext.ts 样式代码中的光标语义识别
scriptImports.ts import / require 绑定解析
scriptSource.ts Vue 脚本区域映射与模块语法节点提取
vueTemplate.ts setup 模板表达式、局部作用域与原文位置映射
vueSemanticTypes.ts 定义查询专用的 Vue 响应式类型关系
resolvers/
symbolResolver.ts 光标位置 -> 定义位置列表(Definition 与 Hover 共用)
scriptResolver.ts JS / TS / Vue script 的语义定义解析
providers/
definitionProvider.ts 返回 LocationLink
hoverProvider.ts
分层原则:parsers 不依赖 vscode,因此可以在普通 Node 环境下单测;core 负责索引与 IO;resolvers 是唯一的语义解析出口;providers 只做 VS Code API 适配。后续增加 Find References、Rename、统一搜索时,复用 SymbolIndex 与 SymbolResolver 即可,无需改动解析层。
性能
- 激活后做一次全量扫描,每 40 个文件让出一次事件循环,不阻塞 Extension Host;扫描期间状态栏显示进度;
- 样式索引靠
FileSystemWatcher(200ms 防抖)与文档编辑事件(350ms 防抖)增量更新,跳转时查询内存索引;
- 未保存的修改也会被索引,编辑后无需保存即可跳转;
- 文件路径解析结果带存在性缓存,文件系统变化时失效。
- 脚本按需读取当前文件的本地依赖图,每次最多 64 个文件、总计 8 MiB,同时遵守单文件体积上限;循环引用去重,不递归扫描
node_modules。语义工程在本次查询完成后释放。
开发
pnpm install
pnpm run build # 打包到 dist/
pnpm run watch # 监听构建
pnpm run typecheck # 类型检查
pnpm run test # 解析器与跳转解析的 Node 单测
pnpm run test:integration # 在本机 VS Code 中跑真实 Extension Host 集成测试
在 VS Code 中按 F5 可启动扩展开发宿主,fixtures/demo 是覆盖各类跳转场景的示例工作区。
集成测试通过 vscode.executeDefinitionProvider 触发,与用户 Ctrl + 左键 / F12 是同一条链路;隔离宿主禁用内置 JS/TS 跳转服务,确认返回结果来自 SymbolGo。可用 VSCODE_PATH 指定 VS Code 可执行文件位置。
复现业务项目时,可设置 SYMBOLGO_TEST_WORKSPACE 为项目目录,并通过 SYMBOLGO_TEST_CASES 提供 JSON 用例数组(字段见 src/test/integration/index.ts 的 Case)。用例只打开文件、调用定义查询,不编辑业务源码;支持用 expectDeclaration 校验原文声明位置,避免项目行号变化导致误报。
已知限制
- 索引是纯静态解析,运行期动态拼接的类名(例如
`btn-${type}`)无法解析;
- CSS-in-JS(styled-components、emotion 等)暂未支持;
- 组件跳转基于 import 语句与文件名约定,不解析构建工具的自定义组件解析规则;
- 脚本补齐范围是本地静态语义:第三方包的完整类型、Options API 的模板实例、复杂宏或运行期注入仍交给原生 JS/TS 或 Vue 语言扩展;不解析
<script src> 的外部脚本上下文及 Pug 模板;
.vue 与 .sass 在未安装对应语言扩展时会落到 plaintext,扩展已通过文件名 glob 兜底注册,但语法高亮等仍需对应扩展。
License
MIT