From 8b01806aa068c6684f940a56fe696ce942cd2f23 Mon Sep 17 00:00:00 2001 From: Kris McGinnes Date: Fri, 1 May 2026 19:08:03 -0500 Subject: [PATCH] Enable default oxlint correctness rules --- .oxlintrc.json | 222 +++++------------- eslint.config.mjs | 36 --- package.json | 8 +- .../src/app.test.ts | 4 +- .../graph-explorer-proxy-server/src/app.ts | 5 +- .../src/error-handler.ts | 1 + .../src/components/EmptyState.tsx | 5 +- .../src/components/FileButton.tsx | 4 +- .../Graph/hooks/useAddClickEvents.ts | 18 +- .../Graph/hooks/useManageConfigChanges.ts | 6 +- .../Graph/hooks/useUpdateGraphElements.ts | 2 +- .../components/Graph/useGraphGlobalActions.ts | 2 +- .../src/components/SelectField.tsx | 2 +- .../src/components/SettingsSection.tsx | 5 +- .../src/components/Tabular/TabularRow.tsx | 6 +- .../components/Tabular/filters/TextFilter.tsx | 8 +- .../src/components/Tabular/useTabular.ts | 4 +- .../src/components/Typography.tsx | 10 +- .../src/components/VertexIcon.tsx | 7 +- .../connector/fetchDatabaseRequest.test.ts | 2 +- .../src/connector/fetchDatabaseRequest.ts | 8 +- .../connector/sparql/neighborCounts.test.ts | 1 - .../src/core/StateProvider/configuration.ts | 16 +- .../src/core/StateProvider/localDb.test.ts | 2 +- .../src/hooks/useContextMenuTarget.ts | 6 +- .../graph-explorer/src/hooks/useDeepMemo.ts | 4 +- .../ConnectionDetail/ConnectionDetail.tsx | 2 +- .../CreateConnection/CreateConnection.tsx | 6 +- .../EdgesStyling/SingleEdgeStyling.tsx | 2 +- .../EntitiesFilter/useFiltersConfig.tsx | 12 +- .../src/modules/GraphViewer/useContextMenu.ts | 3 + .../src/modules/Namespaces/UserPrefixes.tsx | 4 +- .../modules/NodesStyling/NodeStyleDialog.tsx | 4 +- .../NodesStyling/SingleNodeStyling.tsx | 2 +- .../modules/SchemaGraph/Sidebar/Details.tsx | 5 +- .../src/routes/Settings/SettingsRoot.tsx | 2 +- .../src/utils/saveConfigurationToFile.ts | 2 +- pnpm-lock.yaml | 110 --------- tsconfig.json | 2 +- 39 files changed, 182 insertions(+), 368 deletions(-) delete mode 100644 eslint.config.mjs diff --git a/.oxlintrc.json b/.oxlintrc.json index b31ae18b8..3d62da9cc 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -1,69 +1,30 @@ { "$schema": "./node_modules/oxlint/configuration_schema.json", - "plugins": ["typescript", "unicorn"], + "plugins": [ + "eslint", + "oxc", + "typescript", + "unicorn", + "import", + "vitest", + "promise" + ], "options": { "typeAware": true }, "categories": { - // Disabled so rules are enabled explicitly below for full control. - // New oxlint correctness rules won't auto-enable on upgrades. - "correctness": "off" + "correctness": "error" }, "env": { "builtin": true }, "ignorePatterns": ["**/*.config.{js,ts,mjs}", "**/vitest.workspace.ts"], "rules": { - "constructor-super": "error", - "for-direction": "error", - "getter-return": "error", - "no-async-promise-executor": "error", - "no-case-declarations": "error", - "no-class-assign": "error", - "no-compare-neg-zero": "error", - "no-cond-assign": "error", - "no-const-assign": "error", - "no-constant-binary-expression": "error", - "no-constant-condition": "error", - "no-control-regex": "error", - "no-debugger": "error", - "no-delete-var": "error", - "no-dupe-class-members": "error", - "no-dupe-else-if": "error", - "no-dupe-keys": "error", - "no-duplicate-case": "error", - "no-empty": "error", - "no-empty-character-class": "error", - "no-empty-pattern": "error", - "no-empty-static-block": "error", - "no-ex-assign": "error", - "no-extra-boolean-cast": "error", - "no-fallthrough": "error", - "no-func-assign": "error", - "no-global-assign": "error", - "no-import-assign": "error", - "no-invalid-regexp": "error", - "no-irregular-whitespace": "error", - "no-loss-of-precision": "error", - "no-misleading-character-class": "error", - "no-new-native-nonconstructor": "error", - "no-nonoctal-decimal-escape": "error", - "no-obj-calls": "error", - "no-prototype-builtins": "error", - "no-redeclare": "error", - "no-regex-spaces": "error", - "no-self-assign": "error", - "no-setter-return": "error", - "no-shadow-restricted-names": "error", - "no-sparse-arrays": "error", - "no-this-before-super": "error", - "no-unassigned-vars": "error", - "no-unsafe-finally": "error", - "no-unsafe-negation": "error", - "no-unsafe-optional-chaining": "error", - "no-unreachable": "error", - "no-unused-labels": "error", - "no-unused-private-class-members": "error", + "vitest/require-mock-type-parameters": "off", + "jest/require-to-throw-message": "off", + "jest/valid-expect": "off", + "typescript/unbound-method": "off", + "typescript/no-floating-promises": "off", "no-unused-vars": [ "error", { @@ -72,106 +33,33 @@ "caughtErrorsIgnorePattern": "^_" } ], - "no-useless-backreference": "error", - "no-useless-catch": "error", - "no-useless-escape": "error", - "no-with": "error", - "preserve-caught-error": "error", - "require-yield": "error", - "use-isnan": "error", - "valid-typeof": "error", - "no-array-constructor": "error", - "no-console": [ + "no-console": ["error", { "allow": ["warn", "error"] }], + "typescript/consistent-type-imports": [ "error", - { - "allow": ["warn", "error"] - } + { "fixStyle": "inline-type-imports" } ], - "no-caller": "error", - "no-eval": "error", - "no-iterator": "error", - "no-useless-rename": "error", - - // oxc rules - "bad-array-method-on-arguments": "error", - "bad-char-at-comparison": "error", - "bad-comparison-sequence": "error", - "bad-min-max-func": "error", - "bad-object-literal-comparison": "error", - "bad-replace-all-arg": "error", - "const-comparisons": "error", - "double-comparisons": "error", - "erasing-op": "error", - "missing-throw": "error", - "number-arg-out-of-range": "error", - "only-used-in-recursion": "error", - "uninvoked-array-callback": "error", - - // unicorn rules - "unicorn/no-await-in-promise-methods": "error", - "unicorn/no-empty-file": "error", - "unicorn/no-invalid-fetch-options": "error", - "unicorn/no-invalid-remove-event-listener": "error", - "unicorn/no-new-array": "error", - "unicorn/no-single-promise-in-promise-methods": "error", - "unicorn/no-thenable": "error", - "unicorn/no-unnecessary-await": "error", - "unicorn/no-useless-spread": "error", - "unicorn/prefer-set-size": "error", + "typescript/no-import-type-side-effects": "error", + "typescript/switch-exhaustiveness-check": "error", + "no-case-declarations": "error", + "no-empty": "error", + "no-fallthrough": "error", + "no-prototype-builtins": "error", + "no-redeclare": "error", + "no-regex-spaces": "error", + "preserve-caught-error": "error", + "no-array-constructor": "error", "typescript/ban-ts-comment": "error", - "typescript/no-duplicate-enum-values": "error", "typescript/no-empty-object-type": "error", - "typescript/no-extra-non-null-assertion": "error", - "typescript/no-misused-new": "error", "typescript/no-namespace": "error", - "typescript/no-non-null-asserted-optional-chain": "error", "typescript/no-require-imports": "error", - "typescript/no-this-alias": "error", "typescript/no-unnecessary-type-constraint": "error", - "typescript/no-unsafe-declaration-merging": "error", "typescript/no-unsafe-function-type": "error", - "typescript/no-wrapper-object-types": "error", - "typescript/prefer-as-const": "error", - "typescript/prefer-namespace-keyword": "error", - "typescript/triple-slash-reference": "error", - "typescript/consistent-type-imports": [ - "error", - { - "fixStyle": "inline-type-imports" - } - ], - "typescript/no-import-type-side-effects": "error", - "typescript/no-unnecessary-parameter-property-assignment": "error", - "typescript/no-useless-empty-export": "error", - - // Type-aware rules (from typescript-eslint recommendedTypeChecked) - "typescript/await-thenable": "error", - "typescript/no-array-delete": "error", - "typescript/no-base-to-string": "error", - "typescript/no-duplicate-type-constituents": "error", - "typescript/no-for-in-array": "error", - "typescript/no-implied-eval": "error", - "typescript/no-redundant-type-constituents": "error", "typescript/no-unnecessary-type-assertion": "error", "typescript/no-unsafe-enum-comparison": "error", - "typescript/no-unsafe-unary-minus": "error", "typescript/only-throw-error": "error", "typescript/prefer-promise-reject-errors": "error", "typescript/require-await": "error", - "typescript/restrict-plus-operands": "error", - "typescript/restrict-template-expressions": "error", - "typescript/switch-exhaustiveness-check": "error", - "typescript/require-array-sort-compare": "error", - - // Type-aware rules explicitly disabled - "typescript/no-unsafe-argument": "off", - "typescript/no-unsafe-assignment": "off", - "typescript/no-unsafe-call": "off", - "typescript/no-unsafe-member-access": "off", - "typescript/no-unsafe-return": "off", - "typescript/unbound-method": "off", - "typescript/no-floating-promises": "off", - "typescript/no-misused-promises": "off" + "typescript/restrict-plus-operands": "error" }, "overrides": [ { @@ -199,12 +87,38 @@ }, { "files": ["packages/graph-explorer/**/*.{ts,tsx}"], + "plugins": ["react", "react-perf", "jsx-a11y"], + "jsPlugins": [ + "@tanstack/eslint-plugin-query", + { "name": "react-compiler", "specifier": "eslint-plugin-react-hooks" } + ], + "env": { "browser": true }, "rules": { "react/display-name": "off", + "react/no-unsafe": "error", + "react/react-in-jsx-scope": "off", + "jsx-a11y/no-autofocus": "off", + "jsx-a11y/click-events-have-key-events": "off", + "jsx-a11y/no-static-element-interactions": "off", + "jsx-a11y/mouse-events-have-key-events": "off", + "react-compiler/rules-of-hooks": "off", + "react-compiler/exhaustive-deps": "off", + "react-compiler/config": "error", + "react-compiler/error-boundaries": "error", + "react-compiler/gating": "error", + "react-compiler/globals": "error", + "react-compiler/immutability": "error", + "react-compiler/incompatible-library": "warn", + "react-compiler/preserve-manual-memoization": "error", + "react-compiler/purity": "error", + "react-compiler/refs": "error", + "react-compiler/set-state-in-effect": "error", + "react-compiler/set-state-in-render": "error", + "react-compiler/static-components": "error", + "react-compiler/unsupported-syntax": "warn", + "react-compiler/use-memo": "error", "react/jsx-key": "error", - "react/jsx-no-comment-textnodes": "error", "react/jsx-no-duplicate-props": "error", - "react/jsx-no-target-blank": "error", "react/jsx-no-undef": "error", "react/no-children-prop": "error", "react/no-danger-with-children": "error", @@ -213,32 +127,26 @@ "react/no-is-mounted": "error", "react/no-render-return-value": "error", "react/no-string-refs": "error", + "react/jsx-no-comment-textnodes": "error", + "react/jsx-no-target-blank": "error", "react/no-unescaped-entities": "error", "react/no-unknown-property": "error", - "react/no-unsafe": "off", - "react/react-in-jsx-scope": "off", + "react/jsx-curly-brace-presence": "error", + "react/rules-of-hooks": "error", + "react/exhaustive-deps": "warn", "@tanstack/query/exhaustive-deps": "error", "@tanstack/query/no-rest-destructuring": "warn", "@tanstack/query/stable-query-client": "error", "@tanstack/query/no-unstable-deps": "error", "@tanstack/query/infinite-query-property-order": "error", "@tanstack/query/no-void-query-fn": "error", - "@tanstack/query/mutation-property-order": "error", - "react/jsx-curly-brace-presence": "error", - "react/rules-of-hooks": "error", - "react/exhaustive-deps": "warn" - }, - "jsPlugins": ["@tanstack/eslint-plugin-query"], - "plugins": ["react"], - "env": { - "browser": true + "@tanstack/query/mutation-property-order": "error" } }, { "files": ["packages/graph-explorer-proxy-server/**/*.{ts,js}"], - "env": { - "node": true - } + "plugins": ["node"], + "env": { "node": true } } ] } diff --git a/eslint.config.mjs b/eslint.config.mjs deleted file mode 100644 index 634a31115..000000000 --- a/eslint.config.mjs +++ /dev/null @@ -1,36 +0,0 @@ -import oxlint from "eslint-plugin-oxlint"; -import reactHooks from "eslint-plugin-react-hooks"; -import tseslint from "typescript-eslint"; - -export default [ - // Ignore everything except graph-explorer frontend - { - ignores: [ - "**/*.config.{js,ts,mjs}", - "**/vitest.workspace.ts", - "**/dist/", - "packages/graph-explorer-proxy-server/**", - "packages/shared/**", - ], - }, - - // TypeScript parser for all files - tseslint.configs.base, - - // React Compiler rules (for graph-explorer only) - { - files: ["packages/graph-explorer/**/*.{ts,tsx}"], - plugins: { - "react-hooks": reactHooks, - }, - rules: { - ...reactHooks.configs.flat.recommended.rules, - // Disable rules that oxlint handles natively - "react-hooks/rules-of-hooks": "off", - "react-hooks/exhaustive-deps": "off", - }, - }, - - // Disable all ESLint rules that oxlint already covers - ...oxlint.buildFromOxlintConfigFile("./.oxlintrc.json"), -]; diff --git a/package.json b/package.json index ffc266154..299dee31d 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "type": "module", "scripts": { "prepare": "husky", - "lint": "oxlint --fix && eslint --fix", - "check:lint": "oxlint && eslint", + "lint": "oxlint --fix", + "check:lint": "oxlint", "format": "oxfmt", "check:format": "oxfmt --check", "test": "vitest run", @@ -29,8 +29,6 @@ "@types/node": "^24.10.9", "@vitest/coverage-v8": "4.1.5", "babel-plugin-react-compiler": "^1.0.0", - "eslint": "^10.2.1", - "eslint-plugin-oxlint": "1.62.0", "eslint-plugin-react-hooks": "7.1.1", "husky": "^9.1.7", "lint-staged": "^16.4.0", @@ -38,14 +36,12 @@ "oxlint": "1.62.0", "oxlint-tsgolint": "0.22.1", "typescript": "^6.0.3", - "typescript-eslint": "^8.59.1", "vitest": "4.1.5" }, "lint-staged": { "!(**/*.{js,ts,tsx})": "oxfmt --no-error-on-unmatched-pattern", "**/*.{js,ts,tsx}": [ "oxlint --fix", - "eslint --fix", "oxfmt" ] }, diff --git a/packages/graph-explorer-proxy-server/src/app.test.ts b/packages/graph-explorer-proxy-server/src/app.test.ts index 7491305f7..f51177a6b 100644 --- a/packages/graph-explorer-proxy-server/src/app.test.ts +++ b/packages/graph-explorer-proxy-server/src/app.test.ts @@ -362,7 +362,7 @@ describe("createApp", () => { await request(app).post("/sparql").set(dbHeaders()).send({ query }); const fetchOptions = mockFetch.mock.calls[0][1] as any; - expect(fetchOptions.headers["Content-Type"]).toBe( + expect(fetchOptions.headers["content-type"]).toBe( "application/x-www-form-urlencoded", ); expect(fetchOptions.body).toContain(`query=${encodeURIComponent(query)}`); @@ -503,7 +503,7 @@ describe("createApp", () => { await request(app).post("/openCypher").set(dbHeaders()).send({ query }); const fetchOptions = mockFetch.mock.calls[0][1] as any; - expect(fetchOptions.headers["Content-Type"]).toBe( + expect(fetchOptions.headers["content-type"]).toBe( "application/x-www-form-urlencoded", ); expect(fetchOptions.body).toBe(`query=${encodeURIComponent(query)}`); diff --git a/packages/graph-explorer-proxy-server/src/app.ts b/packages/graph-explorer-proxy-server/src/app.ts index b7e98b9b5..450ee85be 100644 --- a/packages/graph-explorer-proxy-server/src/app.ts +++ b/packages/graph-explorer-proxy-server/src/app.ts @@ -212,7 +212,10 @@ export function createApp({ new URL(url), { ...options, - headers: { "User-Agent": userAgent, ...options.headers }, + headers: { + "User-Agent": userAgent, + ...Object.fromEntries(new Headers(options.headers as HeadersInit)), + }, }, isIamEnabled, region, diff --git a/packages/graph-explorer-proxy-server/src/error-handler.ts b/packages/graph-explorer-proxy-server/src/error-handler.ts index f8c43a2ad..c0a0cde46 100644 --- a/packages/graph-explorer-proxy-server/src/error-handler.ts +++ b/packages/graph-explorer-proxy-server/src/error-handler.ts @@ -66,6 +66,7 @@ function extractErrorInfo(error: unknown) { if (error instanceof Error) { return { + // oxlint-disable-next-line typescript/no-misused-spread -- Intentionally extracting Error properties for serialization ...error, status: getStatusFromError(error), message: error.message || defaultErrorMessage, diff --git a/packages/graph-explorer/src/components/EmptyState.tsx b/packages/graph-explorer/src/components/EmptyState.tsx index 830bd2e8a..c4c1bef4d 100644 --- a/packages/graph-explorer/src/components/EmptyState.tsx +++ b/packages/graph-explorer/src/components/EmptyState.tsx @@ -69,6 +69,7 @@ EmptyStateContent.displayName = "EmptyStateContent"; function EmptyStateTitle({ className, + children, ...props }: React.ComponentPropsWithRef<"h3">) { return ( @@ -78,7 +79,9 @@ function EmptyStateTitle({ className, )} {...props} - /> + > + {children} + ); } EmptyStateTitle.displayName = "EmptyStateTitle"; diff --git a/packages/graph-explorer/src/components/FileButton.tsx b/packages/graph-explorer/src/components/FileButton.tsx index 5e9cf0744..498bbe373 100644 --- a/packages/graph-explorer/src/components/FileButton.tsx +++ b/packages/graph-explorer/src/components/FileButton.tsx @@ -51,7 +51,9 @@ export const FileButton = ({ const inputRef = React.useRef(null); const handleClick = () => { - !disabled && inputRef.current?.click(); + if (!disabled) { + inputRef.current?.click(); + } }; // Calls onChange with the selected file or files diff --git a/packages/graph-explorer/src/components/Graph/hooks/useAddClickEvents.ts b/packages/graph-explorer/src/components/Graph/hooks/useAddClickEvents.ts index a7d536f16..dc5603e49 100755 --- a/packages/graph-explorer/src/components/Graph/hooks/useAddClickEvents.ts +++ b/packages/graph-explorer/src/components/Graph/hooks/useAddClickEvents.ts @@ -117,7 +117,9 @@ const useAddClickEvents = ({ return () => { // Clear the timeout if it exists - tappedTimeout && clearTimeout(tappedTimeout); + if (tappedTimeout) { + clearTimeout(tappedTimeout); + } cy.off("tap", handleDoubleTap); }; }, [cy]); @@ -245,9 +247,12 @@ const useAddClickEvents = ({ cy.on("mouseout", "edge", handleOnEdgeMouseOut); // Group events - onGroupClick && cy.on("tap", "node[?__isGroupNode]", handleOnGroupClick); - onGroupDoubleClick && + if (onGroupClick) { + cy.on("tap", "node[?__isGroupNode]", handleOnGroupClick); + } + if (onGroupDoubleClick) { cy.on("doubleTap", "node[?__isGroupNode]", handleOnGroupDoubleClick); + } return () => { // Graph events cy.off("tap", handleOnGraphClick); @@ -269,9 +274,12 @@ const useAddClickEvents = ({ cy.off("mouseout", "edge", handleOnGroupClick); // Group events - onGroupClick && cy.off("tap", "node[?__isGroupNode]", handleOnGroupClick); - onGroupDoubleClick && + if (onGroupClick) { + cy.off("tap", "node[?__isGroupNode]", handleOnGroupClick); + } + if (onGroupDoubleClick) { cy.off("doubleTap", "node[?__isGroupNode]", handleOnGroupDoubleClick); + } }; }, [ cy, diff --git a/packages/graph-explorer/src/components/Graph/hooks/useManageConfigChanges.ts b/packages/graph-explorer/src/components/Graph/hooks/useManageConfigChanges.ts index 301e82e89..1d99b82ac 100755 --- a/packages/graph-explorer/src/components/Graph/hooks/useManageConfigChanges.ts +++ b/packages/graph-explorer/src/components/Graph/hooks/useManageConfigChanges.ts @@ -64,7 +64,11 @@ export const useManageConfigChanges = (config: Config, cy?: CytoscapeType) => { useEffect(() => { if (cy) { //lock nodes instead of using cy.autolock(autolock) to allow layout to run on newly added nodes before locking - autolock ? cy.nodes().lock() : cy.nodes().unlock(); + if (autolock) { + cy.nodes().lock(); + } else { + cy.nodes().unlock(); + } } }, [autolock, cy]); diff --git a/packages/graph-explorer/src/components/Graph/hooks/useUpdateGraphElements.ts b/packages/graph-explorer/src/components/Graph/hooks/useUpdateGraphElements.ts index 3fb056658..f8f6f9964 100644 --- a/packages/graph-explorer/src/components/Graph/hooks/useUpdateGraphElements.ts +++ b/packages/graph-explorer/src/components/Graph/hooks/useUpdateGraphElements.ts @@ -45,7 +45,7 @@ const useUpdateGraphElements = ({ wereElementsAddedOrRemoved(cy.edges(), edges); if (structureChanged) { - // eslint-disable-next-line react-hooks/set-state-in-effect + // oxlint-disable-next-line react-compiler/set-state-in-effect setGraphStructureVersion(v => v + 1); } diff --git a/packages/graph-explorer/src/components/Graph/useGraphGlobalActions.ts b/packages/graph-explorer/src/components/Graph/useGraphGlobalActions.ts index fe9e0352c..9982eca4b 100644 --- a/packages/graph-explorer/src/components/Graph/useGraphGlobalActions.ts +++ b/packages/graph-explorer/src/components/Graph/useGraphGlobalActions.ts @@ -40,7 +40,7 @@ export function useGraphGlobalActions() { } // graphRef is a reference that doesn't change but cytoscape can // oxlint-disable-next-line react/exhaustive-deps - // eslint-disable-next-line react-hooks/refs + // oxlint-disable-next-line react-compiler/refs }, [graphRef?.current?.cytoscape]); const onFitSelectionToCanvas = () => { diff --git a/packages/graph-explorer/src/components/SelectField.tsx b/packages/graph-explorer/src/components/SelectField.tsx index 5ad79bc03..d161a645a 100644 --- a/packages/graph-explorer/src/components/SelectField.tsx +++ b/packages/graph-explorer/src/components/SelectField.tsx @@ -37,7 +37,7 @@ export type SelectFieldProps = { ComponentPropsWithRef; function SelectField({ - options = [], + options, value, onValueChange, label, diff --git a/packages/graph-explorer/src/components/SettingsSection.tsx b/packages/graph-explorer/src/components/SettingsSection.tsx index a80234a52..09fd42cc9 100644 --- a/packages/graph-explorer/src/components/SettingsSection.tsx +++ b/packages/graph-explorer/src/components/SettingsSection.tsx @@ -30,6 +30,7 @@ export function SettingsSectionHeader({ export function SettingsSectionTitle({ className, + children, ...props }: ComponentProps<"h2">) { return ( @@ -39,7 +40,9 @@ export function SettingsSectionTitle({ className, )} {...props} - /> + > + {children} + ); } diff --git a/packages/graph-explorer/src/components/Tabular/TabularRow.tsx b/packages/graph-explorer/src/components/Tabular/TabularRow.tsx index 5299e7d43..15b76d319 100644 --- a/packages/graph-explorer/src/components/Tabular/TabularRow.tsx +++ b/packages/graph-explorer/src/components/Tabular/TabularRow.tsx @@ -25,7 +25,7 @@ const TabularRow = ({ const [selectable, setSelectable] = useState(true); useEffect(() => { if (tableInstance.state.columnResizing.isResizingColumn) { - // eslint-disable-next-line react-hooks/set-state-in-effect + // oxlint-disable-next-line react-compiler/set-state-in-effect setSelectable(false); return; } @@ -35,7 +35,9 @@ const TabularRow = ({ }, 100); return () => { - timeout && clearTimeout(timeout); + if (timeout) { + clearTimeout(timeout); + } }; }, [tableInstance.state.columnResizing.isResizingColumn]); diff --git a/packages/graph-explorer/src/components/Tabular/filters/TextFilter.tsx b/packages/graph-explorer/src/components/Tabular/filters/TextFilter.tsx index b216ee048..00f6ab33d 100644 --- a/packages/graph-explorer/src/components/Tabular/filters/TextFilter.tsx +++ b/packages/graph-explorer/src/components/Tabular/filters/TextFilter.tsx @@ -28,9 +28,11 @@ export function TextFilter({ onChange={e => { // do not use value || undefined because // if the user types zero, it clears the filter too - e.target.value !== "" && e.target.value !== undefined - ? column.setFilter(e.target.value) - : column.setFilter(undefined); + if (e.target.value !== "" && e.target.value !== undefined) { + column.setFilter(e.target.value); + } else { + column.setFilter(undefined); + } }} />
diff --git a/packages/graph-explorer/src/components/Tabular/useTabular.ts b/packages/graph-explorer/src/components/Tabular/useTabular.ts index aa581f4af..f5c8963a0 100644 --- a/packages/graph-explorer/src/components/Tabular/useTabular.ts +++ b/packages/graph-explorer/src/components/Tabular/useTabular.ts @@ -326,7 +326,7 @@ export const useTabular = (options: TabularOptions) => { // Avoid table to delete filter on re-render useEffect(() => { skipPageResetRef.current = true; - // eslint-disable-next-line react-hooks/set-state-in-effect -- react-table v7 pattern to prevent auto-reset on data changes + // oxlint-disable-next-line react-compiler/set-state-in-effect -- react-table v7 pattern to prevent auto-reset on data changes setUpdatedData(data); }, [data]); @@ -424,7 +424,7 @@ export const useTabular = (options: TabularOptions) => { }, stateReducer, useControlledState, - /* eslint-disable react-hooks/refs -- react-table v7 requires reading refs during render to control auto-reset */ + /* oxlint-disable react-compiler/refs -- react-table v7 requires reading refs during render to control auto-reset */ autoResetPage: !skipPageResetRef.current, autoResetExpanded: !skipPageResetRef.current, autoResetGroupBy: !skipPageResetRef.current, diff --git a/packages/graph-explorer/src/components/Typography.tsx b/packages/graph-explorer/src/components/Typography.tsx index c51feaeef..64d0620f8 100644 --- a/packages/graph-explorer/src/components/Typography.tsx +++ b/packages/graph-explorer/src/components/Typography.tsx @@ -6,7 +6,11 @@ import { cn } from "@/utils"; import { Alert, AlertDescription, AlertTitle } from "./Alert"; -export function PageHeading({ className, ...props }: ComponentProps<"h1">) { +export function PageHeading({ + className, + children, + ...props +}: ComponentProps<"h1">) { return (

) { className, )} {...props} - /> + > + {children} +

); } diff --git a/packages/graph-explorer/src/components/VertexIcon.tsx b/packages/graph-explorer/src/components/VertexIcon.tsx index 1b8abb429..8ad4122e2 100644 --- a/packages/graph-explorer/src/components/VertexIcon.tsx +++ b/packages/graph-explorer/src/components/VertexIcon.tsx @@ -14,15 +14,19 @@ import { SearchResultSymbol } from "./SearchResult"; interface Props { vertexStyle: VertexPreferences; className?: string; + alt?: string; } -function VertexIcon({ vertexStyle, className }: Props) { +function VertexIcon({ vertexStyle, className, alt }: Props) { + const altText = alt ?? `${vertexStyle.displayLabel ?? vertexStyle.type} icon`; + if (vertexStyle.iconImageType === "image/svg+xml") { return ( ); } @@ -30,6 +34,7 @@ function VertexIcon({ vertexStyle, className }: Props) { return ( {altText} diff --git a/packages/graph-explorer/src/connector/fetchDatabaseRequest.test.ts b/packages/graph-explorer/src/connector/fetchDatabaseRequest.test.ts index 9b4c5da54..4a73383aa 100644 --- a/packages/graph-explorer/src/connector/fetchDatabaseRequest.test.ts +++ b/packages/graph-explorer/src/connector/fetchDatabaseRequest.test.ts @@ -185,7 +185,7 @@ describe("fetchDatabaseRequest", () => { }); const headers = mockFetch.mock.calls[0][1].headers; - expect(headers["Content-Type"]).toBe("application/x-www-form-urlencoded"); + expect(headers["content-type"]).toBe("application/x-www-form-urlencoded"); expect(headers["graph-db-connection-url"]).toBeDefined(); }); }); diff --git a/packages/graph-explorer/src/connector/fetchDatabaseRequest.ts b/packages/graph-explorer/src/connector/fetchDatabaseRequest.ts index 6993b3181..80696820f 100644 --- a/packages/graph-explorer/src/connector/fetchDatabaseRequest.ts +++ b/packages/graph-explorer/src/connector/fetchDatabaseRequest.ts @@ -52,7 +52,7 @@ function getAuthHeaders( featureFlags: FeatureFlags, typeHeaders: HeadersInit | undefined, ) { - const headers: HeadersInit = {}; + const headers: Record = {}; if (connection.proxyConnection) { headers["graph-db-connection-url"] = connection.graphDbUrl || ""; headers["db-query-logging-enabled"] = String( @@ -64,7 +64,11 @@ function getAuthHeaders( headers["service-type"] = connection.serviceType || DEFAULT_SERVICE_TYPE; } - return { ...headers, ...typeHeaders }; + if (typeHeaders) { + Object.assign(headers, Object.fromEntries(new Headers(typeHeaders))); + } + + return headers; } // Construct an AbortSignal for the fetch timeout if configured diff --git a/packages/graph-explorer/src/connector/sparql/neighborCounts.test.ts b/packages/graph-explorer/src/connector/sparql/neighborCounts.test.ts index 77777cb3c..7c6faccb1 100644 --- a/packages/graph-explorer/src/connector/sparql/neighborCounts.test.ts +++ b/packages/graph-explorer/src/connector/sparql/neighborCounts.test.ts @@ -101,7 +101,6 @@ describe("neighborCounts", () => { it("should return blank node neighbor counts", async () => { const vertex = createRandomVertexForRdf(); - vertex.isBlankNode; const blankNodes: BlankNodesMap = new Map(); const expected: NeighborCount = { vertexId: vertex.id, diff --git a/packages/graph-explorer/src/core/StateProvider/configuration.ts b/packages/graph-explorer/src/core/StateProvider/configuration.ts index d08ab3643..6bccbfd82 100644 --- a/packages/graph-explorer/src/core/StateProvider/configuration.ts +++ b/packages/graph-explorer/src/core/StateProvider/configuration.ts @@ -145,8 +145,8 @@ const mergeAttributes = ( return allAttrNames.map(attrName => ({ name: attrName, - ...(schemaAttrMap.get(attrName) || {}), - ...(configAttrMap.get(attrName) || {}), + ...schemaAttrMap.get(attrName), + ...configAttrMap.get(attrName), })); }; @@ -175,11 +175,11 @@ const mergeVertex = ( // Defaults ...getDefaultVertexTypeConfig(vt), // Automatic schema override - ...(patchedSchema || {}), + ...patchedSchema, // File-based override - ...(patchedConfig || {}), + ...patchedConfig, // User preferences override - ...(preferences || {}), + ...preferences, attributes, }; }; @@ -209,11 +209,11 @@ const mergeEdge = ( // Defaults ...getDefaultEdgeTypeConfig(et), // Automatic schema override - ...(patchedSchema || {}), + ...patchedSchema, // File-based override - ...(patchedConfig || {}), + ...patchedConfig, // User preferences override - ...(preferences || {}), + ...preferences, attributes, }; diff --git a/packages/graph-explorer/src/core/StateProvider/localDb.test.ts b/packages/graph-explorer/src/core/StateProvider/localDb.test.ts index 712d12aa8..20cbe6383 100644 --- a/packages/graph-explorer/src/core/StateProvider/localDb.test.ts +++ b/packages/graph-explorer/src/core/StateProvider/localDb.test.ts @@ -235,7 +235,7 @@ test("Remove prefix from restored entries", async () => { }); /** Fake database using a map as the data source. */ -export function createFakeLocalDb( +function createFakeLocalDb( initialState?: Iterable | null, ): LocalDb { const map = new Map(initialState); diff --git a/packages/graph-explorer/src/hooks/useContextMenuTarget.ts b/packages/graph-explorer/src/hooks/useContextMenuTarget.ts index 1ee7f29b8..091a4383f 100644 --- a/packages/graph-explorer/src/hooks/useContextMenuTarget.ts +++ b/packages/graph-explorer/src/hooks/useContextMenuTarget.ts @@ -79,10 +79,8 @@ export function useContextMenuTarget({ // Check if affected is within selection (short-circuits on first match) const hasAffectedInSelection = - (affectedVertexIds.length > 0 && - affectedVertexIds.some(id => graphSelection.isVertexSelected(id))) || - (affectedEdgeIds.length > 0 && - affectedEdgeIds.some(id => graphSelection.isEdgeSelected(id))); + affectedVertexIds.some(id => graphSelection.isVertexSelected(id)) || + affectedEdgeIds.some(id => graphSelection.isEdgeSelected(id)); // Use selection when affected is within it if (hasAffectedInSelection) { diff --git a/packages/graph-explorer/src/hooks/useDeepMemo.ts b/packages/graph-explorer/src/hooks/useDeepMemo.ts index 0b65c9d09..8c7f4f60c 100644 --- a/packages/graph-explorer/src/hooks/useDeepMemo.ts +++ b/packages/graph-explorer/src/hooks/useDeepMemo.ts @@ -17,12 +17,12 @@ export const useDeepMemo = ( ): TValue => { const ref = useRef<{ key: TKey; value: TValue } | null>(null); - // eslint-disable-next-line react-hooks/refs + // oxlint-disable-next-line react-compiler/refs if (!ref.current || !isEqual(key, ref.current.key)) { ref.current = { key, value: memoFn() }; } - // eslint-disable-next-line react-hooks/refs + // oxlint-disable-next-line react-compiler/refs return ref.current.value; }; diff --git a/packages/graph-explorer/src/modules/ConnectionDetail/ConnectionDetail.tsx b/packages/graph-explorer/src/modules/ConnectionDetail/ConnectionDetail.tsx index 564bd013a..481cfc215 100644 --- a/packages/graph-explorer/src/modules/ConnectionDetail/ConnectionDetail.tsx +++ b/packages/graph-explorer/src/modules/ConnectionDetail/ConnectionDetail.tsx @@ -343,7 +343,7 @@ function DebugActions() { const deleteSchema = () => { logger.log("Deleting schema"); setActiveSchema(RESET); - void queryClient.removeQueries({ + queryClient.removeQueries({ queryKey: ["schema"], }); }; diff --git a/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx b/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx index b5f7b6e96..637a0dc26 100644 --- a/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx +++ b/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx @@ -87,7 +87,7 @@ function mapToConnectionForm( } const result: ConnectionForm = { - ...(existingConfig.connection ?? {}), + ...existingConfig.connection, name: existingConfig.displayLabel ?? existingConfig.id, fetchTimeoutEnabled: Boolean(existingConfig.connection?.fetchTimeoutMs), nodeExpansionLimitEnabled: Boolean( @@ -130,7 +130,7 @@ const CreateConnection = ({ const updated = new Map(prev); const currentConfig = updated.get(configId); const updatedConfig: RawConfiguration = { - ...(currentConfig || {}), + ...currentConfig, id: configId, displayLabel: data.name, connection: mapToConnection(data), @@ -171,7 +171,7 @@ const CreateConnection = ({ // Reseting all query state. Using `removeQueries()` to ensure initial data is recalculated. // This ensures dependent queries execute in the right order - void queryClient.removeQueries(); + queryClient.removeQueries(); } }, [configId, initialData, queryClient], diff --git a/packages/graph-explorer/src/modules/EdgesStyling/SingleEdgeStyling.tsx b/packages/graph-explorer/src/modules/EdgesStyling/SingleEdgeStyling.tsx index 36f80f44b..486b5f3b2 100644 --- a/packages/graph-explorer/src/modules/EdgesStyling/SingleEdgeStyling.tsx +++ b/packages/graph-explorer/src/modules/EdgesStyling/SingleEdgeStyling.tsx @@ -30,7 +30,7 @@ export default function SingleEdgeStyling({ if (prevDisplayAs === null || prevDisplayAs === debouncedDisplayAs) { return; } - void setEdgeStyle({ displayLabel: debouncedDisplayAs }); + setEdgeStyle({ displayLabel: debouncedDisplayAs }); }, [debouncedDisplayAs, prevDisplayAs, setEdgeStyle]); return ( diff --git a/packages/graph-explorer/src/modules/EntitiesFilter/useFiltersConfig.tsx b/packages/graph-explorer/src/modules/EntitiesFilter/useFiltersConfig.tsx index ab7960bf9..7fc2d0bb7 100644 --- a/packages/graph-explorer/src/modules/EntitiesFilter/useFiltersConfig.tsx +++ b/packages/graph-explorer/src/modules/EntitiesFilter/useFiltersConfig.tsx @@ -68,7 +68,11 @@ const useFiltersConfig = () => { }; const onChangeVertexTypes = (vertexId: string, isSelected: boolean): void => { - isSelected ? deleteVertex(vertexId) : addVertex(vertexId); + if (isSelected) { + deleteVertex(vertexId); + } else { + addVertex(vertexId); + } }; const onChangeAllVertexTypes = (isSelected: boolean): void => { @@ -79,7 +83,11 @@ const useFiltersConfig = () => { connectionId: string, isSelected: boolean, ): void => { - isSelected ? deleteConnection(connectionId) : addConnection(connectionId); + if (isSelected) { + deleteConnection(connectionId); + } else { + addConnection(connectionId); + } }; const onChangeAllConnectionTypes = (isSelected: boolean): void => { diff --git a/packages/graph-explorer/src/modules/GraphViewer/useContextMenu.ts b/packages/graph-explorer/src/modules/GraphViewer/useContextMenu.ts index cb6d65252..84f8a1a0b 100644 --- a/packages/graph-explorer/src/modules/GraphViewer/useContextMenu.ts +++ b/packages/graph-explorer/src/modules/GraphViewer/useContextMenu.ts @@ -69,6 +69,7 @@ function useContextMenu() { // oxlint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore handleMouseEvent({ + // oxlint-disable-next-line typescript/no-misused-spread -- Intentionally extracting MouseEvent properties for synthetic event ...event.originalEvent, // Override the event position to node bounds and parent offsets clientY: parentBounds.top + bounds.top + bounds.height / 2, @@ -88,6 +89,7 @@ function useContextMenu() { // oxlint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore handleMouseEvent({ + // oxlint-disable-next-line typescript/no-misused-spread -- Intentionally extracting MouseEvent properties for synthetic event ...event.originalEvent, // Override the event position to event position and parent offsets clientY: event.renderedPosition.y + parentBounds.top, @@ -108,6 +110,7 @@ function useContextMenu() { // oxlint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore handleMouseEvent({ + // oxlint-disable-next-line typescript/no-misused-spread -- Intentionally extracting MouseEvent properties for synthetic event ...event.originalEvent, clientY: position.top, clientX: position.left, diff --git a/packages/graph-explorer/src/modules/Namespaces/UserPrefixes.tsx b/packages/graph-explorer/src/modules/Namespaces/UserPrefixes.tsx index 670a5d67b..0a4495902 100644 --- a/packages/graph-explorer/src/modules/Namespaces/UserPrefixes.tsx +++ b/packages/graph-explorer/src/modules/Namespaces/UserPrefixes.tsx @@ -176,7 +176,7 @@ function useDeletePrefixCallback(prefix: string) { const activeSchema = updatedSchemas.get(activeConfigId); updatedSchemas.set(activeConfigId, { - ...(activeSchema || {}), + ...activeSchema, vertices: activeSchema?.vertices || [], edges: activeSchema?.edges || [], prefixes: (activeSchema?.prefixes || []).filter( @@ -227,7 +227,7 @@ function EditPrefixModal({ const activeSchema = updatedSchemas.get(configId); updatedSchemas.set(configId, { - ...(activeSchema || {}), + ...activeSchema, vertices: activeSchema?.vertices || [], edges: activeSchema?.edges || [], prefixes: [ diff --git a/packages/graph-explorer/src/modules/NodesStyling/NodeStyleDialog.tsx b/packages/graph-explorer/src/modules/NodesStyling/NodeStyleDialog.tsx index 4dd74e54e..41399f3c1 100644 --- a/packages/graph-explorer/src/modules/NodesStyling/NodeStyleDialog.tsx +++ b/packages/graph-explorer/src/modules/NodesStyling/NodeStyleDialog.tsx @@ -211,7 +211,9 @@ function Content({ vertexType }: { vertexType: VertexType }) { { - file && convertImageToBase64AndSetNewIcon(file); + if (file) { + convertImageToBase64AndSetNewIcon(file); + } }} variant="outline" className="rounded-full" diff --git a/packages/graph-explorer/src/modules/NodesStyling/SingleNodeStyling.tsx b/packages/graph-explorer/src/modules/NodesStyling/SingleNodeStyling.tsx index 4b8eff9b5..a84c9eb44 100644 --- a/packages/graph-explorer/src/modules/NodesStyling/SingleNodeStyling.tsx +++ b/packages/graph-explorer/src/modules/NodesStyling/SingleNodeStyling.tsx @@ -31,7 +31,7 @@ export default function SingleNodeStyling({ if (prevDisplayAs === null || prevDisplayAs === debouncedDisplayAs) { return; } - void setVertexStyle({ displayLabel: debouncedDisplayAs }); + setVertexStyle({ displayLabel: debouncedDisplayAs }); }, [debouncedDisplayAs, prevDisplayAs, setVertexStyle]); return ( diff --git a/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/Details.tsx b/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/Details.tsx index 8d83e27d6..c37d549f9 100644 --- a/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/Details.tsx +++ b/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/Details.tsx @@ -39,13 +39,16 @@ export function DetailsHeader({ /** Title text for detail sections */ export function DetailsTitle({ className, + children, ...props }: ComponentPropsWithRef<"h2">) { return (

+ > + {children} +

); } diff --git a/packages/graph-explorer/src/routes/Settings/SettingsRoot.tsx b/packages/graph-explorer/src/routes/Settings/SettingsRoot.tsx index a87ffe58c..8e2c3f046 100644 --- a/packages/graph-explorer/src/routes/Settings/SettingsRoot.tsx +++ b/packages/graph-explorer/src/routes/Settings/SettingsRoot.tsx @@ -49,7 +49,7 @@ export default function SettingsRoot() { function SideBar() { return ( -