feat: expand platform management, admin controls, and learning workflows

这个提交包含在:
Codex CLI
2026-02-15 15:41:56 +08:00
父节点 ad29a9f62d
当前提交 f209ae82da
修改 75 个文件,包含 9663 行新增794 行删除

查看文件

@@ -0,0 +1,206 @@
export type Cpp14PolicySeverity = "error" | "warning" | "hint";
export type Cpp14PolicyIssue = {
id: string;
severity: Cpp14PolicySeverity;
message: string;
detail: string;
line: number;
column: number;
endLine: number;
endColumn: number;
};
type RegexRule = {
id: string;
severity: Cpp14PolicySeverity;
pattern: RegExp;
message: string;
detail: string;
};
const REGEX_RULES: RegexRule[] = [
{
id: "cpp17-header",
severity: "error",
pattern: /#\s*include\s*<\s*(optional|variant|any|string_view|filesystem|charconv|execution)\s*>/g,
message: "检测到 C++17+ 头文件",
detail: "福建 CSP-J/S 环境通常以 C++14 为准,建议改为 C++14 可用写法。",
},
{
id: "if-constexpr",
severity: "error",
pattern: /\bif\s+constexpr\b/g,
message: "检测到 if constexprC++17",
detail: "请改用普通条件分支或模板特化方案。",
},
{
id: "structured-binding",
severity: "error",
pattern: /\b(?:const\s+)?auto(?:\s*&|\s*&&)?\s*\[[^\]\n]+\]\s*=/g,
message: "检测到结构化绑定C++17",
detail: "可改为 pair.first/second 或自定义结构体字段。",
},
{
id: "cpp17-stdlib",
severity: "error",
pattern: /\bstd::(optional|variant|any|string_view|filesystem|byte|clamp|gcd|lcm)\b/g,
message: "检测到 C++17+ 标准库符号",
detail: "请替换为 C++14 可用实现,避免提交到老版本 GCC 报 CE。",
},
{
id: "void-main",
severity: "error",
pattern: /\bvoid\s+main\s*\(/g,
message: "main 函数返回类型不规范",
detail: "请使用 int main(),并在末尾 return 0;",
},
{
id: "windows-i64d",
severity: "warning",
pattern: /%I64d/g,
message: "检测到 %I64dWindows 特有)",
detail: "Linux 评测机请使用 %lld 读写 long long。",
},
{
id: "windows-int64",
severity: "warning",
pattern: /\b__int64\b/g,
message: "检测到 __int64非标准",
detail: "建议改为标准类型 long long。",
},
{
id: "bits-header",
severity: "warning",
pattern: /#\s*include\s*<\s*bits\/stdc\+\+\.h\s*>/g,
message: "检测到 <bits/stdc++.h>",
detail: "福建实战建议优先使用标准头文件,提升环境兼容性。",
},
];
export const CSP_CPP14_TIPS: string[] = [
"编译标准固定为 C++14建议按 -std=gnu++14 习惯编码),不要使用 C++17+ 特性。",
"main 必须是 int main(),结尾写 return 0;。",
"long long 的 scanf/printf 请使用 %lld,不要使用 %I64d。",
"命名/提交包按考场须知执行:题目目录与源码名使用英文小写。",
"福建二轮常见要求是文件读写freopen,赛前请按官方样例再核对一次。",
];
function buildLineStarts(text: string): number[] {
const starts = [0];
for (let i = 0; i < text.length; i += 1) {
if (text[i] === "\n") starts.push(i + 1);
}
return starts;
}
function offsetToPosition(lineStarts: number[], offset: number): { line: number; column: number } {
let lo = 0;
let hi = lineStarts.length - 1;
while (lo <= hi) {
const mid = (lo + hi) >> 1;
if (lineStarts[mid] <= offset) lo = mid + 1;
else hi = mid - 1;
}
const lineIdx = Math.max(0, hi);
return {
line: lineIdx + 1,
column: offset - lineStarts[lineIdx] + 1,
};
}
function pushIssue(
issues: Cpp14PolicyIssue[],
id: string,
severity: Cpp14PolicySeverity,
message: string,
detail: string,
line: number,
column: number,
endLine: number,
endColumn: number
) {
issues.push({ id, severity, message, detail, line, column, endLine, endColumn });
}
export function analyzeCpp14Policy(code: string): Cpp14PolicyIssue[] {
const text = (code ?? "").replace(/\r\n?/g, "\n");
if (!text.trim()) return [];
const issues: Cpp14PolicyIssue[] = [];
const lineStarts = buildLineStarts(text);
for (const rule of REGEX_RULES) {
const matcher = new RegExp(rule.pattern.source, rule.pattern.flags);
let match = matcher.exec(text);
while (match) {
const start = match.index;
const end = start + Math.max(1, match[0].length);
const p1 = offsetToPosition(lineStarts, start);
const p2 = offsetToPosition(lineStarts, end);
pushIssue(
issues,
rule.id,
rule.severity,
rule.message,
rule.detail,
p1.line,
p1.column,
p2.line,
p2.column
);
if (matcher.lastIndex === match.index) matcher.lastIndex += 1;
match = matcher.exec(text);
}
}
if (/\bint\s+main\s*\(/.test(text) && !/\breturn\s+0\s*;/.test(text)) {
const idx = text.search(/\bint\s+main\s*\(/);
const pos = offsetToPosition(lineStarts, Math.max(0, idx));
pushIssue(
issues,
"main-return-zero",
"warning",
"建议在 main 末尾显式 return 0;",
"部分考场与评测环境会严格检查主函数返回行为。",
pos.line,
pos.column,
pos.line,
pos.column + 3
);
}
if (/\blong\s+long\b/.test(text) && /\b(?:scanf|printf)\s*\(/.test(text) && !/%lld/.test(text)) {
const idx = text.search(/\b(?:scanf|printf)\s*\(/);
const pos = offsetToPosition(lineStarts, Math.max(0, idx));
pushIssue(
issues,
"ll-format",
"warning",
"检测到 long long + scanf/printf,建议确认格式符为 %lld",
"Linux 评测环境不支持 %I64d。",
pos.line,
pos.column,
pos.line,
pos.column + 6
);
}
if (!/\bfreopen\s*\(/.test(text) && /\bint\s+main\s*\(/.test(text)) {
const idx = text.search(/\bint\s+main\s*\(/);
const pos = offsetToPosition(lineStarts, Math.max(0, idx));
pushIssue(
issues,
"freopen-tip",
"hint",
"未检测到 freopen福建二轮常见文件读写要求",
"若考场题面要求 *.in/*.out,请按官方文件名补上 freopen。",
pos.line,
pos.column,
pos.line,
pos.column + 3
);
}
return issues;
}