From 2a85de9bc3f70b3ec769a20c55d0d7ef145a7fba Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 28 Jan 2026 17:42:46 +0000 Subject: [PATCH] build: add sonarjs lint plugin, fix lint errors (#636) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I noticed in a recent commit that you [removed some unused collections](https://github.com/bvaughn/react-resizable-panels/commit/5a11ea6562cbbea7ff5a42885d64e7969a1f38c4#diff-53ce94c55647fe0f3addc83cf2298ca14ddac4b13403434adf6b6fa6cc9a4afbL15-L18) - they were only added to, never accessed. There was another instance of this, so I added sonarjs to eslint which has a nice check for unused collections. I would personally enable `sonarjs/no-small-switch`. There are instances of switches with only one case, but I know this is some people's preference to keep a clean history, especially if they anticipate additional cases being added later. Likewise I'd enable `sonarjs/no-nested-conditional` but I left it off in case you prefer this style. `@types/sharp` was removed for an unrelated reason: ``` WARN  deprecated @types/sharp@0.32.0: This is a stub types definition. sharp provides its own type definitions, so you do not need this installed. ``` --- eslint.config.js | 16 +++- .../event-handlers/onDocumentPointerDown.ts | 12 --- .../utils/calculateSeparatorAriaValues.ts | 4 +- lib/utils/test/mockResizeObserver.ts | 4 - lib/vendor/stacking-order.ts | 6 +- package.json | 2 +- pnpm-lock.yaml | 89 ++++++++++++++++--- src/App.tsx | 2 +- 8 files changed, 95 insertions(+), 40 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 576a10b21..b614137ec 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -4,6 +4,7 @@ import reactRefresh from "eslint-plugin-react-refresh"; import { globalIgnores } from "eslint/config"; import globals from "globals"; import tseslint from "typescript-eslint"; +import sonarJs from "eslint-plugin-sonarjs"; export default tseslint.config([ globalIgnores([ @@ -18,7 +19,8 @@ export default tseslint.config([ js.configs.recommended, tseslint.configs.recommended, reactHooks.configs["recommended-latest"], - reactRefresh.configs.vite + reactRefresh.configs.vite, + sonarJs.configs.recommended ], languageOptions: { ecmaVersion: 2020, @@ -64,7 +66,17 @@ export default tseslint.config([ varsIgnorePattern: "^_", ignoreRestSiblings: true } - ] + ], + "sonarjs/cognitive-complexity": "off", + "sonarjs/no-commented-code": "off", + "sonarjs/no-small-switch": "off", + "sonarjs/todo-tag": "off", + "sonarjs/no-nested-functions": "off", + "sonarjs/no-unused-vars": "off", + "sonarjs/no-nested-conditional": "off", + "sonarjs/no-dead-store": "off", + "sonarjs/slow-regex": "off", + "sonarjs/no-duplicated-branches": "off" } } ]); diff --git a/lib/global/event-handlers/onDocumentPointerDown.ts b/lib/global/event-handlers/onDocumentPointerDown.ts index c84898462..9bf8d8f95 100644 --- a/lib/global/event-handlers/onDocumentPointerDown.ts +++ b/lib/global/event-handlers/onDocumentPointerDown.ts @@ -1,6 +1,4 @@ import type { Layout, RegisteredGroup } from "../../components/group/types"; -import type { RegisteredPanel } from "../../components/panel/types"; -import type { RegisteredSeparator } from "../../components/separator/types"; import { read, update } from "../mutableState"; import { findMatchingHitRegions } from "../utils/findMatchingHitRegions"; @@ -15,22 +13,12 @@ export function onDocumentPointerDown(event: PointerEvent) { const hitRegions = findMatchingHitRegions(event, mountedGroups); - const groups = new Set(); - const panels = new Set(); - const separators = new Set(); const initialLayoutMap = new Map(); let didChangeFocus = false; hitRegions.forEach((current) => { - groups.add(current.group); - current.panels.forEach((panel) => { - panels.add(panel); - }); - if (current.separator) { - separators.add(current.separator); - if (!didChangeFocus) { didChangeFocus = true; diff --git a/lib/global/utils/calculateSeparatorAriaValues.ts b/lib/global/utils/calculateSeparatorAriaValues.ts index 0deccd45e..a109a4811 100644 --- a/lib/global/utils/calculateSeparatorAriaValues.ts +++ b/lib/global/utils/calculateSeparatorAriaValues.ts @@ -29,9 +29,9 @@ export function calculateSeparatorAriaValues({ ); if (constraints) { const maxSize = constraints.maxSize; - const minSize = (valueMin = constraints.collapsible + const minSize = constraints.collapsible ? constraints.collapsedSize - : constraints.minSize); + : constraints.minSize; const pivotIndices = [panelIndex, panelIndex + 1]; diff --git a/lib/utils/test/mockResizeObserver.ts b/lib/utils/test/mockResizeObserver.ts index 79e753cca..e30fb7a8a 100644 --- a/lib/utils/test/mockResizeObserver.ts +++ b/lib/utils/test/mockResizeObserver.ts @@ -1,7 +1,5 @@ import { emitter } from "./mockBoundingClientRect"; -const elementToDOMRect = new Map(); - let disabled: boolean = false; export function disableResizeObserverForCurrentTest() { @@ -24,8 +22,6 @@ export function mockResizeObserver() { window.ResizeObserver = originalResizeObserver; disabled = false; - - elementToDOMRect.clear(); }; } diff --git a/lib/vendor/stacking-order.ts b/lib/vendor/stacking-order.ts index 08f966a0e..20aac984c 100644 --- a/lib/vendor/stacking-order.ts +++ b/lib/vendor/stacking-order.ts @@ -26,10 +26,8 @@ export function compare( // remove shared ancestors while (ancestors.a.at(-1) === ancestors.b.at(-1)) { - a = ancestors.a.pop() as HTMLElement; - b = ancestors.b.pop() as HTMLElement; - - common_ancestor = a; + common_ancestor = ancestors.a.pop() as HTMLElement; + ancestors.b.pop(); } assert( diff --git a/package.json b/package.json index 6ca741474..f2d11d2b7 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,6 @@ "@types/node": "^24.2.0", "@types/react": "^19.1.8", "@types/react-dom": "^19.2.3", - "@types/sharp": "^0.32.0", "@vitejs/plugin-react-swc": "^3.10.2", "bytes": "^3.1.2", "clsx": "^2.1.1", @@ -79,6 +78,7 @@ "eslint": "^9.30.1", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", + "eslint-plugin-sonarjs": "^3.0.6", "express": "^5.1.0", "globals": "^16.3.0", "husky": "^9.1.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e0e1f3167..30b39fa7c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -59,9 +59,6 @@ importers: '@types/react-dom': specifier: ^19.2.3 version: 19.2.3(@types/react@19.2.7) - '@types/sharp': - specifier: ^0.32.0 - version: 0.32.0 '@vitejs/plugin-react-swc': specifier: ^3.10.2 version: 3.11.0(vite@7.2.4(@types/node@24.2.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.1)) @@ -92,6 +89,9 @@ importers: eslint-plugin-react-refresh: specifier: ^0.4.20 version: 0.4.20(eslint@9.30.1(jiti@2.6.1)) + eslint-plugin-sonarjs: + specifier: ^3.0.6 + version: 3.0.6(eslint@9.30.1(jiti@2.6.1)) express: specifier: ^5.1.0 version: 5.1.0 @@ -922,6 +922,10 @@ packages: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint/config-array@0.21.0': resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1888,10 +1892,6 @@ packages: '@types/serve-static@1.15.10': resolution: {integrity: sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==} - '@types/sharp@0.32.0': - resolution: {integrity: sha512-OOi3kL+FZDnPhVzsfD37J88FNeZh6gQsGcLc95NbeURRGvmSjeXiDcyWzF2o3yh/gQAUn2uhh/e+CPCa5nwAxw==} - deprecated: This is a stub types definition. sharp provides its own type definitions, so you do not need this installed. - '@typescript-eslint/eslint-plugin@8.48.0': resolution: {integrity: sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2291,6 +2291,10 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + builtin-modules@3.3.0: + resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} + engines: {node: '>=6'} + bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -2737,6 +2741,11 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + eslint-plugin-sonarjs@3.0.6: + resolution: {integrity: sha512-3mVUqsAUSylGfkJMj2v0aC2Cu/eUunDLm+XMjLf0uLjAZao205NWF3g6EXxcCAFO+rCZiQ6Or1WQkUcU9/sKFQ==} + peerDependencies: + eslint: ^8.0.0 || ^9.0.0 + eslint-scope@8.4.0: resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2891,6 +2900,9 @@ packages: resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} engines: {node: '>= 0.4'} + functional-red-black-tree@1.0.1: + resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} + functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} @@ -3277,6 +3289,10 @@ packages: jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + jsx-ast-utils-x@0.1.0: + resolution: {integrity: sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -3932,10 +3948,18 @@ packages: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} + refa@0.12.1: + resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} + regexp-ast-analysis@0.7.1: + resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + regexp.prototype.flags@1.5.4: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} @@ -4051,6 +4075,10 @@ packages: scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + scslre@0.3.0: + resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==} + engines: {node: ^14.0.0 || >=16.0.0} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -5252,6 +5280,8 @@ snapshots: '@eslint-community/regexpp@4.12.1': {} + '@eslint-community/regexpp@4.12.2': {} + '@eslint/config-array@0.21.0': dependencies: '@eslint/object-schema': 2.1.6 @@ -6169,10 +6199,6 @@ snapshots: '@types/node': 24.2.0 '@types/send': 0.17.6 - '@types/sharp@0.32.0': - dependencies: - sharp: 0.34.5 - '@typescript-eslint/eslint-plugin@8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -6720,6 +6746,8 @@ snapshots: buffer-from@1.1.2: {} + builtin-modules@3.3.0: {} + bytes@3.1.2: {} cac@6.7.14: {} @@ -7160,7 +7188,7 @@ snapshots: eslint: 9.30.1(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.30.1(jiti@2.6.1)))(eslint@9.30.1(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.1(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.30.1(jiti@2.6.1)))(eslint@9.30.1(jiti@2.6.1)))(eslint@9.30.1(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.30.1(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.30.1(jiti@2.6.1)) eslint-plugin-react-hooks: 7.0.1(eslint@9.30.1(jiti@2.6.1)) @@ -7193,7 +7221,7 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.1(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.30.1(jiti@2.6.1)))(eslint@9.30.1(jiti@2.6.1)))(eslint@9.30.1(jiti@2.6.1)) transitivePeerDependencies: - supports-color @@ -7208,7 +7236,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.1(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.30.1(jiti@2.6.1)))(eslint@9.30.1(jiti@2.6.1)))(eslint@9.30.1(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -7297,6 +7325,20 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 + eslint-plugin-sonarjs@3.0.6(eslint@9.30.1(jiti@2.6.1)): + dependencies: + '@eslint-community/regexpp': 4.12.2 + builtin-modules: 3.3.0 + bytes: 3.1.2 + eslint: 9.30.1(jiti@2.6.1) + functional-red-black-tree: 1.0.1 + jsx-ast-utils-x: 0.1.0 + lodash.merge: 4.6.2 + minimatch: 10.1.1 + scslre: 0.3.0 + semver: 7.7.3 + typescript: 5.9.3 + eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 @@ -7506,6 +7548,8 @@ snapshots: hasown: 2.0.2 is-callable: 1.2.7 + functional-red-black-tree@1.0.1: {} + functions-have-names@1.2.3: {} generator-function@2.0.1: {} @@ -7899,6 +7943,8 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsx-ast-utils-x@0.1.0: {} + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.9 @@ -8471,6 +8517,10 @@ snapshots: indent-string: 4.0.0 strip-indent: 3.0.0 + refa@0.12.1: + dependencies: + '@eslint-community/regexpp': 4.12.2 + reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 @@ -8482,6 +8532,11 @@ snapshots: get-proto: 1.0.1 which-builtin-type: 1.2.1 + regexp-ast-analysis@0.7.1: + dependencies: + '@eslint-community/regexpp': 4.12.2 + refa: 0.12.1 + regexp.prototype.flags@1.5.4: dependencies: call-bind: 1.0.8 @@ -8624,6 +8679,12 @@ snapshots: scheduler@0.27.0: {} + scslre@0.3.0: + dependencies: + '@eslint-community/regexpp': 4.12.2 + refa: 0.12.1 + regexp-ast-analysis: 0.7.1 + semver@6.3.1: {} semver@7.5.4: diff --git a/src/App.tsx b/src/App.tsx index f4008cd49..100f389c6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -214,7 +214,7 @@ const commonQuestions: CommonQuestion[] = [ ResizeObserver {" "} API, either through a hook like{" "} - + useResizeObserver {" "} or a component like{" "}