背景
项目中需要一份json配置文件,这份配置文件的某些字段是依据另外的一个typescrip文件export
出来的字段,为了降低编码过程中造成的字段名出错的机率。
因此,考虑开发一个插件用于:
- json文件中自动补全function name
- jaon文件到typescript文件的自定义跳转
- json文件中错误波浪线智能提示(当typescript文件没有export该字段时显示波浪线)
插件效果:
实现
思路:定义两个插件变量(可根据实际情况自己在插件设置里面配置)
- fileName:用于识别文件名关键字 – 默认为
index
- filePath:以json文件为参照,index typescript文件的相对路径,用于获取ts文件 – 默认为
./src/index
- 关键实现:使用
@babel/parser
、@babel/traverse
将ts文件转化AST,这样才能获取到ts文件中export出来的function name
项目源码
自动补全
思路:
- 获取ts文件中export出来的function列表
- 监听VS Code中光标处输入的字符,当输入
index.
时,弹出自动补全面板,此时自动补全面板中的内容就是ts文件中export出来的所有function列表
关键代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| export function provideCompletionItems(document, position, token, context) { const _filePath = getFilePath(document, filePath); const _fnData = getFnFromTsFile(`${_filePath}.ts`); const linePrefix = document.lineAt(position).text.substr(0, position.character); if (!linePrefix.endsWith(`"${fileName}.`)) { return undefined; } let myitem = (text: string) => { let item = new vscode.CompletionItem(text, vscode.CompletionItemKind.Function); item.range = new vscode.Range(position, position); return item; }; return _fnData.map(_fnItem => myitem(_fnItem.fnName)); }
|
跳转到定义
思路:
- 获取ts文件中export出来的function列表,包括每个function对应的loc(即function在编辑器中所处的位置信息),这就是前面我们为什么要将ts文件转化为AST的重要原因,后面实现代码跳转需要用到这些位置信息
- 获取光标处所在的function并跳转到对应ts文件中的对应位置
关键代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| export function provideDefinition(document: vscode.TextDocument, position: vscode.Position, token) { const _filePath = getFilePath(document, filePath); const _fnData = getFnFromTsFile(`${_filePath}.ts`); const _wordRangePosition = document.getWordRangeAtPosition(position); const word = document.getText(_wordRangePosition); const _reg = new RegExp(`"${fileName}\..*"`); if (_reg.test(word)) { const _fnName = word.replace(new RegExp(`"${fileName}\.|"`, 'g'), ''); if(_fnData.map(_fnItem => _fnItem.fnName).includes(_fnName)) { const filePath = `${_filePath}.ts`; for(const _fnItem of _fnData) { if(_fnItem.fnName === _fnName) { const tmpPath = `file:///${filePath}`; if (fs.existsSync(filePath)) { const _targetUri = vscode.Uri.parse(tmpPath); const _targetPosition = new vscode.Position(_fnItem.loc.start.line, _fnItem.loc.start.column); const _targetRange = new vscode.Range(_targetPosition, _targetPosition); const _fileNameLen = (fileName as string).length || 0; const _orgSelectionStartPosition = new vscode.Position(_wordRangePosition.start.line, _wordRangePosition.start.character + _fileNameLen + 2); const _orgSelectionEndPosition = new vscode.Position(_wordRangePosition.end.line, _wordRangePosition.end.character - 1); const _originSelectionRange = new vscode.Range(_orgSelectionStartPosition, _orgSelectionEndPosition); const _locationLink: vscode.LocationLink = { originSelectionRange: _originSelectionRange, targetUri: _targetUri, targetRange: _targetRange }; return [_locationLink]; } } } } } }
|
智能诊断&提示
这个功能的主要对象是json文件,因此我们需要获取到json文件中所有的字段,包括每个字段所处的位置信息(为了后面显示波浪线),这时候我们可能会想到说,将json文件转化为AST,思路是正确的,但是前面说的@babel/traverse
只能用来转化js或ts文件,并不支持json文件的转化
幸好别人已经有写过类似的轮子了,json-to-ast出场,json-to-ast
可以将json文件转化为AST,感谢开源。
另外:在VS Code中智能诊断使用createDiagnosticCollection
这个API来实现的,切记。
关键代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const collection = vscode.languages.createDiagnosticCollection('testFnName'); if (vscode.window.activeTextEditor) { collection.clear(); updateDiagnostics(vscode.window.activeTextEditor.document, collection); }
context.subscriptions.push(vscode.workspace.onDidChangeTextDocument(editor => { if (editor) { collection.clear(); updateDiagnostics(editor.document, collection); } }));
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| export function updateDiagnostics(document: vscode.TextDocument, collection: vscode.DiagnosticCollection): void { const _text = document.getText(); const _jsonAst = jsonToAst(_text) as jsonToAst.ObjectNode; const _errorText: JsonTextInfo[] = []; const _filePath = getFilePath(document, filePath); const _fnData = getFnFromTsFile(`${_filePath}.ts`); const recursiveJsonAst = (astArr: any[]) => { astArr.forEach((jsonAstItem) => { const _value = jsonAstItem?.value || jsonAstItem?.children; if(_value.children) { recursiveJsonAst(_value.children); } else if(Array.isArray(_value)) { recursiveJsonAst(_value); } else { const _textValue = _value.value; const _reg = new RegExp(`${fileName}\..*`, 'g'); if(_reg.test(_textValue)) { const _fnVal = _textValue.replace(new RegExp(`${fileName}\.`, 'g'), ''); if(!_fnData.map((_fnItem => _fnItem.fnName)).includes(_fnVal)) { _errorText.push({ value: _fnVal, loc: _value.loc }); } } } }); }; recursiveJsonAst(_jsonAst.children); const _diagCollection = []; _errorText.forEach(_errTextItem => { const _start = _errTextItem.loc.start; const _end = _errTextItem.loc.end; const _fileNameLen = (fileName as string).length || 0; const _startPosition = new vscode.Position(_start.line - 1, _start.column + _fileNameLen + 1); const _endPosition = new vscode.Position(_end.line - 1, _end.column - 2); _diagCollection.push({ message: `Function ${_errTextItem.value} does not exist`, range: new vscode.Range(_startPosition, _endPosition), severity: vscode.DiagnosticSeverity.Error, }); }); collection.set(document.uri, _diagCollection); }
|
一些踩坑
- 发布插件时执行
vsce publish
时报错ERROR Failed request: (401)
,生成的personal access token权限弄错了,应该选Full access
-
发布插件时报错:ERROR Make sure to edit the README.md file before you package or publish your extension
– 修改一下工程里面的README.md
文件(原来的文件删除&重写)
-
使用地址创建publisher账号一直不成功 – 网络被限制了(科学上网真香)
-
插件在本地开发环境下可以运行,发布到线上没有响应(也没有报错)
经过排查,项目中使用了一些npm库,但是发布插件时使用了tsc
来编译,这种情况下node_modules
里面一些npm库的代码是没有被编译进去的。需要引入webpack
来进行打包&编译
参考