58 lines
2.5 KiB
TypeScript
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')
|
|
}
|