Skip to content

Comments

Fix sonar unnecessary assertion#3226

Closed
hussainjamal760 wants to merge 5 commits intoOWASP:mainfrom
hussainjamal760:fix-sonar-unnecessary-assertion
Closed

Fix sonar unnecessary assertion#3226
hussainjamal760 wants to merge 5 commits intoOWASP:mainfrom
hussainjamal760:fix-sonar-unnecessary-assertion

Conversation

@hussainjamal760
Copy link
Contributor

Fixes #3225

Description
This PR addresses widespread ESLint warnings and errors across the frontend codebase, specifically targeting redundant TypeScript assertions and configuration mismatches. The primary goal was to align the code with TypeScript's type inference capabilities and ensure a clean, error-free linting process.

Key Changes

  1. Core Configuration Fixes
    eslint.config.mjs:

Added lighthouserc.js, postcss.config.js, and tailwind.config.js to the ignores list. This prevents ESLint from trying to parse non-project JavaScript files using TypeScript rules, which was causing parsing errors.

Configured @typescript-eslint/no-unnecessary-condition to function correctly with the project's type-checking settings.

tsconfig.json:

Ensured strictNullChecks is properly recognized so that the linting engine can accurately identify truly "unnecessary" conditions.

  1. Code Refactoring (Redundant Assertions)
    Following the "Non-null Assertion" and "Redundant Cast" issue, I have refactored multiple files (including ChapterMap.tsx, ProjectsDashboardDropDown.tsx, etc.) to:

Removed Redundant Casts: Removed (x as Type) where TypeScript already inferred the type correctly via type guards (e.g., if (x) or typeof x === 'string').

Removed Unnecessary Non-null Assertions: Removed the ! operator where the compiler already guaranteed that the value was not null or undefined.

Removed Unnecessary Optional Chaining: Cleaned up ?. usage on values that are guaranteed to be present, reducing code noise.

  1. Specific File Fixes
    src/components/UserMenu.tsx: Removed the unused ExtendedSession import which was causing a blocking lint error.

Unit Tests: Standardized assertions in .test.tsx files to maintain test reliability while satisfying linting requirements.

Why these changes?
Code Clarity: Removing redundant as and ! makes the code cleaner and easier to read.

Leveraging TS Inference: Trusting TypeScript's built-in inference is safer than manual casting.

Build Pipeline: These changes resolve over 780 warnings and errors, allowing the CI/CD pipeline to pass with --max-warnings=0.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 6, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Added defensive null and undefined checks throughout the application to prevent runtime errors when data is missing
    • Improved fallback values for user interface elements to gracefully handle incomplete data
    • Enhanced error handling for API and environment variable configuration
  • Tests

    • Strengthened test type safety with stricter TypeScript typing
    • Added configuration for enhanced linting rules
  • Chores

    • Enabled strict null checking in TypeScript compilation

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Comprehensive TypeScript type-safety refactor enabling strict null checks in tsconfig.json, updating ESLint rules for stricter type checking, and refactoring ~80+ files across components, pages, hooks, tests, and utilities to replace non-null assertions with proper null/undefined handling, default values, and defensive guards.

Changes

