Add design spec for pre-submit code auto-formatting

Plan: ruff for Python3, clang-format (LLVM/4-space/Attach) for C/C++,
called via new /api/format_code endpoint before submission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-14 07:41:03 -06:00
parent 95949e565c
commit 1bbc149e55

View File

@@ -0,0 +1,132 @@
# 提交前自动代码格式化 — Design Spec
**Date:** 2026-06-14
**Status:** Approved
---
## Overview
为 Python3、C、C++ 提供"提交前自动格式化"功能:用户点击"提交代码"时,前端先调用后端格式化接口
Python3 用 `ruff format`C/C++ 用 `clang-format`),将格式化后的代码写回编辑器(含 Yjs 协作同步)并提交。
其他语言Java/Go/JavaScript跳过格式化原样提交。无开关默认开启。
---
## Backend
### Endpoint
```
POST /api/format_code
```
- 放在 `submission` 应用,与 `SubmissionAPI` 同级(`submission/views/oj.py` / `submission/urls/oj.py` / `submission/serializers.py`
- 需要登录(`@login_required`),与提交代码一致
### Request
```json
{ "code": "...", "language": "python" }
```
- `language`: `ChoiceField`,取值 `"python"` / `"c"` / `"cpp"`(对应前端 `LANGUAGE_FORMAT_VALUE`
- `code`: `CharField(max_length=1024 * 1024)`,与 `CreateSubmissionSerializer.code` 一致
### 格式化实现
新增 `submission/utils.py`(或现有 utils 模块)中的 `format_code(code: str, language: str) -> str`
- `language == "python"`:
```python
subprocess.run(
["ruff", "format", "--stdin-filename", "code.py", "-"],
input=code, capture_output=True, text=True, timeout=5,
)
```
- `returncode != 0` → 视为语法错误,抛出携带 `stderr` 的异常view 返回 `err="format-error"`
- `language in ("c", "cpp")`:
```python
filename = "code.c" if language == "c" else "code.cpp"
subprocess.run(
["clang-format", f"-assume-filename={filename}",
"-style={BasedOnStyle: LLVM, IndentWidth: 4, BreakBeforeBraces: Attach}"],
input=code, capture_output=True, text=True, timeout=5,
)
```
- `returncode != 0` 或 subprocess 异常(超时等)→ view 返回 `err="server-error"`clang-format 正常情况下几乎不会因代码内容失败)
### Response
| 情况 | 响应 |
|---|---|
| 成功 | `self.success({"code": "<formatted code>"})` |
| Python 语法错误ruff format 非0 | `self.error(msg="<ruff stderr 摘要>", err="format-error")` |
| 工具执行异常(超时/二进制缺失/clang-format 非0 | `self.error(msg="format failed", err="server-error")` |
### 依赖与部署变更
- `pyproject.toml`: 将 `ruff` 从 `[dependency-groups].dev` 移到主依赖(`[project.dependencies]`),同步更新 `deploy/requirements.txt`
- `Dockerfile`: `apk add` 中新增 `clang-extra-tools`(提供 `clang-format` 二进制),放在常驻依赖区(不在 `apk del` 清理列表中)
---
## Frontend
### API
`ojnext/src/oj/api.ts` 新增:
```ts
export function formatCode(data: { code: string; language: string }) {
return http.post<{ code: string }>("format_code", data)
}
```
### 集成点:`ojnext/src/oj/problem/components/SubmitCode.vue`
在现有 Python 语法检测(`checkPythonSyntax`)之后、构建 `SubmitCodePayload` 之前插入:
```ts
const formatLang = LANGUAGE_FORMAT_VALUE[codeStore.code.language]
if (["python", "c", "cpp"].includes(formatLang)) {
try {
const res = await formatCode({
code: codeStore.code.value,
language: formatLang,
})
codeStore.setCode(res.data.code)
} catch (e: any) {
if (e?.error === "format-error") {
// 仅 Python3 会出现:代码本身有语法问题
message.warning(`代码格式化失败:${e.data},请检查代码后重试`)
return
}
// server-error / 网络异常:工具问题,静默降级,提交原代码
}
}
```
注:`http.ts` 拦截器会剥掉响应信封——成功时 `res` 即 `{error: null, data: {...}}`,失败时 `Promise.reject(res.data)`(即 `{error, data}`),因此用 `try/catch` 而非检查 `res.data.error`。
- `codeStore.setCode(...)` 更新 `code.value``SyncCodeEditor` 通过 `v-model` 绑定自动刷新编辑器显示并同步给协作者
- 提交时使用(可能已更新的)`codeStore.code.value` 构建 `SubmitCodePayload`,逻辑不变
- 不增加 loading 文案,格式化请求耗时很短,按钮状态保持原有的"正在提交"流程
---
## Error Handling Summary
| 失败类型 | Python3 | C/C++ |
|---|---|---|
| 格式化工具报代码语法错误(`format-error` | 提示用户检查代码,**阻止提交** | 不会出现clang-format 容错) |
| 工具/服务异常(`server-error`、网络错误、超时) | 静默降级,提交原代码 | 静默降级,提交原代码 |
---
## Out of Scope
- Java / Go / JavaScript 格式化(暂无对应工具集成)
- 格式化开关 / 用户偏好设置
- `CodeEditor.vue`(只读展示组件)不涉及