diff --git a/packages/eslint-config/base.yaml b/packages/eslint-config/base.yaml index a6139a92..25c1b9e3 100644 --- a/packages/eslint-config/base.yaml +++ b/packages/eslint-config/base.yaml @@ -12,6 +12,7 @@ plugins: - '@typescript-eslint' - simple-import-sort - eslint-plugin-import + - eslint-plugin-github rules: '@typescript-eslint/no-shadow': 1 @@ -145,6 +146,8 @@ rules: message: Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand. - selector: WithStatement message: '`with` is disallowed in strict mode because it makes code impossible to predict and optimize.' + + github/array-foreach: 1 simple-import-sort/imports: - warn diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 3b508d3b..20944146 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -8,6 +8,7 @@ "@typescript-eslint/parser": "^6.8.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^17.1.0", + "eslint-plugin-github": "^4.10.1", "eslint-plugin-import": "2.28.1", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-react": "^7.33.2", diff --git a/packages/hydrooj/src/handler/contest.ts b/packages/hydrooj/src/handler/contest.ts index 7f53e900..8cabcdcc 100644 --- a/packages/hydrooj/src/handler/contest.ts +++ b/packages/hydrooj/src/handler/contest.ts @@ -637,9 +637,9 @@ export class ContestUserHandler extends ContestManagementBaseHandler { const tsdocs = await contest.getMultiStatus(domainId, { docId: tid }).project({ uid: 1, attend: 1, startAt: 1, unrank: 1, }).toArray(); - tsdocs.forEach((i) => { - i.endAt = (this.tdoc.duration && i.startAt) ? moment(i.startAt).add(this.tdoc.duration, 'hours').toDate() : null; - }); + for (const tsdoc of tsdocs) { + tsdoc.endAt = (this.tdoc.duration && tsdoc.startAt) ? moment(tsdoc.startAt).add(this.tdoc.duration, 'hours').toDate() : null; + } const udict = await user.getListForRender(domainId, [this.tdoc.owner, ...tsdocs.map((i) => i.uid)]); this.response.body = { tdoc: this.tdoc, tsdocs, udict }; this.response.pjax = 'partials/contest_user.html'; diff --git a/packages/hydrooj/src/handler/home.ts b/packages/hydrooj/src/handler/home.ts index eb8e193b..aa5a7cc9 100644 --- a/packages/hydrooj/src/handler/home.ts +++ b/packages/hydrooj/src/handler/home.ts @@ -36,7 +36,7 @@ export class HomeHandler extends Handler { uids = new Set(); collectUser(uids: number[]) { - uids.forEach((uid) => this.uids.add(uid)); + for (const uid of uids) this.uids.add(uid); } async getHomework(domainId: string, limit = 5) { diff --git a/packages/hydrooj/src/model/domain.ts b/packages/hydrooj/src/model/domain.ts index 451efdc1..5ec4f1b6 100644 --- a/packages/hydrooj/src/model/domain.ts +++ b/packages/hydrooj/src/model/domain.ts @@ -142,7 +142,7 @@ class DomainModel { const affected = await UserModel.getMulti({ _id: { $in: uid } }) .project<{ _id: number, mail: string, uname: string }>({ mail: 1, uname: 1 }) .toArray(); - affected.forEach((udoc) => deleteUserCache(udoc)); + for (const udoc of affected) deleteUserCache(udoc); return await collUser.updateMany({ domainId, uid: { $in: uid } }, { $set: { role } }, { upsert: true }); } diff --git a/packages/hydrooj/src/model/oplog.ts b/packages/hydrooj/src/model/oplog.ts index cf69c3ff..f7e80218 100644 --- a/packages/hydrooj/src/model/oplog.ts +++ b/packages/hydrooj/src/model/oplog.ts @@ -19,7 +19,7 @@ export async function add(data: Partial & { type: string }): Promise d()); + for (const d of disposables) d(); h.cleanup?.(args); }; interval = setInterval(() => { @@ -555,8 +555,10 @@ ${ctx.response.status} ${endTime - startTime}ms ${ctx.response.length}`); const layers = [baseLayer, rendererLayer(router, logger), responseLayer(logger), userLayer]; app.use(async (ctx, next) => await next().catch(console.error)).use(domainLayer); app.use(router.routes()).use(router.allowedMethods()); - layers.forEach((layer) => router.use(layer as any)); - layers.forEach((layer) => app.use(layer as any)); + for (const layer of layers) { + router.use(layer as any); + app.use(layer); + } app.use((ctx) => handle(ctx, NotFoundHandler, () => true)); wsServer.on('connection', async (socket, request) => { socket.on('error', (err) => { diff --git a/packages/hydrooj/src/service/watcher.ts b/packages/hydrooj/src/service/watcher.ts index a139df59..59e15d10 100644 --- a/packages/hydrooj/src/service/watcher.ts +++ b/packages/hydrooj/src/service/watcher.ts @@ -1,3 +1,4 @@ +// This file was adapted from @koishijs, MIT licensed. /* eslint-disable consistent-return */ /* eslint-disable import/no-dynamic-require */ /* eslint-disable @typescript-eslint/no-shadow */ @@ -13,7 +14,7 @@ function loadDependencies(filename: string, ignored: Set) { function traverse({ filename, children }: NodeJS.Module) { if (ignored.has(filename) || dependencies.has(filename) || filename.includes('/node_modules/')) return; dependencies.add(filename); - children.forEach(traverse); + for (const c of children) traverse(c); } traverse(require.cache[filename]); return dependencies; @@ -87,13 +88,13 @@ export default class Watcher extends Service { this.accepted = new Set(this.stashed); this.declined = new Set(this.externals); - this.stashed.forEach((filename) => { + for (const filename of this.stashed) { const { children } = require.cache[filename]; for (const { filename } of children) { if (this.accepted.has(filename) || this.declined.has(filename) || filename.includes('/node_modules/')) continue; pending.push(filename); } - }); + } while (pending.length) { let index = 0; @@ -168,7 +169,7 @@ export default class Watcher extends Service { // we only detect reloads at plugin level // a plugin will be reloaded if any of its dependencies are accepted if (!dependencies.some((dep) => this.accepted.has(dep))) continue; - dependencies.forEach((dep) => this.accepted.add(dep)); + for (const dep of dependencies) this.accepted.add(dep); // prepare for reload let isMarked = false; diff --git a/packages/hydrooj/src/upgrade.ts b/packages/hydrooj/src/upgrade.ts index 1820ee9a..d2f07372 100644 --- a/packages/hydrooj/src/upgrade.ts +++ b/packages/hydrooj/src/upgrade.ts @@ -434,10 +434,10 @@ const scripts: UpgradeScript[] = [ null, async function _65_66() { return await iterateAllDomain(async (ddoc) => { - Object.keys(ddoc.roles).forEach((role) => { + for (const role of Object.keys(ddoc.roles)) { if (['guest', 'root'].includes(role)) return; ddoc.roles[role] = (BigInt(ddoc.roles[role]) | PERM.PREM_VIEW_DISPLAYNAME).toString(); - }); + } await domain.setRoles(ddoc._id, ddoc.roles); }); }, diff --git a/packages/migrate/scripts/syzoj.ts b/packages/migrate/scripts/syzoj.ts index 4e27073b..bc56b216 100644 --- a/packages/migrate/scripts/syzoj.ts +++ b/packages/migrate/scripts/syzoj.ts @@ -331,19 +331,21 @@ export async function run({ if (judgeState) { if (judgeState.compile?.message) data.compilerTexts.push(judgeState.compile.message.replace(/<.+?>/g, '')); if (judgeState.judge) { - judgeState.judge.subtasks.forEach((subtask, index) => { - subtask.cases.forEach((curCase, caseIndex) => { + for (let i = 0; i < judgeState.judge.subtasks.length; i++) { + const subtask = judgeState.judge.subtasks.length[i]; + for (let j = 0; j < subtask.cases.length; j++) { + const curCase = subtask.cases[j]; data.testCases.push({ - subtaskId: index + 1, - id: caseIndex + 1, + subtaskId: i + 1, + id: j + 1, score: Math.trunc((curCase.result?.scoringRate || 0) * 100), time: curCase.result?.time || 0, memory: curCase.result?.memory || 0, message: curCase.result?.spjMessage || curCase.result?.systemMessage || curCase.result?.userError || '', status: curCase.status === 2 ? TestcaseJudgeStatusMap[curCase.result.type] : TestcaseStatusMap[curCase.status], }); - }); - }); + } + } } } if (rdoc.type) { diff --git a/packages/migrate/scripts/universaloj.ts b/packages/migrate/scripts/universaloj.ts index 32045e0d..3b15ee0d 100644 --- a/packages/migrate/scripts/universaloj.ts +++ b/packages/migrate/scripts/universaloj.ts @@ -398,7 +398,7 @@ export async function run({ try { const details = await xml2js.parseStringPromise(result.details); if (details.tests.subtask) { - details.tests.subtask.forEach((subtask) => { + for (const subtask of details.tests.subtask) { if (!subtask.test) { data.testCases.push({ subtaskId: subtask.$.num, @@ -409,7 +409,7 @@ export async function run({ message: 'Skipped', status: STATUS.STATUS_CANCELED, }); - return; + continue; } data.testCases.push(...subtask.test.map((curCase, caseIndex) => ({ subtaskId: subtask.$.num, @@ -420,7 +420,7 @@ export async function run({ message: curCase.res[0] || '', status: statusMap[curCase.$.info] || STATUS.STATUS_WAITING, }))); - }); + } } else if (details.tests.test) { data.testCases.push(...details.tests.test.map((curCase) => ({ subtaskId: 1, diff --git a/packages/onlyoffice/frontend/office.page.ts b/packages/onlyoffice/frontend/office.page.ts index c45a1c27..3ea6d555 100644 --- a/packages/onlyoffice/frontend/office.page.ts +++ b/packages/onlyoffice/frontend/office.page.ts @@ -1,3 +1,4 @@ +/* eslint-disable github/array-foreach */ import { $, addPage, AutoloadPage } from '@hydrooj/ui-default'; /* global DocsAPI */ diff --git a/packages/utils/lib/cases.ts b/packages/utils/lib/cases.ts index 8f2303f3..641ae72f 100644 --- a/packages/utils/lib/cases.ts +++ b/packages/utils/lib/cases.ts @@ -20,13 +20,13 @@ export default async function readYamlCases(cfg: Record = {}, check } if (cfg.interactor) config.interactor = checkFile(cfg.interactor, 'Cannot find interactor {0}.'); if (cfg.validator) config.validator = checkFile(cfg.validator, 'Cannot find validator {0}.'); - ['judge', 'user'].forEach((n) => { + for (const n of ['judge', 'user']) { const conf = cfg[`${n}_extra_files`]; - if (!conf) return; + if (!conf) continue; if (conf instanceof Array) { config[`${n}_extra_files`] = conf.map((file) => checkFile(file, `Cannot find ${n} extra file {0}.`)); } else throw new Error(`Invalid ${n}_extra_files config.`); - }); + } } if (cfg.cases?.length) { config.subtasks = [{ diff --git a/packages/utils/lib/lang.ts b/packages/utils/lib/lang.ts index a311ca53..461bb219 100644 --- a/packages/utils/lib/lang.ts +++ b/packages/utils/lib/lang.ts @@ -27,7 +27,7 @@ export interface LangConfig { export function parseLang(config: string): Record { const file = yaml.load(config) as Record; if (typeof file === 'undefined' || typeof file === 'string' || typeof file === 'number') throw new Error(); - Object.keys(file).filter((i) => i.startsWith('_')).forEach((k) => delete file[k]); + for (const key of Object.keys(file)) if (key.startsWith('_')) delete file[key]; for (const key in file) { const entry = file[key]; if (key.includes('.')) { diff --git a/packages/utils/lib/utils.ts b/packages/utils/lib/utils.ts index 083c218e..1adedf14 100644 --- a/packages/utils/lib/utils.ts +++ b/packages/utils/lib/utils.ts @@ -36,9 +36,7 @@ export function folderSize(folderPath: string) { size += stats.size; const files = fs.readdirSync(p); if (Array.isArray(files)) { - files.forEach((file) => { - _next(path.join(p, file)); - }); + for (const file of files) _next(path.join(p, file)); } } } @@ -247,9 +245,9 @@ export function CallableInstance(property = '__call__') { else func = this.constructor.prototype[property]; const apply = function __call__(...args) { return func.apply(apply, ...args); }; Object.setPrototypeOf(apply, this.constructor.prototype); - Object.getOwnPropertyNames(func).forEach((p) => { + for (const p of Object.getOwnPropertyNames(func)) { Object.defineProperty(apply, p, Object.getOwnPropertyDescriptor(func, p)); - }); + } return apply; } diff --git a/packages/vjudge/src/model.ts b/packages/vjudge/src/model.ts index 55b6429f..564e1ed4 100644 --- a/packages/vjudge/src/model.ts +++ b/packages/vjudge/src/model.ts @@ -59,7 +59,7 @@ class AccountService { await next({ status: STATUS.STATUS_JUDGING, message: `ID = ${rid}` }); const nextFunction = (data) => { if (data.case) delete data.case.message; - if (data.cases) data.cases.forEach((x) => delete x.message); + if (data.cases) for (const x of data.cases) delete x.message; return next(data); }; await this.api.waitForSubmission(rid, task.config?.detail === false ? nextFunction : next, end); diff --git a/packages/vjudge/src/providers/codeforces.ts b/packages/vjudge/src/providers/codeforces.ts index 462557e1..ebd85011 100644 --- a/packages/vjudge/src/providers/codeforces.ts +++ b/packages/vjudge/src/providers/codeforces.ts @@ -177,15 +177,15 @@ export default class CodeforcesProvider extends BasicFetcher implements IBasicPr const text = $dom.window.document.querySelector('.problem-statement').innerHTML; const { window: { document } } = new JSDOM(text); const files = {}; - document.querySelectorAll('img[src]').forEach((ele) => { + for (const ele of document.querySelectorAll('img[src]')) { const src = ele.getAttribute('src'); - if (!src.startsWith('http')) return; + if (!src.startsWith('http')) continue; const file = new PassThrough(); this.get(src).pipe(file); const fid = String.random(8); files[`${fid}.png`] = file; ele.setAttribute('src', `file://${fid}.png`); - }); + } const title = document.querySelector('.title').innerHTML.trim().split('. ')[1]; const time = parseInt(document.querySelector('.time-limit').innerHTML.substr(53, 2), 10); const memory = parseInt(document.querySelector('.memory-limit').innerHTML.substr(55, 4), 10); @@ -200,11 +200,11 @@ export default class CodeforcesProvider extends BasicFetcher implements IBasicPr const note = document.querySelector('.note')?.innerHTML.trim(); document.querySelector('.note')?.remove(); document.querySelector('.sample-tests')?.remove(); - document.querySelectorAll('.section-title').forEach((ele) => { + for (const ele of document.querySelectorAll('.section-title')) { const e = document.createElement('h2'); e.innerHTML = ele.innerHTML; ele.replaceWith(e); - }); + } const description = document.body.innerHTML.trim(); return { title: id.startsWith('P921') ? title.replace('1', id.split('P921')[1]) : title, diff --git a/packages/vjudge/src/providers/csgoj.ts b/packages/vjudge/src/providers/csgoj.ts index 62bd88b1..90a61d35 100644 --- a/packages/vjudge/src/providers/csgoj.ts +++ b/packages/vjudge/src/providers/csgoj.ts @@ -93,12 +93,12 @@ export default class CSGOJProvider extends BasicFetcher implements IBasicProvide const pDescription = document.querySelector('div[name="Description"]'); const files = {}; const images = {}; - pDescription.querySelectorAll('img[src]').forEach((ele) => { + for (const ele of pDescription.querySelectorAll('img[src]')) { let src = ele.getAttribute('src').replace('.svg', '.png'); src = new URL(src, 'https://cpc.csgrandeur.cn').toString(); if (images[src]) { ele.setAttribute('src', `file://${images[src]}.png`); - return; + continue; } const file = new PassThrough(); this.get(src).pipe(file); @@ -106,7 +106,7 @@ export default class CSGOJProvider extends BasicFetcher implements IBasicProvide images[src] = fid; files[`${fid}.png`] = file; ele.setAttribute('src', `file://${fid}.png`); - }); + } const description = [...pDescription.children].map((i) => i.outerHTML).join(''); const input = [...document.querySelector('div[name="Input"]').children].map((i) => i.outerHTML).join(''); const output = [...document.querySelector('div[name="Output"]').children].map((i) => i.outerHTML).join(''); diff --git a/packages/vjudge/src/providers/poj.ts b/packages/vjudge/src/providers/poj.ts index 0b86b1c3..3be57dee 100644 --- a/packages/vjudge/src/providers/poj.ts +++ b/packages/vjudge/src/providers/poj.ts @@ -113,11 +113,11 @@ export default class POJProvider extends BasicFetcher implements IBasicProvider content.children[0].remove(); content.children[0].remove(); content.children[0].remove(); - content.querySelectorAll('img[src]').forEach((ele) => { + for (const ele of content.querySelectorAll('img[src]')) { const src = ele.getAttribute('src'); if (images[src]) { ele.setAttribute('src', `file://${images[src]}.png`); - return; + continue; } const file = new PassThrough(); this.get(src).pipe(file); @@ -125,7 +125,7 @@ export default class POJProvider extends BasicFetcher implements IBasicProvider images[src] = fid; files[`${fid}.png`] = file; ele.setAttribute('src', `file://${fid}.png`); - }); + } let lastId = 0; let markNext = ''; let html = ''; diff --git a/packages/vjudge/src/providers/spoj.ts b/packages/vjudge/src/providers/spoj.ts index 0754fbb9..08e08023 100644 --- a/packages/vjudge/src/providers/spoj.ts +++ b/packages/vjudge/src/providers/spoj.ts @@ -52,15 +52,15 @@ export default class SPOJProvider extends BasicFetcher implements IBasicProvider logger.info(id); const { document } = await this.html(`/problems/${id}/`); const files = {}; - document.querySelector('#problem-body').querySelectorAll('img[src]').forEach((ele) => { + for (const ele of document.querySelector('#problem-body').querySelectorAll('img[src]')) { const src = ele.getAttribute('src'); - if (!src.startsWith('http')) return; + if (!src.startsWith('http')) continue; const file = new PassThrough(); this.get(src).pipe(file); const fid = String.random(8); files[`${fid}.png`] = file; ele.setAttribute('src', `file://${fid}.png`); - }); + } const meta = document.querySelector('#problem-meta').children[1]; const window = await this.html(`/submit/${id}/`); const langs = Array.from(window.document.querySelector('#lang').querySelectorAll('option')) diff --git a/packages/vjudge/src/providers/uoj.ts b/packages/vjudge/src/providers/uoj.ts index 34af7cc6..9da08fc1 100644 --- a/packages/vjudge/src/providers/uoj.ts +++ b/packages/vjudge/src/providers/uoj.ts @@ -71,15 +71,15 @@ export default class UOJProvider extends BasicFetcher implements IBasicProvider const res = await this.get(`/problem/${id.split('P')[1]}`); const { window: { document } } = new JSDOM(res.text); const files = {}; - document.querySelectorAll('article>img[src]').forEach((ele) => { + for (const ele of document.querySelectorAll('article>img[src]')) { const src = ele.getAttribute('src'); - if (!src.startsWith('http')) return; + if (!src.startsWith('http')) continue; const file = new PassThrough(); this.get(src).pipe(file); const fid = String.random(8); files[`${fid}.png`] = file; ele.setAttribute('src', `file://${fid}.png`); - }); + } const contentNode = document.querySelector('article'); const titles = contentNode.querySelectorAll('h3'); for (const title of titles) {