Написание JS-плагинов
INFO
JS-плагины сейчас в alpha и активно развиваются.
API должны вести себя как в ESLint. Любые отличия — баг; сообщите об этом.
API, совместимый с ESLint
В Oxlint API плагинов совпадает с ESLint. См. документацию ESLint: создание плагина и пользовательские правила.
Простой плагин, ругающийся на файлы с более чем пятью объявлениями классов:
// plugin.js
const rule = {
create(context) {
let classCount = 0;
return {
ClassDeclaration(node) {
classCount++;
if (classCount === 6) {
context.report({ message: "Too many classes", node });
}
},
};
},
};
const plugin = {
meta: {
name: "best-plugin-ever",
},
rules: {
"max-classes": rule,
},
};
export default plugin;{
"jsPlugins": ["./plugin.js"],
"rules": {
"best-plugin-ever/max-classes": "error"
}
}import { defineConfig } from "oxlint";
export default defineConfig({
jsPlugins: ["./plugin.js"],
rules: {
"best-plugin-ever/max-classes": "error",
},
});Альтернативный API
Oxlint предлагает чуть другой API с лучшей производительностью.
Правила на этом API остаются совместимыми с ESLint (см. ниже).
Тот же пример на альтернативном API:
import { eslintCompatPlugin } from "@oxlint/plugins";
const rule = {
createOnce(context) {
// Define counter variable
let classCount;
return {
before() {
// Reset counter before traversing AST of each file
classCount = 0;
},
// Same as before
ClassDeclaration(node) {
classCount++;
if (classCount === 6) {
context.report({ message: "Too many classes", node });
}
},
};
},
};
const plugin = eslintCompatPlugin({
meta: {
name: "best-plugin-ever",
},
rules: {
"max-classes": rule,
},
});
export default plugin;Отличия:
- Оборачиваем плагин в
eslintCompatPlugin(...).
- const plugin = {
+ const plugin = eslintCompatPlugin({- Используем
createOnceвместоcreate.
- create(context) {
+ createOnce(context) {- В ESLint
createвызывается для каждого файла;createOnce— один раз. Подготовку на файл делайте в хукеbefore.
- let classCount = 0;
+ let classCount;
return {
+ before() {
+ classCount = 0; // Reset counter
+ },
ClassDeclaration(node) {
classCount++;
if (classCount === 6) {
context.report({ message: "Too many classes", node });
}
},
};Что делает eslintCompatPlugin?
eslintCompatPlugin добавляет каждому правилу метод create, который делегирует в createOnce.
Плагин можно использовать и в Oxlint, и в ESLint.
- В Oxlint получаете выгоду от более быстрого API
createOnce. - В ESLint поведение как у классического
create.
Публикуя плагин в NPM, добавьте @oxlint/plugins как runtime-зависимость (не dev).
Пропуск обхода AST
Если before возвращает false, правило пропускает файл.
// This rule does not run on files which start with a `// @skip-me` comment
const rule = {
createOnce(context) {
return {
before() {
if (context.sourceCode.text.startsWith("// @skip-me")) {
return false;
}
},
FunctionDeclaration(node) {
// Do stuff
},
};
},
};Эквивалент в ESLint:
const rule = {
create(context) {
if (context.sourceCode.text.startsWith("// @skip-me")) {
return {};
}
return {
FunctionDeclaration(node) {
// Do stuff
},
};
},
};Хук before
before выполняется до обхода AST.
ВАЖНО: для каждого файла вызов before не гарантирован.
Сейчас он вызывается, но позже планируется логика на стороне Rust: вызывать правило только если нужны интересующие его узлы AST — ради производительности.
В примере выше, если в файле нет FunctionDeclaration, правило для файла не запускается вообще, в том числе без вызова before.
Если код должен выполняться для каждого файла, используйте посетитель Program:
const rule = {
createOnce(context) {
return {
Program(node) {
// This always runs for every file, even if it
// doesn't contain any `FunctionDeclaration`s
},
FunctionDeclaration(node) {
/* do stuff */
},
};
},
};Хук after
Есть и хук after: один раз на файл после полного обхода AST (после Program:exit).
Используйте для освобождения ресурсов после обхода.
Если before вернул false, after не вызывается.
Как и для before, вызов after для каждого файла не гарантирован (см. выше).
Почему альтернативный API быстрее?
Коротко: сейчас — не быстрее. Но скоро будет.
Перед превью JS-плагинов был длительный R&D: найдены возможности оптимизации, прототипирован следующий уровень плагинов Oxlint с очень высокой производительностью.
Многие оптимизации ещё не в текущем релизе; в ближайшие месяцы их доведут и включат в Oxlint.
Альтернативный API рассчитан на эти оптимизации. Переходя на него сейчас, авторы плагинов позже получат существенный прирост скорости без правок кода — достаточно обновить версию oxlint.
Какие именно оптимизации?
Вернёмся к правилу «не больше пяти классов»:
const rule = {
create(context) {
let classCount = 0;
return {
ClassDeclaration(node) {
classCount++;
if (classCount === 6) {
context.report({ message: "Too many classes", node });
}
},
};
},
};create вызывается на каждый файл с новым context.
Для максимальной скорости хотелось бы статически знать, какие узлы AST интересны правилу. Тогда возможны две оптимизации:
Не обходить AST в JS: при обходе в Rust собрать «указатели» на нужные узлы, передать список в JS — и прыгать сразу к ним, а не искать всё дерево.
Если в AST нет подходящих узлов (в примере — объявлений классов), не вызывать JS для этого файла.
JS динамичен: create может что угодно вернуть. Приходится вызывать create, чтобы узнать, нужен ли вызов create.
С альтернативным API createOnce вызывается один раз — и поведение правила становится известно заранее, включая перечисленные оптимизации.
API create — не «плохое решение» ESLint; при связке Rust↔JS оно создаёт дополнительные сложности.
Дальше
Раздел Поддержка API — какие API ESLint доступны в плагинах Oxlint.