告别 TS “裸奔”:精准扫描函数 TS 类型定义情况

85 阅读3分钟

引子

还在为 TypeScript 项目中函数缺少类型定义(或者一堆any)而烦恼吗?还在手动检查每个函数的参数和返回值类型吗?是时候解放双手,拥抱自动化,很多同学问我学习AST,代码扫描有什么使用场景,本文将带你从 0 到 1 带你开发一个扫描函数 TS 类型定义缺失情况的代码扫描小工具!

工具介绍

这款工具名为 function-type-check,它可以扫描指定目录下的所有 TS 文件,分析每个函数的参数和返回值是否带有类型定义,并将缺少类型定义(写any也将被视为缺失类型)的函数信息输出到控制台(也可以是文件,或者上报到分析平台)。

工具原理

function-type-check 使用 TypeScript 编译器 API 解析 TS 代码,并遍历抽象语法树 (AST) 查找函数声明。对于每个函数声明,它会检查其参数和返回值是否带有类型注解。如果缺少类型注解,则记录该函数的信息,包括函数名、所在文件和行数。

代码实现

import * as ts from 'typescript';
import * as fs from 'fs';
import * as path from 'path';

// 定义一个接口来存储函数信息
interface FunctionInfo {
  name: string;
  file: string;
  line: number;
  hasParameterTypes: boolean;
  hasReturnType: boolean;
}

// 1.创建一个函数来扫描指定目录下的所有TS文件
function scanDirectory(dir: string): string[] {
  const files: string[] = [];
  const entries = fs.readdirSync(dir, { withFileTypes: true });
  for (const entry of entries) {
    const fullPath = path.join(dir, entry.name);
    if (entry.isDirectory()) {
      files.push(...scanDirectory(fullPath));
    } else if (entry.isFile() && entry.name.endsWith('.ts')) {
      files.push(fullPath);
    }
  }
  return files;
}

// 2.创建一个函数来分析单个TS文件并返回函数信息
function analyzeFile(file: string): FunctionInfo[] {
  const functions: FunctionInfo[] = [];
  const sourceCode = fs.readFileSync(file, 'utf8');
  const sourceFile = ts.createSourceFile(
    file,
    sourceCode,
    ts.ScriptTarget.Latest,
    true
  );

  // 遍历 AST 树,查找函数声明
  const traverse = (node: ts.Node) => {
    if (ts.isFunctionDeclaration(node)) {
      const name = node.name ? node.name.getText() : 'anonymous';
      const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
      const hasValidParameterTypes = node.parameters.every(p => { 
          // 检查参数类型是否为 any 或未定义 
          return p.type && p.type.kind !== ts.SyntaxKind.AnyKeyword; 
      });
      // 检查参数类型是否为 any 或未定义
      const hasValidReturnType = node.type && node.type.kind !== ts.SyntaxKind.AnyKeyword; 
      if (!hasValidParameterTypes || !hasValidReturnType) { 
          functions.push({ name, file, line, hasValidParameterTypes, hasValidReturnType }); 
      }
    }
    ts.forEachChild(node, traverse);
  };

  traverse(sourceFile);
  return functions;
}

// 扫描入口
function scan(dir: string) {
  const files = scanDirectory(dir);
  let total = 0;
  let missingTypes = 0;
  const results: FunctionInfo[] = [];

  for (const file of files) {
    const functions = analyzeFile(file);
    total += functions.length;
    for (const func of functions) {
      if (!func.hasParameterTypes || !func.hasReturnType) {
        missingTypes++;
        results.push(func);
      }
    }
  }

  console.table(results);
}

// 运行程序
scan('./src'); // 替换为你要扫描的目录

上面的脚本工具将TS文件解析为AST对象后,通过遍历树结构,找到所有的函数定义节点,对函数节点中的入参与返回进行类型检查,分析完所有TS文件后,统一输出扫描结果

使用方法

  1. 将以上代码保存为 function-type-check.ts 文件。
  2. 使用 npm install typescript @types/node 安装依赖。
  3. 修改 main('./src') 中的目录为你想要扫描的目录。
  4. 运行 npx ts-node function-type-check.ts

总结

function-type-check 可以帮助你快速识别 TS 项目中缺少类型定义的函数及对应信息,帮助开发者进行代码规范治理, 计算函数TS类型覆盖率,any覆盖率等,当然想要实现对类型定义扫描的工具和方式有很多,本文只是以此为例,帮助大家了解AST具体可以做那些事情,更重要的是自己要动手实际去写实现工具,从而保证更好的学习效率。

延伸

如果我们想将上面的扫描能力放在一个独立的 npm 包中,那么可以通过npm init 初始化一个项目,然后将 scan 函数通过 Promise 改造一下,这样其他应用就可以以依赖的形式安装 npm 包后,再通过API方法的模式进行调用了,具体的实行我们放在 index.js 文件中(举例)

index.js:

// 扫描入口
export function scan(dir) {
  return new Promise((resolve, reject)=>{
      try {
          const files = scanDirectory(dir);
          let total = 0;
          let missingTypes = 0;
          const results: FunctionInfo[] = [];

          for (const file of files) {
            const functions = analyzeFile(file);
            total += functions.length;
            for (const func of functions) {
              if (!func.hasParameterTypes || !func.hasReturnType) {
                missingTypes++;
                results.push(func);
              }
            }
          }

          console.log(`总共扫描了 ${total} 个函数。`);
          console.log(`其中 ${missingTypes} 个函数缺少类型定义:`);
          console.table(results);
          resolve(results);
      } catch(e) {
          reject(e);
      }
  })
}

然后在 package.json 中声明包名为 function-type-check,导出的文件为 index.js,执行 npm publish 就大功告成了