为依赖Angular.js的上古项目给VSCode编写$scope定义跳转扩展插件

简介: 虽然Angular.js停止更新已经一年了,但依赖它的上古时代的项目并不少。由于都是使用js开发,很难为其提供很好的维护,所以直到今天开发维护也并不愉快。可以说没有开发插件的支持,再成熟老练的框架都发挥困难。

去年一月份,Angular.js就停止更新了,但由于年代久远,许多老项目还在使用。就像使用jQuery网站的数量到今天仍然比Vue+React+Angular的总和还多!有理由相信十余年的维护期,让使用Angular.js的网站数量不会逊于Angular,为什么Angular升级这么难呢?其实从Angular.js升级到Angular近似于让Angular.js换成Vue,变化实在太大,几乎没办法简单升级过去的。

但这么老的框架,开发生态是不是就很好呢?想多了!搜搜VSCode里关于Angular.js的扩展项吧:

image.png

几乎全都是一些代码片段、自动补全、模板这样的东西,连个变量跳转都没有啊。

那么接下来就讲讲如何为Angular.js做一个非常基本的关于$scope作用域变量的跳转。

初始化

VSCode插件开发最基本的是要装几个东西,Yeoman、generator-code、vsce,都是npm里面的都需要全局安装,新建插件项目可以直接yo code,里面语言我这里就选择Typescript了。

然后就是具体实现了,做这种代码跳转插件呢主要是要实现vscode.DefinitionProvider接口,首先新建一个类文件,然后起一个类名叫AngularJsDefineProvider好了:

import * as vscode from "vscode"

class AngularJsDefineProvider implements vscode.DefinitionProvider {
   
   
    provideDefinition(document: vscode.TextDocument, position: vscode.Position): vscode.ProviderResult<vscode.Definition | vscode.DefinitionLink[]> {
   
   
        ...
    }
}

这个Provider其实也就一个provideDefinition()需要实现,但不是定义了VSCode就会加载这个Provider的,还需要去注册它,可以回到新建项目时它自带的main.ts,在里面activate()里面导入上面的AngularJsDefineProvider类,然后向vscode注册:

vscode.languages.registerDefinitionProvider({
   
   
    scheme: 'file', language: 'javascript'
}, new AngularJsDefineProvider())

这就算是完成初始化了,一般情况下注册东西都要去package.json里面再写点什么,但这里是语言定义区的注册,所以没有关系,下面看看具体如何实现:

参数解析

先说一说provideDefinition()这个方法本身,它有2个参数,还有个返回值:

  • document顾名思义,是文档对象,是触发跳转定义请求的文档,这个请求当然只可能由一篇文档发出,不可能好几篇文档里面都要求要跳往各自的地方,所以它不是数组。
  • position则是发出这个定义跳转的具体位置,这个位置他不会是一个点,比喻说你光标停在一个词中间,它会自动拿到整个词的position。
  • 然后就是这个返回值vscode.ProviderResult<vscode.Definition | vscode.DefinitionLink[]>,这个ProviderResult就是个说明性的类型,其实内容就是它的泛型本身,也就是vscode.Definition和vscode.DefinitionLink[],而Definition就是Location | Location[],直接由new vscode.Location()实现的,为什么都有数组呢,这个很好理解,因为除了找定义之外,找引用也是由这个方法发起的,引用它就可能不止一个地方有了,所以要数组来包含多个位置。

然后还需要自己先在类里定义3个变量方便使用:

private readonly document: vscode.TextDocument
private readonly range: vscode.Range
private readonly search: string

为什么定义这些变量呢,首先这里不会只有一个方法,而只要处理这个定义跳转就免不了用document这个参数,所以要先在类里定义个document变量当做共享参数,以免以后这个对象传来传去的麻烦。

然后是范围和搜索文本,这个范围是其实就是选中的文本,当然也是很重要的,后面会用它扩大选取搜索更多的内容,而search就是具体的搜索内容,range是可以根据情况扩大的,而search就相当于是根据range选出来的文本。

下面就可以进行具体的操作了,不过还需要一些前置准备,例如验证条件和搜索方法的实现,来为最终的跳转查找铺平道路。

前置准备

写当然是从provideDefinition()写起,先验证一下到底有没有position和range:

if (position.character <= 0) {
   
   
    return
}
const range = document.getWordRangeAtPosition(position)
if (!range) {
   
   
    return
}

如果没有直接return即可。然后给前面定义的3个变量赋值:

this.document = document
this.search = document.getText(range)
this.range = range

然后需要看看,它是不是scope,那无论是定义还是引用前面必然都要有个$scope.`,至于其他地方变量也叫这个`$scope.xx不需要我们找,那也无所谓了毕竟概率很小。那么还有一个问题,怎么分辨选的那块代码是要找定义还是找引用,一般来说,这里是需要用抽象语法树(AST)来解析的,但我这个其实需求比较简单,直接根据range搜后面的文本就好了,定义后面肯定有个=嘛,至于重新赋值也不用太考虑,所以先要写个根据range搜上下文的方法:

/**
 * 在搜索字符串的前后剪裁字符串
 */
public strAt(index): string {
   
   
    if (this.range.start.character + index < 0) {
   
   
        return ''
    }
    if (index < 0) {
   
   
        const s = new vscode.Range(new vscode.Position(this.range.start.line, this.range.start.character + index),
            this.range.start)
        return this.document.getText(this.document.validateRange(s))
    }
    return this.document.getText(new vscode.Range(
        this.range.end,
        new vscode.Position(this.range.end.line, this.range.end.character + index)))
}

