blob: 77d9e66a8ed39428b88fadd1b585929027610af6 [file] [log] [blame]
// 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
}
}
}