UNPKG

unplugin-oxlint

Version:

A universal bundler plugin for integrating the Oxlint linter into your project.

435 lines (424 loc) 13.2 kB
//#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; } });