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  }