Skip to content

Commit 6a6e345

Browse files
authored
Merge pull request #142 from itmisx/fix/codegraph-skip-generated-140
🐛 fix: codegraph-skip-generated
2 parents 2708d8f + 3588892 commit 6a6e345

2 files changed

Lines changed: 142 additions & 0 deletions

File tree

codegraph/generated_skip_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package codegraph
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"strings"
7+
"testing"
8+
)
9+
10+
// issue #140:自动生成的源码(EF Core 模型快照、protoc / go generate 产物等)按头部标记跳过,
11+
// 不喂给 tree-sitter、不进图谱 —— 避免大型 .NET 仓把子进程顶过内存上限(exit 75)。
12+
func TestIsGeneratedSource(t *testing.T) {
13+
gen := []string{
14+
"// <auto-generated />\npublic class Foo {}\n", // EF Core / .NET
15+
"// <auto-generated>\n// 由工具生成\n// </auto-generated>\nclass F{}", // 带换行的 .NET 头
16+
"// Code generated by protoc-gen-go. DO NOT EDIT.\npackage pb\n", // Go / protoc
17+
"/* @generated */\nexport const x = 1\n", // @generated 约定
18+
}
19+
for _, s := range gen {
20+
if !isGeneratedSource([]byte(s)) {
21+
t.Errorf("应判定为生成文件:\n%s", s)
22+
}
23+
}
24+
normal := []string{
25+
"package x\n\nfunc RealFunc() {}\n",
26+
"public class Service {\n // do not edit lightly, but hand-written\n", // 注:不含 DO NOT EDIT 全大写连写
27+
"",
28+
}
29+
for _, s := range normal {
30+
if isGeneratedSource([]byte(s)) {
31+
t.Errorf("正常源码不该判定为生成文件:\n%q", s)
32+
}
33+
}
34+
35+
// 标记在 1KB 头部之外 → 不判定(只扫头部,省时且避免误伤正文里偶然出现的字样)。
36+
deep := strings.Repeat("x\n", 2000) + "// <auto-generated />\n"
37+
if isGeneratedSource([]byte(deep)) {
38+
t.Error("超出头部扫描窗口的标记不应触发判定")
39+
}
40+
}
41+
42+
// TestMigrationsDirSkipped:`Migrations/` 目录(EF Core / Django 约定)整体跳过。
43+
func TestMigrationsDirSkipped(t *testing.T) {
44+
for _, n := range []string{"migrations", "Migrations", "MIGRATIONS"} {
45+
if !shouldSkipDir(n) {
46+
t.Errorf("%q 应被跳过", n)
47+
}
48+
}
49+
}
50+
51+
// TestAssembleSkipsGeneratedAndMigrations:端到端验证收紧后的行为(issue #140):
52+
// - 手写文件入图;
53+
// - **大**生成文件跳过(内存元凶 + 低导航价值);
54+
// - **小**生成文件**保留**(常含被引用的类型定义,且不撑内存);
55+
// - Migrations 目录整体跳过。
56+
func TestAssembleSkipsGeneratedAndMigrations(t *testing.T) {
57+
root := t.TempDir()
58+
mk := func(rel, content string) {
59+
p := filepath.Join(root, rel)
60+
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
61+
t.Fatal(err)
62+
}
63+
if err := os.WriteFile(p, []byte(content), 0o644); err != nil {
64+
t.Fatal(err)
65+
}
66+
}
67+
const genHeader = "// Code generated by tool. DO NOT EDIT.\npackage x\n\n"
68+
// 大生成文件:头部标记 + ≥128KiB 短注释行填充(不触发超长行)+ 一个符号。
69+
pad := strings.Repeat("// padding line to inflate file size\n", 4000) // ~148 KiB
70+
mk("real.go", "package x\n\nfunc RealFunc() {}\n") // 手写,入图
71+
mk("gen_small.go", genHeader+"func GeneratedSmall() {}\n") // 生成但小 → 保留
72+
mk("gen_big.go", genHeader+pad+"func GeneratedBig() {}\n") // 生成且大 → 跳过
73+
mk("Migrations/mig.go", "package x\n\nfunc MigFunc() {}\n") // Migrations 目录 → 跳过
74+
75+
ix := &Index{root: root}
76+
g, _, err := ix.assemble(nil, false)
77+
if err != nil {
78+
t.Fatal(err)
79+
}
80+
names := map[string]bool{}
81+
for _, s := range g.Symbols {
82+
names[s.Name] = true
83+
}
84+
if !names["RealFunc"] {
85+
t.Error("手写文件的 RealFunc 应入图")
86+
}
87+
if !names["GeneratedSmall"] {
88+
t.Error("小生成文件的 GeneratedSmall 应保留入图(收紧后只跳大文件)")
89+
}
90+
if names["GeneratedBig"] {
91+
t.Error("大生成文件的 GeneratedBig 不该入图")
92+
}
93+
if names["MigFunc"] {
94+
t.Error("Migrations 目录的 MigFunc 不该入图")
95+
}
96+
}

