为依赖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插件开发最基本的是要装几个东西,Yeomangenerator-codevsce,都是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.Definitionvscode.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()写起,先验证一下到底有没有positionrange

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.getTextrange对象剪裁取值,比喻说aa.bb.c,光标在bbrange也就会在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的稀土掘金社区与阿里云开发者社区。

参考

目录
相关文章
|
1月前
|
存储 JavaScript 前端开发
Vue.js项目中全面解析定义全局变量的常用方法与技巧
Vue.js项目中全面解析定义全局变量的常用方法与技巧
39 0
|
2月前
|
JavaScript 前端开发
js函数调用与定义
js函数调用与定义
|
2月前
|
JavaScript 前端开发
JavaScript基础知识-数组的定义方式
本文介绍了JavaScript中数组的多种定义方式。
27 1
JavaScript基础知识-数组的定义方式
|
2月前
|
JavaScript 前端开发
JavaScript基础知识-构造函数(也称为"类")定义
本文介绍了JavaScript中构造函数(也称为“类”)的定义和使用方法。
34 1
JavaScript基础知识-构造函数(也称为"类")定义
|
2月前
|
存储 JavaScript 前端开发
JavaScript 函数定义
JavaScript 函数定义
27 3
|
1月前
|
JavaScript 前端开发 安全
JavaScript实现跳转的方法
JavaScript实现跳转的方法
15 0
|
2月前
|
C++
HTML+JavaScript构建一个将C/C++定义的ANSI字符串转换为MASM32定义的DWUniCode字符串的工具
HTML+JavaScript构建一个将C/C++定义的ANSI字符串转换为MASM32定义的DWUniCode字符串的工具
|
2月前
|
JavaScript 前端开发
用JavaScript编程定义二维数组并初始化,然后输出元素值
用JavaScript编程定义二维数组并初始化,然后输出元素值
|
2月前
|
JavaScript 前端开发
JavaScript基础知识-三种定义函数的定义方式
关于JavaScript中三种定义函数方式的基础知识介绍。
28 0
JavaScript基础知识-三种定义函数的定义方式
|
2月前
|
JavaScript
Vue3基础(21)___在axios.js中使用路由跳转
本文介绍了在Vue 3中如何在axios.js中使用路由跳转,通过直接引入路由实例并使用`router.push`实现页面跳转。
111 0