vite-plugin-eslint2
Version:
ESLint plugin for Vite.
333 lines (332 loc) • 12.3 kB
JavaScript
//#region \0rolldown/runtime.js
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 __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
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
let _rollup_pluginutils = require("@rollup/pluginutils");
//#region src/constants.ts
const ESLINT_SEVERITY = {
ERROR: 2,
WARNING: 1
};
const PLUGIN_NAME = "vite:eslint2";
const WS_RUNTIME_LOADED_EVENT = "vite:eslint2:runtime-loaded";
const WS_OVERLAY_EVENT = "vite:eslint2:overlay";
const RUNTIME_CLIENT_RUNTIME_PATH = "/@vite-plugin-eslint2-runtime";
const RUNTIME_CLIENT_ENTRY_PATH = "/@vite-plugin-eslint2-runtime-entry";
const PLUGIN_OPTION_KEYS = [
"test",
"dev",
"build",
"include",
"exclude",
"eslintPath",
"formatter",
"lintInWorker",
"lintOnStart",
"lintDirtyOnly",
"emitError",
"emitErrorAsWarning",
"emitWarning",
"emitWarningAsError",
"customOverlay"
];
const COLOR_MAPPING = {
error: "red",
warning: "yellow",
plugin: "magenta"
};
//#endregion
//#region src/linter.ts
var import_picocolors = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
let p = process || {}, argv = p.argv || [], env = p.env || {};
let isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
let formatter = (open, close, replace = open) => (input) => {
let string = "" + input, index = string.indexOf(close, open.length);
return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
};
let replaceClose = (string, close, replace, index) => {
let result = "", cursor = 0;
do {
result += string.substring(cursor, index) + replace;
cursor = index + close.length;
index = string.indexOf(close, cursor);
} while (~index);
return result + string.substring(cursor);
};
let createColors = (enabled = isColorSupported) => {
let f = enabled ? formatter : () => String;
return {
isColorSupported: enabled,
reset: f("\x1B[0m", "\x1B[0m"),
bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
italic: f("\x1B[3m", "\x1B[23m"),
underline: f("\x1B[4m", "\x1B[24m"),
inverse: f("\x1B[7m", "\x1B[27m"),
hidden: f("\x1B[8m", "\x1B[28m"),
strikethrough: f("\x1B[9m", "\x1B[29m"),
black: f("\x1B[30m", "\x1B[39m"),
red: f("\x1B[31m", "\x1B[39m"),
green: f("\x1B[32m", "\x1B[39m"),
yellow: f("\x1B[33m", "\x1B[39m"),
blue: f("\x1B[34m", "\x1B[39m"),
magenta: f("\x1B[35m", "\x1B[39m"),
cyan: f("\x1B[36m", "\x1B[39m"),
white: f("\x1B[37m", "\x1B[39m"),
gray: f("\x1B[90m", "\x1B[39m"),
bgBlack: f("\x1B[40m", "\x1B[49m"),
bgRed: f("\x1B[41m", "\x1B[49m"),
bgGreen: f("\x1B[42m", "\x1B[49m"),
bgYellow: f("\x1B[43m", "\x1B[49m"),
bgBlue: f("\x1B[44m", "\x1B[49m"),
bgMagenta: f("\x1B[45m", "\x1B[49m"),
bgCyan: f("\x1B[46m", "\x1B[49m"),
bgWhite: f("\x1B[47m", "\x1B[49m"),
blackBright: f("\x1B[90m", "\x1B[39m"),
redBright: f("\x1B[91m", "\x1B[39m"),
greenBright: f("\x1B[92m", "\x1B[39m"),
yellowBright: f("\x1B[93m", "\x1B[39m"),
blueBright: f("\x1B[94m", "\x1B[39m"),
magentaBright: f("\x1B[95m", "\x1B[39m"),
cyanBright: f("\x1B[96m", "\x1B[39m"),
whiteBright: f("\x1B[97m", "\x1B[39m"),
bgBlackBright: f("\x1B[100m", "\x1B[49m"),
bgRedBright: f("\x1B[101m", "\x1B[49m"),
bgGreenBright: f("\x1B[102m", "\x1B[49m"),
bgYellowBright: f("\x1B[103m", "\x1B[49m"),
bgBlueBright: f("\x1B[104m", "\x1B[49m"),
bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
bgCyanBright: f("\x1B[106m", "\x1B[49m"),
bgWhiteBright: f("\x1B[107m", "\x1B[49m")
};
};
module.exports = createColors();
module.exports.createColors = createColors;
})))(), 1);
const interopDefault = (m) => m.default || m;
const getFilter = (options) => (0, _rollup_pluginutils.createFilter)(options.include, options.exclude);
const getESLintConstructorOptions = (options) => ({
...Object.fromEntries(Object.entries(options).filter(([key]) => !PLUGIN_OPTION_KEYS.includes(key))),
errorOnUnmatchedPattern: false
});
const initializeESLint = async (options) => {
const { eslintPath, formatter } = options;
try {
const module = interopDefault(await import(eslintPath));
const ESLintClass = module.loadESLint ? await module.loadESLint() : module.ESLint || module.FlatESLint || module.LegacyESLint;
const eslintInstance = new ESLintClass(getESLintConstructorOptions(options));
return {
eslintInstance,
formatter: await eslintInstance.loadFormatter(formatter),
outputFixes: ESLintClass.outputFixes.bind(ESLintClass)
};
} catch (error) {
throw new Error(`Failed to initialize ESLint. Have you installed and configured correctly? ${error}`);
}
};
const isVirtualModule = (id) => id.startsWith("virtual:") || id[0] === "\0" || !id.includes("/");
const getFilePath = (id) => (0, _rollup_pluginutils.normalizePath)(id).split("?")[0];
const removeESLintErrorResults = (results) => results.map((result) => {
const filteredMessages = result.messages.filter((message) => message.severity !== ESLINT_SEVERITY.ERROR);
const filteredSuppressedMessages = result.suppressedMessages.filter((message) => message.severity !== ESLINT_SEVERITY.ERROR);
return {
...result,
messages: filteredMessages,
suppressedMessages: filteredSuppressedMessages,
errorCount: 0,
fatalErrorCount: 0,
fixableErrorCount: 0
};
});
const removeESLintWarningResults = (results) => results.map((result) => {
const filteredMessages = result.messages.filter((message) => message.severity !== ESLINT_SEVERITY.WARNING);
const filteredSuppressedMessages = result.suppressedMessages.filter((message) => message.severity !== ESLINT_SEVERITY.WARNING);
return {
...result,
messages: filteredMessages,
suppressedMessages: filteredSuppressedMessages,
warningCount: 0,
fixableWarningCount: 0
};
});
const filterESLintLintResults = (results) => results.filter((result) => result.errorCount > 0 || result.warningCount > 0);
const colorize = (text, textType) => import_picocolors.default[COLOR_MAPPING[textType]](text);
const log = (text, textType, context) => {
console.log("");
if (context) {
if (textType === "error") context.error(text);
else if (textType === "warning") context.warn(text);
} else console.log(`${text} Plugin: ${colorize(PLUGIN_NAME, "plugin")}\r\n`);
};
const shouldIgnoreModule = async (id, filter, eslintInstance) => {
if (isVirtualModule(id)) return true;
if (!filter(id)) return true;
const filePath = getFilePath(id);
if ([".vue", ".svelte"].some((extname) => filePath.endsWith(extname)) && id.includes("?") && id.includes("type=style")) return true;
if (eslintInstance) return await eslintInstance.isPathIgnored(filePath);
return false;
};
const filterResults = (results, options) => {
let filtered = [...results];
if (!options.emitError) filtered = removeESLintErrorResults(filtered);
if (!options.emitWarning) filtered = removeESLintWarningResults(filtered);
filtered = filterESLintLintResults(filtered);
let textType;
if (filtered.some((result) => result.errorCount > 0)) textType = options.emitErrorAsWarning ? "warning" : "error";
else textType = options.emitWarningAsError ? "error" : "warning";
return {
results: filtered,
textType
};
};
const report = async (results, formatter, textType, context, emit) => {
const formattedText = await formatter.format(results);
if (emit) emit({
formattedText,
textType,
context
});
else log(formattedText, textType, context);
};
/**
* Build a structured overlay payload from filtered results.
*
* Severity follows the *post-filter* textType so `emitErrorAsWarning` /
* `emitWarningAsError` are reflected in the overlay, matching the terminal
* channel. Each result retains only the fields the runtime needs to render.
*
* Exported for testing.
*/
const buildOverlayPayload = (results, textType) => ({ results: results.map((result) => ({
filePath: result.filePath,
messages: result.messages.map((message) => ({
line: message.line,
column: message.column,
severity: textType === "error" ? "error" : "warning",
ruleId: message.ruleId,
message: message.message
}))
})) });
/**
* Accumulates per-file overlay payloads so the panel shows all current problems
* across the project, not just the last-linted file. Files with no remaining
* messages are dropped; once empty, `snapshot` returns `undefined` so the caller
* can clear the overlay.
*/
var OverlayManager = class {
files = /* @__PURE__ */ new Map();
upsert(payload) {
for (const result of payload.results) if (result.messages.length === 0) this.files.delete(result.filePath);
else this.files.set(result.filePath, result);
return this.snapshot();
}
snapshot() {
if (this.files.size === 0) return;
return { results: [...this.files.values()] };
}
};
function createLinter(options, adapters) {
const filter = getFilter(options);
let eslintInstance;
let formatter;
let outputFixes;
const ready = initializeESLint(options).then((result) => {
eslintInstance = result.eslintInstance;
formatter = result.formatter;
outputFixes = result.outputFixes;
});
const overlayManager = adapters?.onOverlayPayload ? new OverlayManager() : void 0;
const lintFiles = async (files, context) => {
await ready;
const lintResults = await eslintInstance.lintFiles(files);
if (!lintResults || lintResults.length === 0) return;
if (options.fix) outputFixes(lintResults);
const { results, textType } = filterResults(lintResults, options);
if (results.length === 0) {
if (overlayManager && adapters?.onOverlayPayload) {
overlayManager.upsert({ results: lintResults.map((result) => ({
filePath: result.filePath,
messages: []
})) });
adapters.onOverlayPayload(overlayManager.snapshot());
}
return;
}
if (overlayManager && adapters?.onOverlayPayload) {
const payload = overlayManager.upsert(buildOverlayPayload(results, textType));
adapters.onOverlayPayload(payload);
}
return report(results, formatter, textType, context, adapters?.emit);
};
return {
lint: async (id, context) => {
await ready;
if (await shouldIgnoreModule(id, filter, eslintInstance)) return;
const filePath = getFilePath(id);
return lintFiles(options.lintDirtyOnly ? filePath : options.include, context);
},
lintAll: async (context) => lintFiles(options.include, context)
};
}
//#endregion
Object.defineProperty(exports, "PLUGIN_NAME", {
enumerable: true,
get: function() {
return PLUGIN_NAME;
}
});
Object.defineProperty(exports, "RUNTIME_CLIENT_ENTRY_PATH", {
enumerable: true,
get: function() {
return RUNTIME_CLIENT_ENTRY_PATH;
}
});
Object.defineProperty(exports, "RUNTIME_CLIENT_RUNTIME_PATH", {
enumerable: true,
get: function() {
return RUNTIME_CLIENT_RUNTIME_PATH;
}
});
Object.defineProperty(exports, "WS_OVERLAY_EVENT", {
enumerable: true,
get: function() {
return WS_OVERLAY_EVENT;
}
});
Object.defineProperty(exports, "WS_RUNTIME_LOADED_EVENT", {
enumerable: true,
get: function() {
return WS_RUNTIME_LOADED_EVENT;
}
});
Object.defineProperty(exports, "__toESM", {
enumerable: true,
get: function() {
return __toESM;
}
});
Object.defineProperty(exports, "createLinter", {
enumerable: true,
get: function() {
return createLinter;
}
});