diff --git a/eslint.config.ts b/eslint.config.ts index 91a170cda..75de2a169 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -118,6 +118,16 @@ const websiteEslintConfig: Config = defineConfig({ "max-params": ["error", {max: 5}], // we allow a maximum of 5 parameters per function. "max-lines": ["error", {max: 1000}], // we allow a maximum of 1000 lines per file. "max-statements": "off", // we don't impose a max statements limit on functions. + "prefer-destructuring": "off", // Destructuring is a style preference, not always better. + "arrow-body-style": "off", // We allow both styles for arrow functions. + "prefer-template": "off", // String concatenation is acceptable. + "no-continue": "off", // Continue statements are acceptable in loops. + "no-undef-init": "off", // Explicit undefined initialization is acceptable for clarity. + "no-use-before-define": "off", // Function hoisting allows usage before definition. + "complexity": ["warn", {max: 25}], // Warn at 25 complexity, was 20. + "no-unmodified-loop-condition": "warn", // Warn about unmodified loop conditions. + "radix": "warn", // Warn about missing radix in parseInt. + "no-duplicate-imports": "warn", // Warn about duplicate imports. "react/jsx-indent": "off", // We format via Prettier. "react/jsx-newline": "off", // We use Prettier for formatting. @@ -134,6 +144,15 @@ const websiteEslintConfig: Config = defineConfig({ "react/jsx-closing-bracket-location": "off", // We use Prettier for formatting. "react/function-component-definition": "off", // Sometimes we use arrow syntax. "react/jsx-filename-extension": [2, {extensions: [".tsx", ".ts"]}], + "react/jsx-props-no-spreading": "off", // We allow props spreading for composition patterns. + "react/jsx-no-bind": "off", // Event handlers are stable references in modern React. + "react/no-array-index-key": "off", // Used intentionally in stable lists. + "react/prefer-read-only-props": "off", // TypeScript Readonly is sufficient. + "react/require-default-props": "off", // TypeScript optional props handle defaults. + "react/no-unescaped-entities": "off", // Apostrophes and quotes are fine in JSX text content. + "react/no-unstable-nested-components": "warn", // Warn about nested component definitions. + "react/button-has-type": "warn", // Warn about missing button type. + "react/jsx-no-leaked-render": "warn", // Warn about potential leaked renders, but don't block. "react-hooks-extra/no-direct-set-state-in-use-effect": "off", // We allow direct setState calls in useEffect. @@ -141,6 +160,14 @@ const websiteEslintConfig: Config = defineConfig({ "n/no-unsupported-features/node-builtins": "off", // We use Node.js v24+ built-ins. "sonarjs/todo-tag": "off", // We allow todos tags. + "sonarjs/prefer-read-only-props": "off", // TypeScript Readonly is sufficient. + "sonarjs/no-nested-template-literals": "off", // Template literals improve readability over concatenation. + "sonarjs/no-nested-conditional": "off", // Nested ternaries are acceptable when properly formatted. + "sonarjs/cognitive-complexity": "off", // Complexity metrics are guidelines, not hard rules. + "sonarjs/no-unused-vars": "off", // TypeScript handles unused variables. + "sonarjs/different-types-comparison": "warn", // Warn but don't block on type mismatches. + "sonarjs/pseudo-random": "warn", // Math.random is acceptable for non-cryptographic use. + "sonarjs/no-redundant-assignments": "warn", // Warn about redundant assignments. "unicorn/no-null": "off", // We allow null values. "unicorn/prefer-spread": "off", // We have no preference. @@ -152,6 +179,18 @@ const websiteEslintConfig: Config = defineConfig({ "unicorn/no-typeof-undefined": "off", // We allow typeof undefined comparison checks. "unicorn/prevent-abbreviations": "off", // this rule is biased. "unicorn/no-abusive-eslint-disable": "warn", // Warn about abusive eslint-disable usage. + "unicorn/no-array-reduce": "off", // Array.reduce is idiomatic JavaScript. + "unicorn/no-array-sort": "off", // Array.sort is widely used and understood. + "unicorn/explicit-length-check": "off", // Implicit boolean coercion is idiomatic. + "unicorn/numeric-separators-style": "off", // Numeric separators are a style preference. + "unicorn/no-nested-ternary": "off", // Nested ternaries are acceptable when formatted. + "unicorn/prefer-string-slice": "off", // Both substring and slice are acceptable. + "unicorn/no-negated-condition": "off", // Negated conditions can be clearer in some contexts. + "unicorn/consistent-function-scoping": "off", // Inner functions can improve readability. + "unicorn/consistent-destructuring": "off", // Destructuring is a style preference. + "unicorn/no-useless-switch-case": "off", // Explicit cases improve clarity. + "unicorn/prefer-add-event-listener": "warn", // Warn about inline event handlers. + "unicorn/no-zero-fractions": "warn", // Warn about zero fractions in numbers. }, settings: { react: { diff --git a/packages/components/src/components/ui/calendar.tsx b/packages/components/src/components/ui/calendar.tsx index 409588fca..942a1f8c0 100644 --- a/packages/components/src/components/ui/calendar.tsx +++ b/packages/components/src/components/ui/calendar.tsx @@ -1,6 +1,6 @@ "use client"; -/* eslint-disable */ +/* eslint-disable react/jsx-props-no-spreading, react/no-array-index-key, unicorn/no-null -- shadcn/ui component pattern requirements */ import {ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon} from "lucide-react"; import * as React from "react"; diff --git a/packages/components/src/components/ui/carousel.tsx b/packages/components/src/components/ui/carousel.tsx index 00e1c7519..a665a2d4b 100644 --- a/packages/components/src/components/ui/carousel.tsx +++ b/packages/components/src/components/ui/carousel.tsx @@ -52,6 +52,7 @@ const Carousel = React.forwardRef { if (!api) { return; diff --git a/packages/components/src/components/ui/chart.tsx b/packages/components/src/components/ui/chart.tsx index ea80f4b31..ceb87a486 100644 --- a/packages/components/src/components/ui/chart.tsx +++ b/packages/components/src/components/ui/chart.tsx @@ -1,6 +1,6 @@ "use client"; -/* eslint-disable */ +/* eslint-disable react/jsx-props-no-spreading, unicorn/no-null -- shadcn/ui component pattern requirements */ import {cn} from "@/lib/utilities"; import * as React from "react"; diff --git a/packages/components/src/components/ui/dot-background.tsx b/packages/components/src/components/ui/dot-background.tsx index de721d371..13950bfd8 100644 --- a/packages/components/src/components/ui/dot-background.tsx +++ b/packages/components/src/components/ui/dot-background.tsx @@ -69,6 +69,7 @@ export function DotBackground({ const containerRef = useRef(null); const [dimensions, setDimensions] = useState({width: 0, height: 0}); + // eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect -- setState is called within a function, not directly in useEffect useEffect(() => { const updateDimensions = () => { if (containerRef.current) { diff --git a/packages/components/src/components/ui/dropdrawer.tsx b/packages/components/src/components/ui/dropdrawer.tsx index aea0fbe6d..340954074 100644 --- a/packages/components/src/components/ui/dropdrawer.tsx +++ b/packages/components/src/components/ui/dropdrawer.tsx @@ -1,6 +1,6 @@ "use client"; -/* eslint-disable */ +/* eslint-disable react/jsx-props-no-spreading, unicorn/no-null -- shadcn/ui component pattern requirements */ import {ChevronLeftIcon, ChevronRightIcon} from "lucide-react"; import {AnimatePresence, motion, Transition} from "motion/react"; diff --git a/packages/components/src/components/ui/fireworks-background.tsx b/packages/components/src/components/ui/fireworks-background.tsx index a6292d4b1..be2d26a39 100644 --- a/packages/components/src/components/ui/fireworks-background.tsx +++ b/packages/components/src/components/ui/fireworks-background.tsx @@ -252,6 +252,7 @@ const FireworksBackground = React.forwardRef( clipPath.ellipse(disc.x, disc.y, disc.w, disc.h, 0, 0, Math.PI * 2); clipPath.rect(disc.x - disc.w, 0, disc.w * 2, disc.y); stateRef.current.clip.path = clipPath; - }, [tweenDisc]); + }, [tweenDisc, numberOfDiscs]); const setLines = React.useCallback(() => { const {width, height} = stateRef.current.rect; @@ -150,7 +150,7 @@ const HoleBackground = React.forwardRef( ctx.restore(); }); stateRef.current.linesCanvas = offCanvas; - }, [strokeColor]); + }, [strokeColor, numberOfLines]); const initParticle = React.useCallback((start: boolean = false) => { const sx = stateRef.current.particleArea.sx + stateRef.current.particleArea.sw * Math.random(); @@ -169,7 +169,7 @@ const HoleBackground = React.forwardRef( r, c: `rgba(${particleRGBColor[0]}, ${particleRGBColor[1]}, ${particleRGBColor[2]}, ${Math.random()})`, }; - }, []); + }, [particleRGBColor]); const setParticles = React.useCallback(() => { const {width, height} = stateRef.current.rect; diff --git a/packages/components/src/components/ui/input-group.tsx b/packages/components/src/components/ui/input-group.tsx index 98578c23d..056f2b1cd 100644 --- a/packages/components/src/components/ui/input-group.tsx +++ b/packages/components/src/components/ui/input-group.tsx @@ -1,6 +1,6 @@ "use client"; -/* eslint-disable */ +/* eslint-disable react/jsx-props-no-spreading, unicorn/no-null -- shadcn/ui component pattern requirements */ import {cva, type VariantProps} from "class-variance-authority"; import * as React from "react"; diff --git a/packages/components/src/components/ui/scratcher.tsx b/packages/components/src/components/ui/scratcher.tsx index 4e6c3258f..4788793df 100644 --- a/packages/components/src/components/ui/scratcher.tsx +++ b/packages/components/src/components/ui/scratcher.tsx @@ -86,7 +86,7 @@ export const Scratcher: React.FC = ({ document.removeEventListener("touchend", handleDocumentTouchEnd); document.removeEventListener("touchcancel", handleDocumentTouchEnd); }; - }, [isScratching]); + }, [isScratching, checkCompletion]); const handleMouseDown = () => setIsScratching(true); diff --git a/packages/components/src/components/ui/sidebar.tsx b/packages/components/src/components/ui/sidebar.tsx index 204944968..a0f23536c 100644 --- a/packages/components/src/components/ui/sidebar.tsx +++ b/packages/components/src/components/ui/sidebar.tsx @@ -1,6 +1,6 @@ "use client"; -/* eslint-disable */ +/* eslint-disable react/jsx-props-no-spreading, unicorn/no-null -- shadcn/ui component pattern requirements */ import {Slot} from "@radix-ui/react-slot"; import {VariantProps, cva} from "class-variance-authority"; diff --git a/packages/components/src/components/ui/table.tsx b/packages/components/src/components/ui/table.tsx index 0db8ccc15..e5d8df836 100644 --- a/packages/components/src/components/ui/table.tsx +++ b/packages/components/src/components/ui/table.tsx @@ -1,6 +1,6 @@ "use client"; -/* eslint-disable */ +/* eslint-disable react/jsx-props-no-spreading, unicorn/no-null -- shadcn/ui component pattern requirements */ import * as React from "react"; diff --git a/packages/components/src/components/ui/typewriter.tsx b/packages/components/src/components/ui/typewriter.tsx index 0a9ff558c..7ea3e3480 100644 --- a/packages/components/src/components/ui/typewriter.tsx +++ b/packages/components/src/components/ui/typewriter.tsx @@ -42,7 +42,7 @@ export const TypewriterText = ({ }, ); } - }, [isInView]); + }, [isInView, animate]); const renderWords = () => { return ( diff --git a/sites/arolariu.ro/src/app/providers.tsx b/sites/arolariu.ro/src/app/providers.tsx index 985f1711c..20cc707b5 100644 --- a/sites/arolariu.ro/src/app/providers.tsx +++ b/sites/arolariu.ro/src/app/providers.tsx @@ -119,6 +119,7 @@ type Props = { * @see RFC 1003 - Internationalization System documentation */ export default function ContextProviders({locale, children}: Readonly): React.JSX.Element { + // eslint-disable-next-line unicorn/prefer-module -- Dynamic require for locale-based JSON loading, can't use static import const messages = locale === "ro" ? require("../../messages/ro.json") : require("../../messages/en.json"); return ( ; } diff --git a/sites/arolariu.ro/src/instrumentation.server.ts b/sites/arolariu.ro/src/instrumentation.server.ts index d547e2e56..30c3ce6cf 100644 --- a/sites/arolariu.ro/src/instrumentation.server.ts +++ b/sites/arolariu.ro/src/instrumentation.server.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line n/no-extraneous-import -- server-only is a Next.js build-time marker, not a runtime import import "server-only"; /** diff --git a/sites/arolariu.ro/src/lib/utils.client.ts b/sites/arolariu.ro/src/lib/utils.client.ts index 8cc1940da..5ecdce83e 100644 --- a/sites/arolariu.ro/src/lib/utils.client.ts +++ b/sites/arolariu.ro/src/lib/utils.client.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line n/no-extraneous-import -- client-only is a Next.js build-time marker, not a runtime import import "client-only"; import type {BrowserInformation} from "@/types"; diff --git a/sites/arolariu.ro/src/lib/utils.generic.ts b/sites/arolariu.ro/src/lib/utils.generic.ts index 0b4d74a08..c6e318b8e 100644 --- a/sites/arolariu.ro/src/lib/utils.generic.ts +++ b/sites/arolariu.ro/src/lib/utils.generic.ts @@ -102,7 +102,7 @@ export const EMPTY_GUID = "00000000-0000-0000-0000-000000000000"; * * @see {@link https://tools.ietf.org/html/rfc4122#section-4.4} - RFC 4122 Section 4.4 */ -const UUID_V4_REGEX = /^[\da-f]{8}-[\da-f]{4}-4[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/i; +const UUID_V4_REGEX = /^[\da-f]{8}-[\da-f]{4}-4[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/iu; /** * Asserts that a given string is a valid UUID v4 format or a special sentinel GUID. @@ -281,7 +281,7 @@ export interface FormatDateOptions extends Partial { * ``` */ export function formatDate(possibleDate: string | Date, options: FormatDateOptions): string { - let date: Date | undefined; + let date: Date | undefined = undefined; if (typeof possibleDate === "string") { date = new Date(possibleDate); diff --git a/sites/arolariu.ro/src/lib/utils.server.ts b/sites/arolariu.ro/src/lib/utils.server.ts index b6bb76e09..746eb112a 100644 --- a/sites/arolariu.ro/src/lib/utils.server.ts +++ b/sites/arolariu.ro/src/lib/utils.server.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line n/no-extraneous-import -- server-only is a Next.js build-time marker, not a runtime import import "server-only"; import {addSpanEvent, logWithTrace, recordSpanError, withSpan} from "@/instrumentation.server"; diff --git a/sites/arolariu.ro/src/stores/invoicesStore.tsx b/sites/arolariu.ro/src/stores/invoicesStore.tsx index 3fbc20cc9..3f752afa3 100644 --- a/sites/arolariu.ro/src/stores/invoicesStore.tsx +++ b/sites/arolariu.ro/src/stores/invoicesStore.tsx @@ -138,7 +138,7 @@ const createInvoicesSlice = ( upsertInvoice: (invoice) => set((state) => { const existingIndex = state.invoices.findIndex((inv) => inv.id === invoice.id); - if (existingIndex >= 0) { + if (existingIndex !== -1) { // Update existing invoice const updatedInvoices = [...state.invoices]; updatedInvoices[existingIndex] = invoice; diff --git a/sites/arolariu.ro/src/stores/merchantsStore.tsx b/sites/arolariu.ro/src/stores/merchantsStore.tsx index d90b7b741..6afe6e699 100644 --- a/sites/arolariu.ro/src/stores/merchantsStore.tsx +++ b/sites/arolariu.ro/src/stores/merchantsStore.tsx @@ -124,7 +124,7 @@ const createMerchantsSlice = ( upsertMerchant: (merchant) => set((state) => { const existingIndex = state.merchants.findIndex((m) => m.id === merchant.id); - if (existingIndex >= 0) { + if (existingIndex !== -1) { // Update existing merchant const updatedMerchants = [...state.merchants]; updatedMerchants[existingIndex] = merchant; diff --git a/sites/arolariu.ro/src/stores/storage/indexedDBStorage.ts b/sites/arolariu.ro/src/stores/storage/indexedDBStorage.ts index b6c32782b..44e7e559a 100644 --- a/sites/arolariu.ro/src/stores/storage/indexedDBStorage.ts +++ b/sites/arolariu.ro/src/stores/storage/indexedDBStorage.ts @@ -171,9 +171,7 @@ export function createIndexedDBStorage( if (!db) return null; const table = getTable(db, tableName); - const entities = await db.transaction("r", table, async () => { - return table.toArray(); - }); + const entities = await db.transaction("r", table, async () => table.toArray()); if (entities.length === 0) { return null; @@ -275,9 +273,7 @@ export function createSharedStorage(options?: CreateSharedStorageOptions): Pe /* v8 ignore next - Defensive guard for IndexedDB unavailability */ if (!db) return null; - const item = await db.transaction("r", db.shared, async () => { - return db.shared.get(keyPrefix + name); - }); + const item = await db.transaction("r", db.shared, async () => db.shared.get(keyPrefix + name)); if (!item) return null; return JSON.parse(item.value) as StorageValue; diff --git a/sites/cv.arolariu.ro/src/service-worker.ts b/sites/cv.arolariu.ro/src/service-worker.ts index bff071c3b..349a5cc51 100644 --- a/sites/cv.arolariu.ro/src/service-worker.ts +++ b/sites/cv.arolariu.ro/src/service-worker.ts @@ -133,17 +133,14 @@ self.addEventListener("fetch", (event: FetchEvent) => { // Fetch in background to update cache (stale-while-revalidate) event.waitUntil( fetch(request) - .then((networkResponse) => { + .then(async (networkResponse) => { if (networkResponse.ok) { - caches - .open(CACHE_NAME) - .then((cache) => { - cache.put(request, networkResponse); - return networkResponse; - }) - .catch(() => { - // Caching failed - }); + try { + const cache = await caches.open(CACHE_NAME); + await cache.put(request, networkResponse); + } catch { + // Caching failed + } } return networkResponse;