From c8ea9f621169b107e9d7748dbcab07d51a26c29e Mon Sep 17 00:00:00 2001 From: ennajari Date: Fri, 10 Apr 2026 16:04:25 +0100 Subject: [PATCH 1/7] fix: resolve sort comparator and code quality issues - Use localeCompare in sort calls to ensure consistent ordering across locales (setters.ts, Sidebar.tsx, connections.ts, themes.ts) - Add initial value 0 to reduce() in stdDev to handle empty arrays safely - Use explicit undefined check for cached Promise in memoize utility - Add NOSONAR annotations for intentional patterns (thenable object, CharacterCounter side-effect instantiation) - Add .scannerwork/, .claude/, sonar-project.properties to .gitignore --- .gitignore | 5 ++++- backend/src/dal/connections.ts | 2 +- frontend/src/ts/components/pages/leaderboard/Sidebar.tsx | 4 ++-- frontend/src/ts/config/setters.ts | 2 +- frontend/src/ts/constants/themes.ts | 2 +- frontend/src/ts/modals/user-report.ts | 2 +- frontend/src/ts/utils/json-data.ts | 2 +- frontend/src/ts/utils/misc.ts | 1 + packages/util/src/numbers.ts | 2 +- 9 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 2946d00bdf7b..bb7ffde6a1c4 100644 --- a/.gitignore +++ b/.gitignore @@ -132,4 +132,7 @@ frontend/static/webfonts-preview .turbo frontend/.env.sentry-build-plugin .claude/worktrees -1024MiB \ No newline at end of file +1024MiB +.scannerwork/ +.claude/ +sonar-project.properties diff --git a/backend/src/dal/connections.ts b/backend/src/dal/connections.ts index db10e074872d..55e407f12b4a 100644 --- a/backend/src/dal/connections.ts +++ b/backend/src/dal/connections.ts @@ -331,7 +331,7 @@ export async function aggregateWithAcceptedConnections( function getKey(initiatorUid: string, receiverUid: string): string { const ids = [initiatorUid, receiverUid]; - ids.sort(); + ids.sort((a, b) => a.localeCompare(b)); return ids.join("/"); } diff --git a/frontend/src/ts/components/pages/leaderboard/Sidebar.tsx b/frontend/src/ts/components/pages/leaderboard/Sidebar.tsx index 1a875379def8..713d61051fed 100644 --- a/frontend/src/ts/components/pages/leaderboard/Sidebar.tsx +++ b/frontend/src/ts/components/pages/leaderboard/Sidebar.tsx @@ -149,7 +149,7 @@ function normalizeSelection( if (validModes === undefined) throw new Error("no valid leaderboards"); if (mode === null || validModes[mode] === undefined) { - const firstMode = Object.keys(validModes).sort()[0] as Mode | undefined; + const firstMode = Object.keys(validModes).sort((a, b) => a.localeCompare(b))[0] as Mode | undefined; if (!firstMode) { throw new Error(`No valid mode for type ${draft.type}`); } @@ -174,7 +174,7 @@ function normalizeSelection( } if (!language || !supportedLanguages.includes(language)) { - language = supportedLanguages.sort()[0] as Language; + language = supportedLanguages.sort((a, b) => a.localeCompare(b))[0] as Language; } return { ...draft, mode, mode2, language }; diff --git a/frontend/src/ts/config/setters.ts b/frontend/src/ts/config/setters.ts index 385b948fc428..d1ce0c9ab196 100644 --- a/frontend/src/ts/config/setters.ts +++ b/frontend/src/ts/config/setters.ts @@ -186,7 +186,7 @@ export function toggleFunbox(funbox: FunboxName, nosave?: boolean): boolean { newConfig = newConfig.filter((it) => it !== funbox); } else { newConfig.push(funbox); - newConfig.sort(); + newConfig.sort((a, b) => a.localeCompare(b)); } if (!isConfigValueValid("funbox", newConfig, ConfigSchemas.FunboxSchema)) { diff --git a/frontend/src/ts/constants/themes.ts b/frontend/src/ts/constants/themes.ts index 0bb86f625671..787851b4b351 100644 --- a/frontend/src/ts/constants/themes.ts +++ b/frontend/src/ts/constants/themes.ts @@ -2326,7 +2326,7 @@ export const themes: Record = { export type ThemeWithName = Theme & { name: ThemeName }; export const ThemesList: ThemeWithName[] = Object.keys(themes) - .sort() + .sort((a, b) => a.localeCompare(b)) .map( (it) => ({ diff --git a/frontend/src/ts/modals/user-report.ts b/frontend/src/ts/modals/user-report.ts index 13db2a555000..edd4cbc74924 100644 --- a/frontend/src/ts/modals/user-report.ts +++ b/frontend/src/ts/modals/user-report.ts @@ -72,7 +72,7 @@ export async function show(options: ShowOptions): Promise { }, }); - new CharacterCounter(modal.getModal().qsr(".comment"), 250); + new CharacterCounter(modal.getModal().qsr(".comment"), 250); // NOSONAR - constructor registers DOM listeners as side effect } async function hide(): Promise { diff --git a/frontend/src/ts/utils/json-data.ts b/frontend/src/ts/utils/json-data.ts index 3151e9ce5daa..0437db6dbd37 100644 --- a/frontend/src/ts/utils/json-data.ts +++ b/frontend/src/ts/utils/json-data.ts @@ -52,7 +52,7 @@ export function memoizeAsync( const key = getKey ? getKey(...args) : (args[0] as P); const cached = cache.get(key); - if (cached) { + if (cached !== undefined) { return cached; } diff --git a/frontend/src/ts/utils/misc.ts b/frontend/src/ts/utils/misc.ts index 0bc03a48e08e..56d1eea39d4b 100644 --- a/frontend/src/ts/utils/misc.ts +++ b/frontend/src/ts/utils/misc.ts @@ -539,6 +539,7 @@ export function promiseWithResolvers(): { const promiseLike = { // oxlint-disable-next-line no-thenable promise-function-async require-await + // NOSONAR async then( onfulfilled?: ((value: T) => TResult1 | PromiseLike) | null, onrejected?: diff --git a/packages/util/src/numbers.ts b/packages/util/src/numbers.ts index ce29b3cb313e..118472ac11e3 100644 --- a/packages/util/src/numbers.ts +++ b/packages/util/src/numbers.ts @@ -46,7 +46,7 @@ export function stdDev(array: number[]): number { const n = array.length; const meanValue = mean(array); return Math.sqrt( - array.map((x) => Math.pow(x - meanValue, 2)).reduce((a, b) => a + b) / n, + array.map((x) => Math.pow(x - meanValue, 2)).reduce((a, b) => a + b, 0) / n, ); } catch (e) { return 0; From faa7bc42450ce6934ecb3cd801d41768614021b8 Mon Sep 17 00:00:00 2001 From: ennajari Date: Fri, 10 Apr 2026 16:09:13 +0100 Subject: [PATCH 2/7] fix: apply prettier formatting --- frontend/src/ts/components/pages/leaderboard/Sidebar.tsx | 8 ++++++-- packages/util/src/numbers.ts | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/frontend/src/ts/components/pages/leaderboard/Sidebar.tsx b/frontend/src/ts/components/pages/leaderboard/Sidebar.tsx index 713d61051fed..5f39cf0be43b 100644 --- a/frontend/src/ts/components/pages/leaderboard/Sidebar.tsx +++ b/frontend/src/ts/components/pages/leaderboard/Sidebar.tsx @@ -149,7 +149,9 @@ function normalizeSelection( if (validModes === undefined) throw new Error("no valid leaderboards"); if (mode === null || validModes[mode] === undefined) { - const firstMode = Object.keys(validModes).sort((a, b) => a.localeCompare(b))[0] as Mode | undefined; + const firstMode = Object.keys(validModes).sort((a, b) => + a.localeCompare(b), + )[0] as Mode | undefined; if (!firstMode) { throw new Error(`No valid mode for type ${draft.type}`); } @@ -174,7 +176,9 @@ function normalizeSelection( } if (!language || !supportedLanguages.includes(language)) { - language = supportedLanguages.sort((a, b) => a.localeCompare(b))[0] as Language; + language = supportedLanguages.sort((a, b) => + a.localeCompare(b), + )[0] as Language; } return { ...draft, mode, mode2, language }; diff --git a/packages/util/src/numbers.ts b/packages/util/src/numbers.ts index 118472ac11e3..347ce5925ac8 100644 --- a/packages/util/src/numbers.ts +++ b/packages/util/src/numbers.ts @@ -46,7 +46,8 @@ export function stdDev(array: number[]): number { const n = array.length; const meanValue = mean(array); return Math.sqrt( - array.map((x) => Math.pow(x - meanValue, 2)).reduce((a, b) => a + b, 0) / n, + array.map((x) => Math.pow(x - meanValue, 2)).reduce((a, b) => a + b, 0) / + n, ); } catch (e) { return 0; From 0ef75ea342e0096b7320b8e43412738ddfc75e5c Mon Sep 17 00:00:00 2001 From: ennajari Date: Fri, 10 Apr 2026 17:48:22 +0100 Subject: [PATCH 3/7] fix: address review feedback --- .gitignore | 3 --- backend/src/dal/connections.ts | 2 +- frontend/src/ts/modals/user-report.ts | 2 +- frontend/src/ts/utils/misc.ts | 1 - 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index bb7ffde6a1c4..e2a50cb343d3 100644 --- a/.gitignore +++ b/.gitignore @@ -133,6 +133,3 @@ frontend/static/webfonts-preview frontend/.env.sentry-build-plugin .claude/worktrees 1024MiB -.scannerwork/ -.claude/ -sonar-project.properties diff --git a/backend/src/dal/connections.ts b/backend/src/dal/connections.ts index 55e407f12b4a..db10e074872d 100644 --- a/backend/src/dal/connections.ts +++ b/backend/src/dal/connections.ts @@ -331,7 +331,7 @@ export async function aggregateWithAcceptedConnections( function getKey(initiatorUid: string, receiverUid: string): string { const ids = [initiatorUid, receiverUid]; - ids.sort((a, b) => a.localeCompare(b)); + ids.sort(); return ids.join("/"); } diff --git a/frontend/src/ts/modals/user-report.ts b/frontend/src/ts/modals/user-report.ts index edd4cbc74924..13db2a555000 100644 --- a/frontend/src/ts/modals/user-report.ts +++ b/frontend/src/ts/modals/user-report.ts @@ -72,7 +72,7 @@ export async function show(options: ShowOptions): Promise { }, }); - new CharacterCounter(modal.getModal().qsr(".comment"), 250); // NOSONAR - constructor registers DOM listeners as side effect + new CharacterCounter(modal.getModal().qsr(".comment"), 250); } async function hide(): Promise { diff --git a/frontend/src/ts/utils/misc.ts b/frontend/src/ts/utils/misc.ts index 56d1eea39d4b..0bc03a48e08e 100644 --- a/frontend/src/ts/utils/misc.ts +++ b/frontend/src/ts/utils/misc.ts @@ -539,7 +539,6 @@ export function promiseWithResolvers(): { const promiseLike = { // oxlint-disable-next-line no-thenable promise-function-async require-await - // NOSONAR async then( onfulfilled?: ((value: T) => TResult1 | PromiseLike) | null, onrejected?: From 536aa2c7e08a66d1ed28f80f1fc4d5acc19444a0 Mon Sep 17 00:00:00 2001 From: ennajari Date: Fri, 10 Apr 2026 18:09:38 +0100 Subject: [PATCH 4/7] fix: use locale-independent sort comparator --- frontend/src/ts/components/pages/leaderboard/Sidebar.tsx | 4 ++-- frontend/src/ts/config/setters.ts | 2 +- frontend/src/ts/constants/themes.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/ts/components/pages/leaderboard/Sidebar.tsx b/frontend/src/ts/components/pages/leaderboard/Sidebar.tsx index 5f39cf0be43b..4f886f953280 100644 --- a/frontend/src/ts/components/pages/leaderboard/Sidebar.tsx +++ b/frontend/src/ts/components/pages/leaderboard/Sidebar.tsx @@ -150,7 +150,7 @@ function normalizeSelection( if (mode === null || validModes[mode] === undefined) { const firstMode = Object.keys(validModes).sort((a, b) => - a.localeCompare(b), + a < b ? -1 : a > b ? 1 : 0, )[0] as Mode | undefined; if (!firstMode) { throw new Error(`No valid mode for type ${draft.type}`); @@ -177,7 +177,7 @@ function normalizeSelection( if (!language || !supportedLanguages.includes(language)) { language = supportedLanguages.sort((a, b) => - a.localeCompare(b), + a < b ? -1 : a > b ? 1 : 0, )[0] as Language; } diff --git a/frontend/src/ts/config/setters.ts b/frontend/src/ts/config/setters.ts index d1ce0c9ab196..cc71c9030df9 100644 --- a/frontend/src/ts/config/setters.ts +++ b/frontend/src/ts/config/setters.ts @@ -186,7 +186,7 @@ export function toggleFunbox(funbox: FunboxName, nosave?: boolean): boolean { newConfig = newConfig.filter((it) => it !== funbox); } else { newConfig.push(funbox); - newConfig.sort((a, b) => a.localeCompare(b)); + newConfig.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); } if (!isConfigValueValid("funbox", newConfig, ConfigSchemas.FunboxSchema)) { diff --git a/frontend/src/ts/constants/themes.ts b/frontend/src/ts/constants/themes.ts index 787851b4b351..6a4afbe1e95b 100644 --- a/frontend/src/ts/constants/themes.ts +++ b/frontend/src/ts/constants/themes.ts @@ -2326,7 +2326,7 @@ export const themes: Record = { export type ThemeWithName = Theme & { name: ThemeName }; export const ThemesList: ThemeWithName[] = Object.keys(themes) - .sort((a, b) => a.localeCompare(b)) + .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)) .map( (it) => ({ From dffb72110f715d1f2e186fda8ff113f0f0ae9b4d Mon Sep 17 00:00:00 2001 From: ennajari Date: Sat, 11 Apr 2026 10:27:58 +0100 Subject: [PATCH 5/7] sonar-scanner --- .gitignore | 1 + .scannerwork/.sonar_lock | 0 .scannerwork/report-task.txt | 6 ++++++ sonar-project.properties | 14 ++++++++++++++ 4 files changed, 21 insertions(+) create mode 100644 .scannerwork/.sonar_lock create mode 100644 .scannerwork/report-task.txt create mode 100644 sonar-project.properties diff --git a/.gitignore b/.gitignore index e2a50cb343d3..f4786f0431dc 100644 --- a/.gitignore +++ b/.gitignore @@ -133,3 +133,4 @@ frontend/static/webfonts-preview frontend/.env.sentry-build-plugin .claude/worktrees 1024MiB +.claude/settings.local.json diff --git a/.scannerwork/.sonar_lock b/.scannerwork/.sonar_lock new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/.scannerwork/report-task.txt b/.scannerwork/report-task.txt new file mode 100644 index 000000000000..05043dc52f37 --- /dev/null +++ b/.scannerwork/report-task.txt @@ -0,0 +1,6 @@ +projectKey=monkeytype +serverUrl=http://localhost:9000 +serverVersion=26.3.0.120487 +dashboardUrl=http://localhost:9000/dashboard?id=monkeytype +ceTaskId=64806580-c325-406c-aa9a-f7af7ef77360 +ceTaskUrl=http://localhost:9000/api/ce/task?id=64806580-c325-406c-aa9a-f7af7ef77360 diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 000000000000..3b63c5103156 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,14 @@ +sonar.projectKey=monkeytype +sonar.projectName=Monkeytype +sonar.projectVersion=26.15.0 + +sonar.host.url=http://localhost:9000 +sonar.token=sqp_1efa4508047307308d66aebca69d97faefddbdf8 + +sonar.sources=frontend/src,backend/src,packages +sonar.exclusions=**/node_modules/**,**/dist/**,**/.turbo/**,**/build/**,**/*.min.js,**/coverage/** + +sonar.javascript.lcov.reportPaths=coverage/lcov.info +sonar.javascript.node.maxspace=4096 + +sonar.sourceEncoding=UTF-8 From c12b95666fa8a0bc202a88f8de1ecf4766eea74a Mon Sep 17 00:00:00 2001 From: ennajari Date: Sat, 11 Apr 2026 10:32:25 +0100 Subject: [PATCH 6/7] Revert "sonar-scanner" This reverts commit dffb72110f715d1f2e186fda8ff113f0f0ae9b4d. --- .gitignore | 1 - .scannerwork/.sonar_lock | 0 .scannerwork/report-task.txt | 6 ------ sonar-project.properties | 14 -------------- 4 files changed, 21 deletions(-) delete mode 100644 .scannerwork/.sonar_lock delete mode 100644 .scannerwork/report-task.txt delete mode 100644 sonar-project.properties diff --git a/.gitignore b/.gitignore index f4786f0431dc..e2a50cb343d3 100644 --- a/.gitignore +++ b/.gitignore @@ -133,4 +133,3 @@ frontend/static/webfonts-preview frontend/.env.sentry-build-plugin .claude/worktrees 1024MiB -.claude/settings.local.json diff --git a/.scannerwork/.sonar_lock b/.scannerwork/.sonar_lock deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/.scannerwork/report-task.txt b/.scannerwork/report-task.txt deleted file mode 100644 index 05043dc52f37..000000000000 --- a/.scannerwork/report-task.txt +++ /dev/null @@ -1,6 +0,0 @@ -projectKey=monkeytype -serverUrl=http://localhost:9000 -serverVersion=26.3.0.120487 -dashboardUrl=http://localhost:9000/dashboard?id=monkeytype -ceTaskId=64806580-c325-406c-aa9a-f7af7ef77360 -ceTaskUrl=http://localhost:9000/api/ce/task?id=64806580-c325-406c-aa9a-f7af7ef77360 diff --git a/sonar-project.properties b/sonar-project.properties deleted file mode 100644 index 3b63c5103156..000000000000 --- a/sonar-project.properties +++ /dev/null @@ -1,14 +0,0 @@ -sonar.projectKey=monkeytype -sonar.projectName=Monkeytype -sonar.projectVersion=26.15.0 - -sonar.host.url=http://localhost:9000 -sonar.token=sqp_1efa4508047307308d66aebca69d97faefddbdf8 - -sonar.sources=frontend/src,backend/src,packages -sonar.exclusions=**/node_modules/**,**/dist/**,**/.turbo/**,**/build/**,**/*.min.js,**/coverage/** - -sonar.javascript.lcov.reportPaths=coverage/lcov.info -sonar.javascript.node.maxspace=4096 - -sonar.sourceEncoding=UTF-8 From 555b3f57c5f09b034ac8c807d5e78000900e074c Mon Sep 17 00:00:00 2001 From: ennajari Date: Sat, 11 Apr 2026 12:23:16 +0100 Subject: [PATCH 7/7] fix: revert sort comparator to plain .sort() per review --- frontend/src/ts/components/pages/leaderboard/Sidebar.tsx | 8 ++------ frontend/src/ts/config/setters.ts | 2 +- frontend/src/ts/constants/themes.ts | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/frontend/src/ts/components/pages/leaderboard/Sidebar.tsx b/frontend/src/ts/components/pages/leaderboard/Sidebar.tsx index 4f886f953280..1a875379def8 100644 --- a/frontend/src/ts/components/pages/leaderboard/Sidebar.tsx +++ b/frontend/src/ts/components/pages/leaderboard/Sidebar.tsx @@ -149,9 +149,7 @@ function normalizeSelection( if (validModes === undefined) throw new Error("no valid leaderboards"); if (mode === null || validModes[mode] === undefined) { - const firstMode = Object.keys(validModes).sort((a, b) => - a < b ? -1 : a > b ? 1 : 0, - )[0] as Mode | undefined; + const firstMode = Object.keys(validModes).sort()[0] as Mode | undefined; if (!firstMode) { throw new Error(`No valid mode for type ${draft.type}`); } @@ -176,9 +174,7 @@ function normalizeSelection( } if (!language || !supportedLanguages.includes(language)) { - language = supportedLanguages.sort((a, b) => - a < b ? -1 : a > b ? 1 : 0, - )[0] as Language; + language = supportedLanguages.sort()[0] as Language; } return { ...draft, mode, mode2, language }; diff --git a/frontend/src/ts/config/setters.ts b/frontend/src/ts/config/setters.ts index cc71c9030df9..385b948fc428 100644 --- a/frontend/src/ts/config/setters.ts +++ b/frontend/src/ts/config/setters.ts @@ -186,7 +186,7 @@ export function toggleFunbox(funbox: FunboxName, nosave?: boolean): boolean { newConfig = newConfig.filter((it) => it !== funbox); } else { newConfig.push(funbox); - newConfig.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); + newConfig.sort(); } if (!isConfigValueValid("funbox", newConfig, ConfigSchemas.FunboxSchema)) { diff --git a/frontend/src/ts/constants/themes.ts b/frontend/src/ts/constants/themes.ts index 6a4afbe1e95b..0bb86f625671 100644 --- a/frontend/src/ts/constants/themes.ts +++ b/frontend/src/ts/constants/themes.ts @@ -2326,7 +2326,7 @@ export const themes: Record = { export type ThemeWithName = Theme & { name: ThemeName }; export const ThemesList: ThemeWithName[] = Object.keys(themes) - .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)) + .sort() .map( (it) => ({