diff --git a/docs/superpowers/specs/2026-06-14-code-format-design.md b/docs/superpowers/specs/2026-06-14-code-format-design.md new file mode 100644 index 0000000..a7da224 --- /dev/null +++ b/docs/superpowers/specs/2026-06-14-code-format-design.md @@ -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": ""})` | +| Python 语法错误(ruff format 非0) | `self.error(msg="", 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`(只读展示组件)不涉及