Go scanner.ErrorList.RemoveMultiples 怎样让每行只保留一条诊断
来源:17golang原创
时间:2026-11-05 21:51:44 354浏览 收藏
做 Go 源码扫描器时,同一行可能同时产生多个诊断:列号不同、消息不同,但对编辑器或命令行摘要来说,逐条展示往往太吵。scanner.ErrorList.RemoveMultiples 正好提供了一个收敛展示列表的入口:它先排序,再按文件名和行号只保留每行遇到的第一条诊断。
官方文档:https://pkg.go.dev/go/scanner#ErrorList.RemoveMultiples
RemoveMultiples会直接整理当前ErrorList:排序依据包含文件名、行号、列号和消息,去重键只看文件名与行号。要保留完整诊断,就在调用前复制一份列表,把原始列表用于详细报告,把整理后的列表用于摘要。
这个方法到底保留哪一条错误
ErrorList 是 []*scanner.Error。每个错误带有 token.Position 和消息。标准库的排序先比较文件名,再比较行号、列号,最后比较消息;因此同一文件同一行中,列号更靠前的错误会排在前面。
随后 RemoveMultiples 只比较当前错误和上一条已保留错误的文件名、行号。如果两者相同,当前错误就被跳过;如果文件名或行号变化,当前错误就成为该行的首条代表。

func compactDiagnostics(list scanner.ErrorList) scanner.ErrorList {
// RemoveMultiples 会先排序,再让每个文件的每一行只保留一条诊断。
list.RemoveMultiples()
return list
}
这里返回的是同一个切片变量整理后的结果,不会创建一组新的 scanner.Error 对象。调用方如果只需要摘要,这种写法很直接;如果后面还要输出所有错误,就不要对唯一的原始列表直接调用。
把完整诊断和摘要诊断分开
最实用的做法是准备两份切片:一份保存全部指针,另一份作为展示副本。下面的复制是浅复制,复制的是指针切片;RemoveMultiples 只会重排和缩短切片,不会改写每个 Error 的位置或消息,所以适合这个场景。
func splitDiagnostics(list scanner.ErrorList) (scanner.ErrorList, scanner.ErrorList) {
// 复制切片头,保留完整列表供详细报告使用。
all := append(scanner.ErrorList(nil), list...)
// 再复制一份,只让摘要副本承担排序和按行去重。
summary := append(scanner.ErrorList(nil), list...)
summary.RemoveMultiples()
return all, summary
}
复制要发生在整理之前。若先对 list 调用 RemoveMultiples,原切片长度已经缩短,再复制就只能得到摘要,无法恢复被移除的指针顺序。
从 Scanner 收集错误时保留位置
scanner.Scanner 可以通过 Init 接收一个错误处理函数。处理函数拿到的是错误位置和消息,收集时应把这两个值加入 ErrorList,否则后面的按文件、按行归并就没有可靠依据。
func scanSource(filename string, src []byte) scanner.ErrorList {
var list scanner.ErrorList
fset := token.NewFileSet()
file := fset.AddFile(filename, fset.Base(), len(src))
var s scanner.Scanner
s.Init(file, src, func(pos token.Position, msg string) {
// 位置和消息一起保存,后续才能按文件和行号整理。
list.Add(pos, msg)
}, 0)
for {
_, tok, _ := s.Scan()
if tok == token.EOF {
break
}
}
return list
}
扫描器在遇到语法错误后仍可能继续返回可用 token,所以不要只根据是否出现非法 token 判断扫描是否成功;错误处理函数收到的诊断数量才是更直接的收集结果。
同一行多条错误时为什么是“第一条”
“第一条”不是指错误最早被扫描到的那一条,而是排序完成后的第一条。排序同时使用文件名、行号、列号和消息,因此同一文件同一行通常会优先保留列号更小的诊断。这个规则适合做稳定摘要,但不等于错误重要性排序。
如果业务认为某种消息更重要,不能直接依赖 RemoveMultiples 选出它。可以先自行过滤、调整消息或构造业务排序后的列表,再决定是否调用标准库方法;也可以保留完整列表,在展示层按优先级选择代表项。
注意没有有效位置的 Error
按行去重的前提是 Position.Filename 和 Position.Line 能表达一个稳定位置。没有文件名或行号的错误不适合和源码行诊断混在同一摘要里,最好单独放到“全局错误”区域,或在进入 RemoveMultiples 前单独保留。
尤其不要把“没有位置”误读成“所有未定位错误属于同一源码行”。该方法的去重判断就是文件名加行号,调用方应先决定无位置错误的展示策略。

原地整理带来的调用边界
RemoveMultiples 使用指针接收者,是因为它既要排序元素,又要把切片重新切到保留数量。整理后的列表通常复用原来的底层数组;如果程序还持有同一切片的其他视图,就不要把这些视图当成独立的“原始快照”。最稳妥的做法仍是先复制,再分别传给详细报告和摘要输出。
| 目标 | 推荐做法 | 需要注意 |
|---|---|---|
| 输出所有诊断 | 直接遍历原始 ErrorList | 不要先调用 RemoveMultiples |
| 生成一行一条摘要 | 对副本调用 RemoveMultiples | 代表项由排序规则决定 |
| 自定义重要性 | 先做业务过滤或自定义选择 | 不要把标准排序当成优先级排序 |
| 处理无位置错误 | 单独展示或提前归类 | 按行去重缺少稳定位置依据 |
最后记住这几个判断
RemoveMultiples的去重键是文件名和行号,不是消息文本,也不是列号。- 它会先排序,所以“第一条”由位置排序决定,不代表最重要或最早发生。
- 方法直接修改当前
ErrorList的切片顺序和长度;需要完整错误时先复制。 - 收集 Scanner 错误时要保存
token.Position,否则无法得到稳定的按行摘要。 - 无有效位置的错误应有单独展示策略,不要强行当作源码行诊断。
相关问题
RemoveMultiples 会删除原始 Error 对象吗?它主要重排指针并缩短切片,不会把每个 Error 的消息和位置字段改写;但原切片的顺序和长度会变化。
同一行想保留最严重的错误怎么办?不要直接依赖标准库的排序结果,先按业务规则筛选代表项,再把完整列表留作详细输出。
只调用 Sort 能不能达到同样效果?不能。Sort 只排序,RemoveMultiples 才会按文件名和行号压缩重复行。
为什么要复制两份 ErrorList?因为摘要操作会改变切片长度和顺序,先复制可以同时保留详细诊断与简洁展示。
-
860 收藏
-
843 收藏
-
826 收藏
-
809 收藏
-
792 收藏
-
228 收藏
-
488 收藏
-
501 收藏
-
482 收藏
-
418 收藏
-
135 收藏
-
106 收藏
-
110 收藏
-
200 收藏
-
497 收藏
-
373 收藏
-
260 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习