refactor: enable no-unsafe-argument rule (@miodec) (#5872)

This commit is contained in:
Jack 2024-09-11 14:23:00 +02:00 committed by GitHub
parent 4f75a00cb3
commit 93d6fff895
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 157 additions and 55 deletions

View file

@ -116,7 +116,11 @@ export async function handleReports(
});
await UserDAL.addToInbox(report.uid, [mail], inboxConfig);
} catch (e) {
throw new MonkeyError(e.status, e.message);
if (e instanceof MonkeyError) {
throw new MonkeyError(e.status, e.message);
} else {
throw new MonkeyError(500, "Error handling reports: " + e.message);
}
}
}
}

View file

@ -1109,9 +1109,9 @@ export function generateCurrentTestActivity(
//make sure lastYearData covers the full year
if (lastYearData.length < Dates.getDaysInYear(lastYear)) {
lastYearData.push(
...new Array(Dates.getDaysInYear(lastYear) - lastYearData.length).fill(
...(new Array(Dates.getDaysInYear(lastYear) - lastYearData.length).fill(
undefined
)
) as (number | null)[])
);
}
//use enough days of the last year to have 372 days in total to always fill the first week of the graph

View file

@ -73,9 +73,9 @@ export async function add(
const quoteFile = await readFile(fileDir);
const quoteFileJSON = JSON.parse(quoteFile.toString());
quoteFileJSON.quotes.every((old) => {
if (compareTwoStrings(old.text, quote.text) > 0.9) {
if (compareTwoStrings(old.text as string, quote.text) > 0.9) {
duplicateId = old.id;
similarityScore = compareTwoStrings(old.text, quote.text);
similarityScore = compareTwoStrings(old.text as string, quote.text);
return false;
}
return true;
@ -157,7 +157,7 @@ export async function approve(
const quoteFile = await readFile(fileDir);
const quoteObject = JSON.parse(quoteFile.toString());
quoteObject.quotes.every((old) => {
if (compareTwoStrings(old.text, quote.text) > 0.8) {
if (compareTwoStrings(old.text as string, quote.text) > 0.8) {
throw new MonkeyError(409, "Duplicate quote");
}
});

View file

@ -756,7 +756,7 @@ export async function getStats(
}
export async function getFavoriteQuotes(
uid
uid: string
): Promise<NonNullable<MonkeyTypes.DBUser["favoriteQuotes"]>> {
const user = await getPartialUser(uid, "get favorite quotes", [
"favoriteQuotes",
@ -1072,7 +1072,7 @@ export async function updateStreak(
} else if (!isToday(streak.lastResultTimestamp, streak.hourOffset ?? 0)) {
void addImportantLog(
"streak_lost",
JSON.parse(JSON.stringify(streak)),
JSON.parse(JSON.stringify(streak)) as Record<string, unknown>,
uid
);
streak.length = 1;

View file

@ -32,7 +32,7 @@ function mergeConfigurations(
const isSourceValueObject = _.isPlainObject(sourceValue);
if (isBaseValueObject && isSourceValueObject) {
merge(baseValue, sourceValue);
merge(baseValue as object, sourceValue as object);
} else if (identity(baseValue) === identity(sourceValue)) {
base[key] = sourceValue;
}

View file

@ -58,7 +58,7 @@ export async function connect(): Promise<void> {
await mongoClient.connect();
db = mongoClient.db(DB_NAME);
} catch (error) {
Logger.error(error.message);
Logger.error(error.message as string);
Logger.error(
"Failed to connect to database. Exiting with exit status code 1."
);

View file

@ -72,7 +72,7 @@ export async function init(): Promise<void> {
Logger.success("Email client configuration verified");
} catch (error) {
transportInitialized = false;
Logger.error(error.message);
Logger.error(error.message as string);
Logger.error("Failed to verify email client configuration.");
}
}

View file

@ -53,7 +53,7 @@ export async function connect(): Promise<void> {
connected = true;
} catch (error) {
Logger.error(error.message);
Logger.error(error.message as string);
if (isDevEnvironment()) {
await connection.quit();
Logger.warning(

View file

@ -14,7 +14,10 @@ import crypto from "crypto";
import { performance } from "perf_hooks";
import { TsRestRequestHandler } from "@ts-rest/express";
import { AppRoute, AppRouter } from "@ts-rest/core";
import { RequestAuthenticationOptions } from "@monkeytype/contracts/schemas/api";
import {
EndpointMetadata,
RequestAuthenticationOptions,
} from "@monkeytype/contracts/schemas/api";
import { Configuration } from "@monkeytype/contracts/schemas/configuration";
const DEFAULT_OPTIONS: RequestAuthenticationOptions = {
@ -45,7 +48,8 @@ export function authenticateTsRestRequest<
): Promise<void> => {
const options = {
...DEFAULT_OPTIONS,
...(req.tsRestRoute["metadata"]?.["authenticationOptions"] ?? {}),
...((req.tsRestRoute["metadata"]?.["authenticationOptions"] ??
{}) as EndpointMetadata),
};
return _authenticateRequestInternal(req, _res, next, options);
};

View file

@ -18,7 +18,7 @@ export function verifyRequiredConfiguration<
next: NextFunction
): Promise<void> => {
const requiredConfigurations = getRequireConfigurations(
req.tsRestRoute["metadata"]
req.tsRestRoute["metadata"] as EndpointMetadata | undefined
);
if (requiredConfigurations === undefined) {

View file

@ -68,7 +68,10 @@ async function errorHandlingMiddleware(
) {
recordServerErrorByVersion(version);
const { uid, errorId } = monkeyResponse.data;
const { uid, errorId } = monkeyResponse.data as {
uid: string;
errorId: string;
};
try {
await addLog(
@ -77,7 +80,7 @@ async function errorHandlingMiddleware(
uid
);
await db.collection<DBError>("errors").insertOne({
_id: errorId,
_id: new ObjectId(errorId),
timestamp: Date.now(),
status: monkeyResponse.status,
uid,
@ -89,7 +92,8 @@ async function errorHandlingMiddleware(
});
} catch (e) {
Logger.error("Logging to db failed.");
Logger.error(e);
Logger.error(e.message as string);
console.error(e);
}
} else {
Logger.error(`Error: ${error.message} Stack: ${error.stack}`);
@ -103,7 +107,8 @@ async function errorHandlingMiddleware(
return;
} catch (e) {
Logger.error("Error handling middleware failed.");
Logger.error(e);
Logger.error(e.message as string);
console.error(e);
}
handleMonkeyResponse(

View file

@ -74,7 +74,7 @@ async function bootServer(port: number): Promise<Server> {
recordServerVersion(version);
} catch (error) {
Logger.error("Failed to boot server");
Logger.error(error.message);
Logger.error(error.message as string);
console.error(error);
return process.exit(1);
}

View file

@ -196,7 +196,7 @@ export class WeeklyXpLeaderboard {
}
//TODO parse with zod?
const parsed = JSON.parse(result ?? "null") as Omit<
const parsed = JSON.parse((result as string) ?? "null") as Omit<
XpLeaderboardEntry,
"rank" | "count" | "totalXp"
>;
@ -204,7 +204,7 @@ export class WeeklyXpLeaderboard {
return {
rank: rank + 1,
count: count ?? 0,
totalXp: parseInt(totalXp, 10),
totalXp: parseInt(totalXp as string, 10),
...parsed,
};
}

View file

@ -167,17 +167,25 @@ export class DailyLeaderboard {
const { leaderboardScoresKey, leaderboardResultsKey } =
this.getTodaysLeaderboardKeys();
// @ts-expect-error
const [[, rank], [, count], [, result], [, minScore]] = await connection
const redisExecResult = (await connection
.multi()
.zrevrank(leaderboardScoresKey, uid)
.zcard(leaderboardScoresKey)
.hget(leaderboardResultsKey, uid)
.zrange(leaderboardScoresKey, 0, 0, "WITHSCORES")
.exec();
.exec()) as [
[null, number | null],
[null, number | null],
[null, string | null],
[null, [string, string] | null]
];
const [[, rank], [, count], [, result], [, minScore]] = redisExecResult;
const minWpm =
minScore.length > 0 ? parseInt(minScore[1]?.slice(1, 6)) / 100 : 0;
minScore !== null && minScore.length > 0
? parseInt(minScore[1]?.slice(1, 6)) / 100
: 0;
if (rank === null) {
return {
minWpm,

View file

@ -35,7 +35,8 @@ export function handleMonkeyResponse(
//@ts-expect-error ignored so that we can see message in swagger stats
res.monkeyMessage = message;
if ([301, 302].includes(status)) {
res.redirect(data);
// todo add stronger types here, maybe a MonkeyRedirectResponse
res.redirect(data as string);
return;
}

View file

@ -104,6 +104,7 @@ import * as TestStats from "../test/test-stats";
import * as QuoteSearchModal from "../modals/quote-search";
import * as FPSCounter from "../elements/fps-counter";
import { migrateConfig } from "../utils/config";
import { PartialConfigSchema } from "@monkeytype/contracts/schemas/configs";
const layoutsPromise = JSONData.getLayoutsList();
layoutsPromise
@ -372,7 +373,10 @@ export const commands: MonkeyTypes.CommandsSubgroup = {
exec: async ({ input }): Promise<void> => {
if (input === undefined || input === "") return;
try {
await UpdateConfig.apply(migrateConfig(JSON.parse(input)));
const parsedConfig = PartialConfigSchema.strip().parse(
JSON.parse(input)
);
await UpdateConfig.apply(migrateConfig(parsedConfig));
UpdateConfig.saveFullConfigToLocalStorage();
void Settings.update();
Notifications.add("Done", 1);

View file

@ -1138,7 +1138,11 @@ $(document).on("keydown", async (event) => {
);
if (funbox?.functions?.preventDefaultEvent) {
if (
await funbox.functions.preventDefaultEvent(event as JQuery.KeyDownEvent)
await funbox.functions.preventDefaultEvent(
//i cant figure this type out, but it works fine
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
event as JQuery.KeyDownEvent
)
) {
event.preventDefault();
handleChar(event.key, TestInput.input.current.length);

View file

@ -205,7 +205,7 @@ export function preview(
debouncedPreview(themeIdentifier, customColorsOverride);
}
const debouncedPreview = debounce(
const debouncedPreview = debounce<(t: string, c?: string[]) => void>(
250,
(themeIdenfitier, customColorsOverride) => {
isPreviewingTheme = true;

View file

@ -796,7 +796,9 @@ export async function appendButtons(
): void | boolean => {
return selectBeforeChangeFn(
"language",
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
selectedOptions,
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
oldSelectedOptions
);
},
@ -855,7 +857,9 @@ export async function appendButtons(
): void | boolean => {
return selectBeforeChangeFn(
"funbox",
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
selectedOptions,
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
oldSelectedOptions
);
},
@ -910,7 +914,9 @@ export async function appendButtons(
): void | boolean => {
return selectBeforeChangeFn(
"tags",
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
selectedOptions,
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
oldSelectedOptions
);
},
@ -945,7 +951,7 @@ $(".group.presetFilterButtons .filterBtns").on(
"click",
".filterPresets .delete-filter-preset",
(e) => {
void deleteFilterPreset($(e.currentTarget).data("id"));
void deleteFilterPreset($(e.currentTarget).data("id") as string);
}
);

View file

@ -80,7 +80,7 @@ export default class SettingsGroup<T extends ConfigValue> {
return;
}
const debounced = debounce(250, (val) => {
const debounced = debounce<(val: T) => void>(250, (val) => {
this.setValue(val);
});

View file

@ -41,7 +41,7 @@ $(accountPage).on("click", ".group.history .resultEditTagsButton", (e) => {
const tags = $(e.target).attr("data-tags");
EditResultTagsModal.show(
resultid ?? "",
JSON.parse(tags ?? "[]"),
JSON.parse(tags ?? "[]") as string[],
"accountPage"
);
});

View file

@ -47,7 +47,7 @@ window.onerror = function (message, url, line, column, error): void {
window.onunhandledrejection = function (e): void {
if (Misc.isDevEnvironment()) {
const message = e.reason.message ?? e.reason;
Notifications.add(message, -1, {
Notifications.add(`${message}`, -1, {
customTitle: "DEV: Unhandled rejection",
duration: 5,
});

View file

@ -1,3 +1,4 @@
import { PartialConfigSchema } from "@monkeytype/contracts/schemas/configs";
import * as UpdateConfig from "../config";
import * as Notifications from "../elements/notifications";
import AnimatedModal from "../utils/animated-modal";
@ -46,10 +47,20 @@ const modal = new AnimatedModal({
return;
}
try {
await UpdateConfig.apply(migrateConfig(JSON.parse(state.value)));
const parsedConfig = PartialConfigSchema.strip().parse(
JSON.parse(state.value)
);
await UpdateConfig.apply(migrateConfig(parsedConfig));
} catch (e) {
Notifications.add("Failed to import settings: " + e, -1);
Notifications.add(
"Failed to import settings: incorrect data schema",
0
);
console.error(e);
void modal.hide();
return;
}
Notifications.add("Settings imported", 1);
UpdateConfig.saveFullConfigToLocalStorage();
void modal.hide();
});

View file

@ -34,7 +34,9 @@ function hide(): void {
function save(): boolean {
const name = $("#saveCustomTextModal .textName").val() as string;
const checkbox = $("#saveCustomTextModal .isLongText").prop("checked");
const checkbox = $("#saveCustomTextModal .isLongText").prop(
"checked"
) as boolean;
if (!name) {
Notifications.add("Custom text needs a name", 0);
@ -54,7 +56,9 @@ function save(): boolean {
function updateIndicatorAndButton(): void {
const val = $("#saveCustomTextModal .textName").val() as string;
const checkbox = $("#saveCustomTextModal .isLongText").prop("checked");
const checkbox = $("#saveCustomTextModal .isLongText").prop(
"checked"
) as boolean;
if (!val) {
indicator?.hide();

View file

@ -6,6 +6,7 @@ import AnimatedModal, {
ShowOptions,
} from "../utils/animated-modal";
import { showPopup } from "./simple-modals";
import * as Notifications from "../elements/notifications";
async function fill(): Promise<void> {
const names = CustomText.getCustomTextNames();
@ -46,7 +47,13 @@ async function fill(): Promise<void> {
longListEl.html(longList);
$("#savedTextsModal .list .savedText .button.delete").on("click", (e) => {
const name = $(e.target).closest(".savedText").data("name");
const name = $(e.target).closest(".savedText").data("name") as
| string
| undefined;
if (name === undefined) {
Notifications.add("Failed to show delete modal: no name found", -1);
return;
}
showPopup("deleteCustomText", [name], {
modalChain: modal as AnimatedModal<unknown, unknown>,
});
@ -55,7 +62,13 @@ async function fill(): Promise<void> {
$("#savedTextsModal .listLong .savedLongText .button.delete").on(
"click",
(e) => {
const name = $(e.target).closest(".savedLongText").data("name");
const name = $(e.target).closest(".savedLongText").data("name") as
| string
| undefined;
if (name === undefined) {
Notifications.add("Failed to show delete modal: no name found", -1);
return;
}
showPopup("deleteCustomTextLong", [name], {
modalChain: modal as AnimatedModal<unknown, unknown>,
});
@ -65,7 +78,13 @@ async function fill(): Promise<void> {
$("#savedTextsModal .listLong .savedLongText .button.resetProgress").on(
"click",
(e) => {
const name = $(e.target).closest(".savedLongText").data("name");
const name = $(e.target).closest(".savedLongText").data("name") as
| string
| undefined;
if (name === undefined) {
Notifications.add("Failed to show delete modal: no name found", -1);
return;
}
showPopup("resetProgressCustomTextLong", [name], {
modalChain: modal as AnimatedModal<unknown, unknown>,
});

View file

@ -1217,7 +1217,7 @@ $(".pageAccount .group.presetFilterButtons").on(
"click",
".filterBtns .filterPresets .select-filter-preset",
async (e) => {
await ResultFilters.setFilterPreset($(e.target).data("id"));
await ResultFilters.setFilterPreset($(e.target).data("id") as string);
void update();
}
);

View file

@ -58,7 +58,7 @@ export async function replace(word: string): Promise<string> {
(_, $1, $2, $3) =>
$1 +
($2.charAt(0) === $2.charAt(0).toUpperCase()
? shouldWholeReplacementWordBeCapitalised($2)
? shouldWholeReplacementWordBeCapitalised($2 as string)
? randomReplacement.toUpperCase()
: capitalizeFirstLetterOfEachWord(randomReplacement)
: randomReplacement) +

View file

@ -325,9 +325,9 @@ $("#replayWords").on("click", "letter", (event) => {
const replayWords = document.querySelector("#replayWords");
const words = [...(replayWords?.children ?? [])];
targetWordPos = words.indexOf(event.target.parentNode);
targetWordPos = words.indexOf(event.target.parentNode as HTMLElement);
const letters = [...(words[targetWordPos] as HTMLElement).children];
targetCurPos = letters.indexOf(event.target);
targetCurPos = letters.indexOf(event.target as HTMLElement);
initializeReplayPrompt();
loadOldReplay();

View file

@ -1054,7 +1054,9 @@ export async function finish(difficultyFailed = false): Promise<void> {
$("#result .stats .dailyLeaderboard").addClass("hidden");
TestStats.setLastResult(JSON.parse(JSON.stringify(completedEvent)));
TestStats.setLastResult(
JSON.parse(JSON.stringify(completedEvent)) as CompletedEvent
);
if (!ConnectionState.get()) {
ConnectionState.showOfflineBanner();

View file

@ -12,7 +12,16 @@ import * as AccountButton from "../elements/account-button";
import { restart as restartTest } from "../test/test-logic";
import * as ChallengeController from "../controllers/challenge-controller";
import { Mode, Mode2 } from "@monkeytype/contracts/schemas/shared";
import { Difficulty } from "@monkeytype/contracts/schemas/configs";
import {
CustomBackgroundFilter,
CustomBackgroundFilterSchema,
CustomBackgroundSize,
CustomBackgroundSizeSchema,
CustomThemeColors,
CustomThemeColorsSchema,
Difficulty,
} from "@monkeytype/contracts/schemas/configs";
import { z } from "zod";
export async function linkDiscord(hashOverride: string): Promise<void> {
if (!hashOverride) return;
@ -64,12 +73,33 @@ export function loadCustomThemeFromUrl(getOverride?: string): void {
try {
decoded = JSON.parse(atob(getValue));
} catch (e) {
Notifications.add("Invalid custom theme ", 0);
console.log("Custom theme URL decoding failed", e);
Notifications.add(
"Failed to load theme from URL: could not decode theme",
0
);
return;
}
let colorArray = [];
let image, size, filter;
const decodedSchema = z.object({
c: CustomThemeColorsSchema,
i: z.string().optional(),
s: CustomBackgroundSizeSchema.optional(),
f: CustomBackgroundFilterSchema.optional(),
});
const parsed = decodedSchema.safeParse(decoded);
if (!parsed.success) {
Notifications.add("Failed to load theme from URL: invalid data schema", 0);
return;
}
decoded = parsed.data;
let colorArray: CustomThemeColors | undefined;
let image: string | undefined;
let size: CustomBackgroundSize | undefined;
let filter: CustomBackgroundFilter | undefined;
if (Array.isArray(decoded.c) && decoded.c.length === 10) {
colorArray = decoded.c;
image = decoded.i;
@ -77,11 +107,11 @@ export function loadCustomThemeFromUrl(getOverride?: string): void {
filter = decoded.f;
} else if (Array.isArray(decoded) && decoded.length === 10) {
// This is for backward compatibility with old format
colorArray = decoded;
colorArray = decoded as unknown as CustomThemeColors;
}
if (colorArray.length === 0) {
Notifications.add("Invalid custom theme ", 0);
if (colorArray === undefined || colorArray.length !== 10) {
Notifications.add("Failed to load theme from URL: no colors found", 0);
return;
}
@ -91,7 +121,7 @@ export function loadCustomThemeFromUrl(getOverride?: string): void {
UpdateConfig.setCustomThemeColors(colorArray);
Notifications.add("Custom theme applied", 1);
if (image !== undefined) {
if (image !== undefined && size !== undefined && filter !== undefined) {
UpdateConfig.setCustomBackground(image);
UpdateConfig.setCustomBackgroundSize(size);
UpdateConfig.setCustomBackgroundFilter(filter);

View file

@ -81,11 +81,11 @@ module.exports = {
// TODO: enable at some point
"@typescript-eslint/no-unsafe-assignment": "off", //~63
"@typescript-eslint/no-unsafe-argument": "off", //~37
"@typescript-eslint/no-unsafe-call": "off", //~76
"@typescript-eslint/no-unsafe-member-access": "off", //~105
//
"@typescript-eslint/no-unsafe-argument": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-confusing-void-expression": [
"error",