我必须以特定方式修改大约 1000 个 typescript 文件:我需要用 CallExpression
替换所有 StringLiteral
和 JsxText
标记> 用于我的应用程序国际化目的的翻译功能。
我已经使用我们的 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);
我有哪些选择?
- 我可以坚持使用上述方法,但它会导致大量无用的编辑、损坏的 github 历史记录和巨大的拉取请求。通过这种方法,我绝对应该在转换后使用 Prettier 并且可能我应该将其作为开发人员依赖项安装在我们的 CI 中,这样我们将来就不会遇到此类问题。
- 我仍然可以使用 AST 来检测我的标记,但我可以在没有
ts.Printer
和ts.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/