我正在尝试使用打字稿编译器 API 编写打字稿编译器转换。然而,在创建新的 Identifier 节点时,即使节点被发送到最终的 .js 文件,它们似乎缺少符号绑定信息,因此最终输出是不正确的。
假设我有以下程序:
A.ts
export class A {
static myMethod() {
return 'value';
}
}
Run Code Online (Sandbox Code Playgroud)
索引.ts
import { A } from './A';
export function main() {
const value1 = 'replaceMe';
const value2 = A.myMethod();
const equals = value1 == value2;
}
Run Code Online (Sandbox Code Playgroud)
假设我尝试使用以下转换器编译上述程序:
function transformer(program: ts.Program): ts.TransformerFactory<ts.SourceFile> {
return (context: ts.TransformationContext) => (file: ts.SourceFile) => transformFile(program, context, file);
}
function transformFile(program: ts.Program, context: ts.TransformationContext, file: ts.SourceFile): ts.SourceFile {
const transformedFile = ts.visitEachChild(file, child => visit(child, context), context);
console.log(ts.createPrinter().printFile(transformedFile));
return transformedFile;
}
function visit(node: ts.Node, context: ts.TransformationContext): ts.Node {
if (ts.isStringLiteral(node) && node.text == 'replaceMe') {
return ts.createCall(
ts.createPropertyAccess(
ts.createIdentifier('A'),
'myMethod'),
[],
[]);
}
return ts.visitEachChild(node, child => visit(child, context), context);
}
Run Code Online (Sandbox Code Playgroud)
中间 AST 在漂亮打印时实际上看起来是正确的:
import { A } from './A';
export function main() {
const value1 = A.myMethod();
const value2 = A.myMethod();
const equals = value1 == value2;
}
Run Code Online (Sandbox Code Playgroud)
但输出 javascript 不正确:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var A_1 = require("./A");
function main() {
var value1 = A.myMethod();
var value2 = A_1.A.myMethod();
var equals = value1 == value2;
}
exports.main = main;
Run Code Online (Sandbox Code Playgroud)
我了解这可能是因为通过使用 来创建新标识符ts.createIdentitier('A'),此新标识符不会绑定到与A同一文件中的其他标识符相同的符号。
有没有办法使用公共编译器 API 将新标识符绑定到现有符号?
Typescript 编译分阶段进行(解析、绑定、类型检查、发出,这里有更多细节)。您可以使用来自前一阶段的信息,但通常无法更改它。您可以在发出阶段进行的转换旨在允许您将 AST 从 Typescript 带到 Javascript,而不是重构代码。
实现目标的一种方法是创建程序,应用转换,然后使用修改后的代码创建一个新程序,尽可能多地重用原始程序(SourceFile在未发生更改的地方重用相同的程序)
function transformFile(program: ts.Program, file: ts.SourceFile): ts.SourceFile {
let empty = ()=> {};
// Dummy transformation context
let context: ts.TransformationContext = {
startLexicalEnvironment: empty,
suspendLexicalEnvironment: empty,
resumeLexicalEnvironment: empty,
endLexicalEnvironment: ()=> [],
getCompilerOptions: ()=> program.getCompilerOptions(),
hoistFunctionDeclaration: empty,
hoistVariableDeclaration: empty,
readEmitHelpers: ()=>undefined,
requestEmitHelper: empty,
enableEmitNotification: empty,
enableSubstitution: empty,
isEmitNotificationEnabled: ()=> false,
isSubstitutionEnabled: ()=> false,
onEmitNode: empty,
onSubstituteNode: (hint, node)=>node,
};
const transformedFile = ts.visitEachChild(file, child => visit(child, context), context);
return transformedFile;
}
function visit(node: ts.Node, context: ts.TransformationContext): ts.Node {
if (ts.isStringLiteral(node) && node.text == 'replaceMe') {
return ts.createCall(
ts.createPropertyAccess(
ts.createIdentifier('A'),
'myMethod'),
[],
[]);
}
return ts.visitEachChild(node, child => visit(child, context), context);
}
let host = ts.createCompilerHost({});
let program = ts.createProgram(["toTrans.ts"], {}, host)
let transformed = program.getSourceFiles()
.map(f=> ({ original: f, transformed: transformFile(program, f) }))
.reduce<{ [name: string] : {original: ts.SourceFile, transformed: ts.SourceFile }}>((r, f)=> { r[f.original.fileName] = f; return r; }, {});
let originalGetSourceFile = host.getSourceFile;
let printer = ts.createPrinter();
// Rig the host to return the new verisons of transformed files.
host.getSourceFile = function(fileName, languageVersion, onError, shouldCreateNewSourceFile){
let file = transformed[fileName];
if(file){
if(file.original != file.transformed){
// Since we need to return a SourceFile it is tempting to return the transformed source file and not parse it again
// The compiler doe not support Synthesized nodes in the AST except during emit, and it will check node positions
// (which for Synthesized are -1) and fail. So we need to reparse
return ts.createSourceFile(fileName, printer.printFile(file.transformed), languageVersion);
} else {
// For unchanged files it should be safe to reuse the source file
return file.original;
}
}
return originalGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
}
// Recreate the program, we pass in the original to
program = ts.createProgram(["toTrans.ts"], {}, host, program);
var result = program.emit();
Run Code Online (Sandbox Code Playgroud)
另一种方法是使用语言服务并通过语言服务应用这些更改,但老实说,我对编译器的那部分没有经验,而且它似乎比这种方法更复杂。
| 归档时间: |
|
| 查看次数: |
714 次 |
| 最近记录: |