diff --git a/CLAUDE.md b/CLAUDE.md index e52cb95..5eb7de0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -58,6 +58,7 @@ bun run --filter '@oj2/api' check:ast # AST 节点类型检查,升级 cd apps/web && bun run type-check # 前端类型检查 cd apps/web && bun run build # 前端构建 bun run fmt # Prettier,全仓一把(只在根目录有) +bun run lint # oxlint,全仓一把,有一条 warning 就算失败 ``` **格式化是全仓一套 Prettier**,配置只有根目录的 `.prettierrc.toml`(`semi=false`, diff --git a/apps/api/src/db/schema.ts b/apps/api/src/db/schema.ts index bf333d1..10e77f8 100644 --- a/apps/api/src/db/schema.ts +++ b/apps/api/src/db/schema.ts @@ -755,7 +755,7 @@ export const problemTag = pgTable( id: serial().primaryKey().notNull(), name: text().notNull(), }, - (table) => [ + () => [ uniqueIndex("problem_tag_name_ci_unique").using("btree", sql`lower(name)`), ], ) diff --git a/apps/api/src/judge/sql/engine.ts b/apps/api/src/judge/sql/engine.ts index 24193df..2d65f75 100644 --- a/apps/api/src/judge/sql/engine.ts +++ b/apps/api/src/judge/sql/engine.ts @@ -159,7 +159,7 @@ function iterate(db: Database, script: string): Iterable { /** * 取语句的首关键字。优先用 sqlite3_normalized_sql —— 归一化由 SQLite 自己做, - * 注释、大小写、空白都已抹平(`/*x*​/ pragma Query_Only = 0` → `PRAGMA query_only=?`), + * 注释、大小写、空白都已抹平(`/*x*\/ pragma Query_Only = 0` → `PRAGMA query_only=?`), * 比在原文上自己做词法猜测可靠得多。 */ function leadingKeyword(statement: PreparedStatement) { diff --git a/apps/api/src/routes/account.ts b/apps/api/src/routes/account.ts index c664167..9280a28 100644 --- a/apps/api/src/routes/account.ts +++ b/apps/api/src/routes/account.ts @@ -2,7 +2,6 @@ import { randomBytes } from "node:crypto" import { resolve } from "node:path" import { - rankProfileSchema, registerRequestSchema, STUDENT_ROLES, updateProfileRequestSchema, diff --git a/apps/api/src/routes/admin/contest.ts b/apps/api/src/routes/admin/contest.ts index 7c26fc2..561e68b 100644 --- a/apps/api/src/routes/admin/contest.ts +++ b/apps/api/src/routes/admin/contest.ts @@ -14,7 +14,7 @@ import type { AuthUser } from "../../auth/session" import { db, schema } from "../../db" import { failure, parseBody, success } from "../../http" import { contestStatus } from "../../services/contest" -import { objectValue, queryInteger, sampleUser } from "../helpers" +import { queryInteger, sampleUser } from "../helpers" export const adminContestRoutes = new Hono() diff --git a/apps/api/src/routes/content.ts b/apps/api/src/routes/content.ts index d6d5e55..53066f1 100644 --- a/apps/api/src/routes/content.ts +++ b/apps/api/src/routes/content.ts @@ -1,6 +1,5 @@ import { createMessageRequestSchema, - embeddedSubmissionSchema, exerciseAttemptRequestSchema, reactionKeySchema, setReactionRequestSchema, diff --git a/apps/api/src/routes/flowchart.ts b/apps/api/src/routes/flowchart.ts index 9d82449..1b43ce7 100644 --- a/apps/api/src/routes/flowchart.ts +++ b/apps/api/src/routes/flowchart.ts @@ -147,7 +147,7 @@ flowchartRoutes.post("/flowcharts", requireAuth, async (c) => { }) try { await flowchartQueue.add("evaluate", { submissionId: id }, { jobId: id }) - } catch (error) { + } catch { await db .update(schema.flowchartSubmission) .set({ status: 3 }) @@ -642,7 +642,7 @@ flowchartRoutes.post("/flowcharts/:id/retry", requireAuth, async (c) => { { submissionId: row.flowchart.id }, { jobId: `${row.flowchart.id}:retry:${Date.now()}` }, ) - } catch (error) { + } catch { // 入队失败就落 FAILED,别把提交丢在 PENDING 上 —— 和 POST /flowcharts 同一处理 await db .update(schema.flowchartSubmission) diff --git a/apps/api/src/routes/submission-statistics.ts b/apps/api/src/routes/submission-statistics.ts index 25fdd55..6805e60 100644 --- a/apps/api/src/routes/submission-statistics.ts +++ b/apps/api/src/routes/submission-statistics.ts @@ -26,7 +26,6 @@ import { import { Hono } from "hono" import { optionalAuth, requireTeacher } from "../auth/middleware" -import type { AuthUser } from "../auth/session" import { db, schema } from "../db" import { failure, success } from "../http" import { diff --git a/apps/web/src/admin/problem/components/AstRulesEditor.vue b/apps/web/src/admin/problem/components/AstRulesEditor.vue index f5e7a29..e230931 100644 --- a/apps/web/src/admin/problem/components/AstRulesEditor.vue +++ b/apps/web/src/admin/problem/components/AstRulesEditor.vue @@ -162,7 +162,7 @@ function getRulesForLang(lang: string): AstRule[] { } function updateRules(lang: string, rules: AstRule[]) { - const current = { ...(props.modelValue || {}) } + const current = { ...props.modelValue } if (rules.length === 0) { delete current[lang] } else { diff --git a/apps/web/src/admin/problem/detail.vue b/apps/web/src/admin/problem/detail.vue index db0a443..586e96d 100644 --- a/apps/web/src/admin/problem/detail.vue +++ b/apps/web/src/admin/problem/detail.vue @@ -266,9 +266,6 @@ async function getProblemDetail() { code: "", })) } - if (problem.value.contestId) { - problem.value.contestId = problem.value.contestId - } // 下面是用来显示的: // 代码模板 和 模板开关 diff --git a/apps/web/src/admin/user/generate.vue b/apps/web/src/admin/user/generate.vue index 0316b75..bb48232 100644 --- a/apps/web/src/admin/user/generate.vue +++ b/apps/web/src/admin/user/generate.vue @@ -30,7 +30,7 @@ function generateUsers() { ) return false } - let className = !!prefix.value ? `ks${prefix.value}` : "" + let className = prefix.value ? `ks${prefix.value}` : "" // 占位邮箱必须全站唯一:注册、编辑用户、导入三条路都查重,而且库里同一个邮箱 // 出现两次的话,那两个账号在「编辑用户」里保存一次就撞 409,从此改不动。 // 原来只按「班级 + 批内序号」拼,同一个班分两批导入必然重号 —— 加一段每批随机的 diff --git a/apps/web/src/main.ts b/apps/web/src/main.ts index 200f371..cbb98df 100644 --- a/apps/web/src/main.ts +++ b/apps/web/src/main.ts @@ -55,7 +55,7 @@ router.beforeEach(async (to, _from, next) => { if (!userStore.user) { try { await userStore.getMyProfile() - } catch (error) { + } catch { next("/") return } @@ -85,7 +85,7 @@ router.beforeEach(async (to, _from, next) => { app.mount("#app") -if (!!import.meta.env.PUBLIC_ICONIFY_URL) { +if (import.meta.env.PUBLIC_ICONIFY_URL) { addAPIProvider("", { resources: [import.meta.env.PUBLIC_ICONIFY_URL], }) diff --git a/apps/web/src/oj/contest/components/LineChart.vue b/apps/web/src/oj/contest/components/LineChart.vue index a086287..6e53c1f 100644 --- a/apps/web/src/oj/contest/components/LineChart.vue +++ b/apps/web/src/oj/contest/components/LineChart.vue @@ -113,7 +113,7 @@ const chartData = computed(() => { if (b.solved !== a.solved) return b.solved - a.solved return a.penalty - b.penalty }) - const ranks = new Array(topUsers.length).fill(0) + const ranks = Array.from({ length: topUsers.length }, () => 0) indexed.forEach((item, pos) => { ranks[item.i] = pos + 1 }) diff --git a/apps/web/src/oj/problem/components/EditorForTest.vue b/apps/web/src/oj/problem/components/EditorForTest.vue index 62ac570..e3df833 100644 --- a/apps/web/src/oj/problem/components/EditorForTest.vue +++ b/apps/web/src/oj/problem/components/EditorForTest.vue @@ -12,7 +12,7 @@ import { copyToClipboard } from "utils/functions" const message = useMessage() const route = useRoute() -const contestID = !!route.params.contestID ? route.params.contestID : null +const contestID = route.params.contestID ? route.params.contestID : null const codeStore = useCodeStore() const problemStore = useProblemStore() diff --git a/apps/web/src/oj/store/contest.ts b/apps/web/src/oj/store/contest.ts index d23c791..4595d48 100644 --- a/apps/web/src/oj/store/contest.ts +++ b/apps/web/src/oj/store/contest.ts @@ -106,7 +106,7 @@ export const useContestStore = defineStore("contest", () => { async function _getProblems(contestID: string) { try { problems.value = await getContestProblems(contestID) - } catch (err) { + } catch { problems.value = [] toggleAccess(false) } diff --git a/apps/web/src/oj/store/problem.ts b/apps/web/src/oj/store/problem.ts index 4c7dd54..6af4c39 100644 --- a/apps/web/src/oj/store/problem.ts +++ b/apps/web/src/oj/store/problem.ts @@ -29,7 +29,7 @@ export const useProblemStore = defineStore("problem", () => { const languages = computed(() => { if (route.name === "problem" && problem.value?.allowFlowchart) { - return ["Flowchart", ...problem.value?.languages] + return ["Flowchart", ...problem.value.languages] } return problem.value?.languages ?? [] }) diff --git a/apps/web/src/shared/store/loginSummary.ts b/apps/web/src/shared/store/loginSummary.ts index c19a1c4..498f1b0 100644 --- a/apps/web/src/shared/store/loginSummary.ts +++ b/apps/web/src/shared/store/loginSummary.ts @@ -34,7 +34,7 @@ export const useLoginSummaryStore = defineStore("loginSummary", () => { summary.value = res.summary analysis.value = res.analysis || "" analysisError.value = res.analysisError || "" - } catch (err) { + } catch { analysisError.value = "获取登录统计失败,请稍后再试" } finally { loading.value = false diff --git a/apps/web/src/utils/stream.ts b/apps/web/src/utils/stream.ts index 8441a55..d76d49c 100644 --- a/apps/web/src/utils/stream.ts +++ b/apps/web/src/utils/stream.ts @@ -47,7 +47,7 @@ export async function consumeJSONEventStream( let parsed: T try { parsed = JSON.parse(payloadStr) - } catch (error) { + } catch { throw new Error(`无法解析服务端事件数据: ${payloadStr}`) } diff --git a/bun.lock b/bun.lock index 630a40d..d1ef065 100644 --- a/bun.lock +++ b/bun.lock @@ -6,6 +6,7 @@ "name": "oj2", "devDependencies": { "@types/bun": "latest", + "oxlint": "^1.85.0", "prettier": "^3.9.6", "typescript": "^5.9.3", }, @@ -531,6 +532,44 @@ "@oxc-project/types": ["@oxc-project/types@0.146.0", "https://registry.npmjs.com/@oxc-project/types/-/types-0.146.0.tgz", {}, "sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA=="], + "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.85.0", "", { "os": "android", "cpu": "arm" }, "sha512-q2KO/Zso9UT+OMn0NF9ywn4E4t0MI3yxiDhNyhsQ7DyQJrC4FhFE4TXOi4bktFnOWXTMds8qZSbpv2XwRaNOBg=="], + + "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.85.0", "", { "os": "android", "cpu": "arm64" }, "sha512-SxLN3ALjoT9NNdvpjEevGeHvfzTAFrF0NBYB5tzK7/GtCKMze3j1e/m/X2ozqGj2U9hfGG/dg/OG8vpVK4PiDA=="], + + "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.85.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y/Sup/J4f0f9UGsSd/xyCNTeWL+gepO63GBdEDAfue9nBsnk9zMmnIXx1O6b1V8C90vB5nucYNZ0pbMXAp8zJA=="], + + "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.85.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ApOSNC04ynpDTwvBD+//0wyfODRSbEzvRoKpX8teffmc27z8AockwSNeMXGJXn5KP85eahDgR/2llICWLkzcnw=="], + + "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.85.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-bNrVrCOA/kHky3Tu79IXWXe5bhIgLXfUuUEDHlAGOHUk96MkvDZ1ecaQF19rwstrnaqfP1o9nBTqzIr9+ZHkUg=="], + + "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.85.0", "", { "os": "linux", "cpu": "arm" }, "sha512-NUrzOJ1s/EqsVvfn2L/1D8Wro2LPIZUbihL8kOJLh5fEdGEN3rdOGUYq3HwnUIL8sjpoP+4N6RaGrgmMJnaMPw=="], + + "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.85.0", "", { "os": "linux", "cpu": "arm" }, "sha512-UJXrAT3E/RWkEqXLIs2ehETja1qfgkPb+5gwLIIS+o/6cf+grHvoOXTa5997a/YNQfcJS0DRBTOfZt95cvOI1g=="], + + "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.85.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-lK40QLjI0HxigO7CjDDshEtfYIeiYS0020v5BHFPqN4uuQBQxd2K9LNom2dW15o9F1937quSCRVp4ZsVhdbYdg=="], + + "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.85.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-c2zbdBwGKreHXwRx3gWBuFGJxLhxgsg6YlZ+3H+RgRusU/UEV9jNwJ3HGYK+nRo0LvBa7mt6Kj86xoVotUo8cw=="], + + "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.85.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tlt/Hy8lZ97/lCPmCgw/B3k/mwh+BzaIPbPkldZEly7TwLmx0xe2CQcaW2g/rR0dOgS9JNGCZsMEqLhUNMGvaw=="], + + "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.85.0", "", { "os": "linux", "cpu": "none" }, "sha512-3tNR9Xey82X0zKuY1d8hJ6Rc9gwRDurmqGLnQZa5xqOXy8/YyiqFXjAtugkKLY82obOlpK1eSiDRlgcNPuxtIg=="], + + "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.85.0", "", { "os": "linux", "cpu": "none" }, "sha512-wbGRd5PqCcjkJFHhZuZ2OBSUQY9czlQsoA/cQQB9JK/L9mC5MQgGoKAh+xd8QjA5V+0D3j+Qd1lAWn1I8zlelA=="], + + "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.85.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-3Sn0kSrE4DPZCWV/8o+n4x3aFZxI9ulMnkYlwCbJ8eUVkwRK2IerohE/A/z3SNbCwoPFOCJmGE5Avrq0rrvdvQ=="], + + "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.85.0", "", { "os": "linux", "cpu": "x64" }, "sha512-JY2pxxYfB62bAGfejljVCqc44etItehPuAyaeSAdMuEMtwNA00ggMnS66lC1oIhos6oOXUkuU6mZ9bpFh3BqWg=="], + + "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.85.0", "", { "os": "linux", "cpu": "x64" }, "sha512-5k74vZ6qJBjBHEOlBk9B/iv68Yu0F1Afw/vvT2ar6OGCqEeXLaSjXz2n/IPCbhLG22UoKoYEJTzpYraRdcp6PA=="], + + "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.85.0", "", { "os": "none", "cpu": "arm64" }, "sha512-GbAl5qt5TCkPLXTaIISZJnugrcBhra6rodcXc9jYt620UtdsTt71NlNmJmm0frxzFpd54x/G+MkitEJA8I/BoA=="], + + "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.85.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-kjmws5MK0et2swk4ND85D7NVQyDHw162i6whtZDLUA/lo6FQyBZDcmMRCMcVZcNrAhIaftVb00x9ChGDOjjNJA=="], + + "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.85.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-eSsIJx9n4yxvOqYTZyPEMyEXRmE60XH7xGAU7i0Qbsn1lf6Za3CWJ9aRd82oSFKXaxhp+sA6/yMJVRIpLpna6A=="], + + "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.85.0", "", { "os": "win32", "cpu": "x64" }, "sha512-pBebIPUpKKhWrhSMWhy8TdAZBewiXnfxmaAGxhzxM1068GagqFaTwgKlU6e+UyJ2sPR+VoHouhXuGJkQjsrDvA=="], + "@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.5", "https://registry.npmjs.com/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.5.tgz", { "os": "android", "cpu": "arm" }, "sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA=="], "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.5", "https://registry.npmjs.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.5.tgz", { "os": "android", "cpu": "arm64" }, "sha512-zXcwKlQApYAOELHd8PwKDFkagYF9Wy4e0RJ+0qnzl9Pjnpj75TEG8ufv40p2J7kCEfwZAsNiuzRIyNNMWT38ig=="], @@ -1215,6 +1254,8 @@ "obug": ["obug@2.1.4", "https://registry.npmjs.com/obug/-/obug-2.1.4.tgz", {}, "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA=="], + "oxlint": ["oxlint@1.85.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.85.0", "@oxlint/binding-android-arm64": "1.85.0", "@oxlint/binding-darwin-arm64": "1.85.0", "@oxlint/binding-darwin-x64": "1.85.0", "@oxlint/binding-freebsd-x64": "1.85.0", "@oxlint/binding-linux-arm-gnueabihf": "1.85.0", "@oxlint/binding-linux-arm-musleabihf": "1.85.0", "@oxlint/binding-linux-arm64-gnu": "1.85.0", "@oxlint/binding-linux-arm64-musl": "1.85.0", "@oxlint/binding-linux-ppc64-gnu": "1.85.0", "@oxlint/binding-linux-riscv64-gnu": "1.85.0", "@oxlint/binding-linux-riscv64-musl": "1.85.0", "@oxlint/binding-linux-s390x-gnu": "1.85.0", "@oxlint/binding-linux-x64-gnu": "1.85.0", "@oxlint/binding-linux-x64-musl": "1.85.0", "@oxlint/binding-openharmony-arm64": "1.85.0", "@oxlint/binding-win32-arm64-msvc": "1.85.0", "@oxlint/binding-win32-ia32-msvc": "1.85.0", "@oxlint/binding-win32-x64-msvc": "1.85.0" }, "peerDependencies": { "oxlint-tsgolint": ">=7.0.2001", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-bc26s97nuvPj1ViyPsqmKecVkUWFMEdtayO8MaQ6oiLfs1pj94cQlZZhrh4BPNlr9HQosjhIlwgZKsfcwmcNgg=="], + "p-retry": ["p-retry@6.2.1", "https://registry.npmjs.com/p-retry/-/p-retry-6.2.1.tgz", { "dependencies": { "@types/retry": "0.12.2", "is-network-error": "^1.0.0", "retry": "^0.13.1" } }, "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ=="], "package-manager-detector": ["package-manager-detector@1.8.0", "https://registry.npmjs.com/package-manager-detector/-/package-manager-detector-1.8.0.tgz", {}, "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A=="], diff --git a/package.json b/package.json index bbbe54a..6640500 100644 --- a/package.json +++ b/package.json @@ -13,11 +13,13 @@ "db:generate": "bun run --filter '@oj2/api' db:generate", "db:migrate": "bun run --filter '@oj2/api' db:migrate", "db:down": "docker compose -f docker/compose.dev.yml down", + "lint": "oxlint --deny-warnings apps/api/src apps/web/src packages/contract/src", "fmt": "prettier --write apps/api/src apps/web/src apps/web/tests packages/contract/src apps/api/drizzle.config.ts apps/web/vite.config.ts docker/judge/smoke.ts" }, "devDependencies": { + "@types/bun": "latest", + "oxlint": "^1.85.0", "prettier": "^3.9.6", - "typescript": "^5.9.3", - "@types/bun": "latest" + "typescript": "^5.9.3" } }