refactor: enable no-unsafe-assignment rule (@miodec) (#5874)

Co-authored-by: Nad Alaba <37968805+NadAlaba@users.noreply.github.com>
Co-authored-by: Christian Fehmer <fehmer@users.noreply.github.com>
Co-authored-by: Igor Bedesqui <igorbedesqui@gmail.com>
Co-authored-by: amarnathsama <63007641+amarnathsama@users.noreply.github.com>
This commit is contained in:
Jack 2024-09-13 13:18:06 +02:00 committed by GitHub
parent e19b3e3e8b
commit 955eeae2a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 281 additions and 129 deletions

View file

@ -57,10 +57,15 @@ describe("Pb Utils", () => {
mode2: "15",
} as unknown as Result<Mode>;
const run = pb.checkAndUpdatePb(userPbs, undefined, result);
const run = pb.checkAndUpdatePb(
userPbs,
{} as MonkeyTypes.LbPersonalBests,
result
);
expect(run.isPb).toBe(true);
expect(run.personalBests?.["time"]?.["15"]?.[0]).not.toBe(undefined);
expect(run.lbPersonalBests).not.toBe({});
});
it("should not override default pb when saving numbers test", () => {
const userPbs: PersonalBests = {
@ -111,4 +116,93 @@ describe("Pb Utils", () => {
);
});
});
describe("updateLeaderboardPersonalBests", () => {
const userPbs: PersonalBests = {
time: {
"15": [
{
acc: 100,
consistency: 100,
difficulty: "normal",
lazyMode: false,
language: "english",
numbers: false,
punctuation: false,
raw: 100,
timestamp: 0,
wpm: 100,
},
{
acc: 100,
consistency: 100,
difficulty: "normal",
lazyMode: false,
language: "spanish",
numbers: false,
punctuation: false,
raw: 100,
timestamp: 0,
wpm: 100,
},
],
},
words: {},
custom: {},
quote: {},
zen: {},
};
it("should update leaderboard personal bests if they dont exist or the structure is incomplete", () => {
const lbpbstartingvalues = [
undefined,
{},
{ time: {} },
{ time: { "15": {} } },
{ time: { "15": { english: {} } } },
];
const result15 = {
mode: "time",
mode2: "15",
} as unknown as Result<Mode>;
for (const lbPb of lbpbstartingvalues) {
const lbPbPb = pb.updateLeaderboardPersonalBests(
userPbs,
_.cloneDeep(lbPb) as MonkeyTypes.LbPersonalBests,
result15
);
expect(lbPbPb).toEqual({
time: {
"15": {
english: {
acc: 100,
consistency: 100,
difficulty: "normal",
lazyMode: false,
language: "english",
numbers: false,
punctuation: false,
raw: 100,
timestamp: 0,
wpm: 100,
},
spanish: {
acc: 100,
consistency: 100,
difficulty: "normal",
lazyMode: false,
language: "spanish",
numbers: false,
punctuation: false,
raw: 100,
timestamp: 0,
wpm: 100,
},
},
},
});
}
});
});
});

View file

@ -198,7 +198,7 @@ export async function sendVerificationEmail(
JSON.stringify({
decodedTokenEmail: email,
userInfoEmail: userInfo.email,
stack: e.stack,
stack: e.stack as unknown,
}),
userInfo.uid
);

View file

