workspace: add oxlint

This commit is contained in:
undefined committed 2025-03-10 16:16:34 +08:00
1 parent b48e70019a
commit cda3a8f855
18 files changed
+120 -44

No files matched your search

+1
View File
@@ -30,6 +30,7 @@ yarn.lock
pnpm-lock.yaml
tsconfig.json
tsconfig.*.json
.oxlintrc.json
# Project Rules
## Core
+72
View File
@@ -0,0 +1,72 @@
import fs from 'fs';
import { rules as baseBestPracticesRules } from 'eslint-config-airbnb-base/rules/best-practices';
import { rules as baseErrorsRules } from 'eslint-config-airbnb-base/rules/errors';
import { rules as baseES6Rules } from 'eslint-config-airbnb-base/rules/es6';
import { rules as baseStyleRules } from 'eslint-config-airbnb-base/rules/style';
import { rules as baseVariablesRules } from 'eslint-config-airbnb-base/rules/variables';
import yaml from 'js-yaml';
const base = yaml.load(fs.readFileSync('base.yaml', 'utf8')) as any;
const react = yaml.load(fs.readFileSync('react.yaml', 'utf8')) as any;
function sort<T>(obj: Record<string, T>): Record<string, T> {
const clone = { ...obj };
Object.keys(clone).sort().reduce(
(acc, key) => {
acc[key] = clone[key];
return acc;
},
);
return clone;
}
// NOTE: oxlint doesn't support most stylistic rules
const rules = sort({
...baseBestPracticesRules,
...baseErrorsRules,
...baseES6Rules,
...baseStyleRules,
...baseVariablesRules,
...base.rules,
...react.rules,
'no-unused-vars': [
'off',
// 'error',
// {
// caughtErrorsIgnorePattern: '^_|e',
// argsIgnorePattern: '^_',
// },
],
'no-await-in-loop': 'off', // temporary disable the file until oxlint support extending config
// Let typescript to the thing
'no-undef': 'off',
'no-empty-function': 'off',
'no-lonely-if': 'off',
'no-unsafe-declaration-merging': 'off',
'no-this-alias': 'off',
'no-new-array': 'off', // It breaks [...new Array(n)].map()
'no-useless-spread': 'off', // It breaks [...new Array(n)].map()
'no-script-url': 'off', // It breaks createElement('a').setAttribute('href', 'javascript:;')
});
delete rules['@typescript-eslint/no-unused-vars'];
fs.writeFileSync('.oxlintrc.json', JSON.stringify({
$schema: './node_modules/oxlint/configuration_schema.json',
rules,
env: {
browser: true,
es6: true,
jquery: true,
node: true,
},
globals: {
UiContext: 'readonly',
UserContext: 'readonly',
externalModules: 'readonly',
LOCALES: 'readonly',
},
}, null, 2));
+2
View File
@@ -20,6 +20,7 @@
"build:ui:production:webpack": "cross-env NODE_OPTIONS=--max_old_space_size=8192 node packages/ui-default/build --production",
"test": "node -r @hydrooj/register packages/common/tests/subtask.spec.ts && node test/entry.js",
"benchmark": "cross-env BENCHMARK=true node test/entry.js",
"oxlint": "cd framework/eslint-config && node -r @hydrooj/register build.ts && cd ../../ && oxlint --config framework/eslint-config/.oxlintrc.json",
"lint": "eslint packages plugins modules framework --ext .ts,.tsx,.jsx --fix",
"lint:ci": "eslint packages plugins modules framework --ext .ts,.tsx,.jsx --max-warnings=0",
"lint:ui": "yarn workspace @hydrooj/ui-default lint --ext .js,.ts,.jsx,.tsx . --fix",
@@ -53,6 +54,7 @@
"mongodb-memory-server": "10.1.4",
"nyc": "^17.1.0",
"ora": "^8.2.0",
"oxlint": "^0.15.13",
"package-json": "^10.0.1",
"semver": "^7.7.1",
"simple-git": "^3.27.0",
+1 -1
View File
@@ -4,7 +4,7 @@ import {
param, PRIV, Types, UserModel,
} from 'hydrooj';
export const TYPE_BLOG: 70 = 70;
export const TYPE_BLOG = 70 as const;
export interface BlogDoc {
docType: 70;
docId: ObjectId;
+1 -1
View File
@@ -85,7 +85,7 @@ function proc(params: Parameter): Cmd {
copyOutCached.push('stdout', 'stderr');
if (params.filename) copyOutCached.push(`${params.filename}.out?`);
} else if (params.filename) copyOut.push(`${params.filename}.out${supportOptional ? '?' : ''}`);
const copyIn = { ...(params.copyIn || {}) };
const copyIn = { ...params.copyIn };
const stdin = params.stdin || { content: '' };
if (params.filename) copyIn[`${params.filename}.in`] = stdin;
const time = params.time || 16000;
+1 -1
View File
@@ -464,7 +464,7 @@ class OauthCallbackHandler extends Handler {
break;
}
}
const set: Partial<Udoc> = { ...(r.set || {}), oauth: args.type };
const set: Partial<Udoc> = { ...r.set, oauth: args.type };
if (r.bio) set.bio = r.bio;
if (r.viewLang) set.viewLang = r.viewLang;
if (r.avatar) set.avatar = r.avatar;
+1 -1
View File
@@ -101,7 +101,7 @@ export const PERM = {
PERM_NEVER: 1n << 60n,
};
export const Permission = (family: string, key: BigInt, desc: string) => ({ family, key, desc });
export const Permission = (family: string, key: bigint, desc: string) => ({ family, key, desc });
export const PERMS = [
Permission('perm_general', PERM.PERM_VIEW, 'View this domain'),
+11 -11
View File
@@ -15,22 +15,22 @@ import { ArrayKeys, MaybeArray, NumberKeys, Projection } from '../typeutils';
import { buildProjection } from '../utils';
type DocID = ObjectId | string | number;
type NormalArrayKeys<O, P = any> = Exclude<ArrayKeys<O, P>, Symbol>;
type NormalArrayKeys<O, P = any> = Exclude<ArrayKeys<O, P>, symbol>;
export const coll = db.collection('document');
export const collStatus = db.collection('document.status');
export const TYPE_PROBLEM: 10 = 10;
export const TYPE_PROBLEM_SOLUTION: 11 = 11;
export const TYPE_PROBLEM_LIST: 12 = 12;
export const TYPE_DISCUSSION_NODE: 20 = 20;
export const TYPE_DISCUSSION: 21 = 21;
export const TYPE_DISCUSSION_REPLY: 22 = 22;
export const TYPE_CONTEST: 30 = 30;
export const TYPE_CONTEST_CLARIFICATION: 31 = 31;
export const TYPE_TRAINING: 40 = 40;
export const TYPE_PROBLEM = 10 as const;
export const TYPE_PROBLEM_SOLUTION = 11 as const;
export const TYPE_PROBLEM_LIST = 12 as const;
export const TYPE_DISCUSSION_NODE = 20 as const;
export const TYPE_DISCUSSION = 21 as const;
export const TYPE_DISCUSSION_REPLY = 22 as const;
export const TYPE_CONTEST = 30 as const;
export const TYPE_CONTEST_CLARIFICATION = 31 as const;
export const TYPE_TRAINING = 40 as const;
/** @deprecated use `TYPE_CONTEST` with rule `homework` instead. */
export const TYPE_HOMEWORK: 60 = 60;
export const TYPE_HOMEWORK = 60 as const;
export interface DocType {
[TYPE_PROBLEM]: ProblemDoc;
+1 -1
View File
@@ -58,4 +58,4 @@ export { EventMap } from './service/bus';
export { db, pwsh };
// to load ctx.scoreboard
export { } from './handler/contest';
export { } from './handler/contest'; // eslint-disable-line @typescript-eslint/no-useless-empty-export
+1
View File
@@ -242,6 +242,7 @@ export default class HMR extends Service {
for (const [plugin, { filename, runtime }] of reloads) {
try {
this.ctx.registry.delete(attempts[filename]);
// eslint-disable-next-line no-await-in-loop
await reload(plugin, runtime);
} catch (err) {
logger.warn(err);
+1 -1
View File
@@ -51,7 +51,7 @@ export const coreScripts: MigrationScript[] = [
return true;
},
// Init
...new Array(28).fill(unsupportedUpgrade),
...new Array(28).fill(unsupportedUpgrade), // oxlint-disable-line no-new-array
async function _29_30() {
return await iterateAllDomain((ddoc) => RecordModel.coll.updateMany({ domainId: ddoc._id }, { $set: { pdomain: ddoc._id } }));
},
@@ -259,28 +259,27 @@ export function start(data: ResolverInput, options: DisplaySettings) {
background: 'transparent',
}),
}}
children={<>
<div className="rank">{team.rank === -1 ? '⭐' : team.rank}</div>
{props.showAvatar && <img className="avatar" src={`${teamInfo?.avatar}`} />}
<div className="content">
<div className="name">
{props.showSchool ? `${teamInfo.institution} - ` : ''}{teamInfo.name}
</div>
<div className="problems">
{data.problems.map((v) => {
const uncover = team.id === selectedTeam && selectedProblem === v.id;
const problemStatus = team.problems.find((idx) => idx.id === v.id);
if (!problemStatus) return <span key={v.id} className="item">ERR</span>;
return <span key={v.id} className={`${status(problemStatus)} ${uncover ? 'uncover' : ''} item`}>
{submissions(problemStatus)}
</span>;
})}
</div>
>
<div className="rank">{team.rank === -1 ? '⭐' : team.rank}</div>
{props.showAvatar && <img className="avatar" src={`${teamInfo?.avatar}`} />}
<div className="content">
<div className="name">
{props.showSchool ? `${teamInfo.institution} - ` : ''}{teamInfo.name}
</div>
<div className="solved">{team.score}</div>
<div className="penalty">{team.penalty}</div>
</>}
/>;
<div className="problems">
{data.problems.map((v) => {
const uncover = team.id === selectedTeam && selectedProblem === v.id;
const problemStatus = team.problems.find((idx) => idx.id === v.id);
if (!problemStatus) return <span key={v.id} className="item">ERR</span>;
return <span key={v.id} className={`${status(problemStatus)} ${uncover ? 'uncover' : ''} item`}>
{submissions(problemStatus)}
</span>;
})}
</div>
</div>
<div className="solved">{team.score}</div>
<div className="penalty">{team.penalty}</div>
</animated.div>;
})}
</>);
}
+1 -1
View File
@@ -16,7 +16,7 @@ export function createRegistry(ctx: Context) {
function createMetric<Q extends string, T extends (new (a: any) => Metric<Q>)>(
C: T, name: string, help: string, extra?: T extends new (a: infer R) => any ? Partial<R> : never,
): T extends (new (a) => Gauge<Q>) ? Gauge<Q> : T extends (new (a) => Counter<Q>) ? Counter<Q> : Metric<Q> {
const metric = new C({ name, help, ...(extra || {}) });
const metric = new C({ name, help, ...extra });
registry.registerMetric(metric);
return metric as any;
}
+2 -1
View File
@@ -2,7 +2,7 @@ import * as status from '@hydrooj/common/status';
import { findFileSync } from '@hydrooj/utils/lib/utils';
import { convert } from 'html-to-text';
import {
avatar, Context, fs, PERM, PRIV, Service, STATUS, yaml,
avatar, Context, difficultyAlgorithm, fs, PERM, PRIV, Service, STATUS, yaml,
} from 'hydrooj';
import jsesc from 'jsesc';
import nunjucks from 'nunjucks';
@@ -134,6 +134,7 @@ class Nunjucks extends nunjucks.Environment {
this.addGlobal('avatarUrl', avatar);
this.addGlobal('formatSeconds', misc.formatSeconds);
this.addGlobal('model', global.Hydro.model);
this.addGlobal('lib', { difficulty: difficultyAlgorithm });
this.addGlobal('ui', global.Hydro.ui);
this.addGlobal('isIE', (str) => {
if (!str) return false;
@@ -38,7 +38,7 @@ export default function reducer(state = {}, action = {}) {
return {
...state,
[to]: {
...state[to] || {},
...state[to],
udoc,
messages: [
...state[to]?.messages || [],
@@ -242,7 +242,7 @@ const page = new NamedPage(['problem_detail', 'contest_detail_problem', 'homewor
function ProblemNavigation() {
[, setUpdate] = React.useState(0);
return <div className="contest-problems" style={{ margin: '1em' }}>
{pids.map((i) => <a href={`#p${i}`} className={ans[i] ? 'pending ' : ''}>
{pids.map((i) => <a href={`#p${i}`} key={i} className={ans[i] ? 'pending ' : ''}>
<span className="id">{i}</span>
</a>)}
</div>;
+1 -1
View File
@@ -160,12 +160,12 @@ async function get(request: Request) {
method: request.method,
credentials: isResource ? 'same-origin' : 'include',
headers: request.headers,
body: request.body,
redirect: request.redirect,
keepalive: request.keepalive,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
signal: request.signal,
...(request.method.toLowerCase() === 'get' ? {} : { body: request.body }),
});
if (r.ok) {
console.log('Load success from ', source.toString());
+1 -1
View File
@@ -1,8 +1,8 @@
import fs from 'fs';
import path from 'path';
import importMetaUrlPlugin from '@codingame/esbuild-import-meta-url-plugin';
import react from '@vitejs/plugin-react-swc';
import c2k from 'koa2-connect';
import fs from 'fs';
import { createServer } from 'vite';
import { } from '@hydrooj/framework';
import { Context } from 'hydrooj';