Cohort / File(s) Summary
Configuration & Type Setup
frontend/tsconfig.json, frontend/eslint.config.mjs, frontend/src/utils/env.server.ts
Added strictNullChecks: true; configured ESLint with TypeScript project references, added rules (@typescript-eslint/no-unnecessary-type-assertion: error, @typescript-eslint/no-non-null-assertion: error); removed non-null assertions from GITHUB_CLIENT_ID/SECRET, changing types from string to string | undefined
Page-Level Data Handling
frontend/src/app/members/[memberKey]/page.tsx, frontend/src/app/chapters/[chapterKey]/page.tsx, frontend/src/app/board/[year]/candidates/page.tsx, frontend/src/app/organizations/[organizationKey]/repositories/[repositoryKey]/page.tsx, frontend/src/app/page.tsx
Initialized state to null instead of undefined; added query skip conditions; cast GraphQL data with explicit types and safe fallbacks; added numeric/string defaults (e.g., ?? 0, || ''); added render guards for nullable data; introduced new public types (e.g., RepositoryData)
Dynamic Layout/Metadata Pages
frontend/src/app/*/layout.tsx (chapters, committees, snapshots, members, organizations, projects)
Changed generateMetadata fallback return from null to {} for consistency; added title fallbacks (e.g., 'Member Profile', 'Organization Profile')
Component-Level Guards
frontend/src/components/CardDetailsPage.tsx, frontend/src/components/ChapterMap.tsx, frontend/src/components/MultiSearch.tsx, frontend/src/components/SingleModuleCard.tsx, frontend/src/components/UserMenu.tsx
Added default prop values to prevent undefined usage; guarded array access with || []; removed unnecessary type assertions; updated session/admin checks with optional chaining; simplified type handling
Utility & Form Components
frontend/src/components/RepositoryCard.tsx, frontend/src/components/UserCard.tsx, frontend/src/components/Leaders.tsx, frontend/src/components/HealthMetrics.tsx, frontend/src/components/InfoBlock.tsx, frontend/src/components/MetricsCard.tsx, frontend/src/components/NavDropDown.tsx, frontend/src/components/ModuleForm.tsx
Added default parameter values; applied nullish coalescing (?? 0, ?? []); hardened numeric/string defaults; improved type annotations on refs and handlers
Server & Auth Files
frontend/src/app/api/auth/[...nextauth]/route.ts, frontend/src/server/apolloClient.ts, frontend/src/server/fetchCsrfToken.ts, frontend/src/server/fetchCsrfTokenServer.ts, frontend/src/hooks/useDjangoSession.ts
Added type imports (Profile, GithubProfile); typed providers array; guarded env vars with defaults (?? ''); added preflight CSRF_URL validation; ensured mutation payloads always have string values
Instrumentation & Sentry
frontend/src/instrumentation-client.ts, frontend/src/sentry.server.config.ts
Added environment fallbacks: (ENVIRONMENT ?? 'development').toLowerCase(); added replaysSessionSampleRate: 0.5 to Sentry config
Hooks & Utilities
frontend/src/hooks/useSearchPage.ts, frontend/src/utils/structuredData.ts, frontend/src/wrappers/provider.tsx, frontend/src/types/card.ts
Enhanced URL param parsing with initial state from searchParams; updated structured data fallbacks; exported Stats type; added apolloClient guard in providers
Mentorship & Module Pages
frontend/src/app/mentorship/programs/*/page.tsx, frontend/src/app/my/mentorship/programs/*/page.tsx, frontend/src/app/my/mentorship/programs/[programKey]/modules/create/page.tsx
Added explicit type casts (Program, Module, Contributor[]); applied safe defaults for status, dates, counts; hardened admin/module property access; added CreateModuleInput typing
Community & Dashboard Pages
frontend/src/app/community/snapshots/[id]/page.tsx, frontend/src/app/community/snapshots/page.tsx, frontend/src/app/about/page.tsx, frontend/src/app/projects/dashboard/metrics/*
Updated snapshot/project handling with explicit casts and safe fallbacks; introduced level mapping type safety; broadened leader data construction; added HealthFilterValue/LevelFilterValue types; applied ?? 0 defaults for metrics
Layout & Structure Pages
frontend/src/app/chapters/page.tsx, frontend/src/app/committees/[committeeKey]/page.tsx, frontend/src/app/contribute/page.tsx, frontend/src/app/layout.tsx, frontend/src/app/organizations/page.tsx, frontend/src/app/projects/page.tsx
Applied safe array defaults (|| [], ?? []); added string fallbacks for titles/summaries; moved GoogleAnalytics inside body with GTM_ID || '' guard
Component Test Files
frontend/__tests__/unit/components/ChapterMap.test.tsx, frontend/__tests__/unit/components/BarChart.test.tsx, frontend/__tests__/unit/components/CardDetailsPage.test.tsx, frontend/__tests__/unit/components/Footer.test.tsx, frontend/__tests__/unit/components/ItemCardList.test.tsx, frontend/__tests__/unit/components/LeadersList.test.tsx, frontend/__tests__/unit/components/LineChart.test.tsx
Updated type casts and assertions (e.g., as unknown as Type); replaced nulls with explicitly typed values; added non-null assertions (!) where appropriate; refactored mocks to use explicit types (e.g., React.ReactNode, React.ElementType)
Additional Test Files
frontend/__tests__/unit/components/LoginPageContent.test.tsx, frontend/__tests__/unit/components/Modal.test.tsx, frontend/__tests__/unit/components/RecentIssues.test.tsx, frontend/__tests__/unit/components/RecentPullRequests.test.tsx, frontend/__tests__/unit/components/RecentRelease.test.tsx, frontend/__tests__/unit/components/Release.test.tsx, frontend/__tests__/unit/components/RepositoryCard.test.tsx, frontend/__tests__/unit/components/Search.test.tsx
Applied type casts for edge cases (null/undefined to proper types); used non-null assertions on DOM queries; adjusted Image style props (boolean to ternary with object fallback)
Page & Utility Tests
frontend/__tests__/unit/components/SecondaryCard.test.tsx, frontend/__tests__/unit/components/UserMenu.test.tsx, frontend/__tests__/unit/components/ContributionHeatmap.test.tsx, frontend/__tests__/unit/pages/About.test.tsx, frontend/__tests__/unit/pages/CreateProgram.test.tsx, frontend/__tests__/unit/pages/Header.test.tsx, frontend/__tests__/unit/pages/ModuleIssueDetailsPage.test.tsx, frontend/__tests__/unit/pages/RepositoryDetails.test.tsx, frontend/__tests__/unit/utils/pluralize.test.ts
Updated mocks to use ExtendedSession with explicit types; added non-null assertions on DOM elements; refactored test cases to data-driven loops; type-cast variables and assertions; ensured test data matches strict types
GraphQL & Form Utilities
frontend/graphql-codegen.ts, frontend/src/components/forms/shared/formValidationUtils.ts
Changed catch error from returning undefined to throwing explicit error; minor boolean coercion update in validation rules

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • kasya
  • arkid15r
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix sonar unnecessary assertion' clearly reflects the main objective of the PR: removing redundant type assertions.
Description check ✅ Passed The description is detailed and directly related to the changeset, explaining the fixes for ESLint warnings, redundant assertions, and configuration updates.
Linked Issues check ✅ Passed The PR addresses all requirements from issue #3225: removes redundant type assertions, fixes ESLint config, updates tsconfig.json strictNullChecks, and eliminates unnecessary assertions throughout the codebase.
Out of Scope Changes check ✅ Passed All changes directly address the objectives of fixing redundant type assertions and ESLint configuration issues specified in issue #3225; no out-of-scope modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Collaborator

@arkid15r arkid15r left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the PR template and run code quality check locally.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI Agents
In @frontend/tsconfig.json:
- Around line 18-22: The tsconfig contains a duplicate "strict" key so the later
"strict": false overrides your new "strict": true; locate the duplicate entries
in the tsconfig.json (the "strict" property you added and the existing "strict":
false) and remove the undesired one — preferably delete the existing "strict":
false so the file only has "strict": true (or, if you intend to keep strict
disabled, remove the newly added "strict": true and related comment instead).
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae2b40c and b4cb0c9.

📒 Files selected for processing (15)
  • frontend/__tests__/unit/components/ChapterMap.test.tsx
  • frontend/__tests__/unit/components/DonutBarChart.test.tsx
  • frontend/__tests__/unit/data/mockCommitteeDetailsData.ts
  • frontend/__tests__/unit/data/mockSnapshotData.ts
  • frontend/__tests__/unit/pages/ApiKeysPage.test.tsx
  • frontend/eslint.config.mjs
  • frontend/src/app/members/[memberKey]/page.tsx
  • frontend/src/components/CardDetailsPage.tsx
  • frontend/src/components/ChapterMap.tsx
  • frontend/src/components/MultiSearch.tsx
  • frontend/src/components/SingleModuleCard.tsx
  • frontend/src/components/UserMenu.tsx
  • frontend/src/hooks/useSearchPage.ts
  • frontend/src/utils/env.server.ts
  • frontend/tsconfig.json
🧰 Additional context used
🧠 Learnings (11)
📓 Common learnings
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2178
File: frontend/src/app/snapshots/[id]/page.tsx:0-0
Timestamp: 2025-09-21T17:04:48.154Z
Learning: User rudransh-shrivastava confirmed that suggested type safety improvements during Apollo Client migration were no longer relevant, reinforcing their preference to keep migration PRs focused on core migration changes rather than additional improvements.
📚 Learning: 2025-07-12T17:36:57.255Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/__tests__/unit/pages/createProgram.test.tsx:70-86
Timestamp: 2025-07-12T17:36:57.255Z
Learning: When testing React page components that use mocked form components, validation logic should be tested at the form component level, not the page level. Page-level tests should focus on authentication, role checking, submission handling, and navigation logic.

Applied to files:

  • frontend/__tests__/unit/pages/ApiKeysPage.test.tsx
📚 Learning: 2025-11-17T16:47:05.578Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:427-427
Timestamp: 2025-11-17T16:47:05.578Z
Learning: In the frontend test files for the OWASP/Nest repository, `expect(true).toBe(true)` no-op assertions may be intentionally added as workarounds when ESLint rule jest/expect-expect doesn't detect expectations inside helper functions or waitFor callbacks. These can be resolved by configuring the ESLint rule's assertFunctionNames option to recognize custom assertion helpers instead of flagging them as redundant.

Applied to files:

  • frontend/__tests__/unit/pages/ApiKeysPage.test.tsx
📚 Learning: 2025-07-13T11:34:31.823Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/src/app/mentorship/programs/page.tsx:14-14
Timestamp: 2025-07-13T11:34:31.823Z
Learning: In the Next.js frontend mentorship application, there are two distinct types for authentication-related data: ExtendedSession for useSession hook (containing accessToken and user.login properties) and UserRolesData for useUserRoles hook (containing currentUserRoles.roles array). The correct access pattern for GitHub username is `(session as ExtendedSession)?.user?.login`.

Applied to files:

  • frontend/src/components/UserMenu.tsx
  • frontend/src/components/CardDetailsPage.tsx
  • frontend/src/components/SingleModuleCard.tsx
📚 Learning: 2025-07-11T15:46:58.233Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/src/components/UserMenu.tsx:56-60
Timestamp: 2025-07-11T15:46:58.233Z
Learning: In the UserMenu component's handleLogout function, both logout() and signOut() calls are intentionally kept despite the useLogout hook already calling signOut internally, as this is necessary for the specific implementation requirements.

Applied to files:

  • frontend/src/components/UserMenu.tsx
📚 Learning: 2025-07-03T03:08:03.290Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 1703
File: frontend/src/components/BarChart.tsx:33-46
Timestamp: 2025-07-03T03:08:03.290Z
Learning: In the OWASP Nest project's BarChart component (frontend/src/components/BarChart.tsx), the days and requirements arrays are guaranteed to always have the same length in their use cases, so input validation for array length matching is not needed.

Applied to files:

  • frontend/__tests__/unit/components/DonutBarChart.test.tsx
📚 Learning: 2025-06-20T16:12:59.256Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 1633
File: frontend/src/components/HealthMetrics.tsx:30-30
Timestamp: 2025-06-20T16:12:59.256Z
Learning: In the DetailsCard component (frontend/src/components/CardDetailsPage.tsx), there's a length check before rendering HealthMetrics: `healthMetricsData.length > 0`. This ensures that when HealthMetrics is rendered, the data array has at least one element, making accessing data[0] safe within the HealthMetrics component.

Applied to files:

  • frontend/src/components/CardDetailsPage.tsx
📚 Learning: 2025-06-20T16:12:59.256Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 1633
File: frontend/src/components/HealthMetrics.tsx:30-30
Timestamp: 2025-06-20T16:12:59.256Z
Learning: In the DetailsCard component (frontend/src/components/CardDetailsPage.tsx), there's a safety check that ensures HealthMetrics component is only rendered when healthMetricsData exists and has at least one element: `healthMetricsData && healthMetricsData.length > 0`. This makes accessing data[0] safe within the HealthMetrics component.

Applied to files:

  • frontend/src/components/CardDetailsPage.tsx
📚 Learning: 2025-08-31T13:47:15.861Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2155
File: frontend/src/server/queries/programsQueries.ts:81-81
Timestamp: 2025-08-31T13:47:15.861Z
Learning: In frontend/src/server/queries/programsQueries.ts, GET_PROGRAM_DETAILS is actively used in frontend/src/app/my/mentorship/programs/[programKey]/edit/page.tsx for program editing functionality and cannot be removed. It serves a different purpose than GET_PROGRAM_ADMIN_DETAILS, providing comprehensive program information needed for editing.

Applied to files:

  • frontend/src/components/CardDetailsPage.tsx
  • frontend/src/components/SingleModuleCard.tsx
📚 Learning: 2025-09-29T06:02:35.566Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2178
File: frontend/src/components/SingleModuleCard.tsx:54-54
Timestamp: 2025-09-29T06:02:35.566Z
Learning: In the Module type from types/mentorship.ts, the experienceLevel field is required (experienceLevel: ExperienceLevelEnum), not optional, so null/undefined checks are not needed when accessing this field.

Applied to files:

  • frontend/src/components/SingleModuleCard.tsx
📚 Learning: 2025-07-12T17:14:28.536Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/src/app/mentorship/programs/[programKey]/edit/page.tsx:90-128
Timestamp: 2025-07-12T17:14:28.536Z
Learning: Both ProgramForm (programCard.tsx) and ModuleForm (mainmoduleCard.tsx) components already implement HTML validation using the `required` attribute on form fields. The browser's native validation prevents form submission and displays error messages when required fields are empty, eliminating the need for additional JavaScript validation before GraphQL mutations.

Applied to files:

  • frontend/src/components/SingleModuleCard.tsx
🧬 Code graph analysis (4)
frontend/src/app/members/[memberKey]/page.tsx (1)
frontend/src/utils/helpers/githubHeatmap.ts (1)
  • fetchHeatmapData (40-89)
frontend/src/components/UserMenu.tsx (1)
frontend/src/app/api/auth/[...nextauth]/route.ts (1)
  • session (96-106)
frontend/src/components/CardDetailsPage.tsx (1)
frontend/src/types/auth.ts (1)
  • ExtendedSession (8-20)
frontend/src/components/SingleModuleCard.tsx (2)
backend/apps/mentorship/api/internal/nodes/program.py (1)
  • admins (32-34)
frontend/src/types/auth.ts (1)
  • ExtendedSession (8-20)
🪛 Biome (2.1.2)
frontend/tsconfig.json

[error] 19-19: The key strict was already declared.

This where a duplicated key was declared again.

If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.

(lint/suspicious/noDuplicateObjectKeys)

🔇 Additional comments (20)
frontend/src/hooks/useSearchPage.ts (1)

107-107: LGTM! Type assertion correctly removed.

The removal of as number is appropriate here. After the type guard on line 105, TypeScript can properly infer that response.totalPages is already a number, making the explicit assertion redundant. This change improves code clarity by relying on TypeScript's type inference.

frontend/src/components/MultiSearch.tsx (1)

65-65: LGTM: Unnecessary type assertion correctly removed.

TypeScript already infers the correct type from filteredEvents (which is Event[]), making the explicit cast redundant.

frontend/src/components/ChapterMap.tsx (2)

44-44: LGTM: Numeric literal normalized.

Changing 1.0 to 1 is functionally equivalent and aligns with the PR's objective to remove unnecessary zero fractions.


64-66: LGTM: Unnecessary type assertion removed.

The originalEvent property of L.LeafletMouseEvent is already properly typed, making the explicit MouseEvent cast redundant.

frontend/src/app/members/[memberKey]/page.tsx (1)

60-60: LGTM: Unnecessary type assertions correctly removed.

The memberKey parameter from useParams<{ memberKey: string }>() is already typed as string, making both as string casts redundant.

Also applies to: 66-66

frontend/src/components/CardDetailsPage.tsx (1)

129-131: LGTM: Unnecessary type assertion removed.

Both admin.login and (data as ExtendedSession)?.user?.login are already typed as string | undefined per the ExtendedSession interface, making the final as string cast redundant. The comparison handles optional values correctly.

Based on learnings, the ExtendedSession type assertion is the established pattern for accessing GitHub username from session data in this codebase.

frontend/src/components/SingleModuleCard.tsx (1)

31-31: LGTM! Removed truly redundant type assertion.

The as string cast was unnecessary since login is already typed as string | undefined in the ExtendedSession type. The comparison works correctly without the explicit cast.

frontend/__tests__/unit/components/ChapterMap.test.tsx (1)

144-144: LGTM! Consistent numeric literal format.

The change from 1.0 to 1 is a stylistic improvement that aligns with the codebase convention of using integer literals where decimals are unnecessary. Both values are functionally identical in JavaScript.

frontend/__tests__/unit/data/mockSnapshotData.ts (1)

88-88: LGTM! Consistent numeric literal format.

Removing the unnecessary decimal .0 improves code consistency. Both 1727353371.0 and 1727353371 are functionally identical in JavaScript.

frontend/src/components/UserMenu.tsx (2)

10-10: ExtendedSession import is not needed—the session is already properly typed by useDjangoSession() hook.

The useDjangoSession() hook already returns session?: ExtendedSession, so the session variable has the correct type without needing an explicit import or type cast. The commented-out import is correctly omitted. Other files that use useSession() directly (without the hook) need explicit casting like (session as ExtendedSession)?.user?.login, but UserMenu.tsx benefits from the hook's built-in typing.

Likely an incorrect or invalid review comment.


22-22: No TypeScript errors without the ExtendedSession cast.

The useDjangoSession() hook explicitly returns session?: ExtendedSession (see frontend/src/hooks/useDjangoSession.ts line 12), so TypeScript correctly infers that accessing session?.user?.isLeader and session?.user?.isOwaspStaff is valid. The build and type-checking pass successfully. While other components in the codebase use explicit casts for clarity (e.g., frontend/src/app/my/mentorship/page.tsx), they are not required here because type safety is guaranteed by the hook's return type annotation. The commented-out import is correct.

frontend/src/utils/env.server.ts (1)

1-2: No action needed — these constants are not imported or used downstream.

The exported GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET constants are not imported anywhere in the codebase. The only exported constant that is used downstream is IS_GITHUB_AUTH_ENABLED, which already correctly handles undefined values through Boolean(GITHUB_CLIENT_ID && GITHUB_CLIENT_SECRET). When either environment variable is undefined, the Boolean expression evaluates to false, which is the correct behavior.

Likely an incorrect or invalid review comment.

frontend/__tests__/unit/components/DonutBarChart.test.tsx (1)

340-340: LGTM: Integer literal normalization.

The update correctly reflects that values rounding to whole numbers (1000000.0 → 1000000) should use integer literal syntax. The test expectation remains mathematically correct.

frontend/__tests__/unit/data/mockCommitteeDetailsData.ts (1)

4-4: LGTM: Removed unnecessary decimal notation.

Unix timestamps are integers, so the .0 suffix was redundant. This cleanup improves code clarity.

frontend/__tests__/unit/pages/ApiKeysPage.test.tsx (2)

236-237: Verify type safety after removing HTMLInputElement assertions.

Similar to the previous concern, removing the as HTMLInputElement casts (lines 236, 339) while accessing the .value property (lines 237, 340) may compromise type safety.

Ensure TypeScript strict mode accepts these changes without errors.

Also applies to: 339-340


190-191: Type safety concern: accessing .value without type assertions, but current tsconfig won't catch it.

The code accesses .value on HTMLElement returned by screen.getByLabelText() without type assertions. Although TypeScript compilation succeeds due to "strict": false in tsconfig.json, this is technically unsafe typing. The getByLabelText() method returns HTMLElement, which lacks the .value property—only HTMLInputElement has it.

If strict type checking is enabled (as mentioned in the PR description), this will fail. Either restore the as HTMLInputElement assertions, or ensure "strict": false is intentional if you're accepting loose typing.

Applies to lines 190–195 and 339–340.

frontend/eslint.config.mjs (4)

38-40: LGTM: Added ignores for configuration files.

Excluding these non-TypeScript configuration files from ESLint parsing prevents unnecessary parsing errors.


55-58: LGTM: Enabled type-aware linting.

Adding project and tsconfigRootDir to parser options enables type-aware linting rules, which is necessary for the newly added @typescript-eslint rules to function correctly.


184-189: LGTM: Reasonable test file override.

Disabling no-non-null-assertion for test files is appropriate, as tests often need to assert values are non-null when working with test fixtures and mocked data.


97-99: ESLint TypeScript rules configuration verified.

The three rules are properly configured and structured:

  • no-unnecessary-type-assertion and no-non-null-assertion set to error in source files for strict type safety
  • no-non-null-assertion is explicitly disabled for test files (**/*.test.ts, **/*.test.tsx), which aligns with common testing patterns where type assertions are used as workarounds
  • no-unnecessary-condition disabled (likely due to false positives with optional chaining patterns like obj?.prop and nullish coalescing)

The codebase uses type assertions appropriately (e.g., casting API response types, narrowing discriminated unions). No evidence of problematic violations or false positives.

Comment on lines 18 to 22

"strict": true,
// OR if you don't want full strict mode:
"strictNullChecks": true,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Duplicate strict key renders new configuration ineffective.

The "strict": true added at line 19 is overridden by "strict": false at line 41. In JSON, duplicate keys result in only the last value being used, so strict mode remains disabled despite this change. This defeats the PR's objective to enable stricter type-checking.

🔎 Proposed fix

Option 1: Enable strict mode (recommended)

Remove the old "strict": false at line 41 and clean up the comment:

-    
-    "strict": true, 
-    // OR if you don't want full strict mode:
-    "strictNullChecks": true,
-    
+    "strict": true,
     "baseUrl": ".",
     "plugins": [
       {
@@ -38,7 +35,6 @@
       "types/*": ["src/types/*"],
       "utils/*": ["src/utils/*"],
       "wrappers/*": ["src/wrappers/*"]
-    },
-    "strict": false
+    }
   },

Option 2: Keep strict mode disabled

If strict mode should remain off, remove lines 19-21:

     "inlineSources": true,
-    
-    "strict": true, 
-    // OR if you don't want full strict mode:
-    "strictNullChecks": true,
-    
     "baseUrl": ".",

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome (2.1.2)

[error] 19-19: The key strict was already declared.

This where a duplicated key was declared again.

If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.

(lint/suspicious/noDuplicateObjectKeys)

🤖 Prompt for AI Agents
In @frontend/tsconfig.json around lines 18 - 22, The tsconfig contains a
duplicate "strict" key so the later "strict": false overrides your new "strict":
true; locate the duplicate entries in the tsconfig.json (the "strict" property
you added and the existing "strict": false) and remove the undesired one —
preferably delete the existing "strict": false so the file only has "strict":
true (or, if you intend to keep strict disabled, remove the newly added
"strict": true and related comment instead).


"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-non-null-assertion": "error",
"@typescript-eslint/no-unnecessary-condition": "off",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why 'off' for this?

'error',
{
patterns: ['.*'], // Disable relative imports.
patterns: ['.*'],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep existing comments.

@arkid15r
Copy link
Collaborator

arkid15r commented Jan 7, 2026

@hussainjamal760 you need to address the comments in order to get this PR merged.

https://github.com/OWASP/Nest/blob/main/CONTRIBUTING.md#contributing-workflow

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
frontend/__tests__/unit/components/RecentIssues.test.tsx (2)

116-120: Remove the double assertion and use direct type annotation instead.

The repositoryName field is already declared as optional (repositoryName?: string | null) in the Issue type definition, making the double assertion as unknown as Issue unnecessary. Use the direct type annotation pattern consistently with line 133 of the same test:

const issue: Issue = { ...baseIssue, repositoryName: undefined }
render(<RecentIssues data={[issue]} />)
expect(screen.queryByText('repo')).not.toBeInTheDocument()

Alternatively, use Partial<Issue> to clarify that the object has optional properties:

const issue: Partial<Issue> = { ...baseIssue }
delete issue.repositoryName
render(<RecentIssues data={[issue]} />)
expect(screen.queryByText('repo')).not.toBeInTheDocument()

139-144: Make title optional in the Issue type definition.

The Issue type defines title: string as required, but the component defensively handles undefined titles using item.title || item.name. To remove the unnecessary type assertion and align the type definition with how the component actually handles the data, change title: string to title?: string in frontend/src/types/issue.ts.

This makes the test case valid without requiring the double assertion as unknown as Issue, matching the pattern used for other optional fields like author.

frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx (1)

190-215: Defaulting missing requirements to 0 is invalid per health metrics constraints.

Based on the codebase patterns, a requirement value of 0 is considered invalid in health metrics. The BarChart's color function (lines 85–97 in BarChart.tsx) compares metric values against requirements; when requirement is 0, any positive metric value triggers a red color indicating failure, which is semantically incorrect.

Defaulting missing requirements to 0 (lines 208–212) masks incomplete data instead of surfacing it. Consider using a sentinel value, filtering, or displaying a warning to distinguish between "requirement is missing" and "requirement is 0."

frontend/src/app/community/snapshots/[id]/layout.tsx (1)

19-27: Consider providing minimal fallback metadata instead of an empty object.

When the snapshot is not found, returning {} results in no SEO metadata being set (no title, description, keywords). This could negatively impact SEO and the user experience when sharing links to non-existent snapshots.

Suggested improvement
  return snapshot
    ? generateSeoMetadata({
        canonicalPath: `/community/snapshots/${snapshotKey}`,
        description: `${snapshot?.title} details.`,
        keywords: ['owasp', 'snapshot', snapshotKey, snapshot?.title],
        title: snapshot?.title,
      })
-   :  {}
+   : generateSeoMetadata({
+       canonicalPath: `/community/snapshots/${snapshotKey}`,
+       description: 'OWASP snapshot details.',
+       keywords: ['owasp', 'snapshot'],
+       title: 'Snapshot Not Found',
+     })
frontend/__tests__/unit/components/ItemCardList.test.tsx (1)

1-11: Remove duplicate import.

@testing-library/jest-dom is imported twice (lines 2 and 11).

🧹 Remove duplicate
 'use client'
 import '@testing-library/jest-dom'
 import React from 'react'
 import { FaCircleExclamation } from 'react-icons/fa6'
 import { render, screen, cleanup } from 'wrappers/testUtil'
 import type { Issue } from 'types/issue'
 import type { Milestone } from 'types/milestone'
 import type { PullRequest } from 'types/pullRequest'
 import type { Release } from 'types/release'
 import ItemCardList from 'components/ItemCardList'
-import '@testing-library/jest-dom'
frontend/src/app/my/mentorship/programs/create/page.tsx (1)

75-84: Potential type error with err?.message on unknown catch parameter.

With strict TypeScript settings, err in catch blocks is typed as unknown, so err?.message may cause a type error. Consider narrowing the error type.

🛠️ Suggested fix
     } catch (err) {
+      const errorMessage = err instanceof Error ? err.message : 'Unable to complete the requested operation.'
       addToast({
-        description: err?.message || 'Unable to complete the requested operation.',
+        description: errorMessage,
         title: 'GraphQL Request Failed',
         timeout: 3000,
         shouldShowTimeoutProgress: true,
         color: 'danger',
         variant: 'solid',
       })
     }
frontend/src/app/my/mentorship/programs/[programKey]/modules/create/page.tsx (1)

138-146: Unsafe access to err.message on caught exception.

In strict TypeScript mode (which this PR enables), err in a catch block is typed as unknown. Accessing .message directly will cause a type error.

🔧 Proposed fix
     } catch (err) {
+      const errorMessage = err instanceof Error ? err.message : 'Something went wrong while creating the module.'
       addToast({
         title: 'Creation Failed',
-        description: err.message || 'Something went wrong while creating the module.',
+        description: errorMessage,
         color: 'danger',
         variant: 'solid',
         timeout: 4000,
       })
     }
🤖 Fix all issues with AI agents
In @frontend/__tests__/unit/components/LoginPageContent.test.tsx:
- Around line 261-264: The test object for the mocked useSession has a
syntax/formatting mistake and an unclear status value; fix the object passed to
the mock (remove the duplicated/malformed "data: null" fragment so there is a
single data: null property) and replace the confusing status 'unauthenticated as
authenticated' with either a valid SessionStatus (e.g., 'unauthenticated' or
'authenticated') or an explicit invalid/edge value you intend to test (e.g.,
'invalid' or undefined), keeping the cast to ReturnType<typeof useSession> and
leaving update: jest.fn() as-is so the test verifies the component behavior for
that explicit status.

In @frontend/__tests__/unit/components/Modal.test.tsx:
- Line 171: The test uses a double cast `as unknown as Parameters<typeof
DialogComp>[0]` which defeats TypeScript checks; replace this by making
`minimalProps` match the component props or explicitly typing it as a partial of
the component props (e.g., `Partial<Parameters<typeof DialogComp>[0]>`) and then
render with `render(<DialogComp {...minimalProps} />)`, or if you intentionally
want to pass incorrect props, annotate the render line with `//
@ts-expect-error` and a short reason; update the `minimalProps` declaration (not
the render call) or add the `@ts-expect-error` comment near the
`render(<DialogComp ...>)` call accordingly.

In @frontend/__tests__/unit/components/RecentRelease.test.tsx:
- Around line 187-191: The test contains contradictory assertions on the same
element: in RecentRelease.test.tsx the variable gridContainer is first asserted
toHaveClass('grid-cols-1') and then immediately asserted
not.toHaveClass('grid-cols-1') while expecting multi-column classes; remove or
change the first assertion so the test consistently asserts the default is
multi-column. Specifically, update the assertions around gridContainer (the
.grid selector) to NOT expect 'grid-cols-1' and to expect 'md:grid-cols-2' and
'lg:grid-cols-3' only, ensuring no opposing checks remain.
- Around line 174-179: Remove the duplicate assertion on gridContainer: there
are two identical expect(gridContainer).toHaveClass('grid-cols-1') checks;
delete the redundant one (the second occurrence) so the test only asserts
gridContainer has 'grid-cols-1' once and still asserts it does not have
'md:grid-cols-2'. Use the existing gridContainer variable and keep the
not.toHaveClass assertion intact.

In @frontend/__tests__/unit/pages/ProjectDetails.test.tsx:
- Around line 188-189: The test defines const data = undefined as { project?: {
recentReleases?: unknown[]; recentIssues?: unknown[] } } | undefined which
needlessly casts undefined; replace the cast with a proper type annotation or
omit it—e.g., declare const data: { project?: { recentReleases?: unknown[];
recentIssues?: unknown[] } } | undefined = undefined or simply const data =
undefined—so the value remains undefined while keeping the intended type for the
test that relies on optional chaining when accessing
project.recentReleases/recentIssues.

In @frontend/__tests__/unit/utils/pluralize.test.ts:
- Around line 16-17: Remove the type-unsafe test cases that cast null/undefined
to number in frontend/__tests__/unit/utils/pluralize.test.ts: delete the two
expect lines that call pluralize(null as unknown as number, ...) and
pluralize(undefined as unknown as number, ...). If you actually need to support
null/undefined inputs, instead update the pluralize signature to accept count:
number | null | undefined and add a runtime guard (e.g., treat null/undefined as
not equal to 1) and then adjust tests to call pluralize with properly typed
null/undefined values rather than using double casts.

In @frontend/src/app/api/auth/[...nextauth]/route.ts:
- Around line 51-52: When initializing the GitHub provider, don’t fall back to
empty strings for NEXT_SERVER_GITHUB_CLIENT_ID or
NEXT_SERVER_GITHUB_CLIENT_SECRET; instead read them into local consts and if
IS_GITHUB_AUTH_ENABLED is true validate both values are present — if missing,
throw or log a clear error and do not call providers.push(GitHubProvider(...));
otherwise pass the actual clientId/clientSecret into GitHubProvider and keep the
existing profile(...) implementation.

In @frontend/src/app/committees/[committeeKey]/page.tsx:
- Around line 61-62: The Link usage in page.tsx is using unnecessary fallbacks
for committee.url despite the Committee type declaring url: string; remove the
fallbacks (the "|| '#'" and "|| '''") so the JSX uses committee.url directly; if
the URL can actually be optional, instead update the Committee type to make
url?: string and adjust Link to handle undefined (e.g., guard or conditional
render). Ensure changes reference the Link element and the Committee type and
remove the inconsistent fallbacks.
- Line 57: The code uses a defensive fallback "(committee.leaders || [])" for
the required field committee.leaders which contradicts the Committee type;
either remove the unnecessary fallback and use committee.leaders.join(', ')
directly (in the component where { label: 'Leaders', value: (committee.leaders
|| []).join(', ') } appears), or if the GraphQL response can actually be
null/undefined, update the Committee type to leaders?: string[] (and adjust any
consumers) to reflect optionality; pick one of these two fixes and apply it
consistently wherever committee.leaders is referenced.
- Around line 86-87: The props passed to the component use unnecessary fallbacks
for fields that are required on the Committee type: remove the `|| ''` fallbacks
from the `summary={committee.summary || ''}` and `title={committee.name || ''}`
usages in page.tsx so the component relies on the declared `Committee` type (or
alternatively, if `summary`/`name` can actually be missing, update the
`Committee` type to make them optional and handle null/undefined where used);
specifically edit the lines setting `summary` and `title` to use
`committee.summary` and `committee.name` directly (or adjust the `Committee`
interface declaration to `summary?: string`/`name?: string` and add appropriate
runtime checks).
- Line 84: The code passes a fallback for a non-optional field: remove the
unnecessary "|| []" and pass socialLinks={committee.relatedUrls} (reference:
committee.relatedUrls and the socialLinks prop in page.tsx) unless the data can
truly be absent; if it can be absent, instead update the type definition of
relatedUrls to relatedUrls?: string[] so the fallback makes sense and adjust
usages accordingly.

In @frontend/src/app/community/snapshots/[id]/page.tsx:
- Around line 30-31: The code is casting graphQLData.snapshot to SnapshotDetails
when calling setSnapshot, hiding a real type mismatch between the generated
GraphQL type (GetSnapshotDetailsQuery.snapshot) and your manual SnapshotDetails;
fix this by removing the cast and aligning types: either extend SnapshotDetails
to include the extra fields (__typename, id) and correct types for endAt/startAt
to match the generated types, or implement a mapping function (e.g.,
mapGraphqlSnapshotToSnapshotDetails) that accepts
GetSnapshotDetailsQuery.snapshot and returns a properly shaped SnapshotDetails,
also mapping nested types (Release, Project, Chapter) and converting any
any-typed dates to strings before calling setSnapshot.

In @frontend/src/app/page.tsx:
- Line 59: Remove the unsafe type assertion on setData(graphQLData as
MainPageData); instead make the component/state use the generated Apollo type or
explicitly map the data: update the state type and any useState/setData
declarations to use GetMainPageDataQuery (or GetMainPageDataQuery | null), or
perform an explicit transformation from graphQLData to the custom MainPageData
shape before calling setData (use the graphQLData variable and map/omit
__typename as needed); refer to setData, graphQLData, MainPageData and
GetMainPageDataQuery when applying the change.

In @frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx:
- Around line 43-47: Remove the unnecessary and contradictory type assertions on
health metrics: drop the "as HealthMetricsProps" on the setMetricsLatest call
and the "as HealthMetricsProps[]" on the setMetricsList call so you assign
data.project.healthMetricsLatest and data.project.healthMetricsList directly;
update the lines that call setMetricsLatest(...) and setMetricsList(...) to pass
the GraphQL-generated values without casting to
HealthMetricsProps/HealthMetricsProps[] (the generated types are structurally
compatible).

In @frontend/src/components/ModuleForm.tsx:
- Around line 68-76: The handler handleSelectChange uses a forced type assertion
for value which can be undefined; instead compute value without asserting (e.g.,
get the first element safely from Array.from(keySet) or use
keySet.values().next().value) and guard against undefined before calling
setFormData/setTouched; update references to keySet, value, setFormData, and
setTouched so TypeScript infers value as string | undefined and only pass a
confirmed string to experienceLevel.

In @frontend/src/components/UserCard.tsx:
- Line 11: The component's props use default values in destructuring (e.g.,
badgeCount = 0, unreadCount = 0, messageCount = 0) so later nullish coalescing
(badgeCount ?? 0, unreadCount ?? 0, messageCount ?? 0) is redundant; remove the
`?? 0` occurrences wherever those variables are used (lines referencing
badgeCount, unreadCount, messageCount in render/JSX) and rely on the
destructured defaults instead, keeping all existing uses of the symbols
(badgeCount, unreadCount, messageCount) unchanged otherwise.

In @frontend/src/utils/structuredData.ts:
- Line 71: The constructed `url: `${baseUrl}/members/${user.login || ''}`` can
produce an invalid trailing slash when `user.login` is missing; update the
object creation to either omit the `url` property when `user.login` is falsy
(use a conditional spread like ...(user.login ? { url:
`${baseUrl}/members/${user.login}` } : {})), or ensure `user.login` is validated
earlier and always present, or provide a meaningful fallback URL (e.g., `{ url:
`${baseUrl}/members` }` or a generic profile placeholder). Ensure you reference
`baseUrl`, `user.login`, and the `url` property in `structuredData.ts` when
making the change.
- Around line 52-54: The Person JSON-LD currently sets description, identifier,
and image to empty/placeholder values; replace those direct assignments in the
Person object so that description is only added when user.bio exists, identifier
only when user.login exists (remove the 'unknown' fallback), and image only when
user.avatarUrl exists, using the same conditional spread pattern already used
for location and interactionStatistic to omit properties with no meaningful
value.

In @frontend/src/wrappers/provider.tsx:
- Around line 21-23: The early return when apolloClient is falsy bypasses all
app providers; instead remove that return and always render SessionProvider,
HeroUIProvider, NextThemesProvider, and ToastProvider around children;
conditionally include <ApolloProvider client={apolloClient}> only if
apolloClient is non-null (or provide a lightweight fallback client), and
optionally log a warning when apolloClient is missing so GraphQL-dependent hooks
(e.g., useSession) still have provider context even if GraphQL is unavailable.
🟡 Minor comments (14)
frontend/__tests__/unit/pages/RepositoryDetails.test.tsx-160-169 (1)

160-169: Unnecessary type assertion contradicts PR objectives.

The explicit cast undefined as MockData | undefined is unnecessary since:

  • TypeScript already infers undefined correctly
  • Optional chaining works on undefined without explicit typing
  • This test would function identically with const data = undefined;

This addition of a type assertion appears to contradict the PR's stated goal of removing unnecessary type assertions.

♻️ Simplify to remove unnecessary type assertion
-     // Define a proper type without 'any'
-  type MockData = { 
-    project?: { 
-      recentReleases?: unknown[]; 
-      recentIssues?: unknown[] 
-    } 
-  };
-
-  // Explicitly type the variable and initialize as undefined
-  const data = undefined as MockData | undefined;
+  const data = undefined;
       setRecentReleasesMock(data?.project?.recentReleases)
       setRecentIssuesMock(data?.project?.recentIssues)
frontend/src/app/about/page.tsx-187-189 (1)

187-189: Sort comparator returns boolean instead of number.

The sort callback returns a boolean (true/false) instead of the required numeric values (-1, 0, 1). While this may appear to work in some JS engines, it's incorrect per the ECMAScript specification and can produce inconsistent results.

Proposed fix
              {[...(projectMetadata.recentMilestones || [])]
                .filter((milestone) => milestone && milestone.state !== 'closed')
-               .sort((a, b) => ((a?.title || '') > (b?.title || '') ? 1 : -1))
+               .sort((a, b) => (a?.title || '').localeCompare(b?.title || ''))
                .map((milestone, index) => (
frontend/__tests__/unit/components/RecentPullRequests.test.tsx-105-109 (1)

105-109: Double type assertion contradicts PR objectives; update component type signature instead.

The pattern undefined as unknown as [] bypasses TypeScript's type safety when RecentPullRequests explicitly requires data: PullRequest[]. While the underlying ItemCardList does handle undefined gracefully with its defensive check, this test's use of a double assertion is inconsistent with the PR's goal of removing unnecessary type assertions.

If undefined data is a valid scenario, update RecentPullRequests to accept data?: PullRequest[] to make this explicit in the type signature and eliminate the assertion. If undefined is not valid, remove the test case instead.

frontend/__tests__/unit/components/RecentRelease.test.tsx-386-400 (1)

386-400: Fix indentation and remove unnecessary blank lines.

Multiple formatting issues throughout this test:

  • Lines 389, 394, and 399: expect statements have excessive leading whitespace
  • Lines 390, 395, and 400: Unnecessary blank lines

These formatting inconsistencies reduce readability.

🔧 Proposed fix
 const cardElement = container!.querySelector(
   '.mb-4.w-full.rounded-lg.bg-gray-200.p-4.dark\\:bg-gray-700'
 )
-      expect(cardElement).toBeInTheDocument()
-    
+expect(cardElement).toBeInTheDocument()

 // Check for proper grid layout
 const gridElement = container!.querySelector('.grid')
-      expect(gridElement).toBeInTheDocument()
-    
+expect(gridElement).toBeInTheDocument()

 // Check for proper text styling - look for the title
 const titleElement = container!.querySelector('.text-2xl.font-semibold')
-     expect(titleElement).toBeInTheDocument()
-    
+expect(titleElement).toBeInTheDocument()

Committable suggestion skipped: line range outside the PR's diff.

frontend/__tests__/unit/components/RecentRelease.test.tsx-286-288 (1)

286-288: Fix indentation.

Line 287 has incorrect indentation—the expect statement should align with the previous line.

🔧 Proposed fix
 const gridContainer = container!.querySelector('.grid')
-      expect(gridContainer).toHaveClass('md:grid-cols-2', 'lg:grid-cols-3')
-    
+expect(gridContainer).toHaveClass('md:grid-cols-2', 'lg:grid-cols-3')

Committable suggestion skipped: line range outside the PR's diff.

frontend/src/components/MetricsCard.tsx-7-14 (1)

7-14: Enhance date validation to handle invalid date strings.

The defensive check only validates that createdAt is truthy but doesn't verify if it's a valid date string. If metric.createdAt contains an invalid date string, new Date(metric.createdAt) will return an Invalid Date object, which renders as "Invalid Date" instead of the intended "N/A" fallback.

Consider this more robust approach:

  // Defensive check for date validity
- const formattedDate = metric.createdAt 
-   ? new Date(metric.createdAt).toLocaleString('default', {
-       month: 'short',
-       day: 'numeric',
-       year: 'numeric',
-     })
-   : 'N/A'
+ const date = metric.createdAt ? new Date(metric.createdAt) : null
+ const formattedDate = date && !isNaN(date.getTime())
+   ? date.toLocaleString('default', {
+       month: 'short',
+       day: 'numeric',
+       year: 'numeric',
+     })
+   : 'N/A'

This validates both that createdAt exists and that it represents a valid date before formatting.

frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/mentees/[menteeKey]/page.tsx-50-50 (1)

50-50: Remove the type assertion — it contradicts the PR goal of removing unnecessary assertions.

Line 50 adds as MenteeDetails while the PR objective (per #3220) is to remove redundant type assertions. The GraphQL-generated type for getMenteeDetails is structurally compatible with MenteeDetails; the assertion is unnecessary and should be removed.

setMenteeDetails(data.getMenteeDetails || null)
frontend/src/app/my/mentorship/programs/[programKey]/page.tsx-128-128 (1)

128-128: Update the Program type definition to match the GraphQL return type, or remove the unnecessary cast.

The Program type defines status?: ProgramStatusEnum as optional, but the GetProgramAndModulesQuery returns status: ProgramStatusEnum as non-optional. The cast as ProgramStatusEnum masks this type inconsistency. Since the GraphQL query guarantees status is always present and DetailsCardProps accepts status?: string, the cast is unnecessary. Either update Program.status to be required (matching GraphQL) or properly handle the type mismatch without casting.

frontend/src/app/page.tsx-189-189 (1)

189-189: Problematic fallback for required URL field.

Using || '#' as a fallback for event.url creates a poor user experience. According to the Event type definition, url is a required field (not optional), so this fallback should never be needed. If it is needed at runtime, it indicates a data quality issue that should be addressed rather than masked.

A '#' link is non-functional and clicking it scrolls to the top of the page, which is confusing for users.

💡 Better approaches
  1. If url is truly required - remove the fallback and let it fail explicitly:
-  url: event.url || '#',
+  url: event.url,
  1. If url can be missing - update the type and handle gracefully:
// Update Event type
url?: string

// Handle in UI
{event.url ? (
  <CalendarButton ... />
) : (
  <span className="text-gray-400">URL not available</span>
)}
  1. If this is defensive - at least use a meaningful fallback:
-  url: event.url || '#',
+  url: event.url || '/events', // or disable the button

Committable suggestion skipped: line range outside the PR's diff.

frontend/src/app/page.tsx-94-94 (1)

94-94: Ineffective null check for geoLocData.

The condition !geoLocData will never be true because geoLocData is initialized as an empty array [] (line 54), and empty arrays are truthy in JavaScript.

If the intent is to wait until chapter data is loaded, consider checking geoLocData.length === 0 instead. However, this might not be necessary since the map component can handle an empty array gracefully.

♻️ Suggested fix if you need to wait for data
-  if (isLoading || !data || !geoLocData) {
+  if (isLoading || !data) {
     return <LoadingSpinner />
   }

Or, if you specifically need to wait for chapter data:

-  if (isLoading || !data || !geoLocData) {
+  if (isLoading || !data || geoLocData.length === 0) {
     return <LoadingSpinner />
   }
frontend/src/app/members/[memberKey]/page.tsx-124-132 (1)

124-132: Fix: Inconsistent nullish handling for numeric stats.

Lines 124, 125, and 132 use || 0, which treats 0 as falsy and replaces it with the fallback. Line 130 correctly uses ?? 0, which only falls back for null/undefined.

For count values that can legitimately be 0, use the nullish coalescing operator (??) consistently:

🔧 Suggested fix
-    { icon: FaPersonWalkingArrowRight, value: user.followersCount || 0, unit: 'Follower' },
-    { icon: FaUserPlus, value: user.followingCount || 0, unit: 'Following' },
+    { icon: FaPersonWalkingArrowRight, value: user.followersCount ?? 0, unit: 'Follower' },
+    { icon: FaUserPlus, value: user.followingCount ?? 0, unit: 'Following' },
     {
       icon: FaFolderOpen,
       pluralizedName: 'Repositories',
       unit: 'Repository',
       value: user.publicRepositoriesCount ?? 0,
     },
-    { icon: FaCodeMerge, value: user.contributionsCount || 0, unit: 'Contribution' },
+    { icon: FaCodeMerge, value: user.contributionsCount ?? 0, unit: 'Contribution' },
frontend/src/app/organizations/[organizationKey]/repositories/[repositoryKey]/page.tsx-101-103 (1)

101-103: Avoid href="#" as a fallback.

Using # as a fallback URL is an accessibility anti-pattern—it creates a link that scrolls to the top of the page. Consider conditionally rendering the link or using a more appropriate fallback.

🛠️ Suggested fix
       value: (
-        <Link href={repository.url || '#'} className="text-blue-400 hover:underline">
-          {repository.url || ''}
-        </Link>
+        repository.url ? (
+          <Link href={repository.url} className="text-blue-400 hover:underline">
+            {repository.url}
+          </Link>
+        ) : (
+          <span className="text-gray-400">N/A</span>
+        )
       ),
frontend/src/app/api/auth/[...nextauth]/route.ts-99-109 (1)

99-109: Token fields may be undefined; unsafe casts could propagate incorrect values.

The as string and as boolean casts assume token fields are always populated, but they may be undefined if the JWT callback didn't set them (e.g., sign-in without GitHub, or corrupted token). Consider using nullish coalescing with defaults.

🛠️ Suggested safer approach
     async session({ session, token }) {
       const extendedSession = session as ExtendedSession
-      extendedSession.accessToken = token.accessToken as string
+      extendedSession.accessToken = (token.accessToken as string | undefined) ?? ''

       if (extendedSession.user) {
-        extendedSession.user.login = token.login as string
-        extendedSession.user.isMentor = token.isMentor as boolean
-        extendedSession.user.isLeader = token.isLeader as boolean
-        extendedSession.user.isOwaspStaff = token.isOwaspStaff as boolean
+        extendedSession.user.login = (token.login as string | undefined) ?? ''
+        extendedSession.user.isMentor = (token.isMentor as boolean | undefined) ?? false
+        extendedSession.user.isLeader = (token.isLeader as boolean | undefined) ?? false
+        extendedSession.user.isOwaspStaff = (token.isOwaspStaff as boolean | undefined) ?? false
       }
       return extendedSession
     },
frontend/src/components/InfoBlock.tsx-24-24 (1)

24-24: Empty string fallback produces confusing display text when unit is undefined.

The unit ?? '' correctly addresses the type mismatch (optional unit vs required singular parameter), but when unit is undefined, the pluralize function produces just 's' (since '' + 's' = 's'), resulting in user-facing text like "5 s" or "No s".

While current usage in CardDetailsPage always provides unit, the component signature allows it to be undefined. Consider using a more descriptive default like unit ?? 'item' to prevent confusing output if the component is used without unit in the future.

Comment on lines +261 to +264
status: 'unauthenticated as authenticated', // Explicitly fixed data: null,
data:null,
update: jest.fn(),
})
} as unknown as ReturnType<typeof useSession>)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix syntax error and clarify invalid status test.

Lines 261-262 appear to have a formatting/syntax error. Line 261 contains both a status assignment and what looks like a partial data: null assignment in the same line, then line 262 sets data again. The status value 'unauthenticated as authenticated' is confusing and not a valid SessionStatus.

If the intent is to test how the component handles an invalid/undefined session status (which is a valid test case), use a clearer approach:

🔧 Proposed fix for cleaner edge case testing
    it('handles undefined session status gracefully', () => {
      mockUseSession.mockReturnValue({
-       status: 'unauthenticated as authenticated', // Explicitly fixed        data: null,
-       data:null,
+       status: undefined as any, // Test invalid/undefined status
+       data: null,
        update: jest.fn(),
      } as unknown as ReturnType<typeof useSession>)

      render(<LoginPageContent isGitHubAuthEnabled={true} />)

-     // Should render the login form as fallback
+     // Should render the login form as fallback when status is invalid
      expect(screen.getByText('Welcome back')).toBeInTheDocument()
    })

Alternatively, if testing a non-standard string status:

    it('handles undefined session status gracefully', () => {
      mockUseSession.mockReturnValue({
-       status: 'unauthenticated as authenticated', // Explicitly fixed        data: null,
-       data:null,
+       status: 'invalid' as any, // Test unrecognized status string
+       data: null,
        update: jest.fn(),
      } as unknown as ReturnType<typeof useSession>)

      render(<LoginPageContent isGitHubAuthEnabled={true} />)

-     // Should render the login form as fallback
+     // Should render the login form as fallback for unrecognized status
      expect(screen.getByText('Welcome back')).toBeInTheDocument()
    })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
status: 'unauthenticated as authenticated', // Explicitly fixed data: null,
data:null,
update: jest.fn(),
})
} as unknown as ReturnType<typeof useSession>)
status: undefined as any, // Test invalid/undefined status
data: null,
update: jest.fn(),
} as unknown as ReturnType<typeof useSession>)
Suggested change
status: 'unauthenticated as authenticated', // Explicitly fixed data: null,
data:null,
update: jest.fn(),
})
} as unknown as ReturnType<typeof useSession>)
status: 'invalid' as any, // Test unrecognized status string
data: null,
update: jest.fn(),
} as unknown as ReturnType<typeof useSession>)
🤖 Prompt for AI Agents
In @frontend/__tests__/unit/components/LoginPageContent.test.tsx around lines
261 - 264, The test object for the mocked useSession has a syntax/formatting
mistake and an unclear status value; fix the object passed to the mock (remove
the duplicated/malformed "data: null" fragment so there is a single data: null
property) and replace the confusing status 'unauthenticated as authenticated'
with either a valid SessionStatus (e.g., 'unauthenticated' or 'authenticated')
or an explicit invalid/edge value you intend to test (e.g., 'invalid' or
undefined), keeping the cast to ReturnType<typeof useSession> and leaving
update: jest.fn() as-is so the test verifies the component behavior for that
explicit status.

Comment on lines +174 to 179
const gridContainer = container!.querySelector('.grid');
expect(gridContainer).toHaveClass('grid-cols-1');

expect(gridContainer).toHaveClass('grid-cols-1')
expect(gridContainer).not.toHaveClass('md:grid-cols-2')
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Remove duplicate assertion.

Lines 175 and 177 contain identical assertions checking for grid-cols-1. This appears to be a copy-paste error that should be removed.

🔧 Proposed fix
 const gridContainer = container!.querySelector('.grid');
 expect(gridContainer).toHaveClass('grid-cols-1');
 
-expect(gridContainer).toHaveClass('grid-cols-1')
 expect(gridContainer).not.toHaveClass('md:grid-cols-2')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const gridContainer = container!.querySelector('.grid');
expect(gridContainer).toHaveClass('grid-cols-1');
expect(gridContainer).toHaveClass('grid-cols-1')
expect(gridContainer).not.toHaveClass('md:grid-cols-2')
})
const gridContainer = container!.querySelector('.grid');
expect(gridContainer).toHaveClass('grid-cols-1');
expect(gridContainer).not.toHaveClass('md:grid-cols-2')
})
🤖 Prompt for AI Agents
In @frontend/__tests__/unit/components/RecentRelease.test.tsx around lines 174 -
179, Remove the duplicate assertion on gridContainer: there are two identical
expect(gridContainer).toHaveClass('grid-cols-1') checks; delete the redundant
one (the second occurrence) so the test only asserts gridContainer has
'grid-cols-1' once and still asserts it does not have 'md:grid-cols-2'. Use the
existing gridContainer variable and keep the not.toHaveClass assertion intact.

Comment on lines +187 to 191
const gridContainer = container!.querySelector('.grid');
expect(gridContainer).toHaveClass('grid-cols-1');

expect(gridContainer).not.toHaveClass('grid-cols-1')
expect(gridContainer).toHaveClass('md:grid-cols-2', 'lg:grid-cols-3')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix contradictory test assertions.

Line 188 asserts that gridContainer has the class grid-cols-1, but line 190 immediately asserts the opposite—that it does NOT have grid-cols-1. This test is checking multi-column layout by default, so the assertion on line 188 is incorrect and will cause the test to fail.

🔧 Proposed fix
 const gridContainer = container!.querySelector('.grid');
-expect(gridContainer).toHaveClass('grid-cols-1');
 
 expect(gridContainer).not.toHaveClass('grid-cols-1')
 expect(gridContainer).toHaveClass('md:grid-cols-2', 'lg:grid-cols-3')
🤖 Prompt for AI Agents
In @frontend/__tests__/unit/components/RecentRelease.test.tsx around lines 187 -
191, The test contains contradictory assertions on the same element: in
RecentRelease.test.tsx the variable gridContainer is first asserted
toHaveClass('grid-cols-1') and then immediately asserted
not.toHaveClass('grid-cols-1') while expecting multi-column classes; remove or
change the first assertion so the test consistently asserts the default is
multi-column. Specifically, update the assertions around gridContainer (the
.grid selector) to NOT expect 'grid-cols-1' and to expect 'md:grid-cols-2' and
'lg:grid-cols-3' only, ensuring no opposing checks remain.

Comment on lines +51 to +52
clientId: process.env.NEXT_SERVER_GITHUB_CLIENT_ID || '',
clientSecret: process.env.NEXT_SERVER_GITHUB_CLIENT_SECRET || '',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Empty string fallback may cause silent authentication failures.

If NEXT_SERVER_GITHUB_CLIENT_ID or NEXT_SERVER_GITHUB_CLIENT_SECRET are not set, the provider will be initialized with empty strings, which will fail silently or produce cryptic errors at runtime.

Consider throwing an error or logging a warning when these required credentials are missing.

🔒️ Suggested defensive check
if (IS_GITHUB_AUTH_ENABLED) {
  const clientId = process.env.NEXT_SERVER_GITHUB_CLIENT_ID
  const clientSecret = process.env.NEXT_SERVER_GITHUB_CLIENT_SECRET
  
  if (!clientId || !clientSecret) {
    console.error('GitHub OAuth credentials are missing despite IS_GITHUB_AUTH_ENABLED being true')
  } else {
    providers.push(
      GitHubProvider({
        clientId,
        clientSecret,
        profile(profile: GithubProfile) {
          // ...
        },
      })
    )
  }
}
🤖 Prompt for AI Agents
In @frontend/src/app/api/auth/[...nextauth]/route.ts around lines 51 - 52, When
initializing the GitHub provider, don’t fall back to empty strings for
NEXT_SERVER_GITHUB_CLIENT_ID or NEXT_SERVER_GITHUB_CLIENT_SECRET; instead read
them into local consts and if IS_GITHUB_AUTH_ENABLED is true validate both
values are present — if missing, throw or log a clear error and do not call
providers.push(GitHubProvider(...)); otherwise pass the actual
clientId/clientSecret into GitHubProvider and keep the existing profile(...)
implementation.

Comment on lines +86 to +87
summary={committee.summary || ''}
title={committee.name || ''}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Unnecessary fallbacks for non-optional summary and name fields.

Both summary: string and name: string are required fields in the Committee type (not optional). The fallbacks || '' are inconsistent with these type definitions.

If these fields can genuinely be absent or empty, update the type definitions accordingly. Otherwise, trust TypeScript's type guarantees and remove the fallbacks.

🤖 Prompt for AI Agents
In @frontend/src/app/committees/[committeeKey]/page.tsx around lines 86 - 87,
The props passed to the component use unnecessary fallbacks for fields that are
required on the Committee type: remove the `|| ''` fallbacks from the
`summary={committee.summary || ''}` and `title={committee.name || ''}` usages in
page.tsx so the component relies on the declared `Committee` type (or
alternatively, if `summary`/`name` can actually be missing, update the
`Committee` type to make them optional and handle null/undefined where used);
specifically edit the lines setting `summary` and `title` to use
`committee.summary` and `committee.name` directly (or adjust the `Committee`
interface declaration to `summary?: string`/`name?: string` and add appropriate
runtime checks).

Comment on lines +30 to +31
if (graphQLData?.snapshot) {
setSnapshot(graphQLData.snapshot as SnapshotDetails)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find generated GraphQL types for GetSnapshotDetailsDocument
rg -n "export.*GetSnapshotDetailsDocument\|GetSnapshotDetails" --type=ts -g "*generated*" -A 20 | head -100

# Also search in common generated locations
find . -path "*/generated*" -name "*.ts" -o -path "*/graphql*" -name "*.ts" | grep -i generated | head -20

