Some checks failed
Deploy / deploy (push) Has been cancelled
原来只有 `apps/web` 在 Prettier 下(配置在 `apps/web/.prettierrc.toml`、脚本在 web 的 package.json),后端和契约从来没格式化过 —— 手写在 100 列上下,`db/schema.ts` 还是 drizzle-kit pull 留下的 tab 缩进。两套口径分叉久了,跨端改一处就得记着「这边 什么风格」。 - 配置搬到根目录 `.prettierrc.toml`,内容不变(`semi=false`,其余全默认, printWidth 80 —— 和前端已有的格式一致,不另立一套宽度); - 脚本统一成根目录 `bun run fmt`,覆盖 `apps/*/src`、`apps/web/tests` 和两个构建 配置;web 自己那份 `fmt` 和重复的 prettier 依赖删掉; - `.prettierignore` 挡掉两类不该碰的:drizzle-kit 生成的 `src/db/meta/` 结构快照 (它是 db:generate 的比对输入,只该由 drizzle-kit 写)、unplugin 每次 dev 都会 重写的 `auto-imports.d.ts` / `components.d.ts`; - 全量跑了一遍。纯格式,无行为改动:api typecheck / check:routes / check:ast、 前端 type-check 全过,起 api 打了接口确认正常。前端这 39 个文件的小改动是 prettier 版本漂移(类型断言的换行口径变了),不是新配置带来的。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
82 lines
3.3 KiB
TypeScript
82 lines
3.3 KiB
TypeScript
/**
|
||
* 检查契约里每个 AST target 的 `node` 在对应语言的语法里真实存在。
|
||
*
|
||
* bun run --filter '@oj2/api' check:ast
|
||
*
|
||
* ## 为什么需要这个
|
||
*
|
||
* 判题机拿 `node` 去比 tree-sitter 的节点类型,**对不上不会报错**:collectNodes
|
||
* 一个都收不到,于是「必须使用 X」永远失败、「不能使用 X」永远通过。两头都不报错,
|
||
* 只有学生受着 —— 他明明写了 f-string,「不能使用 f-string」却judge成通过。
|
||
*
|
||
* 这正是本仓库真实踩过的坑:`f_string` 一直配的是 `format_string`,而这个版本的
|
||
* tree-sitter-python 里根本没有这种节点(f-string 是 `string` 里带 `interpolation`),
|
||
* 所以那条规则从上线起就没生效过。加这个检查那天,56 个 target 里就它一个是坏的。
|
||
*
|
||
* 升级 tree-sitter-* 依赖之后一定要跑一次:语法改个节点名是很常见的事,
|
||
* 而它造成的故障完全静默。
|
||
*
|
||
* 只验节点类型**存在**,不验语义对不对(比如把 `while_loop` 配成 `for_statement`
|
||
* 这种,语法里两个都存在,机器看不出来)。语义那一层还是得靠实跑。
|
||
*/
|
||
|
||
import { AST_NODE_TARGETS_BY_LANGUAGE } from "@oj2/contract"
|
||
import { Language, Parser } from "web-tree-sitter"
|
||
|
||
import cWasmPath from "tree-sitter-c/tree-sitter-c.wasm" with { type: "file" }
|
||
import cppWasmPath from "tree-sitter-cpp/tree-sitter-cpp.wasm" with { type: "file" }
|
||
import pythonWasmPath from "tree-sitter-python/tree-sitter-python.wasm" with { type: "file" }
|
||
import treeSitterWasmPath from "web-tree-sitter/web-tree-sitter.wasm" with { type: "file" }
|
||
|
||
const WASM_BY_LANGUAGE: Record<string, string> = {
|
||
C: cWasmPath,
|
||
"C++": cppWasmPath,
|
||
Python3: pythonWasmPath,
|
||
}
|
||
|
||
await Parser.init({ locateFile: () => treeSitterWasmPath })
|
||
|
||
let checked = 0
|
||
const missing: Array<{ language: string; target: string; node: string }> = []
|
||
|
||
for (const [language, table] of Object.entries(AST_NODE_TARGETS_BY_LANGUAGE)) {
|
||
const wasmPath = WASM_BY_LANGUAGE[language]
|
||
if (!wasmPath) {
|
||
console.log(
|
||
`⚠ ${language} 在 AST_NODE_TARGETS_BY_LANGUAGE 里,但这个脚本没有它的语法 wasm`,
|
||
)
|
||
console.log(
|
||
` 加语言时记得同步 WASM_BY_LANGUAGE 和 judge/ast.ts 的 loadLanguage`,
|
||
)
|
||
process.exit(2)
|
||
}
|
||
const loaded = await Language.load(wasmPath)
|
||
// 语法里声明过的全部节点类型名
|
||
const declared = new Set<string>()
|
||
for (let id = 0; id < loaded.nodeTypeCount; id++) {
|
||
const name = loaded.nodeTypeForId(id)
|
||
if (name) declared.add(name)
|
||
}
|
||
for (const [target, entry] of Object.entries(table)) {
|
||
checked++
|
||
if (!declared.has(entry.node))
|
||
missing.push({ language, target, node: entry.node })
|
||
}
|
||
}
|
||
|
||
console.log(`检查了 ${checked} 个 AST target 的节点类型`)
|
||
if (missing.length === 0) {
|
||
console.log("✓ 每个 target 的 node 都在对应语言的语法里真实存在")
|
||
process.exit(0)
|
||
}
|
||
for (const { language, target, node } of missing) {
|
||
console.log(`\n⚠ ${language} 的 ${target} → "${node}"`)
|
||
console.log(
|
||
` 这个节点类型在语法里不存在,规则永远失败(或永远通过),且不报错`,
|
||
)
|
||
console.log(
|
||
` 改法:在 packages/contract/src/problem.ts 把它的 node 改成语法里真实的名字`,
|
||
)
|
||
}
|
||
process.exit(1)
|