引子
还在为 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文件后,统一输出扫描结果
使用方法
- 将以上代码保存为
function-type-check.ts
文件。 - 使用
npm install typescript @types/node
安装依赖。 - 修改
main('./src')
中的目录为你想要扫描的目录。 - 运行
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 就大功告成了