如何将标识符与typescript中编译器转换器上的现有符号绑定?

如何将标识符与typescript中编译器转换器上的现有符号绑定?,typescript,Typescript,我正在尝试使用typescript编译器API编写typescript编译器转换。但是,在创建新的标识符节点时,即使节点被发送到final.js文件,它们似乎缺少符号绑定信息,因此最终输出是不正确的 假设我有以下程序: A.T 索引 import { A } from './A'; export function main() { const value1 = 'replaceMe'; const value2 = A.myMethod(); const equals

我正在尝试使用typescript编译器API编写typescript编译器转换。但是,在创建新的标识符节点时,即使节点被发送到final.js文件,它们似乎缺少符号绑定信息,因此最终输出是不正确的

假设我有以下程序:

A.T

索引

import { A } from './A';

export function main() {
    const value1 = 'replaceMe';
    const value2 = A.myMethod();
    const equals = value1 == value2;
}
假设我尝试使用以下转换器编译上述程序:

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);
}
但输出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;
我理解这可能是因为通过使用
ts.createIdentitier('a')
创建一个新标识符,该新标识符不会与同一文件中的另一个
a
标识符绑定到同一个符号


有没有一种方法可以使用公共编译器API将新标识符绑定到现有符号?

类型脚本编译分阶段进行(解析、绑定、类型检查、发出,更详细一点)。您可以使用前一阶段的信息,但通常不能更改它。在emit阶段可以进行的转换旨在允许您将AST从Typescript转换为Javascript,而不是重构代码

实现目标的一种方法是创建程序,应用转换,然后使用修改后的代码创建一个新程序,尽可能多地重用原始程序(在未发生更改的情况下重用相同的
SourceFile

函数转换文件(程序:ts.program,文件:ts.SourceFile):ts.SourceFile{
设为空=()=>{};
//虚拟转换上下文
let上下文:ts.TransformationContext={
惊人的环境:空,
suspendLexicalEnvironment:空,
resumeLexicalEnvironment:空,
endLexicalEnvironment:()=>[],
getCompilerOptions:()=>program.getCompilerOptions(),
声明:空,
WingVariableDeclaration:空,
readEmitHelpers:()=>未定义,
requestEmitHelper:空,
enableEmitNotification:空,
enableSubstitution:空,
ISmitnotificationEnabled:()=>false,
isSubstitutionEnabled:()=>false,
onEmitNode:空,
onSubstituteNode:(提示,节点)=>节点,
};
const transformedFile=ts.visitEachChild(file,child=>visit(child,context),context);
返回转换文件;
}
功能访问(节点:ts.node,上下文:ts.TransformationContext):ts.node{
if(ts.isStringLiteral(node)&&node.text=='replaceMe'){
返回ts.createCall(
ts.createPropertyAccess(
ts.createIdentifier('A'),
“我的方法”),
[],
[]);
}
返回ts.visitEachChild(节点,子节点=>visit(子节点,上下文),上下文);
}
让host=ts.createCompilerHost({});
让program=ts.createProgram([“toTrans.ts”],{},主机)
让transformed=program.getSourceFiles()
.map(f=>({original:f,transformed:transformFile(program,f)}))
.reduce((r,f)=>{r[f.original.fileName]=f;返回r;},{});
让originalGetSourceFile=host.getSourceFile;
让打印机=ts.createPrinter();
//装备主机以返回转换文件的新版本。
host.getSourceFile=函数(文件名、语言版本、OneError、shouldCreateNewSourceFile){
让file=transformed[fileName];
如果(文件){
if(file.original!=file.transformed){
//因为我们需要返回一个源文件,所以很容易返回转换后的源文件而不再解析它
//编译器不支持AST中的合成节点(emit期间除外),它将检查节点位置
//(对于合成的是-1)并且失败。所以我们需要重新分析
返回ts.createSourceFile(文件名,printer.printFile(file.transformed),languageVersion);
}否则{
//对于未更改的文件,重用源文件应该是安全的
返回file.original文件;
}
}
返回originalGetSourceFile(文件名、语言版本、OneError、shouldCreateNewSourceFile);
}
//重新创建程序,我们将原始文件传递给
program=ts.createProgram([“toTrans.ts”],{},主机,程序);
var result=program.emit();

另一种方法是使用语言服务并通过语言服务应用这些更改,但老实说,我对编译器的这一部分没有经验,它似乎比这种方法更复杂。

我试图避免第二次通过,但我想在这种情况下必须这样做。关于编译器API中不同接口的非常有用的信息,非常感谢。你知道我在哪里可以找到关于这些东西的更多信息吗?(即什么是词法环境、提升函数声明等)@PedroPedrosa上述代码避免了对任何未修改的源文件进行第二次传递,至少在解析时是这样。我从阅读github和源代码中了解到,我在编译器内部没有任何好的资源
import { A } from './A';

export function main() {
    const value1 = A.myMethod();
    const value2 = A.myMethod();
    const equals = value1 == value2;
}
"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;
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();