unplugin-oxlint
Version:
A universal bundler plugin for integrating the Oxlint linter into your project.
435 lines (424 loc) • 13.2 kB
JavaScript
//#region rolldown:runtime
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
const unplugin = __toESM(require("unplugin"));
const node_path = __toESM(require("node:path"));
const node_process = __toESM(require("node:process"));
const local_pkg = __toESM(require("local-pkg"));
const nypm = __toESM(require("nypm"));
const consola_utils = __toESM(require("consola/utils"));
const defu = __toESM(require("defu"));
const destr = __toESM(require("destr"));
const eslint = __toESM(require("eslint"));
const execa = __toESM(require("execa"));
const fs_extra = __toESM(require("fs-extra"));
const eslint_use_at_your_own_risk = __toESM(require("eslint/use-at-your-own-risk"));
const ohash = __toESM(require("ohash"));
const chokidar = __toESM(require("chokidar"));
const fast_glob = __toESM(require("fast-glob"));
//#region package.json
var version = "0.7.4";
//#endregion
//#region src/core/logger.ts
const defaultLogger = { log(value) {
return node_process.default.stdout.write(value);
} };
function createLogger(logger = defaultLogger) {
function printLintResults(records) {
logger.log("\r\n");
logger.log(consola_utils.colors.bgCyan(consola_utils.colors.black(` v${version} `)));
logger.log(" unplugin-oxlint ");
logger.log("\r\n");
Object.entries(records).forEach(([filename, results]) => {
if (results.length) {
logger.log("\r\n");
logger.log(consola_utils.colors.blue(consola_utils.colors.underline((0, node_path.join)(node_process.default.cwd(), filename))));
logger.log("\r\n\r\n");
results.forEach(({ message, severity, ruleId, line, column }) => {
const suffix = consola_utils.colors.gray(`(${ruleId})`);
let prefix = `${[line, column].join(":")}`;
const prefixPlaceholder = 7 - prefix.length;
if (prefixPlaceholder > 0) prefix += Array.from({ length: prefixPlaceholder }).join(" ");
prefix = consola_utils.colors.gray(prefix);
if (severity === "error") logger.log(` ${prefix}${consola_utils.colors.red("✘")} ${consola_utils.colors.red(message)} ${suffix}\r\n`);
if (severity === "warning") logger.log(` ${prefix}${consola_utils.colors.yellow("⚠")} ${consola_utils.colors.yellow(message)} ${suffix}\r\n`);
});
}
});
logger.log("\r\n");
}
return { printLintResults };
}
//#endregion
//#region src/core/options.ts
const DEFAULT_OXLINT_OPTIONS = {
rootDir: ".",
glob: false,
deny: [],
allow: [],
fix: false,
params: "",
config: "",
noIgnore: false,
quiet: false,
denyWarnings: false,
watch: false,
includes: ".",
excludes: [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/]
};
function resolveOptions(options = {}) {
return (0, defu.defu)(options, DEFAULT_OXLINT_OPTIONS);
}
//#endregion
//#region src/core/regexp.ts
const oxlintRE = /(\w+)\((\w+(?:-\w+)*)\):\s*([\s\S]*)/g;
//#endregion
//#region src/core/utils.ts
function until(value, truthyValue = true, ms = 500) {
return new Promise((resolve$1) => {
function c() {
if (value() === truthyValue) resolve$1();
else setTimeout(c, ms);
}
c();
});
}
function isString(value) {
return typeof value === "string";
}
function normalizeIgnores(excludes) {
return excludes.map((pattern) => {
if (isString(pattern)) return new RegExp(pattern);
return pattern;
});
}
function normalizeAbsolutePath(ids, defaults = []) {
return (() => {
if (Array.isArray(ids) ? !!ids.length : !!ids) return [ids];
return [defaults];
})().flat().map((path) => (0, node_path.isAbsolute)(path) ? path : (0, node_path.join)(node_process.default.cwd(), path));
}
function generateFileHash(id) {
const [path] = normalizeAbsolutePath(id, []);
return (0, ohash.hash)(fs_extra.default.readFileSync(path));
}
async function createESLint(options = {}) {
const isFlat = await eslint_use_at_your_own_risk.default.shouldUseFlatConfig();
if (!isFlat) return new eslint_use_at_your_own_risk.default.LegacyESLint(options);
return new eslint_use_at_your_own_risk.default.FlatESLint(options);
}
//#endregion
//#region src/core/oxlint.ts
const agents = {
bun: ["bunx"],
npm: ["npx"],
yarn: ["yarn", "dlx"],
pnpm: ["pnpm", "dlx"]
};
async function runNpxCommand(command, args, ctx) {
const pkgm = await ctx.getPackageManager() ?? "npm";
const [agent, dlx] = agents[pkgm];
const params = [
dlx,
command,
...args
].filter(Boolean);
return (0, execa.execa)(agent, params, { reject: false });
}
function detectOxlintMessage(message) {
let ruleId = "";
let content = message;
let plugin = "";
const match = oxlintRE.exec(message);
if (match) {
plugin = match[1];
ruleId = match[2];
content = match[3];
}
oxlintRE.lastIndex = 0;
return {
ruleId,
content,
plugin
};
}
async function runOxlintCommand(ids, ctx) {
const options = ctx.options;
const contentRecord = {};
const paths = normalizeAbsolutePath(ids, options.rootDir);
const { stdout } = await runNpxCommand("oxlint", [
...options.deny.map((d) => ["-D", d]).flat(),
...options.allow.map((a) => ["-A", a]).flat(),
...options.config ? ["-c", options.config] : [],
...options.params.split(" "),
options.fix ? "--fix" : "",
options.noIgnore ? "--no-ignore" : "",
options.quiet ? "--quiet" : "",
options.denyWarnings ? "--deny-warnings" : "",
"--format",
"json",
...paths
], ctx);
function format(value) {
const index = value.indexOf("Finished");
if (index > -1) return value.slice(0, index).trim();
return value;
}
const results = [];
const outputs = (0, destr.destr)(format(stdout));
if (Array.isArray(outputs)) {
for await (const { filename } of outputs) {
if (contentRecord[filename]) continue;
const c = await fs_extra.default.readFile(filename, { encoding: "utf8" });
contentRecord[filename] = c;
}
outputs.forEach(({ code, filename, severity, message, labels }) => {
const [{ span }] = labels;
const lines = contentRecord[filename].slice(0, span.offset).split("\n");
const line = lines.length;
const column = lines[lines.length - 1].length + 1;
const { content } = detectOxlintMessage(message);
const ruleId = code.match(/\(([^)]+)\)/)?.[1] ?? "";
const result = {
filename,
linter: "oxlint",
severity,
message: content,
ruleId,
line,
column
};
results.push(result);
ctx.insertLintResult(filename, result);
});
}
return results;
}
function resolveESLintOptions({ options }) {
return (0, defu.defu)({}, {
fix: options.fix,
overrideConfigFile: options.config || void 0,
ignore: !options.noIgnore
});
}
async function runESLintCommand(ids, ctx) {
const options = ctx.options;
const paths = normalizeAbsolutePath(ids, options.rootDir || ".");
const eslint$1 = await createESLint(resolveESLintOptions(ctx));
const outputs = await eslint$1.lintFiles(paths);
if (options.fix) await eslint.ESLint.outputFixes(outputs);
if (options.quiet) return [];
const results = [];
outputs.forEach(({ filePath: filename, messages }) => {
messages.forEach(({ message, severity, ruleId, line, column }) => {
const ESLINT_SEVERITY = [
"off",
"warning",
"error"
];
const result = {
filename,
linter: "eslint",
severity: ESLINT_SEVERITY[severity],
ruleId: ruleId ?? "",
message,
line,
column
};
results.push(result);
ctx.insertLintResult(filename, result);
});
});
return results;
}
async function runLintCommand(ids, ctx) {
ctx.setHoldingStatus(true);
if (isString(ids) ? !!ids : ids.length) [ids].flat().forEach((filename) => {
ctx.resetLintResults(filename);
});
const results = await Promise.all([ctx.isExist("oxlint") ? runOxlintCommand(ids, ctx) : void 0, ctx.isExist("eslint") ? runESLintCommand(ids, ctx) : void 0].filter(Boolean));
ctx.outputLintResults();
ctx.setHoldingStatus(false);
return results.flat();
}
//#endregion
//#region src/core/watcher.ts
let watcher;
function setupWatcher(paths, ctx) {
watcher = chokidar.default.watch(ctx.options.glob ? fast_glob.default.sync(paths) : paths, {
ignored: (id) => normalizeIgnores(ctx.options.excludes).some((regex) => regex.test(id)),
persistent: true,
ignoreInitial: true
});
watcher.on("change", (id) => {
if (ctx.getHoldingStatus()) return;
const hash$1 = generateFileHash(id);
if (hash$1 === ctx.getFileHash(id)) return;
ctx.runLintCommand(id, ctx);
ctx.setFileHash(id, hash$1);
});
watcher.on("add", (id) => {
ctx.runLintCommand(id, ctx);
});
watcher.on("unlink", (id) => {
ctx.setFileHash(id, void 0);
ctx.resetLintResults(id);
ctx.outputLintResults();
});
node_process.default.on("exit", () => {
if (watcher) watcher.close?.();
});
return watcher;
}
//#endregion
//#region src/core/context.ts
function createOxlint(rawOptions = {}) {
let watcher$1;
const options = resolveOptions(rawOptions);
const ctx = createInternalContext(options);
const paths = [ctx.options.includes].flat().map((path) => (0, node_path.join)(node_process.default.cwd(), ctx.options.rootDir || ".", path));
async function runLintCommandWithContext(ids) {
return ctx.runLintCommand(ids, ctx);
}
function setup() {
if (options.watch) watcher$1 = setupWatcher(paths, ctx);
return runLintCommandWithContext(paths);
}
return {
options,
setup,
watcher: watcher$1,
runLintCommand: runLintCommandWithContext,
getHoldingStatus: ctx.getHoldingStatus,
getFileHash: ctx.getFileHash,
setFileHash: ctx.setFileHash,
resetLintResults: ctx.resetLintResults,
outputLintResults: ctx.outputLintResults
};
}
function createInternalContext(options) {
let isHolding = false;
let hasESLint = null;
let hasOxlint = null;
let packageManagerName = options.packageManager;
const logger = createLogger();
const lintResultRecord = {};
const fileHashRecord = {};
async function getPackageManager() {
if (!packageManagerName) {
const pkg = await (0, nypm.detectPackageManager)(node_process.default.cwd());
packageManagerName = pkg?.name ?? "npm";
}
return packageManagerName;
}
function getHoldingStatus() {
return isHolding;
}
function setHoldingStatus(value) {
isHolding = value;
}
function getFileHash(id) {
return fileHashRecord[id];
}
function setFileHash(id, hash$1) {
if (hash$1) {
fileHashRecord[id] = hash$1;
return;
}
delete fileHashRecord[id];
}
function insertLintResult(filename, result) {
filename = (0, node_path.relative)(node_process.default.cwd(), filename);
if (!lintResultRecord[filename]) lintResultRecord[filename] = [];
lintResultRecord[filename].push({
...result,
filename
});
}
function resetLintResults(filename) {
filename = (0, node_path.relative)(node_process.default.cwd(), filename);
lintResultRecord[filename] = [];
}
function outputLintResults() {
const isEmpty = !Object.values(lintResultRecord ?? {}).filter((results) => !!results.length)?.length;
if (isEmpty) return;
logger.printLintResults(lintResultRecord);
}
async function detectDependencies() {
if ([hasOxlint, hasESLint].includes(null)) {
const exists = await Promise.all([(0, local_pkg.isPackageExists)("oxlint", { paths: [(0, node_path.resolve)(options.rootDir)] }), (0, local_pkg.isPackageExists)("eslint", { paths: [(0, node_path.resolve)(options.rootDir)] })]);
hasOxlint = exists[0];
hasESLint = exists[1];
}
}
function isExist(dep) {
if (dep === "oxlint") return hasOxlint ?? false;
if (dep === "eslint") return hasESLint ?? false;
return false;
}
async function runLintCommand$1(ids, ctx) {
await detectDependencies();
return runLintCommand(ids, ctx);
}
return {
version,
options,
logger,
getPackageManager,
runLintCommand: runLintCommand$1,
getHoldingStatus,
setHoldingStatus,
getFileHash,
setFileHash,
insertLintResult,
resetLintResults,
outputLintResults,
detectDependencies,
isExist
};
}
//#endregion
//#region src/core/unplugin.ts
const unplugin$1 = (0, unplugin.createUnplugin)((rawOptions = {}) => {
const ctx = createOxlint(rawOptions);
ctx.setup();
return {
name: "oxlint",
async buildEnd() {
await until(ctx.getHoldingStatus, false);
if (!ctx.options.watch) await ctx.watcher?.close();
}
};
});
//#endregion
Object.defineProperty(exports, 'createOxlint', {
enumerable: true,
get: function () {
return createOxlint;
}
});
Object.defineProperty(exports, 'unplugin', {
enumerable: true,
get: function () {
return unplugin$1;
}
});