12 KiB
代码格式化(整理代码)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 调用 ruff(Python/海龟绘图)和 clang-format(C/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 为运行依赖
cd /home/xuyue/Projects/Code/codeapinew && uv add ruff
Expected: pyproject.toml 的 dependencies 列表新增 "ruff>=...",uv.lock 更新。
- Step 2: 添加 pytest 为开发依赖
cd /home/xuyue/Projects/Code/codeapinew && uv add --dev pytest
Expected: pyproject.toml 新增 [dependency-groups] / dev = ["pytest>=..."]。
- Step 3: 查询已安装的 ruff 版本,同步到 requirements.txt
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 安装步骤:
# 安装系统依赖
RUN apt-get update && apt-get install -y \
gcc \
clang-format \
&& rm -rf /var/lib/apt/lists/*
- Step 5: Commit
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:
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: 运行测试,确认失败
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:
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: 运行测试,确认通过
cd /home/xuyue/Projects/Code/codeapinew && uv run pytest test_formatter.py -v
Expected: 全部 6 个测试 PASS。
- Step 5: Commit
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:
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: 运行测试,确认失败
cd /home/xuyue/Projects/Code/codeapinew && uv run pytest test_main.py -v
Expected: FAIL,两个测试均得到 404(路由不存在)。
- Step 3: 在
schemas.py新增请求/响应模型
在 codeapinew/schemas.py 末尾追加:
class FormatRequest(BaseModel):
"""格式化代码的请求模式"""
code: str
language: str
class FormatResponse(BaseModel):
"""格式化代码的响应模式"""
code: str
- Step 4: 在
main.py新增/format端点
修改 codeapinew/main.py 第 8 行的导入,加入新模型:
from schemas import (
PresetCodeCreate,
AIAnalysisRequest,
DebugRequest,
FormatRequest,
FormatResponse,
)
在文件顶部其他导入旁加入:
from formatter import format_code, FormatError
在 /debug 端点(第 127-139 行)之后新增:
@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: 运行测试,确认通过
cd /home/xuyue/Projects/Code/codeapinew && uv run pytest -v
Expected: 全部测试(test_formatter.py + test_main.py)PASS。
- Step 6: Commit
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 行:
import { Code, Submission } from "./types"
改为:
import { Code, LANGUAGE, Submission } from "./types"
在文件末尾(debug 函数之后)新增:
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
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: 修改导入并新增
formataction
将 codenext/src/composables/code.ts 第 5 行:
import { getCodeByQuery, submit } from "../api"
改为:
import { formatCode, getCodeByQuery, submit } from "../api"
在文件末尾(share 函数之后)新增:
export async function format() {
code.value = await formatCode(code.value, code.language)
}
- Step 2: Commit
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 行:
import { code, input, reset, size } from "../composables/code"
改为:
import { code, format, input, reset, size } from "../composables/code"
- Step 2: 新增
handleFormat函数
在 copy 函数(第 20-23 行)之后新增:
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 插槽:
<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>
改为:
<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: 启动前后端,手动验证
cd /home/xuyue/Projects/Code/codeapinew && uv run python main.py
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
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与前端请求体一致。