Repository: OWASP/Nest

Length of output: 99


🏁 Script executed:

#!/bin/bash
# Read the SnapshotDetails type definition
if [ -f "frontend/src/types/snapshot.ts" ]; then
  echo "=== SnapshotDetails Type Definition ==="
  cat -n frontend/src/types/snapshot.ts
else
  # Search for SnapshotDetails definition
  rg -n "type SnapshotDetails|interface SnapshotDetails" --type=ts -B 2 -A 20
fi

Repository: OWASP/Nest

Length of output: 634


🏁 Script executed:

#!/bin/bash
# Search for the actual file at the location mentioned
if [ -f "frontend/src/app/community/snapshots/[id]/page.tsx" ]; then
  echo "=== File exists, reading context around lines 30-31 ==="
  sed -n '1,40p' "frontend/src/app/community/snapshots/[id]/page.tsx"
else
  # Try to find the file with a pattern
  find . -path "*snapshots*" -name "page.tsx" -type f | head -5
fi

Repository: OWASP/Nest

Length of output: 1763


🏁 Script executed:

#!/bin/bash
# Check the generated snapshot queries file
if [ -f "frontend/src/types/__generated__/snapshotQueries.generated.ts" ]; then
  echo "=== snapshotQueries.generated.ts ==="
  cat -n frontend/src/types/__generated__/snapshotQueries.generated.ts
