aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/vendor/golang.org/x
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/vendor/golang.org/x')
-rw-r--r--src/cmd/vendor/golang.org/x/mod/modfile/read.go8
-rw-r--r--src/cmd/vendor/golang.org/x/mod/modfile/rule.go243
-rw-r--r--src/cmd/vendor/golang.org/x/mod/module/module.go33
-rw-r--r--src/cmd/vendor/golang.org/x/mod/zip/zip.go621
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go6
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unmarshal/unmarshal.go7
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go2
-rw-r--r--src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go343
-rw-r--r--src/cmd/vendor/golang.org/x/tools/internal/lsp/fuzzy/input.go168
-rw-r--r--src/cmd/vendor/golang.org/x/tools/internal/lsp/fuzzy/matcher.go398
10 files changed, 1602 insertions, 227 deletions
diff --git a/src/cmd/vendor/golang.org/x/mod/modfile/read.go b/src/cmd/vendor/golang.org/x/mod/modfile/read.go
index c1f2008ee4..2a961ca81c 100644
--- a/src/cmd/vendor/golang.org/x/mod/modfile/read.go
+++ b/src/cmd/vendor/golang.org/x/mod/modfile/read.go
@@ -477,9 +477,17 @@ func (in *input) startToken() {
// endToken marks the end of an input token.
// It records the actual token string in tok.text.
+// A single trailing newline (LF or CRLF) will be removed from comment tokens.
func (in *input) endToken(kind tokenKind) {
in.token.kind = kind
text := string(in.tokenStart[:len(in.tokenStart)-len(in.remaining)])
+ if kind.isComment() {
+ if strings.HasSuffix(text, "\r\n") {
+ text = text[:len(text)-2]
+ } else {
+ text = strings.TrimSuffix(text, "\n")
+ }
+ }
in.token.text = text
in.token.endPos = in.pos
}
diff --git a/src/cmd/vendor/golang.org/x/mod/modfile/rule.go b/src/cmd/vendor/golang.org/x/mod/modfile/rule.go
index 91ca6828df..83398dda5d 100644
--- a/src/cmd/vendor/golang.org/x/mod/modfile/rule.go
+++ b/src/cmd/vendor/golang.org/x/mod/modfile/rule.go
@@ -30,6 +30,7 @@ import (
"golang.org/x/mod/internal/lazyregexp"
"golang.org/x/mod/module"
+ "golang.org/x/mod/semver"
)
// A File is the parsed, interpreted form of a go.mod file.
@@ -39,6 +40,7 @@ type File struct {
Require []*Require
Exclude []*Exclude
Replace []*Replace
+ Retract []*Retract
Syntax *FileSyntax
}
@@ -75,6 +77,21 @@ type Replace struct {
Syntax *Line
}
+// A Retract is a single retract statement.
+type Retract struct {
+ VersionInterval
+ Rationale string
+ Syntax *Line
+}
+
+// A VersionInterval represents a range of versions with upper and lower bounds.
+// Intervals are closed: both bounds are included. When Low is equal to High,
+// the interval may refer to a single version ('v1.2.3') or an interval
+// ('[v1.2.3, v1.2.3]'); both have the same representation.
+type VersionInterval struct {
+ Low, High string
+}
+
func (f *File) AddModuleStmt(path string) error {
if f.Syntax == nil {
f.Syntax = new(FileSyntax)
@@ -138,7 +155,7 @@ func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (*File
for _, x := range fs.Stmt {
switch x := x.(type) {
case *Line:
- f.add(&errs, x, x.Token[0], x.Token[1:], fix, strict)
+ f.add(&errs, nil, x, x.Token[0], x.Token[1:], fix, strict)
case *LineBlock:
if len(x.Token) > 1 {
@@ -161,9 +178,9 @@ func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (*File
})
}
continue
- case "module", "require", "exclude", "replace":
+ case "module", "require", "exclude", "replace", "retract":
for _, l := range x.Line {
- f.add(&errs, l, x.Token[0], l.Token, fix, strict)
+ f.add(&errs, x, l, x.Token[0], l.Token, fix, strict)
}
}
}
@@ -177,7 +194,7 @@ func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (*File
var GoVersionRE = lazyregexp.New(`^([1-9][0-9]*)\.(0|[1-9][0-9]*)$`)
-func (f *File) add(errs *ErrorList, line *Line, verb string, args []string, fix VersionFixer, strict bool) {
+func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, args []string, fix VersionFixer, strict bool) {
// If strict is false, this module is a dependency.
// We ignore all unknown directives as well as main-module-only
// directives like replace and exclude. It will work better for
@@ -186,7 +203,7 @@ func (f *File) add(errs *ErrorList, line *Line, verb string, args []string, fix
// and simply ignore those statements.
if !strict {
switch verb {
- case "module", "require", "go":
+ case "go", "module", "retract", "require":
// want these even for dependency go.mods
default:
return
@@ -232,6 +249,7 @@ func (f *File) add(errs *ErrorList, line *Line, verb string, args []string, fix
f.Go = &Go{Syntax: line}
f.Go.Version = args[0]
+
case "module":
if f.Module != nil {
errorf("repeated module statement")
@@ -248,6 +266,7 @@ func (f *File) add(errs *ErrorList, line *Line, verb string, args []string, fix
return
}
f.Module.Mod = module.Version{Path: s}
+
case "require", "exclude":
if len(args) != 2 {
errorf("usage: %s module/path v1.2.3", verb)
@@ -284,6 +303,7 @@ func (f *File) add(errs *ErrorList, line *Line, verb string, args []string, fix
Syntax: line,
})
}
+
case "replace":
arrow := 2
if len(args) >= 2 && args[1] == "=>" {
@@ -347,6 +367,33 @@ func (f *File) add(errs *ErrorList, line *Line, verb string, args []string, fix
New: module.Version{Path: ns, Version: nv},
Syntax: line,
})
+
+ case "retract":
+ rationale := parseRetractRationale(block, line)
+ vi, err := parseVersionInterval(verb, &args, fix)
+ if err != nil {
+ if strict {
+ wrapError(err)
+ return
+ } else {
+ // Only report errors parsing intervals in the main module. We may
+ // support additional syntax in the future, such as open and half-open
+ // intervals. Those can't be supported now, because they break the
+ // go.mod parser, even in lax mode.
+ return
+ }
+ }
+ if len(args) > 0 && strict {
+ // In the future, there may be additional information after the version.
+ errorf("unexpected token after version: %q", args[0])
+ return
+ }
+ retract := &Retract{
+ VersionInterval: vi,
+ Rationale: rationale,
+ Syntax: line,
+ }
+ f.Retract = append(f.Retract, retract)
}
}
@@ -444,6 +491,53 @@ func AutoQuote(s string) string {
return s
}
+func parseVersionInterval(verb string, args *[]string, fix VersionFixer) (VersionInterval, error) {
+ toks := *args
+ if len(toks) == 0 || toks[0] == "(" {
+ return VersionInterval{}, fmt.Errorf("expected '[' or version")
+ }
+ if toks[0] != "[" {
+ v, err := parseVersion(verb, "", &toks[0], fix)
+ if err != nil {
+ return VersionInterval{}, err
+ }
+ *args = toks[1:]
+ return VersionInterval{Low: v, High: v}, nil
+ }
+ toks = toks[1:]
+
+ if len(toks) == 0 {
+ return VersionInterval{}, fmt.Errorf("expected version after '['")
+ }
+ low, err := parseVersion(verb, "", &toks[0], fix)
+ if err != nil {
+ return VersionInterval{}, err
+ }
+ toks = toks[1:]
+
+ if len(toks) == 0 || toks[0] != "," {
+ return VersionInterval{}, fmt.Errorf("expected ',' after version")
+ }
+ toks = toks[1:]
+
+ if len(toks) == 0 {
+ return VersionInterval{}, fmt.Errorf("expected version after ','")
+ }
+ high, err := parseVersion(verb, "", &toks[0], fix)
+ if err != nil {
+ return VersionInterval{}, err
+ }
+ toks = toks[1:]
+
+ if len(toks) == 0 || toks[0] != "]" {
+ return VersionInterval{}, fmt.Errorf("expected ']' after version")
+ }
+ toks = toks[1:]
+
+ *args = toks
+ return VersionInterval{Low: low, High: high}, nil
+}
+
func parseString(s *string) (string, error) {
t := *s
if strings.HasPrefix(t, `"`) {
@@ -461,6 +555,27 @@ func parseString(s *string) (string, error) {
return t, nil
}
+// parseRetractRationale extracts the rationale for a retract directive from the
+// surrounding comments. If the line does not have comments and is part of a
+// block that does have comments, the block's comments are used.
+func parseRetractRationale(block *LineBlock, line *Line) string {
+ comments := line.Comment()
+ if block != nil && len(comments.Before) == 0 && len(comments.Suffix) == 0 {
+ comments = block.Comment()
+ }
+ groups := [][]Comment{comments.Before, comments.Suffix}
+ var lines []string
+ for _, g := range groups {
+ for _, c := range g {
+ if !strings.HasPrefix(c.Token, "//") {
+ continue // blank line
+ }
+ lines = append(lines, strings.TrimSpace(strings.TrimPrefix(c.Token, "//")))
+ }
+ }
+ return strings.Join(lines, "\n")
+}
+
type ErrorList []Error
func (e ErrorList) Error() string {
@@ -494,6 +609,8 @@ func (e *Error) Error() string {
var directive string
if e.ModPath != "" {
directive = fmt.Sprintf("%s %s: ", e.Verb, e.ModPath)
+ } else if e.Verb != "" {
+ directive = fmt.Sprintf("%s: ", e.Verb)
}
return pos + directive + e.Err.Error()
@@ -585,6 +702,15 @@ func (f *File) Cleanup() {
}
f.Replace = f.Replace[:w]
+ w = 0
+ for _, r := range f.Retract {
+ if r.Low != "" || r.High != "" {
+ f.Retract[w] = r
+ w++
+ }
+ }
+ f.Retract = f.Retract[:w]
+
f.Syntax.Cleanup()
}
@@ -778,6 +904,34 @@ func (f *File) DropReplace(oldPath, oldVers string) error {
return nil
}
+func (f *File) AddRetract(vi VersionInterval, rationale string) error {
+ r := &Retract{
+ VersionInterval: vi,
+ }
+ if vi.Low == vi.High {
+ r.Syntax = f.Syntax.addLine(nil, "retract", AutoQuote(vi.Low))
+ } else {
+ r.Syntax = f.Syntax.addLine(nil, "retract", "[", AutoQuote(vi.Low), ",", AutoQuote(vi.High), "]")
+ }
+ if rationale != "" {
+ for _, line := range strings.Split(rationale, "\n") {
+ com := Comment{Token: "// " + line}
+ r.Syntax.Comment().Before = append(r.Syntax.Comment().Before, com)
+ }
+ }
+ return nil
+}
+
+func (f *File) DropRetract(vi VersionInterval) error {
+ for _, r := range f.Retract {
+ if r.VersionInterval == vi {
+ f.Syntax.removeLine(r.Syntax)
+ *r = Retract{}
+ }
+ }
+ return nil
+}
+
func (f *File) SortBlocks() {
f.removeDups() // otherwise sorting is unsafe
@@ -786,28 +940,38 @@ func (f *File) SortBlocks() {
if !ok {
continue
}
- sort.Slice(block.Line, func(i, j int) bool {
- li := block.Line[i]
- lj := block.Line[j]
- for k := 0; k < len(li.Token) && k < len(lj.Token); k++ {
- if li.Token[k] != lj.Token[k] {
- return li.Token[k] < lj.Token[k]
- }
- }
- return len(li.Token) < len(lj.Token)
+ less := lineLess
+ if block.Token[0] == "retract" {
+ less = lineRetractLess
+ }
+ sort.SliceStable(block.Line, func(i, j int) bool {
+ return less(block.Line[i], block.Line[j])
})
}
}
+// removeDups removes duplicate exclude and replace directives.
+//
+// Earlier exclude directives take priority.
+//
+// Later replace directives take priority.
+//
+// require directives are not de-duplicated. That's left up to higher-level
+// logic (MVS).
+//
+// retract directives are not de-duplicated since comments are
+// meaningful, and versions may be retracted multiple times.
func (f *File) removeDups() {
- have := make(map[module.Version]bool)
kill := make(map[*Line]bool)
+
+ // Remove duplicate excludes.
+ haveExclude := make(map[module.Version]bool)
for _, x := range f.Exclude {
- if have[x.Mod] {
+ if haveExclude[x.Mod] {
kill[x.Syntax] = true
continue
}
- have[x.Mod] = true
+ haveExclude[x.Mod] = true
}
var excl []*Exclude
for _, x := range f.Exclude {
@@ -817,15 +981,16 @@ func (f *File) removeDups() {
}
f.Exclude = excl
- have = make(map[module.Version]bool)
+ // Remove duplicate replacements.
// Later replacements take priority over earlier ones.
+ haveReplace := make(map[module.Version]bool)
for i := len(f.Replace) - 1; i >= 0; i-- {
x := f.Replace[i]
- if have[x.Old] {
+ if haveReplace[x.Old] {
kill[x.Syntax] = true
continue
}
- have[x.Old] = true
+ haveReplace[x.Old] = true
}
var repl []*Replace
for _, x := range f.Replace {
@@ -835,6 +1000,9 @@ func (f *File) removeDups() {
}
f.Replace = repl
+ // Duplicate require and retract directives are not removed.
+
+ // Drop killed statements from the syntax tree.
var stmts []Expr
for _, stmt := range f.Syntax.Stmt {
switch stmt := stmt.(type) {
@@ -858,3 +1026,38 @@ func (f *File) removeDups() {
}
f.Syntax.Stmt = stmts
}
+
+// lineLess returns whether li should be sorted before lj. It sorts
+// lexicographically without assigning any special meaning to tokens.
+func lineLess(li, lj *Line) bool {
+ for k := 0; k < len(li.Token) && k < len(lj.Token); k++ {
+ if li.Token[k] != lj.Token[k] {
+ return li.Token[k] < lj.Token[k]
+ }
+ }
+ return len(li.Token) < len(lj.Token)
+}
+
+// lineRetractLess returns whether li should be sorted before lj for lines in
+// a "retract" block. It treats each line as a version interval. Single versions
+// are compared as if they were intervals with the same low and high version.
+// Intervals are sorted in descending order, first by low version, then by
+// high version, using semver.Compare.
+func lineRetractLess(li, lj *Line) bool {
+ interval := func(l *Line) VersionInterval {
+ if len(l.Token) == 1 {
+ return VersionInterval{Low: l.Token[0], High: l.Token[0]}
+ } else if len(l.Token) == 5 && l.Token[0] == "[" && l.Token[2] == "," && l.Token[4] == "]" {
+ return VersionInterval{Low: l.Token[1], High: l.Token[3]}
+ } else {
+ // Line in unknown format. Treat as an invalid version.
+ return VersionInterval{}
+ }
+ }
+ vii := interval(li)
+ vij := interval(lj)
+ if cmp := semver.Compare(vii.Low, vij.Low); cmp != 0 {
+ return cmp > 0
+ }
+ return semver.Compare(vii.High, vij.High) > 0
+}
diff --git a/src/cmd/vendor/golang.org/x/mod/module/module.go b/src/cmd/vendor/golang.org/x/mod/module/module.go
index 3a8b080c7b..c1c5263c42 100644
--- a/src/cmd/vendor/golang.org/x/mod/module/module.go
+++ b/src/cmd/vendor/golang.org/x/mod/module/module.go
@@ -225,13 +225,13 @@ func firstPathOK(r rune) bool {
}
// pathOK reports whether r can appear in an import path element.
-// Paths can be ASCII letters, ASCII digits, and limited ASCII punctuation: + - . _ and ~.
+// Paths can be ASCII letters, ASCII digits, and limited ASCII punctuation: - . _ and ~.
// This matches what "go get" has historically recognized in import paths.
// TODO(rsc): We would like to allow Unicode letters, but that requires additional
// care in the safe encoding (see "escaped paths" above).
func pathOK(r rune) bool {
if r < utf8.RuneSelf {
- return r == '+' || r == '-' || r == '.' || r == '_' || r == '~' ||
+ return r == '-' || r == '.' || r == '_' || r == '~' ||
'0' <= r && r <= '9' ||
'A' <= r && r <= 'Z' ||
'a' <= r && r <= 'z'
@@ -314,11 +314,13 @@ func CheckPath(path string) error {
// separated by slashes (U+002F). (It must not begin with nor end in a slash.)
//
// A valid path element is a non-empty string made up of
-// ASCII letters, ASCII digits, and limited ASCII punctuation: + - . _ and ~.
+// ASCII letters, ASCII digits, and limited ASCII punctuation: - . _ and ~.
// It must not begin or end with a dot (U+002E), nor contain two dots in a row.
//
// The element prefix up to the first dot must not be a reserved file name
-// on Windows, regardless of case (CON, com1, NuL, and so on).
+// on Windows, regardless of case (CON, com1, NuL, and so on). The element
+// must not have a suffix of a tilde followed by one or more ASCII digits
+// (to exclude paths elements that look like Windows short-names).
//
// CheckImportPath may be less restrictive in the future, but see the
// top-level package documentation for additional information about
@@ -403,6 +405,29 @@ func checkElem(elem string, fileName bool) error {
return fmt.Errorf("%q disallowed as path element component on Windows", short)
}
}
+
+ if fileName {
+ // don't check for Windows short-names in file names. They're
+ // only an issue for import paths.
+ return nil
+ }
+
+ // Reject path components that look like Windows short-names.
+ // Those usually end in a tilde followed by one or more ASCII digits.
+ if tilde := strings.LastIndexByte(short, '~'); tilde >= 0 && tilde < len(short)-1 {
+ suffix := short[tilde+1:]
+ suffixIsDigits := true
+ for _, r := range suffix {
+ if r < '0' || r > '9' {
+ suffixIsDigits = false
+ break
+ }
+ }
+ if suffixIsDigits {
+ return fmt.Errorf("trailing tilde and digits in path element")
+ }
+ }
+
return nil
}
diff --git a/src/cmd/vendor/golang.org/x/mod/zip/zip.go b/src/cmd/vendor/golang.org/x/mod/zip/zip.go
index 6865895b3d..5b401ad4d8 100644
--- a/src/cmd/vendor/golang.org/x/mod/zip/zip.go
+++ b/src/cmd/vendor/golang.org/x/mod/zip/zip.go
@@ -48,6 +48,7 @@ package zip
import (
"archive/zip"
"bytes"
+ "errors"
"fmt"
"io"
"io/ioutil"
@@ -92,40 +93,134 @@ type File interface {
Open() (io.ReadCloser, error)
}
-// Create builds a zip archive for module m from an abstract list of files
-// and writes it to w.
+// CheckedFiles reports whether a set of files satisfy the name and size
+// constraints required by module zip files. The constraints are listed in the
+// package documentation.
//
-// Create verifies the restrictions described in the package documentation
-// and should not produce an archive that Unzip cannot extract. Create does not
-// include files in the output archive if they don't belong in the module zip.
-// In particular, Create will not include files in modules found in
-// subdirectories, most files in vendor directories, or irregular files (such
-// as symbolic links) in the output archive.
-func Create(w io.Writer, m module.Version, files []File) (err error) {
- defer func() {
- if err != nil {
- err = &zipError{verb: "create zip", err: err}
- }
- }()
+// Functions that produce this report may include slightly different sets of
+// files. See documentation for CheckFiles, CheckDir, and CheckZip for details.
+type CheckedFiles struct {
+ // Valid is a list of file paths that should be included in a zip file.
+ Valid []string
+
+ // Omitted is a list of files that are ignored when creating a module zip
+ // file, along with the reason each file is ignored.
+ Omitted []FileError
+
+ // Invalid is a list of files that should not be included in a module zip
+ // file, along with the reason each file is invalid.
+ Invalid []FileError
+
+ // SizeError is non-nil if the total uncompressed size of the valid files
+ // exceeds the module zip size limit or if the zip file itself exceeds the
+ // limit.
+ SizeError error
+}
- // Check that the version is canonical, the module path is well-formed, and
- // the major version suffix matches the major version.
- if vers := module.CanonicalVersion(m.Version); vers != m.Version {
- return fmt.Errorf("version %q is not canonical (should be %q)", m.Version, vers)
+// Err returns an error if CheckedFiles does not describe a valid module zip
+// file. SizeError is returned if that field is set. A FileErrorList is returned
+// if there are one or more invalid files. Other errors may be returned in the
+// future.
+func (cf CheckedFiles) Err() error {
+ if cf.SizeError != nil {
+ return cf.SizeError
}
- if err := module.Check(m.Path, m.Version); err != nil {
- return err
+ if len(cf.Invalid) > 0 {
+ return FileErrorList(cf.Invalid)
+ }
+ return nil
+}
+
+type FileErrorList []FileError
+
+func (el FileErrorList) Error() string {
+ buf := &strings.Builder{}
+ sep := ""
+ for _, e := range el {
+ buf.WriteString(sep)
+ buf.WriteString(e.Error())
+ sep = "\n"
+ }
+ return buf.String()
+}
+
+type FileError struct {
+ Path string
+ Err error
+}
+
+func (e FileError) Error() string {
+ return fmt.Sprintf("%s: %s", e.Path, e.Err)
+}
+
+func (e FileError) Unwrap() error {
+ return e.Err
+}
+
+var (
+ // Predefined error messages for invalid files. Not exhaustive.
+ errPathNotClean = errors.New("file path is not clean")
+ errPathNotRelative = errors.New("file path is not relative")
+ errGoModCase = errors.New("go.mod files must have lowercase names")
+ errGoModSize = fmt.Errorf("go.mod file too large (max size is %d bytes)", MaxGoMod)
+ errLICENSESize = fmt.Errorf("LICENSE file too large (max size is %d bytes)", MaxLICENSE)
+
+ // Predefined error messages for omitted files. Not exhaustive.
+ errVCS = errors.New("directory is a version control repository")
+ errVendored = errors.New("file is in vendor directory")
+ errSubmoduleFile = errors.New("file is in another module")
+ errSubmoduleDir = errors.New("directory is in another module")
+ errHgArchivalTxt = errors.New("file is inserted by 'hg archive' and is always omitted")
+ errSymlink = errors.New("file is a symbolic link")
+ errNotRegular = errors.New("not a regular file")
+)
+
+// CheckFiles reports whether a list of files satisfy the name and size
+// constraints listed in the package documentation. The returned CheckedFiles
+// record contains lists of valid, invalid, and omitted files. Every file in
+// the given list will be included in exactly one of those lists.
+//
+// CheckFiles returns an error if the returned CheckedFiles does not describe
+// a valid module zip file (according to CheckedFiles.Err). The returned
+// CheckedFiles is still populated when an error is returned.
+//
+// Note that CheckFiles will not open any files, so Create may still fail when
+// CheckFiles is successful due to I/O errors and reported size differences.
+func CheckFiles(files []File) (CheckedFiles, error) {
+ cf, _, _ := checkFiles(files)
+ return cf, cf.Err()
+}
+
+// checkFiles implements CheckFiles and also returns lists of valid files and
+// their sizes, corresponding to cf.Valid. These lists are used in Crewate to
+// avoid repeated calls to File.Lstat.
+func checkFiles(files []File) (cf CheckedFiles, validFiles []File, validSizes []int64) {
+ errPaths := make(map[string]struct{})
+ addError := func(path string, omitted bool, err error) {
+ if _, ok := errPaths[path]; ok {
+ return
+ }
+ errPaths[path] = struct{}{}
+ fe := FileError{Path: path, Err: err}
+ if omitted {
+ cf.Omitted = append(cf.Omitted, fe)
+ } else {
+ cf.Invalid = append(cf.Invalid, fe)
+ }
}
// Find directories containing go.mod files (other than the root).
+ // Files in these directories will be omitted.
// These directories will not be included in the output zip.
haveGoMod := make(map[string]bool)
for _, f := range files {
- dir, base := path.Split(f.Path())
+ p := f.Path()
+ dir, base := path.Split(p)
if strings.EqualFold(base, "go.mod") {
info, err := f.Lstat()
if err != nil {
- return err
+ addError(p, false, err)
+ continue
}
if info.Mode().IsRegular() {
haveGoMod[dir] = true
@@ -146,77 +241,292 @@ func Create(w io.Writer, m module.Version, files []File) (err error) {
}
}
- // Create the module zip file.
- zw := zip.NewWriter(w)
- prefix := fmt.Sprintf("%s@%s/", m.Path, m.Version)
-
- addFile := func(f File, path string, size int64) error {
- rc, err := f.Open()
- if err != nil {
- return err
- }
- defer rc.Close()
- w, err := zw.Create(prefix + path)
- if err != nil {
- return err
- }
- lr := &io.LimitedReader{R: rc, N: size + 1}
- if _, err := io.Copy(w, lr); err != nil {
- return err
- }
- if lr.N <= 0 {
- return fmt.Errorf("file %q is larger than declared size", path)
- }
- return nil
- }
-
collisions := make(collisionChecker)
maxSize := int64(MaxZipFile)
for _, f := range files {
p := f.Path()
if p != path.Clean(p) {
- return fmt.Errorf("file path %s is not clean", p)
+ addError(p, false, errPathNotClean)
+ continue
}
if path.IsAbs(p) {
- return fmt.Errorf("file path %s is not relative", p)
+ addError(p, false, errPathNotRelative)
+ continue
+ }
+ if isVendoredPackage(p) {
+ addError(p, true, errVendored)
+ continue
}
- if isVendoredPackage(p) || inSubmodule(p) {
+ if inSubmodule(p) {
+ addError(p, true, errSubmoduleFile)
continue
}
if p == ".hg_archival.txt" {
// Inserted by hg archive.
// The go command drops this regardless of the VCS being used.
+ addError(p, true, errHgArchivalTxt)
continue
}
if err := module.CheckFilePath(p); err != nil {
- return err
+ addError(p, false, err)
+ continue
}
if strings.ToLower(p) == "go.mod" && p != "go.mod" {
- return fmt.Errorf("found file named %s, want all lower-case go.mod", p)
+ addError(p, false, errGoModCase)
+ continue
}
info, err := f.Lstat()
if err != nil {
- return err
+ addError(p, false, err)
+ continue
}
if err := collisions.check(p, info.IsDir()); err != nil {
- return err
+ addError(p, false, err)
+ continue
}
- if !info.Mode().IsRegular() {
+ if info.Mode()&os.ModeType == os.ModeSymlink {
// Skip symbolic links (golang.org/issue/27093).
+ addError(p, true, errSymlink)
+ continue
+ }
+ if !info.Mode().IsRegular() {
+ addError(p, true, errNotRegular)
continue
}
size := info.Size()
- if size < 0 || maxSize < size {
- return fmt.Errorf("module source tree too large (max size is %d bytes)", MaxZipFile)
+ if size >= 0 && size <= maxSize {
+ maxSize -= size
+ } else if cf.SizeError == nil {
+ cf.SizeError = fmt.Errorf("module source tree too large (max size is %d bytes)", MaxZipFile)
}
- maxSize -= size
if p == "go.mod" && size > MaxGoMod {
- return fmt.Errorf("go.mod file too large (max size is %d bytes)", MaxGoMod)
+ addError(p, false, errGoModSize)
+ continue
}
if p == "LICENSE" && size > MaxLICENSE {
- return fmt.Errorf("LICENSE file too large (max size is %d bytes)", MaxLICENSE)
+ addError(p, false, errLICENSESize)
+ continue
+ }
+
+ cf.Valid = append(cf.Valid, p)
+ validFiles = append(validFiles, f)
+ validSizes = append(validSizes, info.Size())
+ }
+
+ return cf, validFiles, validSizes
+}
+
+// CheckDir reports whether the files in dir satisfy the name and size
+// constraints listed in the package documentation. The returned CheckedFiles
+// record contains lists of valid, invalid, and omitted files. If a directory is
+// omitted (for example, a nested module or vendor directory), it will appear in
+// the omitted list, but its files won't be listed.
+//
+// CheckDir returns an error if it encounters an I/O error or if the returned
+// CheckedFiles does not describe a valid module zip file (according to
+// CheckedFiles.Err). The returned CheckedFiles is still populated when such
+// an error is returned.
+//
+// Note that CheckDir will not open any files, so CreateFromDir may still fail
+// when CheckDir is successful due to I/O errors.
+func CheckDir(dir string) (CheckedFiles, error) {
+ // List files (as CreateFromDir would) and check which ones are omitted
+ // or invalid.
+ files, omitted, err := listFilesInDir(dir)
+ if err != nil {
+ return CheckedFiles{}, err
+ }
+ cf, cfErr := CheckFiles(files)
+ _ = cfErr // ignore this error; we'll generate our own after rewriting paths.
+
+ // Replace all paths with file system paths.
+ // Paths returned by CheckFiles will be slash-separated paths relative to dir.
+ // That's probably not appropriate for error messages.
+ for i := range cf.Valid {
+ cf.Valid[i] = filepath.Join(dir, cf.Valid[i])
+ }
+ cf.Omitted = append(cf.Omitted, omitted...)
+ for i := range cf.Omitted {
+ cf.Omitted[i].Path = filepath.Join(dir, cf.Omitted[i].Path)
+ }
+ for i := range cf.Invalid {
+ cf.Invalid[i].Path = filepath.Join(dir, cf.Invalid[i].Path)
+ }
+ return cf, cf.Err()
+}
+
+// CheckZip reports whether the files contained in a zip file satisfy the name
+// and size constraints listed in the package documentation.
+//
+// CheckZip returns an error if the returned CheckedFiles does not describe
+// a valid module zip file (according to CheckedFiles.Err). The returned
+// CheckedFiles is still populated when an error is returned. CheckZip will
+// also return an error if the module path or version is malformed or if it
+// encounters an error reading the zip file.
+//
+// Note that CheckZip does not read individual files, so Unzip may still fail
+// when CheckZip is successful due to I/O errors.
+func CheckZip(m module.Version, zipFile string) (CheckedFiles, error) {
+ f, err := os.Open(zipFile)
+ if err != nil {
+ return CheckedFiles{}, err
+ }
+ defer f.Close()
+ _, cf, err := checkZip(m, f)
+ return cf, err
+}
+
+// checkZip implements checkZip and also returns the *zip.Reader. This is
+// used in Unzip to avoid redundant I/O.
+func checkZip(m module.Version, f *os.File) (*zip.Reader, CheckedFiles, error) {
+ // Make sure the module path and version are valid.
+ if vers := module.CanonicalVersion(m.Version); vers != m.Version {
+ return nil, CheckedFiles{}, fmt.Errorf("version %q is not canonical (should be %q)", m.Version, vers)
+ }
+ if err := module.Check(m.Path, m.Version); err != nil {
+ return nil, CheckedFiles{}, err
+ }
+
+ // Check the total file size.
+ info, err := f.Stat()
+ if err != nil {
+ return nil, CheckedFiles{}, err
+ }
+ zipSize := info.Size()
+ if zipSize > MaxZipFile {
+ cf := CheckedFiles{SizeError: fmt.Errorf("module zip file is too large (%d bytes; limit is %d bytes)", zipSize, MaxZipFile)}
+ return nil, cf, cf.Err()
+ }
+
+ // Check for valid file names, collisions.
+ var cf CheckedFiles
+ addError := func(zf *zip.File, err error) {
+ cf.Invalid = append(cf.Invalid, FileError{Path: zf.Name, Err: err})
+ }
+ z, err := zip.NewReader(f, zipSize)
+ if err != nil {
+ return nil, CheckedFiles{}, err
+ }
+ prefix := fmt.Sprintf("%s@%s/", m.Path, m.Version)
+ collisions := make(collisionChecker)
+ var size int64
+ for _, zf := range z.File {
+ if !strings.HasPrefix(zf.Name, prefix) {
+ addError(zf, fmt.Errorf("path does not have prefix %q", prefix))
+ continue
}
+ name := zf.Name[len(prefix):]
+ if name == "" {
+ continue
+ }
+ isDir := strings.HasSuffix(name, "/")
+ if isDir {
+ name = name[:len(name)-1]
+ }
+ if path.Clean(name) != name {
+ addError(zf, errPathNotClean)
+ continue
+ }
+ if err := module.CheckFilePath(name); err != nil {
+ addError(zf, err)
+ continue
+ }
+ if err := collisions.check(name, isDir); err != nil {
+ addError(zf, err)
+ continue
+ }
+ if isDir {
+ continue
+ }
+ if base := path.Base(name); strings.EqualFold(base, "go.mod") {
+ if base != name {
+ addError(zf, fmt.Errorf("go.mod file not in module root directory"))
+ continue
+ }
+ if name != "go.mod" {
+ addError(zf, errGoModCase)
+ continue
+ }
+ }
+ sz := int64(zf.UncompressedSize64)
+ if sz >= 0 && MaxZipFile-size >= sz {
+ size += sz
+ } else if cf.SizeError == nil {
+ cf.SizeError = fmt.Errorf("total uncompressed size of module contents too large (max size is %d bytes)", MaxZipFile)
+ }
+ if name == "go.mod" && sz > MaxGoMod {
+ addError(zf, fmt.Errorf("go.mod file too large (max size is %d bytes)", MaxGoMod))
+ continue
+ }
+ if name == "LICENSE" && sz > MaxLICENSE {
+ addError(zf, fmt.Errorf("LICENSE file too large (max size is %d bytes)", MaxLICENSE))
+ continue
+ }
+ cf.Valid = append(cf.Valid, zf.Name)
+ }
+ return z, cf, cf.Err()
+}
+
+// Create builds a zip archive for module m from an abstract list of files
+// and writes it to w.
+//
+// Create verifies the restrictions described in the package documentation
+// and should not produce an archive that Unzip cannot extract. Create does not
+// include files in the output archive if they don't belong in the module zip.
+// In particular, Create will not include files in modules found in
+// subdirectories, most files in vendor directories, or irregular files (such
+// as symbolic links) in the output archive.
+func Create(w io.Writer, m module.Version, files []File) (err error) {
+ defer func() {
+ if err != nil {
+ err = &zipError{verb: "create zip", err: err}
+ }
+ }()
+
+ // Check that the version is canonical, the module path is well-formed, and
+ // the major version suffix matches the major version.
+ if vers := module.CanonicalVersion(m.Version); vers != m.Version {
+ return fmt.Errorf("version %q is not canonical (should be %q)", m.Version, vers)
+ }
+ if err := module.Check(m.Path, m.Version); err != nil {
+ return err
+ }
+
+ // Check whether files are valid, not valid, or should be omitted.
+ // Also check that the valid files don't exceed the maximum size.
+ cf, validFiles, validSizes := checkFiles(files)
+ if err := cf.Err(); err != nil {
+ return err
+ }
+
+ // Create the module zip file.
+ zw := zip.NewWriter(w)
+ prefix := fmt.Sprintf("%s@%s/", m.Path, m.Version)
+
+ addFile := func(f File, path string, size int64) error {
+ rc, err := f.Open()
+ if err != nil {
+ return err
+ }
+ defer rc.Close()
+ w, err := zw.Create(prefix + path)
+ if err != nil {
+ return err
+ }
+ lr := &io.LimitedReader{R: rc, N: size + 1}
+ if _, err := io.Copy(w, lr); err != nil {
+ return err
+ }
+ if lr.N <= 0 {
+ return fmt.Errorf("file %q is larger than declared size", path)
+ }
+ return nil
+ }
+
+ for i, f := range validFiles {
+ p := f.Path()
+ size := validSizes[i]
if err := addFile(f, p, size); err != nil {
return err
}
@@ -245,61 +555,7 @@ func CreateFromDir(w io.Writer, m module.Version, dir string) (err error) {
}
}()
- var files []File
- err = filepath.Walk(dir, func(filePath string, info os.FileInfo, err error) error {
- if err != nil {
- return err
- }
- relPath, err := filepath.Rel(dir, filePath)
- if err != nil {
- return err
- }
- slashPath := filepath.ToSlash(relPath)
-
- if info.IsDir() {
- if filePath == dir {
- // Don't skip the top-level directory.
- return nil
- }
-
- // Skip VCS directories.
- // fossil repos are regular files with arbitrary names, so we don't try
- // to exclude them.
- switch filepath.Base(filePath) {
- case ".bzr", ".git", ".hg", ".svn":
- return filepath.SkipDir
- }
-
- // Skip some subdirectories inside vendor, but maintain bug
- // golang.org/issue/31562, described in isVendoredPackage.
- // We would like Create and CreateFromDir to produce the same result
- // for a set of files, whether expressed as a directory tree or zip.
- if isVendoredPackage(slashPath) {
- return filepath.SkipDir
- }
-
- // Skip submodules (directories containing go.mod files).
- if goModInfo, err := os.Lstat(filepath.Join(filePath, "go.mod")); err == nil && !goModInfo.IsDir() {
- return filepath.SkipDir
- }
- return nil
- }
-
- if info.Mode().IsRegular() {
- if !isVendoredPackage(slashPath) {
- files = append(files, dirFile{
- filePath: filePath,
- slashPath: slashPath,
- info: info,
- })
- }
- return nil
- }
-
- // Not a regular file or a directory. Probably a symbolic link.
- // Irregular files are ignored, so skip it.
- return nil
- })
+ files, _, err := listFilesInDir(dir)
if err != nil {
return err
}
@@ -356,89 +612,28 @@ func Unzip(dir string, m module.Version, zipFile string) (err error) {
}
}()
- if vers := module.CanonicalVersion(m.Version); vers != m.Version {
- return fmt.Errorf("version %q is not canonical (should be %q)", m.Version, vers)
- }
- if err := module.Check(m.Path, m.Version); err != nil {
- return err
- }
-
// Check that the directory is empty. Don't create it yet in case there's
// an error reading the zip.
- files, _ := ioutil.ReadDir(dir)
- if len(files) > 0 {
+ if files, _ := ioutil.ReadDir(dir); len(files) > 0 {
return fmt.Errorf("target directory %v exists and is not empty", dir)
}
- // Open the zip file and ensure it's under the size limit.
+ // Open the zip and check that it satisfies all restrictions.
f, err := os.Open(zipFile)
if err != nil {
return err
}
defer f.Close()
- info, err := f.Stat()
+ z, cf, err := checkZip(m, f)
if err != nil {
return err
}
- zipSize := info.Size()
- if zipSize > MaxZipFile {
- return fmt.Errorf("module zip file is too large (%d bytes; limit is %d bytes)", zipSize, MaxZipFile)
- }
-
- z, err := zip.NewReader(f, zipSize)
- if err != nil {
+ if err := cf.Err(); err != nil {
return err
}
- // Check total size, valid file names.
- collisions := make(collisionChecker)
+ // Unzip, enforcing sizes declared in the zip file.
prefix := fmt.Sprintf("%s@%s/", m.Path, m.Version)
- var size int64
- for _, zf := range z.File {
- if !strings.HasPrefix(zf.Name, prefix) {
- return fmt.Errorf("unexpected file name %s", zf.Name)
- }
- name := zf.Name[len(prefix):]
- if name == "" {
- continue
- }
- isDir := strings.HasSuffix(name, "/")
- if isDir {
- name = name[:len(name)-1]
- }
- if path.Clean(name) != name {
- return fmt.Errorf("invalid file name %s", zf.Name)
- }
- if err := module.CheckFilePath(name); err != nil {
- return err
- }
- if err := collisions.check(name, isDir); err != nil {
- return err
- }
- if isDir {
- continue
- }
- if base := path.Base(name); strings.EqualFold(base, "go.mod") {
- if base != name {
- return fmt.Errorf("found go.mod file not in module root directory (%s)", zf.Name)
- } else if name != "go.mod" {
- return fmt.Errorf("found file named %s, want all lower-case go.mod", zf.Name)
- }
- }
- s := int64(zf.UncompressedSize64)
- if s < 0 || MaxZipFile-size < s {
- return fmt.Errorf("total uncompressed size of module contents too large (max size is %d bytes)", MaxZipFile)
- }
- size += s
- if name == "go.mod" && s > MaxGoMod {
- return fmt.Errorf("go.mod file too large (max size is %d bytes)", MaxGoMod)
- }
- if name == "LICENSE" && s > MaxLICENSE {
- return fmt.Errorf("LICENSE file too large (max size is %d bytes)", MaxLICENSE)
- }
- }
-
- // Unzip, enforcing sizes checked earlier.
if err := os.MkdirAll(dir, 0777); err != nil {
return err
}
@@ -515,6 +710,72 @@ func (cc collisionChecker) check(p string, isDir bool) error {
return nil
}
+// listFilesInDir walks the directory tree rooted at dir and returns a list of
+// files, as well as a list of directories and files that were skipped (for
+// example, nested modules and symbolic links).
+func listFilesInDir(dir string) (files []File, omitted []FileError, err error) {
+ err = filepath.Walk(dir, func(filePath string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ relPath, err := filepath.Rel(dir, filePath)
+ if err != nil {
+ return err
+ }
+ slashPath := filepath.ToSlash(relPath)
+
+ // Skip some subdirectories inside vendor, but maintain bug
+ // golang.org/issue/31562, described in isVendoredPackage.
+ // We would like Create and CreateFromDir to produce the same result
+ // for a set of files, whether expressed as a directory tree or zip.
+ if isVendoredPackage(slashPath) {
+ omitted = append(omitted, FileError{Path: slashPath, Err: errVendored})
+ return nil
+ }
+
+ if info.IsDir() {
+ if filePath == dir {
+ // Don't skip the top-level directory.
+ return nil
+ }
+
+ // Skip VCS directories.
+ // fossil repos are regular files with arbitrary names, so we don't try
+ // to exclude them.
+ switch filepath.Base(filePath) {
+ case ".bzr", ".git", ".hg", ".svn":
+ omitted = append(omitted, FileError{Path: slashPath, Err: errVCS})
+ return filepath.SkipDir
+ }
+
+ // Skip submodules (directories containing go.mod files).
+ if goModInfo, err := os.Lstat(filepath.Join(filePath, "go.mod")); err == nil && !goModInfo.IsDir() {
+ omitted = append(omitted, FileError{Path: slashPath, Err: errSubmoduleDir})
+ return filepath.SkipDir
+ }
+ return nil
+ }
+
+ // Skip irregular files and files in vendor directories.
+ // Irregular files are ignored. They're typically symbolic links.
+ if !info.Mode().IsRegular() {
+ omitted = append(omitted, FileError{Path: slashPath, Err: errNotRegular})
+ return nil
+ }
+
+ files = append(files, dirFile{
+ filePath: filePath,
+ slashPath: slashPath,
+ info: info,
+ })
+ return nil
+ })
+ if err != nil {
+ return nil, nil, err
+ }
+ return files, omitted, nil
+}
+
type zipError struct {
verb, path string
err error
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go
index e09160379f..f0b15051c5 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go
@@ -116,7 +116,11 @@ func checkCanonicalFieldTag(pass *analysis.Pass, field *types.Var, tag string, s
}
for _, enc := range [...]string{"json", "xml"} {
- if reflect.StructTag(tag).Get(enc) != "" {
+ switch reflect.StructTag(tag).Get(enc) {
+ // Ignore warning if the field not exported and the tag is marked as
+ // ignored.
+ case "", "-":
+ default:
pass.Reportf(field.Pos(), "struct field %s has %s tag but is not exported", field.Name(), enc)
return
}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unmarshal/unmarshal.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unmarshal/unmarshal.go
index f9cc993cbb..92b37caff9 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unmarshal/unmarshal.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unmarshal/unmarshal.go
@@ -30,7 +30,7 @@ var Analyzer = &analysis.Analyzer{
func run(pass *analysis.Pass) (interface{}, error) {
switch pass.Pkg.Path() {
- case "encoding/gob", "encoding/json", "encoding/xml":
+ case "encoding/gob", "encoding/json", "encoding/xml", "encoding/asn1":
// These packages know how to use their own APIs.
// Sometimes they are testing what happens to incorrect programs.
return nil, nil
@@ -53,9 +53,10 @@ func run(pass *analysis.Pass) (interface{}, error) {
recv := fn.Type().(*types.Signature).Recv()
if fn.Name() == "Unmarshal" && recv == nil {
// "encoding/json".Unmarshal
- // "encoding/xml".Unmarshal
+ // "encoding/xml".Unmarshal
+ // "encoding/asn1".Unmarshal
switch fn.Pkg().Path() {
- case "encoding/json", "encoding/xml":
+ case "encoding/json", "encoding/xml", "encoding/asn1":
argidx = 1 // func([]byte, interface{})
}
} else if fn.Name() == "Decode" && recv != nil {
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go
index 76d4ab2382..bececee7e9 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go
@@ -44,7 +44,7 @@ var funcs, stringMethods stringSetFlag
func init() {
// TODO(adonovan): provide a comment syntax to allow users to
// add their functions to this set using facts.
- funcs.Set("errors.New,fmt.Errorf,fmt.Sprintf,fmt.Sprint,sort.Reverse")
+ funcs.Set("errors.New,fmt.Errorf,fmt.Sprintf,fmt.Sprint,sort.Reverse,context.WithValue,context.WithCancel,context.WithDeadline,context.WithTimeout")
Analyzer.Flags.Var(&funcs, "funcs",
"comma-separated list of functions whose results must be used")
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go b/src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go
index 26586810c7..01f6e829f7 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go
@@ -14,6 +14,12 @@ import (
"strings"
"golang.org/x/tools/go/ast/astutil"
+ "golang.org/x/tools/internal/lsp/fuzzy"
+)
+
+var (
+ GetTypeErrors func(p interface{}) []types.Error
+ SetTypeErrors func(p interface{}, errors []types.Error)
)
func TypeErrorEndPos(fset *token.FileSet, src []byte, start token.Pos) token.Pos {
@@ -45,32 +51,34 @@ func ZeroValue(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.T
default:
panic("unknown basic type")
}
- case *types.Chan, *types.Interface, *types.Map, *types.Pointer, *types.Signature, *types.Slice:
+ case *types.Chan, *types.Interface, *types.Map, *types.Pointer, *types.Signature, *types.Slice, *types.Array:
return ast.NewIdent("nil")
case *types.Struct:
- texpr := typeExpr(fset, f, pkg, typ) // typ because we want the name here.
+ texpr := TypeExpr(fset, f, pkg, typ) // typ because we want the name here.
if texpr == nil {
return nil
}
return &ast.CompositeLit{
Type: texpr,
}
- case *types.Array:
- texpr := typeExpr(fset, f, pkg, u.Elem())
- if texpr == nil {
- return nil
- }
- return &ast.CompositeLit{
- Type: &ast.ArrayType{
- Elt: texpr,
- Len: &ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf("%v", u.Len())},
- },
- }
}
return nil
}
-func typeExpr(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Type) ast.Expr {
+// IsZeroValue checks whether the given expression is a 'zero value' (as determined by output of
+// analysisinternal.ZeroValue)
+func IsZeroValue(expr ast.Expr) bool {
+ switch e := expr.(type) {
+ case *ast.BasicLit:
+ return e.Value == "0" || e.Value == `""`
+ case *ast.Ident:
+ return e.Name == "nil" || e.Name == "false"
+ default:
+ return false
+ }
+}
+
+func TypeExpr(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Type) ast.Expr {
switch t := typ.(type) {
case *types.Basic:
switch t.Kind() {
@@ -79,7 +87,96 @@ func typeExpr(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Ty
default:
return ast.NewIdent(t.Name())
}
+ case *types.Pointer:
+ x := TypeExpr(fset, f, pkg, t.Elem())
+ if x == nil {
+ return nil
+ }
+ return &ast.UnaryExpr{
+ Op: token.MUL,
+ X: x,
+ }
+ case *types.Array:
+ elt := TypeExpr(fset, f, pkg, t.Elem())
+ if elt == nil {
+ return nil
+ }
+ return &ast.ArrayType{
+ Len: &ast.BasicLit{
+ Kind: token.INT,
+ Value: fmt.Sprintf("%d", t.Len()),
+ },
+ Elt: elt,
+ }
+ case *types.Slice:
+ elt := TypeExpr(fset, f, pkg, t.Elem())
+ if elt == nil {
+ return nil
+ }
+ return &ast.ArrayType{
+ Elt: elt,
+ }
+ case *types.Map:
+ key := TypeExpr(fset, f, pkg, t.Key())
+ value := TypeExpr(fset, f, pkg, t.Elem())
+ if key == nil || value == nil {
+ return nil
+ }
+ return &ast.MapType{
+ Key: key,
+ Value: value,
+ }
+ case *types.Chan:
+ dir := ast.ChanDir(t.Dir())
+ if t.Dir() == types.SendRecv {
+ dir = ast.SEND | ast.RECV
+ }
+ value := TypeExpr(fset, f, pkg, t.Elem())
+ if value == nil {
+ return nil
+ }
+ return &ast.ChanType{
+ Dir: dir,
+ Value: value,
+ }
+ case *types.Signature:
+ var params []*ast.Field
+ for i := 0; i < t.Params().Len(); i++ {
+ p := TypeExpr(fset, f, pkg, t.Params().At(i).Type())
+ if p == nil {
+ return nil
+ }
+ params = append(params, &ast.Field{
+ Type: p,
+ Names: []*ast.Ident{
+ {
+ Name: t.Params().At(i).Name(),
+ },
+ },
+ })
+ }
+ var returns []*ast.Field
+ for i := 0; i < t.Results().Len(); i++ {
+ r := TypeExpr(fset, f, pkg, t.Results().At(i).Type())
+ if r == nil {
+ return nil
+ }
+ returns = append(returns, &ast.Field{
+ Type: r,
+ })
+ }
+ return &ast.FuncType{
+ Params: &ast.FieldList{
+ List: params,
+ },
+ Results: &ast.FieldList{
+ List: returns,
+ },
+ }
case *types.Named:
+ if t.Obj().Pkg() == nil {
+ return ast.NewIdent(t.Obj().Name())
+ }
if t.Obj().Pkg() == pkg {
return ast.NewIdent(t.Obj().Name())
}
@@ -101,14 +198,15 @@ func typeExpr(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Ty
X: ast.NewIdent(pkgName),
Sel: ast.NewIdent(t.Obj().Name()),
}
+ case *types.Struct:
+ return ast.NewIdent(t.String())
+ case *types.Interface:
+ return ast.NewIdent(t.String())
default:
- return nil // TODO: anonymous structs, but who does that
+ return nil
}
}
-var GetTypeErrors = func(p interface{}) []types.Error { return nil }
-var SetTypeErrors = func(p interface{}, errors []types.Error) {}
-
type TypeErrorPass string
const (
@@ -116,3 +214,212 @@ const (
NoResultValues TypeErrorPass = "noresultvalues"
UndeclaredName TypeErrorPass = "undeclaredname"
)
+
+// StmtToInsertVarBefore returns the ast.Stmt before which we can safely insert a new variable.
+// Some examples:
+//
+// Basic Example:
+// z := 1
+// y := z + x
+// If x is undeclared, then this function would return `y := z + x`, so that we
+// can insert `x := ` on the line before `y := z + x`.
+//
+// If stmt example:
+// if z == 1 {
+// } else if z == y {}
+// If y is undeclared, then this function would return `if z == 1 {`, because we cannot
+// insert a statement between an if and an else if statement. As a result, we need to find
+// the top of the if chain to insert `y := ` before.
+func StmtToInsertVarBefore(path []ast.Node) ast.Stmt {
+ enclosingIndex := -1
+ for i, p := range path {
+ if _, ok := p.(ast.Stmt); ok {
+ enclosingIndex = i
+ break
+ }
+ }
+ if enclosingIndex == -1 {
+ return nil
+ }
+ enclosingStmt := path[enclosingIndex]
+ switch enclosingStmt.(type) {
+ case *ast.IfStmt:
+ // The enclosingStmt is inside of the if declaration,
+ // We need to check if we are in an else-if stmt and
+ // get the base if statement.
+ return baseIfStmt(path, enclosingIndex)
+ case *ast.CaseClause:
+ // Get the enclosing switch stmt if the enclosingStmt is
+ // inside of the case statement.
+ for i := enclosingIndex + 1; i < len(path); i++ {
+ if node, ok := path[i].(*ast.SwitchStmt); ok {
+ return node
+ } else if node, ok := path[i].(*ast.TypeSwitchStmt); ok {
+ return node
+ }
+ }
+ }
+ if len(path) <= enclosingIndex+1 {
+ return enclosingStmt.(ast.Stmt)
+ }
+ // Check if the enclosing statement is inside another node.
+ switch expr := path[enclosingIndex+1].(type) {
+ case *ast.IfStmt:
+ // Get the base if statement.
+ return baseIfStmt(path, enclosingIndex+1)
+ case *ast.ForStmt:
+ if expr.Init == enclosingStmt || expr.Post == enclosingStmt {
+ return expr
+ }
+ }
+ return enclosingStmt.(ast.Stmt)
+}
+
+// baseIfStmt walks up the if/else-if chain until we get to
+// the top of the current if chain.
+func baseIfStmt(path []ast.Node, index int) ast.Stmt {
+ stmt := path[index]
+ for i := index + 1; i < len(path); i++ {
+ if node, ok := path[i].(*ast.IfStmt); ok && node.Else == stmt {
+ stmt = node
+ continue
+ }
+ break
+ }
+ return stmt.(ast.Stmt)
+}
+
+// WalkASTWithParent walks the AST rooted at n. The semantics are
+// similar to ast.Inspect except it does not call f(nil).
+func WalkASTWithParent(n ast.Node, f func(n ast.Node, parent ast.Node) bool) {
+ var ancestors []ast.Node
+ ast.Inspect(n, func(n ast.Node) (recurse bool) {
+ if n == nil {
+ ancestors = ancestors[:len(ancestors)-1]
+ return false
+ }
+
+ var parent ast.Node
+ if len(ancestors) > 0 {
+ parent = ancestors[len(ancestors)-1]
+ }
+ ancestors = append(ancestors, n)
+ return f(n, parent)
+ })
+}
+
+// FindMatchingIdents finds all identifiers in 'node' that match any of the given types.
+// 'pos' represents the position at which the identifiers may be inserted. 'pos' must be within
+// the scope of each of identifier we select. Otherwise, we will insert a variable at 'pos' that
+// is unrecognized.
+func FindMatchingIdents(typs []types.Type, node ast.Node, pos token.Pos, info *types.Info, pkg *types.Package) map[types.Type][]*ast.Ident {
+ matches := map[types.Type][]*ast.Ident{}
+ // Initialize matches to contain the variable types we are searching for.
+ for _, typ := range typs {
+ if typ == nil {
+ continue
+ }
+ matches[typ] = []*ast.Ident{}
+ }
+ seen := map[types.Object]struct{}{}
+ ast.Inspect(node, func(n ast.Node) bool {
+ if n == nil {
+ return false
+ }
+ // Prevent circular definitions. If 'pos' is within an assignment statement, do not
+ // allow any identifiers in that assignment statement to be selected. Otherwise,
+ // we could do the following, where 'x' satisfies the type of 'f0':
+ //
+ // x := fakeStruct{f0: x}
+ //
+ assignment, ok := n.(*ast.AssignStmt)
+ if ok && pos > assignment.Pos() && pos <= assignment.End() {
+ return false
+ }
+ if n.End() > pos {
+ return n.Pos() <= pos
+ }
+ ident, ok := n.(*ast.Ident)
+ if !ok || ident.Name == "_" {
+ return true
+ }
+ obj := info.Defs[ident]
+ if obj == nil || obj.Type() == nil {
+ return true
+ }
+ if _, ok := obj.(*types.TypeName); ok {
+ return true
+ }
+ // Prevent duplicates in matches' values.
+ if _, ok = seen[obj]; ok {
+ return true
+ }
+ seen[obj] = struct{}{}
+ // Find the scope for the given position. Then, check whether the object
+ // exists within the scope.
+ innerScope := pkg.Scope().Innermost(pos)
+ if innerScope == nil {
+ return true
+ }
+ _, foundObj := innerScope.LookupParent(ident.Name, pos)
+ if foundObj != obj {
+ return true
+ }
+ // The object must match one of the types that we are searching for.
+ if idents, ok := matches[obj.Type()]; ok {
+ matches[obj.Type()] = append(idents, ast.NewIdent(ident.Name))
+ }
+ // If the object type does not exactly match any of the target types, greedily
+ // find the first target type that the object type can satisfy.
+ for typ := range matches {
+ if obj.Type() == typ {
+ continue
+ }
+ if equivalentTypes(obj.Type(), typ) {
+ matches[typ] = append(matches[typ], ast.NewIdent(ident.Name))
+ }
+ }
+ return true
+ })
+ return matches
+}
+
+func equivalentTypes(want, got types.Type) bool {
+ if want == got || types.Identical(want, got) {
+ return true
+ }
+ // Code segment to help check for untyped equality from (golang/go#32146).
+ if rhs, ok := want.(*types.Basic); ok && rhs.Info()&types.IsUntyped > 0 {
+ if lhs, ok := got.Underlying().(*types.Basic); ok {
+ return rhs.Info()&types.IsConstType == lhs.Info()&types.IsConstType
+ }
+ }
+ return types.AssignableTo(want, got)
+}
+
+// FindBestMatch employs fuzzy matching to evaluate the similarity of each given identifier to the
+// given pattern. We return the identifier whose name is most similar to the pattern.
+func FindBestMatch(pattern string, idents []*ast.Ident) ast.Expr {
+ fuzz := fuzzy.NewMatcher(pattern)
+ var bestFuzz ast.Expr
+ highScore := float32(0) // minimum score is 0 (no match)
+ for _, ident := range idents {
+ // TODO: Improve scoring algorithm.
+ score := fuzz.Score(ident.Name)
+ if score > highScore {
+ highScore = score
+ bestFuzz = ident
+ } else if score == 0 {
+ // Order matters in the fuzzy matching algorithm. If we find no match
+ // when matching the target to the identifier, try matching the identifier
+ // to the target.
+ revFuzz := fuzzy.NewMatcher(ident.Name)
+ revScore := revFuzz.Score(pattern)
+ if revScore > highScore {
+ highScore = revScore
+ bestFuzz = ident
+ }
+ }
+ }
+ return bestFuzz
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/lsp/fuzzy/input.go b/src/cmd/vendor/golang.org/x/tools/internal/lsp/fuzzy/input.go
new file mode 100644
index 0000000000..ac377035ec
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/tools/internal/lsp/fuzzy/input.go
@@ -0,0 +1,168 @@
+// 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 fuzzy
+
+import (
+ "unicode"
+)
+
+// RuneRole specifies the role of a rune in the context of an input.
+type RuneRole byte
+
+const (
+ // RNone specifies a rune without any role in the input (i.e., whitespace/non-ASCII).
+ RNone RuneRole = iota
+ // RSep specifies a rune with the role of segment separator.
+ RSep
+ // RTail specifies a rune which is a lower-case tail in a word in the input.
+ RTail
+ // RUCTail specifies a rune which is an upper-case tail in a word in the input.
+ RUCTail
+ // RHead specifies a rune which is the first character in a word in the input.
+ RHead
+)
+
+// RuneRoles detects the roles of each byte rune in an input string and stores it in the output
+// slice. The rune role depends on the input type. Stops when it parsed all the runes in the string
+// or when it filled the output. If output is nil, then it gets created.
+func RuneRoles(str string, reuse []RuneRole) []RuneRole {
+ var output []RuneRole
+ if cap(reuse) < len(str) {
+ output = make([]RuneRole, 0, len(str))
+ } else {
+ output = reuse[:0]
+ }
+
+ prev, prev2 := rtNone, rtNone
+ for i := 0; i < len(str); i++ {
+ r := rune(str[i])
+
+ role := RNone
+
+ curr := rtLower
+ if str[i] <= unicode.MaxASCII {
+ curr = runeType(rt[str[i]] - '0')
+ }
+
+ if curr == rtLower {
+ if prev == rtNone || prev == rtPunct {
+ role = RHead
+ } else {
+ role = RTail
+ }
+ } else if curr == rtUpper {
+ role = RHead
+
+ if prev == rtUpper {
+ // This and previous characters are both upper case.
+
+ if i+1 == len(str) {
+ // This is last character, previous was also uppercase -> this is UCTail
+ // i.e., (current char is C): aBC / BC / ABC
+ role = RUCTail
+ }
+ }
+ } else if curr == rtPunct {
+ switch r {
+ case '.', ':':
+ role = RSep
+ }
+ }
+ if curr != rtLower {
+ if i > 1 && output[i-1] == RHead && prev2 == rtUpper && (output[i-2] == RHead || output[i-2] == RUCTail) {
+ // The previous two characters were uppercase. The current one is not a lower case, so the
+ // previous one can't be a HEAD. Make it a UCTail.
+ // i.e., (last char is current char - B must be a UCTail): ABC / ZABC / AB.
+ output[i-1] = RUCTail
+ }
+ }
+
+ output = append(output, role)
+ prev2 = prev
+ prev = curr
+ }
+ return output
+}
+
+type runeType byte
+
+const (
+ rtNone runeType = iota
+ rtPunct
+ rtLower
+ rtUpper
+)
+
+const rt = "00000000000000000000000000000000000000000000001122222222221000000333333333333333333333333330000002222222222222222222222222200000"
+
+// LastSegment returns the substring representing the last segment from the input, where each
+// byte has an associated RuneRole in the roles slice. This makes sense only for inputs of Symbol
+// or Filename type.
+func LastSegment(input string, roles []RuneRole) string {
+ // Exclude ending separators.
+ end := len(input) - 1
+ for end >= 0 && roles[end] == RSep {
+ end--
+ }
+ if end < 0 {
+ return ""
+ }
+
+ start := end - 1
+ for start >= 0 && roles[start] != RSep {
+ start--
+ }
+
+ return input[start+1 : end+1]
+}
+
+// ToLower transforms the input string to lower case, which is stored in the output byte slice.
+// The lower casing considers only ASCII values - non ASCII values are left unmodified.
+// Stops when parsed all input or when it filled the output slice. If output is nil, then it gets
+// created.
+func ToLower(input string, reuse []byte) []byte {
+ output := reuse
+ if cap(reuse) < len(input) {
+ output = make([]byte, len(input))
+ }
+
+ for i := 0; i < len(input); i++ {
+ r := rune(input[i])
+ if r <= unicode.MaxASCII {
+ if 'A' <= r && r <= 'Z' {
+ r += 'a' - 'A'
+ }
+ }
+ output[i] = byte(r)
+ }
+ return output[:len(input)]
+}
+
+// WordConsumer defines a consumer for a word delimited by the [start,end) byte offsets in an input
+// (start is inclusive, end is exclusive).
+type WordConsumer func(start, end int)
+
+// Words find word delimiters in an input based on its bytes' mappings to rune roles. The offset
+// delimiters for each word are fed to the provided consumer function.
+func Words(roles []RuneRole, consume WordConsumer) {
+ var wordStart int
+ for i, r := range roles {
+ switch r {
+ case RUCTail, RTail:
+ case RHead, RNone, RSep:
+ if i != wordStart {
+ consume(wordStart, i)
+ }
+ wordStart = i
+ if r != RHead {
+ // Skip this character.
+ wordStart = i + 1
+ }
+ }
+ }
+ if wordStart != len(roles) {
+ consume(wordStart, len(roles))
+ }
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/lsp/fuzzy/matcher.go b/src/cmd/vendor/golang.org/x/tools/internal/lsp/fuzzy/matcher.go
new file mode 100644
index 0000000000..16a643097d
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/tools/internal/lsp/fuzzy/matcher.go
@@ -0,0 +1,398 @@
+// 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 fuzzy implements a fuzzy matching algorithm.
+package fuzzy
+
+import (
+ "bytes"
+ "fmt"
+)
+
+const (
+ // MaxInputSize is the maximum size of the input scored against the fuzzy matcher. Longer inputs
+ // will be truncated to this size.
+ MaxInputSize = 127
+ // MaxPatternSize is the maximum size of the pattern used to construct the fuzzy matcher. Longer
+ // inputs are truncated to this size.
+ MaxPatternSize = 63
+)
+
+type scoreVal int
+
+func (s scoreVal) val() int {
+ return int(s) >> 1
+}
+
+func (s scoreVal) prevK() int {
+ return int(s) & 1
+}
+
+func score(val int, prevK int /*0 or 1*/) scoreVal {
+ return scoreVal(val<<1 + prevK)
+}
+
+// Matcher implements a fuzzy matching algorithm for scoring candidates against a pattern.
+// The matcher does not support parallel usage.
+type Matcher struct {
+ pattern string
+ patternLower []byte // lower-case version of the pattern
+ patternShort []byte // first characters of the pattern
+ caseSensitive bool // set if the pattern is mix-cased
+
+ patternRoles []RuneRole // the role of each character in the pattern
+ roles []RuneRole // the role of each character in the tested string
+
+ scores [MaxInputSize + 1][MaxPatternSize + 1][2]scoreVal
+
+ scoreScale float32
+
+ lastCandidateLen int // in bytes
+ lastCandidateMatched bool
+
+ // Here we save the last candidate in lower-case. This is basically a byte slice we reuse for
+ // performance reasons, so the slice is not reallocated for every candidate.
+ lowerBuf [MaxInputSize]byte
+ rolesBuf [MaxInputSize]RuneRole
+}
+
+func (m *Matcher) bestK(i, j int) int {
+ if m.scores[i][j][0].val() < m.scores[i][j][1].val() {
+ return 1
+ }
+ return 0
+}
+
+// NewMatcher returns a new fuzzy matcher for scoring candidates against the provided pattern.
+func NewMatcher(pattern string) *Matcher {
+ if len(pattern) > MaxPatternSize {
+ pattern = pattern[:MaxPatternSize]
+ }
+
+ m := &Matcher{
+ pattern: pattern,
+ patternLower: ToLower(pattern, nil),
+ }
+
+ for i, c := range m.patternLower {
+ if pattern[i] != c {
+ m.caseSensitive = true
+ break
+ }
+ }
+
+ if len(pattern) > 3 {
+ m.patternShort = m.patternLower[:3]
+ } else {
+ m.patternShort = m.patternLower
+ }
+
+ m.patternRoles = RuneRoles(pattern, nil)
+
+ if len(pattern) > 0 {
+ maxCharScore := 4
+ m.scoreScale = 1 / float32(maxCharScore*len(pattern))
+ }
+
+ return m
+}
+
+// Score returns the score returned by matching the candidate to the pattern.
+// This is not designed for parallel use. Multiple candidates must be scored sequentially.
+// Returns a score between 0 and 1 (0 - no match, 1 - perfect match).
+func (m *Matcher) Score(candidate string) float32 {
+ if len(candidate) > MaxInputSize {
+ candidate = candidate[:MaxInputSize]
+ }
+ lower := ToLower(candidate, m.lowerBuf[:])
+ m.lastCandidateLen = len(candidate)
+
+ if len(m.pattern) == 0 {
+ // Empty patterns perfectly match candidates.
+ return 1
+ }
+
+ if m.match(candidate, lower) {
+ sc := m.computeScore(candidate, lower)
+ if sc > minScore/2 && !m.poorMatch() {
+ m.lastCandidateMatched = true
+ if len(m.pattern) == len(candidate) {
+ // Perfect match.
+ return 1
+ }
+
+ if sc < 0 {
+ sc = 0
+ }
+ normalizedScore := float32(sc) * m.scoreScale
+ if normalizedScore > 1 {
+ normalizedScore = 1
+ }
+
+ return normalizedScore
+ }
+ }
+
+ m.lastCandidateMatched = false
+ return 0
+}
+
+const minScore = -10000
+
+// MatchedRanges returns matches ranges for the last scored string as a flattened array of
+// [begin, end) byte offset pairs.
+func (m *Matcher) MatchedRanges() []int {
+ if len(m.pattern) == 0 || !m.lastCandidateMatched {
+ return nil
+ }
+ i, j := m.lastCandidateLen, len(m.pattern)
+ if m.scores[i][j][0].val() < minScore/2 && m.scores[i][j][1].val() < minScore/2 {
+ return nil
+ }
+
+ var ret []int
+ k := m.bestK(i, j)
+ for i > 0 {
+ take := (k == 1)
+ k = m.scores[i][j][k].prevK()
+ if take {
+ if len(ret) == 0 || ret[len(ret)-1] != i {
+ ret = append(ret, i)
+ ret = append(ret, i-1)
+ } else {
+ ret[len(ret)-1] = i - 1
+ }
+ j--
+ }
+ i--
+ }
+ // Reverse slice.
+ for i := 0; i < len(ret)/2; i++ {
+ ret[i], ret[len(ret)-1-i] = ret[len(ret)-1-i], ret[i]
+ }
+ return ret
+}
+
+func (m *Matcher) match(candidate string, candidateLower []byte) bool {
+ i, j := 0, 0
+ for ; i < len(candidateLower) && j < len(m.patternLower); i++ {
+ if candidateLower[i] == m.patternLower[j] {
+ j++
+ }
+ }
+ if j != len(m.patternLower) {
+ return false
+ }
+
+ // The input passes the simple test against pattern, so it is time to classify its characters.
+ // Character roles are used below to find the last segment.
+ m.roles = RuneRoles(candidate, m.rolesBuf[:])
+
+ return true
+}
+
+func (m *Matcher) computeScore(candidate string, candidateLower []byte) int {
+ pattLen, candLen := len(m.pattern), len(candidate)
+
+ for j := 0; j <= len(m.pattern); j++ {
+ m.scores[0][j][0] = minScore << 1
+ m.scores[0][j][1] = minScore << 1
+ }
+ m.scores[0][0][0] = score(0, 0) // Start with 0.
+
+ segmentsLeft, lastSegStart := 1, 0
+ for i := 0; i < candLen; i++ {
+ if m.roles[i] == RSep {
+ segmentsLeft++
+ lastSegStart = i + 1
+ }
+ }
+
+ // A per-character bonus for a consecutive match.
+ consecutiveBonus := 2
+ wordIdx := 0 // Word count within segment.
+ for i := 1; i <= candLen; i++ {
+
+ role := m.roles[i-1]
+ isHead := role == RHead
+
+ if isHead {
+ wordIdx++
+ } else if role == RSep && segmentsLeft > 1 {
+ wordIdx = 0
+ segmentsLeft--
+ }
+
+ var skipPenalty int
+ if i == 1 || (i-1) == lastSegStart {
+ // Skipping the start of first or last segment.
+ skipPenalty++
+ }
+
+ for j := 0; j <= pattLen; j++ {
+ // By default, we don't have a match. Fill in the skip data.
+ m.scores[i][j][1] = minScore << 1
+
+ // Compute the skip score.
+ k := 0
+ if m.scores[i-1][j][0].val() < m.scores[i-1][j][1].val() {
+ k = 1
+ }
+
+ skipScore := m.scores[i-1][j][k].val()
+ // Do not penalize missing characters after the last matched segment.
+ if j != pattLen {
+ skipScore -= skipPenalty
+ }
+ m.scores[i][j][0] = score(skipScore, k)
+
+ if j == 0 || candidateLower[i-1] != m.patternLower[j-1] {
+ // Not a match.
+ continue
+ }
+ pRole := m.patternRoles[j-1]
+
+ if role == RTail && pRole == RHead {
+ if j > 1 {
+ // Not a match: a head in the pattern matches a tail character in the candidate.
+ continue
+ }
+ // Special treatment for the first character of the pattern. We allow
+ // matches in the middle of a word if they are long enough, at least
+ // min(3, pattern.length) characters.
+ if !bytes.HasPrefix(candidateLower[i-1:], m.patternShort) {
+ continue
+ }
+ }
+
+ // Compute the char score.
+ var charScore int
+ // Bonus 1: the char is in the candidate's last segment.
+ if segmentsLeft <= 1 {
+ charScore++
+ }
+ // Bonus 2: Case match or a Head in the pattern aligns with one in the word.
+ // Single-case patterns lack segmentation signals and we assume any character
+ // can be a head of a segment.
+ if candidate[i-1] == m.pattern[j-1] || role == RHead && (!m.caseSensitive || pRole == RHead) {
+ charScore++
+ }
+
+ // Penalty 1: pattern char is Head, candidate char is Tail.
+ if role == RTail && pRole == RHead {
+ charScore--
+ }
+ // Penalty 2: first pattern character matched in the middle of a word.
+ if j == 1 && role == RTail {
+ charScore -= 4
+ }
+
+ // Third dimension encodes whether there is a gap between the previous match and the current
+ // one.
+ for k := 0; k < 2; k++ {
+ sc := m.scores[i-1][j-1][k].val() + charScore
+
+ isConsecutive := k == 1 || i-1 == 0 || i-1 == lastSegStart
+ if isConsecutive {
+ // Bonus 3: a consecutive match. First character match also gets a bonus to
+ // ensure prefix final match score normalizes to 1.0.
+ // Logically, this is a part of charScore, but we have to compute it here because it
+ // only applies for consecutive matches (k == 1).
+ sc += consecutiveBonus
+ }
+ if k == 0 {
+ // Penalty 3: Matching inside a segment (and previous char wasn't matched). Penalize for the lack
+ // of alignment.
+ if role == RTail || role == RUCTail {
+ sc -= 3
+ }
+ }
+
+ if sc > m.scores[i][j][1].val() {
+ m.scores[i][j][1] = score(sc, k)
+ }
+ }
+ }
+ }
+
+ result := m.scores[len(candidate)][len(m.pattern)][m.bestK(len(candidate), len(m.pattern))].val()
+
+ return result
+}
+
+// ScoreTable returns the score table computed for the provided candidate. Used only for debugging.
+func (m *Matcher) ScoreTable(candidate string) string {
+ var buf bytes.Buffer
+
+ var line1, line2, separator bytes.Buffer
+ line1.WriteString("\t")
+ line2.WriteString("\t")
+ for j := 0; j < len(m.pattern); j++ {
+ line1.WriteString(fmt.Sprintf("%c\t\t", m.pattern[j]))
+ separator.WriteString("----------------")
+ }
+
+ buf.WriteString(line1.String())
+ buf.WriteString("\n")
+ buf.WriteString(separator.String())
+ buf.WriteString("\n")
+
+ for i := 1; i <= len(candidate); i++ {
+ line1.Reset()
+ line2.Reset()
+
+ line1.WriteString(fmt.Sprintf("%c\t", candidate[i-1]))
+ line2.WriteString("\t")
+
+ for j := 1; j <= len(m.pattern); j++ {
+ line1.WriteString(fmt.Sprintf("M%6d(%c)\t", m.scores[i][j][0].val(), dir(m.scores[i][j][0].prevK())))
+ line2.WriteString(fmt.Sprintf("H%6d(%c)\t", m.scores[i][j][1].val(), dir(m.scores[i][j][1].prevK())))
+ }
+ buf.WriteString(line1.String())
+ buf.WriteString("\n")
+ buf.WriteString(line2.String())
+ buf.WriteString("\n")
+ buf.WriteString(separator.String())
+ buf.WriteString("\n")
+ }
+
+ return buf.String()
+}
+
+func dir(prevK int) rune {
+ if prevK == 0 {
+ return 'M'
+ }
+ return 'H'
+}
+
+func (m *Matcher) poorMatch() bool {
+ if len(m.pattern) < 2 {
+ return false
+ }
+
+ i, j := m.lastCandidateLen, len(m.pattern)
+ k := m.bestK(i, j)
+
+ var counter, len int
+ for i > 0 {
+ take := (k == 1)
+ k = m.scores[i][j][k].prevK()
+ if take {
+ len++
+ if k == 0 && len < 3 && m.roles[i-1] == RTail {
+ // Short match in the middle of a word
+ counter++
+ if counter > 1 {
+ return true
+ }
+ }
+ j--
+ } else {
+ len = 0
+ }
+ i--
+ }
+ return false
+}