Как я могу добавить дополнительные операторы в функцию с помощью ts.transform

Я использую API-интерфейс компилятора Typescript (ts.transform, ts.updateFunctionDeclaration) для вставки дополнительных операторов в начало функций в существующих исходных файлах. Это отлично работает, за исключением того, что когда я печатаю преобразованный код (с помощью ts.Printer), первый комментарий в исходном источнике излучается над моими введенными операторами. Как я могу этого предотвратить?

Я пробовал различные способы построения новых узлов, возвращаемых трансформатором, но ничего не меняет результат. Вот мой полный код, я выполняю его с помощью Node.

    import * as ts from 'typescript';

    const sourceLines = [
        "// The list of properties exposed by this script to the user",
        "var properties = {'speed': 20};",
        "",
        "function tick(deltaTime)",
        "{",
        "  var currentDeltaTime = deltaTime;",
        "}"
    ];

    var sourceCode = sourceLines.join('\n');

    const sourceFile = ts.createSourceFile("originalSource.js", sourceCode, ts.ScriptTarget.Latest, true);

    const additionalSource: ts.SourceFile = ts.createSourceFile(
        'ownerCheck.js', "var variableToAdd = 5;", ts.ScriptTarget.ES5, false, ts.ScriptKind.JS
    );

    const transformer = <T extends ts.Node>(context: ts.TransformationContext) =>
        (rootNode: T) => {
            function visit(node: ts.Node): ts.Node {

                if (ts.isFunctionDeclaration(node)) {
                    var functionDeclaration = <ts.FunctionDeclaration>node;
                    var functionName: ts.Identifier = functionDeclaration.name;

                    var updatedFunction = ts.updateFunctionDeclaration(
                        functionDeclaration,
                        /*decorators*/     undefined,
                        /*modifiers*/      undefined,
                        /*asteriskToken*/  functionDeclaration.asteriskToken,
                        /*functionName*/   functionName,
                        /*typeParameters*/ undefined,
                        /*parameters*/     functionDeclaration.parameters,
                        /*returnType*/     undefined,
                        /*body*/           ts.createBlock(ts.createNodeArray([].concat(additionalSource.statements, functionDeclaration.body.statements), false))
                    );

                    return updatedFunction;

                }
                return ts.visitEachChild(node, visit, context);
            }
            return ts.visitNode(rootNode, visit);
        };

    const result = ts.transform(
        sourceFile, [transformer]
    );

    const transformedNodes = result.transformed[0];

    const printer: ts.Printer = ts.createPrinter({
        newLine: ts.NewLineKind.LineFeed,
        removeComments: false
    });

    console.log(printer.printNode(ts.EmitHint.SourceFile, transformedNodes, sourceFile));

Выход:

    // The list of properties exposed by this script to the user
    var properties = { 'speed': 20 };
    function tick(deltaTime) {
        // The list of properties exposed by this script to the user
        var variableToAdd = 5;
        var currentDeltaTime = deltaTime;
    }

Я ожидал бы, что вывод будет включать дополнительные операторы без повторного комментария перед ними.

Обновление: добавление этой функции:

stripRanges(additionalSource);

function stripRanges(node: ts.Node) {
    node.pos = -1;
    node.end = -1;

    ts.forEachChild(node, stripRanges);
}

удаление диапазонов узлов в additionalSource узлах технически работает, но удаляет все переводы строки в выводе после этих узлов, что приводит к следующему выводу:

// The list of properties exposed by this script to the user
var properties = { 'speed': 20 };
function tick(deltaTime) { var variableToAdd = 5; var currentDeltaTime = deltaTime; }

Обновление 2: изменение вызова ts.createBlock () на true, чтобы для multiLine было установлено значение true, исправлено следующее:

ts.createBlock(
    ts.createNodeArray(
        [].concat(
            additionalSource.statements,
            functionDeclaration.body.statements),
    false), /*has trailing comma*/ 
true) /*multiLine*/ 

person Robert Anderberg    schedule 06.08.2019    source источник


Ответы (1)


Это потому, что, когда принтер отправляется на печать узлов additionalSource.statements, он использует текст узла sourceFile для получения комментариев на основе положения узлов в additionalSource.

Вы можете увидеть это воспроизведенным, запустив следующий код:

console.log(ts.createPrinter().printNode(
    ts.EmitHint.Unspecified,
    additionalSource.statements[0],
    sourceFile // it's getting the comments from this sourceFile.text
));

Выходы:

// The list of properties exposed by this script to the user
var variableToAdd = 5;

Решение

Обходной путь - удалить позиции из узлов в additionalSource перед использованием в sourceFile:

stripRanges(additionalSource);

function stripRanges(node: ts.Node) {
    node.pos = -1;
    node.end = -1;

    ts.forEachChild(node, stripRanges);
}

Я использую -1 для pos и end, потому что это то, что делает компилятор, когда узел создается с помощью фабричной функции. Например:

const binaryExpr = ts.createBinary(1, "+", 2);
binaryExpr.pos; // -1
binaryExpr.end; // -1

Боковое примечание: способ, которым компилятор TypeScript обрабатывает комментарии, на мой взгляд, не идеален ... особенно для преобразования и печати (я предпочитаю, как Babel работает с комментариями, но есть улучшения, которые тоже можно было бы сделать ...).

person David Sherret    schedule 06.08.2019
comment
Спасибо, это работает, но, как упоминалось выше в редактировании обновления, в печатном выводе отсутствуют переводы строки, следующие за узлами, для которых был вызван метод stripRanges (). Есть идеи, как это исправить? - person Robert Anderberg; 06.08.2019
comment
@RobertAnderberg в ts.createBlock в коде вопроса есть многострочный аргумент, который является ложным. Попробуйте установить для него значение true. - person David Sherret; 06.08.2019