Когда код это данные

tf3wmseehxw3r2srqx3jypde49e.png
«Представь, что люди как бы находятся в подземном жилище наподобие пещеры, где во всю её длину тянется широкий просвет. С малых лет у них на ногах и на шее оковы, так что людям не двинуться с места, и видят они только то, что у них прямо перед глазами, ибо повернуть голову они не могут из-за этих оков.»

© Платон «Государство», книга 7: Миф О Пещере

Время от времени мне пишут с просьбой помочь в написании кода, который меняет код (далее кодмод, от слов код и модификация — изменение) и сегодня я расскажу об этом нехитром процессе в новом формате, вдохновлённом диалогами Платона, он будет содержать вопросы обратившегося ко мне человека по поводу линтера нового поколения, и мои развёрнутые ответы.

Забегая вперед скажу, что результатом общения стал loader ESTrace, который при запуске может показать что-то вроде:

ibzkrl_29jelaqtue8qc4lbvahu.png

Но об этом позже, а сейчас:

▍Следим за функциями


«Люди обращены спиной к свету, исходящему от огня, который горит далеко в вышине, а между огнём и узниками проходит верхняя дорога, ограждённая невысокой стеной вроде той ширмы, за которой фокусники помещают своих помощников, когда поверх ширмы показывают кукол.»

© Платон «Государство», книга 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 '';
}


которая отрабатывает так (картинка кликабельная):
b2ac25dad5090e90d4024581365a994a.png

▍Вносим неразбериху улучшения


«За этой стеной другие люди несут различную утварь, держа её так, что она видна поверх стены; проносят они и статуи, и всяческие изображения живых существ, сделанные из камня и дерева. При этом, как водится, одни из несущих разговаривают, другие молчат.»

© Платон «Государство», книга 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, '
    
            

© Habrahabr.ru