json-go-ts VS Code插件开发

背景

项目中需要一份json配置文件,这份配置文件的某些字段是依据另外的一个typescrip文件export出来的字段,为了降低编码过程中造成的字段名出错的机率。

因此,考虑开发一个插件用于:

  • json文件中自动补全function name
  • jaon文件到typescript文件的自定义跳转
  • json文件中错误波浪线智能提示(当typescript文件没有export该字段时显示波浪线)

插件效果:

IDE

实现

思路:定义两个插件变量(可根据实际情况自己在插件设置里面配置)

  • 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列表

关键代码实现:

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文件中的对应位置

关键代码实现:

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(/"index\.|"/g, '');
    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}`; // TODO: 踩坑记录 必须 ///
          if (fs.existsSync(filePath)) {
            const _targetUri = vscode.Uri.parse(tmpPath); // TODO: 踩坑记录 使用parse
            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;
             // 当按住ctrl时,编辑器默认会将连在一块的字符都显示出来下划线 这时候如果想指定哪些字符需要显示下划线需要配置以下的originSelectionRange
            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];
            // return new vscode.Location(vscode.Uri.parse(tmpPath), new vscode.Position(_fnItem.loc.start.line, _fnItem.loc.start.column));
          }
        }
      }
    }
  }
}

智能诊断&提示

这个功能的主要对象是json文件,因此我们需要获取到json文件中所有的字段,包括每个字段所处的位置信息(为了后面显示波浪线),这时候我们可能会想到说,将json文件转化为AST,思路是正确的,但是前面说的@babel/traverse只能用来转化js或ts文件,并不支持json文件的转化

幸好别人已经有写过类似的轮子了,json-to-ast出场,json-to-ast可以将json文件转化为AST,感谢开源。

另外:在VS Code中智能诊断使用createDiagnosticCollection这个API来实现的,切记。

关键代码实现:

// 智能诊断 波浪线提示
const collection = vscode.languages.createDiagnosticCollection('testFnName');
if (vscode.window.activeTextEditor) {
  collection.clear();
  updateDiagnostics(vscode.window.activeTextEditor.document, collection);
}
// 使用onDidChangeTextDocument监听编辑文档事件
context.subscriptions.push(vscode.workspace.onDidChangeTextDocument(editor => {
  if (editor) {
    collection.clear();
    updateDiagnostics(editor.document, collection);
  }
}));
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);
}

一些踩坑

  1. 发布插件时执行vsce publish时报错ERROR Failed request: (401),生成的personal access token权限弄错了,应该选Full access

  1. 发布插件时报错:ERROR Make sure to edit the README.md file before you package or publish your extension -- 修改一下工程里面的README.md文件(原来的文件删除&重写)
  2. 使用地址创建publisher账号一直不成功 -- 网络被限制了(科学上网真香)
  3. 插件在本地开发环境下可以运行,发布到线上没有响应(也没有报错)

    经过排查,项目中使用了一些npm库,但是发布插件时使用了tsc来编译,这种情况下node_modules里面一些npm库的代码是没有被编译进去的。需要引入webpack来进行打包&编译

参考

11 声望
1 粉丝
0 条评论
推荐阅读
vue2子组件中定义的data为什么必须是一个function
如果是上面这种写法,不出意外的话,在vs code data处将会出现红线报错 data property in component must be a function,提示我们data必须是一个function。

痞痞鰕阅读 877

快速构建页面结构的 3D Visualization
可以通过 控制台 --> 右边的三个小点 --> More Tools --> Layers 打开。即可以看到页面的一个 3D 层级关系,像是这样:

chokcoco6阅读 1.6k

前端性能优化到底该怎么做(上)— 开门见山
前端性能优化 又是个听起来很高大上的词,确实是的,因为它需要 高在性能,大在范围,所幸很多大佬都已经输出了很多高质量的内容供大家参考,作者最近也在学习和了解这方面的内容,对如下文中的一些理解若有不当...

熊的猫10阅读 2.1k

封面图
表格集算表高性能原理——怎样实现纯前端百万行数据秒级响应
集算表 (Table Sheet)是一个具备高性能渲染、数据绑定功能、公式计算能力的数据表格,通过全新构建的关系型数据管理器结合结构化公式,在高性能表格的基础上提供排序、筛选、样式、行列冻结、自动更新、单元格...

葡萄城技术团队4阅读 15.2k

三年前端的2022,如果创造比卷更有趣,那为何创造不能成为主旋律?
up主2019年毕业,找了一份前端工作,一直干到现在。2019年末出现疫情,三年的时间,也差不多与我的职业生涯完全重合了。刚过去的2022年,我也没有避开阳的命运(这病毒是真的强),就12月这一个月时间,几乎全公...

Gomi8阅读 1.3k

封面图
UI 提供的【progress-step】要🙉怎么🙊实现!!!
这天突然收到了 UI 修改设计稿的消息通知:"xxx 已修改 xxx 项目并 @ 了你,请及时查看变更内容",一条、两条、三条 ......,修改消息铺天盖地而来,然后就什么都看不到了(因为我选择开启消息免打扰)!,但没多...

熊的猫8阅读 1.1k

封面图
常见 JavaScript 设计模式 — 原来这么简单
设计模式总共有 23 种,但在前端领域其实没必要全部都去学习,毕竟大部分的设计模式是在 JavaScript 中占的比重并不是那么大,本文会列举出一些 JavaScript 常见的、容易被忽视的设计模式,不过还是有必要先简单...

熊的猫14阅读 1.6k

封面图
11 声望
1 粉丝
宣传栏