github.com/v2fly/tools@v0.100.0/internal/lsp/tests/normalizer.go (about) 1 // Copyright 2019 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package tests 6 7 import ( 8 "path/filepath" 9 "strconv" 10 "strings" 11 12 "github.com/v2fly/tools/go/packages/packagestest" 13 ) 14 15 type Normalizer struct { 16 path string 17 slashed string 18 escaped string 19 fragment string 20 } 21 22 func CollectNormalizers(exported *packagestest.Exported) []Normalizer { 23 // build the path normalizing patterns 24 var normalizers []Normalizer 25 for _, m := range exported.Modules { 26 for fragment := range m.Files { 27 n := Normalizer{ 28 path: exported.File(m.Name, fragment), 29 fragment: fragment, 30 } 31 if n.slashed = filepath.ToSlash(n.path); n.slashed == n.path { 32 n.slashed = "" 33 } 34 quoted := strconv.Quote(n.path) 35 if n.escaped = quoted[1 : len(quoted)-1]; n.escaped == n.path { 36 n.escaped = "" 37 } 38 normalizers = append(normalizers, n) 39 } 40 } 41 return normalizers 42 } 43 44 // NormalizePrefix normalizes a single path at the front of the input string. 45 func NormalizePrefix(s string, normalizers []Normalizer) string { 46 for _, n := range normalizers { 47 if t := strings.TrimPrefix(s, n.path); t != s { 48 return n.fragment + t 49 } 50 if t := strings.TrimPrefix(s, n.slashed); t != s { 51 return n.fragment + t 52 } 53 if t := strings.TrimPrefix(s, n.escaped); t != s { 54 return n.fragment + t 55 } 56 } 57 return s 58 } 59 60 // Normalize replaces all paths present in s with just the fragment portion 61 // this is used to make golden files not depend on the temporary paths of the files 62 func Normalize(s string, normalizers []Normalizer) string { 63 type entry struct { 64 path string 65 index int 66 fragment string 67 } 68 var match []entry 69 // collect the initial state of all the matchers 70 for _, n := range normalizers { 71 index := strings.Index(s, n.path) 72 if index >= 0 { 73 match = append(match, entry{n.path, index, n.fragment}) 74 } 75 if n.slashed != "" { 76 index := strings.Index(s, n.slashed) 77 if index >= 0 { 78 match = append(match, entry{n.slashed, index, n.fragment}) 79 } 80 } 81 if n.escaped != "" { 82 index := strings.Index(s, n.escaped) 83 if index >= 0 { 84 match = append(match, entry{n.escaped, index, n.fragment}) 85 } 86 } 87 } 88 // result should be the same or shorter than the input 89 var b strings.Builder 90 last := 0 91 for { 92 // find the nearest path match to the start of the buffer 93 next := -1 94 nearest := len(s) 95 for i, c := range match { 96 if c.index >= 0 && nearest > c.index { 97 nearest = c.index 98 next = i 99 } 100 } 101 // if there are no matches, we copy the rest of the string and are done 102 if next < 0 { 103 b.WriteString(s[last:]) 104 return b.String() 105 } 106 // we have a match 107 n := &match[next] 108 // copy up to the start of the match 109 b.WriteString(s[last:n.index]) 110 // skip over the filename 111 last = n.index + len(n.path) 112 113 // Hack: In multi-module mode, we add a "testmodule/" prefix, so trim 114 // it from the fragment. 115 fragment := n.fragment 116 if strings.HasPrefix(fragment, "testmodule") { 117 split := strings.Split(filepath.ToSlash(fragment), "/") 118 fragment = filepath.FromSlash(strings.Join(split[1:], "/")) 119 } 120 121 // add in the fragment instead 122 b.WriteString(fragment) 123 // see what the next match for this path is 124 n.index = strings.Index(s[last:], n.path) 125 if n.index >= 0 { 126 n.index += last 127 } 128 } 129 }