docs: add implementation plan for code formatting feature

This commit is contained in:
2026-06-14 06:43:52 -06:00
parent 799216b184
commit 55a42597bb

View File

@@ -0,0 +1,474 @@
# 代码格式化整理代码Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:**`codenext` 编辑器增加"整理代码"按钮,后端通过 `POST /format` 调用 ruffPython/海龟绘图)和 clang-formatC/C++)格式化代码。
**Architecture:** `codeapinew` 新增 `formatter.py`(封装 subprocess 调用 ruff/clang-format`main.py` 新增 `/format` 端点;`codenext` 新增 `api.ts``formatCode``composables/code.ts``format()`,并在 `CodeSection.vue` 加按钮。
**Tech Stack:** FastAPI, Pydantic, subprocess, ruff (CLI), clang-format (CLI), Vue 3, axios, naive-ui
---
## Task 1: 后端依赖ruff、pytest、clang-format
**Files:**
- Modify: `codeapinew/pyproject.toml`
- Modify: `codeapinew/requirements.txt`
- Modify: `codeapinew/Dockerfile`
- [ ] **Step 1: 添加 ruff 为运行依赖**
```bash
cd /home/xuyue/Projects/Code/codeapinew && uv add ruff
```
Expected: `pyproject.toml``dependencies` 列表新增 `"ruff>=..."``uv.lock` 更新。
- [ ] **Step 2: 添加 pytest 为开发依赖**
```bash
cd /home/xuyue/Projects/Code/codeapinew && uv add --dev pytest
```
Expected: `pyproject.toml` 新增 `[dependency-groups]` / `dev = ["pytest>=..."]`
- [ ] **Step 3: 查询已安装的 ruff 版本,同步到 requirements.txt**
```bash
cd /home/xuyue/Projects/Code/codeapinew && uv run ruff --version
```
记录输出的版本号(如 `ruff 0.8.x`),然后在 `requirements.txt` 中按字母序插入一行(例如插在 `pyyaml``pytz` 之间requirements.txt 当前是字母序排列):
```
ruff==<上一步输出的版本号>
```
- [ ] **Step 4: Dockerfile 中加入 clang-format**
修改 `codeapinew/Dockerfile` 中的 apt 安装步骤:
```dockerfile
# 安装系统依赖
RUN apt-get update && apt-get install -y \
gcc \
clang-format \
&& rm -rf /var/lib/apt/lists/*
```
- [ ] **Step 5: Commit**
```bash
cd /home/xuyue/Projects/Code/codeapinew && git add pyproject.toml uv.lock requirements.txt Dockerfile && git commit -m "build: add ruff and pytest dependencies, install clang-format in image"
```
---
## Task 2: `formatter.py` — 格式化核心逻辑TDD
**Files:**
- Create: `codeapinew/formatter.py`
- Test: `codeapinew/test_formatter.py`
- [ ] **Step 1: 写失败的测试**
创建 `codeapinew/test_formatter.py`
```python
import pytest
from formatter import FormatError, format_code
def test_format_python_normalizes_spacing():
result = format_code("x=1\n", "python")
assert result == "x = 1\n"
def test_format_turtle_uses_python_formatter():
result = format_code("t=1\n", "turtle")
assert result == "t = 1\n"
def test_format_python_syntax_error_raises():
with pytest.raises(FormatError):
format_code("def foo(:\n", "python")
def test_format_c_uses_allman_braces():
result = format_code("int main(){int x=1;return x;}", "c")
assert result.rstrip("\n") == "int main()\n{\n int x = 1;\n return x;\n}"
def test_format_cpp_uses_allman_braces():
result = format_code("class Foo{};", "cpp")
assert result.rstrip("\n") == "class Foo\n{\n};"
def test_format_unsupported_language_raises():
with pytest.raises(FormatError):
format_code("print(1)", "java")
```
- [ ] **Step 2: 运行测试,确认失败**
```bash
cd /home/xuyue/Projects/Code/codeapinew && uv run pytest test_formatter.py -v
```
Expected: FAIL`ModuleNotFoundError: No module named 'formatter'`(或 collection error
- [ ] **Step 3: 实现 `formatter.py`**
创建 `codeapinew/formatter.py`
```python
import subprocess
TIMEOUT_SECONDS = 5
CLANG_FORMAT_STYLE = "{BasedOnStyle: LLVM, IndentWidth: 4, BreakBeforeBraces: Allman}"
class FormatError(Exception):
"""代码格式化失败时抛出message 为可展示给用户的错误信息"""
def _run(cmd: list[str], code: str) -> str:
try:
result = subprocess.run(
cmd,
input=code,
capture_output=True,
text=True,
timeout=TIMEOUT_SECONDS,
)
except subprocess.TimeoutExpired:
raise FormatError("格式化超时")
if result.returncode != 0:
raise FormatError(result.stderr.strip() or "格式化失败")
return result.stdout
def format_code(code: str, language: str) -> str:
if language in ("python", "turtle"):
return _run(["ruff", "format", "-", "--stdin-filename", "main.py"], code)
if language in ("c", "cpp"):
ext = "c" if language == "c" else "cpp"
return _run(
[
"clang-format",
f"-style={CLANG_FORMAT_STYLE}",
f"-assume-filename=main.{ext}",
],
code,
)
raise FormatError(f"不支持的语言: {language}")
```
- [ ] **Step 4: 运行测试,确认通过**
```bash
cd /home/xuyue/Projects/Code/codeapinew && uv run pytest test_formatter.py -v
```
Expected: 全部 6 个测试 PASS。
- [ ] **Step 5: Commit**
```bash
cd /home/xuyue/Projects/Code/codeapinew && git add formatter.py test_formatter.py && git commit -m "feat: add formatter module wrapping ruff and clang-format"
```
---
## Task 3: `POST /format` 端点TDD
**Files:**
- Modify: `codeapinew/schemas.py`
- Modify: `codeapinew/main.py`
- Test: `codeapinew/test_main.py`
- [ ] **Step 1: 写失败的测试**
创建 `codeapinew/test_main.py`
```python
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_format_endpoint_formats_python_code():
response = client.post("/format", json={"code": "x=1\n", "language": "python"})
assert response.status_code == 200
assert response.json() == {"code": "x = 1\n"}
def test_format_endpoint_returns_400_on_syntax_error():
response = client.post(
"/format", json={"code": "def foo(:\n", "language": "python"}
)
assert response.status_code == 400
```
- [ ] **Step 2: 运行测试,确认失败**
```bash
cd /home/xuyue/Projects/Code/codeapinew && uv run pytest test_main.py -v
```
Expected: FAIL两个测试均得到 404路由不存在
- [ ] **Step 3: 在 `schemas.py` 新增请求/响应模型**
`codeapinew/schemas.py` 末尾追加:
```python
class FormatRequest(BaseModel):
"""格式化代码的请求模式"""
code: str
language: str
class FormatResponse(BaseModel):
"""格式化代码的响应模式"""
code: str
```
- [ ] **Step 4: 在 `main.py` 新增 `/format` 端点**
修改 `codeapinew/main.py` 第 8 行的导入,加入新模型:
```python
from schemas import (
PresetCodeCreate,
AIAnalysisRequest,
DebugRequest,
FormatRequest,
FormatResponse,
)
```
在文件顶部其他导入旁加入:
```python
from formatter import format_code, FormatError
```
`/debug` 端点(第 127-139 行)之后新增:
```python
@app.post("/format", response_model=FormatResponse)
async def format_code_endpoint(request: FormatRequest) -> FormatResponse:
"""格式化代码"""
try:
formatted = format_code(request.code, request.language)
except FormatError as e:
raise HTTPException(status_code=400, detail=str(e))
return FormatResponse(code=formatted)
```
- [ ] **Step 5: 运行测试,确认通过**
```bash
cd /home/xuyue/Projects/Code/codeapinew && uv run pytest -v
```
Expected: 全部测试(`test_formatter.py` + `test_main.py`PASS。
- [ ] **Step 6: Commit**
```bash
cd /home/xuyue/Projects/Code/codeapinew && git add schemas.py main.py test_main.py && git commit -m "feat: add POST /format endpoint"
```
---
## Task 4: 前端 API 客户端 `formatCode`
**Files:**
- Modify: `codenext/src/api.ts`
- [ ] **Step 1: 修改导入并新增 `formatCode`**
`codenext/src/api.ts` 第 3 行:
```ts
import { Code, Submission } from "./types"
```
改为:
```ts
import { Code, LANGUAGE, Submission } from "./types"
```
在文件末尾(`debug` 函数之后)新增:
```ts
export async function formatCode(code: string, language: LANGUAGE) {
const res = await api.post("/format", { code, language })
return res.data.code as string
}
```
- [ ] **Step 2: Commit**
```bash
cd /home/xuyue/Projects/Code/codenext && git add src/api.ts && git commit -m "feat: add formatCode API client"
```
---
## Task 5: 前端状态 action `format()`
**Files:**
- Modify: `codenext/src/composables/code.ts`
- [ ] **Step 1: 修改导入并新增 `format` action**
`codenext/src/composables/code.ts` 第 5 行:
```ts
import { getCodeByQuery, submit } from "../api"
```
改为:
```ts
import { formatCode, getCodeByQuery, submit } from "../api"
```
在文件末尾(`share` 函数之后)新增:
```ts
export async function format() {
code.value = await formatCode(code.value, code.language)
}
```
- [ ] **Step 2: Commit**
```bash
cd /home/xuyue/Projects/Code/codenext && git add src/composables/code.ts && git commit -m "feat: add format action to code composable"
```
---
## Task 6: "整理代码"按钮(`CodeSection.vue`+ 手动验证
**Files:**
- Modify: `codenext/src/desktop/CodeSection.vue`
- [ ] **Step 1: 修改导入**
`codenext/src/desktop/CodeSection.vue` 第 7 行:
```ts
import { code, input, reset, size } from "../composables/code"
```
改为:
```ts
import { code, format, input, reset, size } from "../composables/code"
```
- [ ] **Step 2: 新增 `handleFormat` 函数**
`copy` 函数(第 20-23 行)之后新增:
```ts
async function handleFormat() {
try {
await format()
message.success("代码已整理")
} catch (err: any) {
message.error(
`整理失败: ${err?.response?.data?.detail ?? err?.message ?? "未知错误"}`,
)
}
}
```
- [ ] **Step 3: 在 actions 插槽中加入按钮**
将模板中第 68-80 行的 `#actions` 插槽:
```vue
<template #actions>
<n-button quaternary type="primary" @click="copy">复制</n-button>
<n-button quaternary @click="reset">清空</n-button>
<n-button
v-if="code.language === 'python'"
quaternary
type="error"
:disabled="!code.value"
@click="handleDebug"
>
调试
</n-button>
</template>
```
改为:
```vue
<template #actions>
<n-button quaternary type="primary" @click="copy">复制</n-button>
<n-button quaternary @click="handleFormat">整理代码</n-button>
<n-button quaternary @click="reset">清空</n-button>
<n-button
v-if="code.language === 'python'"
quaternary
type="error"
:disabled="!code.value"
@click="handleDebug"
>
调试
</n-button>
</template>
```
- [ ] **Step 4: 启动前后端,手动验证**
```bash
cd /home/xuyue/Projects/Code/codeapinew && uv run python main.py
```
```bash
cd /home/xuyue/Projects/Code/codenext && npm run start
```
打开 `http://localhost:3000`,对每种语言验证:
- Python输入 `x=1\ny =2`,点击"整理代码",确认变为 `x = 1\ny = 2`,并提示"代码已整理"
- C选择 C 语言,输入 `int main(){int x=1;return x;}`,点击"整理代码"确认大括号换行Allman 风格)、缩进 4 空格
- C++:同上,验证 `class Foo{};` 格式化结果
- 海龟绘图:输入含 `t=1` 的代码,验证按 Python 格式化
- 错误场景:输入语法错误的 Python`def foo(:`),点击"整理代码",确认弹出错误提示且编辑器内容不变
- [ ] **Step 5: Commit**
```bash
cd /home/xuyue/Projects/Code/codenext && git add src/desktop/CodeSection.vue && git commit -m "feat: add format code button to editor toolbar"
```
---
## Self-Review Notes
- Spec coverage: `/format` 接口Task 2-3、依赖安装Task 1、前端按钮与调用链Task 4-6、错误处理Task 3 的 400 响应 + Task 6 的 message.error、海龟绘图复用 python 格式化Task 2/3 测试覆盖)均已覆盖。移动端按设计明确排除,无需任务。
- 类型一致性:`formatCode(code, language)``api.ts``composables/code.ts``formatter.py``format_code(code, language)` 参数顺序与命名一致;`FormatRequest`/`FormatResponse` 字段名 `code`/`language` 与前端请求体一致。