codegraph/index.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package codegraph
22

33
import (
4+
"bytes"
45
"fmt"
56
"io/fs"
67
"os"
@@ -52,6 +53,9 @@ var skipDirs = map[string]bool{
5253
"bower_components": true, "pods": true, "__pycache__": true,
5354
"venv": true, "site-packages": true, ".nuxt": true,
5455
"out": true, "obj": true, "coverage": true, "__snapshots__": true,
56+
// 自动生成的迁移代码(EF Core / Django 等约定 `migrations/` 目录):体量大(EF Core 模型快照
57+
// 单文件可达数 MB)、对代码导航价值低,大仓里会把建图顶过子进程内存上限(issue #140)。
58+
"migrations": true,
5559
}
5660

5761
// shouldSkipDir 判断遍历时是否跳过该目录:命中 skipDirs(**大小写不敏感**)或以 "." 开头。
@@ -69,6 +73,42 @@ const maxFileSize = 1 << 20 // 1 MiB
6973
// (遗弃的 runaway 会持续吃内存直到 OOM)→ 只能从源头不喂这类文件。用 var 便于测试调小。
7074
var maxLineBytes = 40 << 10 // 40 KiB
7175

76+
// generatedMarkers:出现在文件头部即判定为"自动生成的源码"的标记(语言无关)。
77+
// 覆盖 EF Core 的 *.Designer.cs / *ModelSnapshot.cs(`<auto-generated/>`)、Go generate /
78+
// protoc 产物(`Code generated … DO NOT EDIT`)、以及 `@generated` 约定(部分工具 / Meta)。
79+
var generatedMarkers = [][]byte{
80+
[]byte("<auto-generated"), // C# / VB / 各类 .NET 代码生成器
81+
[]byte("DO NOT EDIT"), // Go「Code generated … DO NOT EDIT.」及多数代码生成工具
82+
[]byte("@generated"), // 部分工具 / Meta 约定
83+
}
84+
85+
// generatedScanPrefix:只在文件头这么多字节里找生成标记 —— 标记总在顶部,扫全文件没必要也更慢。
86+
const generatedScanPrefix = 1 << 10 // 1 KiB
87+
88+
// generatedSkipMinBytes:只有"带生成标记 **且** 体量 ≥ 此阈值"的文件才跳过(issue #140)。
89+
// 取舍依据是"低导航价值 + 大体量"的交集,而非单纯"是否生成":
90+
// - 大的生成文件(EF Core 模型快照、ANTLR 产物等)既是内存元凶、又几乎没人导航进去 → 跳;
91+
// - 小的生成文件(多数 protobuf/gRPC/mock 的类型定义,几十 KB)往往**定义了被手写代码引用的符号**,
92+
// 留在图里才能正确"跳转定义 / 查找引用",且本就不撑内存 → 保留。
93+
//
94+
// 用 var 便于测试调小。
95+
var generatedSkipMinBytes = 128 << 10 // 128 KiB
96+
97+
// isGeneratedSource 报告 src 是否为自动生成的源码(按头部标记判定,语言无关,issue #140)。
98+
// 注意:这只判"是不是生成的";要不要跳过还要叠加大小门槛(generatedSkipMinBytes),见 assemble。
99+
func isGeneratedSource(src []byte) bool {
100+
head := src
101+
if len(head) > generatedScanPrefix {
102+
head = head[:generatedScanPrefix]
103+
}
104+
for _, m := range generatedMarkers {
105+
if bytes.Contains(head, m) {
106+
return true
107+
}
108+
}
109+
return false
110+
}
111+
72112
// hasOverlongLine 报告 src 是否含超过 max 字节的行(O(n) 扫一遍,不分配)。
73113
func hasOverlongLine(src []byte, max int) bool {
74114
start := 0
@@ -453,6 +493,12 @@ func (ix *Index) assemble(preciseGoCalls []Edge, usePrecise bool) (_ *Graph, deg
453493
if hasOverlongLine(src, maxLineBytes) {
454494
return nil
455495
}
496+
// 大体量的自动生成源码(EF Core 模型快照 / ANTLR 产物等):内存元凶且几乎无导航价值,
497+
// 解析前跳过(issue #140)。只跳"生成 + 够大"的:小的生成类型定义(protobuf/mock 等)
498+
// 常被手写代码引用,留下才能正确跳转/查引用,且本就不撑内存。
499+
if len(src) >= generatedSkipMinBytes && isGeneratedSource(src) {
500+
return nil
501+
}
456502
rel, relErr := filepath.Rel(ix.root, path)
457503
if relErr != nil {
458504
rel = path

0 commit comments

Comments
 (0)