TypeScript编译器API在转换过程中丢失格式

Tim*_*sky 6 compiler-construction abstract-syntax-tree automated-refactoring typescript

我必须以特定的方式修改大约1000个打字稿文件:为了我的应用程序的国际化,我需要将全部StringLiteralJsxText令牌替换CallExpression为翻译功能。

我已经使用Roslyn在我们的C#代码库中完成了这样的任务,因此现在我正在尝试使用Typescript编译器API完成类似的任务。它与Roslyn API十分相似,但是它们之间有一个讨厌的区别。在Roslyn中,您有Trivia令牌的概念:这种令牌不会发出任何有趣的东西,但对于可读性而言是必不可少的。这些是空格,制表符,注释等。在Roslyn语法树中,您具有源文件中的所有琐事。当您以某种方式更改C#语法树并从该语法树发出源代码时,您将拥有所有相同的格式,注释,空格以及所有这些东西。

不幸的是,打字稿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);
Run Code Online (Sandbox Code Playgroud)

我有什么选择?

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

所以我该怎么做?我还有其他选择吗?

Ira*_*ter 1

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

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

我们的DMS 软件重组工具包可以做到这一点。

必须为其定义目标语言语法;我们已经对包括 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};
Run Code Online (Sandbox Code Playgroud)

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

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

现在,您有 1000 个文件需要更改。这已经足够大了,因此定义 TypeScript 和应用 DMS 可能是值得的。(如果 DMS​​ 的 TypeScript 前端已经准备好,那么就完成上述操作)。有时并非如此;YMMV 取决于你真正想做的事情。DMS 最适合在大型代码库上使用,如果您需要进行复杂的转换,DMS 确实会大放异彩。