Skip to content

[FIX] Resolve SonarQube findings from static analysis scan #13

Description

@divideby0

[FIX] Resolve SonarQube findings from static analysis scan

SonarQube Community Edition 26.2 scan on commit 2a402dd — Feb 16, 2026.

Summary: 6 bugs, 0 vulnerabilities, 40 code smells, 0% test coverage, 0% duplication (2,195 LOC).

Rating Grade
Security A
Reliability C
Maintainability A

🔴 Bugs (6)

Missing keyboard listeners on click handlers

Non-interactive elements with onClick must also have onKeyDown/onKeyPress for keyboard and screen reader accessibility.

Rule File Line
jsx-a11y/click-events-have-key-events src/PughMatrix.tsx#L266 Tool header click-to-edit
jsx-a11y/click-events-have-key-events src/PughMatrix.tsx#L314 Criterion cell click-to-edit
jsx-a11y/click-events-have-key-events src/PughMatrix.tsx#L379 Score cell click-to-open editor
jsx-a11y/click-events-have-key-events src/BranchSelector.tsx#L105 Branch item click-to-switch

Fix: Add onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') onClick() }}, tabIndex={0}, and role="button". Or replace <td>/<div> with <button> styled as table cells.

React Hooks called in wrong context

useState and useMemo called inside a Storybook render function that doesn't start with an uppercase letter.

Rule File Line
react-hooks/rules-of-hooks src/PughMatrix.stories.tsx#L267 useState in render
react-hooks/rules-of-hooks src/PughMatrix.stories.tsx#L268 useMemo in render

Fix: Extract to a named component (function InteractiveDemo() { ... }) or rename render to Render.


🔴 Critical Code Smells (3)

Cognitive complexity: projectEvents() — 37 (max 15)

File Line
src/events/projection.ts#L4 function projectEvents(events: PughEvent[])

The giant switch over 8 event types with nested logic. Will improve significantly with the event type refactor (#6) — recommend splitting into per-event-type handler functions:

const handlers: Record<PughEvent['type'], (state: Draft, event: PughEvent) => void> = {
  CriterionAdded: (state, e) => { ... },
  // ...
};

Cognitive complexity: PughMatrix render — 21 (max 15)

File Line
src/PughMatrix.tsx#L353 Score cell rendering block

Fix: Extract score cell, header cell, and totals row into sub-components.

Function nesting > 4 levels deep

File Line
src/store/createPughStore.ts#L207 persistmerge → lambda nesting

Fix: Extract the merge function to a named top-level helper.


🟡 Major Code Smells (14)

Non-native interactive elements missing ARIA roles (5)

File Line Element
src/BranchSelector.tsx#L105 Branch item
src/BranchSelector.tsx#L125 Branch rename area
src/PughMatrix.tsx#L266 Tool header
src/PughMatrix.tsx#L314 Criterion cell
src/PughMatrix.tsx#L379 Score cell

Fix: Add role="button" (or role="option" for branch list items) + tabIndex={0} + keyboard handlers.

Nested ternary operations (5)

File Line Context
src/PughMatrix.tsx#L262 Header class: isWinner ? ... : isHighlighted ? ...
src/PughMatrix.tsx#L288 Header class variant
src/PughMatrix.tsx#L368 Cell class
src/PughMatrix.tsx#L426 Total cell class
src/PughMatrix.tsx#L483 Another class computation

Fix: Extract to a helper function or use clsx:

const cellClass = clsx('pugh-tool-header', {
  'pugh-winner-header': isWinner(tool.id),
  'pugh-highlight-header': isHighlighted(tool.id),
});

CSS contrast failures (2)

File Line
src/pugh-matrix.css#L480 Text doesn't meet WCAG AA 4.5:1 ratio
src/pugh-matrix.css#L486 Text doesn't meet WCAG AA 4.5:1 ratio

Fix: Use Radix color step pairings that guarantee AA contrast (e.g., step 11 text on step 3 background).

React Hooks in Storybook render (2)

Covered above in Bugs section — same root cause.


🟢 Minor Code Smells (25)

Readonly props (4)

Component props interfaces should use Readonly<>:

File Line
src/PughMatrix.tsx#L63 PughMatrixProps
src/BranchSelector.tsx#L8 BranchSelectorProps
src/PughMatrix.stories.tsx#L109 Story wrapper props
src/store/PughStoreProvider.tsx#L10 Provider props

Prefer Number.isNaN over isNaN (3)

File Lines
src/PughMatrix.tsx L164, L175, L183

Prefer String#replaceAll() over String#replace() (2)

File Lines
src/PughMatrix.tsx L221, L228

Prefer globalThis over window (8)

File Lines
src/persist/localStoragePersister.ts L7, L8, L17, L18, L27, L28, L41, L42

Unexpected negated conditions (4)

File Lines
src/PughMatrix.tsx L262, L310, L368, L445
src/persist/localStoragePersister.ts L7

Unused import + unnecessary assertion (2)

File Line Issue
src/store/createPughStore.ts#L5 Unused import PughDomainState
src/store/createPughStore.ts#L63 Unnecessary type assertion

Acceptance Criteria

  • 0 bugs (a11y keyboard + hooks)
  • 0 critical code smells (cognitive complexity, nesting)
  • 0 major code smells (ARIA roles, ternaries, contrast)
  • Minor smells addressed (globalThis, Number.isNaN, Readonly, etc.)
  • Re-scan confirms clean dashboard

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions