feat: Enable essential ESLint rules and refactor frontend lint config (#4923)

* Install eslint-plugin-unsued-imports

* Refactor eslint to a sane working config

* enable jsx-no-target-blank | disable no-escaped-entities

* disable react/display-name react-hooks/immutability and react-hooks/preserve-manual-memoization

* chore: remove unused imports (#4925)

remove unused imports

* fix: resolve react-hooks ESLint errors (#4929)

fix react-hooks linting errors

* fix: add rel=noreferrer to target=_blank links (#4928)

Fix no target blank errors

* fix: resolve undefined variable errors in frontend (#4927)

* delete unused file src/components/DataConnectorOption/index.jsx

* fix undefined errors

* chore: Remove unused variables in frontend (#4924)

Remove unused variables

---------

Co-authored-by: Timothy Carambat <rambat1010@gmail.com>
This commit is contained in:
Marcello Fitton 2026-01-29 17:01:39 -08:00 committed by GitHub
parent 17a399d43c
commit c734566a67
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 117 additions and 117 deletions

View File

@ -4,43 +4,77 @@ import pluginReact from "eslint-plugin-react"
import pluginReactHooks from "eslint-plugin-react-hooks"
import pluginPrettier from "eslint-plugin-prettier"
import configPrettier from "eslint-config-prettier"
import { defineConfig } from "eslint/config"
import unusedImports from "eslint-plugin-unused-imports"
export default defineConfig([
export default [
{
ignores: ["**/*.min.js", "src/media/**/*"]
},
// Base JS recommended rules
js.configs.recommended,
// Your React/JSX files
{
files: ["src/**/*.{js,jsx}"],
plugins: { js },
extends: ["js/recommended"],
languageOptions: { globals: globals.browser }
},
{
files: ["src/**/*.{js,jsx}"],
...pluginReact.configs.flat.recommended,
files: ["src/**/*.{js,jsx,mjs,cjs}"],
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
parserOptions: {
ecmaFeatures: { jsx: true }
},
globals: globals.browser
},
plugins: {
react: pluginReact,
"react-hooks": pluginReactHooks,
"unused-imports": unusedImports,
prettier: pluginPrettier
},
settings: {
react: {
version: "detect"
}
react: { version: "detect" }
},
rules: {
// React recommended rules (inline, since we're not "extending" in flat config)
...pluginReact.configs.flat.recommended.rules,
// If you want hooks rules, add these (recommended)
...pluginReactHooks.configs.recommended.rules,
// Prettier: disable conflicting stylistic rules + optionally enforce formatting
...configPrettier.rules,
"prettier/prettier": "error",
// Your overrides
"react/react-in-jsx-scope": "off",
"react-hooks/exhaustive-deps": "off",
"react/prop-types": "off",
"react-hooks/set-state-in-effect": "off",
"react/jsx-no-target-blank": "error",
"react/no-unescaped-entities": "off",
"react/display-name": "off",
"react-hooks/immutability": "off",
"react-hooks/preserve-manual-memoization": "off",
"no-extra-boolean-cast": "off",
"no-prototype-builtins": "off",
"no-unused-vars": "off",
"no-empty": "off",
"no-useless-escape": "off",
"no-undef": "off",
"no-undef": "error",
"no-unsafe-optional-chaining": "off",
"no-constant-binary-expression": "off"
"no-constant-binary-expression": "off",
// Unused cleanup
"no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{
vars: "all",
varsIgnorePattern: "^_",
args: "after-used",
argsIgnorePattern: "^_"
}
]
}
}
])
]

View File

@ -69,6 +69,7 @@
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.3",
"eslint-plugin-unused-imports": "^4.3.0",
"flow-bin": "^0.217.0",
"flow-remove-types": "^2.217.1",
"globals": "^16.5.0",

View File

@ -4,7 +4,7 @@ import { userFromStorage } from "@/utils/request";
import renderMarkdown from "@/utils/chat/markdown";
import DOMPurify from "@/utils/chat/purify";
export default function ChatBubble({ message, type, popMsg }) {
export default function ChatBubble({ message, type }) {
const isUser = type === "user";
return (

View File

@ -1,25 +0,0 @@
export default function DataConnectorOption({ slug }) {
if (!DATA_CONNECTORS.hasOwnProperty(slug)) return null;
const { path, image, name, description, link } = DATA_CONNECTORS[slug];
return (
<a href={path}>
<label className="transition-all duration-300 inline-flex flex-col h-full w-60 cursor-pointer items-start justify-between rounded-2xl bg-preference-gradient border-2 border-transparent shadow-md px-5 py-4 text-white hover:bg-selected-preference-gradient hover:border-white/60 peer-checked:border-white peer-checked:border-opacity-90 peer-checked:bg-selected-preference-gradient">
<div className="flex items-center">
<img src={image} alt={name} className="h-10 w-10 rounded" />
<div className="ml-4 text-sm font-semibold">{name}</div>
</div>
<div className="mt-2 text-xs font-base text-white tracking-wide">
{description}
</div>
<a
href={link}
target="_blank"
className="mt-2 text-xs text-white font-medium underline"
>
{link}
</a>
</label>
</a>
);
}

View File

@ -5,10 +5,7 @@ import PreLoader from "@/components/Preloader";
import { DPAIS_COMMON_URLS } from "@/utils/constants";
import useProviderEndpointAutoDiscovery from "@/hooks/useProviderEndpointAutoDiscovery";
export default function DellProAIStudioOptions({
settings,
showAlert = false,
}) {
export default function DellProAIStudioOptions({ settings }) {
const {
autoDetecting: loading,
basePath,

View File

@ -2,6 +2,6 @@
* This component is used to select, start, and manage NVIDIA NIM
* containers and images via docker management tools.
*/
export default function ManagedNvidiaNimOptions({ settings }) {
export default function ManagedNvidiaNimOptions({ settings: _settings }) {
return null;
}

View File

@ -256,7 +256,7 @@ const WatchForChanges = memo(({ workspace, docPath, item }) => {
);
});
const RemoveItemFromWorkspace = ({ item, onClick }) => {
const RemoveItemFromWorkspace = ({ item: _item, onClick }) => {
return (
<div>
<ArrowUUpLeft

View File

@ -1,17 +1,5 @@
export default function CustomCell({ ...props }) {
const {
root,
depth,
x,
y,
width,
height,
index,
payload,
colors,
rank,
name,
} = props;
const { root, depth, x, y, width, height, index, colors, name } = props;
return (
<g>
<rect

View File

@ -42,12 +42,13 @@ function combineLikeSources(sources) {
}
export default function Citations({ sources = [] }) {
if (sources.length === 0) return null;
const [open, setOpen] = useState(false);
const [selectedSource, setSelectedSource] = useState(null);
const { t } = useTranslation();
const { textSizeClass } = useTextSize();
if (sources.length === 0) return null;
return (
<div className="flex flex-col mt-4 justify-left">
<button

View File

@ -128,8 +128,8 @@ function CopyMessage({ message }) {
}
function RegenerateMessage({ regenerateMessage, chatId }) {
if (!chatId) return null;
const { t } = useTranslation();
if (!chatId) return null;
return (
<div className="mt-3 relative">
<button

View File

@ -1,3 +1,4 @@
/* eslint-disable react-hooks/refs */
import { memo, useRef, useEffect } from "react";
import { Warning } from "@phosphor-icons/react";
import UserIcon from "../../../../UserIcon";
@ -17,7 +18,6 @@ const PromptReply = ({
error,
workspace,
sources = [],
closed = true,
}) => {
const assistantBackgroundColor = "bg-theme-bg-chat";

View File

@ -38,7 +38,7 @@ export function validatedModelSelection(model) {
// If the model is in the dropdown, return the model as is
return model;
} catch (error) {
} catch {
return null; // If the dropdown was empty or something else went wrong, return null to abort the save
}
}

View File

@ -1,7 +1,7 @@
import { useEffect, useCallback, useRef } from "react";
import { Microphone } from "@phosphor-icons/react";
import { Tooltip } from "react-tooltip";
import _regeneratorRuntime from "regenerator-runtime";
import "regenerator-runtime"; //required polyfill for speech recognition;
import SpeechRecognition, {
useSpeechRecognition,
} from "react-speech-recognition";

View File

@ -247,7 +247,7 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
setLoadingResponse(true);
try {
handleSocketResponse(socket, event, setChatHistory);
} catch (e) {
} catch {
console.error("Failed to parse data");
window.dispatchEvent(new CustomEvent(AGENT_SESSION_END));
socket.close();

View File

@ -104,28 +104,28 @@ export default function ModelTable({
);
}
function DeviceTypeTagWrapper({ text, bgClass, textClass }) {
return (
<div
className={
bgClass + " px-1.5 py-1 rounded-full flex items-center gap-x-1 w-fit"
}
>
<Cpu size={14} weight="bold" className={textClass} />
<p className={textClass + " text-xs"}>{text}</p>
</div>
);
}
/**
* @param {{deviceType: ModelDefinition["deviceType"]}} deviceType
* @returns {React.ReactNode}
*/
function DeviceTypeTag({ deviceType }) {
const Wrapper = ({ text, bgClass, textClass }) => {
return (
<div
className={
bgClass + " px-1.5 py-1 rounded-full flex items-center gap-x-1 w-fit"
}
>
<Cpu size={14} weight="bold" className={textClass} />
<p className={textClass + " text-xs"}>{text}</p>
</div>
);
};
switch (deviceType?.toLowerCase()) {
case "cpu":
return (
<Wrapper
<DeviceTypeTagWrapper
text="CPU"
bgClass="bg-zinc-800 light:bg-zinc-200"
textClass="text-theme-text-primary"
@ -133,7 +133,7 @@ function DeviceTypeTag({ deviceType }) {
);
case "gpu":
return (
<Wrapper
<DeviceTypeTagWrapper
text="GPU"
bgClass="bg-green-800 light:bg-green-200"
textClass="text-theme-text-primary"
@ -141,7 +141,7 @@ function DeviceTypeTag({ deviceType }) {
);
case "npu":
return (
<Wrapper
<DeviceTypeTagWrapper
text="NPU"
bgClass="bg-indigo-800 light:bg-indigo-200"
textClass="text-theme-text-primary"
@ -149,7 +149,7 @@ function DeviceTypeTag({ deviceType }) {
);
default:
return (
<Wrapper
<DeviceTypeTagWrapper
text="CPU"
bgClass="bg-zinc-800 light:bg-zinc-200"
textClass="text-theme-text-primary"

View File

@ -1,3 +1,4 @@
/* eslint-disable react-hooks/static-components */
// https://lobehub.com/icons for all the icons
import OpenAI from "@lobehub/icons/es/OpenAI/components/Mono";
import Anthropic from "@lobehub/icons/es/Anthropic/components/Mono";

View File

@ -17,7 +17,7 @@ export default function useProviderEndpointAutoDiscovery({
const [autoDetectAttempted, setAutoDetectAttempted] = useState(false);
const [showAdvancedControls, setShowAdvancedControls] = useState(true);
async function autoDetect(isInitialAttempt = false) {
async function autoDetect() {
setLoading(true);
setAutoDetectAttempted(true);
const possibleEndpoints = [];

View File

@ -1,3 +1,4 @@
/* global process */
// This script is used to normalize the translations files to ensure they are all the same.
// This will take the en file and compare it to all other files and ensure they are all the same.
// If a non-en file is missing a key, it will be added to the file and set to null
@ -75,7 +76,7 @@ function compareStructures(lang, a, b, subdir = null) {
}
}
function normalizeTranslations(lang, source, target, subdir = null) {
function normalizeTranslations(lang, source, target, _subdir = null) {
// Handle primitives - if target exists, keep it, otherwise set null
if (!source || typeof source !== "object") {
return target ?? null;

View File

@ -1,3 +1,4 @@
/* global process */
import { resources } from "./resources.js";
const languageNames = new Intl.DisplayNames(Object.keys(resources), {
type: "language",

View File

@ -11,7 +11,7 @@ import SimpleSSOPassthrough from "@/pages/Login/SSO/simple";
import OnboardingFlow from "@/pages/OnboardingFlow";
import "@/index.css";
const isDev = process.env.NODE_ENV !== "production";
const isDev = import.meta.env.DEV;
const REACTWRAP = isDev ? React.Fragment : React.StrictMode;
const router = createBrowserRouter([

View File

@ -58,7 +58,7 @@ const AgentFlows = {
headers: baseHeaders(),
})
.then((res) => {
if (!res.ok) throw new Error(response.error || "Failed to get flow");
if (!res.ok) throw new Error(res.error || "Failed to get flow");
return res;
})
.then((res) => res.json())
@ -107,7 +107,7 @@ const AgentFlows = {
headers: baseHeaders(),
})
.then((res) => {
if (!res.ok) throw new Error(response.error || "Failed to delete flow");
if (!res.ok) throw new Error(res.error || "Failed to delete flow");
return res;
})
.then((res) => res.json())

View File

@ -457,12 +457,11 @@ const System = {
throw new Error("Failed to fetch pfp.");
})
.then((blob) => (blob ? URL.createObjectURL(blob) : null))
.catch((e) => {
// console.log(e);
.catch(() => {
return null;
});
},
removePfp: async function (id) {
removePfp: async function () {
return await fetch(`${API_BASE}/system/remove-pfp`, {
method: "DELETE",
headers: baseHeaders(),

View File

@ -348,7 +348,7 @@ const Workspace = {
throw new Error("Failed to fetch TTS.");
})
.then((blob) => (blob ? URL.createObjectURL(blob) : null))
.catch((e) => {
.catch(() => {
return null;
});
},
@ -379,8 +379,7 @@ const Workspace = {
throw new Error("Failed to fetch pfp.");
})
.then((blob) => (blob ? URL.createObjectURL(blob) : null))
.catch((e) => {
// console.log(e);
.catch(() => {
return null;
});
},

View File

@ -14,7 +14,7 @@ const WorkspaceThread = {
}
)
.then((res) => res.json())
.catch((e) => {
.catch(() => {
return { threads: [] };
});

View File

@ -93,9 +93,9 @@ export default function HeaderMenu({
<div className="absolute top-full left-0 mt-1 w-full min-w-[200px] max-w-[350px] bg-theme-settings-input-bg border border-white/10 rounded-md shadow-lg z-50 animate-fadeUpIn">
{availableFlows
.filter((flow) => flow.uuid !== flowId)
.map((flow) => (
.map((flow, index) => (
<button
key={flow?.uuid || Math.random()}
key={flow?.uuid || `flow-${index}`}
onClick={() => {
navigate(paths.agents.editAgent(flow.uuid));
setShowDropdown(false);

View File

@ -48,9 +48,7 @@ export default function AgentBuilder() {
const [blocks, setBlocks] = useState(DEFAULT_BLOCKS);
const [selectedBlock, setSelectedBlock] = useState("start");
const [showBlockMenu, setShowBlockMenu] = useState(false);
const [showLoadMenu, setShowLoadMenu] = useState(false);
const [availableFlows, setAvailableFlows] = useState([]);
const [selectedFlowForDetails, setSelectedFlowForDetails] = useState(null);
const nameRef = useRef(null);
const descriptionRef = useRef(null);
const [showPublishModal, setShowPublishModal] = useState(false);
@ -122,7 +120,6 @@ export default function AgentBuilder() {
setActive(flow.config.active ?? true);
setCurrentFlowUuid(flow.uuid);
setBlocks(flowBlocks);
setShowLoadMenu(false);
} catch (error) {
console.error(error);
showToast("Failed to load flow", "error", { clear: true });

View File

@ -1,3 +1,4 @@
/* eslint-disable react-hooks/refs */
import React, { useRef, useState } from "react";
import { Plus, X, CaretDown } from "@phosphor-icons/react";

View File

@ -1,3 +1,4 @@
/* eslint-disable react-hooks/refs */
import React, { forwardRef } from "react";
const FlowInfoNode = forwardRef(({ config, onConfigChange }, refs) => {

View File

@ -14,6 +14,7 @@ export default function AgentFlowsList({
href="https://docs.anythingllm.com/agent-flows/getting-started"
target="_blank"
className="text-theme-text-secondary underline hover:text-cta-button"
rel="noreferrer"
>
Learn more about Agent Flows.
</a>

View File

@ -1,4 +1,4 @@
export function DefaultBadge({ title }) {
export function DefaultBadge({ title: _title }) {
return (
<>
<span

View File

@ -16,6 +16,7 @@ export default function ImportedSkillList({
href="https://docs.anythingllm.com/agent/custom/developer-guide"
target="_blank"
className="text-theme-text-secondary underline hover:text-cta-button"
rel="noreferrer"
>
AnythingLLM Agent Docs
</a>

View File

@ -10,7 +10,6 @@ import { Tooltip } from "react-tooltip";
export default function AgentSQLConnectorSelection({
skill,
settings, // unused.
toggleSkill,
enabled = false,
setHasChanges,

View File

@ -62,6 +62,7 @@ export default function LiveSyncToggle({ enabled = false, onToggle }) {
href="https://docs.anythingllm.com/beta-preview/active-features/live-document-sync"
target="_blank"
className="text-sm text-blue-400 light:text-blue-500 hover:underline flex items-center gap-x-1"
rel="noreferrer"
>
<ArrowSquareOut size={14} />
<span>Feature Documentation and Warnings</span>

View File

@ -1,5 +1,4 @@
import LiveSyncToggle from "./Features/LiveSync/toggle";
import paths from "@/utils/paths";
export const configurableFeatures = {
experimental_live_file_sync: {

View File

@ -3,7 +3,7 @@ import Admin from "@/models/admin";
import paths from "@/utils/paths";
import { LinkSimple, Trash } from "@phosphor-icons/react";
export default function WorkspaceRow({ workspace, users }) {
export default function WorkspaceRow({ workspace, users: _users }) {
const rowRef = useRef(null);
const handleDelete = async () => {
if (

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState, useRef } from "react";
import React, { useEffect, useState } from "react";
import { isMobile } from "react-device-detect";
import Sidebar from "@/components/SettingsSidebar";
import System from "@/models/system";

View File

@ -12,7 +12,7 @@ const md = new MarkdownIt({
if (lang && hljs.getLanguage(lang)) {
try {
return hljs.highlight(str, { language: lang }).value;
} catch (__) {}
} catch {}
}
return ""; // use external default escaping
},

View File

@ -2,12 +2,10 @@ import { useState } from "react";
import Sidebar from "@/components/SettingsSidebar";
import { isMobile } from "react-device-detect";
import { CaretLeft, CaretRight } from "@phosphor-icons/react";
import { useTranslation } from "react-i18next";
import EmbedConfigsView from "./EmbedConfigs";
import EmbedChatsView from "./EmbedChats";
export default function ChatEmbedWidgets() {
const { t } = useTranslation();
const [selectedView, setSelectedView] = useState("configs");
const [showViewModal, setShowViewModal] = useState(false);

View File

@ -12,7 +12,7 @@ const md = new MarkdownIt({
if (lang && hljs.getLanguage(lang)) {
try {
return hljs.highlight(str, { language: lang }).value;
} catch (__) {}
} catch {}
}
return ""; // use external default escaping
},

View File

@ -17,8 +17,6 @@ export default function SlashCommand({ item, setStep }) {
} catch (e) {
console.error(e);
showToast(`Failed to import slash command. ${e.message}`, "error");
} finally {
setLoading(false);
}
}

View File

@ -63,7 +63,7 @@ export default function EmbeddingTextSplitterPreference() {
setHasChanges(false);
closeModal();
showToast("Text chunking strategy settings saved.", "success");
} catch (error) {
} catch {
showToast("Failed to save text chunking strategy settings.", "error");
} finally {
setSaving(false);

View File

@ -101,6 +101,7 @@ function TelemetryLogs({ settings }) {
href="https://github.com/search?q=repo%3AMintplex-Labs%2Fanything-llm%20.sendTelemetry(&type=code"
className="underline text-blue-400"
target="_blank"
rel="noreferrer"
>
GitHub here
</a>
@ -116,6 +117,7 @@ function TelemetryLogs({ settings }) {
href="mailto:team@mintplexlabs.com"
className="underline text-blue-400"
target="_blank"
rel="noreferrer"
>
team@mintplexlabs.com
</a>

View File

@ -38,7 +38,7 @@ const markdown = markdownIt({
hljs.highlight(code, { language: lang, ignoreIllegals: true }).value +
"</pre></div>"
);
} catch (__) {}
} catch {}
}
return (

View File

@ -33,7 +33,7 @@ function isValidDelim(state, pos) {
}
function math_inline(state, silent) {
var start, match, token, res, pos, esc_count;
var start, match, token, res, pos;
// Only process $ and \( delimiters for inline math
if (

View File

@ -13,7 +13,7 @@ export function formatDateTimeAsMoment(dateString, format = "LLL") {
if (!dateString) return moment().format(format);
try {
return moment(dateString).format(format);
} catch (error) {
} catch {
return moment().format(format);
}
}

View File

@ -2346,6 +2346,11 @@ eslint-plugin-react@^7.37.5:
string.prototype.matchall "^4.0.12"
string.prototype.repeat "^1.0.0"
eslint-plugin-unused-imports@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.3.0.tgz#69ff9c4f83f02f7789808bbbab77636cb742af50"
integrity sha512-ZFBmXMGBYfHttdRtOG9nFFpmUvMtbHSjsKrS20vdWdbfiVYsO3yA2SGYy9i9XmZJDfMGBflZGBCm70SEnFQtOA==
eslint-scope@^8.4.0:
version "8.4.0"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82"