@ -8,7 +8,7 @@ function addSwaggerMiddlewares(app: Application): void {
const openApiSpec = __dirname + "/../../static/api/openapi.json";
let spec = {};
try {
spec = JSON.parse(readFileSync(openApiSpec, "utf8"));
spec = JSON.parse(readFileSync(openApiSpec, "utf8")) as string;
} catch (err) {
Logger.warning(
`Cannot read openApi specification from ${openApiSpec}. Swagger stats will not fully work.`

View file

@ -22,7 +22,7 @@ export function callController<
query: all.query as TQuery,
params: all.params as TParams,
raw: all.req,
ctx: all.req["ctx"],
ctx: all.req["ctx"] as MonkeyTypes.Context,
};
const result = await handler(req);

View file

@ -8,6 +8,20 @@ import MonkeyError from "../utils/error";
import { compareTwoStrings } from "string-similarity";
import { ApproveQuote, Quote } from "@monkeytype/contracts/schemas/quotes";
type JsonQuote = {
text: string;
britishText?: string;
source: string;
length: number;
id: number;
};
type QuoteData = {
language: string;
quotes: JsonQuote[];
groups: [number, number][];
};
const PATH_TO_REPO = "../../../../monkeytype-new-quotes";
let git;
@ -71,11 +85,11 @@ export async function add(
let similarityScore = -1;
if (existsSync(fileDir)) {
const quoteFile = await readFile(fileDir);
const quoteFileJSON = JSON.parse(quoteFile.toString());
const quoteFileJSON = JSON.parse(quoteFile.toString()) as QuoteData;
quoteFileJSON.quotes.every((old) => {
if (compareTwoStrings(old.text as string, quote.text) > 0.9) {
if (compareTwoStrings(old.text, quote.text) > 0.9) {
duplicateId = old.id;
similarityScore = compareTwoStrings(old.text as string, quote.text);
similarityScore = compareTwoStrings(old.text, quote.text);
return false;
}
return true;
@ -155,9 +169,9 @@ export async function approve(
await git.pull("upstream", "master");
if (existsSync(fileDir)) {
const quoteFile = await readFile(fileDir);
const quoteObject = JSON.parse(quoteFile.toString());
const quoteObject = JSON.parse(quoteFile.toString()) as QuoteData;
quoteObject.quotes.every((old) => {
if (compareTwoStrings(old.text as string, quote.text) > 0.8) {
if (compareTwoStrings(old.text, quote.text) > 0.8) {
throw new MonkeyError(409, "Duplicate quote");
}
});
@ -168,7 +182,7 @@ export async function approve(
}
});
quote.id = maxid + 1;
quoteObject.quotes.push(quote);
quoteObject.quotes.push(quote as JsonQuote);
writeFileSync(fileDir, JSON.stringify(quoteObject, null, 2));
message = `Added quote to ${language}.json.`;
} else {

View file

@ -87,7 +87,7 @@ export async function getLastResult(
export async function getResultByTimestamp(
uid: string,
timestamp
timestamp: number
): Promise<MonkeyTypes.DBResult | null> {
return await getResultCollection().findOne({ uid, timestamp });
}

View file

@ -25,14 +25,14 @@ function mergeConfigurations(
const commonKeys = _.intersection(_.keys(base), _.keys(source));
commonKeys.forEach((key) => {
const baseValue = base[key];
const sourceValue = source[key];
const baseValue = base[key] as object;
const sourceValue = source[key] as object;
const isBaseValueObject = _.isPlainObject(baseValue);
const isSourceValueObject = _.isPlainObject(sourceValue);
if (isBaseValueObject && isSourceValueObject) {
merge(baseValue as object, sourceValue as object);
merge(baseValue, sourceValue);
} else if (identity(baseValue) === identity(sourceValue)) {
base[key] = sourceValue;
}

View file

@ -103,14 +103,16 @@ export async function sendEmail(
html: template,
};
let result;
type Result = { response: string; accepted: string[] };
let result: Result;
try {
result = await transporter.sendMail(mailOptions);
result = (await transporter.sendMail(mailOptions)) as Result;
} catch (e) {
recordEmail(templateName, "fail");
return {
success: false,
message: e.message,
message: e.message as string,
};
}

View file

@ -29,11 +29,9 @@ export function init(): void {
encoding: "utf8",
flag: "r",
})
);
) as ServiceAccount;
admin.initializeApp({
credential: admin.credential.cert(
serviceAccount as unknown as ServiceAccount
),
credential: admin.credential.cert(serviceAccount),
});
Logger.success("Firebase app initialized");
}

View file

@ -188,33 +188,27 @@ async function authenticateWithBearerToken(
email: decodedToken.email ?? "",
};
} catch (error) {
const errorCode = error?.errorInfo?.code;
const errorCode = error?.errorInfo?.code as string | undefined;
if (errorCode?.includes("auth/id-token-expired") as boolean | undefined) {
if (errorCode?.includes("auth/id-token-expired")) {
throw new MonkeyError(
401,
"Token expired - please login again",
"authenticateWithBearerToken"
);
} else if (
errorCode?.includes("auth/id-token-revoked") as boolean | undefined
) {
} else if (errorCode?.includes("auth/id-token-revoked")) {
throw new MonkeyError(
401,
"Token revoked - please login again",
"authenticateWithBearerToken"
);
} else if (
errorCode?.includes("auth/user-not-found") as boolean | undefined
) {
} else if (errorCode?.includes("auth/user-not-found")) {
throw new MonkeyError(
404,
"User not found",
"authenticateWithBearerToken"
);
} else if (
errorCode?.includes("auth/argument-error") as boolean | undefined
) {
} else if (errorCode?.includes("auth/argument-error")) {
throw new MonkeyError(
400,
"Incorrect Bearer token format",

View file

@ -53,11 +53,12 @@ function getValue(
path: ConfigurationPath
): boolean {
const keys = (path as string).split(".");
let result = configuration;
let result: unknown = configuration;
for (const key of keys) {
if (result === undefined || result === null)
if (result === undefined || result === null) {
throw new MonkeyError(500, `Invalid configuration path: "${path}"`);
}
result = result[key];
}

View file

@ -129,14 +129,14 @@ export class WeeklyXpLeaderboard {
this.getThisWeeksXpLeaderboardKeys();
// @ts-expect-error
const [results, scores]: string[][] = await connection.getResults(
const [results, scores] = (await connection.getResults(
2, // How many of the arguments are redis keys (https://redis.io/docs/manual/programmability/lua-api/)
weeklyXpLeaderboardScoresKey,
weeklyXpLeaderboardResultsKey,
minRank,
maxRank,
"true"
);
)) as string[][];
if (results === undefined) {
throw new Error(
@ -183,13 +183,17 @@ export class WeeklyXpLeaderboard {
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
// @ts-ignore
const [[, rank], [, totalXp], [, count], [, result]] = await connection
const [[, rank], [, totalXp], [, count], [, result]] = (await connection
.multi()
.zrevrank(weeklyXpLeaderboardScoresKey, uid)
.zscore(weeklyXpLeaderboardScoresKey, uid)
.zcard(weeklyXpLeaderboardScoresKey)
.hget(weeklyXpLeaderboardResultsKey, uid)
.exec();
.exec()) as [
[null, number | null],
[null, string | null],
[null, number | null]
];
if (rank === null) {
return null;

View file

@ -121,14 +121,14 @@ export class DailyLeaderboard {
this.getTodaysLeaderboardKeys();
// @ts-expect-error
const [results]: string[][] = await connection.getResults(
const [results] = (await connection.getResults(
2,
leaderboardScoresKey,
leaderboardResultsKey,
minRank,
maxRank,
"false"
);
)) as string[][];
if (results === undefined) {
throw new Error(
@ -198,7 +198,7 @@ export class DailyLeaderboard {
count: count ?? 0,
rank: rank + 1,
entry: {
...JSON.parse(result ?? "null"),
...(JSON.parse(result ?? "null") as LeaderboardEntry),
},
};
}

View file

@ -285,7 +285,7 @@ export function formatSeconds(
}
export function intersect<T>(a: T[], b: T[], removeDuplicates = false): T[] {
let t;
let t: T[];
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
if (b.length > a.length) (t = b), (b = a), (a = t); // indexOf to loop over shorter
const filtered = a.filter(function (e) {

View file

@ -59,7 +59,14 @@ export function checkAndUpdatePb(
}
if (!_.isNil(lbPersonalBests)) {
updateLeaderboardPersonalBests(userPb, lbPersonalBests, result);
const newLbPb = updateLeaderboardPersonalBests(
userPb,
lbPersonalBests,
result
);
if (newLbPb !== null) {
lbPersonalBests = newLbPb;
}
}
return {
@ -165,26 +172,20 @@ function buildPersonalBest(result: Result): PersonalBest {
};
}
function updateLeaderboardPersonalBests(
export function updateLeaderboardPersonalBests(
userPersonalBests: PersonalBests,
lbPersonalBests: MonkeyTypes.LbPersonalBests,
result: Result
): void {
): MonkeyTypes.LbPersonalBests | null {
if (!shouldUpdateLeaderboardPersonalBests(result)) {
return;
return null;
}
const mode = result.mode;
const mode2 = result.mode2;
lbPersonalBests[mode] = lbPersonalBests[mode] ?? {};
const lbMode2 = lbPersonalBests[mode][mode2] as MonkeyTypes.LbPersonalBests;
if (lbMode2 === undefined || Array.isArray(lbMode2)) {
lbPersonalBests[mode][mode2] = {};
}
const lbPb = lbPersonalBests ?? {};
lbPb[mode] ??= {};
lbPb[mode][mode2] ??= {};
const bestForEveryLanguage = {};
userPersonalBests[mode][mode2].forEach((pb: PersonalBest) => {
const language = pb.language;
if (
@ -194,18 +195,18 @@ function updateLeaderboardPersonalBests(
bestForEveryLanguage[language] = pb;
}
});
_.each(bestForEveryLanguage, (pb: PersonalBest, language: string) => {
const languageDoesNotExist =
lbPersonalBests[mode][mode2][language] === undefined;
const languageDoesNotExist = lbPb[mode][mode2][language] === undefined;
const languageIsEmpty = _.isEmpty(lbPb[mode][mode2][language]);
if (
languageDoesNotExist ||
lbPersonalBests[mode][mode2][language].wpm < pb.wpm
languageIsEmpty ||
lbPb[mode][mode2][language].wpm < pb.wpm
) {
lbPersonalBests[mode][mode2][language] = pb;
lbPb[mode][mode2][language] = pb;
}
});
return lbPb;
}
function shouldUpdateLeaderboardPersonalBests(result: Result): boolean {

View file

@ -2,18 +2,15 @@ import _ from "lodash";
import IORedis from "ioredis";
import { Worker, Job, type ConnectionOptions } from "bullmq";
import Logger from "../utils/logger";
import EmailQueue, {
type EmailTaskContexts,
type EmailType,
} from "../queues/email-queue";
import EmailQueue, { EmailTask, type EmailType } from "../queues/email-queue";
import { sendEmail } from "../init/email-client";
import { recordTimeToCompleteJob } from "../utils/prometheus";
import { addLog } from "../dal/logs";
async function jobHandler(job: Job): Promise<void> {
const type: EmailType = job.data.type;
const email: string = job.data.email;
const ctx: EmailTaskContexts[typeof type] = job.data.ctx;
async function jobHandler(job: Job<EmailTask<EmailType>>): Promise<void> {
const type = job.data.type;
const email = job.data.email;
const ctx = job.data.ctx;
Logger.info(`Starting job: ${type}`);

View file

@ -180,8 +180,8 @@ async function handleWeeklyXpLeaderboardResults(
await addToInboxBulk(mailEntries, inboxConfig);
}
async function jobHandler(job: Job): Promise<void> {
const { taskName, ctx }: LaterTask<LaterTaskType> = job.data;
async function jobHandler(job: Job<LaterTask<LaterTaskType>>): Promise<void> {
const { taskName, ctx } = job.data;
Logger.info(`Starting job: ${taskName}`);

View file

@ -41,7 +41,7 @@ function buildApi(timeout: number): (args: ApiFetcherArgs) => Promise<{
: AbortSignal.timeout(timeout),
});
const body = await response.json();
const body = (await response.json()) as object;
if (response.status >= 400) {
console.error(`${request.method} ${request.path} failed`, {
status: response.status,

View file

@ -9,6 +9,11 @@ import { createErrorMessage } from "../utils/misc";
let analytics: AnalyticsType;
type AcceptedCookies = {
security: boolean;
analytics: boolean;
};
export async function log(
eventName: string,
params?: Record<string, string>
@ -21,12 +26,9 @@ export async function log(
}
const lsString = localStorage.getItem("acceptedCookies");
let acceptedCookies: {
security: boolean;
analytics: boolean;
} | null;
let acceptedCookies;
if (lsString !== undefined && lsString !== null && lsString !== "") {
acceptedCookies = JSON.parse(lsString);
acceptedCookies = JSON.parse(lsString) as AcceptedCookies;
} else {
acceptedCookies = null;
}

View file

@ -13,12 +13,13 @@ export function render(
}
//@ts-expect-error
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const widgetId = grecaptcha.render(element, {
sitekey: siteKey,
callback,
});
captchas[id] = widgetId;
captchas[id] = widgetId as number;
}
export function reset(id: string): void {

View file

@ -143,8 +143,9 @@ export function init(): void {
headOfDocument.appendChild(rampScript);
window._pwGA4PageviewId = "".concat(Date.now());
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/no-unsafe-assignment
window.dataLayer = window.dataLayer || [];
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
window.gtag =
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
window.gtag ||
@ -156,6 +157,7 @@ export function init(): void {
gtag("config", "G-KETCPNHRJF", { send_page_view: false });
gtag("event", "ramp_js", {
send_to: "G-KETCPNHRJF",
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
pageview_id: window._pwGA4PageviewId,
});
}

View file

@ -350,10 +350,12 @@ export function updateActive(): void {
}
for (const [id, select] of Object.entries(groupSelects)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
const ss = select;
const group = getGroup(id as ResultFiltersGroup);
const everythingSelected = Object.values(group).every((v) => v === true);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
const newData = ss.store.getData();
const allOption = $(
@ -682,8 +684,10 @@ function adjustScrollposition(
group: ResultFiltersGroup,
topItem: number = 0
): void {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
const slimSelect = groupSelects[group];
if (slimSelect === undefined) return;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
const listElement = slimSelect.render.content.list;
const topListItem = listElement.children.item(topItem) as HTMLElement;
@ -779,6 +783,7 @@ export async function appendButtons(
);
if (el) {
el.innerHTML = html;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
groupSelects["language"] = new SlimSelect({
select: el.querySelector(".languageSelect") as HTMLSelectElement,
settings: {
@ -840,6 +845,7 @@ export async function appendButtons(
);
if (el) {
el.innerHTML = html;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
groupSelects["funbox"] = new SlimSelect({
select: el.querySelector(".funboxSelect") as HTMLSelectElement,
settings: {
@ -897,6 +903,7 @@ export async function appendButtons(
);
if (el) {
el.innerHTML = html;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
groupSelects["tags"] = new SlimSelect({
select: el.querySelector(".tagsSelect") as HTMLSelectElement,
settings: {

View file

@ -712,6 +712,7 @@ export function show(): void {
Config.typingSpeedUnit + '<br><div class="sub">accuracy</div>'
);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
languageSelector = new SlimSelect({
select:
"#leaderboardsWrapper #leaderboards .leaderboardsTop .buttonGroup.timeRange .languageSelect",

View file

@ -120,7 +120,7 @@ export default class SettingsGroup<T extends ConfigValue> {
select.value = this.configValue as string;
//@ts-expect-error
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unnecessary-type-assertion -- TODO: update slim-select
const ss = select.slim as SlimSelect | undefined;
ss?.store.setSelectedBy("value", [this.configValue as string]);
ss?.render.renderValues();

View file

@ -57,9 +57,9 @@ export class TestActivityCalendar implements MonkeyTypes.TestActivityCalendar {
lastDay: Date
): (number | null | undefined)[] {
//fill calendar with enough values
const values: (number | null | undefined)[] = new Array(
Math.max(0, 386 - data.length)
).fill(undefined);
const values = new Array(Math.max(0, 386 - data.length)).fill(
undefined
) as (number | null | undefined)[];
values.push(...data);
//discard values outside the calendar range

View file

@ -18,6 +18,7 @@ export function init(
}
$("#testActivity").removeClass("hidden");
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
yearSelector = getYearSelector();
initYearSelector("current", userSignUpDate?.getFullYear() || 2022);
update(calendar);
@ -84,6 +85,7 @@ export function initYearSelector(
}
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
const yearSelect = getYearSelector();
yearSelect.setData(years);
years.length > 1 ? yearSelect.enable() : yearSelect.disable();
@ -102,6 +104,7 @@ function updateMonths(months: MonkeyTypes.TestActivityMonth[]): void {
function getYearSelector(): SlimSelect {
if (yearSelector !== undefined) return yearSelector;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
yearSelector = new SlimSelect({
select: "#testActivity .yearSelect",
settings: {

View file

@ -46,7 +46,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;
const message = (e.reason.message ?? e.reason) as string;
Notifications.add(`${message}`, -1, {
customTitle: "DEV: Unhandled rejection",
duration: 5,
@ -54,6 +54,6 @@ window.onunhandledrejection = function (e): void {
console.error(e);
}
void log("error", {
error: e.reason.stack ?? "",
error: (e.reason.stack ?? "") as string,
});
};

View file

@ -139,7 +139,9 @@ function addCheckboxListeners(): void {
`#editPresetModal .modal .changePresetToCurrentCheckbox input`
);
presetToCurrentCheckbox.on("change", async () => {
state.setPresetToCurrent = presetToCurrentCheckbox.prop("checked");
state.setPresetToCurrent = presetToCurrentCheckbox.prop(
"checked"
) as boolean;
await updateEditPresetUI();
});
}
@ -212,9 +214,9 @@ async function apply(): Promise<void> {
"data-preset-id"
) as string;
const updateConfig: boolean = $("#editPresetModal .modal label input").prop(
const updateConfig = $("#editPresetModal .modal label input").prop(
"checked"
);
) as boolean;
const snapshotPresets = DB.getSnapshot()?.presets ?? [];
@ -474,14 +476,13 @@ function getPartialConfigChanges(
state.checkboxes.get(getSettingGroup(settingName)) === true
)
.forEach((settingName) => {
//@ts-expect-error this is fine
activeConfigChanges[settingName] =
//@ts-expect-error this is fine
configChanges[settingName] !== undefined
? //@ts-expect-error this is fine
configChanges[settingName]
: //@ts-expect-error this is fine
defaultConfig[settingName];
const safeSettingName = settingName as keyof MonkeyTypes.ConfigChanges;
const newValue =
configChanges[safeSettingName] !== undefined
? configChanges[safeSettingName]
: defaultConfig[safeSettingName];
// @ts-expect-error cant figure this one out, but it works
activeConfigChanges[safeSettingName] = newValue;
});
return activeConfigChanges;
}

View file

@ -46,6 +46,7 @@ export async function show(
$("#quoteReportModal .reason").val("Grammatical error");
$("#quoteReportModal .comment").val("");
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
state.reasonSelect = new SlimSelect({
select: "#quoteReportModal .reason",
settings: {

View file

@ -273,6 +273,7 @@ export async function show(showOptions?: ShowOptions): Promise<void> {
$("#quoteSearchModal .goToQuoteApprove").addClass("hidden");
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
lengthSelect = new SlimSelect({
select: "#quoteSearchModal .quoteLengthFilter",

View file

@ -65,6 +65,7 @@ export async function show(showOptions: ShowOptions): Promise<void> {
);
await initDropdown();
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
select = new SlimSelect({
select: "#quoteSubmitModal .newQuoteLanguage",
});

View file

@ -51,6 +51,7 @@ export async function show(options: ShowOptions): Promise<void> {
"Inappropriate name";
(modalEl.querySelector(".comment") as HTMLTextAreaElement).value = "";
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
select = new SlimSelect({
select: modalEl.querySelector(".reason") as HTMLElement,
settings: {

View file

@ -159,18 +159,21 @@ export async function show(showOptions?: ShowOptions): Promise<void> {
void modal.show({
...showOptions,
beforeAnimation: async (modalEl) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
languageSelect = new SlimSelect({
select: "#wordFilterModal .languageInput",
settings: {
contentLocation: modalEl,
},
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
layoutSelect = new SlimSelect({
select: "#wordFilterModal .layoutInput",
settings: {
contentLocation: modal.getModal(),
},
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
presetSelect = new SlimSelect({
select: "#wordFilterModal .presetInput",
settings: {

View file

@ -183,7 +183,7 @@ export function updateUI(): void {
}
$(".page.pageAccountSettings").on("click", ".tabs button", (event) => {
state.activeTab = $(event.target).data("tab");
state.activeTab = $(event.target).data("tab") as State["activeTab"];
updateTabs();
});

View file

@ -1062,7 +1062,7 @@ $(".pageSettings .section.tags").on(
"click",
".tagsList .tag .tagButton",
(e) => {
const target = e.currentTarget;
const target = e.currentTarget as HTMLElement;
const tagid = $(target).parent(".tag").attr("data-id") as string;
TagController.toggle(tagid);
$(target).toggleClass("active");
@ -1073,7 +1073,7 @@ $(".pageSettings .section.presets").on(
"click",
".presetsList .preset .presetButton",
async (e) => {
const target = e.currentTarget;
const target = e.currentTarget as HTMLElement;
const presetid = $(target).parent(".preset").attr("data-id") as string;
await PresetController.apply(presetid);
void update();

View file

@ -49,8 +49,12 @@ export async function getPoem(): Promise<Section | false> {
try {
const response = await fetch(apiURL);
const data = await response.json();
const poemObj: PoemObject = data[0];
const data = (await response.json()) as PoemObject[];
const poemObj = data[0];
if (!poemObj) {
return false;
}
const words: string[] = [];

View file

@ -1182,9 +1182,9 @@ async function saveResult(
}
if (data.insertedId !== undefined) {
const result: MonkeyTypes.FullResult<Mode> = JSON.parse(
const result = JSON.parse(
JSON.stringify(completedEvent)
);
) as MonkeyTypes.FullResult<Mode>;
result._id = data.insertedId;
if (data.isPb !== undefined && data.isPb) {
result.isPb = true;

View file

@ -268,7 +268,7 @@ export async function getSection(language: string): Promise<Section> {
let pageid = 0;
if (randomPostReq.status === 200) {
const postObj: Post = await randomPostReq.json();
const postObj = (await randomPostReq.json()) as Post;
sectionObj.title = postObj.title;
sectionObj.author = postObj.author;
pageid = postObj.pageid;
@ -286,8 +286,9 @@ export async function getSection(language: string): Promise<Section> {
sectionReq.onload = (): void => {
if (sectionReq.readyState === 4) {
if (sectionReq.status === 200) {
let sectionText: string = JSON.parse(sectionReq.responseText).query
.pages[pageid.toString()].extract;
let sectionText = JSON.parse(sectionReq.responseText).query.pages[
pageid.toString()
].extract as string;
// Converting to one paragraph
sectionText = sectionText.replace(/<\/p><p>+/g, " ");

View file

@ -25,7 +25,7 @@ export class LocalStorageWithSchema<T> {
return this.fallback;
}
let jsonParsed;
let jsonParsed: unknown;
try {
jsonParsed = JSON.parse(value);
} catch (e) {

View file

@ -8,6 +8,7 @@ import {
Mode2,
PersonalBests,
} from "@monkeytype/contracts/schemas/shared";
import { ZodError, ZodSchema } from "zod";
export function kogasa(cov: number): number {
return (
@ -684,4 +685,24 @@ export function isObject(obj: unknown): obj is Record<string, unknown> {
return typeof obj === "object" && !Array.isArray(obj) && obj !== null;
}
/**
* Parse a JSON string into an object and validate it against a schema
* @param input JSON string
* @param schema Zod schema to validate the JSON against
* @returns The parsed JSON object
*/
export function parseJsonWithSchema<T>(input: string, schema: ZodSchema<T>): T {
try {
const jsonParsed = JSON.parse(input) as unknown;
const zodParsed = schema.parse(jsonParsed);
return zodParsed;
} catch (error) {
if (error instanceof ZodError) {
throw new Error(error.errors.map((err) => err.message).join("\n"));
} else {
throw error;
}
}
}
// DO NOT ALTER GLOBAL OBJECTSONSTRUCTOR, IT WILL BREAK RESULT HASHES

View file

@ -19,9 +19,9 @@ export async function syncNotSignedInLastResult(uid: string): Promise<void> {
return;
}
const result: MonkeyTypes.FullResult<Mode> = JSON.parse(
const result = JSON.parse(
JSON.stringify(notSignedInLastResult)
);
) as MonkeyTypes.FullResult<Mode>;
result._id = response.body.data.insertedId;
if (response.body.data.isPb) {
result.isPb = true;

View file

@ -65,37 +65,32 @@ export async function linkDiscord(hashOverride: string): Promise<void> {
}
}
const customThemeUrlDataSchema = z.object({
c: CustomThemeColorsSchema,
i: z.string().optional(),
s: CustomBackgroundSizeSchema.optional(),
f: CustomBackgroundFilterSchema.optional(),
});
export function loadCustomThemeFromUrl(getOverride?: string): void {
const getValue = Misc.findGetParameter("customTheme", getOverride);
if (getValue === null) return;
let decoded = null;
let decoded: z.infer<typeof customThemeUrlDataSchema>;
try {
decoded = JSON.parse(atob(getValue));
decoded = Misc.parseJsonWithSchema(
atob(getValue),
customThemeUrlDataSchema
);
} catch (e) {
console.log("Custom theme URL decoding failed", e);
Notifications.add(
"Failed to load theme from URL: could not decode theme",
"Failed to load theme from URL: " + (e as Error).message,
0
);
return;
}
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;
@ -151,7 +146,9 @@ export function loadTestSettingsFromUrl(getOverride?: string): void {
const getValue = Misc.findGetParameter("testSettings", getOverride);
if (getValue === null) return;
const de: SharedTestSettings = JSON.parse(decompressFromURI(getValue) ?? "");
const de = JSON.parse(
decompressFromURI(getValue) ?? ""
) as SharedTestSettings;
const applied: Record<string, string> = {};

View file

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