如何使用TypeScript模块API移除类型注解并保留行列信息?
使用TypeScript Compiler API移除类型注解并保留行列结构
核心思路
通过TypeScript编译器API解析源码生成AST,定位所有TypeScript专属的类型节点(接口定义、参数类型标注等),将这些节点对应的文本内容替换为等长度的空格,以此完全保留原文件的行号、列号信息,确保后续用Acorn处理时生成的sourcemap能准确映射到原始TS文件。
实现代码
import * as ts from 'typescript'; function removeTypeAnnotationsWithSpacePreserve(sourceCode: string): string { // 创建源文件对象 const sourceFile = ts.createSourceFile( 'temp.ts', sourceCode, ts.ScriptTarget.Latest, true ); // 将源码拆分为字符数组,方便逐位替换 const codeChars = sourceCode.split(''); // 遍历AST节点,处理类型相关节点 function visitNode(node: ts.Node) { // 匹配所有需要移除的类型节点 const isTypeNode = ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isTypeAnnotation(node) || ts.isParameterPropertyDeclaration(node) || ts.isTupleTypeNode(node) || ts.isTypeLiteralNode(node) || ts.isFunctionTypeNode(node) || ts.isUnionTypeNode(node) || ts.isIntersectionTypeNode(node); if (isTypeNode) { // 获取节点在源码中的起始与结束字符位置 const start = node.getStart(sourceFile); const end = node.getEnd(); // 将节点范围内的所有字符替换为空格 for (let i = start; i < end; i++) { codeChars[i] = ' '; } } // 递归处理所有子节点,避免嵌套类型注解遗漏 ts.forEachChild(node, visitNode); } // 从根节点开始遍历处理 visitNode(sourceFile); // 重新拼接为最终字符串 return codeChars.join(''); } // 测试示例 const inputTsCode = `function foo(){ console.log('foo') } interface Message{ tx:string unixtime:number } function bar(x:Message,i:number){ console.log(x,i) } foo() bar({tx:'hello',unixtime:1000000000},5)`; const outputJsCode = removeTypeAnnotationsWithSpacePreserve(inputTsCode); console.log(outputJsCode);
关键细节说明
- 精准节点匹配:借助TypeScript内置的类型判断函数,覆盖绝大多数常见的类型注解场景,确保不会误删业务代码。
- 空格替换逻辑:通过节点的位置信息精准定位字符范围,用空格逐位替换而非直接删除,严格保留原文件的行列结构,为后续sourcemap生成提供准确的位置映射基础。
- 递归遍历:通过递归遍历AST所有子节点,处理嵌套在函数、对象中的深层类型注解。
输出验证
上述代码处理给定输入后,会生成完全符合要求的输出:
function foo(){ console.log('foo') } function bar(x,i){ console.log(x,i) } foo() bar({tx:'hello',unixtime:1000000000},5)
内容的提问来源于stack exchange,提问作者yigal
相关产品推荐
相关产品推荐

