0.0.1 • Published 2 years ago

refactor-box v0.0.1

Weekly downloads
-
License
MIT
Repository
-
Last release
2 years ago

typescript plugin demo

demoneovimvscode都能顺利运行,因为本人vscode使用经验较少,可能vscode有些地方描述不对,或者使用上的问题都欢迎探讨。

术语:

lsp: 微软定义的语言服务协议,https://microsoft.github.io/language-server-protocol/specifications/specification-current

tsserver: vscode背后提供智能编辑体验的服务器,https://github.com/microsoft/TypeScript/wiki/Standalone-Server-%28tsserver%29

typescript plugin: tsserver插件,可以用来扩展编辑器服务,比如补全,refactor,https://github.com/microsoft/TypeScript/wiki/Writing-a-Language-Service-Plugin,可以在tsconfig配置,https://www.typescriptlang.org/tsconfig#plugins,然后npm安装到vscode语言服务插件目录,我的是在E:\app\Microsoft VS Code\resources\app\extensions\typescript-language-features\node_modules

refactor: 重构,这里指的不是大范围的重构,指的是比较小的操作。比如将箭头函数改成函数表达式,将某段if代码改成swith。

开发参考资源

  1. 入门认识,https://juejin.cn/post/6942380528456695844
  2. typescript ast查看器
  3. 可以参考typescript-transformer,因为原理基本一样,通过操作ast实现

开发简单文档

lsp基本原理

编辑器前端收到用户的需要语法解析的编辑操作(比如跳转定义,补全) 编辑器前端按照lsp标准发请求给lsp后端(vscodelsp后端是tsserver) lsp后端收到并分析返回结果(比如得到补全列表,得到定义位置) 编辑器前端通过自己的ui系统展示结果,并等待用户其他操作

lsp的意义在于让编辑器插件变得通用。比如要为neovimvscodesublimeatomtypescript,pythonc++插件,我们要写4*3一共12个。 使用lsp编写的插件只需要4+3一共7个。用时间复杂度来说就是: n为编辑器数量,m为语言数量,使用lsp将插件数量从n*m减少到了n+m

为什么是n+m呢? 因为lsp其实是一个前后端分离的架构,需要实现n个编辑器前端插件和m个语言服务插件。

简单例子

function init(modules: { typescript: typeof import("typescript/lib/tsserverlibrary") }) {
  const ts = modules.typescript;

  function create(info: ts.server.PluginCreateInfo) {
    // Set up decorator object
    const proxy: ts.LanguageService = Object.create(null);
    for (let k of Object.keys(info.languageService) as Array<keyof ts.LanguageService>) {
      const x = info.languageService[k]!;
      // @ts-expect-error - JS runtime trickery which is tricky to type tersely
      proxy[k] = (...args: Array<{}>) => x.apply(info.languageService, args);
    }
    return { create };
  }
}

info.LanguageService是服务器暴露给我们的api,当编辑器访问一些功能时会调用,比如获取补全列表,获取可以重构提示,跳转定义等。可以跳转到它的类型定义了解更多。 info.LanguageService定义位置 简单介绍一些api: getApplicableRefactors: 获取重构提示,即是返回一些重构操作的选项,供用户选择。vscode可以按ctrl+shift+R来弹出这个菜单, 或者点击代码行左边的小灯泡,都会请求服务器,服务器会执行这个api。 getEditsForRefactor: 上一步获取到重构操作的名字,当用户确定要执行这个操作时,会请求服务器,然后服务器执行这个api获取重构操作具体要干什么返回编辑器。 getCompletionsAtPosition: api补全菜单的获取需要这个

以上的代码例子意思是说可以通过代理这些api来扩展我们需要的操作。我们可以先获取原始api的结果,然后做修改,或者添加。或者不使用原始api的结果,用我们自己的方法。

简单编写流程

我们代理这些api后,具体要怎么做才能得到结果呢?一般的流程是:

  1. 我们编辑的时候,发现了一个不舒服的地方,比如有时我们写了一个React functional component,但是后来发现我们更想使用class component,所以就慢慢将一些函数写成方法,一些变量改成类属性,外面包一个class。实在是太费功夫了。
  2. 这个就是一个重构(vscode里叫做refactor),可以用typescript plugin实现的。首先我们用上面ast查看工具看看functional componentclass component的节点都是什么东西
  3. functional component其实是一个FunctionDeclarationclass component是一个ClassDeclaration。要做的东西其实就清晰了,我们要将一个FunctionDeclaration转成一个ClassDeclaration(或者相反)。
  4. 代理getApplicableRefactors方法,获取到光标位置的节点(具体可以看代码),然后判断它是不是FunctionDeclaration,是则返回这个重构操作名字,比如MyRefactor
  5. 代理getEditsForRefactor方法,获取到光标位置的节点,判断操作名是不是MyRefactor且节点是FunctionDeclaration,是则根据FunctionDeclaration创建一个ClassDeclaration,具体api可以用ts.factory(ts则是上面代码init传入的变量),然后返回重构操作

操作ast其实可以参考各种transformer,因为原理是一样的。

调试配置(暂时只会打log调试)

vscode

参考https://github.com/microsoft/TypeScript/wiki/Debugging-Language-Service-in-VS-Code 没有试过

neovim

前提是已经lsp已经配置好,以下配置wsl运行通过 1. npm link typescript plugin模块链接到全局(即NODE_PATH) 2. built-in-lspconfig tsserver配置修改:

local bin_name = 'typescript-language-server'
local getPath = function (str)
  return str:match("(.*/)")
end
lspconfig.tsserver.setup {
  init_options = { plugins = {{ name = 'ts-plugin-test', location = getPath(os.getenv('NODE_PATH'))} }},
  cmd = { bin_name, '--stdio', '--tsserver-log-file', os.getenv('HOME') .. '/tsserver.log', '--log-level', '3' }
}

然后就可以在代码里通过这个logger模块来输出日志了。

意义

对比其他插件:

这个是更通用的方案,在所有支持语言服务器的编辑器都可以收益。

重构功能对比babel-transformertypesccript-transformer

  1. 场景不一样。当我们明确知道需要改动什么,并且要修改jsts语法,可以使用transformer。如果是一些小优化,或者说是没有强制使用的规范,比如class componentfunctional component,我们是根据具体情况使用的,再者是我们要第一时间知道我们的refactor具体做了什么,判断refactor合理与否,我们需要使用typescript plugin
  2. typescript plugin作用于编辑过程,只需要某个人修改一次就不需要再做处理了。transformer每次编译都会处理。

一些资源

关于ast 入门demo 是带我入门的一篇blog lsp specifications languager server protocol语言服务协议规范 Write a typescript plugin官方typescript plugin文档,比较简陋 ast查看器 很有用,编写插件全靠它 typescript languager servertsserver包装成一个基于lsp规范的语言服务器。比较好笑的是,tsserver并没有完全按照lsp规范,所以需要一层包装。应该是因为先有tsserver,然后再从tsserver抽离出lsp规范。