Files
im0o 26bb0f3456 把分隔符里的字面 NUL 字节改成转义写法
源码里直接写 NUL 字节会让 git 把整个文件判定成二进制,此后再也看不到这个文件的 diff。
用 \u0000 转义,运行时的值不变。
2026-08-10 02:44:50 +08:00

58 lines
2.5 KiB
TypeScript

/**
* 写回 YAML 之后的格式修复。
*
* 背景:`yaml` 库把行尾注释的文字存在节点上,但不存注释前面那几个对齐空格。
* 于是即便某一段落根本没被改动,重新输出时 `interval: 86400 # 每 24 小时…`
* 也会变成 `interval: 86400 # 每 24 小时…`。信息没丢,但 diff 里会多出十几行噪音,
* 而「改一处只动一处」正是这个工具赖以取信于人的地方。
*
* 做法:逐行比对。如果输出的某一行与原文某一行「注释前的内容」和「注释文字」都相同,
* 只是中间的空格数不同,就把原文那一行原样放回去。
*/
/** 把一行拆成注释前的内容和注释文字。没有行尾注释则返回 null。 */
function splitInlineComment(line: string): { content: string; comment: string } | null {
const hashIndex = line.indexOf('#')
// 整行注释(# 前面只有空白)不在处理范围内——那种注释的缩进本来就会被保留。
if (hashIndex <= 0) return null
if (line.slice(0, hashIndex).trim() === '') return null
// 注释符号前必须有空白,否则 `a#b` 这种不是注释。
if (!/\s$/.test(line.slice(0, hashIndex))) return null
return {
content: line.slice(0, hashIndex).trimEnd(),
comment: line.slice(hashIndex),
}
}
const keyOf = (content: string, comment: string) => `${content}\u0000${comment}`
/**
* 把 `output` 里因为重新输出而丢失对齐的行,还原成 `source` 里对应的那一行。
*
* 只在「注释前内容」和「注释文字」都完全一致时才还原,所以不会改变任何语义;
* 匹配不上的行原样保留。同样的内容在原文里出现多次时按出现顺序依次配对。
*/
export function restoreInlineCommentAlignment(source: string, output: string): string {
const pending = new Map<string, string[]>()
for (const line of source.split('\n')) {
const parts = splitInlineComment(line)
if (!parts) continue
const key = keyOf(parts.content, parts.comment)
const bucket = pending.get(key)
if (bucket) bucket.push(line)
else pending.set(key, [line])
}
if (pending.size === 0) return output
return output
.split('\n')
.map((line) => {
const parts = splitInlineComment(line)
if (!parts) return line
const bucket = pending.get(keyOf(parts.content, parts.comment))
if (!bucket || bucket.length === 0) return line
return bucket.shift()!
})
.join('\n')
}