我简单设计了一个strAt(),根据document.getText和range对象剪裁取值,比喻说aa.bb.c,光标在bb那range也就会在bb,只要调用这个strAt(2)就会获取向后获取到.c这段字符串,而只要strAt(-3)就能向前获取到aa.这段字符串。

那么下面就可以使用这个strAt()做验证、定义、引用的判断了:

if (this.strAt(-7) !== "&scope.") {
   
   
    return
}
if (this.strAt(1) === "=") {
   
   
    return this.findScopeReference()
}
return this.findScopeDefine()

可以看到有了这个方法真的超简单,那下面就可以专心实现findScopeReference()和findScopeDefine()了:

定义与引用查找

到了这一步,其实都不需要用strAt()来进行上下文查找了,而是直接对整个document进行全文检索,正常来说其实要检索整个项目所有可查找的document,但是这里图方便,而且因为Angular.js模块化其实挺差,写多了都写到一个文件里去了,所以其实也没必要检索整个项目了,如果想检索一些特定文件可以用vscode.workspace.openTextDocument(uri)来打开具体的文档来进行检索,这里就不演示了,下面看看具体怎么做:

首先是查找定义:

public findScopeDefine(): vscode.Definition {
   
   
    const thisDocIndex = this.document.getText().indexOf(`scope.${
     
     this.search} = `)
    if (thisDocIndex === -1) {
   
   
        return
    }
    return new vscode.Location(vscode.Uri.file(this.document.fileName), new vscode.Range(
        this.document.positionAt(thisDocIndex + 6),
        this.document.positionAt(thisDocIndex + 6 + this.search.length)))
}

定义一般只有一个,直接indexOf需要找的东西就行了,然后用vscode.Location包一下就可以完成跳转了。

public findScopeReference(): vscode.Definition[] {
   
   
    const thisDocArr = [...this.document.getText().matchAll(new RegExp("scope." + this.search, "g"))]
    if (!thisDocArr.length) {
   
   
        return
    }
    return thisDocArr.map((item) => {
   
   
        return new vscode.Location(vscode.Uri.file(this.document.fileName), new vscode.Range(
            this.document.positionAt(item.index + 6),
            this.document.positionAt(item.index + 6 + this.search.length)))
    })
}

找引用就需用正则匹配多项了,找到后解构到数组里,再用map转成vscode.Location对象。

下面用GIF演示一下:

code-jump.gif

本文依照作者在2021年的一些开发经验,于2023年7月18日同时写作并发布在lyrieek的稀土掘金社区与阿里云开发者社区。

参考

目录
相关文章
|
Web App开发 JavaScript 前端开发
如何在JavaScript中确定异步操作之间的依赖关系?
如何在JavaScript中确定异步操作之间的依赖关系?
662 156
|
Linux Go iOS开发
怎么禁用 vscode 中点击 go 包名时自动打开浏览器跳转到 pkg.go.dev
本文介绍了如何在 VSCode 中禁用点击 Go 包名时自动打开浏览器跳转到 pkg.go.dev 的功能。通过将 gopls 的 `ui.navigation.importShortcut` 设置为 &quot;Definition&quot;,可以实现仅跳转到定义处而不打开链接。具体操作步骤包括:打开设置、搜索 gopls、编辑 settings.json 文件并保存更改,最后重启 VSCode 使设置生效。
1034 9
怎么禁用 vscode 中点击 go 包名时自动打开浏览器跳转到 pkg.go.dev
|
机器学习/深度学习 JavaScript Cloud Native
Node.js作为一种快速、可扩展的服务器端运行时环境
Node.js作为一种快速、可扩展的服务器端运行时环境
394 8
|
JavaScript 前端开发
JavaScript基础知识-数组的定义方式
本文介绍了JavaScript中数组的多种定义方式。
237 1
JavaScript基础知识-数组的定义方式
|
JavaScript 前端开发
JavaScript基础知识-构造函数(也称为"类")定义
本文介绍了JavaScript中构造函数(也称为“类”)的定义和使用方法。
318 1
JavaScript基础知识-构造函数(也称为"类")定义
|
开发框架 JavaScript 前端开发
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势。通过明确的类型定义,TypeScript 能够在编码阶段发现潜在错误,提高代码质量;支持组件的清晰定义与复用,增强代码的可维护性;与 React、Vue 等框架结合,提供更佳的开发体验;适用于大型项目,优化代码结构和性能。随着 Web 技术的发展,TypeScript 的应用前景广阔,将继续引领 Web 开发的新趋势。
660 2
|
人工智能 监控 JavaScript
模拟依赖关系和 AI 是Vue.js测试的下一个前沿领域
模拟依赖关系和 AI 是Vue.js测试的下一个前沿领域
371 1
|
JavaScript 前端开发
js函数调用与定义
js函数调用与定义
|
存储 JavaScript 前端开发
JavaScript 函数定义
JavaScript 函数定义
238 3
|
前端开发 JavaScript 应用服务中间件
Nginx 支持 JavaScript:前所未有的扩展
Nginx 是全球领先的高性能 Web 服务器,以其高效的反向代理和负载均衡功能著称。近期,Nginx 正式支持 JavaScript(通过 NJS 模块),基于 V8 引擎,允许在配置中嵌入 JS 代码,极大提升了灵活性和扩展性。开发者可以使用 JavaScript 实现动态请求处理、自定义认证、复杂响应处理、中间件编写及流量控制等功能,显著降低开发和维护难度,同时保持高性能。NJS 模块的引入为 Nginx 带来了前所未有的扩展能力,适应快速变化的业务需求。
616 0