TypeScript Compiler API 在转换过程中丢失格式

标签 typescript compiler-construction abstract-syntax-tree automated-refactoring

我必须以特定方式修改大约 1000 个 typescript 文件:我需要用 CallExpression 替换所有 StringLiteralJsxText 标记> 用于我的应用程序国际化目的的翻译功能。

我已经使用我们的 C# 代码库和 Roslyn 完成了这样的任务,所以现在我正在尝试使用 typescript 编译器 API 完成类似的任务。它与 Roslyn API 非常相似,但它们有一个明显的区别。在 Roslyn 中,您有一个 Trivia 标记的概念:它不会发出任何有趣的东西,但对于可读性目的来说是必不可少的。这些是空格、制表符、注释等。在 Roslyn 语法树中,您拥有源文件中的所有琐事。当您以某种方式更改 C# 语法树并从该语法树返回源代码时,您将拥有所有相同的格式、注释、空格和所有这些东西。

不幸的是, typescript AST 中没有任何琐事标记,所以当我使用这样的代码时,我的所有格式都会消失。

const result: ts.TransformationResult<ts.SourceFile> = ts.transform(
  sourceFile, [ transformerFactory(visitorFunction) ]
);

const transformedSourceFile: ts.SourceFile = result.transformed[0];

const printer: ts.Printer = ts.createPrinter();

const generated: string = printer.printNode( ts.EmitHint.SourceFile, transformedSourceFile, sourceFile);

我有哪些选择?

  1. 我可以坚持使用上述方法,但它会导致大量无用的编辑、损坏的 github 历史记录和巨大的拉取请求。通过这种方法,我绝对应该在转换后使用 Prettier 并且可能我应该将其作为开发人员依赖项安装在我们的 CI 中,这样我们将来就不会遇到此类问题。
  2. 我仍然可以使用 AST 来检测我的标记,但我可以在没有 ts.Printerts.Transformation 的情况下进行转换。我可以让所有文字在检测阶段进行处理,按它们在文件中的位置降序排列,并使用 substring 或类似的东西替换它们。这是一件非常棘手的事情,我真的不想这样做,但我对第一种选择的缺点不满意。

那我该怎么办呢?我还有其他选择吗?

最佳答案

您可以使用捕获格式和注释的工具,并在完成转换过程后重新生成它们,正如您注意到 Roslyn 所做的那样。但是,Roslyn 和 TypeScript“编译器”特定于它们的目标语言。

总的来说,你想要的是一个“程序转换系统”。这些工具接受语法,自动构建 ASTs 来捕获所有格式数据,允许您使用源代码级模式定义转换,并通过匹配/修补 ASTs 来执行这些转换,并且它们漂亮地打印保留格式数据的修改后的树。

我们的 DMS Software Reengineering Toolkit可以做到这一点。

必须为其定义目标语言语法;我们已经为包括 JavaScript 在内的许多语言完成了工作,但还没有为 TypeScript 完成。但是,您可以通过在其他定义之上构建语言方言。或者,您可以从头开始编写 TypeScript;如果你有一个明确的语法,这并不难,我认为它存在于 TypeScript 中。该定义的一部分告诉解析器如何识别注释以便保存它们; DMS 知道如何保存所有格式和布局数据。

这样,为了解决您的特定任务,您实际上可以使用 DMS 重写规则编写非常简单的转换:

source domain ECMAScript~TypeScript; -- assuming TypeScript is built as a dialect
target domain ECMAScript~TypeScript; -- we're defining rules that map TypeScript to itself
    -- you could write rules map TypeScript to C++ if you insist

rule InternationalizeStringLiteral(s:STRINGLITERAL): primary-> primary
  = "\s"-> "Translate(\s)";

rule InternationalizeJsText(jst:JSTText): primary -> primary
  = " \jst " -> "Translate(\jst)";

ruleset Internationalize = { InternationalizeStringLiteral, InternationalizeJsText};

您可以要求 DMS 解析文件,将规则集自下而上应用到您的树,然后漂亮地打印结果。

这些规则完全是语法感知的,因为它们在 AST 上运行,所以它们不会被注释中的文本或字符串文字,或行边界/空白/格式/交织的注释所愚弄,...

现在,您有 1000 个文件要更改。这已经足够大了,因此定义 TypeScript 和应用 DMS 可能是值得的。 (如果 DMS​​ 的 TypeScript 前端已经准备好,那将是一个灌篮,执行上述操作)。有时不是; YMMV 取决于你真正想做什么。 DMS 最适用于大型代码库,如果您要进行复杂的转换,它会大放异彩。

关于TypeScript Compiler API 在转换过程中丢失格式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53500087/

相关文章:

angular - 未加载运行时编译器

compiler-construction - FPGA 编译器的中间表示

python - ast.fix_missing_locations 何时更改树?

java - JDT : Nesting MethodInvocation

java - AST 解析器获取 Java 类的名称

javascript - 在 Visual Studio Code 中提取 JS/TS 中的局部变量的键盘快捷键

javascript - 我的 GAS 脚本无法创建没有任何错误的事件

java - 如何找到已编译类的目标 Java 版本?

java - JIBX 绑定(bind)目录引发意外的访问绑定(bind)错误

typescript - 在服务器和客户端之间共享一个公共(public)包