fi

Repository: OWASP/Nest

Length of output: 8392


🏁 Script executed:

#!/bin/bash
# Also check the main graphql.ts generated file for Snapshot/SnapshotDetails types
echo "=== Checking graphql.ts for Snapshot types ==="
rg -n "type Snapshot|interface Snapshot" frontend/src/types/__generated__/graphql.ts -A 10 | head -80

Repository: OWASP/Nest

Length of output: 566


🏁 Script executed:

#!/bin/bash
# Check Release, Project, Chapter type definitions
echo "=== Release Type ==="
cat -n frontend/src/types/release.ts | head -30

echo -e "\n=== Project Type ==="
cat -n frontend/src/types/project.ts | head -40

echo -e "\n=== Chapter Type ==="
cat -n frontend/src/types/chapter.ts | head -40

Repository: OWASP/Nest

Length of output: 2814


Remove the type cast and align types instead.

The cast is necessary due to a type mismatch between the generated GraphQL types and your manual SnapshotDetails type. The generated GetSnapshotDetailsQuery.snapshot includes extra fields (__typename, id) and different field types (endAt and startAt as any instead of string) that don't match SnapshotDetails.

Rather than casting to bypass TypeScript, either:

  1. Update SnapshotDetails to match the generated GraphQL types, or
  2. Create a mapping function that transforms the generated types to SnapshotDetails.

