Статьи

Код рефакторинга в обеденный перерыв: начало работы с Codemods

Делорин в парке ночью. Используйте codemods, чтобы вернуться и изменить свой код.

Поддержание кодовой базы может быть разочаровывающим опытом для любого разработчика, особенно кодовой базы JavaScript. С постоянно меняющимися стандартами, синтаксисом и сторонними изменениями пакетов может быть сложно идти в ногу.

В последние годы ландшафт JavaScript изменился до неузнаваемости. Достижения в основном языке JavaScript привели к тому, что даже самая простая задача объявления переменных была изменена. ES6 представила let и const, функции стрелок и многие другие основные изменения, каждое из которых приносит улучшения и преимущества для разработчиков и их приложений.

Давление на разработчиков для разработки и поддержки кода, который выдержит испытание временем, возрастает. Эта статья покажет вам, как вы можете автоматизировать масштабные задачи рефакторинга с использованием codemods и инструмента JSCodeshift, что позволяет вам легко обновлять ваш код, например, для использования преимуществ новых языковых функций.

Codemod

Codemod — это инструмент, разработанный Facebook, чтобы помочь с рефакторингом крупномасштабных кодовых баз. Это позволяет разработчику реорганизовать большую кодовую базу за небольшое количество времени. В некоторых случаях разработчик может использовать IDE для выполнения рефакторинга имени класса или переменной, однако обычно это ограничивается одним файлом за раз. Следующим инструментом в наборе инструментов рефакторинга для разработчиков является поиск и замена по всему миру. Это может работать во многих случаях с использованием сложных регулярных выражений. Многие сценарии не подходят для этого метода; например, когда есть несколько реализаций, которые необходимо изменить.

Codemod — это инструмент Python, который принимает ряд параметров, включая выражение, которое вы хотите сопоставить, и замену.

codemod -m -d /code/myAwesomeSite/pages --extensions php,html \ '<font *color="?(.*?)"?>(.*?)</font>' \ '<span style="color: \1;">\2</span>' 

В приведенном выше примере мы заменяем использование <font> на span и добавляем стиль цвета. Первые два параметра являются флагами, указывающими совпадение нескольких строк (-m) и каталог, с которого начинается обработка (-d / code / myAwesomeSite / pages). Мы также можем ограничить обрабатываемые расширения (–extensions php, html). Затем мы предоставляем выражение соответствия и замену. Если замена не предусмотрена, нам будет предложен один во время выполнения. Инструмент работает, но он очень похож на существующие инструменты сопоставления регулярных выражений.

JSCodeshift

JSCodeshift — это следующий шаг в наборе инструментов для рефакторинга. Также разработанный Facebook, это инструмент для запуска codemods для нескольких файлов. Как узел Node, JSCodeshift предоставляет чистый и простой в использовании API и использует Recast под капотом. Recast — это инструмент преобразования AST в AST (абстрактное синтаксическое дерево).

Сформулировать

Recast — это Node-модуль, который предоставляет интерфейс для анализа и перепечатки кода JavaScript. Он может анализировать код в строковом формате и генерирует из него объект, который следует структуре AST. Это позволяет нам проверять код на наличие таких шаблонов, как объявления функций.

 var recast = require("recast"); var code = [ "function add(a, b) {", " return a + b", "}" ].join("\n"); var ast = recast.parse(code); console.log(ast); //output { "program": { "type": "Program", "body": [ { "type": "FunctionDeclaration", "id": { "type": "Identifier", "name": "add", "loc": { "start": { "line": 1, "column": 9 }, "end": { "line": 1, "column": 12 }, "lines": {}, "indent": 0 } }, ........... 

Как видно из приведенного выше примера, мы передаем строку кода для функции, которая добавляет два числа. Когда мы анализируем и регистрируем объект, мы видим AST. Мы видим FunctionDeclaration имя функции и т. Д. Так как это всего лишь объект JavaScript, мы можем изменить его по своему усмотрению. Затем мы можем запустить функцию печати для возврата обновленной строки кода.

AST (абстрактное синтаксическое дерево)

Как упоминалось ранее, Recast создает AST из нашей строки кода. AST — это древовидное представление абстрактного синтаксиса исходного кода. Каждый узел дерева представляет собой конструкцию в исходном коде, а узел предоставляет важную информацию о конструкции. ASTExplorer — это браузерный инструмент, который может помочь разобрать и понять дерево вашего кода.

Используя ASTExplorer, мы можем просмотреть AST простого примера кода. Начиная с нашего кода, мы объявим const с именем foo, и он будет равен строке ‘bar’.

 const foo = 'bar'; 

Это приводит к следующему AST:

Скриншот полученного АСТ

