#typescript #typescript-compiler-api
#typescript #typescript-compiler-api
Вопрос:
Описание
Привет,
Я пытаюсь создать что-то, что добавляло бы комментарии поверх функции. К сожалению, похоже, что ts.setSyntheticLeadingComments
не позволяет мне заменять существующие комментарии.
Я пытался:
ts.setSyntheticLeadingComments(node, [])
ts.setSyntheticLeadingComments(node, undefined)
node = ts.setSyntheticLeadingComments(node, [])
Но ни один из этих способов не работает. В конечном счете, моей целью было иметь возможность заменить существующие комментарии, которые я бы сгенерировал, новыми.
Есть идея? Спасибо 🙏
Воспроизведение
const transformFactory = (context: ts.TransformationContext) => (
rootNode: ts.SourceFile
): ts.SourceFile => {
const visit = (node: ts.Node) => {
node = ts.visitEachChild(node, visit, context);
ts.setSyntheticLeadingComments(node, []);
return node;
};
return ts.visitNode(rootNode, visit);
};
const sourceFile = ts.createSourceFile(
path,
source,
ts.ScriptTarget.ESNext,
true,
ts.ScriptKind.TS
);
const result = ts.transform(sourceFile, [transformFactory]);
const resultPrinter = ts.createPrinter({ removeComments: false });
console.log(resultPrinter.printFile(result.transformed[0]));
Попробуйте следующий преобразователь и увидите, что комментарии вообще не удаляются
Использование ts.createPrinter(..., { substituteNode(hint, node) { ... } })
также не помогает
Дополнительное примечание
Похоже, ts.getSyntheticLeadingComments()
что это тоже работает не так, как я ожидаю. Он всегда возвращает undefined
, что приводит меня к использованию следующих утилит, хотя я не уверен, что полностью понимаю их назначение (заимствовано из https://github.com/angular/tsickle/blob/6f5835a644f3c628a61e3dcd558bb9c59c73dc2f/src/transformer_util.ts#L257-L266 )
/**
* A replacement for ts.getLeadingCommentRanges that returns the union of synthetic and
* non-synthetic comments on the given node, with their text included. The returned comments must
* not be mutated, as their content might or might not be reflected back into the AST.
*/
export function getAllLeadingComments(node: ts.Node):
ReadonlyArray<Readonly<ts.CommentRangeamp;{text: string}>> {
const allRanges: Array<Readonly<ts.CommentRangeamp;{text: string}>> = [];
const nodeText = node.getFullText();
const cr = ts.getLeadingCommentRanges(nodeText, 0);
if (cr) allRanges.push(...cr.map(c => ({...c, text: nodeText.substring(c.pos, c.end)})));
const synthetic = ts.getSyntheticLeadingComments(node);
if (synthetic) allRanges.push(...synthetic);
return allRanges;
}
Комментарии:
1. По этому поводу есть проблема с GitHub: github.com/Microsoft/TypeScript/issues/30191
2. Теперь, когда я выгляжу лучше, это тот же контент: D
3. Вы пытаетесь добавить jsdocs? Взгляните на эту проблему: github.com/Microsoft/TypeScript/issues/17146 — Кстати, они хранятся в ast, а обычные комментарии — нет.
Ответ №1:
Проблема в том, что вы ожидаете, что *SyntheticLeadingComments
функции будут влиять на исходные комментарии. Они не будут. Они будут влиять только на комментарии, которые были ранее синтезированы (т. Е. добавлены вами в код).
Фактические комментарии не хранятся как узлы в AST. Вы можете получить фактические исходные комментарии, используя getLeadingCommentRanges
и getTrailingCommentRanges
.
Узел имеет start
и end
позицию, которые не содержат никаких комментариев. Существует также полный запуск узла, который является позицией, включая любые ведущие комментарии. При выводе узла именно так typescript узнает, как скопировать комментарии в выходные данные.
Если мы используем setTextRange
для установки диапазона узла, чтобы исключить эти существующие комментарии, в результате мы эффективно удаляем их из выходных данных и можем добавлять новые комментарии с помощью setSyntheticLeadingComments
:
import * as ts from 'typescript'
const transformFactory = (context: ts.TransformationContext) => (
rootNode: ts.SourceFile
): ts.SourceFile => {
const visit = (node: ts.Node) => {
node = ts.visitEachChild(node, visit, context);
if(ts.isFunctionDeclaration(node)) {
let sourceFileText = node.getSourceFile().text;
const existingComments = ts.getLeadingCommentRanges(sourceFileText, node.pos);
if (existingComments) {
// Log existing comments just for fun
for (const comment of existingComments) {
console.log(sourceFileText.substring(comment.pos, comment.end))
}
// Comment also attaches to the first child, we must remove it recursively.
let removeComments = (c: ts.Node) => {
if (c.getFullStart() === node.getFullStart()) {
ts.setTextRange(c, { pos: c.getStart(), end: c.getEnd() });
}
c = ts.visitEachChild(c, removeComments, context);
return c;
}
ts.visitEachChild(node, removeComments, context);
ts.setTextRange(node, { pos: node.getStart(), end: node.getEnd() })
ts.setSyntheticLeadingComments(node, [{
pos: -1,
end: -1,
hasTrailingNewLine: false,
text: "Improved comment",
kind: ts.SyntaxKind.SingleLineCommentTrivia
}]);
}
}
return node;
};
return ts.visitNode(rootNode, visit);
};
const sourceFile = ts.createSourceFile(
"path.ts",
`
// Original comment
function test () {
}
`,
ts.ScriptTarget.ESNext,
true,
ts.ScriptKind.TS
);
const result = ts.transform(sourceFile, [transformFactory]);
const resultPrinter = ts.createPrinter({ removeComments: false });
console.log("!");
console.log(resultPrinter.printFile(result.transformed[0]));
Комментарии:
1. Спасибо за вашу помощь! Я был немного неточен, из-за чего ваш код не работает для того, что я пытаюсь сделать. Я пытаюсь добавлять комментарии не сверху
FunctionDeclaration
, а поверхVariableStatement
. И по некоторым причинам,ts.setTextRange
похоже, не дает того же эффекта, что дляFunctionDeclaration
. Это не удаляет существующий комментарий, хотяnode.start
иnode.end
комментарий не включает. Извините за неправильную начальную тему, я думал, что буду использовать функции, чтобы сделать ее более простой для понимания. У вас есть какие-либо идеи, почему?2. @FlavianDesverne так и должно быть .. Я не могу проверить прямо сейчас, но сделаю это немного позже и свяжусь с вами. Не могли бы вы предоставить какой-нибудь пример кода, который вы хотите изменить? Что находится в файле, каким будет ожидаемый результат? Это помогло бы
3. Вот результат, который я получаю для следующего кода: « импорт { ObjectType } из «nexus»; // Улучшенный комментарий // Экспорт исходного комментария const Toto = ObjectType({ имя: «Запрос», определение(t) { t.string(«привет»); } }); «
4. Обновление: блоки кода не работают в комментариях. Вот вставленный файл с вводом, ожидаемым результатом, фактическим результатом и моей реализацией (которая просто заменяет ts.isFunctionDeclaration на ts.isVariableStatement): pastebin.com/axhmk8eP . Огромное спасибо за вашу помощь 🙏
5. @FlavianDesverne по-видимому, комментарий также привязывается к первому дочернему элементу, нам нужно удалить его рекурсивно, я вскоре обновлю код