| // Copyright 2019 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package tests |
| |
| import ( |
| "path/filepath" |
| "strconv" |
| "strings" |
| |
| "golang.org/x/tools/go/packages/packagestest" |
| ) |
| |
| type Normalizer struct { |
| path string |
| slashed string |
| escaped string |
| fragment string |
| } |
| |
| func CollectNormalizers(exported *packagestest.Exported) []Normalizer { |
| // build the path normalizing patterns |
| var normalizers []Normalizer |
| for _, m := range exported.Modules { |
| for fragment := range m.Files { |
| n := Normalizer{ |
| path: exported.File(m.Name, fragment), |
| fragment: fragment, |
| } |
| if n.slashed = filepath.ToSlash(n.path); n.slashed == n.path { |
| n.slashed = "" |
| } |
| quoted := strconv.Quote(n.path) |
| if n.escaped = quoted[1 : len(quoted)-1]; n.escaped == n.path { |
| n.escaped = "" |
| } |
| normalizers = append(normalizers, n) |
| } |
| } |
| return normalizers |
| } |
| |
| // NormalizePrefix normalizes a single path at the front of the input string. |
| func NormalizePrefix(s string, normalizers []Normalizer) string { |
| for _, n := range normalizers { |
| if t := strings.TrimPrefix(s, n.path); t != s { |
| return n.fragment + t |
| } |
| if t := strings.TrimPrefix(s, n.slashed); t != s { |
| return n.fragment + t |
| } |
| if t := strings.TrimPrefix(s, n.escaped); t != s { |
| return n.fragment + t |
| } |
| } |
| return s |
| } |
| |
| // Normalize replaces all paths present in s with just the fragment portion |
| // this is used to make golden files not depend on the temporary paths of the files |
| func Normalize(s string, normalizers []Normalizer) string { |
| type entry struct { |
| path string |
| index int |
| fragment string |
| } |
| var match []entry |
| // collect the initial state of all the matchers |
| for _, n := range normalizers { |
| index := strings.Index(s, n.path) |
| if index >= 0 { |
| match = append(match, entry{n.path, index, n.fragment}) |
| } |
| if n.slashed != "" { |
| index := strings.Index(s, n.slashed) |
| if index >= 0 { |
| match = append(match, entry{n.slashed, index, n.fragment}) |
| } |
| } |
| if n.escaped != "" { |
| index := strings.Index(s, n.escaped) |
| if index >= 0 { |
| match = append(match, entry{n.escaped, index, n.fragment}) |
| } |
| } |
| } |
| // result should be the same or shorter than the input |
| var b strings.Builder |
| last := 0 |
| for { |
| // find the nearest path match to the start of the buffer |
| next := -1 |
| nearest := len(s) |
| for i, c := range match { |
| if c.index >= 0 && nearest > c.index { |
| nearest = c.index |
| next = i |
| } |
| } |
| // if there are no matches, we copy the rest of the string and are done |
| if next < 0 { |
| b.WriteString(s[last:]) |
| return b.String() |
| } |
| // we have a match |
| n := &match[next] |
| // copy up to the start of the match |
| b.WriteString(s[last:n.index]) |
| // skip over the filename |
| last = n.index + len(n.path) |
| |
| // Hack: In multi-module mode, we add a "testmodule/" prefix, so trim |
| // it from the fragment. |
| fragment := n.fragment |
| if strings.HasPrefix(fragment, "testmodule") { |
| split := strings.Split(filepath.ToSlash(fragment), "/") |
| fragment = filepath.FromSlash(strings.Join(split[1:], "/")) |
| } |
| |
| // add in the fragment instead |
| b.WriteString(fragment) |
| // see what the next match for this path is |
| n.index = strings.Index(s[last:], n.path) |
| if n.index >= 0 { |
| n.index += last |
| } |
| } |
| } |