You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 08:30:45