Мы можем видеть VariableDeclaration под массивом body, который содержит наш const. Все VariableDeclarations имеют атрибут id, который содержит нашу важную информацию, такую ​​как имя и т. Д. Если мы строим кодовый модуль для переименования всех экземпляров foo мы можем использовать этот атрибут имени и перебирать все экземпляры, чтобы изменить имя.

Установка и использование

Используя инструменты и методы, описанные выше, мы теперь можем в полной мере воспользоваться преимуществами JSCodeshift. Поскольку JSCodeshift является модулем узла, мы можем установить его на уровне проекта или на глобальном уровне.

 npm install -g jscodeshift 

После установки мы можем использовать существующие кодовые модули с JSCodeshift. Мы должны предоставить некоторые параметры, чтобы сообщить JSCodeshift, чего мы хотим достичь. Основной синтаксис — это вызов jscodeshift с jscodeshift пути к файлу или файлам, которые мы хотим преобразовать. Важным параметром является местоположение преобразования (-t). Это может быть либо локальный файл, либо URL-адрес файла codemod. Параметр transform по умолчанию ищет файл transform.js в текущем каталоге.

Другие полезные параметры включают в себя пробный запуск (-d), который будет применять преобразование, но не обновлять файлы, и Verbose (-v), который будет выводить всю информацию о процессе преобразования. Преобразования — это кодовые модули, простые модули JavaScript, которые экспортируют функцию. Эта функция принимает следующие параметры:

  • FILEINFO
  • апи
  • параметры

FileInfo содержит всю информацию о файле, обрабатываемом в данный момент, включая путь и источник. Api — это объект, который обеспечивает доступ к вспомогательным функциям JSCodeshift, таким как findVariableDeclarators и renameTo. Наш последний параметр — это параметры, которые позволяют нам передавать параметры от CLI до codemod. Например, если мы работали на сервере развертывания и хотели добавить версию кода во все файлы, мы могли бы передать ее через CLI jscodeshift -t myTransforms fileA fileB --codeVersion=1.2 . Параметры будут содержать {codeVersion: '1.2'} .

Внутри функции, которую мы выставляем, мы должны возвращать преобразованный код в виде строки. Например, если у нас есть строка кода const foo = 'bar' и мы хотим преобразовать ее, чтобы заменить const foo на const bar, наш код модуля будет выглядеть следующим образом:

 export default function transformer(file, api) { const j = api.jscodeshift; return j(file.source) .find(j.Identifier) .forEach(path => { j(path).replaceWith( j.identifier('bar') ); }) .toSource(); } 

Как видите, мы объединяем несколько функций вместе и в конце вызываем toSource() для генерации строки преобразованного кода.

Есть некоторые правила, которым мы должны следовать при возврате кода. Возвращение строки, отличной от ввода, приведет к успешному преобразованию. Если строка совпадает с вводом, то преобразование будет неудачным, и если ничего не будет возвращено, преобразование не потребуется. Затем JSCodeshift использует эти результаты при обработке статистики преобразований.

Существующие кодовые модули

В большинстве случаев разработчикам не нужно писать собственный код. Многие общие действия по рефакторингу уже превращены в кодовые модули.

Некоторые примеры включают js-codemod no-vars, который преобразует все экземпляры var в let или const , в зависимости от использования переменной. Например, пусть, если переменная переназначается позднее, и const, когда переменная никогда не переназначается.

Шаблонные литералы js-codemod заменят экземпляры конкатенации строк шаблонными литералами, например

 const sayHello = 'Hi my name is ' + name; //after transform const sayHello = `Hi my name is ${name}`; 

Как пишутся codemods

Мы можем взять кодовый код no-vars сверху и разбить код, чтобы увидеть, как работает сложный кодовый модуль.

 const updatedAnything = root.find(j.VariableDeclaration).filter( dec => dec.value.kind === 'var' ).filter(declaration => { return declaration.value.declarations.every(declarator => { return !isTruelyVar(declaration, declarator); }); }).forEach(declaration => { const forLoopWithoutInit = isForLoopDeclarationWithoutInit(declaration); if ( declaration.value.declarations.some(declarator => { return (!declarator.init && !forLoopWithoutInit) || isMutated(declaration, declarator); }) ) { declaration.value.kind = 'let'; } else { declaration.value.kind = 'const'; } }).size() !== 0; return updatedAnything ? root.toSource() : null; 

Приведенный выше код является ядром кодового модуля no-vars. Во-первых, фильтр запускается на всех переменных VariableDeclaration, включая var, let и const. Фильтр возвращает только объявления var. Которые передаются во второй фильтр, это вызывает пользовательскую функцию isTruelyVar . Это используется для определения характера переменной var (например, является ли переменная внутри замыкания или объявлена ​​дважды, или является объявлением функции, которое может быть поднято). Это определит, безопасно ли делать преобразование в var. Для каждого var, который проходит фильтр isTruelyVar , они обрабатываются в цикле forEach.

