如何实现TypeScript转换器:自定义?语法实现值为undefined时省略键
扩展TypeScript转换器实现
?对象键省略语法 要实现你需要的b?: args.b语法(值为undefined时省略对应对象键),需要分三步完成:定义自定义AST节点、适配解析逻辑、实现转译规则。以下是具体操作方案:
一、定义自定义AST节点
TypeScript原生AST中没有对应这种可选省略属性的节点,我们可以扩展现有节点类型:
import * as ts from 'typescript'; // 自定义节点类型标识,避免与原生语法冲突 export const OptionalPropertyAssignmentKind = ts.SyntaxKind.FirstUserDefined + 1; // 定义节点接口,继承自原生PropertyAssignment export interface OptionalPropertyAssignment extends ts.PropertyAssignment { kind: typeof OptionalPropertyAssignmentKind; } // 辅助函数创建自定义节点 export function createOptionalPropertyAssignment( name: ts.PropertyName, initializer: ts.Expression ): OptionalPropertyAssignment { return { ...ts.createPropertyAssignment(name, initializer), kind: OptionalPropertyAssignmentKind, } as OptionalPropertyAssignment; }
二、适配解析逻辑
原生TypeScript解析器无法识别?:语法,有两种适配方式:
方式1:源码预处理(快速实现)
无需修改TS源码,先对输入源码做字符串替换,把b?: args.b转换为TS能解析的特殊命名属性(比如b__optional__: args.b),后续在转换器中识别该标记:
const sourceCode = `var args = { a: undefined, b: undefined, c: 'foo'} const x = { a: args.a, b?: args.b, c?: args.c }`; // 替换自定义语法为原生可解析格式 const processedCode = sourceCode.replace(/(\w+)\?:/g, '$1__optional__:');
方式2:修改TypeScript源码(规范实现)
如果要让TS原生解析器直接识别?:语法,需要修改TS核心代码:
- 在
src/compiler/types.ts中新增SyntaxKind.OptionalPropertyAssignment枚举值; - 修改
src/compiler/parser.ts中的解析逻辑,添加对PropertyName '?:' Initializer语法的识别,创建自定义OptionalPropertyAssignment节点; - 重新编译TypeScript源码。
三、实现转译规则
通过TypeScript Compiler API的转换器,将自定义节点(或标记属性)转译为目标代码:
import * as ts from 'typescript'; function transformer(context: ts.TransformationContext) { return (sourceFile: ts.SourceFile) => { function visit(node: ts.Node): ts.Node | ts.Node[] { // 处理包含自定义属性的变量声明 if (ts.isVariableDeclaration(node) && ts.isObjectLiteralExpression(node.initializer)) { const optionalKeys: string[] = []; const regularProperties: ts.PropertyAssignment[] = []; // 遍历对象属性,分离普通属性和可选省略属性 node.initializer.properties.forEach(prop => { if (ts.isPropertyAssignment(prop)) { const propName = ts.isIdentifier(prop.name) ? prop.name.text : ''; // 识别预处理后的标记属性 if (propName.endsWith('__optional__')) { const realKey = propName.slice(0, -11); optionalKeys.push(realKey); regularProperties.push(ts.createPropertyAssignment(realKey, prop.initializer)); } else { regularProperties.push(prop); } } }); if (optionalKeys.length > 0) { // 生成存储可选键的数组变量 const omitArray = ts.createArrayLiteral( optionalKeys.map(key => ts.createStringLiteral(key)) ); const omitVarStmt = ts.createVariableStatement( undefined, [ts.createVariableDeclaration('omitKeyIfUndefined', undefined, omitArray)] ); // 生成reduce过滤逻辑 const reduceFn = ts.createFunctionExpression( undefined, undefined, [ts.createParameter(undefined, undefined, 'r'), ts.createParameter(undefined, undefined, 'key')], undefined, undefined, ts.createBlock([ ts.createIf( ts.createLogicalAnd( ts.createStrictEquality( ts.createElementAccess(ts.createIdentifier('args'), ts.createIdentifier('key')), ts.createIdentifier('undefined') ), ts.createBinary(ts.createIdentifier('key'), ts.SyntaxKind.InKeyword, ts.createIdentifier('omitKeyIfUndefined')) ), ts.createStatement(ts.createDelete(ts.createElementAccess(ts.createIdentifier('r'), ts.createIdentifier('key')))) ), ts.createReturn(ts.createIdentifier('r')) ]) ); const reduceCall = ts.createCall( ts.createPropertyAccess(ts.createIdentifier('omitKeyIfUndefined'), 'reduce'), undefined, [reduceFn, ts.createIdentifier('args')] ); // 替换原变量的初始化器 const newVarDec = ts.updateVariableDeclaration(node, node.name, node.type, reduceCall); const newVarStmt = ts.createVariableStatement(node.modifiers, [newVarDec]); // 返回新的语句序列:辅助变量声明 + 处理后的变量声明 return [omitVarStmt, newVarStmt]; } } // 递归处理子节点 return ts.visitEachChild(node, visit, context); } return ts.visitNode(sourceFile, visit) as ts.SourceFile; }; } // 执行转译 const result = ts.transpileModule(processedCode, { compilerOptions: { module: ts.ModuleKind.CommonJS }, transformers: { before: [transformer] } }); console.log(result.outputText);
转译效果
运行上述代码后,输入的源码会被转译为你期望的格式,最终x的结果会自动省略值为undefined的b键。
内容的提问来源于stack exchange,提问作者tkowt
相关产品推荐
相关产品推荐

