vite-plugin-eslint2
Version:
ESLint plugin for Vite.
308 lines (307 loc) • 9.92 kB
JavaScript
//#region \0@oxc-project+runtime@0.137.0/helpers/esm/typeof.js
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
return typeof o;
} : function(o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
//#endregion
//#region \0@oxc-project+runtime@0.137.0/helpers/esm/toPrimitive.js
function toPrimitive(t, r) {
if ("object" != _typeof(t) || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != _typeof(i)) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
//#endregion
//#region \0@oxc-project+runtime@0.137.0/helpers/esm/toPropertyKey.js
function toPropertyKey(t) {
var i = toPrimitive(t, "string");
return "symbol" == _typeof(i) ? i : i + "";
}
//#endregion
//#region \0@oxc-project+runtime@0.137.0/helpers/esm/defineProperty.js
function _defineProperty(e, r, t) {
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0
}) : e[r] = t, e;
}
//#endregion
//#region src/client/runtime.ts
const WS_RUNTIME_LOADED_EVENT = "vite:eslint2:runtime-loaded";
const WS_OVERLAY_EVENT = "vite:eslint2:overlay";
const OVERLAY_TAG = "vite-plugin-eslint2-overlay";
const DEFAULT_THEME = {
"--vite-plugin-eslint2-bg": "#1e1e1e",
"--vite-plugin-eslint2-panel-bg": "#252525ee",
"--vite-plugin-eslint2-error": "#f48771",
"--vite-plugin-eslint2-warning": "#cca700",
"--vite-plugin-eslint2-text": "#d4d4d4",
"--vite-plugin-eslint2-font-mono": "ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace",
"--vite-plugin-eslint2-radius": "6px"
};
const POSITION_STYLES = {
tl: {
host: "top:0;left:0;",
panel: "top:56px;left:16px;"
},
tr: {
host: "top:0;right:0;",
panel: "top:56px;right:16px;"
},
bl: {
host: "bottom:0;left:0;",
panel: "bottom:56px;left:16px;"
},
br: {
host: "bottom:0;right:0;",
panel: "bottom:56px;right:16px;"
}
};
const normalizeConfig = (overlayConfig) => {
const cfg = overlayConfig === true || overlayConfig === false || overlayConfig === void 0 ? {} : overlayConfig;
return {
position: cfg.position ?? "br",
initialIsOpen: cfg.initialIsOpen ?? "error",
zIndex: cfg.zIndex ?? 99998,
theme: cfg.theme
};
};
const escapeHtml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
var VitePluginESLint2Overlay = class extends HTMLElement {
constructor() {
super();
_defineProperty(this, "shadow", void 0);
_defineProperty(this, "payload", void 0);
_defineProperty(this, "isOpen", false);
_defineProperty(this, "config", normalizeConfig(false));
_defineProperty(this, "userToggled", false);
this.shadow = this.attachShadow({ mode: "open" });
}
setConfig(config) {
this.config = config;
}
connectedCallback() {
this.renderShell();
}
get hasError() {
return this.payload?.results.some((r) => r.messages.some((m) => m.severity === "error")) ?? false;
}
get totalCount() {
let errors = 0;
let warnings = 0;
for (const result of this.payload?.results ?? []) for (const message of result.messages) if (message.severity === "error") errors++;
else warnings++;
return {
errors,
warnings
};
}
resolveOpenState() {
const { initialIsOpen } = this.config;
if (initialIsOpen === true) return true;
if (initialIsOpen === false) return false;
return this.hasError;
}
setPayload(payload) {
this.payload = payload;
if (!this.userToggled) this.isOpen = this.resolveOpenState();
if (this.isConnected) this.renderBody();
}
toggle() {
this.userToggled = true;
this.isOpen = !this.isOpen;
this.renderBody();
}
renderShell() {
const { position, zIndex, theme } = this.config;
const themeVars = {
...DEFAULT_THEME,
...theme
};
const themeDecls = Object.entries(themeVars).map(([k, v]) => `${k}:${v};`).join("");
const pos = POSITION_STYLES[position];
this.shadow.innerHTML = `
<style>
:host {
all: initial;
${themeDecls}
display: block;
position: fixed;
${pos.host}
z-index: ${zIndex};
font-family: var(--vite-plugin-eslint2-font-mono);
color: var(--vite-plugin-eslint2-text);
}
.badge {
display: inline-flex;
align-items: center;
gap: 8px;
margin: 16px;
padding: 8px 14px;
background: var(--vite-plugin-eslint2-bg);
color: var(--vite-plugin-eslint2-text);
border-radius: var(--vite-plugin-eslint2-radius);
cursor: pointer;
user-select: none;
font-size: 13px;
line-height: 1;
box-shadow: 0 2px 8px rgba(0,0,0,0.35);
}
.badge .dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--vite-plugin-eslint2-error);
}
.badge .dot.warn { background: var(--vite-plugin-eslint2-warning); }
.badge .dot.ok { background: #4ec9b0; }
.panel {
position: fixed;
${pos.panel}
width: calc(100vw - 32px);
height: calc(100dvh - 96px);
overflow: auto;
background: var(--vite-plugin-eslint2-panel-bg);
border-radius: var(--vite-plugin-eslint2-radius);
box-shadow: 0 4px 24px rgba(0,0,0,0.5);
padding: 12px 0;
opacity: 0;
visibility: hidden;
transform: translateY(8px);
transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s;
pointer-events: none;
}
.panel.open {
opacity: 1;
visibility: visible;
transform: translateY(0);
pointer-events: auto;
}
.file {
padding: 10px 16px;
border-bottom: 1px solid rgba(255,255,255,0.08);
}
.file:last-child { border-bottom: none; }
.file-path {
color: var(--vite-plugin-eslint2-text);
opacity: 0.75;
margin-bottom: 8px;
font-size: 13px;
font-weight: 600;
word-break: break-all;
}
.msg {
display: flex;
gap: 10px;
padding: 3px 0;
font-size: 13px;
line-height: 1.5;
}
.msg .sev {
flex: 0 0 auto;
font-weight: 700;
text-transform: uppercase;
font-size: 11px;
letter-spacing: 0.5px;
}
.msg .sev.error { color: var(--vite-plugin-eslint2-error); }
.msg .sev.warning { color: var(--vite-plugin-eslint2-warning); }
.msg .loc {
flex: 0 0 auto;
opacity: 0.6;
}
.msg .text {
flex: 1 1 auto;
}
.msg .rule {
opacity: 0.55;
font-style: italic;
}
.empty {
padding: 16px;
opacity: 0.6;
font-size: 13px;
}
</style>
<div class="badge" part="badge">
<span class="dot"></span>
<span class="label">ESLint</span>
</div>
<div class="panel" part="panel"></div>
`;
this.shadow.querySelector(".badge").addEventListener("click", () => this.toggle());
this.isOpen = this.resolveOpenState();
this.renderBody();
}
renderBody() {
const badge = this.shadow.querySelector(".badge");
const panel = this.shadow.querySelector(".panel");
if (!(badge && panel)) return;
const dot = badge.querySelector(".dot");
const label = badge.querySelector(".label");
const { errors, warnings } = this.totalCount;
if (errors > 0) {
dot.className = "dot";
label.textContent = `ESLint ${errors} error${errors > 1 ? "s" : ""}${warnings > 0 ? ` / ${warnings} warning${warnings > 1 ? "s" : ""}` : ""}`;
} else if (warnings > 0) {
dot.className = "dot warn";
label.textContent = `ESLint ${warnings} warning${warnings > 1 ? "s" : ""}`;
} else {
dot.className = "dot ok";
label.textContent = "ESLint";
}
panel.classList.toggle("open", this.isOpen && errors + warnings > 0);
if (!this.isOpen || errors + warnings === 0 || !this.payload) {
panel.innerHTML = `<div class="empty">No problems</div>`;
return;
}
panel.innerHTML = this.payload.results.map((result) => {
const msgs = result.messages.map((m) => {
return `<div class="msg">
<span class="sev ${m.severity === "error" ? "error" : "warning"}">${m.severity}</span>
<span class="loc">${m.line}:${m.column}</span>
<span class="text">${escapeHtml(m.message)}</span>
${m.ruleId ? `<span class="rule">${escapeHtml(m.ruleId)}</span>` : ""}
</div>`;
}).join("");
return `<div class="file">
<div class="file-path">${escapeHtml(result.filePath)}</div>
${msgs}
</div>`;
}).join("");
}
};
/**
* Mount the overlay custom element and wire the WebSocket events.
*
* Guarded for non-DOM environments (SSR, headless tests): if `document` is
* absent, this is a no-op and the runtime-loaded ping is skipped, which tells
* the server to fall back to terminal-only output.
*/
function inject({ overlayConfig }) {
if (typeof document === "undefined") return;
if (!customElements.get(OVERLAY_TAG)) customElements.define(OVERLAY_TAG, VitePluginESLint2Overlay);
if (document.querySelector(OVERLAY_TAG)) return;
const config = normalizeConfig(overlayConfig);
const el = document.createElement(OVERLAY_TAG);
el.setConfig(config);
document.body.appendChild(el);
if (import.meta.hot) {
import.meta.hot.on(WS_OVERLAY_EVENT, (payload) => {
el.setPayload(payload);
});
import.meta.hot.send(WS_RUNTIME_LOADED_EVENT);
}
}
//#endregion
export { inject };