Внутри цикла выполняется проверка переменной var, если переменная находится внутри цикла, например

 for(var i = 0; i < 10; i++) { doSomething(); } 

Чтобы определить, находится ли var внутри цикла, можно проверить родительский тип.

 const isForLoopDeclarationWithoutInit = declaration => { const parentType = declaration.parentPath.value.type; return parentType === 'ForOfStatement' || parentType === 'ForInStatement'; }; 

Если переменная находится внутри цикла и не мутирована, ее можно изменить на const. Проверка на наличие мутаций может быть выполнена путем фильтрации узлов var AssignmentExpression и UpdateExpression. AssignmentExpression покажет, где и когда была назначена переменная, например

 var foo = 'bar'; 

UpdateExpression покажет, где и когда была обновлена ​​переменная, например

 var foo = 'bar'; foo = 'Foo Bar'; //Updated 

Если переменная is находится внутри цикла с мутацией, тогда используется let, так как let можно переназначить после создания экземпляра. Последняя строка в in codemod проверяла, обновлялось ли что-нибудь, например, любые переменные. В этом случае возвращается новый источник файла, в противном случае возвращается значение null, которое сообщает JSCodeshift, что никакой обработки не было. Полный исходный код для codemod можно найти здесь .

Команда Facebook также добавила несколько кодов для обновления синтаксиса React и обработки изменений в React API. Некоторые кодовые модули включают sort -codemod sort-comp, который сортирует методы жизненного цикла React в соответствии с правилом ESlint sort-comp .

Самым последним и популярным кодовым модулем React является React-PropTypes-to-prop-types, который помогает в недавнем переходе от основной команды React переместить React.PropTypes в собственный модуль узла. Это означает, что в React v16 разработчикам потребуется установить prop-types, если они хотят продолжать использовать propTypes в компонентах. Это отличный пример варианта использования для codemod. Метод использования PropTypes не установлен в камне.

Следующие все действительны:

Импорт React и доступ к PropTypes из импорта по умолчанию:

 import React from 'react'; class HelloWorld extends React.Component { static propTypes = { name: React.PropTypes.string, } ..... 

Импорт React и именованный импорт для PropTypes:

 import React, { PropTypes, Component } from 'react'; class HelloWorld extends Component { static propTypes = { name: PropTypes.string, } ..... 

Импортируем React и именованный импорт для PropTypes, но объявляем PropTypes для компонента без состояния:

 import React, { PropTypes } from 'react'; const HelloWorld = ({name}) => { ..... } HelloWorld.propTypes = { name: PropTypes.string }; 

Наличие трех способов реализации одного и того же решения особенно затрудняет выполнение регулярного выражения для поиска и замены. Если бы в нашей базе кода было три вышеуказанных кода, мы могли бы легко перейти на новый шаблон PropTypes, выполнив следующее:

 jscodeshift src/ -t transforms/proptypes.js 

В этом примере мы извлекли кодовый модуль PropTypes из репозитория response-codemods и добавили его в каталог transforms нашего проекта. Кодмод добавит import PropTypes from 'prop-types'; к каждому файлу и замените все экземпляры React.PropTypes на PropTypes .

Вывод

Facebook стал пионером в поддержке кода, позволяя разработчикам приспосабливаться к постоянно меняющимся методам API и кода. Усталость JavaScript стала большой проблемой, и, как я показал, наличие инструментов, которые могут помочь справиться со стрессом при обновлении существующего кода, может помочь уменьшить эту усталость.

В мире серверной разработки с опорой на базы данных разработчики регулярно создают сценарии миграции для поддержки базы данных и обеспечения пользователей самой последней версией своей базы данных. Сопровождающие библиотеки JavaScript могут предоставлять кодовые модули в виде сценария миграции при выпуске основных версий, при этом с критическими изменениями кодовый модуль может обрабатывать процесс обновления.

Это будет соответствовать существующему процессу миграции, так как с помощью скриптов npm install можно запускать. Автоматический запуск codemod во время установки / обновления может ускорить обновления и повысить доверие потребителей. Включение этого в процесс выпуска будет полезно не только для потребителей, но и уменьшит накладные расходы для сопровождающих при обновлении примеров и руководств.

В этой статье мы увидели мощную природу codemods и JSCodeshift и то, как они могут быстро обновлять сложный код. С самого начала с помощью инструмента Codemod и перехода к таким инструментам, как ASTExplorer и JSCodeshift, мы теперь можем создавать кодовые модули в соответствии с нашими собственными потребностями. Использование уже широкого спектра готовых кодовых модулей позволяет разработчикам вовремя продвигаться вперед.

Вы уже использовали codemods? Что входит в ваш инструментарий? Какие другие рефакторы могут быть полезны для codemods? Дай мне знать в комментариях!

Эта статья была рецензирована Грэмом Коксом и Майклом Ваньойке . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!