chore: enable strict mode and refactor types#3652
chore: enable strict mode and refactor types#3652hussainjamal760 wants to merge 2 commits intoOWASP:mainfrom
Conversation
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings. WalkthroughThis PR enhances TypeScript strict mode compliance across the frontend by adding explicit type guards, null safety checks, and defensive fallbacks throughout components and types. The changes prevent potential runtime errors from null/undefined values through optional chaining, nullish coalescing, explicit type narrowing, and property widening to accommodate nullable values. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~35 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
|
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
frontend/src/app/community/snapshots/[id]/page.tsx (2)
38-62: Guard routing whenproject.keyis missing.
Line 43 and Line 61 still build URLs fromproject.keyeven thoughcardKeynow defaults to''. If the key is absent, the button navigates to an invalid route. Gate navigation and URL creation on a truthy key.Proposed fix
const renderProjectCard = (project: Project) => { + const projectKey = project.key ?? '' const params: string[] = ['forksCount', 'starsCount', 'contributorsCount'] const filteredIcons = getFilteredIcons(project, params) const handleButtonClick = () => { - router.push(`/projects/${project.key}`) + if (!projectKey) return + router.push(`/projects/${projectKey}`) } const submitButton = { label: 'View Details', icon: <FaRightToBracket className="h-4 w-4" />, onclick: handleButtonClick, } return ( <Card button={submitButton} - cardKey={project.key || ''} + cardKey={projectKey} icons={filteredIcons} level={level[(project.level || 'incubator').toLowerCase() as keyof typeof level] ?? level.incubator} summary={project.summary || ''} title={project.name} topContributors={project.topContributors} - url={`/projects/${project.key}`} + url={projectKey ? `/projects/${projectKey}` : ''} /> ) }
66-90: Avoid invalid chapter links whenchapter.keyis empty.
Line 72 and Line 89 still assumechapter.keyexists whilecardKeynow defaults to''. If the key is missing, the button routes to a broken URL. Guard navigation/URL similarly to the project card.Proposed fix
const renderChapterCard = (chapter: Chapter) => { + const chapterKey = chapter.key ?? '' const params: string[] = ['updatedAt'] const filteredIcons = getFilteredIcons(chapter, params) const formattedUrls = handleSocialUrls(chapter.relatedUrls || []) const handleButtonClick = () => { - router.push(`/chapters/${chapter.key}`) + if (!chapterKey) return + router.push(`/chapters/${chapterKey}`) } const submitButton = { label: 'View Details', icon: <FaRightToBracket className="h-4 w-4" />, onclick: handleButtonClick, } return ( <Card button={submitButton} - cardKey={chapter.key || ''} + cardKey={chapterKey} icons={filteredIcons} social={formattedUrls} summary={chapter.summary || ''} title={chapter.name} - url={`/chapters/${chapter.key}`} + url={chapterKey ? `/chapters/${chapterKey}` : ''} /> ) }frontend/src/components/HealthMetrics.tsx (1)
9-15: Consider theDate.now()fallback semantics.While the nullish coalescing for numeric values is appropriate, using
Date.now()as a fallback for missingcreatedAtmay produce misleading chart labels — a metric with no timestamp would appear as "today."Given the parent component validates that data exists before rendering, this edge case is unlikely to occur in practice. If it's acceptable to show a "N/A" or skip items without timestamps, that would be more semantically accurate.
💡 Alternative: filter or label fallback
const labels = data.map((item) => { - return new Date(item.createdAt ?? Date.now()).toLocaleDateString('en-US', { + if (!item.createdAt) return 'N/A' + return new Date(item.createdAt).toLocaleDateString('en-US', { month: 'short', day: 'numeric', }) })frontend/src/components/ItemCardList.tsx (1)
54-79: Guard avatar rendering to avoid invalidnext/imagesrc and malformed URLs.When
authoris missing or incomplete, the current code passessrc=""tonext/image(line 70), which is invalid and can error. Additionally,href={/members/${item?.author?.login}}(line 65) generates/members/undefinedwhen author is null or login is missing.Render the avatar block only when both
loginandavatarUrlare present:Suggested fix
- {showAvatar && ( + {showAvatar && item?.author?.login && item?.author?.avatarUrl && ( <Tooltip closeDelay={100} - content={item?.author?.name || item?.author?.login} + content={item.author.name || item.author.login} id={`avatar-tooltip-${index}`} delay={100} placement="bottom" showArrow > <Link className="shrink-0 text-blue-400 hover:underline" - href={`/members/${item?.author?.login}`} + href={`/members/${item.author.login}`} > <Image height={24} width={24} - src={item?.author?.avatarUrl || ''} + src={item.author.avatarUrl} alt={ item.author && (item.author.name || item.author.login) ? `${item.author.name || item.author.login}'s avatar` : "Author's avatar" } className="mr-2 rounded-full" style={{ objectFit: 'cover' }} /> </Link> </Tooltip> )}
🤖 Fix all issues with AI agents
In
`@frontend/src/app/my/mentorship/programs/`[programKey]/modules/[moduleKey]/mentees/[menteeKey]/page.tsx:
- Around line 53-62: The menteeIssuesData state is never updated after the API
response, so the issues list/pagination remains empty; after you
setMenteeDetails from data.getMenteeDetails, also set the menteeIssuesData state
from data.getMenteeModuleIssues (e.g., call the setter for menteeIssuesData with
the response payload or transformed shape expected by the UI, including items
and pagination fields) so the component receives the issues and paging info;
locate the update near setMenteeDetails and use data.getMenteeModuleIssues as
the source.
In
`@frontend/src/app/my/mentorship/programs/`[programKey]/modules/[moduleKey]/page.tsx:
- Around line 63-69: Replace the unsafe "as any" casts for admins, mentees, and
mentors when passing to DetailsCard by either aligning DetailsCard's prop types
with the GraphQL-generated types or adding small, explicit mapping/typing
functions (e.g., mapAdminsToContributors, mapMenteesToContributors,
mapMentorsToContributors) that validate/transform mentorshipModule.admins,
mentorshipModule.mentees, and mentorshipModule.mentors into the exact
Contributor[] (or other specific) type expected; alternatively, if you know the
shape matches, replace "as any" with a precise assertion like "as Contributor[]"
and update DetailsCard props to accept Contributor[] so the compiler enforces
correctness.
In `@frontend/src/components/CardDetailsPage.tsx`:
- Around line 87-114: The default for topContributors being set to [] causes the
existing render guard to always pass and can render an empty block; either stop
defaulting topContributors (remove it from the destructured defaults in the
CardDetailsPage props) or change the render condition to check for content
(e.g., use topContributors && topContributors.length > 0) where the Top
Contributors block is rendered so it only renders when there are actual
contributors.
In `@frontend/src/types/declarations.d.ts`:
- Around line 1-4: The ambient module declaration for 'isomorphic-dompurify' is
invalid because it exports an object literal; replace it with a proper type-only
default export by declaring the sanitize symbol as a function or const type and
then exporting that identifier as the default (e.g. declare function
sanitize(...) or declare const sanitize: (...) and then export default
sanitize), or alternatively remove the custom declaration and install/use
`@types/dompurify` which is re-exported by isomorphic-dompurify to provide correct
types; ensure the module name 'isomorphic-dompurify' and the 'sanitize'
identifier are used to locate and update the declaration.
- Line 6: Remove the shorthand ambient declaration "declare module 'lodash';"
from declarations.d.ts so TypeScript can use the installed `@types/lodash` package
instead of treating lodash imports as any; locate and delete that exact line (or
the entire empty module declaration) in frontend/src/types/declarations.d.ts and
ensure there are no other conflicting lodash module declarations remaining.
- Around line 8-20: Remove the handwritten ambient module for 'jest-axe' (the
declare module block defining AxeResults, axe, and toHaveNoViolations) and
instead install and use the official typings: add the `@types/jest-axe` package to
devDependencies and remove this file; if you still need AxeResults in your code
import the proper type from 'axe-core' (e.g., AxeResults) rather than the
incomplete local interface, and let the installed `@types/jest-axe` expose axe and
toHaveNoViolations to Jest.
🧹 Nitpick comments (9)
frontend/src/app/organizations/[organizationKey]/layout.tsx (1)
31-32: Avoid a leading space in the fallback description.When
titleis empty, the template yields" organization details". Consider a cleaner fallback string.♻️ Suggested tweak
- description: organization?.description ?? `${title || ''} organization details`, - title: title || 'Organization', + description: + organization?.description ?? + (title ? `${title} organization details` : 'Organization details'), + title: title || 'Organization',frontend/src/app/layout.tsx (1)
87-87: Consider conditional rendering instead of empty string fallback.Passing an empty string to
GoogleAnalyticsmay still render the GA script with an invalid measurement ID, potentially causing unnecessary network requests or console warnings in environments whereGTM_IDis not configured.A conditional render would be cleaner:
♻️ Proposed refactor using conditional rendering
</body> - <GoogleAnalytics gaId={GTM_ID || ''} /> + {GTM_ID && <GoogleAnalytics gaId={GTM_ID} />} </html>frontend/src/app/chapters/[chapterKey]/page.tsx (1)
57-69: Fallback handling looks correct; consider consistency withregion.The
|| ''fallback forsuggestedLocationproperly handles the newly nullable type. However,chapter.regionon line 60 is also optional in the type definition but lacks a similar fallback. If both fields can be absent, consider applying consistent defensive handling:{ label: 'Location', value: chapter.suggestedLocation || '' }, - { label: 'Region', value: chapter.region }, + { label: 'Region', value: chapter.region || '' },Alternatively, if the backend guarantees
regionis always present, the type could be tightened to reflect that.frontend/src/app/api/auth/[...nextauth]/route.ts (1)
96-96: Consider using nullish coalescing for boolean values.The
|| falseworks correctly here, but?? falsewould be more precise since it only coercesnull/undefinedrather than all falsy values. This matters ifisOwaspStaffcould legitimately befalse(which would be preserved with??but also coerced tofalsewith||, so functionally equivalent in this case).♻️ Optional: Use nullish coalescing
- token.isOwaspStaff = (session as ExtendedSession).user?.isOwaspStaff || false + token.isOwaspStaff = (session as ExtendedSession).user?.isOwaspStaff ?? falsefrontend/src/components/UserCard.tsx (1)
62-78: Consider using consistent guard patterns to avoid rendering0as text.The outer check (lines 62-64) uses
(X ?? 0) > 0, which always evaluates to a boolean. The inner checks (lines 66, 72, 78) useX && X > 0, which could short-circuit to the number0if any count equals0. In React/JSX,{0 && <Component />}renders the literal "0" as text rather than nothing.For consistency and safety, consider using the same pattern throughout:
♻️ Suggested refactor for consistency
<div className="flex flex-wrap justify-center gap-3 px-2"> - {followersCount && followersCount > 0 && ( + {(followersCount ?? 0) > 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, { precision: 1 })}</span> + <span>{millify(followersCount!, { precision: 1 })}</span> </div> )} - {repositoriesCount && repositoriesCount > 0 && ( + {(repositoriesCount ?? 0) > 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, { precision: 1 })}</span> + <span>{millify(repositoriesCount!, { precision: 1 })}</span> </div> )} - {badgeCount && badgeCount > 0 && ( + {(badgeCount ?? 0) > 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, { precision: 1 })}</span> + <span>{millify(badgeCount!, { precision: 1 })}</span> </div> )} </div>Note: The non-null assertion (
!) inside the guarded block is safe since the condition already ensures the value is positive.frontend/src/utils/metaconfig.ts (1)
68-83: Avoid theascast by typing METADATA_CONFIG.Line 82's cast is unnecessary and bypasses type inference. Since TypeScript 5.9.3 is in use, define a
MetadataConfigEntrytype and applyas const satisfiestoMETADATA_CONFIGsoconfig.typeis properly narrowed and the cast can be removed.🔧 Proposed refactor
diff --git a/frontend/src/utils/metadata.ts b/frontend/src/utils/metadata.ts +export type MetadataConfigEntry = { + description: string + keywords: string[] + pageTitle: string + type: 'website' | 'article' | 'profile' +} + -export const METADATA_CONFIG = { +export const METADATA_CONFIG = { home: { description: 'OWASP Nest is a comprehensive, community-first platform built to enhance collaboration and contribution across the OWASP community.', keywords: ['OWASP', 'security', 'open source', 'web security', 'application security'], pageTitle: 'Home', type: 'website', }, // ... -} +} as const satisfies Record<string, MetadataConfigEntry>diff --git a/frontend/src/utils/metaconfig.ts b/frontend/src/utils/metaconfig.ts - type: config.type as 'website' | 'article' | 'profile', + type: config.type,frontend/src/app/page.tsx (2)
325-337: Avoidas anycasts; align types instead.Using
as anyforcontributors,recentIssues,recentMilestones,recentPullRequests,recentReleases, andsponsorsundermines the type safety goal of this strict mode PR. These casts hide potential type mismatches and can lead to runtime errors.Consider aligning the generated GraphQL types with the component prop types, or creating explicit type mappings/adapters to ensure type-safe data flow.
405-405: Same concern: avoidas anyforsponsors.This cast bypasses type checking. If the
MovingLogoscomponent expects a different shape than what the GraphQL query returns, consider updating either the query or the component's expected type.frontend/src/app/organizations/[organizationKey]/repositories/[repositoryKey]/page.tsx (1)
75-79: Redundant optional chaining inside truthy branch.The condition
repository?.url ?already confirmsurlis truthy, sorepository?.url || '#'on line 76 is redundant—the|| '#'fallback will never trigger.🔧 Suggested simplification
{ label: 'URL', value: repository?.url ? ( - <Link href={repository?.url || '#'} className="text-blue-400 hover:underline"> + <Link href={repository.url} className="text-blue-400 hover:underline"> {repository.url} </Link> ) : '', },
| setMenteeDetails( | ||
| data.getMenteeDetails | ||
| ? { | ||
| ...data.getMenteeDetails, | ||
| bio: data.getMenteeDetails.bio || undefined, | ||
| domains: (data.getMenteeDetails.domains || undefined) as string[] | undefined, | ||
| tags: (data.getMenteeDetails.tags || undefined) as string[] | undefined, | ||
| } | ||
| : null | ||
| ) |
There was a problem hiding this comment.
menteeIssuesData is no longer populated, so issues will always render empty.
menteeIssuesData is initialized but never set from data.getMenteeModuleIssues after this change. This regresses the issues list and pagination.
🛠️ Proposed fix
if (data) {
setMenteeDetails(
data.getMenteeDetails
? {
...data.getMenteeDetails,
bio: data.getMenteeDetails.bio || undefined,
domains: (data.getMenteeDetails.domains || undefined) as string[] | undefined,
tags: (data.getMenteeDetails.tags || undefined) as string[] | undefined,
}
: null
)
+ setMenteeIssuesData(data.getMenteeModuleIssues ?? [])
}📝 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.
| setMenteeDetails( | |
| data.getMenteeDetails | |
| ? { | |
| ...data.getMenteeDetails, | |
| bio: data.getMenteeDetails.bio || undefined, | |
| domains: (data.getMenteeDetails.domains || undefined) as string[] | undefined, | |
| tags: (data.getMenteeDetails.tags || undefined) as string[] | undefined, | |
| } | |
| : null | |
| ) | |
| if (data) { | |
| setMenteeDetails( | |
| data.getMenteeDetails | |
| ? { | |
| ...data.getMenteeDetails, | |
| bio: data.getMenteeDetails.bio || undefined, | |
| domains: (data.getMenteeDetails.domains || undefined) as string[] | undefined, | |
| tags: (data.getMenteeDetails.tags || undefined) as string[] | undefined, | |
| } | |
| : null | |
| ) | |
| setMenteeIssuesData(data.getMenteeModuleIssues ?? []) | |
| } |
🤖 Prompt for AI Agents
In
`@frontend/src/app/my/mentorship/programs/`[programKey]/modules/[moduleKey]/mentees/[menteeKey]/page.tsx
around lines 53 - 62, The menteeIssuesData state is never updated after the API
response, so the issues list/pagination remains empty; after you
setMenteeDetails from data.getMenteeDetails, also set the menteeIssuesData state
from data.getMenteeModuleIssues (e.g., call the setter for menteeIssuesData with
the response payload or transformed shape expected by the UI, including items
and pagination fields) so the component receives the issues and paging info;
locate the update near setMenteeDetails and use data.getMenteeModuleIssues as
the source.
| admins={admins as any} | ||
| details={moduleDetails} | ||
| domains={mentorshipModule.domains} | ||
| domains={mentorshipModule.domains || undefined} | ||
| entityKey={moduleKey} | ||
| labels={mentorshipModule.labels} | ||
| mentees={mentorshipModule.mentees} | ||
| mentors={mentorshipModule.mentors} | ||
| labels={mentorshipModule.labels || undefined} | ||
| mentees={mentorshipModule.mentees as any} | ||
| mentors={mentorshipModule.mentors as any} |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Avoid as any casts — they undermine strict mode benefits.
Using as any on admins, mentees, and mentors bypasses TypeScript's type checking entirely, which contradicts the PR's objective of improving type safety. These casts can hide legitimate type mismatches that may cause runtime errors.
Consider these alternatives:
- Align the
DetailsCardprop types with the GraphQL-generated types - Create explicit mapping functions that safely transform the data
- Use specific type assertions (e.g.,
as Contributor[]) if the types are known to be compatible
♻️ Example of safer type handling
- admins={admins as any}
+ admins={admins ?? []}
details={moduleDetails}
domains={mentorshipModule.domains || undefined}
entityKey={moduleKey}
labels={mentorshipModule.labels || undefined}
- mentees={mentorshipModule.mentees as any}
- mentors={mentorshipModule.mentors as any}
+ mentees={mentorshipModule.mentees ?? []}
+ mentors={mentorshipModule.mentors ?? []}If type incompatibility exists, align the types at the source (GraphQL types or DetailsCard props) rather than silencing the compiler.
📝 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.
| admins={admins as any} | |
| details={moduleDetails} | |
| domains={mentorshipModule.domains} | |
| domains={mentorshipModule.domains || undefined} | |
| entityKey={moduleKey} | |
| labels={mentorshipModule.labels} | |
| mentees={mentorshipModule.mentees} | |
| mentors={mentorshipModule.mentors} | |
| labels={mentorshipModule.labels || undefined} | |
| mentees={mentorshipModule.mentees as any} | |
| mentors={mentorshipModule.mentors as any} | |
| admins={admins ?? []} | |
| details={moduleDetails} | |
| domains={mentorshipModule.domains || undefined} | |
| entityKey={moduleKey} | |
| labels={mentorshipModule.labels || undefined} | |
| mentees={mentorshipModule.mentees ?? []} | |
| mentors={mentorshipModule.mentors ?? []} |
🤖 Prompt for AI Agents
In
`@frontend/src/app/my/mentorship/programs/`[programKey]/modules/[moduleKey]/page.tsx
around lines 63 - 69, Replace the unsafe "as any" casts for admins, mentees, and
mentors when passing to DetailsCard by either aligning DetailsCard's prop types
with the GraphQL-generated types or adding small, explicit mapping/typing
functions (e.g., mapAdminsToContributors, mapMenteesToContributors,
mapMentorsToContributors) that validate/transform mentorshipModule.admins,
mentorshipModule.mentees, and mentorshipModule.mentors into the exact
Contributor[] (or other specific) type expected; alternatively, if you know the
shape matches, replace "as any" with a precise assertion like "as Contributor[]"
and update DetailsCard props to accept Contributor[] so the compiler enforces
correctness.
| tags = [], | ||
| domains = [], | ||
| entityLeaders = [], | ||
| labels = [], | ||
| modules = [], | ||
| mentors = [], | ||
| mentees = [], | ||
| admins = [], | ||
| entityKey = '', | ||
| geolocationData = [], | ||
| healthMetricsData = [], | ||
| isActive = true, | ||
| isArchived = false, | ||
| languages, | ||
| programKey, | ||
| projectName, | ||
| pullRequests, | ||
| recentIssues, | ||
| recentMilestones, | ||
| recentReleases, | ||
| languages = [], | ||
| programKey = '', | ||
| projectName = '', | ||
| pullRequests = [], | ||
| recentIssues = [], | ||
| recentMilestones = [], | ||
| recentReleases = [], | ||
| repositories = [], | ||
| showAvatar = true, | ||
| socialLinks, | ||
| stats, | ||
| summary, | ||
| title, | ||
| topContributors, | ||
| topics, | ||
| socialLinks = [], | ||
| stats = [], | ||
| summary = '', | ||
| title = '', | ||
| topContributors = [], | ||
| topics = [], |
There was a problem hiding this comment.
Avoid rendering an empty Top Contributors block after defaulting to [].
Because topContributors now defaults to [] (Line 113), the truthy guard at Line 325 will always pass and can render an empty list. Consider checking length (or keeping undefined) to preserve previous behavior.
♻️ Suggested fix
- {topContributors && (
+ {topContributors?.length > 0 && (
<ContributorsList
contributors={topContributors}
icon={HiUserGroup}
maxInitialDisplay={12}
label="Top Contributors"
getUrl={getMemberUrl}
/>
)}🤖 Prompt for AI Agents
In `@frontend/src/components/CardDetailsPage.tsx` around lines 87 - 114, The
default for topContributors being set to [] causes the existing render guard to
always pass and can render an empty block; either stop defaulting
topContributors (remove it from the destructured defaults in the CardDetailsPage
props) or change the render condition to check for content (e.g., use
topContributors && topContributors.length > 0) where the Top Contributors block
is rendered so it only renders when there are actual contributors.
| declare module 'isomorphic-dompurify' { | ||
| const sanitize: (content: string) => string | ||
| export default { sanitize } | ||
| } |
There was a problem hiding this comment.
Invalid syntax in ambient module declaration.
export default { sanitize } is not valid TypeScript in a declaration file—object literals cannot appear in ambient contexts. This will cause a compilation error.
🛠️ Proposed fix
declare module 'isomorphic-dompurify' {
- const sanitize: (content: string) => string
- export default { sanitize }
+ const DOMPurify: {
+ sanitize: (content: string) => string
+ }
+ export default DOMPurify
}Alternatively, consider installing @types/dompurify which isomorphic-dompurify re-exports, providing complete and accurate types.
🤖 Prompt for AI Agents
In `@frontend/src/types/declarations.d.ts` around lines 1 - 4, The ambient module
declaration for 'isomorphic-dompurify' is invalid because it exports an object
literal; replace it with a proper type-only default export by declaring the
sanitize symbol as a function or const type and then exporting that identifier
as the default (e.g. declare function sanitize(...) or declare const sanitize:
(...) and then export default sanitize), or alternatively remove the custom
declaration and install/use `@types/dompurify` which is re-exported by
isomorphic-dompurify to provide correct types; ensure the module name
'isomorphic-dompurify' and the 'sanitize' identifier are used to locate and
update the declaration.
| export default { sanitize } | ||
| } | ||
|
|
||
| declare module 'lodash'; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat frontend/package.json | jq '.devDependencies["@types/lodash"] // .dependencies["@types/lodash"] // "not found"'Repository: OWASP/Nest
Length of output: 64
🏁 Script executed:
cat frontend/src/types/declarations.d.tsRepository: OWASP/Nest
Length of output: 652
Remove the shorthand declare module 'lodash'; declaration.
This shorthand declaration types all lodash imports as any, defeating strict type checking. Since @types/lodash is already installed as a dependency (v^4.17.23), remove this declaration to allow the comprehensive types from @types/lodash to be used instead.
🤖 Prompt for AI Agents
In `@frontend/src/types/declarations.d.ts` at line 6, Remove the shorthand ambient
declaration "declare module 'lodash';" from declarations.d.ts so TypeScript can
use the installed `@types/lodash` package instead of treating lodash imports as
any; locate and delete that exact line (or the entire empty module declaration)
in frontend/src/types/declarations.d.ts and ensure there are no other
conflicting lodash module declarations remaining.
| declare module 'jest-axe' { | ||
| interface AxeResults { | ||
| violations: Array<{ | ||
| id: string | ||
| impact?: string | ||
| description: string | ||
| nodes: Array<{ html: string }> | ||
| }> | ||
| } | ||
| const axe: (html: Element | string) => Promise<AxeResults> | ||
| const toHaveNoViolations: jest.CustomMatcher | ||
| export { axe, toHaveNoViolations } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if jest-axe has bundled types or if `@types/jest-axe` is installed
echo "=== Checking jest-axe package.json ==="
if [ -f frontend/node_modules/jest-axe/package.json ]; then
cat frontend/node_modules/jest-axe/package.json | jq '.types // .typings // "no bundled types"'
else
echo "jest-axe not found in node_modules, checking package.json for version"
fi
echo "=== Checking for `@types/jest-axe` in package.json ==="
cat frontend/package.json | jq '.devDependencies["@types/jest-axe"] // .dependencies["@types/jest-axe"] // "not found"'
echo "=== Checking jest-axe version in dependencies ==="
cat frontend/package.json | jq '.devDependencies["jest-axe"] // .dependencies["jest-axe"] // "not found"'
echo "=== Checking if jest-axe has a types index file ==="
if [ -d frontend/node_modules/jest-axe ]; then
ls -la frontend/node_modules/jest-axe/ | grep -E "\.d\.ts|index"
fiRepository: OWASP/Nest
Length of output: 339
🌐 Web query:
jest-axe v10.0.0 typescript types bundled
💡 Result:
jest-axe@10.0.0 does not bundle TypeScript declaration files (.d.ts). Its docs still direct TypeScript users to install the community-maintained DefinitelyTyped package instead: @types/jest-axe. [1] [2]
The typings live in @types/jest-axe (e.g., index.d.ts), published separately on npm. [3]
Install:
npm i -D jest-axe `@types/jest-axe`Sources: npm page for jest-axe [1], jest-axe GitHub README [2], npm page for @types/jest-axe [3].
🌐 Web query:
jest-axe 10.0.0 toHaveNoViolations AxeResults API
💡 Result:
In jest-axe@10.0.0, the matcher toHaveNoViolations() expects to receive an axe-core “results” object (i.e., AxeResults from axe-core.run(...)) and it will error if results.violations is missing/undefined. [1]
AxeResults shape (axe-core)
The AxeResults interface includes (at least) these fields: toolOptions, passes, violations, incomplete, inapplicable. [2]
TypeScript: what to type as “AxeResults”
jest-axe itself doesn’t ship TS types; the community types (@types/jest-axe) type the matcher as:
(results?: Partial<AxeResults>) => AssertionsResult[3]
So in TS you typically do:
import type { AxeResults } from 'axe-core'(and pass the actualAxeResultsreturned byaxe(...)/axeCore.run(...)), or- if you’re calling the matcher “manually”, pass an object that at minimum has
violations: [](because the runtime matcher checks that property). [1][3]
Why this matters in v10
jest-axe@10.0.0 is mainly an axe-core bump (to axe-core@4.10.2). [4]
Sources: [1] [2] [3] [4]
Install @types/jest-axe instead of using manual type declarations.
The jest-axe@10.0.0 package does not bundle TypeScript definitions. The recommended solution is to install @types/jest-axe from DefinitelyTyped rather than maintain custom declarations. Additionally, the manual AxeResults interface is incomplete—it's missing fields like toolOptions, passes, incomplete, and inapplicable that axe-core results include. The AxeResults type should come from axe-core, not redeclared here.
🤖 Prompt for AI Agents
In `@frontend/src/types/declarations.d.ts` around lines 8 - 20, Remove the
handwritten ambient module for 'jest-axe' (the declare module block defining
AxeResults, axe, and toHaveNoViolations) and instead install and use the
official typings: add the `@types/jest-axe` package to devDependencies and remove
this file; if you still need AxeResults in your code import the proper type from
'axe-core' (e.g., AxeResults) rather than the incomplete local interface, and
let the installed `@types/jest-axe` expose axe and toHaveNoViolations to Jest.
|
Implemented in #3776 |




Proposed change
Resolves #3407
This PR addresses extensive TypeScript strict mode errors and logic issues across the frontend codebase to ensure a clean build and improved type safety.
Key Changes:
Strict Mode fixes: Resolved nullability, implicit any, and type mismatch errors .
Bug Fixes:
Fixed duplicate keys and invalid URL generation in CommitteesPage when data is missing.
Fixed a typo (incubation -> incubator) in SnapshotDetailsPage.
Fixed syntax errors and malformed provider configuration in src/app/api/auth/[...nextauth]/route.ts.
Fixed unsafe type casts in ModuleDetailsPage and RepositoryDetailsPage by aligning generated types and using safer defaults.
Infrastructure:
Updated apolloClient to throw a proper error instead of failing silently when GRAPHQL_URL is missing.
Manually patched generated GraphQL types to include missing fields (url in IssueNode) required by UI components.
Checklist
Required: I followed the contributing workflow
Required: I verified that my code works as intended and resolves the issue as described
Required: I ran make check-test locally: all warnings addressed, tests passed