This pattern indicates a broader type alignment issue—all nested types (Release, Project, Chapter) also differ from their generated counterparts. Synchronizing these types will provide proper type safety across your codebase.

🤖 Prompt for AI Agents
In @frontend/src/app/community/snapshots/[id]/page.tsx around lines 30 - 31, The
code is casting graphQLData.snapshot to SnapshotDetails when calling
setSnapshot, hiding a real type mismatch between the generated GraphQL type
(GetSnapshotDetailsQuery.snapshot) and your manual SnapshotDetails; fix this by
removing the cast and aligning types: either extend SnapshotDetails to include
the extra fields (__typename, id) and correct types for endAt/startAt to match
the generated types, or implement a mapping function (e.g.,
mapGraphqlSnapshotToSnapshotDetails) that accepts
GetSnapshotDetailsQuery.snapshot and returns a properly shaped SnapshotDetails,
also mapping nested types (Release, Project, Chapter) and converting any
any-typed dates to strings before calling setSnapshot.

useEffect(() => {
if (graphQLData) {
setData(graphQLData)
setData(graphQLData as MainPageData)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Find the generated type definition for GetMainPageDataQuery
echo "=== Searching for GetMainPageDataQuery type ==="
rg -A 20 "export type GetMainPageDataQuery" --type=ts

# Check the MainPageData type
echo -e "\n=== Searching for MainPageData type ==="
rg -A 30 "export type MainPageData" --type=ts

# Also check if there's a __typename or data property mismatch
echo -e "\n=== Checking GetMainPageDataDocument structure ==="
rg -B 5 -A 10 "GetMainPageDataDocument" frontend/src --type=ts | head -50

Repository: OWASP/Nest

Length of output: 27851


Remove the type assertion on line 59.

The type assertion as MainPageData contradicts the PR's goal of removing unnecessary type assertions. Apollo Client's GetMainPageDataQuery and the custom MainPageData type are structurally incompatible—the generated type includes __typename fields not present in the custom type. Rather than masking this mismatch with an assertion, either:

  • Remove the custom MainPageData type and use GetMainPageDataQuery directly
  • Update MainPageData to match the Apollo-generated type structure
  • If mapping is necessary, explicitly transform the data instead of casting
🤖 Prompt for AI Agents
In @frontend/src/app/page.tsx at line 59, Remove the unsafe type assertion on
setData(graphQLData as MainPageData); instead make the component/state use the
generated Apollo type or explicitly map the data: update the state type and any
useState/setData declarations to use GetMainPageDataQuery (or
GetMainPageDataQuery | null), or perform an explicit transformation from
graphQLData to the custom MainPageData shape before calling setData (use the
graphQLData variable and map/omit __typename as needed); refer to setData,
graphQLData, MainPageData and GetMainPageDataQuery when applying the change.

Comment on lines +43 to 47
setMetricsLatest(data.project.healthMetricsLatest as HealthMetricsProps)
}
if (data?.project?.healthMetricsList) {
setMetricsList(data.project.healthMetricsList)
setMetricsList(data.project.healthMetricsList as HealthMetricsProps[])
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate the file to understand its structure
fd -t f "page.tsx" | grep -E "projects/dashboard/metrics" | head -5

Repository: OWASP/Nest

Length of output: 172


🏁 Script executed:

# Find GraphQL type definitions or generated types
fd -t f -e ts -e tsx | xargs grep -l "GetProjectHealthMetricsDetailsQuery\|healthMetricsLatest\|healthMetricsList" | head -10

Repository: OWASP/Nest

Length of output: 686


🏁 Script executed:

# Find HealthMetricsProps type definition
rg -n "export type HealthMetricsProps\|interface HealthMetricsProps" --type ts -A 20

Repository: OWASP/Nest

Length of output: 36


🏁 Script executed:

# Search for HealthMetricsProps in all TypeScript files
rg -n "HealthMetricsProps" --type ts --type tsx -B 2 -A 15 | head -100

Repository: OWASP/Nest

Length of output: 81


🏁 Script executed:

# Check the generated GraphQL types file
cat -n frontend/src/types/__generated__/projectsHealthDashboardQueries.generated.ts | head -150

Repository: OWASP/Nest

Length of output: 9391


🏁 Script executed:

# Also check the actual page component to see the imports and type usage
cat -n "frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx" | head -80

Repository: OWASP/Nest

Length of output: 3404


🏁 Script executed:

# Search for HealthMetricsProps type definition
rg -n "HealthMetricsProps" -A 15 | head -80

Repository: OWASP/Nest

Length of output: 5472


🏁 Script executed:

# Check the types/healthMetrics file directly
fd -t f "healthMetrics" | xargs cat -n

Repository: OWASP/Nest

Length of output: 1940


🏁 Script executed:

# Let me get the complete HealthMetricsProps type definition to verify all fields
cat -n frontend/src/types/healthMetrics.ts

Repository: OWASP/Nest

Length of output: 1940


🏁 Script executed:

# Check if there's a type mapping or conversion happening elsewhere
rg -n "as HealthMetricsProps" -B 3 -A 3

Repository: OWASP/Nest

Length of output: 1705


🏁 Script executed:

# Check the actual GraphQL schema to understand the source of the type mismatch
cat -n frontend/src/server/queries/projectsHealthDashboardQueries.ts | head -100

Repository: OWASP/Nest

Length of output: 2545


🏁 Script executed:

# Check if there's any type conversion or mapping layer
rg -n "ProjectHealthMetricsNode\|GetProjectHealthMetricsDetailsQuery" -A 5 -B 5 | head -150

Repository: OWASP/Nest

Length of output: 36


🏁 Script executed:

# Let me verify the complete picture - check if these are the only places with unnecessary assertions
rg -n "as HealthMetricsProps" --type ts

Repository: OWASP/Nest

Length of output: 472


🏁 Script executed:

# Also verify the state declarations to understand the typing context
sed -n '28,29p' frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx

Repository: OWASP/Nest

Length of output: 201


🏁 Script executed:

# Check git context to see if this is truly adding these assertions
git diff HEAD~1 frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx | grep -A 3 -B 3 "as HealthMetricsProps" || echo "No git history available"

Repository: OWASP/Nest

Length of output: 282


Remove type assertions that contradict PR objective.

Lines 43 and 46 add as HealthMetricsProps and as HealthMetricsProps[] casts, contradicting the PR's stated goal of removing unnecessary type assertions. GraphQL's generated types are structurally compatible with HealthMetricsProps (required fields in the GraphQL schema correspond to optional fields in the type definition, which TypeScript allows). Remove these casts to improve type safety and align with the PR's objective.

🤖 Prompt for AI Agents
In @frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx around
lines 43 - 47, Remove the unnecessary and contradictory type assertions on
health metrics: drop the "as HealthMetricsProps" on the setMetricsLatest call
and the "as HealthMetricsProps[]" on the setMetricsList call so you assign
data.project.healthMetricsLatest and data.project.healthMetricsList directly;
update the lines that call setMetricsLatest(...) and setMetricsList(...) to pass
the GraphQL-generated values without casting to
HealthMetricsProps/HealthMetricsProps[] (the generated types are structurally
compatible).

const UserCard = ({
avatar,
badgeCount,
badgeCount = 0,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove redundant nullish coalescing after default values.

The default values in destructuring (lines 11, 17, 21) already ensure these props are numbers. The subsequent nullish coalescing operators (?? 0) throughout lines 23, 65, 68, 71, 74, 77, and 80 are redundant because after destructuring, these variables can never be undefined.

For example, badgeCount = 0 means if badgeCount is undefined, it becomes 0. After that, badgeCount ?? 0 always equals badgeCount.

♻️ Simplified implementation without redundant guards
 const UserCard = ({
   avatar,
   badgeCount = 0,
   button,
   className,
   company,
   description,
   email,
   followersCount = 0,
   location,
   login,
   name,
   repositoriesCount = 0,
 }: UserCardProps) => {
-  const hasStats = (followersCount ?? 0) > 0 || (repositoriesCount ?? 0) > 0 || (badgeCount ?? 0) > 0
+  const hasStats = followersCount > 0 || repositoriesCount > 0 || badgeCount > 0

   return (
     ...
         {hasStats && (
           <div className="flex flex-wrap justify-center gap-3 px-2">
-            {(followersCount ?? 0) > 0 && (
+            {followersCount > 0 && (
               <div className="flex items-center gap-1 text-xs text-gray-600 sm:text-sm dark:text-gray-400">
                 <HiUserGroup className="h-4 w-4" />
-                <span>{millify(followersCount ?? 0, { precision: 1 })}</span>
+                <span>{millify(followersCount, { precision: 1 })}</span>
               </div>
             )}
-            {(repositoriesCount ?? 0) > 0 && (
+            {repositoriesCount > 0 && (
               <div className="flex items-center gap-1 text-xs text-gray-600 sm:text-sm dark:text-gray-400">
                 <FaFolderOpen className="h-3.5 w-3.5" />
-                <span>{millify(repositoriesCount ?? 0, { precision: 1 })}</span>
+                <span>{millify(repositoriesCount, { precision: 1 })}</span>
               </div>
             )}
-            {(badgeCount ?? 0) > 0 && (
+            {badgeCount > 0 && (
               <div className="flex items-center gap-1 text-xs text-gray-600 sm:text-sm dark:text-gray-400">
                 <FaMedal className="h-3.5 w-3.5" aria-label="badges" />
-                <span>{millify(badgeCount ?? 0, { precision: 1 })}</span>
+                <span>{millify(badgeCount, { precision: 1 })}</span>
               </div>
             )}
           </div>

Also applies to: 17-17, 21-21, 23-23, 65-65, 68-68, 71-71, 74-74, 77-77, 80-80

🤖 Prompt for AI Agents
In @frontend/src/components/UserCard.tsx at line 11, The component's props use
default values in destructuring (e.g., badgeCount = 0, unreadCount = 0,
messageCount = 0) so later nullish coalescing (badgeCount ?? 0, unreadCount ??
0, messageCount ?? 0) is redundant; remove the `?? 0` occurrences wherever those
variables are used (lines referencing badgeCount, unreadCount, messageCount in
render/JSX) and rely on the destructured defaults instead, keeping all existing
uses of the symbols (badgeCount, unreadCount, messageCount) unchanged otherwise.

@hussainjamal760 hussainjamal760 force-pushed the fix-sonar-unnecessary-assertion branch from 5908487 to 71bd7be Compare January 7, 2026 21:10
@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 7, 2026

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
frontend/__tests__/unit/components/RepositoryCard.test.tsx (1)

298-308: Remove this redundant test case.

The type definition only allows isArchived?: boolean (i.e., boolean | undefined), not null. The double type assertion bypasses TypeScript's type safety to force an invalid value. More importantly, this test is redundant—the undefined case at lines 223-231 already verifies that falsy values don't render the "Archived" badge. Since both null and undefined produce identical behavior, the explicit null test adds no unique coverage and contradicts the PR's goal of removing unnecessary type assertions.

frontend/src/app/organizations/[organizationKey]/repositories/[repositoryKey]/page.tsx (1)

58-69: Double cast bypasses type safety.

The as unknown as RepositoryData pattern completely disables TypeScript's type checking, which contradicts the PR's goal of improving type safety. This can mask structural mismatches between the GraphQL response and your local type.

If using generated types (as suggested above), this would simplify to direct assignment without casting:

setRepository(data.repository)

Or if mapping is needed, do it explicitly rather than casting.

frontend/src/components/CardDetailsPage.tsx (1)

228-259: Simplify checks with default parameters.

With default empty arrays for tags, domains, and labels (Lines 71-74), the || [] operations and the ?.length || 0 patterns are redundant.

♻️ Simplified version
-            {((tags?.length || 0) > 0 || (domains?.length || 0) > 0) && (
+            {(tags.length > 0 || domains.length > 0) && (
               <div
-                className={`mb-8 grid grid-cols-1 gap-6 ${(tags?.length || 0) === 0 || (domains?.length || 0) === 0 ? 'md:col-span-1' : 'md:grid-cols-2'}`}
+                className={`mb-8 grid grid-cols-1 gap-6 ${tags.length === 0 || domains.length === 0 ? 'md:col-span-1' : 'md:grid-cols-2'}`}
               >
-                {(tags?.length || 0) > 0 && (
+                {tags.length > 0 && (
                   <ToggleableList
-                    items={tags || []}
+                    items={tags}
                     icon={FaTags}
                     label={<AnchorTitle title="Tags" />}
                     isDisabled={true}
                   />
                 )}
-                {(domains?.length || 0) > 0 && (
+                {domains.length > 0 && (
                   <ToggleableList
-                    items={domains || []}
+                    items={domains}
                     icon={FaChartPie}
                     label={<AnchorTitle title="Domains" />}
                     isDisabled={true}
                   />
                 )}
               </div>
             )}
-            {(labels?.length || 0) > 0 && (
+            {labels.length > 0 && (
               <div className="mb-8">
                 <ToggleableList
-                  items={labels || []}
+                  items={labels}
                   icon={FaTags}
                   label={<AnchorTitle title="Labels" />}
                   isDisabled={true}
🤖 Fix all issues with AI agents
In @frontend/__tests__/unit/components/Modal.test.tsx:
- Line 200: The test bypasses TypeScript safety by casting null to string in the
render call for DialogComp; change the call to pass undefined if hint is meant
to be optional (e.g., render(<DialogComp {...defaultProps} hint={undefined} />))
or, if you are intentionally asserting null behavior, replace the cast with a
documented suppression using a single-line @ts-expect-error with an explanation
immediately above the render call to indicate you are testing a null edge case
for the hint prop; update references to DialogComp and defaultProps accordingly.
- Line 171: The test is bypassing TypeScript checks by double-casting
minimalProps to Parameters<typeof DialogComp>[0]; fix by making minimalProps
conform to DialogComp's prop type instead of using as unknown as ..., e.g.
change minimalProps to be either Partial<Parameters<typeof DialogComp>[0]> if
you only supply some props, or fully type it as Parameters<typeof
DialogComp>[0], or if the test intentionally provides invalid props mark the
render line with an explanatory // @ts-expect-error; update the minimalProps
declaration (not the render call) so render(<DialogComp {...minimalProps} />) no
longer needs the double cast.

In @frontend/__tests__/unit/components/UserMenu.test.tsx:
- Line 625: Remove the non-null assertion on dropdownId and instead rely on an
explicit null check or runtime guard: ensure the test first confirms dropdownId
!== null, then call const dropdown = document.getElementById(dropdownId) (no !)
and immediately handle the null case (e.g., if (!dropdown) throw or fail the
test) so TypeScript's safety is preserved; apply the same change for the other
occurrence around the symbol dropdownId.
- Line 80: Replace the double-cast escape hatch used for session mocks (e.g.,
`session: { user: null, expires: '2024-12-31' } as unknown as ExtendedSession`)
with a properly typed value or undefined: either pass `session: undefined` for
unauthenticated cases or declare a typed constant `const NULL_SESSION:
ExtendedSession = { user: null, expires: '2024-12-31' }` and return that from
the mock, then update `mockUseSession.mockReturnValue({...})` calls (and any
other occurrences in this test file using the double-cast) to use the typed
constant or undefined instead of the `as unknown as ExtendedSession` pattern.

In @frontend/__tests__/unit/utils/pluralize.test.ts:
- Around line 16-17: Tests are using unsafe double-casts to pass null/undefined
into pluralize(count: number), which contradicts the PR goal; either remove
these invalid test cases or make pluralize accept number | null | undefined and
add runtime guards (e.g., treat null/undefined as 0 or fallback to plural form)
and then update tests to pass real null/undefined without casts; locate usages
in the test file (pluralize.test.ts) and the implementation in
frontend/src/utils/pluralize.ts (function pluralize) and apply one of the two
fixes consistently—prefer removing the invalid tests if pluralize should remain
strictly typed.

In @frontend/src/app/about/page.tsx:
- Around line 296-307: The current leadersData builds a truncated member object
with only login, name, and avatarUrl which may not satisfy the Leaders
component's expected User type; update the mapping in leadersData to pass the
complete user object (or spread the original user into member) when available so
member matches the full User interface used by Leaders (or alternatively change
the Leaders prop type to accept a Partial<User> if intentional), and ensure
references to leadersData/member align with the Leaders component signature
(check Leaders and any User/Leader type definitions).

In @frontend/src/app/community/snapshots/[id]/page.tsx:
- Around line 54-61: The current cast using "as keyof typeof levelMapping" on
projectLevelKey can yield undefined if project.level isn't a valid key; replace
the assertion with a safe runtime validation: compute a rawLevel =
(project.level ?? 'incubating').toLowerCase(), check if rawLevel exists in
levelMapping (e.g., "if (!(rawLevel in levelMapping)) rawLevel = 'incubating'"),
then use levelMapping[rawLevel] when rendering the Card; remove the unsafe type
assertion and ensure the final key is a known keyof typeof levelMapping before
indexing.

In @frontend/src/components/CardDetailsPage.tsx:
- Line 180: The JSX uses a redundant null-coalescing fallback when iterating
stats: replace the expression "(stats || []).map((stat) => ...)" with
"stats.map((stat) => ...)" since the CardDetailsPage component defines stats
with a default value of [] (see the stats default parameter), so the extra "||
[]" can be removed; update the map call accordingly in the CardDetailsPage
component to eliminate the unnecessary null check.
- Around line 212-224: The code repeatedly uses (languages || []) and (topics ||
[]) despite languages and topics already being defaulted to empty arrays; remove
the redundant null-coalescing in the JSX and replace expressions like (languages
|| []).length and (topics || []).length with direct uses languages.length and
topics.length, and similarly pass languages and topics directly to
ToggleableList and AnchorTitle (e.g., items={languages} / items={topics}) to
simplify the conditionals and props for ToggleableList and the className
expression in CardDetailsPage.tsx.
- Around line 325-332: The JSX uses redundant null-coalescing (e.g.,
`recentIssues || []`, `recentMilestones || []`, `pullRequests || []`,
`recentReleases || []`) even though those props are already defaulted to empty
arrays; remove the `|| []` fallbacks in the `RecentIssues`, `Milestones`,
`RecentPullRequests`, and `RecentReleases` component invocations and pass the
variables directly (e.g., `data={recentIssues}`), leaving `showAvatar` and
`showSingleColumn` usage and the `showPullRequestsAndReleases(type)` conditional
unchanged.

In @frontend/src/components/ModuleForm.tsx:
- Around line 68-76: The handler handleSelectChange currently asserts
Array.from(keySet)[0] as string which may be undefined; change it to safely read
the first item without a type assertion (e.g., const value =
Array.from(keySet)[0];) and add an explicit guard (if (typeof value ===
'string') { setFormData(prev => ({ ...prev, experienceLevel: value }));
setTouched(prev => ({ ...prev, experienceLevel: true })); } else { /* optionally
clear or ignore selection */ }) so you never assert a possibly undefined value
before calling setFormData/setTouched.

In @frontend/src/components/RepositoryCard.tsx:
- Around line 39-41: The conditional uses unnecessary optional chaining on
`repositories` which defaults to an array; replace `(repositories?.length || 0)
> maxInitialDisplay` with a direct check `repositories.length >
maxInitialDisplay` in the JSX where `ShowMoreButton` is rendered (the block
using `repositories`, `maxInitialDisplay`, `ShowMoreButton`, and
`toggleRepositories`) to simplify the condition and remove the redundant
fallback.
- Around line 16-25: The displayedRepositories calculation includes a redundant
fallback to (repositories || []) even though repositories is defaulted to [] in
the component props; update the displayedRepositories assignment to use
repositories directly (i.e., when !showAllRepositories use repositories.slice(0,
maxInitialDisplay)) and remove the unnecessary null-check comment/guard; keep
toggleRepositories and the useState logic as-is.
- Around line 81-95: The nullish coalescing ("?? 0") and the comment are
unnecessary because the numeric fields on the props and GraphQL schema are
non-nullable; remove the "?? 0" from each InfoItem value prop (e.g.,
value={details.starsCount}, value={details.forksCount},
value={details.contributorsCount}, value={details.openIssuesCount}) and delete
or update the misleading comment so the code and comment reflect the required
number types; keep InfoItem usage unchanged otherwise.

In @frontend/src/hooks/useSearchPage.ts:
- Around line 39-43: The pageParam parsing can produce NaN for invalid values;
in the initialPage assignment (used alongside pageParam and items state),
validate the parsed integer and fall back to 1 — parse with
Number.parseInt(pageParam, 10), then check Number.isFinite/!Number.isNaN and
that the value is >= 1 before assigning to initialPage; if invalid, set
initialPage to 1. Ensure the same validation is applied wherever pageParam is
read to keep pagination robust.

In @frontend/src/utils/structuredData.ts:
- Line 71: The constructed property url currently uses
`${baseUrl}/members/${user.login || ''}` which yields an invalid trailing slash
when user.login is missing; update the code that builds the structured data so
the url property is omitted when user.login is falsy (use a conditional spread
or only set url if user.login is present) or, if url must always exist,
validate/require user.login upstream and fail fast; reference the url property,
baseUrl variable and user.login in structuredData.ts and apply the conditional
assignment so no `/members/` URL is emitted for missing logins.
- Around line 52-54: The Person JSON-LD currently includes description,
identifier and image with empty-string or placeholder fallbacks; change these to
use the same conditional spread pattern as used for location and
interactionStatistic so the properties are only added when meaningful: include
description only if user.bio is truthy, include identifier only if user.login is
truthy (remove the 'unknown' fallback), and include image only if user.avatarUrl
is truthy; update the object construction in structuredData.ts (the Person node
where description/identifier/image are set) to conditionally spread each
property into the Person object when its corresponding user field exists.

In @frontend/src/wrappers/provider.tsx:
- Around line 21-23: The early return when apolloClient is falsy causes all
critical providers to be skipped; remove that early return and always render
SessionProvider, HeroUIProvider, NextThemesProvider, and ToastProvider around
children, then conditionally wrap only ApolloProvider when apolloClient is
present (or create/use a lightweight fallback client) so components using
useSession(), HeroUI hooks, theme hooks, or toasts still get their providers;
update the component that references apolloClient and children to render
providers unconditionally and only include <ApolloProvider
client={apolloClient}> when apolloClient is truthy.
🟡 Minor comments (6)
frontend/src/app/chapters/page.tsx-69-72 (1)

69-72: Simplify the verbose comment.

The fallbacks are necessary: summary is a required prop while chapter.summary could be undefined. However, the comment explaining this is unnecessarily verbose for a straightforward type safety measure. Remove the comment or replace it with a brief clarification if needed.

The fallback for topContributors is defensive but not required since the prop is already optional in the CardProps type.

-        // Fixed: Provided fallback for summary to satisfy strict string requirement
         summary={chapter.summary || ''}
frontend/src/app/board/[year]/candidates/page.tsx-88-99 (1)

88-99: Inconsistent: Type assertion contradicts PR objectives.

Line 89 uses as string assertion, which contradicts this PR's goal of removing unnecessary type assertions. Next.js useParams() already types route parameters appropriately.

Additionally, the skip condition on line 98 may never trigger because line 97 always falls back to '2024', making the skip redundant.

🔧 Suggested refactor to remove assertion and clarify logic
-  const params = useParams()
-  const year = params?.year as string
+  const params = useParams<{ year: string }>()
+  const year = params.year
   const [candidates, setCandidates] = useState<CandidateWithSnapshot[]>([])

   const {
     data: graphQLData,
     error: graphQLRequestError,
     loading,
   } = useQuery(GetBoardCandidatesDocument, {
-    variables: { year: Number.parseInt(year || '2024') },
-    skip: !year,
+    variables: { year: year ? Number.parseInt(year, 10) : 2024 },
+    skip: !year || Number.isNaN(Number.parseInt(year, 10)),
   })

This removes the type assertion and makes the skip logic more explicit about when to skip (when year is missing or invalid).

frontend/src/app/board/[year]/candidates/page.tsx-238-238 (1)

238-238: Remove unnecessary type casts for Chapter and Project; keep the MemberSnapshot cast.

The generated types for Chapter (line 258) and Project (line 287) match their local type definitions exactly, making those casts redundant. However, the MemberSnapshot cast (line 238) is necessary: the generated type uses any for several fields (startAt, endAt, contributionHeatmapData, communicationHeatmapData, chapterContributions, projectContributions, repositoryContributions, channelCommunications), while the local type narrows these to specific types (string and Record<string, number>).

Type comparison details

Generated GetChapterByKeyQuery.chapter:

{ __typename: 'ChapterNode', id: string, key: string, name: string, url: string } | null

Local Chapter:

type Chapter = { id: string, key: string, name: string, url: string }

Generated GetProjectByKeyQuery.project:

{ __typename: 'ProjectNode', id: string, key: string, level: string, name: string, type: string, url: string } | null

Local Project:

type Project = { id: string, key: string, name: string, level: string, type: string, url: string }

Generated GetMemberSnapshotQuery.memberSnapshot uses any for multiple fields that are typed more specifically in the local MemberSnapshot definition.

frontend/__tests__/unit/components/UserMenu.test.tsx-634-634 (1)

634-634: Format line for readability.

Line 634 contains multiple object properties concatenated on a single line, making it difficult to read. Please format it properly:

🎨 Suggested formatting
-      session: { user: null, expires: '2024-12-31' } as unknown as ExtendedSession,        isSyncing: false,
+      session: { user: null, expires: '2024-12-31' } as unknown as ExtendedSession,
+      isSyncing: false,
frontend/src/components/RepositoryCard.tsx-70-71 (1)

70-71: Remove unnecessary optional chaining and fallback on required field.

According to RepositoryCardProps (in types/project.ts), name is a required string field. The details parameter passed to RepositoryItem is also required and typed as RepositoryCardProps. Both details?. and || '' are unnecessary and inconsistent with how other required fields are accessed in the same component.

♻️ Remove unnecessary guards
-          {/* Fixed: Guaranteed string fallback */}
-          <TruncatedText text={details?.name || ''} />
+          <TruncatedText text={details.name} />
frontend/src/components/CardDetailsPage.tsx-122-122 (1)

122-122: Verify that empty string default doesn't hide bugs.

programKey is optional in DetailsCardProps but required in EntityActions. Defaulting to an empty string when undefined masks this contract violation and creates malformed URLs in router navigation calls (e.g., /my/mentorship/programs//edit). Either guard against rendering EntityActions when programKey is missing, or make programKey truly optional in EntityActions with explicit undefined handling.

🧹 Nitpick comments (20)
frontend/__tests__/unit/components/ContributionHeatmap.test.tsx (1)

118-131: Remove or utilize the unused desc field.

The testCases array defines a desc field for each test case, but it's never used in the forEach loop. Either incorporate it into test output for better debugging (e.g., wrapping in a nested describe block or logging) or remove it to reduce code clutter.

♻️ Option 1: Remove unused field
-  const testCases: { data: Record<string, number>; desc: string }[] = [
-    { data: {}, desc: 'empty' },
-    { data: { '2024-01-01': 0, '2024-01-02': 0 }, desc: 'zero' },
-    { data: { '2024-01-01': 1000, '2024-01-02': 9999 }, desc: 'high' },
+  const testCases: Record<string, number>[] = [
+    {},
+    { '2024-01-01': 0, '2024-01-02': 0 },
+    { '2024-01-01': 1000, '2024-01-02': 9999 },
   ]
   
-  testCases.forEach(({ data }) => {
+  testCases.forEach((data) => {
     const { unmount } = renderWithTheme(
       <ContributionHeatmap {...defaultProps} contributionData={data} />
     )
♻️ Option 2: Use desc for better test output
-  testCases.forEach(({ data }) => {
+  testCases.forEach(({ data, desc }) => {
     const { unmount } = renderWithTheme(
-      <ContributionHeatmap {...defaultProps} contributionData={data} />
+      <ContributionHeatmap {...defaultProps} contributionData={data} />,
     )
-    expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument()
+    const chart = screen.getByTestId('mock-heatmap-chart')
+    expect(chart, `${desc} data should render chart`).toBeInTheDocument()
     unmount()
   })
frontend/__tests__/unit/components/RecentIssues.test.tsx (1)

116-117: Acceptable type assertion for edge case testing.

The double cast through unknown appropriately bypasses TypeScript to test the missing repositoryName scenario. This pattern is acceptable for tests that need to verify behavior with invalid states.

♻️ Optional: Avoid mutation by omitting the property directly
-    const issue = { ...baseIssue } as unknown as Issue
-    delete issue.repositoryName
+    const { repositoryName, ...issue } = baseIssue
+    const testIssue = issue as unknown as Issue
     render(<RecentIssues data={[issue]} />)

This approach uses destructuring to create an object without repositoryName from the start, avoiding the need to mutate and delete.

frontend/src/components/MetricsCard.tsx (1)

7-14: Good defensive date handling with optional enhancement.

The fallback to 'N/A' when createdAt is undefined properly handles the optional field from HealthMetricsProps. This aligns well with the PR's goal of improving type safety.

💡 Optional: Guard against invalid date strings

If createdAt contains an invalid date string, new Date(metric.createdAt) will produce an Invalid Date object, resulting in "Invalid Date" being displayed. Consider adding validation:

  // Defensive check for date validity
  const formattedDate = metric.createdAt 
-    ? new Date(metric.createdAt).toLocaleString('default', {
+    ? (() => {
+        const date = new Date(metric.createdAt)
+        return isNaN(date.getTime())
+          ? 'N/A'
+          : date.toLocaleString('default', {
          month: 'short',
          day: 'numeric',
          year: 'numeric',
        })
+      })()
    : 'N/A'
frontend/src/app/community/snapshots/[id]/page.tsx (1)

124-124: Optional: Simplify date fallbacks.

Since formatDate already returns '' for falsy input (per frontend/src/utils/dateFormatter.ts), the || '' fallbacks are technically redundant. However, they do make the intent explicit and don't cause any issues.

You may optionally simplify to:

{formatDate(snapshot.startAt)} - {formatDate(snapshot.endAt)}

The current code is safe and acceptable as-is.

frontend/src/app/organizations/[organizationKey]/repositories/[repositoryKey]/page.tsx (1)

20-42: Consider using generated types instead of a local interface.

Defining RepositoryData locally duplicates structure that should come from the generated GetRepositoryDataQuery type. This creates maintenance burden and potential drift between the interface and the actual GraphQL schema.

Consider extracting the repository type from the generated query type:

import type { GetRepositoryDataQuery } from 'types/__generated__/repositoryQueries.generated'

type RepositoryData = NonNullable<GetRepositoryDataQuery['repository']>
frontend/src/app/my/mentorship/programs/create/page.tsx (1)

75-84: Consider type-narrowing the error before accessing .message.

With strictNullChecks enabled, accessing err?.message on an unknown type may produce a TypeScript error. The err in a catch block is typed as unknown.

♻️ Suggested fix
     } catch (err) {
+      const errorMessage = err instanceof Error ? err.message : 'Unable to complete the requested operation.'
       addToast({
-        description: err?.message || 'Unable to complete the requested operation.',
+        description: errorMessage,
         title: 'GraphQL Request Failed',
frontend/__tests__/unit/components/ItemCardList.test.tsx (2)

257-257: Avoid double cast—use proper typing or @ts-expect-error.

The cast data as unknown as never[] bypasses type checking. If you're intentionally testing invalid inputs (null/undefined), use @ts-expect-error with a comment instead.

♻️ Suggested fix
-          data={data as unknown as never[]}
+          // @ts-expect-error - Testing component behavior with invalid data types
+          data={data}

629-629: Replace double casts with @ts-expect-error for intentionally malformed test data.

Multiple instances use as unknown as Issue to force incomplete/invalid data into the Issue type. Since these tests intentionally exercise edge cases with malformed data, use @ts-expect-error instead to be explicit about the intent while avoiding double casts.

♻️ Example fix for line 629
       const incompleteItem = {
         author: {
           ...mockUser,
           login: 'test',
           avatarUrl: 'https://github.com/test.png',
           name: 'Test User',
         },
         title: 'Incomplete Item',
         url: 'https://github.com/test/repo/issue/1',
-      } as unknown as Issue
+      }
 
+      // @ts-expect-error - Testing with incomplete Issue data
       render(
         <ItemCardList
           title="Incomplete Items"

Apply similar pattern to lines 707, 726, and 749.

Also applies to: 707-707, 726-726, 749-749

frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/page.tsx (1)

80-108: Consider using nullish coalescing (??) instead of logical OR (||).

The rendering logic is solid with proper loading/error states and safe fallbacks. However, for semantic clarity, consider replacing the || operator with ?? (nullish coalescing) in lines 96-104:

-      domains={module.domains || []}
+      domains={module.domains ?? []}
-      labels={module.labels || []}
+      labels={module.labels ?? []}
-      mentees={module.mentees || []}
+      mentees={module.mentees ?? []}
-      mentors={module.mentors || []}
+      mentors={module.mentors ?? []}
-      summary={module.description || ''}
+      summary={module.description ?? ''}
-      tags={module.tags || []}
+      tags={module.tags ?? []}
-      title={module.name || 'Untitled Module'}
+      title={module.name ?? 'Untitled Module'}

The ?? operator only checks for null or undefined, making the intent clearer and avoiding potential edge cases with other falsy values.

frontend/src/components/InfoBlock.tsx (1)

24-24: Type safety fix is necessary, but the theoretical UX concern doesn't occur in practice.

The nullish coalescing (?? '') correctly resolves the TypeScript strict null check error and is the right approach. However, the concerns about confusing output (e.g., "5 s") are theoretical—in the actual codebase, unit is always provided in every real usage (RepositoryCard, CardDetailsPage, organization/project/member pages, etc.). The edge case only appears in tests that mock the pluralize function.

That said, better patterns exist to prevent this entirely:

  1. Follow the InfoItem pattern: Make unit a required prop (no ?), eliminating the optional case
  2. Provide a sensible default: Use unit ?? 'item' instead of unit ?? ''

Neither is critical since the issue doesn't surface in practice, but either would clarify intent and prevent future confusion.

frontend/src/app/members/[memberKey]/page.tsx (2)

117-133: Inconsistent fallback operators for numeric properties.

After the null guard at line 106, removing optional chaining is safe. However, there's an inconsistency in fallback operators:

  • Line 130 correctly uses ?? (nullish coalescing) for publicRepositoriesCount
  • Lines 124, 125, and 132 use || for other count properties

For numeric values, ?? is preferred because || treats 0 as falsy and would incorrectly replace it with the fallback. Consider using ?? consistently for all numeric fields.

♻️ Standardize to nullish coalescing for numbers
   const userStats = [
-    { icon: FaPersonWalkingArrowRight, value: user.followersCount || 0, unit: 'Follower' },
-    { icon: FaUserPlus, value: user.followingCount || 0, unit: 'Following' },
+    { icon: FaPersonWalkingArrowRight, value: user.followersCount ?? 0, unit: 'Follower' },
+    { icon: FaUserPlus, value: user.followingCount ?? 0, unit: 'Following' },
     {
       icon: FaFolderOpen,
       pluralizedName: 'Repositories',
       unit: 'Repository',
       value: user.publicRepositoriesCount ?? 0,
     },
-    { icon: FaCodeMerge, value: user.contributionsCount || 0, unit: 'Contribution' },
+    { icon: FaCodeMerge, value: user.contributionsCount ?? 0, unit: 'Contribution' },
   ]

208-220: Unnecessary array spread before mapping.

The spread operator [...user.badges] creates a shallow copy before mapping, but map() doesn't mutate the original array. The spread is unnecessary and can be removed.

♻️ Remove unnecessary spread
             {user.badges && user.badges.length > 0 && (
               <div className="flex flex-wrap gap-2">
-                {[...user.badges].map((badge: Badge) => (
+                {user.badges.map((badge: Badge) => (
                   <React.Fragment key={badge.id}>
frontend/src/app/chapters/page.tsx (1)

105-108: Simplify the filter and standardize the null-safety pattern.

The code is functionally correct, but consider these improvements:

  1. Remove the verbose comment (line 105): It restates what the code already shows.
  2. Remove the redundant null check (line 107): Since useSearchPage<Chapter> returns items: Chapter[], array elements are guaranteed to be Chapter objects (not null/undefined). The chapter && check is unnecessary.
  3. Standardize the pattern: Line 91 uses chapters?.length ?? 0 while line 106 uses chapters || []. Prefer the optional chaining + nullish coalescing pattern for consistency.
♻️ Suggested changes
-      {/* Fixed: Safe filtering and mapping on the chapters array */}
-      {(chapters || [])
-        .filter((chapter) => chapter && chapter.isActive)
+      {(chapters ?? [])
+        .filter((chapter) => chapter.isActive)
         .map(renderChapterCard)}
frontend/src/app/board/[year]/candidates/page.tsx (1)

338-338: Minor redundancy: Extra optional chaining.

The additional optional chaining on line 338 (candidate.member?.linkedinPageId) is redundant since this code is already inside the conditional at line 329 that checks candidate.member?.linkedinPageId. However, this defensive guard is harmless and may prevent future refactoring issues.

frontend/src/app/mentorship/programs/[programKey]/page.tsx (3)

26-26: Consider whether the fallback is necessary.

The programKey || '' fallback provides an empty string when programKey is falsy, but the query is already guarded with skip: !programKey on line 27. Since useParams should return a string based on the route parameter, this fallback may be redundant.


82-82: Consider removing the fallback for individual enum values.

The level || '' fallback assumes that individual ExperienceLevelEnum values could be falsy. However, enum values should always be defined strings. The outer || 'N/A' already handles the case where the array is empty or undefined.

Based on learnings, enum fields are required and defined, so the inner fallback may be unnecessary.

♻️ Simplified version
-      value: program.experienceLevels?.map((level) => titleCaseWord(level || '')).join(', ') || 'N/A',
+      value: program.experienceLevels?.map((level) => titleCaseWord(level)).join(', ') || 'N/A',

91-93: Fallbacks for required fields may be unnecessary.

According to the Program type definition, both name and description are required fields (not optional). The || '' fallbacks provide defensive protection against incomplete data, but TypeScript's strict null checks should guarantee these fields are always present if program is defined.

While defensive programming can be valuable, these fallbacks are technically unnecessary given the type constraints.

♻️ Simplified version relying on type safety
-      summary={program.description || ''}
+      summary={program.description}
       tags={program.tags || []}
-      title={program.name || ''}
+      title={program.name}
frontend/src/components/CardDetailsPage.tsx (2)

193-193: Simplify redundant existence check.

With the default parameter geolocationData = [] (Line 80), the existence check geolocationData && is redundant. You can simplify to just the length check.

♻️ Simplified check
-          {type === 'chapter' && geolocationData && geolocationData.length > 0 && (
+          {type === 'chapter' && geolocationData.length > 0 && (

130-131: useSession hook should use generic type parameter to avoid casting.

The cast (data as ExtendedSession) is necessary given the current hook call, but it can be avoided. Update line 102 to:

const { data } = useSession<ExtendedSession>()

This eliminates the need for the cast at line 130, making the code data?.user?.login instead of ((data as ExtendedSession)?.user?.login).

moduleKey should not default to empty string when missing.

Similar to the programKey concern, passing moduleKey={entityKey || ''} at line 131 masks missing entity keys. Either require entityKey to be present or handle missing values differently in EntityActions.

frontend/src/app/my/mentorship/programs/[programKey]/modules/create/page.tsx (1)

86-100: Remove unnecessary empty string fallbacks for required DateTime and ID fields.

The fallback to empty strings for endedAt, startedAt, projectId, and projectName are unreachable code. The ModuleForm component validates all required fields (lines 114-150 in ModuleForm.tsx) before calling onSubmit, preventing submission with empty values. These fallbacks will never execute, making them unnecessary.

Additionally, they create semantic confusion by suggesting empty strings are acceptable values when the GraphQL schema requires valid DateTime and ID types.

Action: Remove the || '' fallbacks entirely since the form validation already prevents empty values from being submitted.

}

render(<DialogComp {...minimalProps} />)
render(<DialogComp {...minimalProps as unknown as Parameters<typeof DialogComp>[0]} />)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Double cast defeats the PR's type-safety improvements.

This cast as unknown as Parameters<typeof DialogComp>[0] completely bypasses TypeScript's type checking, which contradicts the PR's objective to eliminate redundant type assertions and improve type safety.

♻️ Better alternatives

Option 1: Fix minimalProps to match the actual component type:

-    render(<DialogComp {...minimalProps as unknown as Parameters<typeof DialogComp>[0]} />)
+    render(<DialogComp {...minimalProps} />)

Then adjust the minimalProps definition to satisfy the actual type requirements.

Option 2: If intentionally testing with incomplete props, use Partial<>:

const minimalProps: Partial<Parameters<typeof DialogComp>[0]> = {
  // ...
}

Option 3: If deliberately testing invalid input, use @ts-expect-error with a comment:

// @ts-expect-error - Testing component behavior with minimal/edge-case props
render(<DialogComp {...minimalProps} />)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @frontend/__tests__/unit/components/Modal.test.tsx at line 171, The test is
bypassing TypeScript checks by double-casting minimalProps to Parameters<typeof
DialogComp>[0]; fix by making minimalProps conform to DialogComp's prop type
instead of using as unknown as ..., e.g. change minimalProps to be either
Partial<Parameters<typeof DialogComp>[0]> if you only supply some props, or
fully type it as Parameters<typeof DialogComp>[0], or if the test intentionally
provides invalid props mark the render line with an explanatory //
@ts-expect-error; update the minimalProps declaration (not the render call) so
render(<DialogComp {...minimalProps} />) no longer needs the double cast.


it('handles null hint prop', () => {
render(<DialogComp {...defaultProps} hint={null} />)
render(<DialogComp {...defaultProps} hint={null as unknown as string} />)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Casting null to string defeats type safety.

The cast null as unknown as string bypasses TypeScript's type checking and contradicts this PR's goal of removing unnecessary assertions. If the hint prop accepts optional values, use undefined instead. If you're intentionally testing edge-case behavior with null, add a @ts-expect-error comment with an explanation.

♻️ Suggested fix
-    render(<DialogComp {...defaultProps} hint={null as unknown as string} />)
+    render(<DialogComp {...defaultProps} hint={undefined} />)

Or if intentionally testing null:

-    render(<DialogComp {...defaultProps} hint={null as unknown as string} />)
+    // @ts-expect-error - Testing component behavior with null hint value
+    render(<DialogComp {...defaultProps} hint={null} />)
🤖 Prompt for AI Agents
In @frontend/__tests__/unit/components/Modal.test.tsx at line 200, The test
bypasses TypeScript safety by casting null to string in the render call for
DialogComp; change the call to pass undefined if hint is meant to be optional
(e.g., render(<DialogComp {...defaultProps} hint={undefined} />)) or, if you are
intentionally asserting null behavior, replace the cast with a documented
suppression using a single-line @ts-expect-error with an explanation immediately
above the render call to indicate you are testing a null edge case for the hint
prop; update references to DialogComp and defaultProps accordingly.

it('renders nothing when GitHub auth is disabled', () => {
mockUseSession.mockReturnValue({
session: null,
session: { user: null, expires: '2024-12-31' } as unknown as ExtendedSession,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find useDjangoSession definition
rg -nP -A 15 "export (const|function) useDjangoSession|useDjangoSession\s*[:=]" --type=ts --type=tsx

Repository: OWASP/Nest

Length of output: 81


🏁 Script executed:

# Find ExtendedSession type definition
rg -nP -B 2 -A 10 "type ExtendedSession|interface ExtendedSession" --type=ts --type=tsx

Repository: OWASP/Nest

Length of output: 81


🏁 Script executed:

# Check the test file at the mentioned lines to see the pattern
head -n 150 frontend/__tests__/unit/components/UserMenu.test.tsx | tail -n 80

Repository: OWASP/Nest

Length of output: 2889


🏁 Script executed:

# Find useDjangoSession definition - try without file type flag
rg -nP -A 15 "export (const|function) useDjangoSession|useDjangoSession\s*[:=]" --max-count=5

Repository: OWASP/Nest

Length of output: 6947


🏁 Script executed:

# Find ExtendedSession type definition
rg -nP -B 2 -A 10 "type ExtendedSession|interface ExtendedSession" --max-count=5

Repository: OWASP/Nest

Length of output: 705


🏁 Script executed:

# Look for mockSession definition in the test file to see how it's typed
rg -nP -B 3 -A 10 "mockSession\s*[:=]" frontend/__tests__/unit/components/UserMenu.test.tsx

Repository: OWASP/Nest

Length of output: 368


🏁 Script executed:

# Check for the hook imports and type definitions at the top of the test file
head -n 50 frontend/__tests__/unit/components/UserMenu.test.tsx

Repository: OWASP/Nest

Length of output: 1306


🏁 Script executed:

# Check Session base type to understand full requirements
rg -nP -B 2 -A 10 "type Session|interface Session" frontend/src/types/auth.ts

Repository: OWASP/Nest

Length of output: 36


🏁 Script executed:

# Look at the next-auth Session type import
rg -nP -B 2 -A 5 "from.*next-auth" frontend/src/types/auth.ts

Repository: OWASP/Nest

Length of output: 178


Reconsider the double-cast pattern for session mocks.

The pattern session: { user: null, expires: '2024-12-31' } as unknown as ExtendedSession uses a double cast (via unknown) which is a TypeScript escape hatch. This contradicts the PR objective of removing unnecessary assertions.

Since useDjangoSession returns session?: ExtendedSession (optional), tests should either:

  1. Use session: undefined when testing null session cases
  2. Create a properly-typed test constant without casting:
const NULL_SESSION: ExtendedSession = { 
  user: null, 
  expires: '2024-12-31' 
}

Then use it directly:

mockUseSession.mockReturnValue({
  session: NULL_SESSION,
  isSyncing: false,
  status: 'unauthenticated',
})

This improves type safety and test clarity while avoiding the escape hatch.

Also applies to: 91-91, 105-105, 118-118, 182-182, 206-206, 429-429, 500-500, 522-522, 667-667

🤖 Prompt for AI Agents
In @frontend/__tests__/unit/components/UserMenu.test.tsx at line 80, Replace the
double-cast escape hatch used for session mocks (e.g., `session: { user: null,
expires: '2024-12-31' } as unknown as ExtendedSession`) with a properly typed
value or undefined: either pass `session: undefined` for unauthenticated cases
or declare a typed constant `const NULL_SESSION: ExtendedSession = { user: null,
expires: '2024-12-31' }` and return that from the mock, then update
`mockUseSession.mockReturnValue({...})` calls (and any other occurrences in this
test file using the double-cast) to use the typed constant or undefined instead
of the `as unknown as ExtendedSession` pattern.

const dropdownId = avatarButton.getAttribute('aria-controls')
expect(dropdownId).not.toBeNull()
const dropdown = document.getElementById(dropdownId)
const dropdown = document.getElementById(dropdownId!)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Non-null assertion contradicts PR objectives.

The PR's objective is to "remove unnecessary type assertions" including non-null assertions (!), yet this change adds non-null assertions. While the test does verify dropdownId is not null before these lines, using ! bypasses TypeScript's type safety.

Instead, narrow the type properly or handle the null case:

♻️ Refactor to eliminate non-null assertion
 await waitFor(() => {
   const dropdownId = avatarButton.getAttribute('aria-controls')
   expect(dropdownId).not.toBeNull()
-  const dropdown = document.getElementById(dropdownId!)
+  if (dropdownId) {
+    const dropdown = document.getElementById(dropdownId)
+    expect(dropdown).toBeInTheDocument()
+  }
-  expect(dropdown).toBeInTheDocument()
 })

Or leverage TypeScript's control flow analysis:

 await waitFor(() => {
   const dropdownId = avatarButton.getAttribute('aria-controls')
   expect(dropdownId).not.toBeNull()
+  expect(dropdownId).toBeDefined()
-  const dropdown = document.getElementById(dropdownId!)
+  const dropdown = document.getElementById(dropdownId as string)
   expect(dropdown).toBeInTheDocument()
 })

Also applies to: 712-712

🤖 Prompt for AI Agents
In @frontend/__tests__/unit/components/UserMenu.test.tsx at line 625, Remove the
non-null assertion on dropdownId and instead rely on an explicit null check or
runtime guard: ensure the test first confirms dropdownId !== null, then call
const dropdown = document.getElementById(dropdownId) (no !) and immediately
handle the null case (e.g., if (!dropdown) throw or fail the test) so
TypeScript's safety is preserved; apply the same change for the other occurrence
around the symbol dropdownId.

Comment on lines +16 to +17
expect(pluralize(null as unknown as number, 'count')).toBe('counts')
expect(pluralize(undefined as unknown as number, 'issue', 'issues')).toBe('issues')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

These type-unsafe casts directly contradict the PR's objective of removing unnecessary assertions.

The PR's stated goal is to "remove unnecessary type assertions" and "eliminate redundant type assertions," yet these changes ADD double casts (as unknown as number) that deliberately bypass TypeScript's type system. This issue was already raised in previous reviews.

The pluralize function signature expects count: number, not null | undefined. The current implementation has no explicit guards for these values—it only produces the expected test results due to JavaScript's comparison behavior (null === 1false).

Recommended solutions:

  1. Remove these test cases if pluralize is only called from typed contexts where TypeScript prevents invalid inputs.
  2. Update the function signature to explicitly accept count: number | null | undefined and add proper runtime guards, then remove the double casts from the tests.
  3. Add runtime validation in the pluralize implementation if it must handle untyped/external data.

The current approach undermines the type safety improvements this PR aims to achieve.

Option 1: Remove invalid test cases (recommended if not needed)
  test('returns plural form when count is 0 or falsy', () => {
    expect(pluralize(0, 'issue', 'issues')).toBe('issues')
-    expect(pluralize(null as unknown as number, 'count')).toBe('counts')
-    expect(pluralize(undefined as unknown as number, 'issue', 'issues')).toBe('issues')
  })
Option 2: Update function signature and remove casts

In frontend/src/utils/pluralize.ts:

-export const pluralize = (count: number, singular: string, plural?: string): string => {
+export const pluralize = (count: number | null | undefined, singular: string, plural?: string): string => {
-  if (count === 1) return singular
+  if (count === 1) return singular
  return plural ? plural : singular + 's'
}

In the test file:

-    expect(pluralize(null as unknown as number, 'count')).toBe('counts')
-    expect(pluralize(undefined as unknown as number, 'issue', 'issues')).toBe('issues')
+    expect(pluralize(null, 'count')).toBe('counts')
+    expect(pluralize(undefined, 'issue', 'issues')).toBe('issues')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
expect(pluralize(null as unknown as number, 'count')).toBe('counts')
expect(pluralize(undefined as unknown as number, 'issue', 'issues')).toBe('issues')
test('returns plural form when count is 0 or falsy', () => {
expect(pluralize(0, 'issue', 'issues')).toBe('issues')
})
🤖 Prompt for AI Agents
In @frontend/__tests__/unit/utils/pluralize.test.ts around lines 16 - 17, Tests
are using unsafe double-casts to pass null/undefined into pluralize(count:
number), which contradicts the PR goal; either remove these invalid test cases
or make pluralize accept number | null | undefined and add runtime guards (e.g.,
treat null/undefined as 0 or fallback to plural form) and then update tests to
pass real null/undefined without casts; locate usages in the test file
(pluralize.test.ts) and the implementation in frontend/src/utils/pluralize.ts
(function pluralize) and apply one of the two fixes consistently—prefer removing
the invalid tests if pluralize should remain strictly typed.

Comment on lines +81 to 95
{/* Fixed: Ensured values are numbers (handled null/undefined with 0) */}
<InfoItem icon={FaStar} pluralizedName="Stars" unit="Star" value={details.starsCount ?? 0} />
<InfoItem icon={FaCodeFork} pluralizedName="Forks" unit="Fork" value={details.forksCount ?? 0} />
<InfoItem
icon={HiUserGroup}
pluralizedName="Contributors"
unit="Contributor"
value={details.contributorsCount}
value={details.contributorsCount ?? 0}
/>
<InfoItem
icon={FaExclamationCircle}
pluralizedName="Issues"
unit="Issue"
value={details.openIssuesCount}
value={details.openIssuesCount ?? 0}
/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find the RepositoryCard component and its type definition
find . -type f -name "RepositoryCard.tsx" -o -name "*.ts" -o -name "*.tsx" | head -20

Repository: OWASP/Nest

Length of output: 1108


🏁 Script executed:

# Search for RepositoryCardProps type definition
rg "RepositoryCardProps|type RepositoryCardProps|interface RepositoryCardProps" -A 15 --type=tsx --type=ts | head -100

Repository: OWASP/Nest

Length of output: 81


🏁 Script executed:

# Check where RepositoryCard is being used and how objects are passed
rg "RepositoryCard" --type=tsx --type=ts -B 2 -A 2 | head -150

Repository: OWASP/Nest

Length of output: 81


🏁 Script executed:

# Find RepositoryCard.tsx file
fd -t f "RepositoryCard" --extension tsx

Repository: OWASP/Nest

Length of output: 155


🏁 Script executed:

# Search for RepositoryCardProps in all files
rg "RepositoryCardProps" -A 15 | head -150

Repository: OWASP/Nest

Length of output: 9961


🏁 Script executed:

# Look for type/interface definitions with these fields
rg "starsCount|forksCount|contributorsCount|openIssuesCount" -B 5 -A 2 | head -200

Repository: OWASP/Nest

Length of output: 12864


🏁 Script executed:

# Look for where repositories are created/mapped with these fields
rg "starsCount:" -B 3 -A 3 | head -200

Repository: OWASP/Nest

Length of output: 27630


🏁 Script executed:

# Check the GraphQL response mapping in members page
cat frontend/src/app/members/[memberKey]/page.tsx | head -150

Repository: OWASP/Nest

Length of output: 5538


Remove unnecessary nullish coalescing on required number fields.

All numeric fields (starsCount, forksCount, contributorsCount, openIssuesCount) are defined as required number (non-nullable) in RepositoryCardProps and consistently typed as non-nullable in the GraphQL schema. Using ?? 0 contradicts the type contract and adds defensive checks where the types already guarantee values.

The comment "Ensured values are numbers (handled null/undefined with 0)" is misleading since the type system already ensures these are numbers at compile time.

♻️ Remove unnecessary nullish coalescing
-        {/* Fixed: Ensured values are numbers (handled null/undefined with 0) */}
-        <InfoItem icon={FaStar} pluralizedName="Stars" unit="Star" value={details.starsCount ?? 0} />
-        <InfoItem icon={FaCodeFork} pluralizedName="Forks" unit="Fork" value={details.forksCount ?? 0} />
+        <InfoItem icon={FaStar} pluralizedName="Stars" unit="Star" value={details.starsCount} />
+        <InfoItem icon={FaCodeFork} pluralizedName="Forks" unit="Fork" value={details.forksCount} />
         <InfoItem
           icon={HiUserGroup}
           pluralizedName="Contributors"
           unit="Contributor"
-          value={details.contributorsCount ?? 0}
+          value={details.contributorsCount}
         />
         <InfoItem
           icon={FaExclamationCircle}
           pluralizedName="Issues"
           unit="Issue"
-          value={details.openIssuesCount ?? 0}
+          value={details.openIssuesCount}
         />
🤖 Prompt for AI Agents
In @frontend/src/components/RepositoryCard.tsx around lines 81 - 95, The nullish
coalescing ("?? 0") and the comment are unnecessary because the numeric fields
on the props and GraphQL schema are non-nullable; remove the "?? 0" from each
InfoItem value prop (e.g., value={details.starsCount},
value={details.forksCount}, value={details.contributorsCount},
value={details.openIssuesCount}) and delete or update the misleading comment so
the code and comment reflect the required number types; keep InfoItem usage
unchanged otherwise.

Comment on lines +39 to +43
const pageParam = searchParams?.get('page')

const [items, setItems] = useState<T[]>([])
const [currentPage, setCurrentPage] = useState<number>(
Number.parseInt(searchParams.get('page') || '1')
)
const [searchQuery, setSearchQuery] = useState<string>(searchParams.get('q') || '')
const [sortBy, setSortBy] = useState<string>(searchParams.get('sortBy') || defaultSortBy)
const [order, setOrder] = useState<string>(searchParams.get('order') || defaultOrder)
// Safety: handle null searchParams
const initialPage = pageParam ? Number.parseInt(pageParam, 10) : 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate page parameter to prevent NaN.

Number.parseInt(pageParam, 10) returns NaN if pageParam is not a valid number (e.g., ?page=abc). This can cause unexpected behavior in pagination logic.

🛡️ Proposed fix
- const pageParam = searchParams?.get('page')
+ const pageParam = searchParams?.get('page')
  
  const [items, setItems] = useState<T[]>([])
  // Safety: handle null searchParams
- const initialPage = pageParam ? Number.parseInt(pageParam, 10) : 1 
+ const parsedPage = pageParam ? Number.parseInt(pageParam, 10) : 1
+ const initialPage = Number.isNaN(parsedPage) ? 1 : parsedPage
  const initialQuery = searchParams?.get('q') || ''
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const pageParam = searchParams?.get('page')
const [items, setItems] = useState<T[]>([])
const [currentPage, setCurrentPage] = useState<number>(
Number.parseInt(searchParams.get('page') || '1')
)
const [searchQuery, setSearchQuery] = useState<string>(searchParams.get('q') || '')
const [sortBy, setSortBy] = useState<string>(searchParams.get('sortBy') || defaultSortBy)
const [order, setOrder] = useState<string>(searchParams.get('order') || defaultOrder)
// Safety: handle null searchParams
const initialPage = pageParam ? Number.parseInt(pageParam, 10) : 1
const pageParam = searchParams?.get('page')
const [items, setItems] = useState<T[]>([])
// Safety: handle null searchParams
const parsedPage = pageParam ? Number.parseInt(pageParam, 10) : 1
const initialPage = Number.isNaN(parsedPage) ? 1 : parsedPage
const initialQuery = searchParams?.get('q') || ''
🤖 Prompt for AI Agents
In @frontend/src/hooks/useSearchPage.ts around lines 39 - 43, The pageParam
parsing can produce NaN for invalid values; in the initialPage assignment (used
alongside pageParam and items state), validate the parsed integer and fall back
to 1 — parse with Number.parseInt(pageParam, 10), then check
Number.isFinite/!Number.isNaN and that the value is >= 1 before assigning to
initialPage; if invalid, set initialPage to 1. Ensure the same validation is
applied wherever pageParam is read to keep pagination robust.

Comment on lines +52 to +54
description: user.bio || '',
identifier: user.login || 'unknown',
image: user.avatarUrl || '',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use conditional spread pattern to omit empty-value properties from Person schema.

According to schema.org and Google Search Central best practices, properties with no meaningful value should be omitted entirely from JSON-LD, not included as empty strings or placeholders. The current code violates this for three properties:

  • description: user.bio || '' — Should only be included when user.bio exists
  • identifier: user.login || 'unknown' — Should only be included when user.login exists; 'unknown' is semantically incorrect
  • image: user.avatarUrl || '' — Should only be included when user.avatarUrl exists

The codebase already demonstrates the correct pattern with location (line 49) and interactionStatistic (line 55). Apply the same conditional spread approach here:

-      description: user.bio || '',
-      identifier: user.login || 'unknown',
-      image: user.avatarUrl || '',
+      ...(user.bio && {
+        description: user.bio,
+      }),
+      ...(user.login && {
+        identifier: user.login,
+      }),
+      ...(user.avatarUrl && {
+        image: user.avatarUrl,
+      }),

Empty strings and placeholder values negatively impact SEO and can confuse search engine crawlers.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
description: user.bio || '',
identifier: user.login || 'unknown',
image: user.avatarUrl || '',
...(user.bio && {
description: user.bio,
}),
...(user.login && {
identifier: user.login,
}),
...(user.avatarUrl && {
image: user.avatarUrl,
}),
🤖 Prompt for AI Agents
In @frontend/src/utils/structuredData.ts around lines 52 - 54, The Person
JSON-LD currently includes description, identifier and image with empty-string
or placeholder fallbacks; change these to use the same conditional spread
pattern as used for location and interactionStatistic so the properties are only
added when meaningful: include description only if user.bio is truthy, include
identifier only if user.login is truthy (remove the 'unknown' fallback), and
include image only if user.avatarUrl is truthy; update the object construction
in structuredData.ts (the Person node where description/identifier/image are
set) to conditionally spread each property into the Person object when its
corresponding user field exists.

url: `${baseUrl}/members/${user.login}`,
name: user.name || user.login || 'OWASP Member',
sameAs: user.url ? [user.url] : [], // Fixed: Handle possibly undefined url
url: `${baseUrl}/members/${user.login || ''}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix invalid URL construction when login is missing.

When user.login is undefined or null, this creates an incomplete URL ending with a trailing slash (https://nest.owasp.org/members/), which doesn't point to a valid profile page.

Consider using conditional spread to omit the property when login is missing:

-      url: `${baseUrl}/members/${user.login || ''}`,
+      ...(user.login && {
+        url: `${baseUrl}/members/${user.login}`,
+      }),

Or if url should always be present, ensure login is validated upstream and treat it as required.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
url: `${baseUrl}/members/${user.login || ''}`,
...(user.login && {
url: `${baseUrl}/members/${user.login}`,
}),
🤖 Prompt for AI Agents
In @frontend/src/utils/structuredData.ts at line 71, The constructed property
url currently uses `${baseUrl}/members/${user.login || ''}` which yields an
invalid trailing slash when user.login is missing; update the code that builds
the structured data so the url property is omitted when user.login is falsy (use
a conditional spread or only set url if user.login is present) or, if url must
always exist, validate/require user.login upstream and fail fast; reference the
url property, baseUrl variable and user.login in structuredData.ts and apply the
conditional assignment so no `/members/` URL is emitted for missing logins.

Comment on lines +21 to +23
if (!apolloClient) {
return <>{children}</>
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Early return bypasses all essential providers.

This guard was already flagged in a previous review. When apolloClient is falsy (e.g., when GRAPHQL_URL is unavailable), this early return skips all providers—SessionProvider, HeroUIProvider, NextThemesProvider, ToastProvider—causing runtime failures in any component using useSession(), HeroUI components, theme hooks, or toast notifications.

Recommended fix: Always render the non-Apollo providers; conditionally wrap only ApolloProvider or provide a fallback client.

🔍 Verify downstream impact

Run the following to identify components that depend on these providers and would break without them:

#!/bin/bash
# Check for components using hooks that require the bypassed providers
echo "=== Components using useSession (requires SessionProvider) ==="
rg -n --type=tsx --type=ts 'useSession\(' -g '!node_modules' -C2

echo ""
echo "=== Components using HeroUI hooks (requires HeroUIProvider) ==="
rg -n --type=tsx --type=ts 'use(Toast|Disclosure|Modal|Dropdown)' -g '!node_modules' -C2

echo ""
echo "=== Components using useTheme (requires NextThemesProvider) ==="
rg -n --type=tsx --type=ts 'useTheme\(' -g '!node_modules' -C2
♻️ Proposed fix
 export function Providers({ children }: { children: React.ReactNode }) {
-  if (!apolloClient) {
-    return <>{children}</> 
-   }
   return (
     <Suspense>
       <SessionProvider>
         <HeroUIProvider>
           <NextThemesProvider attribute="class" defaultTheme="dark">
             <ToastProvider />
-            <ApolloProvider client={apolloClient}>
-              <AppInitializer />
-              {children}
-            </ApolloProvider>
+            {apolloClient ? (
+              <ApolloProvider client={apolloClient}>
+                <AppInitializer />
+                {children}
+              </ApolloProvider>
+            ) : (
+              <>
+                {console.warn('Apollo client unavailable; GraphQL features disabled.')}
+                {children}
+              </>
+            )}
           </NextThemesProvider>
         </HeroUIProvider>
       </SessionProvider>
     </Suspense>
   )
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @frontend/src/wrappers/provider.tsx around lines 21 - 23, The early return
when apolloClient is falsy causes all critical providers to be skipped; remove
that early return and always render SessionProvider, HeroUIProvider,
NextThemesProvider, and ToastProvider around children, then conditionally wrap
only ApolloProvider when apolloClient is present (or create/use a lightweight
fallback client) so components using useSession(), HeroUI hooks, theme hooks, or
toasts still get their providers; update the component that references
apolloClient and children to render providers unconditionally and only include
<ApolloProvider client={apolloClient}> when apolloClient is truthy.

@arkid15r arkid15r marked this pull request as draft January 8, 2026 03:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove unnecessary type assertion as the receiver accepts the original expression type

2 participants