Когда код это данные
«Представь, что люди как бы находятся в подземном жилище наподобие пещеры, где во всю её длину тянется широкий просвет. С малых лет у них на ногах и на шее оковы, так что людям не двинуться с места, и видят они только то, что у них прямо перед глазами, ибо повернуть голову они не могут из-за этих оков.»Время от времени мне пишут с просьбой помочь в написании кода, который меняет код (далее кодмод, от слов код и модификация — изменение) и сегодня я расскажу об этом нехитром процессе в новом формате, вдохновлённом диалогами Платона, он будет содержать вопросы обратившегося ко мне человека по поводу линтера нового поколения, и мои развёрнутые ответы.© Платон «Государство», книга 7: Миф О Пещере
Забегая вперед скажу, что результатом общения стал loader ESTrace, который при запуске может показать что-то вроде:
Но об этом позже, а сейчас:
▍Следим за функциями
«Люди обращены спиной к свету, исходящему от огня, который горит далеко в вышине, а между огнём и узниками проходит верхняя дорога, ограждённая невысокой стеной вроде той ширмы, за которой фокусники помещают своих помощников, когда поверх ширмы показывают кукол.»© Платон «Государство», книга 7: Миф О Пещере
Я хочу получать информацию о выполнении функций, самый простой вариант console.log ('function name', arguments) мне подойдёт. Если получится добавить поддержку методов будет великолепно.
Узлы содержащие функции в Babel AST могут быть такими:
— FunctionDeclaration — объявление функции
function hello() {
return 'world';
}
— FunctionExpression — анонимная функция
hello(function(word) {
return `hello ${word}`;
});
— ArrowFunctionExpression — анонимная стрелочная функция
hello((word) => {
return `hello ${word}`;
});
— ClassMethod — метод класса
class Hello {
hello(word) {
return `hello ${word}`;
}
}
Для их поиска мы можем использовать Function, он объединяет в себе все перечисленные выше варианты.
Будем использовать Включитель и экспортировать функции:
- include, чтобы знать, что искать;
- fix, для изменения кода;
Таким образом, функция поиска:
module.exports.include = () => [
'Function',
];
Создавать узлы будем с помощью @babel/template, после чего добавим результат в начало функции:
const {template} = require('putout');
// самый простой способ создать узел
const buildLog = template(`console.log('NAME', arguments)`);
module.exports.fix = (path) => {
const {body} = path.node.body;
const NAME = path.node.id.name;
// добавляем в начало функции «console.log»
body.unshift(buildLog({
NAME,
}));
};
Соединив предыдущие две части, и улучшив разбор имени функции в соответствии с внутренней структурой, получим:
Такую реализацию
const {template} = require('putout');
// самый простой способ создать узел
const buildLog = template(`console.log('NAME', arguments)`);
// узлы, которые ищем
module.exports.include = () => [
'Function',
];
module.exports.fix = (path) => {
const {body} = path.node.body;
const NAME = getName(path);
// добавляем в начало функции "console.log"
body.unshift(buildLog({
NAME,
}));
};
// разбираем имя для вывода в логах
function getName(path) {
if (path.isClassMethod())
return path.node.key.name;
if (path.isFunctionDeclaration())
return path.node.id.name;
return '';
}
которая отрабатывает так (картинка кликабельная):
▍Вносим неразбериху улучшения
«За этой стеной другие люди несут различную утварь, держа её так, что она видна поверх стены; проносят они и статуи, и всяческие изображения живых существ, сделанные из камня и дерева. При этом, как водится, одни из несущих разговаривают, другие молчат.»© Платон «Государство», книга 7: Миф О Пещере
Отлично!
❒ Еще я понял что мне нужно логировать события входа в функцию и выхода из нее, меняя:function X() { console.log('hello') }
наfunction X() { console.log('enter X') try { console.log('hello') } finally { console.log('exit X') } }
Буду рад помощи.
Для краткости и наглядности будем использовать бросающиеся в глаза сокращения:
const enterLog = buildLogEvent(name, '