internal/deps/gomod.go
60 lines · 1536 bytes
1package deps
2
3import "strings"
4
5// parseGoMod reads the direct requirements from go.mod. Indirect
6// requirements are the module graph's business rather than the maintainer's,
7// and a replaced module does not come from the proxy at all, so both are
8// skipped.
9func parseGoMod(read ReadFile) []Dep {
10 raw, err := read("go.mod")
11 if err != nil {
12 return nil
13 }
14 replaced := map[string]bool{}
15 var reqs [][2]string
16 block := "" // the directive whose ( ... ) block we are inside
17 for _, line := range strings.Split(string(raw), "\n") {
18 line = strings.TrimSpace(line)
19 indirect := false
20 if i := strings.Index(line, "//"); i >= 0 {
21 indirect = strings.Contains(line[i:], "indirect")
22 line = strings.TrimSpace(line[:i])
23 }
24 if line == "" {
25 continue
26 }
27 directive := block
28 if block == "" {
29 d, rest, ok := strings.Cut(line, " ")
30 if !ok || (d != "require" && d != "replace") {
31 continue
32 }
33 if rest = strings.TrimSpace(rest); rest == "(" {
34 block = d
35 continue
36 }
37 directive, line = d, rest
38 } else if line == ")" {
39 block = ""
40 continue
41 }
42 fields := strings.Fields(line)
43 switch {
44 case directive == "require" && !indirect && len(fields) >= 2:
45 reqs = append(reqs, [2]string{fields[0], fields[1]})
46 case directive == "replace" && len(fields) >= 1:
47 replaced[fields[0]] = true
48 }
49 }
50 var out []Dep
51 for _, r := range reqs {
52 if replaced[r[0]] {
53 continue
54 }
55 if v := pin(r[1]); v != "" {
56 out = append(out, Dep{Ecosystem: EcoGo, Name: r[0], Current: r[1]})
57 }
58 }
59 return out
60}