В экосистеме ESLint существует особая категория правил, способных не только обнаруживать нарушения, но и автоматически исправлять их. Такие правила называются fixable-правилами (исправляемыми правилами). Их основная задача заключается в автоматизации рутинных изменений исходного кода без участия разработчика.
При запуске ESLint с параметром --fix движок анализирует
результаты работы правил и применяет предложенные исправления
непосредственно к файлам проекта.
Пример:
const message = "Hello"
При активном правиле semi ESLint обнаружит отсутствие
точки с запятой:
const message = "Hello";
Вместо вывода только предупреждения инструмент автоматически внесёт изменение в исходный файл.
Каждое правило ESLint может сообщать о найденных проблемах через
метод context.report().
Обычное правило выглядит следующим образом:
context.report({
node,
message: "Ошибка оформления"
});
Чтобы правило стало исправляемым, в объект отчёта добавляется функция
fix.
context.report({
node,
message: "Лишний пробел",
fix(fixer) {
return fixer.remove(node);
}
});
При запуске с флагом:
eslint . --fix
ESLint выполнит следующие действия:
fix.Каждое исправляемое правило обязано явно сообщать ESLint о возможности автоматического исправления.
Для этого используется свойство fixable внутри объекта
meta.
Пример:
module.exports = {
meta: {
fixable: "code"
},
create(context) {
return {};
}
};
Возможны два значения:
| Значение | Назначение |
|---|---|
code |
Изменение исходного кода |
whitespace |
Изменение только пробельных символов |
Пример правила форматирования:
meta: {
fixable: "whitespace"
}
Пример правила изменения синтаксиса:
meta: {
fixable: "code"
}
Если функция fix присутствует, но свойство
meta.fixable отсутствует, ESLint выдаст ошибку при загрузке
правила.
Для внесения изменений используется специальный объект
fixer, который передаётся в функцию исправления.
fix(fixer) {
// исправление
}
Объект предоставляет набор методов для модификации текста.
Вставка текста после узла.
Исходный код:
const value = 10
Исправление:
fix(fixer) {
return fixer.insertTextAfter(node, ";");
}
Результат:
const value = 10;
Вставка текста перед узлом.
Пример:
fix(fixer) {
return fixer.insertTextBefore(node, "/* generated */\n");
}
Результат:
/* generated */
const value = 10;
Замена текста узла.
Исходный код:
var count = 5;
Исправление:
fix(fixer) {
return fixer.replaceText(node, "let count = 5");
}
Результат:
let count = 5;
Замена диапазона символов.
Пример:
fix(fixer) {
return fixer.replaceTextRange(
[10, 15],
"const"
);
}
Изменяется только указанный диапазон текста.
Удаление узла целиком.
Исходный код:
debugger;
Исправление:
fix(fixer) {
return fixer.remove(node);
}
Результат:
Удаление произвольного диапазона.
fix(fixer) {
return fixer.removeRange([20, 30]);
}
Метод применяется при необходимости удалить часть конструкции, а не весь AST-узел.
Рассмотрим правило, запрещающее использование var.
Исходный код:
var user = "Alex";
Реализация:
module.exports = {
meta: {
fixable: "code"
},
create(context) {
return {
VariableDeclaration(node) {
if (node.kind !== "var") {
return;
}
context.report({
node,
message: "Используйте let вместо var",
fix(fixer) {
return fixer.replaceTextRange(
[node.range[0], node.range[0] + 3],
"let"
);
}
});
}
};
}
};
После запуска:
eslint src --fix
Получается:
let user = "Alex";
Одна проблема может требовать сразу нескольких изменений.
В этом случае функция возвращает массив исправлений.
Пример:
fix(fixer) {
return [
fixer.insertTextBefore(node, "("),
fixer.insertTextAfter(node, ")")
];
}
ESLint рассматривает их как единое исправление.
Допускается использование генераторов.
Пример:
fix(fixer) {
return [
fixer.remove(tokenA),
fixer.remove(tokenB),
fixer.insertTextAfter(tokenC, ";")
];
}
Либо:
fix(fixer) {
const fixes = [];
fixes.push(
fixer.remove(tokenA)
);
fixes.push(
fixer.remove(tokenB)
);
return fixes;
}
Это удобно при сложной логике формирования набора изменений.
Несколько правил могут попытаться изменить один и тот же участок текста.
Например:
Правило №1:
const a=1;
↓
const a = 1;
Правило №2:
const a=1;
↓
const number = 1;
Если диапазоны изменений пересекаются, ESLint не сможет применить оба исправления одновременно.
Для предотвращения повреждения кода используется механизм разрешения конфликтов.
В результате:
После внесения исправлений ESLint запускает повторный анализ файла.
Схема работы:
Проверка
↓
Исправление
↓
Повторная проверка
↓
Новые исправления
↓
Повторная проверка
Это необходимо, поскольку одно исправление может породить новые нарушения.
Пример:
Исходный код:
var message="Hello"
Первый проход:
let message="Hello"
Второй проход:
let message = "Hello"
Третий проход:
let message = "Hello";
Процесс продолжается до тех пор, пока больше не останется применимых исправлений либо не будет достигнут внутренний лимит итераций.
Исправление должно сохранять поведение программы.
Хорошее исправление:
const value=10;
↓
const value = 10;
Семантика программы не меняется.
Опасное исправление:
foo == bar
↓
foo === bar
Хотя изменение часто считается желательным, оно способно изменить результат вычислений.
Поэтому многие правила предпочитают не выполнять подобные преобразования автоматически.
Качественное fixable-правило изменяет только необходимый участок кода.
Нежелательный вариант:
fix(fixer) {
return fixer.replaceText(
parentNode,
generateEntireFile()
);
}
Предпочтительный вариант:
fix(fixer) {
return fixer.replaceText(
offendingToken,
replacement
);
}
Минимальные изменения:
Для точных исправлений часто используется объект
SourceCode.
Получение объекта:
const sourceCode =
context.getSourceCode();
Получение исходного текста узла:
const text =
sourceCode.getText(node);
Использование при исправлении:
fix(fixer) {
const text = sourceCode.getText(node);
return fixer.replaceText(
node,
text.trim()
);
}
Это позволяет строить исправления на основе фактического содержимого файла.
Автоматические исправления не предназначены для сложного рефакторинга.
Нежелательные сценарии:
Для таких задач используются специализированные инструменты:
Fixable-правила ориентированы на локальные, безопасные и предсказуемые изменения внутри одного файла.
Наиболее распространённые автоматически исправляемые правила относятся к следующим группам:
Форматирование
quotes
semi
comma-spacing
indent
Исправляют:
Стилистические соглашения
object-curly-spacing
array-bracket-spacing
arrow-parens
Приводят код к единому стилю оформления.
Современный синтаксис
prefer-const
object-shorthand
Помогают использовать более современные конструкции языка.
Удаление лишних элементов
no-extra-semi
no-trailing-spaces
Устраняют ненужные символы и пробелы.
При разработке собственных правил обычно соблюдаются следующие принципы:
Такие требования позволяют создавать надёжные автоматические преобразования, хорошо взаимодействующие с остальной экосистемой ESLint и другими правилами анализа кода.