登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

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 只比较当前错误和上一条已保留错误的文件名、行号。如果两者相同,当前错误就被跳过;如果文件名或行号变化,当前错误就成为该行的首条代表。

ErrorList 中的错误按文件名行号列号消息排序并按同文件同一行保留首条诊断的静态关系图
图1:说明图,展示 ErrorList 的排序字段、按行去重条件和保留首条诊断之间的静态关系;它不是运行截图。
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 前部覆盖元素并截短为保留结果的静态说明图
图2:结构图,展示指针接收者、原始切片、按文件名加行号判重、前部覆盖和 [0:i] 结果边界;它不是运行结果截图。

原地整理带来的调用边界

RemoveMultiples 使用指针接收者,是因为它既要排序元素,又要把切片重新切到保留数量。整理后的列表通常复用原来的底层数组;如果程序还持有同一切片的其他视图,就不要把这些视图当成独立的“原始快照”。最稳妥的做法仍是先复制,再分别传给详细报告和摘要输出。

目标推荐做法需要注意
输出所有诊断直接遍历原始 ErrorList不要先调用 RemoveMultiples
生成一行一条摘要对副本调用 RemoveMultiples代表项由排序规则决定
自定义重要性先做业务过滤或自定义选择不要把标准排序当成优先级排序
处理无位置错误单独展示或提前归类按行去重缺少稳定位置依据

最后记住这几个判断

  • RemoveMultiples 的去重键是文件名和行号,不是消息文本,也不是列号。
  • 它会先排序,所以“第一条”由位置排序决定,不代表最重要或最早发生。
  • 方法直接修改当前 ErrorList 的切片顺序和长度;需要完整错误时先复制。
  • 收集 Scanner 错误时要保存 token.Position,否则无法得到稳定的按行摘要。
  • 无有效位置的错误应有单独展示策略,不要强行当作源码行诊断。

相关问题

RemoveMultiples 会删除原始 Error 对象吗?它主要重排指针并缩短切片,不会把每个 Error 的消息和位置字段改写;但原切片的顺序和长度会变化。

同一行想保留最严重的错误怎么办?不要直接依赖标准库的排序结果,先按业务规则筛选代表项,再把完整列表留作详细输出。

只调用 Sort 能不能达到同样效果?不能。Sort 只排序,RemoveMultiples 才会按文件名和行号压缩重复行。

为什么要复制两份 ErrorList?因为摘要操作会改变切片长度和顺序,先复制可以同时保留详细诊断与简洁展示。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>