chore(copy): check:copy 拦「其它」,并查中文模板插值片段开头的半角标点与 ${a}(${b}) 半角括号

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-07 02:10:19 +08:00
1 parent 9f55a5b506
commit e9e20e7e79
1 file changed
+30 -1
+30 -1
View File
@@ -45,6 +45,8 @@ function findIssues(text) {
push("括号内有中文应用全角括号", new RegExp(`\\([^()]*[${CJK}][^()]*\\)`, "g"));
push("引号应为「」", /[“”]/g);
if (termScope) push("处理状态写「整理」不写「入库」(见 glossary)", TERM_RUKU);
// 插值紧邻的半角括号:`${date}(${rel})` 在中文句子里,括号里多半是中文
push("插值旁的半角括号应为全角(括号内有中文时)", new RegExp(`${PH}\\(${PH}\\)`, "g"));
for (const m of text.matchAll(BANNED_WORDS)) out.push({ rule: `用词「${m[0]}」(见 human-copy)`, index: m.index });
return out;
}
@@ -68,6 +70,7 @@ const BANNED_WORDS = new RegExp(
"释放潜能",
"尽在掌握",
"成功地",
"其它",
"恭喜",
"太棒了",
"哎呀",
@@ -197,6 +200,8 @@ function collect(ast, src) {
const parts = node.quasis.map((q) => ({ start: q.start, end: q.end, text: src.slice(q.start, q.end) }));
if (!parts.some((p) => looksLikeRegex(p.text))) groups.push(parts);
}
if (node.quasis.some((q) => CJK_RE.test(q.value.raw)))
for (const e of node.expressions) for (const f of fragmentsOf(e)) checkFragment(f, src);
for (const e of node.expressions) walk(e, node, "expressions");
return;
}
@@ -225,6 +230,24 @@ function collect(ast, src) {
return groups;
}
/** 插值表达式里会被拼进外层中文模板的字面量(`${x ? `,…` : ""}` 里的 `,…`) */
function fragmentsOf(e) {
if (!e) return [];
if (e.type === "StringLiteral" || e.type === "TemplateLiteral") return [e];
if (e.type === "ConditionalExpression") return [...fragmentsOf(e.consequent), ...fragmentsOf(e.alternate)];
if (e.type === "LogicalExpression") return [...fragmentsOf(e.left), ...fragmentsOf(e.right)];
if (e.type === "TSAsExpression" || e.type === "ParenthesizedExpression") return fragmentsOf(e.expression);
return [];
}
/** 外层是中文模板时,插值片段开头的半角 , ; : 会紧贴中文 */
let fragIssues = [];
function checkFragment(f, src) {
const text = f.type === "StringLiteral" ? src.slice(f.start + 1, f.end - 1) : src.slice(f.quasis[0].start, f.quasis[0].end);
const m = /^\s*[,;:](?!\/\/)/.exec(text);
if (m) fragIssues.push({ start: f.start + 1, rule: "中文模板的插值片段以半角标点开头", excerpt: text.slice(0, 20) });
}
function lineOf(src, idx) {
let line = 1;
for (let i = 0; i < idx; i++) if (src.charCodeAt(i) === 10) line++;
@@ -252,7 +275,13 @@ function processFile(file) {
const lines = src.split("\n");
const issues = [];
const edits = [];
for (const g of collect(ast, src)) {
fragIssues = [];
const groups = collect(ast, src);
for (const fi of fragIssues) {
const line = lineOf(src, fi.start);
if (!allowed(lines, line)) issues.push({ line, rule: fi.rule, excerpt: fi.excerpt });
}
for (const g of groups) {
const texts = g.filter(Boolean);
if (texts.length === 0) continue;
const composed = joinWithPH(g);