mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 07:36:27 -04:00
go get -tool mvdan.cc/gofumpt@v0.9.2
This commit is contained in:
parent
21ae632c9b
commit
659908195e
7
go.mod
7
go.mod
|
|
@ -57,7 +57,6 @@ require (
|
|||
github.com/hpcloud/tail v1.0.0 // indirect
|
||||
github.com/invopop/jsonschema v0.10.0 // indirect
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
|
|
@ -65,12 +64,16 @@ require (
|
|||
github.com/onsi/gomega v1.34.1 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
golang.org/x/mod v0.35.0 // indirect
|
||||
golang.org/x/net v0.55.0 // indirect
|
||||
golang.org/x/term v0.43.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
golang.org/x/tools v0.44.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/fsnotify.v1 v1.4.7 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
mvdan.cc/gofumpt v0.9.2 // indirect
|
||||
)
|
||||
|
||||
tool mvdan.cc/gofumpt
|
||||
|
|
|
|||
11
go.sum
11
go.sum
|
|
@ -21,7 +21,6 @@ github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJ
|
|||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:tuijfIjZyjZaHq9xDUh0tNitwXshJpbLkqMOJv4H3do=
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
|
@ -40,6 +39,8 @@ github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3Bop
|
|||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0=
|
||||
|
|
@ -98,13 +99,11 @@ github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
|||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe h1:vHpqOnPlnkba8iSxU4j/CvDSS9J4+F4473esQsYLGoE=
|
||||
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/sahilm/fuzzy v0.1.3 h1:juByESSS32nVD81vr6tHmKmA/8zde7gE+x5CLxrzXPU=
|
||||
|
|
@ -140,6 +139,8 @@ golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0
|
|||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
|
|
@ -180,6 +181,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
|||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
|
|
@ -193,3 +196,5 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
|
|||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
mvdan.cc/gofumpt v0.9.2 h1:zsEMWL8SVKGHNztrx6uZrXdp7AX8r421Vvp23sz7ik4=
|
||||
mvdan.cc/gofumpt v0.9.2/go.mod h1:iB7Hn+ai8lPvofHd9ZFGVg2GOr8sBUw1QUWjNbmIL/s=
|
||||
|
|
|
|||
27
vendor/github.com/google/go-cmp/LICENSE
generated
vendored
Normal file
27
vendor/github.com/google/go-cmp/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2017 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
671
vendor/github.com/google/go-cmp/cmp/compare.go
generated
vendored
Normal file
671
vendor/github.com/google/go-cmp/cmp/compare.go
generated
vendored
Normal file
|
|
@ -0,0 +1,671 @@
|
|||
// Copyright 2017, 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 cmp determines equality of values.
|
||||
//
|
||||
// This package is intended to be a more powerful and safer alternative to
|
||||
// [reflect.DeepEqual] for comparing whether two values are semantically equal.
|
||||
// It is intended to only be used in tests, as performance is not a goal and
|
||||
// it may panic if it cannot compare the values. Its propensity towards
|
||||
// panicking means that its unsuitable for production environments where a
|
||||
// spurious panic may be fatal.
|
||||
//
|
||||
// The primary features of cmp are:
|
||||
//
|
||||
// - When the default behavior of equality does not suit the test's needs,
|
||||
// custom equality functions can override the equality operation.
|
||||
// For example, an equality function may report floats as equal so long as
|
||||
// they are within some tolerance of each other.
|
||||
//
|
||||
// - Types with an Equal method (e.g., [time.Time.Equal]) may use that method
|
||||
// to determine equality. This allows package authors to determine
|
||||
// the equality operation for the types that they define.
|
||||
//
|
||||
// - If no custom equality functions are used and no Equal method is defined,
|
||||
// equality is determined by recursively comparing the primitive kinds on
|
||||
// both values, much like [reflect.DeepEqual]. Unlike [reflect.DeepEqual],
|
||||
// unexported fields are not compared by default; they result in panics
|
||||
// unless suppressed by using an [Ignore] option
|
||||
// (see [github.com/google/go-cmp/cmp/cmpopts.IgnoreUnexported])
|
||||
// or explicitly compared using the [Exporter] option.
|
||||
package cmp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/diff"
|
||||
"github.com/google/go-cmp/cmp/internal/function"
|
||||
"github.com/google/go-cmp/cmp/internal/value"
|
||||
)
|
||||
|
||||
// TODO(≥go1.18): Use any instead of interface{}.
|
||||
|
||||
// Equal reports whether x and y are equal by recursively applying the
|
||||
// following rules in the given order to x and y and all of their sub-values:
|
||||
//
|
||||
// - Let S be the set of all [Ignore], [Transformer], and [Comparer] options that
|
||||
// remain after applying all path filters, value filters, and type filters.
|
||||
// If at least one [Ignore] exists in S, then the comparison is ignored.
|
||||
// If the number of [Transformer] and [Comparer] options in S is non-zero,
|
||||
// then Equal panics because it is ambiguous which option to use.
|
||||
// If S contains a single [Transformer], then use that to transform
|
||||
// the current values and recursively call Equal on the output values.
|
||||
// If S contains a single [Comparer], then use that to compare the current values.
|
||||
// Otherwise, evaluation proceeds to the next rule.
|
||||
//
|
||||
// - If the values have an Equal method of the form "(T) Equal(T) bool" or
|
||||
// "(T) Equal(I) bool" where T is assignable to I, then use the result of
|
||||
// x.Equal(y) even if x or y is nil. Otherwise, no such method exists and
|
||||
// evaluation proceeds to the next rule.
|
||||
//
|
||||
// - Lastly, try to compare x and y based on their basic kinds.
|
||||
// Simple kinds like booleans, integers, floats, complex numbers, strings,
|
||||
// and channels are compared using the equivalent of the == operator in Go.
|
||||
// Functions are only equal if they are both nil, otherwise they are unequal.
|
||||
//
|
||||
// Structs are equal if recursively calling Equal on all fields report equal.
|
||||
// If a struct contains unexported fields, Equal panics unless an [Ignore] option
|
||||
// (e.g., [github.com/google/go-cmp/cmp/cmpopts.IgnoreUnexported]) ignores that field
|
||||
// or the [Exporter] option explicitly permits comparing the unexported field.
|
||||
//
|
||||
// Slices are equal if they are both nil or both non-nil, where recursively
|
||||
// calling Equal on all non-ignored slice or array elements report equal.
|
||||
// Empty non-nil slices and nil slices are not equal; to equate empty slices,
|
||||
// consider using [github.com/google/go-cmp/cmp/cmpopts.EquateEmpty].
|
||||
//
|
||||
// Maps are equal if they are both nil or both non-nil, where recursively
|
||||
// calling Equal on all non-ignored map entries report equal.
|
||||
// Map keys are equal according to the == operator.
|
||||
// To use custom comparisons for map keys, consider using
|
||||
// [github.com/google/go-cmp/cmp/cmpopts.SortMaps].
|
||||
// Empty non-nil maps and nil maps are not equal; to equate empty maps,
|
||||
// consider using [github.com/google/go-cmp/cmp/cmpopts.EquateEmpty].
|
||||
//
|
||||
// Pointers and interfaces are equal if they are both nil or both non-nil,
|
||||
// where they have the same underlying concrete type and recursively
|
||||
// calling Equal on the underlying values reports equal.
|
||||
//
|
||||
// Before recursing into a pointer, slice element, or map, the current path
|
||||
// is checked to detect whether the address has already been visited.
|
||||
// If there is a cycle, then the pointed at values are considered equal
|
||||
// only if both addresses were previously visited in the same path step.
|
||||
func Equal(x, y interface{}, opts ...Option) bool {
|
||||
s := newState(opts)
|
||||
s.compareAny(rootStep(x, y))
|
||||
return s.result.Equal()
|
||||
}
|
||||
|
||||
// Diff returns a human-readable report of the differences between two values:
|
||||
// y - x. It returns an empty string if and only if Equal returns true for the
|
||||
// same input values and options.
|
||||
//
|
||||
// The output is displayed as a literal in pseudo-Go syntax.
|
||||
// At the start of each line, a "-" prefix indicates an element removed from x,
|
||||
// a "+" prefix to indicates an element added from y, and the lack of a prefix
|
||||
// indicates an element common to both x and y. If possible, the output
|
||||
// uses fmt.Stringer.String or error.Error methods to produce more humanly
|
||||
// readable outputs. In such cases, the string is prefixed with either an
|
||||
// 's' or 'e' character, respectively, to indicate that the method was called.
|
||||
//
|
||||
// Do not depend on this output being stable. If you need the ability to
|
||||
// programmatically interpret the difference, consider using a custom Reporter.
|
||||
func Diff(x, y interface{}, opts ...Option) string {
|
||||
s := newState(opts)
|
||||
|
||||
// Optimization: If there are no other reporters, we can optimize for the
|
||||
// common case where the result is equal (and thus no reported difference).
|
||||
// This avoids the expensive construction of a difference tree.
|
||||
if len(s.reporters) == 0 {
|
||||
s.compareAny(rootStep(x, y))
|
||||
if s.result.Equal() {
|
||||
return ""
|
||||
}
|
||||
s.result = diff.Result{} // Reset results
|
||||
}
|
||||
|
||||
r := new(defaultReporter)
|
||||
s.reporters = append(s.reporters, reporter{r})
|
||||
s.compareAny(rootStep(x, y))
|
||||
d := r.String()
|
||||
if (d == "") != s.result.Equal() {
|
||||
panic("inconsistent difference and equality results")
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// rootStep constructs the first path step. If x and y have differing types,
|
||||
// then they are stored within an empty interface type.
|
||||
func rootStep(x, y interface{}) PathStep {
|
||||
vx := reflect.ValueOf(x)
|
||||
vy := reflect.ValueOf(y)
|
||||
|
||||
// If the inputs are different types, auto-wrap them in an empty interface
|
||||
// so that they have the same parent type.
|
||||
var t reflect.Type
|
||||
if !vx.IsValid() || !vy.IsValid() || vx.Type() != vy.Type() {
|
||||
t = anyType
|
||||
if vx.IsValid() {
|
||||
vvx := reflect.New(t).Elem()
|
||||
vvx.Set(vx)
|
||||
vx = vvx
|
||||
}
|
||||
if vy.IsValid() {
|
||||
vvy := reflect.New(t).Elem()
|
||||
vvy.Set(vy)
|
||||
vy = vvy
|
||||
}
|
||||
} else {
|
||||
t = vx.Type()
|
||||
}
|
||||
|
||||
return &pathStep{t, vx, vy}
|
||||
}
|
||||
|
||||
type state struct {
|
||||
// These fields represent the "comparison state".
|
||||
// Calling statelessCompare must not result in observable changes to these.
|
||||
result diff.Result // The current result of comparison
|
||||
curPath Path // The current path in the value tree
|
||||
curPtrs pointerPath // The current set of visited pointers
|
||||
reporters []reporter // Optional reporters
|
||||
|
||||
// recChecker checks for infinite cycles applying the same set of
|
||||
// transformers upon the output of itself.
|
||||
recChecker recChecker
|
||||
|
||||
// dynChecker triggers pseudo-random checks for option correctness.
|
||||
// It is safe for statelessCompare to mutate this value.
|
||||
dynChecker dynChecker
|
||||
|
||||
// These fields, once set by processOption, will not change.
|
||||
exporters []exporter // List of exporters for structs with unexported fields
|
||||
opts Options // List of all fundamental and filter options
|
||||
}
|
||||
|
||||
func newState(opts []Option) *state {
|
||||
// Always ensure a validator option exists to validate the inputs.
|
||||
s := &state{opts: Options{validator{}}}
|
||||
s.curPtrs.Init()
|
||||
s.processOption(Options(opts))
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *state) processOption(opt Option) {
|
||||
switch opt := opt.(type) {
|
||||
case nil:
|
||||
case Options:
|
||||
for _, o := range opt {
|
||||
s.processOption(o)
|
||||
}
|
||||
case coreOption:
|
||||
type filtered interface {
|
||||
isFiltered() bool
|
||||
}
|
||||
if fopt, ok := opt.(filtered); ok && !fopt.isFiltered() {
|
||||
panic(fmt.Sprintf("cannot use an unfiltered option: %v", opt))
|
||||
}
|
||||
s.opts = append(s.opts, opt)
|
||||
case exporter:
|
||||
s.exporters = append(s.exporters, opt)
|
||||
case reporter:
|
||||
s.reporters = append(s.reporters, opt)
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown option %T", opt))
|
||||
}
|
||||
}
|
||||
|
||||
// statelessCompare compares two values and returns the result.
|
||||
// This function is stateless in that it does not alter the current result,
|
||||
// or output to any registered reporters.
|
||||
func (s *state) statelessCompare(step PathStep) diff.Result {
|
||||
// We do not save and restore curPath and curPtrs because all of the
|
||||
// compareX methods should properly push and pop from them.
|
||||
// It is an implementation bug if the contents of the paths differ from
|
||||
// when calling this function to when returning from it.
|
||||
|
||||
oldResult, oldReporters := s.result, s.reporters
|
||||
s.result = diff.Result{} // Reset result
|
||||
s.reporters = nil // Remove reporters to avoid spurious printouts
|
||||
s.compareAny(step)
|
||||
res := s.result
|
||||
s.result, s.reporters = oldResult, oldReporters
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *state) compareAny(step PathStep) {
|
||||
// Update the path stack.
|
||||
s.curPath.push(step)
|
||||
defer s.curPath.pop()
|
||||
for _, r := range s.reporters {
|
||||
r.PushStep(step)
|
||||
defer r.PopStep()
|
||||
}
|
||||
s.recChecker.Check(s.curPath)
|
||||
|
||||
// Cycle-detection for slice elements (see NOTE in compareSlice).
|
||||
t := step.Type()
|
||||
vx, vy := step.Values()
|
||||
if si, ok := step.(SliceIndex); ok && si.isSlice && vx.IsValid() && vy.IsValid() {
|
||||
px, py := vx.Addr(), vy.Addr()
|
||||
if eq, visited := s.curPtrs.Push(px, py); visited {
|
||||
s.report(eq, reportByCycle)
|
||||
return
|
||||
}
|
||||
defer s.curPtrs.Pop(px, py)
|
||||
}
|
||||
|
||||
// Rule 1: Check whether an option applies on this node in the value tree.
|
||||
if s.tryOptions(t, vx, vy) {
|
||||
return
|
||||
}
|
||||
|
||||
// Rule 2: Check whether the type has a valid Equal method.
|
||||
if s.tryMethod(t, vx, vy) {
|
||||
return
|
||||
}
|
||||
|
||||
// Rule 3: Compare based on the underlying kind.
|
||||
switch t.Kind() {
|
||||
case reflect.Bool:
|
||||
s.report(vx.Bool() == vy.Bool(), 0)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
s.report(vx.Int() == vy.Int(), 0)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
s.report(vx.Uint() == vy.Uint(), 0)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
s.report(vx.Float() == vy.Float(), 0)
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
s.report(vx.Complex() == vy.Complex(), 0)
|
||||
case reflect.String:
|
||||
s.report(vx.String() == vy.String(), 0)
|
||||
case reflect.Chan, reflect.UnsafePointer:
|
||||
s.report(vx.Pointer() == vy.Pointer(), 0)
|
||||
case reflect.Func:
|
||||
s.report(vx.IsNil() && vy.IsNil(), 0)
|
||||
case reflect.Struct:
|
||||
s.compareStruct(t, vx, vy)
|
||||
case reflect.Slice, reflect.Array:
|
||||
s.compareSlice(t, vx, vy)
|
||||
case reflect.Map:
|
||||
s.compareMap(t, vx, vy)
|
||||
case reflect.Ptr:
|
||||
s.comparePtr(t, vx, vy)
|
||||
case reflect.Interface:
|
||||
s.compareInterface(t, vx, vy)
|
||||
default:
|
||||
panic(fmt.Sprintf("%v kind not handled", t.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) tryOptions(t reflect.Type, vx, vy reflect.Value) bool {
|
||||
// Evaluate all filters and apply the remaining options.
|
||||
if opt := s.opts.filter(s, t, vx, vy); opt != nil {
|
||||
opt.apply(s, vx, vy)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *state) tryMethod(t reflect.Type, vx, vy reflect.Value) bool {
|
||||
// Check if this type even has an Equal method.
|
||||
m, ok := t.MethodByName("Equal")
|
||||
if !ok || !function.IsType(m.Type, function.EqualAssignable) {
|
||||
return false
|
||||
}
|
||||
|
||||
eq := s.callTTBFunc(m.Func, vx, vy)
|
||||
s.report(eq, reportByMethod)
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *state) callTRFunc(f, v reflect.Value, step Transform) reflect.Value {
|
||||
if !s.dynChecker.Next() {
|
||||
return f.Call([]reflect.Value{v})[0]
|
||||
}
|
||||
|
||||
// Run the function twice and ensure that we get the same results back.
|
||||
// We run in goroutines so that the race detector (if enabled) can detect
|
||||
// unsafe mutations to the input.
|
||||
c := make(chan reflect.Value)
|
||||
go detectRaces(c, f, v)
|
||||
got := <-c
|
||||
want := f.Call([]reflect.Value{v})[0]
|
||||
if step.vx, step.vy = got, want; !s.statelessCompare(step).Equal() {
|
||||
// To avoid false-positives with non-reflexive equality operations,
|
||||
// we sanity check whether a value is equal to itself.
|
||||
if step.vx, step.vy = want, want; !s.statelessCompare(step).Equal() {
|
||||
return want
|
||||
}
|
||||
panic(fmt.Sprintf("non-deterministic function detected: %s", function.NameOf(f)))
|
||||
}
|
||||
return want
|
||||
}
|
||||
|
||||
func (s *state) callTTBFunc(f, x, y reflect.Value) bool {
|
||||
if !s.dynChecker.Next() {
|
||||
return f.Call([]reflect.Value{x, y})[0].Bool()
|
||||
}
|
||||
|
||||
// Swapping the input arguments is sufficient to check that
|
||||
// f is symmetric and deterministic.
|
||||
// We run in goroutines so that the race detector (if enabled) can detect
|
||||
// unsafe mutations to the input.
|
||||
c := make(chan reflect.Value)
|
||||
go detectRaces(c, f, y, x)
|
||||
got := <-c
|
||||
want := f.Call([]reflect.Value{x, y})[0].Bool()
|
||||
if !got.IsValid() || got.Bool() != want {
|
||||
panic(fmt.Sprintf("non-deterministic or non-symmetric function detected: %s", function.NameOf(f)))
|
||||
}
|
||||
return want
|
||||
}
|
||||
|
||||
func detectRaces(c chan<- reflect.Value, f reflect.Value, vs ...reflect.Value) {
|
||||
var ret reflect.Value
|
||||
defer func() {
|
||||
recover() // Ignore panics, let the other call to f panic instead
|
||||
c <- ret
|
||||
}()
|
||||
ret = f.Call(vs)[0]
|
||||
}
|
||||
|
||||
func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) {
|
||||
var addr bool
|
||||
var vax, vay reflect.Value // Addressable versions of vx and vy
|
||||
|
||||
var mayForce, mayForceInit bool
|
||||
step := StructField{&structField{}}
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
step.typ = t.Field(i).Type
|
||||
step.vx = vx.Field(i)
|
||||
step.vy = vy.Field(i)
|
||||
step.name = t.Field(i).Name
|
||||
step.idx = i
|
||||
step.unexported = !isExported(step.name)
|
||||
if step.unexported {
|
||||
if step.name == "_" {
|
||||
continue
|
||||
}
|
||||
// Defer checking of unexported fields until later to give an
|
||||
// Ignore a chance to ignore the field.
|
||||
if !vax.IsValid() || !vay.IsValid() {
|
||||
// For retrieveUnexportedField to work, the parent struct must
|
||||
// be addressable. Create a new copy of the values if
|
||||
// necessary to make them addressable.
|
||||
addr = vx.CanAddr() || vy.CanAddr()
|
||||
vax = makeAddressable(vx)
|
||||
vay = makeAddressable(vy)
|
||||
}
|
||||
if !mayForceInit {
|
||||
for _, xf := range s.exporters {
|
||||
mayForce = mayForce || xf(t)
|
||||
}
|
||||
mayForceInit = true
|
||||
}
|
||||
step.mayForce = mayForce
|
||||
step.paddr = addr
|
||||
step.pvx = vax
|
||||
step.pvy = vay
|
||||
step.field = t.Field(i)
|
||||
}
|
||||
s.compareAny(step)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) compareSlice(t reflect.Type, vx, vy reflect.Value) {
|
||||
isSlice := t.Kind() == reflect.Slice
|
||||
if isSlice && (vx.IsNil() || vy.IsNil()) {
|
||||
s.report(vx.IsNil() && vy.IsNil(), 0)
|
||||
return
|
||||
}
|
||||
|
||||
// NOTE: It is incorrect to call curPtrs.Push on the slice header pointer
|
||||
// since slices represents a list of pointers, rather than a single pointer.
|
||||
// The pointer checking logic must be handled on a per-element basis
|
||||
// in compareAny.
|
||||
//
|
||||
// A slice header (see reflect.SliceHeader) in Go is a tuple of a starting
|
||||
// pointer P, a length N, and a capacity C. Supposing each slice element has
|
||||
// a memory size of M, then the slice is equivalent to the list of pointers:
|
||||
// [P+i*M for i in range(N)]
|
||||
//
|
||||
// For example, v[:0] and v[:1] are slices with the same starting pointer,
|
||||
// but they are clearly different values. Using the slice pointer alone
|
||||
// violates the assumption that equal pointers implies equal values.
|
||||
|
||||
step := SliceIndex{&sliceIndex{pathStep: pathStep{typ: t.Elem()}, isSlice: isSlice}}
|
||||
withIndexes := func(ix, iy int) SliceIndex {
|
||||
if ix >= 0 {
|
||||
step.vx, step.xkey = vx.Index(ix), ix
|
||||
} else {
|
||||
step.vx, step.xkey = reflect.Value{}, -1
|
||||
}
|
||||
if iy >= 0 {
|
||||
step.vy, step.ykey = vy.Index(iy), iy
|
||||
} else {
|
||||
step.vy, step.ykey = reflect.Value{}, -1
|
||||
}
|
||||
return step
|
||||
}
|
||||
|
||||
// Ignore options are able to ignore missing elements in a slice.
|
||||
// However, detecting these reliably requires an optimal differencing
|
||||
// algorithm, for which diff.Difference is not.
|
||||
//
|
||||
// Instead, we first iterate through both slices to detect which elements
|
||||
// would be ignored if standing alone. The index of non-discarded elements
|
||||
// are stored in a separate slice, which diffing is then performed on.
|
||||
var indexesX, indexesY []int
|
||||
var ignoredX, ignoredY []bool
|
||||
for ix := 0; ix < vx.Len(); ix++ {
|
||||
ignored := s.statelessCompare(withIndexes(ix, -1)).NumDiff == 0
|
||||
if !ignored {
|
||||
indexesX = append(indexesX, ix)
|
||||
}
|
||||
ignoredX = append(ignoredX, ignored)
|
||||
}
|
||||
for iy := 0; iy < vy.Len(); iy++ {
|
||||
ignored := s.statelessCompare(withIndexes(-1, iy)).NumDiff == 0
|
||||
if !ignored {
|
||||
indexesY = append(indexesY, iy)
|
||||
}
|
||||
ignoredY = append(ignoredY, ignored)
|
||||
}
|
||||
|
||||
// Compute an edit-script for slices vx and vy (excluding ignored elements).
|
||||
edits := diff.Difference(len(indexesX), len(indexesY), func(ix, iy int) diff.Result {
|
||||
return s.statelessCompare(withIndexes(indexesX[ix], indexesY[iy]))
|
||||
})
|
||||
|
||||
// Replay the ignore-scripts and the edit-script.
|
||||
var ix, iy int
|
||||
for ix < vx.Len() || iy < vy.Len() {
|
||||
var e diff.EditType
|
||||
switch {
|
||||
case ix < len(ignoredX) && ignoredX[ix]:
|
||||
e = diff.UniqueX
|
||||
case iy < len(ignoredY) && ignoredY[iy]:
|
||||
e = diff.UniqueY
|
||||
default:
|
||||
e, edits = edits[0], edits[1:]
|
||||
}
|
||||
switch e {
|
||||
case diff.UniqueX:
|
||||
s.compareAny(withIndexes(ix, -1))
|
||||
ix++
|
||||
case diff.UniqueY:
|
||||
s.compareAny(withIndexes(-1, iy))
|
||||
iy++
|
||||
default:
|
||||
s.compareAny(withIndexes(ix, iy))
|
||||
ix++
|
||||
iy++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) compareMap(t reflect.Type, vx, vy reflect.Value) {
|
||||
if vx.IsNil() || vy.IsNil() {
|
||||
s.report(vx.IsNil() && vy.IsNil(), 0)
|
||||
return
|
||||
}
|
||||
|
||||
// Cycle-detection for maps.
|
||||
if eq, visited := s.curPtrs.Push(vx, vy); visited {
|
||||
s.report(eq, reportByCycle)
|
||||
return
|
||||
}
|
||||
defer s.curPtrs.Pop(vx, vy)
|
||||
|
||||
// We combine and sort the two map keys so that we can perform the
|
||||
// comparisons in a deterministic order.
|
||||
step := MapIndex{&mapIndex{pathStep: pathStep{typ: t.Elem()}}}
|
||||
for _, k := range value.SortKeys(append(vx.MapKeys(), vy.MapKeys()...)) {
|
||||
step.vx = vx.MapIndex(k)
|
||||
step.vy = vy.MapIndex(k)
|
||||
step.key = k
|
||||
if !step.vx.IsValid() && !step.vy.IsValid() {
|
||||
// It is possible for both vx and vy to be invalid if the
|
||||
// key contained a NaN value in it.
|
||||
//
|
||||
// Even with the ability to retrieve NaN keys in Go 1.12,
|
||||
// there still isn't a sensible way to compare the values since
|
||||
// a NaN key may map to multiple unordered values.
|
||||
// The most reasonable way to compare NaNs would be to compare the
|
||||
// set of values. However, this is impossible to do efficiently
|
||||
// since set equality is provably an O(n^2) operation given only
|
||||
// an Equal function. If we had a Less function or Hash function,
|
||||
// this could be done in O(n*log(n)) or O(n), respectively.
|
||||
//
|
||||
// Rather than adding complex logic to deal with NaNs, make it
|
||||
// the user's responsibility to compare such obscure maps.
|
||||
const help = "consider providing a Comparer to compare the map"
|
||||
panic(fmt.Sprintf("%#v has map key with NaNs\n%s", s.curPath, help))
|
||||
}
|
||||
s.compareAny(step)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) comparePtr(t reflect.Type, vx, vy reflect.Value) {
|
||||
if vx.IsNil() || vy.IsNil() {
|
||||
s.report(vx.IsNil() && vy.IsNil(), 0)
|
||||
return
|
||||
}
|
||||
|
||||
// Cycle-detection for pointers.
|
||||
if eq, visited := s.curPtrs.Push(vx, vy); visited {
|
||||
s.report(eq, reportByCycle)
|
||||
return
|
||||
}
|
||||
defer s.curPtrs.Pop(vx, vy)
|
||||
|
||||
vx, vy = vx.Elem(), vy.Elem()
|
||||
s.compareAny(Indirect{&indirect{pathStep{t.Elem(), vx, vy}}})
|
||||
}
|
||||
|
||||
func (s *state) compareInterface(t reflect.Type, vx, vy reflect.Value) {
|
||||
if vx.IsNil() || vy.IsNil() {
|
||||
s.report(vx.IsNil() && vy.IsNil(), 0)
|
||||
return
|
||||
}
|
||||
vx, vy = vx.Elem(), vy.Elem()
|
||||
if vx.Type() != vy.Type() {
|
||||
s.report(false, 0)
|
||||
return
|
||||
}
|
||||
s.compareAny(TypeAssertion{&typeAssertion{pathStep{vx.Type(), vx, vy}}})
|
||||
}
|
||||
|
||||
func (s *state) report(eq bool, rf resultFlags) {
|
||||
if rf&reportByIgnore == 0 {
|
||||
if eq {
|
||||
s.result.NumSame++
|
||||
rf |= reportEqual
|
||||
} else {
|
||||
s.result.NumDiff++
|
||||
rf |= reportUnequal
|
||||
}
|
||||
}
|
||||
for _, r := range s.reporters {
|
||||
r.Report(Result{flags: rf})
|
||||
}
|
||||
}
|
||||
|
||||
// recChecker tracks the state needed to periodically perform checks that
|
||||
// user provided transformers are not stuck in an infinitely recursive cycle.
|
||||
type recChecker struct{ next int }
|
||||
|
||||
// Check scans the Path for any recursive transformers and panics when any
|
||||
// recursive transformers are detected. Note that the presence of a
|
||||
// recursive Transformer does not necessarily imply an infinite cycle.
|
||||
// As such, this check only activates after some minimal number of path steps.
|
||||
func (rc *recChecker) Check(p Path) {
|
||||
const minLen = 1 << 16
|
||||
if rc.next == 0 {
|
||||
rc.next = minLen
|
||||
}
|
||||
if len(p) < rc.next {
|
||||
return
|
||||
}
|
||||
rc.next <<= 1
|
||||
|
||||
// Check whether the same transformer has appeared at least twice.
|
||||
var ss []string
|
||||
m := map[Option]int{}
|
||||
for _, ps := range p {
|
||||
if t, ok := ps.(Transform); ok {
|
||||
t := t.Option()
|
||||
if m[t] == 1 { // Transformer was used exactly once before
|
||||
tf := t.(*transformer).fnc.Type()
|
||||
ss = append(ss, fmt.Sprintf("%v: %v => %v", t, tf.In(0), tf.Out(0)))
|
||||
}
|
||||
m[t]++
|
||||
}
|
||||
}
|
||||
if len(ss) > 0 {
|
||||
const warning = "recursive set of Transformers detected"
|
||||
const help = "consider using cmpopts.AcyclicTransformer"
|
||||
set := strings.Join(ss, "\n\t")
|
||||
panic(fmt.Sprintf("%s:\n\t%s\n%s", warning, set, help))
|
||||
}
|
||||
}
|
||||
|
||||
// dynChecker tracks the state needed to periodically perform checks that
|
||||
// user provided functions are symmetric and deterministic.
|
||||
// The zero value is safe for immediate use.
|
||||
type dynChecker struct{ curr, next int }
|
||||
|
||||
// Next increments the state and reports whether a check should be performed.
|
||||
//
|
||||
// Checks occur every Nth function call, where N is a triangular number:
|
||||
//
|
||||
// 0 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 ...
|
||||
//
|
||||
// See https://en.wikipedia.org/wiki/Triangular_number
|
||||
//
|
||||
// This sequence ensures that the cost of checks drops significantly as
|
||||
// the number of functions calls grows larger.
|
||||
func (dc *dynChecker) Next() bool {
|
||||
ok := dc.curr == dc.next
|
||||
if ok {
|
||||
dc.curr = 0
|
||||
dc.next++
|
||||
}
|
||||
dc.curr++
|
||||
return ok
|
||||
}
|
||||
|
||||
// makeAddressable returns a value that is always addressable.
|
||||
// It returns the input verbatim if it is already addressable,
|
||||
// otherwise it creates a new value and returns an addressable copy.
|
||||
func makeAddressable(v reflect.Value) reflect.Value {
|
||||
if v.CanAddr() {
|
||||
return v
|
||||
}
|
||||
vc := reflect.New(v.Type()).Elem()
|
||||
vc.Set(v)
|
||||
return vc
|
||||
}
|
||||
31
vendor/github.com/google/go-cmp/cmp/export.go
generated
vendored
Normal file
31
vendor/github.com/google/go-cmp/cmp/export.go
generated
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2017, 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 cmp
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// retrieveUnexportedField uses unsafe to forcibly retrieve any field from
|
||||
// a struct such that the value has read-write permissions.
|
||||
//
|
||||
// The parent struct, v, must be addressable, while f must be a StructField
|
||||
// describing the field to retrieve. If addr is false,
|
||||
// then the returned value will be shallowed copied to be non-addressable.
|
||||
func retrieveUnexportedField(v reflect.Value, f reflect.StructField, addr bool) reflect.Value {
|
||||
ve := reflect.NewAt(f.Type, unsafe.Pointer(uintptr(unsafe.Pointer(v.UnsafeAddr()))+f.Offset)).Elem()
|
||||
if !addr {
|
||||
// A field is addressable if and only if the struct is addressable.
|
||||
// If the original parent value was not addressable, shallow copy the
|
||||
// value to make it non-addressable to avoid leaking an implementation
|
||||
// detail of how forcibly exporting a field works.
|
||||
if ve.Kind() == reflect.Interface && ve.IsNil() {
|
||||
return reflect.Zero(f.Type)
|
||||
}
|
||||
return reflect.ValueOf(ve.Interface()).Convert(f.Type)
|
||||
}
|
||||
return ve
|
||||
}
|
||||
18
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go
generated
vendored
Normal file
18
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go
generated
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright 2017, 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.
|
||||
|
||||
//go:build !cmp_debug
|
||||
// +build !cmp_debug
|
||||
|
||||
package diff
|
||||
|
||||
var debug debugger
|
||||
|
||||
type debugger struct{}
|
||||
|
||||
func (debugger) Begin(_, _ int, f EqualFunc, _, _ *EditScript) EqualFunc {
|
||||
return f
|
||||
}
|
||||
func (debugger) Update() {}
|
||||
func (debugger) Finish() {}
|
||||
123
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go
generated
vendored
Normal file
123
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go
generated
vendored
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
// Copyright 2017, 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.
|
||||
|
||||
//go:build cmp_debug
|
||||
// +build cmp_debug
|
||||
|
||||
package diff
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The algorithm can be seen running in real-time by enabling debugging:
|
||||
// go test -tags=cmp_debug -v
|
||||
//
|
||||
// Example output:
|
||||
// === RUN TestDifference/#34
|
||||
// ┌───────────────────────────────┐
|
||||
// │ \ · · · · · · · · · · · · · · │
|
||||
// │ · # · · · · · · · · · · · · · │
|
||||
// │ · \ · · · · · · · · · · · · · │
|
||||
// │ · · \ · · · · · · · · · · · · │
|
||||
// │ · · · X # · · · · · · · · · · │
|
||||
// │ · · · # \ · · · · · · · · · · │
|
||||
// │ · · · · · # # · · · · · · · · │
|
||||
// │ · · · · · # \ · · · · · · · · │
|
||||
// │ · · · · · · · \ · · · · · · · │
|
||||
// │ · · · · · · · · \ · · · · · · │
|
||||
// │ · · · · · · · · · \ · · · · · │
|
||||
// │ · · · · · · · · · · \ · · # · │
|
||||
// │ · · · · · · · · · · · \ # # · │
|
||||
// │ · · · · · · · · · · · # # # · │
|
||||
// │ · · · · · · · · · · # # # # · │
|
||||
// │ · · · · · · · · · # # # # # · │
|
||||
// │ · · · · · · · · · · · · · · \ │
|
||||
// └───────────────────────────────┘
|
||||
// [.Y..M.XY......YXYXY.|]
|
||||
//
|
||||
// The grid represents the edit-graph where the horizontal axis represents
|
||||
// list X and the vertical axis represents list Y. The start of the two lists
|
||||
// is the top-left, while the ends are the bottom-right. The '·' represents
|
||||
// an unexplored node in the graph. The '\' indicates that the two symbols
|
||||
// from list X and Y are equal. The 'X' indicates that two symbols are similar
|
||||
// (but not exactly equal) to each other. The '#' indicates that the two symbols
|
||||
// are different (and not similar). The algorithm traverses this graph trying to
|
||||
// make the paths starting in the top-left and the bottom-right connect.
|
||||
//
|
||||
// The series of '.', 'X', 'Y', and 'M' characters at the bottom represents
|
||||
// the currently established path from the forward and reverse searches,
|
||||
// separated by a '|' character.
|
||||
|
||||
const (
|
||||
updateDelay = 100 * time.Millisecond
|
||||
finishDelay = 500 * time.Millisecond
|
||||
ansiTerminal = true // ANSI escape codes used to move terminal cursor
|
||||
)
|
||||
|
||||
var debug debugger
|
||||
|
||||
type debugger struct {
|
||||
sync.Mutex
|
||||
p1, p2 EditScript
|
||||
fwdPath, revPath *EditScript
|
||||
grid []byte
|
||||
lines int
|
||||
}
|
||||
|
||||
func (dbg *debugger) Begin(nx, ny int, f EqualFunc, p1, p2 *EditScript) EqualFunc {
|
||||
dbg.Lock()
|
||||
dbg.fwdPath, dbg.revPath = p1, p2
|
||||
top := "┌─" + strings.Repeat("──", nx) + "┐\n"
|
||||
row := "│ " + strings.Repeat("· ", nx) + "│\n"
|
||||
btm := "└─" + strings.Repeat("──", nx) + "┘\n"
|
||||
dbg.grid = []byte(top + strings.Repeat(row, ny) + btm)
|
||||
dbg.lines = strings.Count(dbg.String(), "\n")
|
||||
fmt.Print(dbg)
|
||||
|
||||
// Wrap the EqualFunc so that we can intercept each result.
|
||||
return func(ix, iy int) (r Result) {
|
||||
cell := dbg.grid[len(top)+iy*len(row):][len("│ ")+len("· ")*ix:][:len("·")]
|
||||
for i := range cell {
|
||||
cell[i] = 0 // Zero out the multiple bytes of UTF-8 middle-dot
|
||||
}
|
||||
switch r = f(ix, iy); {
|
||||
case r.Equal():
|
||||
cell[0] = '\\'
|
||||
case r.Similar():
|
||||
cell[0] = 'X'
|
||||
default:
|
||||
cell[0] = '#'
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (dbg *debugger) Update() {
|
||||
dbg.print(updateDelay)
|
||||
}
|
||||
|
||||
func (dbg *debugger) Finish() {
|
||||
dbg.print(finishDelay)
|
||||
dbg.Unlock()
|
||||
}
|
||||
|
||||
func (dbg *debugger) String() string {
|
||||
dbg.p1, dbg.p2 = *dbg.fwdPath, dbg.p2[:0]
|
||||
for i := len(*dbg.revPath) - 1; i >= 0; i-- {
|
||||
dbg.p2 = append(dbg.p2, (*dbg.revPath)[i])
|
||||
}
|
||||
return fmt.Sprintf("%s[%v|%v]\n\n", dbg.grid, dbg.p1, dbg.p2)
|
||||
}
|
||||
|
||||
func (dbg *debugger) print(d time.Duration) {
|
||||
if ansiTerminal {
|
||||
fmt.Printf("\x1b[%dA", dbg.lines) // Reset terminal cursor
|
||||
}
|
||||
fmt.Print(dbg)
|
||||
time.Sleep(d)
|
||||
}
|
||||
402
vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go
generated
vendored
Normal file
402
vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go
generated
vendored
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
// Copyright 2017, 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 diff implements an algorithm for producing edit-scripts.
|
||||
// The edit-script is a sequence of operations needed to transform one list
|
||||
// of symbols into another (or vice-versa). The edits allowed are insertions,
|
||||
// deletions, and modifications. The summation of all edits is called the
|
||||
// Levenshtein distance as this problem is well-known in computer science.
|
||||
//
|
||||
// This package prioritizes performance over accuracy. That is, the run time
|
||||
// is more important than obtaining a minimal Levenshtein distance.
|
||||
package diff
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/flags"
|
||||
)
|
||||
|
||||
// EditType represents a single operation within an edit-script.
|
||||
type EditType uint8
|
||||
|
||||
const (
|
||||
// Identity indicates that a symbol pair is identical in both list X and Y.
|
||||
Identity EditType = iota
|
||||
// UniqueX indicates that a symbol only exists in X and not Y.
|
||||
UniqueX
|
||||
// UniqueY indicates that a symbol only exists in Y and not X.
|
||||
UniqueY
|
||||
// Modified indicates that a symbol pair is a modification of each other.
|
||||
Modified
|
||||
)
|
||||
|
||||
// EditScript represents the series of differences between two lists.
|
||||
type EditScript []EditType
|
||||
|
||||
// String returns a human-readable string representing the edit-script where
|
||||
// Identity, UniqueX, UniqueY, and Modified are represented by the
|
||||
// '.', 'X', 'Y', and 'M' characters, respectively.
|
||||
func (es EditScript) String() string {
|
||||
b := make([]byte, len(es))
|
||||
for i, e := range es {
|
||||
switch e {
|
||||
case Identity:
|
||||
b[i] = '.'
|
||||
case UniqueX:
|
||||
b[i] = 'X'
|
||||
case UniqueY:
|
||||
b[i] = 'Y'
|
||||
case Modified:
|
||||
b[i] = 'M'
|
||||
default:
|
||||
panic("invalid edit-type")
|
||||
}
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// stats returns a histogram of the number of each type of edit operation.
|
||||
func (es EditScript) stats() (s struct{ NI, NX, NY, NM int }) {
|
||||
for _, e := range es {
|
||||
switch e {
|
||||
case Identity:
|
||||
s.NI++
|
||||
case UniqueX:
|
||||
s.NX++
|
||||
case UniqueY:
|
||||
s.NY++
|
||||
case Modified:
|
||||
s.NM++
|
||||
default:
|
||||
panic("invalid edit-type")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Dist is the Levenshtein distance and is guaranteed to be 0 if and only if
|
||||
// lists X and Y are equal.
|
||||
func (es EditScript) Dist() int { return len(es) - es.stats().NI }
|
||||
|
||||
// LenX is the length of the X list.
|
||||
func (es EditScript) LenX() int { return len(es) - es.stats().NY }
|
||||
|
||||
// LenY is the length of the Y list.
|
||||
func (es EditScript) LenY() int { return len(es) - es.stats().NX }
|
||||
|
||||
// EqualFunc reports whether the symbols at indexes ix and iy are equal.
|
||||
// When called by Difference, the index is guaranteed to be within nx and ny.
|
||||
type EqualFunc func(ix int, iy int) Result
|
||||
|
||||
// Result is the result of comparison.
|
||||
// NumSame is the number of sub-elements that are equal.
|
||||
// NumDiff is the number of sub-elements that are not equal.
|
||||
type Result struct{ NumSame, NumDiff int }
|
||||
|
||||
// BoolResult returns a Result that is either Equal or not Equal.
|
||||
func BoolResult(b bool) Result {
|
||||
if b {
|
||||
return Result{NumSame: 1} // Equal, Similar
|
||||
} else {
|
||||
return Result{NumDiff: 2} // Not Equal, not Similar
|
||||
}
|
||||
}
|
||||
|
||||
// Equal indicates whether the symbols are equal. Two symbols are equal
|
||||
// if and only if NumDiff == 0. If Equal, then they are also Similar.
|
||||
func (r Result) Equal() bool { return r.NumDiff == 0 }
|
||||
|
||||
// Similar indicates whether two symbols are similar and may be represented
|
||||
// by using the Modified type. As a special case, we consider binary comparisons
|
||||
// (i.e., those that return Result{1, 0} or Result{0, 1}) to be similar.
|
||||
//
|
||||
// The exact ratio of NumSame to NumDiff to determine similarity may change.
|
||||
func (r Result) Similar() bool {
|
||||
// Use NumSame+1 to offset NumSame so that binary comparisons are similar.
|
||||
return r.NumSame+1 >= r.NumDiff
|
||||
}
|
||||
|
||||
var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0
|
||||
|
||||
// Difference reports whether two lists of lengths nx and ny are equal
|
||||
// given the definition of equality provided as f.
|
||||
//
|
||||
// This function returns an edit-script, which is a sequence of operations
|
||||
// needed to convert one list into the other. The following invariants for
|
||||
// the edit-script are maintained:
|
||||
// - eq == (es.Dist()==0)
|
||||
// - nx == es.LenX()
|
||||
// - ny == es.LenY()
|
||||
//
|
||||
// This algorithm is not guaranteed to be an optimal solution (i.e., one that
|
||||
// produces an edit-script with a minimal Levenshtein distance). This algorithm
|
||||
// favors performance over optimality. The exact output is not guaranteed to
|
||||
// be stable and may change over time.
|
||||
func Difference(nx, ny int, f EqualFunc) (es EditScript) {
|
||||
// This algorithm is based on traversing what is known as an "edit-graph".
|
||||
// See Figure 1 from "An O(ND) Difference Algorithm and Its Variations"
|
||||
// by Eugene W. Myers. Since D can be as large as N itself, this is
|
||||
// effectively O(N^2). Unlike the algorithm from that paper, we are not
|
||||
// interested in the optimal path, but at least some "decent" path.
|
||||
//
|
||||
// For example, let X and Y be lists of symbols:
|
||||
// X = [A B C A B B A]
|
||||
// Y = [C B A B A C]
|
||||
//
|
||||
// The edit-graph can be drawn as the following:
|
||||
// A B C A B B A
|
||||
// ┌─────────────┐
|
||||
// C │_|_|\|_|_|_|_│ 0
|
||||
// B │_|\|_|_|\|\|_│ 1
|
||||
// A │\|_|_|\|_|_|\│ 2
|
||||
// B │_|\|_|_|\|\|_│ 3
|
||||
// A │\|_|_|\|_|_|\│ 4
|
||||
// C │ | |\| | | | │ 5
|
||||
// └─────────────┘ 6
|
||||
// 0 1 2 3 4 5 6 7
|
||||
//
|
||||
// List X is written along the horizontal axis, while list Y is written
|
||||
// along the vertical axis. At any point on this grid, if the symbol in
|
||||
// list X matches the corresponding symbol in list Y, then a '\' is drawn.
|
||||
// The goal of any minimal edit-script algorithm is to find a path from the
|
||||
// top-left corner to the bottom-right corner, while traveling through the
|
||||
// fewest horizontal or vertical edges.
|
||||
// A horizontal edge is equivalent to inserting a symbol from list X.
|
||||
// A vertical edge is equivalent to inserting a symbol from list Y.
|
||||
// A diagonal edge is equivalent to a matching symbol between both X and Y.
|
||||
|
||||
// Invariants:
|
||||
// - 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx
|
||||
// - 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny
|
||||
//
|
||||
// In general:
|
||||
// - fwdFrontier.X < revFrontier.X
|
||||
// - fwdFrontier.Y < revFrontier.Y
|
||||
//
|
||||
// Unless, it is time for the algorithm to terminate.
|
||||
fwdPath := path{+1, point{0, 0}, make(EditScript, 0, (nx+ny)/2)}
|
||||
revPath := path{-1, point{nx, ny}, make(EditScript, 0)}
|
||||
fwdFrontier := fwdPath.point // Forward search frontier
|
||||
revFrontier := revPath.point // Reverse search frontier
|
||||
|
||||
// Search budget bounds the cost of searching for better paths.
|
||||
// The longest sequence of non-matching symbols that can be tolerated is
|
||||
// approximately the square-root of the search budget.
|
||||
searchBudget := 4 * (nx + ny) // O(n)
|
||||
|
||||
// Running the tests with the "cmp_debug" build tag prints a visualization
|
||||
// of the algorithm running in real-time. This is educational for
|
||||
// understanding how the algorithm works. See debug_enable.go.
|
||||
f = debug.Begin(nx, ny, f, &fwdPath.es, &revPath.es)
|
||||
|
||||
// The algorithm below is a greedy, meet-in-the-middle algorithm for
|
||||
// computing sub-optimal edit-scripts between two lists.
|
||||
//
|
||||
// The algorithm is approximately as follows:
|
||||
// - Searching for differences switches back-and-forth between
|
||||
// a search that starts at the beginning (the top-left corner), and
|
||||
// a search that starts at the end (the bottom-right corner).
|
||||
// The goal of the search is connect with the search
|
||||
// from the opposite corner.
|
||||
// - As we search, we build a path in a greedy manner,
|
||||
// where the first match seen is added to the path (this is sub-optimal,
|
||||
// but provides a decent result in practice). When matches are found,
|
||||
// we try the next pair of symbols in the lists and follow all matches
|
||||
// as far as possible.
|
||||
// - When searching for matches, we search along a diagonal going through
|
||||
// through the "frontier" point. If no matches are found,
|
||||
// we advance the frontier towards the opposite corner.
|
||||
// - This algorithm terminates when either the X coordinates or the
|
||||
// Y coordinates of the forward and reverse frontier points ever intersect.
|
||||
|
||||
// This algorithm is correct even if searching only in the forward direction
|
||||
// or in the reverse direction. We do both because it is commonly observed
|
||||
// that two lists commonly differ because elements were added to the front
|
||||
// or end of the other list.
|
||||
//
|
||||
// Non-deterministically start with either the forward or reverse direction
|
||||
// to introduce some deliberate instability so that we have the flexibility
|
||||
// to change this algorithm in the future.
|
||||
if flags.Deterministic || randBool {
|
||||
goto forwardSearch
|
||||
} else {
|
||||
goto reverseSearch
|
||||
}
|
||||
|
||||
forwardSearch:
|
||||
{
|
||||
// Forward search from the beginning.
|
||||
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
|
||||
goto finishSearch
|
||||
}
|
||||
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
|
||||
// Search in a diagonal pattern for a match.
|
||||
z := zigzag(i)
|
||||
p := point{fwdFrontier.X + z, fwdFrontier.Y - z}
|
||||
switch {
|
||||
case p.X >= revPath.X || p.Y < fwdPath.Y:
|
||||
stop1 = true // Hit top-right corner
|
||||
case p.Y >= revPath.Y || p.X < fwdPath.X:
|
||||
stop2 = true // Hit bottom-left corner
|
||||
case f(p.X, p.Y).Equal():
|
||||
// Match found, so connect the path to this point.
|
||||
fwdPath.connect(p, f)
|
||||
fwdPath.append(Identity)
|
||||
// Follow sequence of matches as far as possible.
|
||||
for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
|
||||
if !f(fwdPath.X, fwdPath.Y).Equal() {
|
||||
break
|
||||
}
|
||||
fwdPath.append(Identity)
|
||||
}
|
||||
fwdFrontier = fwdPath.point
|
||||
stop1, stop2 = true, true
|
||||
default:
|
||||
searchBudget-- // Match not found
|
||||
}
|
||||
debug.Update()
|
||||
}
|
||||
// Advance the frontier towards reverse point.
|
||||
if revPath.X-fwdFrontier.X >= revPath.Y-fwdFrontier.Y {
|
||||
fwdFrontier.X++
|
||||
} else {
|
||||
fwdFrontier.Y++
|
||||
}
|
||||
goto reverseSearch
|
||||
}
|
||||
|
||||
reverseSearch:
|
||||
{
|
||||
// Reverse search from the end.
|
||||
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
|
||||
goto finishSearch
|
||||
}
|
||||
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
|
||||
// Search in a diagonal pattern for a match.
|
||||
z := zigzag(i)
|
||||
p := point{revFrontier.X - z, revFrontier.Y + z}
|
||||
switch {
|
||||
case fwdPath.X >= p.X || revPath.Y < p.Y:
|
||||
stop1 = true // Hit bottom-left corner
|
||||
case fwdPath.Y >= p.Y || revPath.X < p.X:
|
||||
stop2 = true // Hit top-right corner
|
||||
case f(p.X-1, p.Y-1).Equal():
|
||||
// Match found, so connect the path to this point.
|
||||
revPath.connect(p, f)
|
||||
revPath.append(Identity)
|
||||
// Follow sequence of matches as far as possible.
|
||||
for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
|
||||
if !f(revPath.X-1, revPath.Y-1).Equal() {
|
||||
break
|
||||
}
|
||||
revPath.append(Identity)
|
||||
}
|
||||
revFrontier = revPath.point
|
||||
stop1, stop2 = true, true
|
||||
default:
|
||||
searchBudget-- // Match not found
|
||||
}
|
||||
debug.Update()
|
||||
}
|
||||
// Advance the frontier towards forward point.
|
||||
if revFrontier.X-fwdPath.X >= revFrontier.Y-fwdPath.Y {
|
||||
revFrontier.X--
|
||||
} else {
|
||||
revFrontier.Y--
|
||||
}
|
||||
goto forwardSearch
|
||||
}
|
||||
|
||||
finishSearch:
|
||||
// Join the forward and reverse paths and then append the reverse path.
|
||||
fwdPath.connect(revPath.point, f)
|
||||
for i := len(revPath.es) - 1; i >= 0; i-- {
|
||||
t := revPath.es[i]
|
||||
revPath.es = revPath.es[:i]
|
||||
fwdPath.append(t)
|
||||
}
|
||||
debug.Finish()
|
||||
return fwdPath.es
|
||||
}
|
||||
|
||||
type path struct {
|
||||
dir int // +1 if forward, -1 if reverse
|
||||
point // Leading point of the EditScript path
|
||||
es EditScript
|
||||
}
|
||||
|
||||
// connect appends any necessary Identity, Modified, UniqueX, or UniqueY types
|
||||
// to the edit-script to connect p.point to dst.
|
||||
func (p *path) connect(dst point, f EqualFunc) {
|
||||
if p.dir > 0 {
|
||||
// Connect in forward direction.
|
||||
for dst.X > p.X && dst.Y > p.Y {
|
||||
switch r := f(p.X, p.Y); {
|
||||
case r.Equal():
|
||||
p.append(Identity)
|
||||
case r.Similar():
|
||||
p.append(Modified)
|
||||
case dst.X-p.X >= dst.Y-p.Y:
|
||||
p.append(UniqueX)
|
||||
default:
|
||||
p.append(UniqueY)
|
||||
}
|
||||
}
|
||||
for dst.X > p.X {
|
||||
p.append(UniqueX)
|
||||
}
|
||||
for dst.Y > p.Y {
|
||||
p.append(UniqueY)
|
||||
}
|
||||
} else {
|
||||
// Connect in reverse direction.
|
||||
for p.X > dst.X && p.Y > dst.Y {
|
||||
switch r := f(p.X-1, p.Y-1); {
|
||||
case r.Equal():
|
||||
p.append(Identity)
|
||||
case r.Similar():
|
||||
p.append(Modified)
|
||||
case p.Y-dst.Y >= p.X-dst.X:
|
||||
p.append(UniqueY)
|
||||
default:
|
||||
p.append(UniqueX)
|
||||
}
|
||||
}
|
||||
for p.X > dst.X {
|
||||
p.append(UniqueX)
|
||||
}
|
||||
for p.Y > dst.Y {
|
||||
p.append(UniqueY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *path) append(t EditType) {
|
||||
p.es = append(p.es, t)
|
||||
switch t {
|
||||
case Identity, Modified:
|
||||
p.add(p.dir, p.dir)
|
||||
case UniqueX:
|
||||
p.add(p.dir, 0)
|
||||
case UniqueY:
|
||||
p.add(0, p.dir)
|
||||
}
|
||||
debug.Update()
|
||||
}
|
||||
|
||||
type point struct{ X, Y int }
|
||||
|
||||
func (p *point) add(dx, dy int) { p.X += dx; p.Y += dy }
|
||||
|
||||
// zigzag maps a consecutive sequence of integers to a zig-zag sequence.
|
||||
//
|
||||
// [0 1 2 3 4 5 ...] => [0 -1 +1 -2 +2 ...]
|
||||
func zigzag(x int) int {
|
||||
if x&1 != 0 {
|
||||
x = ^x
|
||||
}
|
||||
return x >> 1
|
||||
}
|
||||
9
vendor/github.com/google/go-cmp/cmp/internal/flags/flags.go
generated
vendored
Normal file
9
vendor/github.com/google/go-cmp/cmp/internal/flags/flags.go
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// 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 flags
|
||||
|
||||
// Deterministic controls whether the output of Diff should be deterministic.
|
||||
// This is only used for testing.
|
||||
var Deterministic bool
|
||||
106
vendor/github.com/google/go-cmp/cmp/internal/function/func.go
generated
vendored
Normal file
106
vendor/github.com/google/go-cmp/cmp/internal/function/func.go
generated
vendored
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
// Copyright 2017, 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 function provides functionality for identifying function types.
|
||||
package function
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type funcType int
|
||||
|
||||
const (
|
||||
_ funcType = iota
|
||||
|
||||
tbFunc // func(T) bool
|
||||
ttbFunc // func(T, T) bool
|
||||
ttiFunc // func(T, T) int
|
||||
trbFunc // func(T, R) bool
|
||||
tibFunc // func(T, I) bool
|
||||
trFunc // func(T) R
|
||||
|
||||
Equal = ttbFunc // func(T, T) bool
|
||||
EqualAssignable = tibFunc // func(T, I) bool; encapsulates func(T, T) bool
|
||||
Transformer = trFunc // func(T) R
|
||||
ValueFilter = ttbFunc // func(T, T) bool
|
||||
Less = ttbFunc // func(T, T) bool
|
||||
Compare = ttiFunc // func(T, T) int
|
||||
ValuePredicate = tbFunc // func(T) bool
|
||||
KeyValuePredicate = trbFunc // func(T, R) bool
|
||||
)
|
||||
|
||||
var boolType = reflect.TypeOf(true)
|
||||
var intType = reflect.TypeOf(0)
|
||||
|
||||
// IsType reports whether the reflect.Type is of the specified function type.
|
||||
func IsType(t reflect.Type, ft funcType) bool {
|
||||
if t == nil || t.Kind() != reflect.Func || t.IsVariadic() {
|
||||
return false
|
||||
}
|
||||
ni, no := t.NumIn(), t.NumOut()
|
||||
switch ft {
|
||||
case tbFunc: // func(T) bool
|
||||
if ni == 1 && no == 1 && t.Out(0) == boolType {
|
||||
return true
|
||||
}
|
||||
case ttbFunc: // func(T, T) bool
|
||||
if ni == 2 && no == 1 && t.In(0) == t.In(1) && t.Out(0) == boolType {
|
||||
return true
|
||||
}
|
||||
case ttiFunc: // func(T, T) int
|
||||
if ni == 2 && no == 1 && t.In(0) == t.In(1) && t.Out(0) == intType {
|
||||
return true
|
||||
}
|
||||
case trbFunc: // func(T, R) bool
|
||||
if ni == 2 && no == 1 && t.Out(0) == boolType {
|
||||
return true
|
||||
}
|
||||
case tibFunc: // func(T, I) bool
|
||||
if ni == 2 && no == 1 && t.In(0).AssignableTo(t.In(1)) && t.Out(0) == boolType {
|
||||
return true
|
||||
}
|
||||
case trFunc: // func(T) R
|
||||
if ni == 1 && no == 1 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var lastIdentRx = regexp.MustCompile(`[_\p{L}][_\p{L}\p{N}]*$`)
|
||||
|
||||
// NameOf returns the name of the function value.
|
||||
func NameOf(v reflect.Value) string {
|
||||
fnc := runtime.FuncForPC(v.Pointer())
|
||||
if fnc == nil {
|
||||
return "<unknown>"
|
||||
}
|
||||
fullName := fnc.Name() // e.g., "long/path/name/mypkg.(*MyType).(long/path/name/mypkg.myMethod)-fm"
|
||||
|
||||
// Method closures have a "-fm" suffix.
|
||||
fullName = strings.TrimSuffix(fullName, "-fm")
|
||||
|
||||
var name string
|
||||
for len(fullName) > 0 {
|
||||
inParen := strings.HasSuffix(fullName, ")")
|
||||
fullName = strings.TrimSuffix(fullName, ")")
|
||||
|
||||
s := lastIdentRx.FindString(fullName)
|
||||
if s == "" {
|
||||
break
|
||||
}
|
||||
name = s + "." + name
|
||||
fullName = strings.TrimSuffix(fullName, s)
|
||||
|
||||
if i := strings.LastIndexByte(fullName, '('); inParen && i >= 0 {
|
||||
fullName = fullName[:i]
|
||||
}
|
||||
fullName = strings.TrimSuffix(fullName, ".")
|
||||
}
|
||||
return strings.TrimSuffix(name, ".")
|
||||
}
|
||||
164
vendor/github.com/google/go-cmp/cmp/internal/value/name.go
generated
vendored
Normal file
164
vendor/github.com/google/go-cmp/cmp/internal/value/name.go
generated
vendored
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
// Copyright 2020, 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 value
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var anyType = reflect.TypeOf((*interface{})(nil)).Elem()
|
||||
|
||||
// TypeString is nearly identical to reflect.Type.String,
|
||||
// but has an additional option to specify that full type names be used.
|
||||
func TypeString(t reflect.Type, qualified bool) string {
|
||||
return string(appendTypeName(nil, t, qualified, false))
|
||||
}
|
||||
|
||||
func appendTypeName(b []byte, t reflect.Type, qualified, elideFunc bool) []byte {
|
||||
// BUG: Go reflection provides no way to disambiguate two named types
|
||||
// of the same name and within the same package,
|
||||
// but declared within the namespace of different functions.
|
||||
|
||||
// Use the "any" alias instead of "interface{}" for better readability.
|
||||
if t == anyType {
|
||||
return append(b, "any"...)
|
||||
}
|
||||
|
||||
// Named type.
|
||||
if t.Name() != "" {
|
||||
if qualified && t.PkgPath() != "" {
|
||||
b = append(b, '"')
|
||||
b = append(b, t.PkgPath()...)
|
||||
b = append(b, '"')
|
||||
b = append(b, '.')
|
||||
b = append(b, t.Name()...)
|
||||
} else {
|
||||
b = append(b, t.String()...)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Unnamed type.
|
||||
switch k := t.Kind(); k {
|
||||
case reflect.Bool, reflect.String, reflect.UnsafePointer,
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
|
||||
reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
|
||||
b = append(b, k.String()...)
|
||||
case reflect.Chan:
|
||||
if t.ChanDir() == reflect.RecvDir {
|
||||
b = append(b, "<-"...)
|
||||
}
|
||||
b = append(b, "chan"...)
|
||||
if t.ChanDir() == reflect.SendDir {
|
||||
b = append(b, "<-"...)
|
||||
}
|
||||
b = append(b, ' ')
|
||||
b = appendTypeName(b, t.Elem(), qualified, false)
|
||||
case reflect.Func:
|
||||
if !elideFunc {
|
||||
b = append(b, "func"...)
|
||||
}
|
||||
b = append(b, '(')
|
||||
for i := 0; i < t.NumIn(); i++ {
|
||||
if i > 0 {
|
||||
b = append(b, ", "...)
|
||||
}
|
||||
if i == t.NumIn()-1 && t.IsVariadic() {
|
||||
b = append(b, "..."...)
|
||||
b = appendTypeName(b, t.In(i).Elem(), qualified, false)
|
||||
} else {
|
||||
b = appendTypeName(b, t.In(i), qualified, false)
|
||||
}
|
||||
}
|
||||
b = append(b, ')')
|
||||
switch t.NumOut() {
|
||||
case 0:
|
||||
// Do nothing
|
||||
case 1:
|
||||
b = append(b, ' ')
|
||||
b = appendTypeName(b, t.Out(0), qualified, false)
|
||||
default:
|
||||
b = append(b, " ("...)
|
||||
for i := 0; i < t.NumOut(); i++ {
|
||||
if i > 0 {
|
||||
b = append(b, ", "...)
|
||||
}
|
||||
b = appendTypeName(b, t.Out(i), qualified, false)
|
||||
}
|
||||
b = append(b, ')')
|
||||
}
|
||||
case reflect.Struct:
|
||||
b = append(b, "struct{ "...)
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
if i > 0 {
|
||||
b = append(b, "; "...)
|
||||
}
|
||||
sf := t.Field(i)
|
||||
if !sf.Anonymous {
|
||||
if qualified && sf.PkgPath != "" {
|
||||
b = append(b, '"')
|
||||
b = append(b, sf.PkgPath...)
|
||||
b = append(b, '"')
|
||||
b = append(b, '.')
|
||||
}
|
||||
b = append(b, sf.Name...)
|
||||
b = append(b, ' ')
|
||||
}
|
||||
b = appendTypeName(b, sf.Type, qualified, false)
|
||||
if sf.Tag != "" {
|
||||
b = append(b, ' ')
|
||||
b = strconv.AppendQuote(b, string(sf.Tag))
|
||||
}
|
||||
}
|
||||
if b[len(b)-1] == ' ' {
|
||||
b = b[:len(b)-1]
|
||||
} else {
|
||||
b = append(b, ' ')
|
||||
}
|
||||
b = append(b, '}')
|
||||
case reflect.Slice, reflect.Array:
|
||||
b = append(b, '[')
|
||||
if k == reflect.Array {
|
||||
b = strconv.AppendUint(b, uint64(t.Len()), 10)
|
||||
}
|
||||
b = append(b, ']')
|
||||
b = appendTypeName(b, t.Elem(), qualified, false)
|
||||
case reflect.Map:
|
||||
b = append(b, "map["...)
|
||||
b = appendTypeName(b, t.Key(), qualified, false)
|
||||
b = append(b, ']')
|
||||
b = appendTypeName(b, t.Elem(), qualified, false)
|
||||
case reflect.Ptr:
|
||||
b = append(b, '*')
|
||||
b = appendTypeName(b, t.Elem(), qualified, false)
|
||||
case reflect.Interface:
|
||||
b = append(b, "interface{ "...)
|
||||
for i := 0; i < t.NumMethod(); i++ {
|
||||
if i > 0 {
|
||||
b = append(b, "; "...)
|
||||
}
|
||||
m := t.Method(i)
|
||||
if qualified && m.PkgPath != "" {
|
||||
b = append(b, '"')
|
||||
b = append(b, m.PkgPath...)
|
||||
b = append(b, '"')
|
||||
b = append(b, '.')
|
||||
}
|
||||
b = append(b, m.Name...)
|
||||
b = appendTypeName(b, m.Type, qualified, true)
|
||||
}
|
||||
if b[len(b)-1] == ' ' {
|
||||
b = b[:len(b)-1]
|
||||
} else {
|
||||
b = append(b, ' ')
|
||||
}
|
||||
b = append(b, '}')
|
||||
default:
|
||||
panic("invalid kind: " + k.String())
|
||||
}
|
||||
return b
|
||||
}
|
||||
34
vendor/github.com/google/go-cmp/cmp/internal/value/pointer.go
generated
vendored
Normal file
34
vendor/github.com/google/go-cmp/cmp/internal/value/pointer.go
generated
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright 2018, 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 value
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Pointer is an opaque typed pointer and is guaranteed to be comparable.
|
||||
type Pointer struct {
|
||||
p unsafe.Pointer
|
||||
t reflect.Type
|
||||
}
|
||||
|
||||
// PointerOf returns a Pointer from v, which must be a
|
||||
// reflect.Ptr, reflect.Slice, or reflect.Map.
|
||||
func PointerOf(v reflect.Value) Pointer {
|
||||
// The proper representation of a pointer is unsafe.Pointer,
|
||||
// which is necessary if the GC ever uses a moving collector.
|
||||
return Pointer{unsafe.Pointer(v.Pointer()), v.Type()}
|
||||
}
|
||||
|
||||
// IsNil reports whether the pointer is nil.
|
||||
func (p Pointer) IsNil() bool {
|
||||
return p.p == nil
|
||||
}
|
||||
|
||||
// Uintptr returns the pointer as a uintptr.
|
||||
func (p Pointer) Uintptr() uintptr {
|
||||
return uintptr(p.p)
|
||||
}
|
||||
106
vendor/github.com/google/go-cmp/cmp/internal/value/sort.go
generated
vendored
Normal file
106
vendor/github.com/google/go-cmp/cmp/internal/value/sort.go
generated
vendored
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
// Copyright 2017, 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 value
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// SortKeys sorts a list of map keys, deduplicating keys if necessary.
|
||||
// The type of each value must be comparable.
|
||||
func SortKeys(vs []reflect.Value) []reflect.Value {
|
||||
if len(vs) == 0 {
|
||||
return vs
|
||||
}
|
||||
|
||||
// Sort the map keys.
|
||||
sort.SliceStable(vs, func(i, j int) bool { return isLess(vs[i], vs[j]) })
|
||||
|
||||
// Deduplicate keys (fails for NaNs).
|
||||
vs2 := vs[:1]
|
||||
for _, v := range vs[1:] {
|
||||
if isLess(vs2[len(vs2)-1], v) {
|
||||
vs2 = append(vs2, v)
|
||||
}
|
||||
}
|
||||
return vs2
|
||||
}
|
||||
|
||||
// isLess is a generic function for sorting arbitrary map keys.
|
||||
// The inputs must be of the same type and must be comparable.
|
||||
func isLess(x, y reflect.Value) bool {
|
||||
switch x.Type().Kind() {
|
||||
case reflect.Bool:
|
||||
return !x.Bool() && y.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return x.Int() < y.Int()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return x.Uint() < y.Uint()
|
||||
case reflect.Float32, reflect.Float64:
|
||||
// NOTE: This does not sort -0 as less than +0
|
||||
// since Go maps treat -0 and +0 as equal keys.
|
||||
fx, fy := x.Float(), y.Float()
|
||||
return fx < fy || math.IsNaN(fx) && !math.IsNaN(fy)
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
cx, cy := x.Complex(), y.Complex()
|
||||
rx, ix, ry, iy := real(cx), imag(cx), real(cy), imag(cy)
|
||||
if rx == ry || (math.IsNaN(rx) && math.IsNaN(ry)) {
|
||||
return ix < iy || math.IsNaN(ix) && !math.IsNaN(iy)
|
||||
}
|
||||
return rx < ry || math.IsNaN(rx) && !math.IsNaN(ry)
|
||||
case reflect.Ptr, reflect.UnsafePointer, reflect.Chan:
|
||||
return x.Pointer() < y.Pointer()
|
||||
case reflect.String:
|
||||
return x.String() < y.String()
|
||||
case reflect.Array:
|
||||
for i := 0; i < x.Len(); i++ {
|
||||
if isLess(x.Index(i), y.Index(i)) {
|
||||
return true
|
||||
}
|
||||
if isLess(y.Index(i), x.Index(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
case reflect.Struct:
|
||||
for i := 0; i < x.NumField(); i++ {
|
||||
if isLess(x.Field(i), y.Field(i)) {
|
||||
return true
|
||||
}
|
||||
if isLess(y.Field(i), x.Field(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
case reflect.Interface:
|
||||
vx, vy := x.Elem(), y.Elem()
|
||||
if !vx.IsValid() || !vy.IsValid() {
|
||||
return !vx.IsValid() && vy.IsValid()
|
||||
}
|
||||
tx, ty := vx.Type(), vy.Type()
|
||||
if tx == ty {
|
||||
return isLess(x.Elem(), y.Elem())
|
||||
}
|
||||
if tx.Kind() != ty.Kind() {
|
||||
return vx.Kind() < vy.Kind()
|
||||
}
|
||||
if tx.String() != ty.String() {
|
||||
return tx.String() < ty.String()
|
||||
}
|
||||
if tx.PkgPath() != ty.PkgPath() {
|
||||
return tx.PkgPath() < ty.PkgPath()
|
||||
}
|
||||
// This can happen in rare situations, so we fallback to just comparing
|
||||
// the unique pointer for a reflect.Type. This guarantees deterministic
|
||||
// ordering within a program, but it is obviously not stable.
|
||||
return reflect.ValueOf(vx.Type()).Pointer() < reflect.ValueOf(vy.Type()).Pointer()
|
||||
default:
|
||||
// Must be Func, Map, or Slice; which are not comparable.
|
||||
panic(fmt.Sprintf("%T is not comparable", x.Type()))
|
||||
}
|
||||
}
|
||||
562
vendor/github.com/google/go-cmp/cmp/options.go
generated
vendored
Normal file
562
vendor/github.com/google/go-cmp/cmp/options.go
generated
vendored
Normal file
|
|
@ -0,0 +1,562 @@
|
|||
// Copyright 2017, 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 cmp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/function"
|
||||
)
|
||||
|
||||
// Option configures for specific behavior of [Equal] and [Diff]. In particular,
|
||||
// the fundamental Option functions ([Ignore], [Transformer], and [Comparer]),
|
||||
// configure how equality is determined.
|
||||
//
|
||||
// The fundamental options may be composed with filters ([FilterPath] and
|
||||
// [FilterValues]) to control the scope over which they are applied.
|
||||
//
|
||||
// The [github.com/google/go-cmp/cmp/cmpopts] package provides helper functions
|
||||
// for creating options that may be used with [Equal] and [Diff].
|
||||
type Option interface {
|
||||
// filter applies all filters and returns the option that remains.
|
||||
// Each option may only read s.curPath and call s.callTTBFunc.
|
||||
//
|
||||
// An Options is returned only if multiple comparers or transformers
|
||||
// can apply simultaneously and will only contain values of those types
|
||||
// or sub-Options containing values of those types.
|
||||
filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption
|
||||
}
|
||||
|
||||
// applicableOption represents the following types:
|
||||
//
|
||||
// Fundamental: ignore | validator | *comparer | *transformer
|
||||
// Grouping: Options
|
||||
type applicableOption interface {
|
||||
Option
|
||||
|
||||
// apply executes the option, which may mutate s or panic.
|
||||
apply(s *state, vx, vy reflect.Value)
|
||||
}
|
||||
|
||||
// coreOption represents the following types:
|
||||
//
|
||||
// Fundamental: ignore | validator | *comparer | *transformer
|
||||
// Filters: *pathFilter | *valuesFilter
|
||||
type coreOption interface {
|
||||
Option
|
||||
isCore()
|
||||
}
|
||||
|
||||
type core struct{}
|
||||
|
||||
func (core) isCore() {}
|
||||
|
||||
// Options is a list of [Option] values that also satisfies the [Option] interface.
|
||||
// Helper comparison packages may return an Options value when packing multiple
|
||||
// [Option] values into a single [Option]. When this package processes an Options,
|
||||
// it will be implicitly expanded into a flat list.
|
||||
//
|
||||
// Applying a filter on an Options is equivalent to applying that same filter
|
||||
// on all individual options held within.
|
||||
type Options []Option
|
||||
|
||||
func (opts Options) filter(s *state, t reflect.Type, vx, vy reflect.Value) (out applicableOption) {
|
||||
for _, opt := range opts {
|
||||
switch opt := opt.filter(s, t, vx, vy); opt.(type) {
|
||||
case ignore:
|
||||
return ignore{} // Only ignore can short-circuit evaluation
|
||||
case validator:
|
||||
out = validator{} // Takes precedence over comparer or transformer
|
||||
case *comparer, *transformer, Options:
|
||||
switch out.(type) {
|
||||
case nil:
|
||||
out = opt
|
||||
case validator:
|
||||
// Keep validator
|
||||
case *comparer, *transformer, Options:
|
||||
out = Options{out, opt} // Conflicting comparers or transformers
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (opts Options) apply(s *state, _, _ reflect.Value) {
|
||||
const warning = "ambiguous set of applicable options"
|
||||
const help = "consider using filters to ensure at most one Comparer or Transformer may apply"
|
||||
var ss []string
|
||||
for _, opt := range flattenOptions(nil, opts) {
|
||||
ss = append(ss, fmt.Sprint(opt))
|
||||
}
|
||||
set := strings.Join(ss, "\n\t")
|
||||
panic(fmt.Sprintf("%s at %#v:\n\t%s\n%s", warning, s.curPath, set, help))
|
||||
}
|
||||
|
||||
func (opts Options) String() string {
|
||||
var ss []string
|
||||
for _, opt := range opts {
|
||||
ss = append(ss, fmt.Sprint(opt))
|
||||
}
|
||||
return fmt.Sprintf("Options{%s}", strings.Join(ss, ", "))
|
||||
}
|
||||
|
||||
// FilterPath returns a new [Option] where opt is only evaluated if filter f
|
||||
// returns true for the current [Path] in the value tree.
|
||||
//
|
||||
// This filter is called even if a slice element or map entry is missing and
|
||||
// provides an opportunity to ignore such cases. The filter function must be
|
||||
// symmetric such that the filter result is identical regardless of whether the
|
||||
// missing value is from x or y.
|
||||
//
|
||||
// The option passed in may be an [Ignore], [Transformer], [Comparer], [Options], or
|
||||
// a previously filtered [Option].
|
||||
func FilterPath(f func(Path) bool, opt Option) Option {
|
||||
if f == nil {
|
||||
panic("invalid path filter function")
|
||||
}
|
||||
if opt := normalizeOption(opt); opt != nil {
|
||||
return &pathFilter{fnc: f, opt: opt}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type pathFilter struct {
|
||||
core
|
||||
fnc func(Path) bool
|
||||
opt Option
|
||||
}
|
||||
|
||||
func (f pathFilter) filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption {
|
||||
if f.fnc(s.curPath) {
|
||||
return f.opt.filter(s, t, vx, vy)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f pathFilter) String() string {
|
||||
return fmt.Sprintf("FilterPath(%s, %v)", function.NameOf(reflect.ValueOf(f.fnc)), f.opt)
|
||||
}
|
||||
|
||||
// FilterValues returns a new [Option] where opt is only evaluated if filter f,
|
||||
// which is a function of the form "func(T, T) bool", returns true for the
|
||||
// current pair of values being compared. If either value is invalid or
|
||||
// the type of the values is not assignable to T, then this filter implicitly
|
||||
// returns false.
|
||||
//
|
||||
// The filter function must be
|
||||
// symmetric (i.e., agnostic to the order of the inputs) and
|
||||
// deterministic (i.e., produces the same result when given the same inputs).
|
||||
// If T is an interface, it is possible that f is called with two values with
|
||||
// different concrete types that both implement T.
|
||||
//
|
||||
// The option passed in may be an [Ignore], [Transformer], [Comparer], [Options], or
|
||||
// a previously filtered [Option].
|
||||
func FilterValues(f interface{}, opt Option) Option {
|
||||
v := reflect.ValueOf(f)
|
||||
if !function.IsType(v.Type(), function.ValueFilter) || v.IsNil() {
|
||||
panic(fmt.Sprintf("invalid values filter function: %T", f))
|
||||
}
|
||||
if opt := normalizeOption(opt); opt != nil {
|
||||
vf := &valuesFilter{fnc: v, opt: opt}
|
||||
if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
|
||||
vf.typ = ti
|
||||
}
|
||||
return vf
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type valuesFilter struct {
|
||||
core
|
||||
typ reflect.Type // T
|
||||
fnc reflect.Value // func(T, T) bool
|
||||
opt Option
|
||||
}
|
||||
|
||||
func (f valuesFilter) filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption {
|
||||
if !vx.IsValid() || !vx.CanInterface() || !vy.IsValid() || !vy.CanInterface() {
|
||||
return nil
|
||||
}
|
||||
if (f.typ == nil || t.AssignableTo(f.typ)) && s.callTTBFunc(f.fnc, vx, vy) {
|
||||
return f.opt.filter(s, t, vx, vy)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f valuesFilter) String() string {
|
||||
return fmt.Sprintf("FilterValues(%s, %v)", function.NameOf(f.fnc), f.opt)
|
||||
}
|
||||
|
||||
// Ignore is an [Option] that causes all comparisons to be ignored.
|
||||
// This value is intended to be combined with [FilterPath] or [FilterValues].
|
||||
// It is an error to pass an unfiltered Ignore option to [Equal].
|
||||
func Ignore() Option { return ignore{} }
|
||||
|
||||
type ignore struct{ core }
|
||||
|
||||
func (ignore) isFiltered() bool { return false }
|
||||
func (ignore) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption { return ignore{} }
|
||||
func (ignore) apply(s *state, _, _ reflect.Value) { s.report(true, reportByIgnore) }
|
||||
func (ignore) String() string { return "Ignore()" }
|
||||
|
||||
// validator is a sentinel Option type to indicate that some options could not
|
||||
// be evaluated due to unexported fields, missing slice elements, or
|
||||
// missing map entries. Both values are validator only for unexported fields.
|
||||
type validator struct{ core }
|
||||
|
||||
func (validator) filter(_ *state, _ reflect.Type, vx, vy reflect.Value) applicableOption {
|
||||
if !vx.IsValid() || !vy.IsValid() {
|
||||
return validator{}
|
||||
}
|
||||
if !vx.CanInterface() || !vy.CanInterface() {
|
||||
return validator{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (validator) apply(s *state, vx, vy reflect.Value) {
|
||||
// Implies missing slice element or map entry.
|
||||
if !vx.IsValid() || !vy.IsValid() {
|
||||
s.report(vx.IsValid() == vy.IsValid(), 0)
|
||||
return
|
||||
}
|
||||
|
||||
// Unable to Interface implies unexported field without visibility access.
|
||||
if !vx.CanInterface() || !vy.CanInterface() {
|
||||
help := "consider using a custom Comparer; if you control the implementation of type, you can also consider using an Exporter, AllowUnexported, or cmpopts.IgnoreUnexported"
|
||||
var name string
|
||||
if t := s.curPath.Index(-2).Type(); t.Name() != "" {
|
||||
// Named type with unexported fields.
|
||||
name = fmt.Sprintf("%q.%v", t.PkgPath(), t.Name()) // e.g., "path/to/package".MyType
|
||||
isProtoMessage := func(t reflect.Type) bool {
|
||||
m, ok := reflect.PointerTo(t).MethodByName("ProtoReflect")
|
||||
return ok && m.Type.NumIn() == 1 && m.Type.NumOut() == 1 &&
|
||||
m.Type.Out(0).PkgPath() == "google.golang.org/protobuf/reflect/protoreflect" &&
|
||||
m.Type.Out(0).Name() == "Message"
|
||||
}
|
||||
if isProtoMessage(t) {
|
||||
help = `consider using "google.golang.org/protobuf/testing/protocmp".Transform to compare proto.Message types`
|
||||
} else if _, ok := reflect.New(t).Interface().(error); ok {
|
||||
help = "consider using cmpopts.EquateErrors to compare error values"
|
||||
} else if t.Comparable() {
|
||||
help = "consider using cmpopts.EquateComparable to compare comparable Go types"
|
||||
}
|
||||
} else {
|
||||
// Unnamed type with unexported fields. Derive PkgPath from field.
|
||||
var pkgPath string
|
||||
for i := 0; i < t.NumField() && pkgPath == ""; i++ {
|
||||
pkgPath = t.Field(i).PkgPath
|
||||
}
|
||||
name = fmt.Sprintf("%q.(%v)", pkgPath, t.String()) // e.g., "path/to/package".(struct { a int })
|
||||
}
|
||||
panic(fmt.Sprintf("cannot handle unexported field at %#v:\n\t%v\n%s", s.curPath, name, help))
|
||||
}
|
||||
|
||||
panic("not reachable")
|
||||
}
|
||||
|
||||
// identRx represents a valid identifier according to the Go specification.
|
||||
const identRx = `[_\p{L}][_\p{L}\p{N}]*`
|
||||
|
||||
var identsRx = regexp.MustCompile(`^` + identRx + `(\.` + identRx + `)*$`)
|
||||
|
||||
// Transformer returns an [Option] that applies a transformation function that
|
||||
// converts values of a certain type into that of another.
|
||||
//
|
||||
// The transformer f must be a function "func(T) R" that converts values of
|
||||
// type T to those of type R and is implicitly filtered to input values
|
||||
// assignable to T. The transformer must not mutate T in any way.
|
||||
//
|
||||
// To help prevent some cases of infinite recursive cycles applying the
|
||||
// same transform to the output of itself (e.g., in the case where the
|
||||
// input and output types are the same), an implicit filter is added such that
|
||||
// a transformer is applicable only if that exact transformer is not already
|
||||
// in the tail of the [Path] since the last non-[Transform] step.
|
||||
// For situations where the implicit filter is still insufficient,
|
||||
// consider using [github.com/google/go-cmp/cmp/cmpopts.AcyclicTransformer],
|
||||
// which adds a filter to prevent the transformer from
|
||||
// being recursively applied upon itself.
|
||||
//
|
||||
// The name is a user provided label that is used as the [Transform.Name] in the
|
||||
// transformation [PathStep] (and eventually shown in the [Diff] output).
|
||||
// The name must be a valid identifier or qualified identifier in Go syntax.
|
||||
// If empty, an arbitrary name is used.
|
||||
func Transformer(name string, f interface{}) Option {
|
||||
v := reflect.ValueOf(f)
|
||||
if !function.IsType(v.Type(), function.Transformer) || v.IsNil() {
|
||||
panic(fmt.Sprintf("invalid transformer function: %T", f))
|
||||
}
|
||||
if name == "" {
|
||||
name = function.NameOf(v)
|
||||
if !identsRx.MatchString(name) {
|
||||
name = "λ" // Lambda-symbol as placeholder name
|
||||
}
|
||||
} else if !identsRx.MatchString(name) {
|
||||
panic(fmt.Sprintf("invalid name: %q", name))
|
||||
}
|
||||
tr := &transformer{name: name, fnc: reflect.ValueOf(f)}
|
||||
if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
|
||||
tr.typ = ti
|
||||
}
|
||||
return tr
|
||||
}
|
||||
|
||||
type transformer struct {
|
||||
core
|
||||
name string
|
||||
typ reflect.Type // T
|
||||
fnc reflect.Value // func(T) R
|
||||
}
|
||||
|
||||
func (tr *transformer) isFiltered() bool { return tr.typ != nil }
|
||||
|
||||
func (tr *transformer) filter(s *state, t reflect.Type, _, _ reflect.Value) applicableOption {
|
||||
for i := len(s.curPath) - 1; i >= 0; i-- {
|
||||
if t, ok := s.curPath[i].(Transform); !ok {
|
||||
break // Hit most recent non-Transform step
|
||||
} else if tr == t.trans {
|
||||
return nil // Cannot directly use same Transform
|
||||
}
|
||||
}
|
||||
if tr.typ == nil || t.AssignableTo(tr.typ) {
|
||||
return tr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tr *transformer) apply(s *state, vx, vy reflect.Value) {
|
||||
step := Transform{&transform{pathStep{typ: tr.fnc.Type().Out(0)}, tr}}
|
||||
vvx := s.callTRFunc(tr.fnc, vx, step)
|
||||
vvy := s.callTRFunc(tr.fnc, vy, step)
|
||||
step.vx, step.vy = vvx, vvy
|
||||
s.compareAny(step)
|
||||
}
|
||||
|
||||
func (tr transformer) String() string {
|
||||
return fmt.Sprintf("Transformer(%s, %s)", tr.name, function.NameOf(tr.fnc))
|
||||
}
|
||||
|
||||
// Comparer returns an [Option] that determines whether two values are equal
|
||||
// to each other.
|
||||
//
|
||||
// The comparer f must be a function "func(T, T) bool" and is implicitly
|
||||
// filtered to input values assignable to T. If T is an interface, it is
|
||||
// possible that f is called with two values of different concrete types that
|
||||
// both implement T.
|
||||
//
|
||||
// The equality function must be:
|
||||
// - Symmetric: equal(x, y) == equal(y, x)
|
||||
// - Deterministic: equal(x, y) == equal(x, y)
|
||||
// - Pure: equal(x, y) does not modify x or y
|
||||
func Comparer(f interface{}) Option {
|
||||
v := reflect.ValueOf(f)
|
||||
if !function.IsType(v.Type(), function.Equal) || v.IsNil() {
|
||||
panic(fmt.Sprintf("invalid comparer function: %T", f))
|
||||
}
|
||||
cm := &comparer{fnc: v}
|
||||
if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
|
||||
cm.typ = ti
|
||||
}
|
||||
return cm
|
||||
}
|
||||
|
||||
type comparer struct {
|
||||
core
|
||||
typ reflect.Type // T
|
||||
fnc reflect.Value // func(T, T) bool
|
||||
}
|
||||
|
||||
func (cm *comparer) isFiltered() bool { return cm.typ != nil }
|
||||
|
||||
func (cm *comparer) filter(_ *state, t reflect.Type, _, _ reflect.Value) applicableOption {
|
||||
if cm.typ == nil || t.AssignableTo(cm.typ) {
|
||||
return cm
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cm *comparer) apply(s *state, vx, vy reflect.Value) {
|
||||
eq := s.callTTBFunc(cm.fnc, vx, vy)
|
||||
s.report(eq, reportByFunc)
|
||||
}
|
||||
|
||||
func (cm comparer) String() string {
|
||||
return fmt.Sprintf("Comparer(%s)", function.NameOf(cm.fnc))
|
||||
}
|
||||
|
||||
// Exporter returns an [Option] that specifies whether [Equal] is allowed to
|
||||
// introspect into the unexported fields of certain struct types.
|
||||
//
|
||||
// Users of this option must understand that comparing on unexported fields
|
||||
// from external packages is not safe since changes in the internal
|
||||
// implementation of some external package may cause the result of [Equal]
|
||||
// to unexpectedly change. However, it may be valid to use this option on types
|
||||
// defined in an internal package where the semantic meaning of an unexported
|
||||
// field is in the control of the user.
|
||||
//
|
||||
// In many cases, a custom [Comparer] should be used instead that defines
|
||||
// equality as a function of the public API of a type rather than the underlying
|
||||
// unexported implementation.
|
||||
//
|
||||
// For example, the [reflect.Type] documentation defines equality to be determined
|
||||
// by the == operator on the interface (essentially performing a shallow pointer
|
||||
// comparison) and most attempts to compare *[regexp.Regexp] types are interested
|
||||
// in only checking that the regular expression strings are equal.
|
||||
// Both of these are accomplished using [Comparer] options:
|
||||
//
|
||||
// Comparer(func(x, y reflect.Type) bool { return x == y })
|
||||
// Comparer(func(x, y *regexp.Regexp) bool { return x.String() == y.String() })
|
||||
//
|
||||
// In other cases, the [github.com/google/go-cmp/cmp/cmpopts.IgnoreUnexported]
|
||||
// option can be used to ignore all unexported fields on specified struct types.
|
||||
func Exporter(f func(reflect.Type) bool) Option {
|
||||
return exporter(f)
|
||||
}
|
||||
|
||||
type exporter func(reflect.Type) bool
|
||||
|
||||
func (exporter) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// AllowUnexported returns an [Option] that allows [Equal] to forcibly introspect
|
||||
// unexported fields of the specified struct types.
|
||||
//
|
||||
// See [Exporter] for the proper use of this option.
|
||||
func AllowUnexported(types ...interface{}) Option {
|
||||
m := make(map[reflect.Type]bool)
|
||||
for _, typ := range types {
|
||||
t := reflect.TypeOf(typ)
|
||||
if t.Kind() != reflect.Struct {
|
||||
panic(fmt.Sprintf("invalid struct type: %T", typ))
|
||||
}
|
||||
m[t] = true
|
||||
}
|
||||
return exporter(func(t reflect.Type) bool { return m[t] })
|
||||
}
|
||||
|
||||
// Result represents the comparison result for a single node and
|
||||
// is provided by cmp when calling Report (see [Reporter]).
|
||||
type Result struct {
|
||||
_ [0]func() // Make Result incomparable
|
||||
flags resultFlags
|
||||
}
|
||||
|
||||
// Equal reports whether the node was determined to be equal or not.
|
||||
// As a special case, ignored nodes are considered equal.
|
||||
func (r Result) Equal() bool {
|
||||
return r.flags&(reportEqual|reportByIgnore) != 0
|
||||
}
|
||||
|
||||
// ByIgnore reports whether the node is equal because it was ignored.
|
||||
// This never reports true if [Result.Equal] reports false.
|
||||
func (r Result) ByIgnore() bool {
|
||||
return r.flags&reportByIgnore != 0
|
||||
}
|
||||
|
||||
// ByMethod reports whether the Equal method determined equality.
|
||||
func (r Result) ByMethod() bool {
|
||||
return r.flags&reportByMethod != 0
|
||||
}
|
||||
|
||||
// ByFunc reports whether a [Comparer] function determined equality.
|
||||
func (r Result) ByFunc() bool {
|
||||
return r.flags&reportByFunc != 0
|
||||
}
|
||||
|
||||
// ByCycle reports whether a reference cycle was detected.
|
||||
func (r Result) ByCycle() bool {
|
||||
return r.flags&reportByCycle != 0
|
||||
}
|
||||
|
||||
type resultFlags uint
|
||||
|
||||
const (
|
||||
_ resultFlags = (1 << iota) / 2
|
||||
|
||||
reportEqual
|
||||
reportUnequal
|
||||
reportByIgnore
|
||||
reportByMethod
|
||||
reportByFunc
|
||||
reportByCycle
|
||||
)
|
||||
|
||||
// Reporter is an [Option] that can be passed to [Equal]. When [Equal] traverses
|
||||
// the value trees, it calls PushStep as it descends into each node in the
|
||||
// tree and PopStep as it ascend out of the node. The leaves of the tree are
|
||||
// either compared (determined to be equal or not equal) or ignored and reported
|
||||
// as such by calling the Report method.
|
||||
func Reporter(r interface {
|
||||
// PushStep is called when a tree-traversal operation is performed.
|
||||
// The PathStep itself is only valid until the step is popped.
|
||||
// The PathStep.Values are valid for the duration of the entire traversal
|
||||
// and must not be mutated.
|
||||
//
|
||||
// Equal always calls PushStep at the start to provide an operation-less
|
||||
// PathStep used to report the root values.
|
||||
//
|
||||
// Within a slice, the exact set of inserted, removed, or modified elements
|
||||
// is unspecified and may change in future implementations.
|
||||
// The entries of a map are iterated through in an unspecified order.
|
||||
PushStep(PathStep)
|
||||
|
||||
// Report is called exactly once on leaf nodes to report whether the
|
||||
// comparison identified the node as equal, unequal, or ignored.
|
||||
// A leaf node is one that is immediately preceded by and followed by
|
||||
// a pair of PushStep and PopStep calls.
|
||||
Report(Result)
|
||||
|
||||
// PopStep ascends back up the value tree.
|
||||
// There is always a matching pop call for every push call.
|
||||
PopStep()
|
||||
}) Option {
|
||||
return reporter{r}
|
||||
}
|
||||
|
||||
type reporter struct{ reporterIface }
|
||||
type reporterIface interface {
|
||||
PushStep(PathStep)
|
||||
Report(Result)
|
||||
PopStep()
|
||||
}
|
||||
|
||||
func (reporter) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// normalizeOption normalizes the input options such that all Options groups
|
||||
// are flattened and groups with a single element are reduced to that element.
|
||||
// Only coreOptions and Options containing coreOptions are allowed.
|
||||
func normalizeOption(src Option) Option {
|
||||
switch opts := flattenOptions(nil, Options{src}); len(opts) {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
return opts[0]
|
||||
default:
|
||||
return opts
|
||||
}
|
||||
}
|
||||
|
||||
// flattenOptions copies all options in src to dst as a flat list.
|
||||
// Only coreOptions and Options containing coreOptions are allowed.
|
||||
func flattenOptions(dst, src Options) Options {
|
||||
for _, opt := range src {
|
||||
switch opt := opt.(type) {
|
||||
case nil:
|
||||
continue
|
||||
case Options:
|
||||
dst = flattenOptions(dst, opt)
|
||||
case coreOption:
|
||||
dst = append(dst, opt)
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid option type: %T", opt))
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
390
vendor/github.com/google/go-cmp/cmp/path.go
generated
vendored
Normal file
390
vendor/github.com/google/go-cmp/cmp/path.go
generated
vendored
Normal file
|
|
@ -0,0 +1,390 @@
|
|||
// Copyright 2017, 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 cmp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/value"
|
||||
)
|
||||
|
||||
// Path is a list of [PathStep] describing the sequence of operations to get
|
||||
// from some root type to the current position in the value tree.
|
||||
// The first Path element is always an operation-less [PathStep] that exists
|
||||
// simply to identify the initial type.
|
||||
//
|
||||
// When traversing structs with embedded structs, the embedded struct will
|
||||
// always be accessed as a field before traversing the fields of the
|
||||
// embedded struct themselves. That is, an exported field from the
|
||||
// embedded struct will never be accessed directly from the parent struct.
|
||||
type Path []PathStep
|
||||
|
||||
// PathStep is a union-type for specific operations to traverse
|
||||
// a value's tree structure. Users of this package never need to implement
|
||||
// these types as values of this type will be returned by this package.
|
||||
//
|
||||
// Implementations of this interface:
|
||||
// - [StructField]
|
||||
// - [SliceIndex]
|
||||
// - [MapIndex]
|
||||
// - [Indirect]
|
||||
// - [TypeAssertion]
|
||||
// - [Transform]
|
||||
type PathStep interface {
|
||||
String() string
|
||||
|
||||
// Type is the resulting type after performing the path step.
|
||||
Type() reflect.Type
|
||||
|
||||
// Values is the resulting values after performing the path step.
|
||||
// The type of each valid value is guaranteed to be identical to Type.
|
||||
//
|
||||
// In some cases, one or both may be invalid or have restrictions:
|
||||
// - For StructField, both are not interface-able if the current field
|
||||
// is unexported and the struct type is not explicitly permitted by
|
||||
// an Exporter to traverse unexported fields.
|
||||
// - For SliceIndex, one may be invalid if an element is missing from
|
||||
// either the x or y slice.
|
||||
// - For MapIndex, one may be invalid if an entry is missing from
|
||||
// either the x or y map.
|
||||
//
|
||||
// The provided values must not be mutated.
|
||||
Values() (vx, vy reflect.Value)
|
||||
}
|
||||
|
||||
var (
|
||||
_ PathStep = StructField{}
|
||||
_ PathStep = SliceIndex{}
|
||||
_ PathStep = MapIndex{}
|
||||
_ PathStep = Indirect{}
|
||||
_ PathStep = TypeAssertion{}
|
||||
_ PathStep = Transform{}
|
||||
)
|
||||
|
||||
func (pa *Path) push(s PathStep) {
|
||||
*pa = append(*pa, s)
|
||||
}
|
||||
|
||||
func (pa *Path) pop() {
|
||||
*pa = (*pa)[:len(*pa)-1]
|
||||
}
|
||||
|
||||
// Last returns the last [PathStep] in the Path.
|
||||
// If the path is empty, this returns a non-nil [PathStep]
|
||||
// that reports a nil [PathStep.Type].
|
||||
func (pa Path) Last() PathStep {
|
||||
return pa.Index(-1)
|
||||
}
|
||||
|
||||
// Index returns the ith step in the Path and supports negative indexing.
|
||||
// A negative index starts counting from the tail of the Path such that -1
|
||||
// refers to the last step, -2 refers to the second-to-last step, and so on.
|
||||
// If index is invalid, this returns a non-nil [PathStep]
|
||||
// that reports a nil [PathStep.Type].
|
||||
func (pa Path) Index(i int) PathStep {
|
||||
if i < 0 {
|
||||
i = len(pa) + i
|
||||
}
|
||||
if i < 0 || i >= len(pa) {
|
||||
return pathStep{}
|
||||
}
|
||||
return pa[i]
|
||||
}
|
||||
|
||||
// String returns the simplified path to a node.
|
||||
// The simplified path only contains struct field accesses.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// MyMap.MySlices.MyField
|
||||
func (pa Path) String() string {
|
||||
var ss []string
|
||||
for _, s := range pa {
|
||||
if _, ok := s.(StructField); ok {
|
||||
ss = append(ss, s.String())
|
||||
}
|
||||
}
|
||||
return strings.TrimPrefix(strings.Join(ss, ""), ".")
|
||||
}
|
||||
|
||||
// GoString returns the path to a specific node using Go syntax.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// (*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyField
|
||||
func (pa Path) GoString() string {
|
||||
var ssPre, ssPost []string
|
||||
var numIndirect int
|
||||
for i, s := range pa {
|
||||
var nextStep PathStep
|
||||
if i+1 < len(pa) {
|
||||
nextStep = pa[i+1]
|
||||
}
|
||||
switch s := s.(type) {
|
||||
case Indirect:
|
||||
numIndirect++
|
||||
pPre, pPost := "(", ")"
|
||||
switch nextStep.(type) {
|
||||
case Indirect:
|
||||
continue // Next step is indirection, so let them batch up
|
||||
case StructField:
|
||||
numIndirect-- // Automatic indirection on struct fields
|
||||
case nil:
|
||||
pPre, pPost = "", "" // Last step; no need for parenthesis
|
||||
}
|
||||
if numIndirect > 0 {
|
||||
ssPre = append(ssPre, pPre+strings.Repeat("*", numIndirect))
|
||||
ssPost = append(ssPost, pPost)
|
||||
}
|
||||
numIndirect = 0
|
||||
continue
|
||||
case Transform:
|
||||
ssPre = append(ssPre, s.trans.name+"(")
|
||||
ssPost = append(ssPost, ")")
|
||||
continue
|
||||
}
|
||||
ssPost = append(ssPost, s.String())
|
||||
}
|
||||
for i, j := 0, len(ssPre)-1; i < j; i, j = i+1, j-1 {
|
||||
ssPre[i], ssPre[j] = ssPre[j], ssPre[i]
|
||||
}
|
||||
return strings.Join(ssPre, "") + strings.Join(ssPost, "")
|
||||
}
|
||||
|
||||
type pathStep struct {
|
||||
typ reflect.Type
|
||||
vx, vy reflect.Value
|
||||
}
|
||||
|
||||
func (ps pathStep) Type() reflect.Type { return ps.typ }
|
||||
func (ps pathStep) Values() (vx, vy reflect.Value) { return ps.vx, ps.vy }
|
||||
func (ps pathStep) String() string {
|
||||
if ps.typ == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
s := value.TypeString(ps.typ, false)
|
||||
if s == "" || strings.ContainsAny(s, "{}\n") {
|
||||
return "root" // Type too simple or complex to print
|
||||
}
|
||||
return fmt.Sprintf("{%s}", s)
|
||||
}
|
||||
|
||||
// StructField is a [PathStep] that represents a struct field access
|
||||
// on a field called [StructField.Name].
|
||||
type StructField struct{ *structField }
|
||||
type structField struct {
|
||||
pathStep
|
||||
name string
|
||||
idx int
|
||||
|
||||
// These fields are used for forcibly accessing an unexported field.
|
||||
// pvx, pvy, and field are only valid if unexported is true.
|
||||
unexported bool
|
||||
mayForce bool // Forcibly allow visibility
|
||||
paddr bool // Was parent addressable?
|
||||
pvx, pvy reflect.Value // Parent values (always addressable)
|
||||
field reflect.StructField // Field information
|
||||
}
|
||||
|
||||
func (sf StructField) Type() reflect.Type { return sf.typ }
|
||||
func (sf StructField) Values() (vx, vy reflect.Value) {
|
||||
if !sf.unexported {
|
||||
return sf.vx, sf.vy // CanInterface reports true
|
||||
}
|
||||
|
||||
// Forcibly obtain read-write access to an unexported struct field.
|
||||
if sf.mayForce {
|
||||
vx = retrieveUnexportedField(sf.pvx, sf.field, sf.paddr)
|
||||
vy = retrieveUnexportedField(sf.pvy, sf.field, sf.paddr)
|
||||
return vx, vy // CanInterface reports true
|
||||
}
|
||||
return sf.vx, sf.vy // CanInterface reports false
|
||||
}
|
||||
func (sf StructField) String() string { return fmt.Sprintf(".%s", sf.name) }
|
||||
|
||||
// Name is the field name.
|
||||
func (sf StructField) Name() string { return sf.name }
|
||||
|
||||
// Index is the index of the field in the parent struct type.
|
||||
// See [reflect.Type.Field].
|
||||
func (sf StructField) Index() int { return sf.idx }
|
||||
|
||||
// SliceIndex is a [PathStep] that represents an index operation on
|
||||
// a slice or array at some index [SliceIndex.Key].
|
||||
type SliceIndex struct{ *sliceIndex }
|
||||
type sliceIndex struct {
|
||||
pathStep
|
||||
xkey, ykey int
|
||||
isSlice bool // False for reflect.Array
|
||||
}
|
||||
|
||||
func (si SliceIndex) Type() reflect.Type { return si.typ }
|
||||
func (si SliceIndex) Values() (vx, vy reflect.Value) { return si.vx, si.vy }
|
||||
func (si SliceIndex) String() string {
|
||||
switch {
|
||||
case si.xkey == si.ykey:
|
||||
return fmt.Sprintf("[%d]", si.xkey)
|
||||
case si.ykey == -1:
|
||||
// [5->?] means "I don't know where X[5] went"
|
||||
return fmt.Sprintf("[%d->?]", si.xkey)
|
||||
case si.xkey == -1:
|
||||
// [?->3] means "I don't know where Y[3] came from"
|
||||
return fmt.Sprintf("[?->%d]", si.ykey)
|
||||
default:
|
||||
// [5->3] means "X[5] moved to Y[3]"
|
||||
return fmt.Sprintf("[%d->%d]", si.xkey, si.ykey)
|
||||
}
|
||||
}
|
||||
|
||||
// Key is the index key; it may return -1 if in a split state
|
||||
func (si SliceIndex) Key() int {
|
||||
if si.xkey != si.ykey {
|
||||
return -1
|
||||
}
|
||||
return si.xkey
|
||||
}
|
||||
|
||||
// SplitKeys are the indexes for indexing into slices in the
|
||||
// x and y values, respectively. These indexes may differ due to the
|
||||
// insertion or removal of an element in one of the slices, causing
|
||||
// all of the indexes to be shifted. If an index is -1, then that
|
||||
// indicates that the element does not exist in the associated slice.
|
||||
//
|
||||
// [SliceIndex.Key] is guaranteed to return -1 if and only if the indexes
|
||||
// returned by SplitKeys are not the same. SplitKeys will never return -1 for
|
||||
// both indexes.
|
||||
func (si SliceIndex) SplitKeys() (ix, iy int) { return si.xkey, si.ykey }
|
||||
|
||||
// MapIndex is a [PathStep] that represents an index operation on a map at some index Key.
|
||||
type MapIndex struct{ *mapIndex }
|
||||
type mapIndex struct {
|
||||
pathStep
|
||||
key reflect.Value
|
||||
}
|
||||
|
||||
func (mi MapIndex) Type() reflect.Type { return mi.typ }
|
||||
func (mi MapIndex) Values() (vx, vy reflect.Value) { return mi.vx, mi.vy }
|
||||
func (mi MapIndex) String() string { return fmt.Sprintf("[%#v]", mi.key) }
|
||||
|
||||
// Key is the value of the map key.
|
||||
func (mi MapIndex) Key() reflect.Value { return mi.key }
|
||||
|
||||
// Indirect is a [PathStep] that represents pointer indirection on the parent type.
|
||||
type Indirect struct{ *indirect }
|
||||
type indirect struct {
|
||||
pathStep
|
||||
}
|
||||
|
||||
func (in Indirect) Type() reflect.Type { return in.typ }
|
||||
func (in Indirect) Values() (vx, vy reflect.Value) { return in.vx, in.vy }
|
||||
func (in Indirect) String() string { return "*" }
|
||||
|
||||
// TypeAssertion is a [PathStep] that represents a type assertion on an interface.
|
||||
type TypeAssertion struct{ *typeAssertion }
|
||||
type typeAssertion struct {
|
||||
pathStep
|
||||
}
|
||||
|
||||
func (ta TypeAssertion) Type() reflect.Type { return ta.typ }
|
||||
func (ta TypeAssertion) Values() (vx, vy reflect.Value) { return ta.vx, ta.vy }
|
||||
func (ta TypeAssertion) String() string { return fmt.Sprintf(".(%v)", value.TypeString(ta.typ, false)) }
|
||||
|
||||
// Transform is a [PathStep] that represents a transformation
|
||||
// from the parent type to the current type.
|
||||
type Transform struct{ *transform }
|
||||
type transform struct {
|
||||
pathStep
|
||||
trans *transformer
|
||||
}
|
||||
|
||||
func (tf Transform) Type() reflect.Type { return tf.typ }
|
||||
func (tf Transform) Values() (vx, vy reflect.Value) { return tf.vx, tf.vy }
|
||||
func (tf Transform) String() string { return fmt.Sprintf("%s()", tf.trans.name) }
|
||||
|
||||
// Name is the name of the [Transformer].
|
||||
func (tf Transform) Name() string { return tf.trans.name }
|
||||
|
||||
// Func is the function pointer to the transformer function.
|
||||
func (tf Transform) Func() reflect.Value { return tf.trans.fnc }
|
||||
|
||||
// Option returns the originally constructed [Transformer] option.
|
||||
// The == operator can be used to detect the exact option used.
|
||||
func (tf Transform) Option() Option { return tf.trans }
|
||||
|
||||
// pointerPath represents a dual-stack of pointers encountered when
|
||||
// recursively traversing the x and y values. This data structure supports
|
||||
// detection of cycles and determining whether the cycles are equal.
|
||||
// In Go, cycles can occur via pointers, slices, and maps.
|
||||
//
|
||||
// The pointerPath uses a map to represent a stack; where descension into a
|
||||
// pointer pushes the address onto the stack, and ascension from a pointer
|
||||
// pops the address from the stack. Thus, when traversing into a pointer from
|
||||
// reflect.Ptr, reflect.Slice element, or reflect.Map, we can detect cycles
|
||||
// by checking whether the pointer has already been visited. The cycle detection
|
||||
// uses a separate stack for the x and y values.
|
||||
//
|
||||
// If a cycle is detected we need to determine whether the two pointers
|
||||
// should be considered equal. The definition of equality chosen by Equal
|
||||
// requires two graphs to have the same structure. To determine this, both the
|
||||
// x and y values must have a cycle where the previous pointers were also
|
||||
// encountered together as a pair.
|
||||
//
|
||||
// Semantically, this is equivalent to augmenting Indirect, SliceIndex, and
|
||||
// MapIndex with pointer information for the x and y values.
|
||||
// Suppose px and py are two pointers to compare, we then search the
|
||||
// Path for whether px was ever encountered in the Path history of x, and
|
||||
// similarly so with py. If either side has a cycle, the comparison is only
|
||||
// equal if both px and py have a cycle resulting from the same PathStep.
|
||||
//
|
||||
// Using a map as a stack is more performant as we can perform cycle detection
|
||||
// in O(1) instead of O(N) where N is len(Path).
|
||||
type pointerPath struct {
|
||||
// mx is keyed by x pointers, where the value is the associated y pointer.
|
||||
mx map[value.Pointer]value.Pointer
|
||||
// my is keyed by y pointers, where the value is the associated x pointer.
|
||||
my map[value.Pointer]value.Pointer
|
||||
}
|
||||
|
||||
func (p *pointerPath) Init() {
|
||||
p.mx = make(map[value.Pointer]value.Pointer)
|
||||
p.my = make(map[value.Pointer]value.Pointer)
|
||||
}
|
||||
|
||||
// Push indicates intent to descend into pointers vx and vy where
|
||||
// visited reports whether either has been seen before. If visited before,
|
||||
// equal reports whether both pointers were encountered together.
|
||||
// Pop must be called if and only if the pointers were never visited.
|
||||
//
|
||||
// The pointers vx and vy must be a reflect.Ptr, reflect.Slice, or reflect.Map
|
||||
// and be non-nil.
|
||||
func (p pointerPath) Push(vx, vy reflect.Value) (equal, visited bool) {
|
||||
px := value.PointerOf(vx)
|
||||
py := value.PointerOf(vy)
|
||||
_, ok1 := p.mx[px]
|
||||
_, ok2 := p.my[py]
|
||||
if ok1 || ok2 {
|
||||
equal = p.mx[px] == py && p.my[py] == px // Pointers paired together
|
||||
return equal, true
|
||||
}
|
||||
p.mx[px] = py
|
||||
p.my[py] = px
|
||||
return false, false
|
||||
}
|
||||
|
||||
// Pop ascends from pointers vx and vy.
|
||||
func (p pointerPath) Pop(vx, vy reflect.Value) {
|
||||
delete(p.mx, value.PointerOf(vx))
|
||||
delete(p.my, value.PointerOf(vy))
|
||||
}
|
||||
|
||||
// isExported reports whether the identifier is exported.
|
||||
func isExported(id string) bool {
|
||||
r, _ := utf8.DecodeRuneInString(id)
|
||||
return unicode.IsUpper(r)
|
||||
}
|
||||
54
vendor/github.com/google/go-cmp/cmp/report.go
generated
vendored
Normal file
54
vendor/github.com/google/go-cmp/cmp/report.go
generated
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright 2017, 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 cmp
|
||||
|
||||
// defaultReporter implements the reporter interface.
|
||||
//
|
||||
// As Equal serially calls the PushStep, Report, and PopStep methods, the
|
||||
// defaultReporter constructs a tree-based representation of the compared value
|
||||
// and the result of each comparison (see valueNode).
|
||||
//
|
||||
// When the String method is called, the FormatDiff method transforms the
|
||||
// valueNode tree into a textNode tree, which is a tree-based representation
|
||||
// of the textual output (see textNode).
|
||||
//
|
||||
// Lastly, the textNode.String method produces the final report as a string.
|
||||
type defaultReporter struct {
|
||||
root *valueNode
|
||||
curr *valueNode
|
||||
}
|
||||
|
||||
func (r *defaultReporter) PushStep(ps PathStep) {
|
||||
r.curr = r.curr.PushStep(ps)
|
||||
if r.root == nil {
|
||||
r.root = r.curr
|
||||
}
|
||||
}
|
||||
func (r *defaultReporter) Report(rs Result) {
|
||||
r.curr.Report(rs)
|
||||
}
|
||||
func (r *defaultReporter) PopStep() {
|
||||
r.curr = r.curr.PopStep()
|
||||
}
|
||||
|
||||
// String provides a full report of the differences detected as a structured
|
||||
// literal in pseudo-Go syntax. String may only be called after the entire tree
|
||||
// has been traversed.
|
||||
func (r *defaultReporter) String() string {
|
||||
assert(r.root != nil && r.curr == nil)
|
||||
if r.root.NumDiff == 0 {
|
||||
return ""
|
||||
}
|
||||
ptrs := new(pointerReferences)
|
||||
text := formatOptions{}.FormatDiff(r.root, ptrs)
|
||||
resolveReferences(text)
|
||||
return text.String()
|
||||
}
|
||||
|
||||
func assert(ok bool) {
|
||||
if !ok {
|
||||
panic("assertion failure")
|
||||
}
|
||||
}
|
||||
433
vendor/github.com/google/go-cmp/cmp/report_compare.go
generated
vendored
Normal file
433
vendor/github.com/google/go-cmp/cmp/report_compare.go
generated
vendored
Normal file
|
|
@ -0,0 +1,433 @@
|
|||
// 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 cmp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// numContextRecords is the number of surrounding equal records to print.
|
||||
const numContextRecords = 2
|
||||
|
||||
type diffMode byte
|
||||
|
||||
const (
|
||||
diffUnknown diffMode = 0
|
||||
diffIdentical diffMode = ' '
|
||||
diffRemoved diffMode = '-'
|
||||
diffInserted diffMode = '+'
|
||||
)
|
||||
|
||||
type typeMode int
|
||||
|
||||
const (
|
||||
// emitType always prints the type.
|
||||
emitType typeMode = iota
|
||||
// elideType never prints the type.
|
||||
elideType
|
||||
// autoType prints the type only for composite kinds
|
||||
// (i.e., structs, slices, arrays, and maps).
|
||||
autoType
|
||||
)
|
||||
|
||||
type formatOptions struct {
|
||||
// DiffMode controls the output mode of FormatDiff.
|
||||
//
|
||||
// If diffUnknown, then produce a diff of the x and y values.
|
||||
// If diffIdentical, then emit values as if they were equal.
|
||||
// If diffRemoved, then only emit x values (ignoring y values).
|
||||
// If diffInserted, then only emit y values (ignoring x values).
|
||||
DiffMode diffMode
|
||||
|
||||
// TypeMode controls whether to print the type for the current node.
|
||||
//
|
||||
// As a general rule of thumb, we always print the type of the next node
|
||||
// after an interface, and always elide the type of the next node after
|
||||
// a slice or map node.
|
||||
TypeMode typeMode
|
||||
|
||||
// formatValueOptions are options specific to printing reflect.Values.
|
||||
formatValueOptions
|
||||
}
|
||||
|
||||
func (opts formatOptions) WithDiffMode(d diffMode) formatOptions {
|
||||
opts.DiffMode = d
|
||||
return opts
|
||||
}
|
||||
func (opts formatOptions) WithTypeMode(t typeMode) formatOptions {
|
||||
opts.TypeMode = t
|
||||
return opts
|
||||
}
|
||||
func (opts formatOptions) WithVerbosity(level int) formatOptions {
|
||||
opts.VerbosityLevel = level
|
||||
opts.LimitVerbosity = true
|
||||
return opts
|
||||
}
|
||||
func (opts formatOptions) verbosity() uint {
|
||||
switch {
|
||||
case opts.VerbosityLevel < 0:
|
||||
return 0
|
||||
case opts.VerbosityLevel > 16:
|
||||
return 16 // some reasonable maximum to avoid shift overflow
|
||||
default:
|
||||
return uint(opts.VerbosityLevel)
|
||||
}
|
||||
}
|
||||
|
||||
const maxVerbosityPreset = 6
|
||||
|
||||
// verbosityPreset modifies the verbosity settings given an index
|
||||
// between 0 and maxVerbosityPreset, inclusive.
|
||||
func verbosityPreset(opts formatOptions, i int) formatOptions {
|
||||
opts.VerbosityLevel = int(opts.verbosity()) + 2*i
|
||||
if i > 0 {
|
||||
opts.AvoidStringer = true
|
||||
}
|
||||
if i >= maxVerbosityPreset {
|
||||
opts.PrintAddresses = true
|
||||
opts.QualifiedNames = true
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// FormatDiff converts a valueNode tree into a textNode tree, where the later
|
||||
// is a textual representation of the differences detected in the former.
|
||||
func (opts formatOptions) FormatDiff(v *valueNode, ptrs *pointerReferences) (out textNode) {
|
||||
if opts.DiffMode == diffIdentical {
|
||||
opts = opts.WithVerbosity(1)
|
||||
} else if opts.verbosity() < 3 {
|
||||
opts = opts.WithVerbosity(3)
|
||||
}
|
||||
|
||||
// Check whether we have specialized formatting for this node.
|
||||
// This is not necessary, but helpful for producing more readable outputs.
|
||||
if opts.CanFormatDiffSlice(v) {
|
||||
return opts.FormatDiffSlice(v)
|
||||
}
|
||||
|
||||
var parentKind reflect.Kind
|
||||
if v.parent != nil && v.parent.TransformerName == "" {
|
||||
parentKind = v.parent.Type.Kind()
|
||||
}
|
||||
|
||||
// For leaf nodes, format the value based on the reflect.Values alone.
|
||||
// As a special case, treat equal []byte as a leaf nodes.
|
||||
isBytes := v.Type.Kind() == reflect.Slice && v.Type.Elem() == byteType
|
||||
isEqualBytes := isBytes && v.NumDiff+v.NumIgnored+v.NumTransformed == 0
|
||||
if v.MaxDepth == 0 || isEqualBytes {
|
||||
switch opts.DiffMode {
|
||||
case diffUnknown, diffIdentical:
|
||||
// Format Equal.
|
||||
if v.NumDiff == 0 {
|
||||
outx := opts.FormatValue(v.ValueX, parentKind, ptrs)
|
||||
outy := opts.FormatValue(v.ValueY, parentKind, ptrs)
|
||||
if v.NumIgnored > 0 && v.NumSame == 0 {
|
||||
return textEllipsis
|
||||
} else if outx.Len() < outy.Len() {
|
||||
return outx
|
||||
} else {
|
||||
return outy
|
||||
}
|
||||
}
|
||||
|
||||
// Format unequal.
|
||||
assert(opts.DiffMode == diffUnknown)
|
||||
var list textList
|
||||
outx := opts.WithTypeMode(elideType).FormatValue(v.ValueX, parentKind, ptrs)
|
||||
outy := opts.WithTypeMode(elideType).FormatValue(v.ValueY, parentKind, ptrs)
|
||||
for i := 0; i <= maxVerbosityPreset && outx != nil && outy != nil && outx.Equal(outy); i++ {
|
||||
opts2 := verbosityPreset(opts, i).WithTypeMode(elideType)
|
||||
outx = opts2.FormatValue(v.ValueX, parentKind, ptrs)
|
||||
outy = opts2.FormatValue(v.ValueY, parentKind, ptrs)
|
||||
}
|
||||
if outx != nil {
|
||||
list = append(list, textRecord{Diff: '-', Value: outx})
|
||||
}
|
||||
if outy != nil {
|
||||
list = append(list, textRecord{Diff: '+', Value: outy})
|
||||
}
|
||||
return opts.WithTypeMode(emitType).FormatType(v.Type, list)
|
||||
case diffRemoved:
|
||||
return opts.FormatValue(v.ValueX, parentKind, ptrs)
|
||||
case diffInserted:
|
||||
return opts.FormatValue(v.ValueY, parentKind, ptrs)
|
||||
default:
|
||||
panic("invalid diff mode")
|
||||
}
|
||||
}
|
||||
|
||||
// Register slice element to support cycle detection.
|
||||
if parentKind == reflect.Slice {
|
||||
ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, true)
|
||||
defer ptrs.Pop()
|
||||
defer func() { out = wrapTrunkReferences(ptrRefs, out) }()
|
||||
}
|
||||
|
||||
// Descend into the child value node.
|
||||
if v.TransformerName != "" {
|
||||
out := opts.WithTypeMode(emitType).FormatDiff(v.Value, ptrs)
|
||||
out = &textWrap{Prefix: "Inverse(" + v.TransformerName + ", ", Value: out, Suffix: ")"}
|
||||
return opts.FormatType(v.Type, out)
|
||||
} else {
|
||||
switch k := v.Type.Kind(); k {
|
||||
case reflect.Struct, reflect.Array, reflect.Slice:
|
||||
out = opts.formatDiffList(v.Records, k, ptrs)
|
||||
out = opts.FormatType(v.Type, out)
|
||||
case reflect.Map:
|
||||
// Register map to support cycle detection.
|
||||
ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, false)
|
||||
defer ptrs.Pop()
|
||||
|
||||
out = opts.formatDiffList(v.Records, k, ptrs)
|
||||
out = wrapTrunkReferences(ptrRefs, out)
|
||||
out = opts.FormatType(v.Type, out)
|
||||
case reflect.Ptr:
|
||||
// Register pointer to support cycle detection.
|
||||
ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, false)
|
||||
defer ptrs.Pop()
|
||||
|
||||
out = opts.FormatDiff(v.Value, ptrs)
|
||||
out = wrapTrunkReferences(ptrRefs, out)
|
||||
out = &textWrap{Prefix: "&", Value: out}
|
||||
case reflect.Interface:
|
||||
out = opts.WithTypeMode(emitType).FormatDiff(v.Value, ptrs)
|
||||
default:
|
||||
panic(fmt.Sprintf("%v cannot have children", k))
|
||||
}
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind, ptrs *pointerReferences) textNode {
|
||||
// Derive record name based on the data structure kind.
|
||||
var name string
|
||||
var formatKey func(reflect.Value) string
|
||||
switch k {
|
||||
case reflect.Struct:
|
||||
name = "field"
|
||||
opts = opts.WithTypeMode(autoType)
|
||||
formatKey = func(v reflect.Value) string { return v.String() }
|
||||
case reflect.Slice, reflect.Array:
|
||||
name = "element"
|
||||
opts = opts.WithTypeMode(elideType)
|
||||
formatKey = func(reflect.Value) string { return "" }
|
||||
case reflect.Map:
|
||||
name = "entry"
|
||||
opts = opts.WithTypeMode(elideType)
|
||||
formatKey = func(v reflect.Value) string { return formatMapKey(v, false, ptrs) }
|
||||
}
|
||||
|
||||
maxLen := -1
|
||||
if opts.LimitVerbosity {
|
||||
if opts.DiffMode == diffIdentical {
|
||||
maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
|
||||
} else {
|
||||
maxLen = (1 << opts.verbosity()) << 1 // 2, 4, 8, 16, 32, 64, etc...
|
||||
}
|
||||
opts.VerbosityLevel--
|
||||
}
|
||||
|
||||
// Handle unification.
|
||||
switch opts.DiffMode {
|
||||
case diffIdentical, diffRemoved, diffInserted:
|
||||
var list textList
|
||||
var deferredEllipsis bool // Add final "..." to indicate records were dropped
|
||||
for _, r := range recs {
|
||||
if len(list) == maxLen {
|
||||
deferredEllipsis = true
|
||||
break
|
||||
}
|
||||
|
||||
// Elide struct fields that are zero value.
|
||||
if k == reflect.Struct {
|
||||
var isZero bool
|
||||
switch opts.DiffMode {
|
||||
case diffIdentical:
|
||||
isZero = r.Value.ValueX.IsZero() || r.Value.ValueY.IsZero()
|
||||
case diffRemoved:
|
||||
isZero = r.Value.ValueX.IsZero()
|
||||
case diffInserted:
|
||||
isZero = r.Value.ValueY.IsZero()
|
||||
}
|
||||
if isZero {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Elide ignored nodes.
|
||||
if r.Value.NumIgnored > 0 && r.Value.NumSame+r.Value.NumDiff == 0 {
|
||||
deferredEllipsis = !(k == reflect.Slice || k == reflect.Array)
|
||||
if !deferredEllipsis {
|
||||
list.AppendEllipsis(diffStats{})
|
||||
}
|
||||
continue
|
||||
}
|
||||
if out := opts.FormatDiff(r.Value, ptrs); out != nil {
|
||||
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||
}
|
||||
}
|
||||
if deferredEllipsis {
|
||||
list.AppendEllipsis(diffStats{})
|
||||
}
|
||||
return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||
case diffUnknown:
|
||||
default:
|
||||
panic("invalid diff mode")
|
||||
}
|
||||
|
||||
// Handle differencing.
|
||||
var numDiffs int
|
||||
var list textList
|
||||
var keys []reflect.Value // invariant: len(list) == len(keys)
|
||||
groups := coalesceAdjacentRecords(name, recs)
|
||||
maxGroup := diffStats{Name: name}
|
||||
for i, ds := range groups {
|
||||
if maxLen >= 0 && numDiffs >= maxLen {
|
||||
maxGroup = maxGroup.Append(ds)
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle equal records.
|
||||
if ds.NumDiff() == 0 {
|
||||
// Compute the number of leading and trailing records to print.
|
||||
var numLo, numHi int
|
||||
numEqual := ds.NumIgnored + ds.NumIdentical
|
||||
for numLo < numContextRecords && numLo+numHi < numEqual && i != 0 {
|
||||
if r := recs[numLo].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 {
|
||||
break
|
||||
}
|
||||
numLo++
|
||||
}
|
||||
for numHi < numContextRecords && numLo+numHi < numEqual && i != len(groups)-1 {
|
||||
if r := recs[numEqual-numHi-1].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 {
|
||||
break
|
||||
}
|
||||
numHi++
|
||||
}
|
||||
if numEqual-(numLo+numHi) == 1 && ds.NumIgnored == 0 {
|
||||
numHi++ // Avoid pointless coalescing of a single equal record
|
||||
}
|
||||
|
||||
// Format the equal values.
|
||||
for _, r := range recs[:numLo] {
|
||||
out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value, ptrs)
|
||||
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||
keys = append(keys, r.Key)
|
||||
}
|
||||
if numEqual > numLo+numHi {
|
||||
ds.NumIdentical -= numLo + numHi
|
||||
list.AppendEllipsis(ds)
|
||||
for len(keys) < len(list) {
|
||||
keys = append(keys, reflect.Value{})
|
||||
}
|
||||
}
|
||||
for _, r := range recs[numEqual-numHi : numEqual] {
|
||||
out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value, ptrs)
|
||||
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||
keys = append(keys, r.Key)
|
||||
}
|
||||
recs = recs[numEqual:]
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle unequal records.
|
||||
for _, r := range recs[:ds.NumDiff()] {
|
||||
switch {
|
||||
case opts.CanFormatDiffSlice(r.Value):
|
||||
out := opts.FormatDiffSlice(r.Value)
|
||||
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||
keys = append(keys, r.Key)
|
||||
case r.Value.NumChildren == r.Value.MaxDepth:
|
||||
outx := opts.WithDiffMode(diffRemoved).FormatDiff(r.Value, ptrs)
|
||||
outy := opts.WithDiffMode(diffInserted).FormatDiff(r.Value, ptrs)
|
||||
for i := 0; i <= maxVerbosityPreset && outx != nil && outy != nil && outx.Equal(outy); i++ {
|
||||
opts2 := verbosityPreset(opts, i)
|
||||
outx = opts2.WithDiffMode(diffRemoved).FormatDiff(r.Value, ptrs)
|
||||
outy = opts2.WithDiffMode(diffInserted).FormatDiff(r.Value, ptrs)
|
||||
}
|
||||
if outx != nil {
|
||||
list = append(list, textRecord{Diff: diffRemoved, Key: formatKey(r.Key), Value: outx})
|
||||
keys = append(keys, r.Key)
|
||||
}
|
||||
if outy != nil {
|
||||
list = append(list, textRecord{Diff: diffInserted, Key: formatKey(r.Key), Value: outy})
|
||||
keys = append(keys, r.Key)
|
||||
}
|
||||
default:
|
||||
out := opts.FormatDiff(r.Value, ptrs)
|
||||
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||
keys = append(keys, r.Key)
|
||||
}
|
||||
}
|
||||
recs = recs[ds.NumDiff():]
|
||||
numDiffs += ds.NumDiff()
|
||||
}
|
||||
if maxGroup.IsZero() {
|
||||
assert(len(recs) == 0)
|
||||
} else {
|
||||
list.AppendEllipsis(maxGroup)
|
||||
for len(keys) < len(list) {
|
||||
keys = append(keys, reflect.Value{})
|
||||
}
|
||||
}
|
||||
assert(len(list) == len(keys))
|
||||
|
||||
// For maps, the default formatting logic uses fmt.Stringer which may
|
||||
// produce ambiguous output. Avoid calling String to disambiguate.
|
||||
if k == reflect.Map {
|
||||
var ambiguous bool
|
||||
seenKeys := map[string]reflect.Value{}
|
||||
for i, currKey := range keys {
|
||||
if currKey.IsValid() {
|
||||
strKey := list[i].Key
|
||||
prevKey, seen := seenKeys[strKey]
|
||||
if seen && prevKey.CanInterface() && currKey.CanInterface() {
|
||||
ambiguous = prevKey.Interface() != currKey.Interface()
|
||||
if ambiguous {
|
||||
break
|
||||
}
|
||||
}
|
||||
seenKeys[strKey] = currKey
|
||||
}
|
||||
}
|
||||
if ambiguous {
|
||||
for i, k := range keys {
|
||||
if k.IsValid() {
|
||||
list[i].Key = formatMapKey(k, true, ptrs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||
}
|
||||
|
||||
// coalesceAdjacentRecords coalesces the list of records into groups of
|
||||
// adjacent equal, or unequal counts.
|
||||
func coalesceAdjacentRecords(name string, recs []reportRecord) (groups []diffStats) {
|
||||
var prevCase int // Arbitrary index into which case last occurred
|
||||
lastStats := func(i int) *diffStats {
|
||||
if prevCase != i {
|
||||
groups = append(groups, diffStats{Name: name})
|
||||
prevCase = i
|
||||
}
|
||||
return &groups[len(groups)-1]
|
||||
}
|
||||
for _, r := range recs {
|
||||
switch rv := r.Value; {
|
||||
case rv.NumIgnored > 0 && rv.NumSame+rv.NumDiff == 0:
|
||||
lastStats(1).NumIgnored++
|
||||
case rv.NumDiff == 0:
|
||||
lastStats(1).NumIdentical++
|
||||
case rv.NumDiff > 0 && !rv.ValueY.IsValid():
|
||||
lastStats(2).NumRemoved++
|
||||
case rv.NumDiff > 0 && !rv.ValueX.IsValid():
|
||||
lastStats(2).NumInserted++
|
||||
default:
|
||||
lastStats(2).NumModified++
|
||||
}
|
||||
}
|
||||
return groups
|
||||
}
|
||||
264
vendor/github.com/google/go-cmp/cmp/report_references.go
generated
vendored
Normal file
264
vendor/github.com/google/go-cmp/cmp/report_references.go
generated
vendored
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
// Copyright 2020, 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 cmp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/flags"
|
||||
"github.com/google/go-cmp/cmp/internal/value"
|
||||
)
|
||||
|
||||
const (
|
||||
pointerDelimPrefix = "⟪"
|
||||
pointerDelimSuffix = "⟫"
|
||||
)
|
||||
|
||||
// formatPointer prints the address of the pointer.
|
||||
func formatPointer(p value.Pointer, withDelims bool) string {
|
||||
v := p.Uintptr()
|
||||
if flags.Deterministic {
|
||||
v = 0xdeadf00f // Only used for stable testing purposes
|
||||
}
|
||||
if withDelims {
|
||||
return pointerDelimPrefix + formatHex(uint64(v)) + pointerDelimSuffix
|
||||
}
|
||||
return formatHex(uint64(v))
|
||||
}
|
||||
|
||||
// pointerReferences is a stack of pointers visited so far.
|
||||
type pointerReferences [][2]value.Pointer
|
||||
|
||||
func (ps *pointerReferences) PushPair(vx, vy reflect.Value, d diffMode, deref bool) (pp [2]value.Pointer) {
|
||||
if deref && vx.IsValid() {
|
||||
vx = vx.Addr()
|
||||
}
|
||||
if deref && vy.IsValid() {
|
||||
vy = vy.Addr()
|
||||
}
|
||||
switch d {
|
||||
case diffUnknown, diffIdentical:
|
||||
pp = [2]value.Pointer{value.PointerOf(vx), value.PointerOf(vy)}
|
||||
case diffRemoved:
|
||||
pp = [2]value.Pointer{value.PointerOf(vx), value.Pointer{}}
|
||||
case diffInserted:
|
||||
pp = [2]value.Pointer{value.Pointer{}, value.PointerOf(vy)}
|
||||
}
|
||||
*ps = append(*ps, pp)
|
||||
return pp
|
||||
}
|
||||
|
||||
func (ps *pointerReferences) Push(v reflect.Value) (p value.Pointer, seen bool) {
|
||||
p = value.PointerOf(v)
|
||||
for _, pp := range *ps {
|
||||
if p == pp[0] || p == pp[1] {
|
||||
return p, true
|
||||
}
|
||||
}
|
||||
*ps = append(*ps, [2]value.Pointer{p, p})
|
||||
return p, false
|
||||
}
|
||||
|
||||
func (ps *pointerReferences) Pop() {
|
||||
*ps = (*ps)[:len(*ps)-1]
|
||||
}
|
||||
|
||||
// trunkReferences is metadata for a textNode indicating that the sub-tree
|
||||
// represents the value for either pointer in a pair of references.
|
||||
type trunkReferences struct{ pp [2]value.Pointer }
|
||||
|
||||
// trunkReference is metadata for a textNode indicating that the sub-tree
|
||||
// represents the value for the given pointer reference.
|
||||
type trunkReference struct{ p value.Pointer }
|
||||
|
||||
// leafReference is metadata for a textNode indicating that the value is
|
||||
// truncated as it refers to another part of the tree (i.e., a trunk).
|
||||
type leafReference struct{ p value.Pointer }
|
||||
|
||||
func wrapTrunkReferences(pp [2]value.Pointer, s textNode) textNode {
|
||||
switch {
|
||||
case pp[0].IsNil():
|
||||
return &textWrap{Value: s, Metadata: trunkReference{pp[1]}}
|
||||
case pp[1].IsNil():
|
||||
return &textWrap{Value: s, Metadata: trunkReference{pp[0]}}
|
||||
case pp[0] == pp[1]:
|
||||
return &textWrap{Value: s, Metadata: trunkReference{pp[0]}}
|
||||
default:
|
||||
return &textWrap{Value: s, Metadata: trunkReferences{pp}}
|
||||
}
|
||||
}
|
||||
func wrapTrunkReference(p value.Pointer, printAddress bool, s textNode) textNode {
|
||||
var prefix string
|
||||
if printAddress {
|
||||
prefix = formatPointer(p, true)
|
||||
}
|
||||
return &textWrap{Prefix: prefix, Value: s, Metadata: trunkReference{p}}
|
||||
}
|
||||
func makeLeafReference(p value.Pointer, printAddress bool) textNode {
|
||||
out := &textWrap{Prefix: "(", Value: textEllipsis, Suffix: ")"}
|
||||
var prefix string
|
||||
if printAddress {
|
||||
prefix = formatPointer(p, true)
|
||||
}
|
||||
return &textWrap{Prefix: prefix, Value: out, Metadata: leafReference{p}}
|
||||
}
|
||||
|
||||
// resolveReferences walks the textNode tree searching for any leaf reference
|
||||
// metadata and resolves each against the corresponding trunk references.
|
||||
// Since pointer addresses in memory are not particularly readable to the user,
|
||||
// it replaces each pointer value with an arbitrary and unique reference ID.
|
||||
func resolveReferences(s textNode) {
|
||||
var walkNodes func(textNode, func(textNode))
|
||||
walkNodes = func(s textNode, f func(textNode)) {
|
||||
f(s)
|
||||
switch s := s.(type) {
|
||||
case *textWrap:
|
||||
walkNodes(s.Value, f)
|
||||
case textList:
|
||||
for _, r := range s {
|
||||
walkNodes(r.Value, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all trunks and leaves with reference metadata.
|
||||
var trunks, leaves []*textWrap
|
||||
walkNodes(s, func(s textNode) {
|
||||
if s, ok := s.(*textWrap); ok {
|
||||
switch s.Metadata.(type) {
|
||||
case leafReference:
|
||||
leaves = append(leaves, s)
|
||||
case trunkReference, trunkReferences:
|
||||
trunks = append(trunks, s)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// No leaf references to resolve.
|
||||
if len(leaves) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Collect the set of all leaf references to resolve.
|
||||
leafPtrs := make(map[value.Pointer]bool)
|
||||
for _, leaf := range leaves {
|
||||
leafPtrs[leaf.Metadata.(leafReference).p] = true
|
||||
}
|
||||
|
||||
// Collect the set of trunk pointers that are always paired together.
|
||||
// This allows us to assign a single ID to both pointers for brevity.
|
||||
// If a pointer in a pair ever occurs by itself or as a different pair,
|
||||
// then the pair is broken.
|
||||
pairedTrunkPtrs := make(map[value.Pointer]value.Pointer)
|
||||
unpair := func(p value.Pointer) {
|
||||
if !pairedTrunkPtrs[p].IsNil() {
|
||||
pairedTrunkPtrs[pairedTrunkPtrs[p]] = value.Pointer{} // invalidate other half
|
||||
}
|
||||
pairedTrunkPtrs[p] = value.Pointer{} // invalidate this half
|
||||
}
|
||||
for _, trunk := range trunks {
|
||||
switch p := trunk.Metadata.(type) {
|
||||
case trunkReference:
|
||||
unpair(p.p) // standalone pointer cannot be part of a pair
|
||||
case trunkReferences:
|
||||
p0, ok0 := pairedTrunkPtrs[p.pp[0]]
|
||||
p1, ok1 := pairedTrunkPtrs[p.pp[1]]
|
||||
switch {
|
||||
case !ok0 && !ok1:
|
||||
// Register the newly seen pair.
|
||||
pairedTrunkPtrs[p.pp[0]] = p.pp[1]
|
||||
pairedTrunkPtrs[p.pp[1]] = p.pp[0]
|
||||
case ok0 && ok1 && p0 == p.pp[1] && p1 == p.pp[0]:
|
||||
// Exact pair already seen; do nothing.
|
||||
default:
|
||||
// Pair conflicts with some other pair; break all pairs.
|
||||
unpair(p.pp[0])
|
||||
unpair(p.pp[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Correlate each pointer referenced by leaves to a unique identifier,
|
||||
// and print the IDs for each trunk that matches those pointers.
|
||||
var nextID uint
|
||||
ptrIDs := make(map[value.Pointer]uint)
|
||||
newID := func() uint {
|
||||
id := nextID
|
||||
nextID++
|
||||
return id
|
||||
}
|
||||
for _, trunk := range trunks {
|
||||
switch p := trunk.Metadata.(type) {
|
||||
case trunkReference:
|
||||
if print := leafPtrs[p.p]; print {
|
||||
id, ok := ptrIDs[p.p]
|
||||
if !ok {
|
||||
id = newID()
|
||||
ptrIDs[p.p] = id
|
||||
}
|
||||
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id))
|
||||
}
|
||||
case trunkReferences:
|
||||
print0 := leafPtrs[p.pp[0]]
|
||||
print1 := leafPtrs[p.pp[1]]
|
||||
if print0 || print1 {
|
||||
id0, ok0 := ptrIDs[p.pp[0]]
|
||||
id1, ok1 := ptrIDs[p.pp[1]]
|
||||
isPair := pairedTrunkPtrs[p.pp[0]] == p.pp[1] && pairedTrunkPtrs[p.pp[1]] == p.pp[0]
|
||||
if isPair {
|
||||
var id uint
|
||||
assert(ok0 == ok1) // must be seen together or not at all
|
||||
if ok0 {
|
||||
assert(id0 == id1) // must have the same ID
|
||||
id = id0
|
||||
} else {
|
||||
id = newID()
|
||||
ptrIDs[p.pp[0]] = id
|
||||
ptrIDs[p.pp[1]] = id
|
||||
}
|
||||
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id))
|
||||
} else {
|
||||
if print0 && !ok0 {
|
||||
id0 = newID()
|
||||
ptrIDs[p.pp[0]] = id0
|
||||
}
|
||||
if print1 && !ok1 {
|
||||
id1 = newID()
|
||||
ptrIDs[p.pp[1]] = id1
|
||||
}
|
||||
switch {
|
||||
case print0 && print1:
|
||||
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0)+","+formatReference(id1))
|
||||
case print0:
|
||||
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0))
|
||||
case print1:
|
||||
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update all leaf references with the unique identifier.
|
||||
for _, leaf := range leaves {
|
||||
if id, ok := ptrIDs[leaf.Metadata.(leafReference).p]; ok {
|
||||
leaf.Prefix = updateReferencePrefix(leaf.Prefix, formatReference(id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func formatReference(id uint) string {
|
||||
return fmt.Sprintf("ref#%d", id)
|
||||
}
|
||||
|
||||
func updateReferencePrefix(prefix, ref string) string {
|
||||
if prefix == "" {
|
||||
return pointerDelimPrefix + ref + pointerDelimSuffix
|
||||
}
|
||||
suffix := strings.TrimPrefix(prefix, pointerDelimPrefix)
|
||||
return pointerDelimPrefix + ref + ": " + suffix
|
||||
}
|
||||
414
vendor/github.com/google/go-cmp/cmp/report_reflect.go
generated
vendored
Normal file
414
vendor/github.com/google/go-cmp/cmp/report_reflect.go
generated
vendored
Normal file
|
|
@ -0,0 +1,414 @@
|
|||
// 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 cmp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/value"
|
||||
)
|
||||
|
||||
var (
|
||||
anyType = reflect.TypeOf((*interface{})(nil)).Elem()
|
||||
stringType = reflect.TypeOf((*string)(nil)).Elem()
|
||||
bytesType = reflect.TypeOf((*[]byte)(nil)).Elem()
|
||||
byteType = reflect.TypeOf((*byte)(nil)).Elem()
|
||||
)
|
||||
|
||||
type formatValueOptions struct {
|
||||
// AvoidStringer controls whether to avoid calling custom stringer
|
||||
// methods like error.Error or fmt.Stringer.String.
|
||||
AvoidStringer bool
|
||||
|
||||
// PrintAddresses controls whether to print the address of all pointers,
|
||||
// slice elements, and maps.
|
||||
PrintAddresses bool
|
||||
|
||||
// QualifiedNames controls whether FormatType uses the fully qualified name
|
||||
// (including the full package path as opposed to just the package name).
|
||||
QualifiedNames bool
|
||||
|
||||
// VerbosityLevel controls the amount of output to produce.
|
||||
// A higher value produces more output. A value of zero or lower produces
|
||||
// no output (represented using an ellipsis).
|
||||
// If LimitVerbosity is false, then the level is treated as infinite.
|
||||
VerbosityLevel int
|
||||
|
||||
// LimitVerbosity specifies that formatting should respect VerbosityLevel.
|
||||
LimitVerbosity bool
|
||||
}
|
||||
|
||||
// FormatType prints the type as if it were wrapping s.
|
||||
// This may return s as-is depending on the current type and TypeMode mode.
|
||||
func (opts formatOptions) FormatType(t reflect.Type, s textNode) textNode {
|
||||
// Check whether to emit the type or not.
|
||||
switch opts.TypeMode {
|
||||
case autoType:
|
||||
switch t.Kind() {
|
||||
case reflect.Struct, reflect.Slice, reflect.Array, reflect.Map:
|
||||
if s.Equal(textNil) {
|
||||
return s
|
||||
}
|
||||
default:
|
||||
return s
|
||||
}
|
||||
if opts.DiffMode == diffIdentical {
|
||||
return s // elide type for identical nodes
|
||||
}
|
||||
case elideType:
|
||||
return s
|
||||
}
|
||||
|
||||
// Determine the type label, applying special handling for unnamed types.
|
||||
typeName := value.TypeString(t, opts.QualifiedNames)
|
||||
if t.Name() == "" {
|
||||
// According to Go grammar, certain type literals contain symbols that
|
||||
// do not strongly bind to the next lexicographical token (e.g., *T).
|
||||
switch t.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Ptr:
|
||||
typeName = "(" + typeName + ")"
|
||||
}
|
||||
}
|
||||
return &textWrap{Prefix: typeName, Value: wrapParens(s)}
|
||||
}
|
||||
|
||||
// wrapParens wraps s with a set of parenthesis, but avoids it if the
|
||||
// wrapped node itself is already surrounded by a pair of parenthesis or braces.
|
||||
// It handles unwrapping one level of pointer-reference nodes.
|
||||
func wrapParens(s textNode) textNode {
|
||||
var refNode *textWrap
|
||||
if s2, ok := s.(*textWrap); ok {
|
||||
// Unwrap a single pointer reference node.
|
||||
switch s2.Metadata.(type) {
|
||||
case leafReference, trunkReference, trunkReferences:
|
||||
refNode = s2
|
||||
if s3, ok := refNode.Value.(*textWrap); ok {
|
||||
s2 = s3
|
||||
}
|
||||
}
|
||||
|
||||
// Already has delimiters that make parenthesis unnecessary.
|
||||
hasParens := strings.HasPrefix(s2.Prefix, "(") && strings.HasSuffix(s2.Suffix, ")")
|
||||
hasBraces := strings.HasPrefix(s2.Prefix, "{") && strings.HasSuffix(s2.Suffix, "}")
|
||||
if hasParens || hasBraces {
|
||||
return s
|
||||
}
|
||||
}
|
||||
if refNode != nil {
|
||||
refNode.Value = &textWrap{Prefix: "(", Value: refNode.Value, Suffix: ")"}
|
||||
return s
|
||||
}
|
||||
return &textWrap{Prefix: "(", Value: s, Suffix: ")"}
|
||||
}
|
||||
|
||||
// FormatValue prints the reflect.Value, taking extra care to avoid descending
|
||||
// into pointers already in ptrs. As pointers are visited, ptrs is also updated.
|
||||
func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, ptrs *pointerReferences) (out textNode) {
|
||||
if !v.IsValid() {
|
||||
return nil
|
||||
}
|
||||
t := v.Type()
|
||||
|
||||
// Check slice element for cycles.
|
||||
if parentKind == reflect.Slice {
|
||||
ptrRef, visited := ptrs.Push(v.Addr())
|
||||
if visited {
|
||||
return makeLeafReference(ptrRef, false)
|
||||
}
|
||||
defer ptrs.Pop()
|
||||
defer func() { out = wrapTrunkReference(ptrRef, false, out) }()
|
||||
}
|
||||
|
||||
// Check whether there is an Error or String method to call.
|
||||
if !opts.AvoidStringer && v.CanInterface() {
|
||||
// Avoid calling Error or String methods on nil receivers since many
|
||||
// implementations crash when doing so.
|
||||
if (t.Kind() != reflect.Ptr && t.Kind() != reflect.Interface) || !v.IsNil() {
|
||||
var prefix, strVal string
|
||||
func() {
|
||||
// Swallow and ignore any panics from String or Error.
|
||||
defer func() { recover() }()
|
||||
switch v := v.Interface().(type) {
|
||||
case error:
|
||||
strVal = v.Error()
|
||||
prefix = "e"
|
||||
case fmt.Stringer:
|
||||
strVal = v.String()
|
||||
prefix = "s"
|
||||
}
|
||||
}()
|
||||
if prefix != "" {
|
||||
return opts.formatString(prefix, strVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether to explicitly wrap the result with the type.
|
||||
var skipType bool
|
||||
defer func() {
|
||||
if !skipType {
|
||||
out = opts.FormatType(t, out)
|
||||
}
|
||||
}()
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Bool:
|
||||
return textLine(fmt.Sprint(v.Bool()))
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return textLine(fmt.Sprint(v.Int()))
|
||||
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return textLine(fmt.Sprint(v.Uint()))
|
||||
case reflect.Uint8:
|
||||
if parentKind == reflect.Slice || parentKind == reflect.Array {
|
||||
return textLine(formatHex(v.Uint()))
|
||||
}
|
||||
return textLine(fmt.Sprint(v.Uint()))
|
||||
case reflect.Uintptr:
|
||||
return textLine(formatHex(v.Uint()))
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return textLine(fmt.Sprint(v.Float()))
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return textLine(fmt.Sprint(v.Complex()))
|
||||
case reflect.String:
|
||||
return opts.formatString("", v.String())
|
||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||
return textLine(formatPointer(value.PointerOf(v), true))
|
||||
case reflect.Struct:
|
||||
var list textList
|
||||
v := makeAddressable(v) // needed for retrieveUnexportedField
|
||||
maxLen := v.NumField()
|
||||
if opts.LimitVerbosity {
|
||||
maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
|
||||
opts.VerbosityLevel--
|
||||
}
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
vv := v.Field(i)
|
||||
if vv.IsZero() {
|
||||
continue // Elide fields with zero values
|
||||
}
|
||||
if len(list) == maxLen {
|
||||
list.AppendEllipsis(diffStats{})
|
||||
break
|
||||
}
|
||||
sf := t.Field(i)
|
||||
if !isExported(sf.Name) {
|
||||
vv = retrieveUnexportedField(v, sf, true)
|
||||
}
|
||||
s := opts.WithTypeMode(autoType).FormatValue(vv, t.Kind(), ptrs)
|
||||
list = append(list, textRecord{Key: sf.Name, Value: s})
|
||||
}
|
||||
return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
return textNil
|
||||
}
|
||||
|
||||
// Check whether this is a []byte of text data.
|
||||
if t.Elem() == byteType {
|
||||
b := v.Bytes()
|
||||
isPrintSpace := func(r rune) bool { return unicode.IsPrint(r) || unicode.IsSpace(r) }
|
||||
if len(b) > 0 && utf8.Valid(b) && len(bytes.TrimFunc(b, isPrintSpace)) == 0 {
|
||||
out = opts.formatString("", string(b))
|
||||
skipType = true
|
||||
return opts.FormatType(t, out)
|
||||
}
|
||||
}
|
||||
|
||||
fallthrough
|
||||
case reflect.Array:
|
||||
maxLen := v.Len()
|
||||
if opts.LimitVerbosity {
|
||||
maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
|
||||
opts.VerbosityLevel--
|
||||
}
|
||||
var list textList
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if len(list) == maxLen {
|
||||
list.AppendEllipsis(diffStats{})
|
||||
break
|
||||
}
|
||||
s := opts.WithTypeMode(elideType).FormatValue(v.Index(i), t.Kind(), ptrs)
|
||||
list = append(list, textRecord{Value: s})
|
||||
}
|
||||
|
||||
out = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||
if t.Kind() == reflect.Slice && opts.PrintAddresses {
|
||||
header := fmt.Sprintf("ptr:%v, len:%d, cap:%d", formatPointer(value.PointerOf(v), false), v.Len(), v.Cap())
|
||||
out = &textWrap{Prefix: pointerDelimPrefix + header + pointerDelimSuffix, Value: out}
|
||||
}
|
||||
return out
|
||||
case reflect.Map:
|
||||
if v.IsNil() {
|
||||
return textNil
|
||||
}
|
||||
|
||||
// Check pointer for cycles.
|
||||
ptrRef, visited := ptrs.Push(v)
|
||||
if visited {
|
||||
return makeLeafReference(ptrRef, opts.PrintAddresses)
|
||||
}
|
||||
defer ptrs.Pop()
|
||||
|
||||
maxLen := v.Len()
|
||||
if opts.LimitVerbosity {
|
||||
maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
|
||||
opts.VerbosityLevel--
|
||||
}
|
||||
var list textList
|
||||
for _, k := range value.SortKeys(v.MapKeys()) {
|
||||
if len(list) == maxLen {
|
||||
list.AppendEllipsis(diffStats{})
|
||||
break
|
||||
}
|
||||
sk := formatMapKey(k, false, ptrs)
|
||||
sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), t.Kind(), ptrs)
|
||||
list = append(list, textRecord{Key: sk, Value: sv})
|
||||
}
|
||||
|
||||
out = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||
out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out)
|
||||
return out
|
||||
case reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
return textNil
|
||||
}
|
||||
|
||||
// Check pointer for cycles.
|
||||
ptrRef, visited := ptrs.Push(v)
|
||||
if visited {
|
||||
out = makeLeafReference(ptrRef, opts.PrintAddresses)
|
||||
return &textWrap{Prefix: "&", Value: out}
|
||||
}
|
||||
defer ptrs.Pop()
|
||||
|
||||
// Skip the name only if this is an unnamed pointer type.
|
||||
// Otherwise taking the address of a value does not reproduce
|
||||
// the named pointer type.
|
||||
if v.Type().Name() == "" {
|
||||
skipType = true // Let the underlying value print the type instead
|
||||
}
|
||||
out = opts.FormatValue(v.Elem(), t.Kind(), ptrs)
|
||||
out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out)
|
||||
out = &textWrap{Prefix: "&", Value: out}
|
||||
return out
|
||||
case reflect.Interface:
|
||||
if v.IsNil() {
|
||||
return textNil
|
||||
}
|
||||
// Interfaces accept different concrete types,
|
||||
// so configure the underlying value to explicitly print the type.
|
||||
return opts.WithTypeMode(emitType).FormatValue(v.Elem(), t.Kind(), ptrs)
|
||||
default:
|
||||
panic(fmt.Sprintf("%v kind not handled", v.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
func (opts formatOptions) formatString(prefix, s string) textNode {
|
||||
maxLen := len(s)
|
||||
maxLines := strings.Count(s, "\n") + 1
|
||||
if opts.LimitVerbosity {
|
||||
maxLen = (1 << opts.verbosity()) << 5 // 32, 64, 128, 256, etc...
|
||||
maxLines = (1 << opts.verbosity()) << 2 // 4, 8, 16, 32, 64, etc...
|
||||
}
|
||||
|
||||
// For multiline strings, use the triple-quote syntax,
|
||||
// but only use it when printing removed or inserted nodes since
|
||||
// we only want the extra verbosity for those cases.
|
||||
lines := strings.Split(strings.TrimSuffix(s, "\n"), "\n")
|
||||
isTripleQuoted := len(lines) >= 4 && (opts.DiffMode == '-' || opts.DiffMode == '+')
|
||||
for i := 0; i < len(lines) && isTripleQuoted; i++ {
|
||||
lines[i] = strings.TrimPrefix(strings.TrimSuffix(lines[i], "\r"), "\r") // trim leading/trailing carriage returns for legacy Windows endline support
|
||||
isPrintable := func(r rune) bool {
|
||||
return unicode.IsPrint(r) || r == '\t' // specially treat tab as printable
|
||||
}
|
||||
line := lines[i]
|
||||
isTripleQuoted = !strings.HasPrefix(strings.TrimPrefix(line, prefix), `"""`) && !strings.HasPrefix(line, "...") && strings.TrimFunc(line, isPrintable) == "" && len(line) <= maxLen
|
||||
}
|
||||
if isTripleQuoted {
|
||||
var list textList
|
||||
list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true})
|
||||
for i, line := range lines {
|
||||
if numElided := len(lines) - i; i == maxLines-1 && numElided > 1 {
|
||||
comment := commentString(fmt.Sprintf("%d elided lines", numElided))
|
||||
list = append(list, textRecord{Diff: opts.DiffMode, Value: textEllipsis, ElideComma: true, Comment: comment})
|
||||
break
|
||||
}
|
||||
list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(line), ElideComma: true})
|
||||
}
|
||||
list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true})
|
||||
return &textWrap{Prefix: "(", Value: list, Suffix: ")"}
|
||||
}
|
||||
|
||||
// Format the string as a single-line quoted string.
|
||||
if len(s) > maxLen+len(textEllipsis) {
|
||||
return textLine(prefix + formatString(s[:maxLen]) + string(textEllipsis))
|
||||
}
|
||||
return textLine(prefix + formatString(s))
|
||||
}
|
||||
|
||||
// formatMapKey formats v as if it were a map key.
|
||||
// The result is guaranteed to be a single line.
|
||||
func formatMapKey(v reflect.Value, disambiguate bool, ptrs *pointerReferences) string {
|
||||
var opts formatOptions
|
||||
opts.DiffMode = diffIdentical
|
||||
opts.TypeMode = elideType
|
||||
opts.PrintAddresses = disambiguate
|
||||
opts.AvoidStringer = disambiguate
|
||||
opts.QualifiedNames = disambiguate
|
||||
opts.VerbosityLevel = maxVerbosityPreset
|
||||
opts.LimitVerbosity = true
|
||||
s := opts.FormatValue(v, reflect.Map, ptrs).String()
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
|
||||
// formatString prints s as a double-quoted or backtick-quoted string.
|
||||
func formatString(s string) string {
|
||||
// Use quoted string if it the same length as a raw string literal.
|
||||
// Otherwise, attempt to use the raw string form.
|
||||
qs := strconv.Quote(s)
|
||||
if len(qs) == 1+len(s)+1 {
|
||||
return qs
|
||||
}
|
||||
|
||||
// Disallow newlines to ensure output is a single line.
|
||||
// Only allow printable runes for readability purposes.
|
||||
rawInvalid := func(r rune) bool {
|
||||
return r == '`' || r == '\n' || !(unicode.IsPrint(r) || r == '\t')
|
||||
}
|
||||
if utf8.ValidString(s) && strings.IndexFunc(s, rawInvalid) < 0 {
|
||||
return "`" + s + "`"
|
||||
}
|
||||
return qs
|
||||
}
|
||||
|
||||
// formatHex prints u as a hexadecimal integer in Go notation.
|
||||
func formatHex(u uint64) string {
|
||||
var f string
|
||||
switch {
|
||||
case u <= 0xff:
|
||||
f = "0x%02x"
|
||||
case u <= 0xffff:
|
||||
f = "0x%04x"
|
||||
case u <= 0xffffff:
|
||||
f = "0x%06x"
|
||||
case u <= 0xffffffff:
|
||||
f = "0x%08x"
|
||||
case u <= 0xffffffffff:
|
||||
f = "0x%010x"
|
||||
case u <= 0xffffffffffff:
|
||||
f = "0x%012x"
|
||||
case u <= 0xffffffffffffff:
|
||||
f = "0x%014x"
|
||||
case u <= 0xffffffffffffffff:
|
||||
f = "0x%016x"
|
||||
}
|
||||
return fmt.Sprintf(f, u)
|
||||
}
|
||||
614
vendor/github.com/google/go-cmp/cmp/report_slices.go
generated
vendored
Normal file
614
vendor/github.com/google/go-cmp/cmp/report_slices.go
generated
vendored
Normal file
|
|
@ -0,0 +1,614 @@
|
|||
// 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 cmp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/diff"
|
||||
)
|
||||
|
||||
// CanFormatDiffSlice reports whether we support custom formatting for nodes
|
||||
// that are slices of primitive kinds or strings.
|
||||
func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool {
|
||||
switch {
|
||||
case opts.DiffMode != diffUnknown:
|
||||
return false // Must be formatting in diff mode
|
||||
case v.NumDiff == 0:
|
||||
return false // No differences detected
|
||||
case !v.ValueX.IsValid() || !v.ValueY.IsValid():
|
||||
return false // Both values must be valid
|
||||
case v.NumIgnored > 0:
|
||||
return false // Some ignore option was used
|
||||
case v.NumTransformed > 0:
|
||||
return false // Some transform option was used
|
||||
case v.NumCompared > 1:
|
||||
return false // More than one comparison was used
|
||||
case v.NumCompared == 1 && v.Type.Name() != "":
|
||||
// The need for cmp to check applicability of options on every element
|
||||
// in a slice is a significant performance detriment for large []byte.
|
||||
// The workaround is to specify Comparer(bytes.Equal),
|
||||
// which enables cmp to compare []byte more efficiently.
|
||||
// If they differ, we still want to provide batched diffing.
|
||||
// The logic disallows named types since they tend to have their own
|
||||
// String method, with nicer formatting than what this provides.
|
||||
return false
|
||||
}
|
||||
|
||||
// Check whether this is an interface with the same concrete types.
|
||||
t := v.Type
|
||||
vx, vy := v.ValueX, v.ValueY
|
||||
if t.Kind() == reflect.Interface && !vx.IsNil() && !vy.IsNil() && vx.Elem().Type() == vy.Elem().Type() {
|
||||
vx, vy = vx.Elem(), vy.Elem()
|
||||
t = vx.Type()
|
||||
}
|
||||
|
||||
// Check whether we provide specialized diffing for this type.
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
case reflect.Array, reflect.Slice:
|
||||
// Only slices of primitive types have specialized handling.
|
||||
switch t.Elem().Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
|
||||
reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
// Both slice values have to be non-empty.
|
||||
if t.Kind() == reflect.Slice && (vx.Len() == 0 || vy.Len() == 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
// If a sufficient number of elements already differ,
|
||||
// use specialized formatting even if length requirement is not met.
|
||||
if v.NumDiff > v.NumSame {
|
||||
return true
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
// Use specialized string diffing for longer slices or strings.
|
||||
const minLength = 32
|
||||
return vx.Len() >= minLength && vy.Len() >= minLength
|
||||
}
|
||||
|
||||
// FormatDiffSlice prints a diff for the slices (or strings) represented by v.
|
||||
// This provides custom-tailored logic to make printing of differences in
|
||||
// textual strings and slices of primitive kinds more readable.
|
||||
func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
|
||||
assert(opts.DiffMode == diffUnknown)
|
||||
t, vx, vy := v.Type, v.ValueX, v.ValueY
|
||||
if t.Kind() == reflect.Interface {
|
||||
vx, vy = vx.Elem(), vy.Elem()
|
||||
t = vx.Type()
|
||||
opts = opts.WithTypeMode(emitType)
|
||||
}
|
||||
|
||||
// Auto-detect the type of the data.
|
||||
var sx, sy string
|
||||
var ssx, ssy []string
|
||||
var isString, isMostlyText, isPureLinedText, isBinary bool
|
||||
switch {
|
||||
case t.Kind() == reflect.String:
|
||||
sx, sy = vx.String(), vy.String()
|
||||
isString = true
|
||||
case t.Kind() == reflect.Slice && t.Elem() == byteType:
|
||||
sx, sy = string(vx.Bytes()), string(vy.Bytes())
|
||||
isString = true
|
||||
case t.Kind() == reflect.Array:
|
||||
// Arrays need to be addressable for slice operations to work.
|
||||
vx2, vy2 := reflect.New(t).Elem(), reflect.New(t).Elem()
|
||||
vx2.Set(vx)
|
||||
vy2.Set(vy)
|
||||
vx, vy = vx2, vy2
|
||||
}
|
||||
if isString {
|
||||
var numTotalRunes, numValidRunes, numLines, lastLineIdx, maxLineLen int
|
||||
for i, r := range sx + sy {
|
||||
numTotalRunes++
|
||||
if (unicode.IsPrint(r) || unicode.IsSpace(r)) && r != utf8.RuneError {
|
||||
numValidRunes++
|
||||
}
|
||||
if r == '\n' {
|
||||
if maxLineLen < i-lastLineIdx {
|
||||
maxLineLen = i - lastLineIdx
|
||||
}
|
||||
lastLineIdx = i + 1
|
||||
numLines++
|
||||
}
|
||||
}
|
||||
isPureText := numValidRunes == numTotalRunes
|
||||
isMostlyText = float64(numValidRunes) > math.Floor(0.90*float64(numTotalRunes))
|
||||
isPureLinedText = isPureText && numLines >= 4 && maxLineLen <= 1024
|
||||
isBinary = !isMostlyText
|
||||
|
||||
// Avoid diffing by lines if it produces a significantly more complex
|
||||
// edit script than diffing by bytes.
|
||||
if isPureLinedText {
|
||||
ssx = strings.Split(sx, "\n")
|
||||
ssy = strings.Split(sy, "\n")
|
||||
esLines := diff.Difference(len(ssx), len(ssy), func(ix, iy int) diff.Result {
|
||||
return diff.BoolResult(ssx[ix] == ssy[iy])
|
||||
})
|
||||
esBytes := diff.Difference(len(sx), len(sy), func(ix, iy int) diff.Result {
|
||||
return diff.BoolResult(sx[ix] == sy[iy])
|
||||
})
|
||||
efficiencyLines := float64(esLines.Dist()) / float64(len(esLines))
|
||||
efficiencyBytes := float64(esBytes.Dist()) / float64(len(esBytes))
|
||||
quotedLength := len(strconv.Quote(sx + sy))
|
||||
unquotedLength := len(sx) + len(sy)
|
||||
escapeExpansionRatio := float64(quotedLength) / float64(unquotedLength)
|
||||
isPureLinedText = efficiencyLines < 4*efficiencyBytes || escapeExpansionRatio > 1.1
|
||||
}
|
||||
}
|
||||
|
||||
// Format the string into printable records.
|
||||
var list textList
|
||||
var delim string
|
||||
switch {
|
||||
// If the text appears to be multi-lined text,
|
||||
// then perform differencing across individual lines.
|
||||
case isPureLinedText:
|
||||
list = opts.formatDiffSlice(
|
||||
reflect.ValueOf(ssx), reflect.ValueOf(ssy), 1, "line",
|
||||
func(v reflect.Value, d diffMode) textRecord {
|
||||
s := formatString(v.Index(0).String())
|
||||
return textRecord{Diff: d, Value: textLine(s)}
|
||||
},
|
||||
)
|
||||
delim = "\n"
|
||||
|
||||
// If possible, use a custom triple-quote (""") syntax for printing
|
||||
// differences in a string literal. This format is more readable,
|
||||
// but has edge-cases where differences are visually indistinguishable.
|
||||
// This format is avoided under the following conditions:
|
||||
// - A line starts with `"""`
|
||||
// - A line starts with "..."
|
||||
// - A line contains non-printable characters
|
||||
// - Adjacent different lines differ only by whitespace
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// """
|
||||
// ... // 3 identical lines
|
||||
// foo
|
||||
// bar
|
||||
// - baz
|
||||
// + BAZ
|
||||
// """
|
||||
isTripleQuoted := true
|
||||
prevRemoveLines := map[string]bool{}
|
||||
prevInsertLines := map[string]bool{}
|
||||
var list2 textList
|
||||
list2 = append(list2, textRecord{Value: textLine(`"""`), ElideComma: true})
|
||||
for _, r := range list {
|
||||
if !r.Value.Equal(textEllipsis) {
|
||||
line, _ := strconv.Unquote(string(r.Value.(textLine)))
|
||||
line = strings.TrimPrefix(strings.TrimSuffix(line, "\r"), "\r") // trim leading/trailing carriage returns for legacy Windows endline support
|
||||
normLine := strings.Map(func(r rune) rune {
|
||||
if unicode.IsSpace(r) {
|
||||
return -1 // drop whitespace to avoid visually indistinguishable output
|
||||
}
|
||||
return r
|
||||
}, line)
|
||||
isPrintable := func(r rune) bool {
|
||||
return unicode.IsPrint(r) || r == '\t' // specially treat tab as printable
|
||||
}
|
||||
isTripleQuoted = !strings.HasPrefix(line, `"""`) && !strings.HasPrefix(line, "...") && strings.TrimFunc(line, isPrintable) == ""
|
||||
switch r.Diff {
|
||||
case diffRemoved:
|
||||
isTripleQuoted = isTripleQuoted && !prevInsertLines[normLine]
|
||||
prevRemoveLines[normLine] = true
|
||||
case diffInserted:
|
||||
isTripleQuoted = isTripleQuoted && !prevRemoveLines[normLine]
|
||||
prevInsertLines[normLine] = true
|
||||
}
|
||||
if !isTripleQuoted {
|
||||
break
|
||||
}
|
||||
r.Value = textLine(line)
|
||||
r.ElideComma = true
|
||||
}
|
||||
if !(r.Diff == diffRemoved || r.Diff == diffInserted) { // start a new non-adjacent difference group
|
||||
prevRemoveLines = map[string]bool{}
|
||||
prevInsertLines = map[string]bool{}
|
||||
}
|
||||
list2 = append(list2, r)
|
||||
}
|
||||
if r := list2[len(list2)-1]; r.Diff == diffIdentical && len(r.Value.(textLine)) == 0 {
|
||||
list2 = list2[:len(list2)-1] // elide single empty line at the end
|
||||
}
|
||||
list2 = append(list2, textRecord{Value: textLine(`"""`), ElideComma: true})
|
||||
if isTripleQuoted {
|
||||
var out textNode = &textWrap{Prefix: "(", Value: list2, Suffix: ")"}
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
if t != stringType {
|
||||
out = opts.FormatType(t, out)
|
||||
}
|
||||
case reflect.Slice:
|
||||
// Always emit type for slices since the triple-quote syntax
|
||||
// looks like a string (not a slice).
|
||||
opts = opts.WithTypeMode(emitType)
|
||||
out = opts.FormatType(t, out)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// If the text appears to be single-lined text,
|
||||
// then perform differencing in approximately fixed-sized chunks.
|
||||
// The output is printed as quoted strings.
|
||||
case isMostlyText:
|
||||
list = opts.formatDiffSlice(
|
||||
reflect.ValueOf(sx), reflect.ValueOf(sy), 64, "byte",
|
||||
func(v reflect.Value, d diffMode) textRecord {
|
||||
s := formatString(v.String())
|
||||
return textRecord{Diff: d, Value: textLine(s)}
|
||||
},
|
||||
)
|
||||
|
||||
// If the text appears to be binary data,
|
||||
// then perform differencing in approximately fixed-sized chunks.
|
||||
// The output is inspired by hexdump.
|
||||
case isBinary:
|
||||
list = opts.formatDiffSlice(
|
||||
reflect.ValueOf(sx), reflect.ValueOf(sy), 16, "byte",
|
||||
func(v reflect.Value, d diffMode) textRecord {
|
||||
var ss []string
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
ss = append(ss, formatHex(v.Index(i).Uint()))
|
||||
}
|
||||
s := strings.Join(ss, ", ")
|
||||
comment := commentString(fmt.Sprintf("%c|%v|", d, formatASCII(v.String())))
|
||||
return textRecord{Diff: d, Value: textLine(s), Comment: comment}
|
||||
},
|
||||
)
|
||||
|
||||
// For all other slices of primitive types,
|
||||
// then perform differencing in approximately fixed-sized chunks.
|
||||
// The size of each chunk depends on the width of the element kind.
|
||||
default:
|
||||
var chunkSize int
|
||||
if t.Elem().Kind() == reflect.Bool {
|
||||
chunkSize = 16
|
||||
} else {
|
||||
switch t.Elem().Bits() {
|
||||
case 8:
|
||||
chunkSize = 16
|
||||
case 16:
|
||||
chunkSize = 12
|
||||
case 32:
|
||||
chunkSize = 8
|
||||
default:
|
||||
chunkSize = 8
|
||||
}
|
||||
}
|
||||
list = opts.formatDiffSlice(
|
||||
vx, vy, chunkSize, t.Elem().Kind().String(),
|
||||
func(v reflect.Value, d diffMode) textRecord {
|
||||
var ss []string
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
switch t.Elem().Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
ss = append(ss, fmt.Sprint(v.Index(i).Int()))
|
||||
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
ss = append(ss, fmt.Sprint(v.Index(i).Uint()))
|
||||
case reflect.Uint8, reflect.Uintptr:
|
||||
ss = append(ss, formatHex(v.Index(i).Uint()))
|
||||
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
|
||||
ss = append(ss, fmt.Sprint(v.Index(i).Interface()))
|
||||
}
|
||||
}
|
||||
s := strings.Join(ss, ", ")
|
||||
return textRecord{Diff: d, Value: textLine(s)}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Wrap the output with appropriate type information.
|
||||
var out textNode = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||
if !isMostlyText {
|
||||
// The "{...}" byte-sequence literal is not valid Go syntax for strings.
|
||||
// Emit the type for extra clarity (e.g. "string{...}").
|
||||
if t.Kind() == reflect.String {
|
||||
opts = opts.WithTypeMode(emitType)
|
||||
}
|
||||
return opts.FormatType(t, out)
|
||||
}
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
out = &textWrap{Prefix: "strings.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)}
|
||||
if t != stringType {
|
||||
out = opts.FormatType(t, out)
|
||||
}
|
||||
case reflect.Slice:
|
||||
out = &textWrap{Prefix: "bytes.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)}
|
||||
if t != bytesType {
|
||||
out = opts.FormatType(t, out)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// formatASCII formats s as an ASCII string.
|
||||
// This is useful for printing binary strings in a semi-legible way.
|
||||
func formatASCII(s string) string {
|
||||
b := bytes.Repeat([]byte{'.'}, len(s))
|
||||
for i := 0; i < len(s); i++ {
|
||||
if ' ' <= s[i] && s[i] <= '~' {
|
||||
b[i] = s[i]
|
||||
}
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (opts formatOptions) formatDiffSlice(
|
||||
vx, vy reflect.Value, chunkSize int, name string,
|
||||
makeRec func(reflect.Value, diffMode) textRecord,
|
||||
) (list textList) {
|
||||
eq := func(ix, iy int) bool {
|
||||
return vx.Index(ix).Interface() == vy.Index(iy).Interface()
|
||||
}
|
||||
es := diff.Difference(vx.Len(), vy.Len(), func(ix, iy int) diff.Result {
|
||||
return diff.BoolResult(eq(ix, iy))
|
||||
})
|
||||
|
||||
appendChunks := func(v reflect.Value, d diffMode) int {
|
||||
n0 := v.Len()
|
||||
for v.Len() > 0 {
|
||||
n := chunkSize
|
||||
if n > v.Len() {
|
||||
n = v.Len()
|
||||
}
|
||||
list = append(list, makeRec(v.Slice(0, n), d))
|
||||
v = v.Slice(n, v.Len())
|
||||
}
|
||||
return n0 - v.Len()
|
||||
}
|
||||
|
||||
var numDiffs int
|
||||
maxLen := -1
|
||||
if opts.LimitVerbosity {
|
||||
maxLen = (1 << opts.verbosity()) << 2 // 4, 8, 16, 32, 64, etc...
|
||||
opts.VerbosityLevel--
|
||||
}
|
||||
|
||||
groups := coalesceAdjacentEdits(name, es)
|
||||
groups = coalesceInterveningIdentical(groups, chunkSize/4)
|
||||
groups = cleanupSurroundingIdentical(groups, eq)
|
||||
maxGroup := diffStats{Name: name}
|
||||
for i, ds := range groups {
|
||||
if maxLen >= 0 && numDiffs >= maxLen {
|
||||
maxGroup = maxGroup.Append(ds)
|
||||
continue
|
||||
}
|
||||
|
||||
// Print equal.
|
||||
if ds.NumDiff() == 0 {
|
||||
// Compute the number of leading and trailing equal bytes to print.
|
||||
var numLo, numHi int
|
||||
numEqual := ds.NumIgnored + ds.NumIdentical
|
||||
for numLo < chunkSize*numContextRecords && numLo+numHi < numEqual && i != 0 {
|
||||
numLo++
|
||||
}
|
||||
for numHi < chunkSize*numContextRecords && numLo+numHi < numEqual && i != len(groups)-1 {
|
||||
numHi++
|
||||
}
|
||||
if numEqual-(numLo+numHi) <= chunkSize && ds.NumIgnored == 0 {
|
||||
numHi = numEqual - numLo // Avoid pointless coalescing of single equal row
|
||||
}
|
||||
|
||||
// Print the equal bytes.
|
||||
appendChunks(vx.Slice(0, numLo), diffIdentical)
|
||||
if numEqual > numLo+numHi {
|
||||
ds.NumIdentical -= numLo + numHi
|
||||
list.AppendEllipsis(ds)
|
||||
}
|
||||
appendChunks(vx.Slice(numEqual-numHi, numEqual), diffIdentical)
|
||||
vx = vx.Slice(numEqual, vx.Len())
|
||||
vy = vy.Slice(numEqual, vy.Len())
|
||||
continue
|
||||
}
|
||||
|
||||
// Print unequal.
|
||||
len0 := len(list)
|
||||
nx := appendChunks(vx.Slice(0, ds.NumIdentical+ds.NumRemoved+ds.NumModified), diffRemoved)
|
||||
vx = vx.Slice(nx, vx.Len())
|
||||
ny := appendChunks(vy.Slice(0, ds.NumIdentical+ds.NumInserted+ds.NumModified), diffInserted)
|
||||
vy = vy.Slice(ny, vy.Len())
|
||||
numDiffs += len(list) - len0
|
||||
}
|
||||
if maxGroup.IsZero() {
|
||||
assert(vx.Len() == 0 && vy.Len() == 0)
|
||||
} else {
|
||||
list.AppendEllipsis(maxGroup)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// coalesceAdjacentEdits coalesces the list of edits into groups of adjacent
|
||||
// equal or unequal counts.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// Input: "..XXY...Y"
|
||||
// Output: [
|
||||
// {NumIdentical: 2},
|
||||
// {NumRemoved: 2, NumInserted 1},
|
||||
// {NumIdentical: 3},
|
||||
// {NumInserted: 1},
|
||||
// ]
|
||||
func coalesceAdjacentEdits(name string, es diff.EditScript) (groups []diffStats) {
|
||||
var prevMode byte
|
||||
lastStats := func(mode byte) *diffStats {
|
||||
if prevMode != mode {
|
||||
groups = append(groups, diffStats{Name: name})
|
||||
prevMode = mode
|
||||
}
|
||||
return &groups[len(groups)-1]
|
||||
}
|
||||
for _, e := range es {
|
||||
switch e {
|
||||
case diff.Identity:
|
||||
lastStats('=').NumIdentical++
|
||||
case diff.UniqueX:
|
||||
lastStats('!').NumRemoved++
|
||||
case diff.UniqueY:
|
||||
lastStats('!').NumInserted++
|
||||
case diff.Modified:
|
||||
lastStats('!').NumModified++
|
||||
}
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
// coalesceInterveningIdentical coalesces sufficiently short (<= windowSize)
|
||||
// equal groups into adjacent unequal groups that currently result in a
|
||||
// dual inserted/removed printout. This acts as a high-pass filter to smooth
|
||||
// out high-frequency changes within the windowSize.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// WindowSize: 16,
|
||||
// Input: [
|
||||
// {NumIdentical: 61}, // group 0
|
||||
// {NumRemoved: 3, NumInserted: 1}, // group 1
|
||||
// {NumIdentical: 6}, // ├── coalesce
|
||||
// {NumInserted: 2}, // ├── coalesce
|
||||
// {NumIdentical: 1}, // ├── coalesce
|
||||
// {NumRemoved: 9}, // └── coalesce
|
||||
// {NumIdentical: 64}, // group 2
|
||||
// {NumRemoved: 3, NumInserted: 1}, // group 3
|
||||
// {NumIdentical: 6}, // ├── coalesce
|
||||
// {NumInserted: 2}, // ├── coalesce
|
||||
// {NumIdentical: 1}, // ├── coalesce
|
||||
// {NumRemoved: 7}, // ├── coalesce
|
||||
// {NumIdentical: 1}, // ├── coalesce
|
||||
// {NumRemoved: 2}, // └── coalesce
|
||||
// {NumIdentical: 63}, // group 4
|
||||
// ]
|
||||
// Output: [
|
||||
// {NumIdentical: 61},
|
||||
// {NumIdentical: 7, NumRemoved: 12, NumInserted: 3},
|
||||
// {NumIdentical: 64},
|
||||
// {NumIdentical: 8, NumRemoved: 12, NumInserted: 3},
|
||||
// {NumIdentical: 63},
|
||||
// ]
|
||||
func coalesceInterveningIdentical(groups []diffStats, windowSize int) []diffStats {
|
||||
groups, groupsOrig := groups[:0], groups
|
||||
for i, ds := range groupsOrig {
|
||||
if len(groups) >= 2 && ds.NumDiff() > 0 {
|
||||
prev := &groups[len(groups)-2] // Unequal group
|
||||
curr := &groups[len(groups)-1] // Equal group
|
||||
next := &groupsOrig[i] // Unequal group
|
||||
hadX, hadY := prev.NumRemoved > 0, prev.NumInserted > 0
|
||||
hasX, hasY := next.NumRemoved > 0, next.NumInserted > 0
|
||||
if ((hadX || hasX) && (hadY || hasY)) && curr.NumIdentical <= windowSize {
|
||||
*prev = prev.Append(*curr).Append(*next)
|
||||
groups = groups[:len(groups)-1] // Truncate off equal group
|
||||
continue
|
||||
}
|
||||
}
|
||||
groups = append(groups, ds)
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
// cleanupSurroundingIdentical scans through all unequal groups, and
|
||||
// moves any leading sequence of equal elements to the preceding equal group and
|
||||
// moves and trailing sequence of equal elements to the succeeding equal group.
|
||||
//
|
||||
// This is necessary since coalesceInterveningIdentical may coalesce edit groups
|
||||
// together such that leading/trailing spans of equal elements becomes possible.
|
||||
// Note that this can occur even with an optimal diffing algorithm.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// Input: [
|
||||
// {NumIdentical: 61},
|
||||
// {NumIdentical: 1 , NumRemoved: 11, NumInserted: 2}, // assume 3 leading identical elements
|
||||
// {NumIdentical: 67},
|
||||
// {NumIdentical: 7, NumRemoved: 12, NumInserted: 3}, // assume 10 trailing identical elements
|
||||
// {NumIdentical: 54},
|
||||
// ]
|
||||
// Output: [
|
||||
// {NumIdentical: 64}, // incremented by 3
|
||||
// {NumRemoved: 9},
|
||||
// {NumIdentical: 67},
|
||||
// {NumRemoved: 9},
|
||||
// {NumIdentical: 64}, // incremented by 10
|
||||
// ]
|
||||
func cleanupSurroundingIdentical(groups []diffStats, eq func(i, j int) bool) []diffStats {
|
||||
var ix, iy int // indexes into sequence x and y
|
||||
for i, ds := range groups {
|
||||
// Handle equal group.
|
||||
if ds.NumDiff() == 0 {
|
||||
ix += ds.NumIdentical
|
||||
iy += ds.NumIdentical
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle unequal group.
|
||||
nx := ds.NumIdentical + ds.NumRemoved + ds.NumModified
|
||||
ny := ds.NumIdentical + ds.NumInserted + ds.NumModified
|
||||
var numLeadingIdentical, numTrailingIdentical int
|
||||
for j := 0; j < nx && j < ny && eq(ix+j, iy+j); j++ {
|
||||
numLeadingIdentical++
|
||||
}
|
||||
for j := 0; j < nx && j < ny && eq(ix+nx-1-j, iy+ny-1-j); j++ {
|
||||
numTrailingIdentical++
|
||||
}
|
||||
if numIdentical := numLeadingIdentical + numTrailingIdentical; numIdentical > 0 {
|
||||
if numLeadingIdentical > 0 {
|
||||
// Remove leading identical span from this group and
|
||||
// insert it into the preceding group.
|
||||
if i-1 >= 0 {
|
||||
groups[i-1].NumIdentical += numLeadingIdentical
|
||||
} else {
|
||||
// No preceding group exists, so prepend a new group,
|
||||
// but do so after we finish iterating over all groups.
|
||||
defer func() {
|
||||
groups = append([]diffStats{{Name: groups[0].Name, NumIdentical: numLeadingIdentical}}, groups...)
|
||||
}()
|
||||
}
|
||||
// Increment indexes since the preceding group would have handled this.
|
||||
ix += numLeadingIdentical
|
||||
iy += numLeadingIdentical
|
||||
}
|
||||
if numTrailingIdentical > 0 {
|
||||
// Remove trailing identical span from this group and
|
||||
// insert it into the succeeding group.
|
||||
if i+1 < len(groups) {
|
||||
groups[i+1].NumIdentical += numTrailingIdentical
|
||||
} else {
|
||||
// No succeeding group exists, so append a new group,
|
||||
// but do so after we finish iterating over all groups.
|
||||
defer func() {
|
||||
groups = append(groups, diffStats{Name: groups[len(groups)-1].Name, NumIdentical: numTrailingIdentical})
|
||||
}()
|
||||
}
|
||||
// Do not increment indexes since the succeeding group will handle this.
|
||||
}
|
||||
|
||||
// Update this group since some identical elements were removed.
|
||||
nx -= numIdentical
|
||||
ny -= numIdentical
|
||||
groups[i] = diffStats{Name: ds.Name, NumRemoved: nx, NumInserted: ny}
|
||||
}
|
||||
ix += nx
|
||||
iy += ny
|
||||
}
|
||||
return groups
|
||||
}
|
||||
432
vendor/github.com/google/go-cmp/cmp/report_text.go
generated
vendored
Normal file
432
vendor/github.com/google/go-cmp/cmp/report_text.go
generated
vendored
Normal file
|
|
@ -0,0 +1,432 @@
|
|||
// 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 cmp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/flags"
|
||||
)
|
||||
|
||||
var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0
|
||||
|
||||
const maxColumnLength = 80
|
||||
|
||||
type indentMode int
|
||||
|
||||
func (n indentMode) appendIndent(b []byte, d diffMode) []byte {
|
||||
// The output of Diff is documented as being unstable to provide future
|
||||
// flexibility in changing the output for more humanly readable reports.
|
||||
// This logic intentionally introduces instability to the exact output
|
||||
// so that users can detect accidental reliance on stability early on,
|
||||
// rather than much later when an actual change to the format occurs.
|
||||
if flags.Deterministic || randBool {
|
||||
// Use regular spaces (U+0020).
|
||||
switch d {
|
||||
case diffUnknown, diffIdentical:
|
||||
b = append(b, " "...)
|
||||
case diffRemoved:
|
||||
b = append(b, "- "...)
|
||||
case diffInserted:
|
||||
b = append(b, "+ "...)
|
||||
}
|
||||
} else {
|
||||
// Use non-breaking spaces (U+00a0).
|
||||
switch d {
|
||||
case diffUnknown, diffIdentical:
|
||||
b = append(b, " "...)
|
||||
case diffRemoved:
|
||||
b = append(b, "- "...)
|
||||
case diffInserted:
|
||||
b = append(b, "+ "...)
|
||||
}
|
||||
}
|
||||
return repeatCount(n).appendChar(b, '\t')
|
||||
}
|
||||
|
||||
type repeatCount int
|
||||
|
||||
func (n repeatCount) appendChar(b []byte, c byte) []byte {
|
||||
for ; n > 0; n-- {
|
||||
b = append(b, c)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// textNode is a simplified tree-based representation of structured text.
|
||||
// Possible node types are textWrap, textList, or textLine.
|
||||
type textNode interface {
|
||||
// Len reports the length in bytes of a single-line version of the tree.
|
||||
// Nested textRecord.Diff and textRecord.Comment fields are ignored.
|
||||
Len() int
|
||||
// Equal reports whether the two trees are structurally identical.
|
||||
// Nested textRecord.Diff and textRecord.Comment fields are compared.
|
||||
Equal(textNode) bool
|
||||
// String returns the string representation of the text tree.
|
||||
// It is not guaranteed that len(x.String()) == x.Len(),
|
||||
// nor that x.String() == y.String() implies that x.Equal(y).
|
||||
String() string
|
||||
|
||||
// formatCompactTo formats the contents of the tree as a single-line string
|
||||
// to the provided buffer. Any nested textRecord.Diff and textRecord.Comment
|
||||
// fields are ignored.
|
||||
//
|
||||
// However, not all nodes in the tree should be collapsed as a single-line.
|
||||
// If a node can be collapsed as a single-line, it is replaced by a textLine
|
||||
// node. Since the top-level node cannot replace itself, this also returns
|
||||
// the current node itself.
|
||||
//
|
||||
// This does not mutate the receiver.
|
||||
formatCompactTo([]byte, diffMode) ([]byte, textNode)
|
||||
// formatExpandedTo formats the contents of the tree as a multi-line string
|
||||
// to the provided buffer. In order for column alignment to operate well,
|
||||
// formatCompactTo must be called before calling formatExpandedTo.
|
||||
formatExpandedTo([]byte, diffMode, indentMode) []byte
|
||||
}
|
||||
|
||||
// textWrap is a wrapper that concatenates a prefix and/or a suffix
|
||||
// to the underlying node.
|
||||
type textWrap struct {
|
||||
Prefix string // e.g., "bytes.Buffer{"
|
||||
Value textNode // textWrap | textList | textLine
|
||||
Suffix string // e.g., "}"
|
||||
Metadata interface{} // arbitrary metadata; has no effect on formatting
|
||||
}
|
||||
|
||||
func (s *textWrap) Len() int {
|
||||
return len(s.Prefix) + s.Value.Len() + len(s.Suffix)
|
||||
}
|
||||
func (s1 *textWrap) Equal(s2 textNode) bool {
|
||||
if s2, ok := s2.(*textWrap); ok {
|
||||
return s1.Prefix == s2.Prefix && s1.Value.Equal(s2.Value) && s1.Suffix == s2.Suffix
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (s *textWrap) String() string {
|
||||
var d diffMode
|
||||
var n indentMode
|
||||
_, s2 := s.formatCompactTo(nil, d)
|
||||
b := n.appendIndent(nil, d) // Leading indent
|
||||
b = s2.formatExpandedTo(b, d, n) // Main body
|
||||
b = append(b, '\n') // Trailing newline
|
||||
return string(b)
|
||||
}
|
||||
func (s *textWrap) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
|
||||
n0 := len(b) // Original buffer length
|
||||
b = append(b, s.Prefix...)
|
||||
b, s.Value = s.Value.formatCompactTo(b, d)
|
||||
b = append(b, s.Suffix...)
|
||||
if _, ok := s.Value.(textLine); ok {
|
||||
return b, textLine(b[n0:])
|
||||
}
|
||||
return b, s
|
||||
}
|
||||
func (s *textWrap) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte {
|
||||
b = append(b, s.Prefix...)
|
||||
b = s.Value.formatExpandedTo(b, d, n)
|
||||
b = append(b, s.Suffix...)
|
||||
return b
|
||||
}
|
||||
|
||||
// textList is a comma-separated list of textWrap or textLine nodes.
|
||||
// The list may be formatted as multi-lines or single-line at the discretion
|
||||
// of the textList.formatCompactTo method.
|
||||
type textList []textRecord
|
||||
type textRecord struct {
|
||||
Diff diffMode // e.g., 0 or '-' or '+'
|
||||
Key string // e.g., "MyField"
|
||||
Value textNode // textWrap | textLine
|
||||
ElideComma bool // avoid trailing comma
|
||||
Comment fmt.Stringer // e.g., "6 identical fields"
|
||||
}
|
||||
|
||||
// AppendEllipsis appends a new ellipsis node to the list if none already
|
||||
// exists at the end. If cs is non-zero it coalesces the statistics with the
|
||||
// previous diffStats.
|
||||
func (s *textList) AppendEllipsis(ds diffStats) {
|
||||
hasStats := !ds.IsZero()
|
||||
if len(*s) == 0 || !(*s)[len(*s)-1].Value.Equal(textEllipsis) {
|
||||
if hasStats {
|
||||
*s = append(*s, textRecord{Value: textEllipsis, ElideComma: true, Comment: ds})
|
||||
} else {
|
||||
*s = append(*s, textRecord{Value: textEllipsis, ElideComma: true})
|
||||
}
|
||||
return
|
||||
}
|
||||
if hasStats {
|
||||
(*s)[len(*s)-1].Comment = (*s)[len(*s)-1].Comment.(diffStats).Append(ds)
|
||||
}
|
||||
}
|
||||
|
||||
func (s textList) Len() (n int) {
|
||||
for i, r := range s {
|
||||
n += len(r.Key)
|
||||
if r.Key != "" {
|
||||
n += len(": ")
|
||||
}
|
||||
n += r.Value.Len()
|
||||
if i < len(s)-1 {
|
||||
n += len(", ")
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (s1 textList) Equal(s2 textNode) bool {
|
||||
if s2, ok := s2.(textList); ok {
|
||||
if len(s1) != len(s2) {
|
||||
return false
|
||||
}
|
||||
for i := range s1 {
|
||||
r1, r2 := s1[i], s2[i]
|
||||
if !(r1.Diff == r2.Diff && r1.Key == r2.Key && r1.Value.Equal(r2.Value) && r1.Comment == r2.Comment) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s textList) String() string {
|
||||
return (&textWrap{Prefix: "{", Value: s, Suffix: "}"}).String()
|
||||
}
|
||||
|
||||
func (s textList) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
|
||||
s = append(textList(nil), s...) // Avoid mutating original
|
||||
|
||||
// Determine whether we can collapse this list as a single line.
|
||||
n0 := len(b) // Original buffer length
|
||||
var multiLine bool
|
||||
for i, r := range s {
|
||||
if r.Diff == diffInserted || r.Diff == diffRemoved {
|
||||
multiLine = true
|
||||
}
|
||||
b = append(b, r.Key...)
|
||||
if r.Key != "" {
|
||||
b = append(b, ": "...)
|
||||
}
|
||||
b, s[i].Value = r.Value.formatCompactTo(b, d|r.Diff)
|
||||
if _, ok := s[i].Value.(textLine); !ok {
|
||||
multiLine = true
|
||||
}
|
||||
if r.Comment != nil {
|
||||
multiLine = true
|
||||
}
|
||||
if i < len(s)-1 {
|
||||
b = append(b, ", "...)
|
||||
}
|
||||
}
|
||||
// Force multi-lined output when printing a removed/inserted node that
|
||||
// is sufficiently long.
|
||||
if (d == diffInserted || d == diffRemoved) && len(b[n0:]) > maxColumnLength {
|
||||
multiLine = true
|
||||
}
|
||||
if !multiLine {
|
||||
return b, textLine(b[n0:])
|
||||
}
|
||||
return b, s
|
||||
}
|
||||
|
||||
func (s textList) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte {
|
||||
alignKeyLens := s.alignLens(
|
||||
func(r textRecord) bool {
|
||||
_, isLine := r.Value.(textLine)
|
||||
return r.Key == "" || !isLine
|
||||
},
|
||||
func(r textRecord) int { return utf8.RuneCountInString(r.Key) },
|
||||
)
|
||||
alignValueLens := s.alignLens(
|
||||
func(r textRecord) bool {
|
||||
_, isLine := r.Value.(textLine)
|
||||
return !isLine || r.Value.Equal(textEllipsis) || r.Comment == nil
|
||||
},
|
||||
func(r textRecord) int { return utf8.RuneCount(r.Value.(textLine)) },
|
||||
)
|
||||
|
||||
// Format lists of simple lists in a batched form.
|
||||
// If the list is sequence of only textLine values,
|
||||
// then batch multiple values on a single line.
|
||||
var isSimple bool
|
||||
for _, r := range s {
|
||||
_, isLine := r.Value.(textLine)
|
||||
isSimple = r.Diff == 0 && r.Key == "" && isLine && r.Comment == nil
|
||||
if !isSimple {
|
||||
break
|
||||
}
|
||||
}
|
||||
if isSimple {
|
||||
n++
|
||||
var batch []byte
|
||||
emitBatch := func() {
|
||||
if len(batch) > 0 {
|
||||
b = n.appendIndent(append(b, '\n'), d)
|
||||
b = append(b, bytes.TrimRight(batch, " ")...)
|
||||
batch = batch[:0]
|
||||
}
|
||||
}
|
||||
for _, r := range s {
|
||||
line := r.Value.(textLine)
|
||||
if len(batch)+len(line)+len(", ") > maxColumnLength {
|
||||
emitBatch()
|
||||
}
|
||||
batch = append(batch, line...)
|
||||
batch = append(batch, ", "...)
|
||||
}
|
||||
emitBatch()
|
||||
n--
|
||||
return n.appendIndent(append(b, '\n'), d)
|
||||
}
|
||||
|
||||
// Format the list as a multi-lined output.
|
||||
n++
|
||||
for i, r := range s {
|
||||
b = n.appendIndent(append(b, '\n'), d|r.Diff)
|
||||
if r.Key != "" {
|
||||
b = append(b, r.Key+": "...)
|
||||
}
|
||||
b = alignKeyLens[i].appendChar(b, ' ')
|
||||
|
||||
b = r.Value.formatExpandedTo(b, d|r.Diff, n)
|
||||
if !r.ElideComma {
|
||||
b = append(b, ',')
|
||||
}
|
||||
b = alignValueLens[i].appendChar(b, ' ')
|
||||
|
||||
if r.Comment != nil {
|
||||
b = append(b, " // "+r.Comment.String()...)
|
||||
}
|
||||
}
|
||||
n--
|
||||
|
||||
return n.appendIndent(append(b, '\n'), d)
|
||||
}
|
||||
|
||||
func (s textList) alignLens(
|
||||
skipFunc func(textRecord) bool,
|
||||
lenFunc func(textRecord) int,
|
||||
) []repeatCount {
|
||||
var startIdx, endIdx, maxLen int
|
||||
lens := make([]repeatCount, len(s))
|
||||
for i, r := range s {
|
||||
if skipFunc(r) {
|
||||
for j := startIdx; j < endIdx && j < len(s); j++ {
|
||||
lens[j] = repeatCount(maxLen - lenFunc(s[j]))
|
||||
}
|
||||
startIdx, endIdx, maxLen = i+1, i+1, 0
|
||||
} else {
|
||||
if maxLen < lenFunc(r) {
|
||||
maxLen = lenFunc(r)
|
||||
}
|
||||
endIdx = i + 1
|
||||
}
|
||||
}
|
||||
for j := startIdx; j < endIdx && j < len(s); j++ {
|
||||
lens[j] = repeatCount(maxLen - lenFunc(s[j]))
|
||||
}
|
||||
return lens
|
||||
}
|
||||
|
||||
// textLine is a single-line segment of text and is always a leaf node
|
||||
// in the textNode tree.
|
||||
type textLine []byte
|
||||
|
||||
var (
|
||||
textNil = textLine("nil")
|
||||
textEllipsis = textLine("...")
|
||||
)
|
||||
|
||||
func (s textLine) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
func (s1 textLine) Equal(s2 textNode) bool {
|
||||
if s2, ok := s2.(textLine); ok {
|
||||
return bytes.Equal([]byte(s1), []byte(s2))
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (s textLine) String() string {
|
||||
return string(s)
|
||||
}
|
||||
func (s textLine) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
|
||||
return append(b, s...), s
|
||||
}
|
||||
func (s textLine) formatExpandedTo(b []byte, _ diffMode, _ indentMode) []byte {
|
||||
return append(b, s...)
|
||||
}
|
||||
|
||||
type diffStats struct {
|
||||
Name string
|
||||
NumIgnored int
|
||||
NumIdentical int
|
||||
NumRemoved int
|
||||
NumInserted int
|
||||
NumModified int
|
||||
}
|
||||
|
||||
func (s diffStats) IsZero() bool {
|
||||
s.Name = ""
|
||||
return s == diffStats{}
|
||||
}
|
||||
|
||||
func (s diffStats) NumDiff() int {
|
||||
return s.NumRemoved + s.NumInserted + s.NumModified
|
||||
}
|
||||
|
||||
func (s diffStats) Append(ds diffStats) diffStats {
|
||||
assert(s.Name == ds.Name)
|
||||
s.NumIgnored += ds.NumIgnored
|
||||
s.NumIdentical += ds.NumIdentical
|
||||
s.NumRemoved += ds.NumRemoved
|
||||
s.NumInserted += ds.NumInserted
|
||||
s.NumModified += ds.NumModified
|
||||
return s
|
||||
}
|
||||
|
||||
// String prints a humanly-readable summary of coalesced records.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// diffStats{Name: "Field", NumIgnored: 5}.String() => "5 ignored fields"
|
||||
func (s diffStats) String() string {
|
||||
var ss []string
|
||||
var sum int
|
||||
labels := [...]string{"ignored", "identical", "removed", "inserted", "modified"}
|
||||
counts := [...]int{s.NumIgnored, s.NumIdentical, s.NumRemoved, s.NumInserted, s.NumModified}
|
||||
for i, n := range counts {
|
||||
if n > 0 {
|
||||
ss = append(ss, fmt.Sprintf("%d %v", n, labels[i]))
|
||||
}
|
||||
sum += n
|
||||
}
|
||||
|
||||
// Pluralize the name (adjusting for some obscure English grammar rules).
|
||||
name := s.Name
|
||||
if sum > 1 {
|
||||
name += "s"
|
||||
if strings.HasSuffix(name, "ys") {
|
||||
name = name[:len(name)-2] + "ies" // e.g., "entrys" => "entries"
|
||||
}
|
||||
}
|
||||
|
||||
// Format the list according to English grammar (with Oxford comma).
|
||||
switch n := len(ss); n {
|
||||
case 0:
|
||||
return ""
|
||||
case 1, 2:
|
||||
return strings.Join(ss, " and ") + " " + name
|
||||
default:
|
||||
return strings.Join(ss[:n-1], ", ") + ", and " + ss[n-1] + " " + name
|
||||
}
|
||||
}
|
||||
|
||||
type commentString string
|
||||
|
||||
func (s commentString) String() string { return string(s) }
|
||||
121
vendor/github.com/google/go-cmp/cmp/report_value.go
generated
vendored
Normal file
121
vendor/github.com/google/go-cmp/cmp/report_value.go
generated
vendored
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
// 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 cmp
|
||||
|
||||
import "reflect"
|
||||
|
||||
// valueNode represents a single node within a report, which is a
|
||||
// structured representation of the value tree, containing information
|
||||
// regarding which nodes are equal or not.
|
||||
type valueNode struct {
|
||||
parent *valueNode
|
||||
|
||||
Type reflect.Type
|
||||
ValueX reflect.Value
|
||||
ValueY reflect.Value
|
||||
|
||||
// NumSame is the number of leaf nodes that are equal.
|
||||
// All descendants are equal only if NumDiff is 0.
|
||||
NumSame int
|
||||
// NumDiff is the number of leaf nodes that are not equal.
|
||||
NumDiff int
|
||||
// NumIgnored is the number of leaf nodes that are ignored.
|
||||
NumIgnored int
|
||||
// NumCompared is the number of leaf nodes that were compared
|
||||
// using an Equal method or Comparer function.
|
||||
NumCompared int
|
||||
// NumTransformed is the number of non-leaf nodes that were transformed.
|
||||
NumTransformed int
|
||||
// NumChildren is the number of transitive descendants of this node.
|
||||
// This counts from zero; thus, leaf nodes have no descendants.
|
||||
NumChildren int
|
||||
// MaxDepth is the maximum depth of the tree. This counts from zero;
|
||||
// thus, leaf nodes have a depth of zero.
|
||||
MaxDepth int
|
||||
|
||||
// Records is a list of struct fields, slice elements, or map entries.
|
||||
Records []reportRecord // If populated, implies Value is not populated
|
||||
|
||||
// Value is the result of a transformation, pointer indirect, of
|
||||
// type assertion.
|
||||
Value *valueNode // If populated, implies Records is not populated
|
||||
|
||||
// TransformerName is the name of the transformer.
|
||||
TransformerName string // If non-empty, implies Value is populated
|
||||
}
|
||||
type reportRecord struct {
|
||||
Key reflect.Value // Invalid for slice element
|
||||
Value *valueNode
|
||||
}
|
||||
|
||||
func (parent *valueNode) PushStep(ps PathStep) (child *valueNode) {
|
||||
vx, vy := ps.Values()
|
||||
child = &valueNode{parent: parent, Type: ps.Type(), ValueX: vx, ValueY: vy}
|
||||
switch s := ps.(type) {
|
||||
case StructField:
|
||||
assert(parent.Value == nil)
|
||||
parent.Records = append(parent.Records, reportRecord{Key: reflect.ValueOf(s.Name()), Value: child})
|
||||
case SliceIndex:
|
||||
assert(parent.Value == nil)
|
||||
parent.Records = append(parent.Records, reportRecord{Value: child})
|
||||
case MapIndex:
|
||||
assert(parent.Value == nil)
|
||||
parent.Records = append(parent.Records, reportRecord{Key: s.Key(), Value: child})
|
||||
case Indirect:
|
||||
assert(parent.Value == nil && parent.Records == nil)
|
||||
parent.Value = child
|
||||
case TypeAssertion:
|
||||
assert(parent.Value == nil && parent.Records == nil)
|
||||
parent.Value = child
|
||||
case Transform:
|
||||
assert(parent.Value == nil && parent.Records == nil)
|
||||
parent.Value = child
|
||||
parent.TransformerName = s.Name()
|
||||
parent.NumTransformed++
|
||||
default:
|
||||
assert(parent == nil) // Must be the root step
|
||||
}
|
||||
return child
|
||||
}
|
||||
|
||||
func (r *valueNode) Report(rs Result) {
|
||||
assert(r.MaxDepth == 0) // May only be called on leaf nodes
|
||||
|
||||
if rs.ByIgnore() {
|
||||
r.NumIgnored++
|
||||
} else {
|
||||
if rs.Equal() {
|
||||
r.NumSame++
|
||||
} else {
|
||||
r.NumDiff++
|
||||
}
|
||||
}
|
||||
assert(r.NumSame+r.NumDiff+r.NumIgnored == 1)
|
||||
|
||||
if rs.ByMethod() {
|
||||
r.NumCompared++
|
||||
}
|
||||
if rs.ByFunc() {
|
||||
r.NumCompared++
|
||||
}
|
||||
assert(r.NumCompared <= 1)
|
||||
}
|
||||
|
||||
func (child *valueNode) PopStep() (parent *valueNode) {
|
||||
if child.parent == nil {
|
||||
return nil
|
||||
}
|
||||
parent = child.parent
|
||||
parent.NumSame += child.NumSame
|
||||
parent.NumDiff += child.NumDiff
|
||||
parent.NumIgnored += child.NumIgnored
|
||||
parent.NumCompared += child.NumCompared
|
||||
parent.NumTransformed += child.NumTransformed
|
||||
parent.NumChildren += child.NumChildren + 1
|
||||
if parent.MaxDepth < child.MaxDepth+1 {
|
||||
parent.MaxDepth = child.MaxDepth + 1
|
||||
}
|
||||
return parent
|
||||
}
|
||||
27
vendor/golang.org/x/mod/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/mod/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
Copyright 2009 The Go Authors.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google LLC nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
22
vendor/golang.org/x/mod/PATENTS
generated
vendored
Normal file
22
vendor/golang.org/x/mod/PATENTS
generated
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
||||
78
vendor/golang.org/x/mod/internal/lazyregexp/lazyre.go
generated
vendored
Normal file
78
vendor/golang.org/x/mod/internal/lazyregexp/lazyre.go
generated
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright 2018 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 lazyregexp is a thin wrapper over regexp, allowing the use of global
|
||||
// regexp variables without forcing them to be compiled at init.
|
||||
package lazyregexp
|
||||
|
||||
import (
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Regexp is a wrapper around [regexp.Regexp], where the underlying regexp will be
|
||||
// compiled the first time it is needed.
|
||||
type Regexp struct {
|
||||
str string
|
||||
once sync.Once
|
||||
rx *regexp.Regexp
|
||||
}
|
||||
|
||||
func (r *Regexp) re() *regexp.Regexp {
|
||||
r.once.Do(r.build)
|
||||
return r.rx
|
||||
}
|
||||
|
||||
func (r *Regexp) build() {
|
||||
r.rx = regexp.MustCompile(r.str)
|
||||
r.str = ""
|
||||
}
|
||||
|
||||
func (r *Regexp) FindSubmatch(s []byte) [][]byte {
|
||||
return r.re().FindSubmatch(s)
|
||||
}
|
||||
|
||||
func (r *Regexp) FindStringSubmatch(s string) []string {
|
||||
return r.re().FindStringSubmatch(s)
|
||||
}
|
||||
|
||||
func (r *Regexp) FindStringSubmatchIndex(s string) []int {
|
||||
return r.re().FindStringSubmatchIndex(s)
|
||||
}
|
||||
|
||||
func (r *Regexp) ReplaceAllString(src, repl string) string {
|
||||
return r.re().ReplaceAllString(src, repl)
|
||||
}
|
||||
|
||||
func (r *Regexp) FindString(s string) string {
|
||||
return r.re().FindString(s)
|
||||
}
|
||||
|
||||
func (r *Regexp) FindAllString(s string, n int) []string {
|
||||
return r.re().FindAllString(s, n)
|
||||
}
|
||||
|
||||
func (r *Regexp) MatchString(s string) bool {
|
||||
return r.re().MatchString(s)
|
||||
}
|
||||
|
||||
func (r *Regexp) SubexpNames() []string {
|
||||
return r.re().SubexpNames()
|
||||
}
|
||||
|
||||
var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test")
|
||||
|
||||
// New creates a new lazy regexp, delaying the compiling work until it is first
|
||||
// needed. If the code is being run as part of tests, the regexp compiling will
|
||||
// happen immediately.
|
||||
func New(str string) *Regexp {
|
||||
lr := &Regexp{str: str}
|
||||
if inTest {
|
||||
// In tests, always compile the regexps early.
|
||||
lr.re()
|
||||
}
|
||||
return lr
|
||||
}
|
||||
184
vendor/golang.org/x/mod/modfile/print.go
generated
vendored
Normal file
184
vendor/golang.org/x/mod/modfile/print.go
generated
vendored
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
// Copyright 2018 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.
|
||||
|
||||
// Module file printer.
|
||||
|
||||
package modfile
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Format returns a go.mod file as a byte slice, formatted in standard style.
|
||||
func Format(f *FileSyntax) []byte {
|
||||
pr := &printer{}
|
||||
pr.file(f)
|
||||
|
||||
// remove trailing blank lines
|
||||
b := pr.Bytes()
|
||||
for len(b) > 0 && b[len(b)-1] == '\n' && (len(b) == 1 || b[len(b)-2] == '\n') {
|
||||
b = b[:len(b)-1]
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// A printer collects the state during printing of a file or expression.
|
||||
type printer struct {
|
||||
bytes.Buffer // output buffer
|
||||
comment []Comment // pending end-of-line comments
|
||||
margin int // left margin (indent), a number of tabs
|
||||
}
|
||||
|
||||
// printf prints to the buffer.
|
||||
func (p *printer) printf(format string, args ...any) {
|
||||
fmt.Fprintf(p, format, args...)
|
||||
}
|
||||
|
||||
// indent returns the position on the current line, in bytes, 0-indexed.
|
||||
func (p *printer) indent() int {
|
||||
b := p.Bytes()
|
||||
n := 0
|
||||
for n < len(b) && b[len(b)-1-n] != '\n' {
|
||||
n++
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// newline ends the current line, flushing end-of-line comments.
|
||||
func (p *printer) newline() {
|
||||
if len(p.comment) > 0 {
|
||||
p.printf(" ")
|
||||
for i, com := range p.comment {
|
||||
if i > 0 {
|
||||
p.trim()
|
||||
p.printf("\n")
|
||||
for i := 0; i < p.margin; i++ {
|
||||
p.printf("\t")
|
||||
}
|
||||
}
|
||||
p.printf("%s", strings.TrimSpace(com.Token))
|
||||
}
|
||||
p.comment = p.comment[:0]
|
||||
}
|
||||
|
||||
p.trim()
|
||||
if b := p.Bytes(); len(b) == 0 || (len(b) >= 2 && b[len(b)-1] == '\n' && b[len(b)-2] == '\n') {
|
||||
// skip the blank line at top of file or after a blank line
|
||||
} else {
|
||||
p.printf("\n")
|
||||
}
|
||||
for i := 0; i < p.margin; i++ {
|
||||
p.printf("\t")
|
||||
}
|
||||
}
|
||||
|
||||
// trim removes trailing spaces and tabs from the current line.
|
||||
func (p *printer) trim() {
|
||||
// Remove trailing spaces and tabs from line we're about to end.
|
||||
b := p.Bytes()
|
||||
n := len(b)
|
||||
for n > 0 && (b[n-1] == '\t' || b[n-1] == ' ') {
|
||||
n--
|
||||
}
|
||||
p.Truncate(n)
|
||||
}
|
||||
|
||||
// file formats the given file into the print buffer.
|
||||
func (p *printer) file(f *FileSyntax) {
|
||||
for _, com := range f.Before {
|
||||
p.printf("%s", strings.TrimSpace(com.Token))
|
||||
p.newline()
|
||||
}
|
||||
|
||||
for i, stmt := range f.Stmt {
|
||||
switch x := stmt.(type) {
|
||||
case *CommentBlock:
|
||||
// comments already handled
|
||||
p.expr(x)
|
||||
|
||||
default:
|
||||
p.expr(x)
|
||||
p.newline()
|
||||
}
|
||||
|
||||
for _, com := range stmt.Comment().After {
|
||||
p.printf("%s", strings.TrimSpace(com.Token))
|
||||
p.newline()
|
||||
}
|
||||
|
||||
if i+1 < len(f.Stmt) {
|
||||
p.newline()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *printer) expr(x Expr) {
|
||||
// Emit line-comments preceding this expression.
|
||||
if before := x.Comment().Before; len(before) > 0 {
|
||||
// Want to print a line comment.
|
||||
// Line comments must be at the current margin.
|
||||
p.trim()
|
||||
if p.indent() > 0 {
|
||||
// There's other text on the line. Start a new line.
|
||||
p.printf("\n")
|
||||
}
|
||||
// Re-indent to margin.
|
||||
for i := 0; i < p.margin; i++ {
|
||||
p.printf("\t")
|
||||
}
|
||||
for _, com := range before {
|
||||
p.printf("%s", strings.TrimSpace(com.Token))
|
||||
p.newline()
|
||||
}
|
||||
}
|
||||
|
||||
switch x := x.(type) {
|
||||
default:
|
||||
panic(fmt.Errorf("printer: unexpected type %T", x))
|
||||
|
||||
case *CommentBlock:
|
||||
// done
|
||||
|
||||
case *LParen:
|
||||
p.printf("(")
|
||||
case *RParen:
|
||||
p.printf(")")
|
||||
|
||||
case *Line:
|
||||
p.tokens(x.Token)
|
||||
|
||||
case *LineBlock:
|
||||
p.tokens(x.Token)
|
||||
p.printf(" ")
|
||||
p.expr(&x.LParen)
|
||||
p.margin++
|
||||
for _, l := range x.Line {
|
||||
p.newline()
|
||||
p.expr(l)
|
||||
}
|
||||
p.margin--
|
||||
p.newline()
|
||||
p.expr(&x.RParen)
|
||||
}
|
||||
|
||||
// Queue end-of-line comments for printing when we
|
||||
// reach the end of the line.
|
||||
p.comment = append(p.comment, x.Comment().Suffix...)
|
||||
}
|
||||
|
||||
func (p *printer) tokens(tokens []string) {
|
||||
sep := ""
|
||||
for _, t := range tokens {
|
||||
if t == "," || t == ")" || t == "]" || t == "}" {
|
||||
sep = ""
|
||||
}
|
||||
p.printf("%s%s", sep, t)
|
||||
sep = " "
|
||||
if t == "(" || t == "[" || t == "{" {
|
||||
sep = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
964
vendor/golang.org/x/mod/modfile/read.go
generated
vendored
Normal file
964
vendor/golang.org/x/mod/modfile/read.go
generated
vendored
Normal file
|
|
@ -0,0 +1,964 @@
|
|||
// Copyright 2018 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 modfile
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// A Position describes an arbitrary source position in a file, including the
|
||||
// file, line, column, and byte offset.
|
||||
type Position struct {
|
||||
Line int // line in input (starting at 1)
|
||||
LineRune int // rune in line (starting at 1)
|
||||
Byte int // byte in input (starting at 0)
|
||||
}
|
||||
|
||||
// add returns the position at the end of s, assuming it starts at p.
|
||||
func (p Position) add(s string) Position {
|
||||
p.Byte += len(s)
|
||||
if n := strings.Count(s, "\n"); n > 0 {
|
||||
p.Line += n
|
||||
s = s[strings.LastIndex(s, "\n")+1:]
|
||||
p.LineRune = 1
|
||||
}
|
||||
p.LineRune += utf8.RuneCountInString(s)
|
||||
return p
|
||||
}
|
||||
|
||||
// An Expr represents an input element.
|
||||
type Expr interface {
|
||||
// Span returns the start and end position of the expression,
|
||||
// excluding leading or trailing comments.
|
||||
Span() (start, end Position)
|
||||
|
||||
// Comment returns the comments attached to the expression.
|
||||
// This method would normally be named 'Comments' but that
|
||||
// would interfere with embedding a type of the same name.
|
||||
Comment() *Comments
|
||||
}
|
||||
|
||||
// A Comment represents a single // comment.
|
||||
type Comment struct {
|
||||
Start Position
|
||||
Token string // without trailing newline
|
||||
Suffix bool // an end of line (not whole line) comment
|
||||
}
|
||||
|
||||
// Comments collects the comments associated with an expression.
|
||||
type Comments struct {
|
||||
Before []Comment // whole-line comments before this expression
|
||||
Suffix []Comment // end-of-line comments after this expression
|
||||
|
||||
// For top-level expressions only, After lists whole-line
|
||||
// comments following the expression.
|
||||
After []Comment
|
||||
}
|
||||
|
||||
// Comment returns the receiver. This isn't useful by itself, but
|
||||
// a [Comments] struct is embedded into all the expression
|
||||
// implementation types, and this gives each of those a Comment
|
||||
// method to satisfy the Expr interface.
|
||||
func (c *Comments) Comment() *Comments {
|
||||
return c
|
||||
}
|
||||
|
||||
// A FileSyntax represents an entire go.mod file.
|
||||
type FileSyntax struct {
|
||||
Name string // file path
|
||||
Comments
|
||||
Stmt []Expr
|
||||
}
|
||||
|
||||
func (x *FileSyntax) Span() (start, end Position) {
|
||||
if len(x.Stmt) == 0 {
|
||||
return
|
||||
}
|
||||
start, _ = x.Stmt[0].Span()
|
||||
_, end = x.Stmt[len(x.Stmt)-1].Span()
|
||||
return start, end
|
||||
}
|
||||
|
||||
// addLine adds a line containing the given tokens to the file.
|
||||
//
|
||||
// If the first token of the hint matches the first token of the
|
||||
// line, the new line is added at the end of the block containing hint,
|
||||
// extracting hint into a new block if it is not yet in one.
|
||||
//
|
||||
// If the hint is non-nil but its first token does not match,
|
||||
// the new line is added after the block containing hint
|
||||
// (or hint itself, if not in a block).
|
||||
//
|
||||
// If no hint is provided, addLine appends the line to the end of
|
||||
// the last block with a matching first token,
|
||||
// or to the end of the file if no such block exists.
|
||||
func (x *FileSyntax) addLine(hint Expr, tokens ...string) *Line {
|
||||
if hint == nil {
|
||||
// If no hint given, add to the last statement of the given type.
|
||||
Loop:
|
||||
for i := len(x.Stmt) - 1; i >= 0; i-- {
|
||||
stmt := x.Stmt[i]
|
||||
switch stmt := stmt.(type) {
|
||||
case *Line:
|
||||
if stmt.Token != nil && stmt.Token[0] == tokens[0] {
|
||||
hint = stmt
|
||||
break Loop
|
||||
}
|
||||
case *LineBlock:
|
||||
if stmt.Token[0] == tokens[0] {
|
||||
hint = stmt
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newLineAfter := func(i int) *Line {
|
||||
new := &Line{Token: tokens}
|
||||
if i == len(x.Stmt) {
|
||||
x.Stmt = append(x.Stmt, new)
|
||||
} else {
|
||||
x.Stmt = append(x.Stmt, nil)
|
||||
copy(x.Stmt[i+2:], x.Stmt[i+1:])
|
||||
x.Stmt[i+1] = new
|
||||
}
|
||||
return new
|
||||
}
|
||||
|
||||
if hint != nil {
|
||||
for i, stmt := range x.Stmt {
|
||||
switch stmt := stmt.(type) {
|
||||
case *Line:
|
||||
if stmt == hint {
|
||||
if stmt.Token == nil || stmt.Token[0] != tokens[0] {
|
||||
return newLineAfter(i)
|
||||
}
|
||||
|
||||
// Convert line to line block.
|
||||
stmt.InBlock = true
|
||||
block := &LineBlock{Token: stmt.Token[:1], Line: []*Line{stmt}}
|
||||
stmt.Token = stmt.Token[1:]
|
||||
x.Stmt[i] = block
|
||||
new := &Line{Token: tokens[1:], InBlock: true}
|
||||
block.Line = append(block.Line, new)
|
||||
return new
|
||||
}
|
||||
|
||||
case *LineBlock:
|
||||
if stmt == hint {
|
||||
if stmt.Token[0] != tokens[0] {
|
||||
return newLineAfter(i)
|
||||
}
|
||||
|
||||
new := &Line{Token: tokens[1:], InBlock: true}
|
||||
stmt.Line = append(stmt.Line, new)
|
||||
return new
|
||||
}
|
||||
|
||||
for j, line := range stmt.Line {
|
||||
if line == hint {
|
||||
if stmt.Token[0] != tokens[0] {
|
||||
return newLineAfter(i)
|
||||
}
|
||||
|
||||
// Add new line after hint within the block.
|
||||
stmt.Line = append(stmt.Line, nil)
|
||||
copy(stmt.Line[j+2:], stmt.Line[j+1:])
|
||||
new := &Line{Token: tokens[1:], InBlock: true}
|
||||
stmt.Line[j+1] = new
|
||||
return new
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new := &Line{Token: tokens}
|
||||
x.Stmt = append(x.Stmt, new)
|
||||
return new
|
||||
}
|
||||
|
||||
func (x *FileSyntax) updateLine(line *Line, tokens ...string) {
|
||||
if line.InBlock {
|
||||
tokens = tokens[1:]
|
||||
}
|
||||
line.Token = tokens
|
||||
}
|
||||
|
||||
// markRemoved modifies line so that it (and its end-of-line comment, if any)
|
||||
// will be dropped by (*FileSyntax).Cleanup.
|
||||
func (line *Line) markRemoved() {
|
||||
line.Token = nil
|
||||
line.Comments.Suffix = nil
|
||||
}
|
||||
|
||||
// Cleanup cleans up the file syntax x after any edit operations.
|
||||
// To avoid quadratic behavior, (*Line).markRemoved marks the line as dead
|
||||
// by setting line.Token = nil but does not remove it from the slice
|
||||
// in which it appears. After edits have all been indicated,
|
||||
// calling Cleanup cleans out the dead lines.
|
||||
func (x *FileSyntax) Cleanup() {
|
||||
w := 0
|
||||
for _, stmt := range x.Stmt {
|
||||
switch stmt := stmt.(type) {
|
||||
case *Line:
|
||||
if stmt.Token == nil {
|
||||
continue
|
||||
}
|
||||
case *LineBlock:
|
||||
ww := 0
|
||||
for _, line := range stmt.Line {
|
||||
if line.Token != nil {
|
||||
stmt.Line[ww] = line
|
||||
ww++
|
||||
}
|
||||
}
|
||||
if ww == 0 {
|
||||
continue
|
||||
}
|
||||
if ww == 1 && len(stmt.RParen.Comments.Before) == 0 {
|
||||
// Collapse block into single line but keep the Line reference used by the
|
||||
// parsed File structure.
|
||||
*stmt.Line[0] = Line{
|
||||
Comments: Comments{
|
||||
Before: commentsAdd(stmt.Before, stmt.Line[0].Before),
|
||||
Suffix: commentsAdd(stmt.Line[0].Suffix, stmt.Suffix),
|
||||
After: commentsAdd(stmt.Line[0].After, stmt.After),
|
||||
},
|
||||
Token: stringsAdd(stmt.Token, stmt.Line[0].Token),
|
||||
}
|
||||
x.Stmt[w] = stmt.Line[0]
|
||||
w++
|
||||
continue
|
||||
}
|
||||
stmt.Line = stmt.Line[:ww]
|
||||
}
|
||||
x.Stmt[w] = stmt
|
||||
w++
|
||||
}
|
||||
x.Stmt = x.Stmt[:w]
|
||||
}
|
||||
|
||||
func commentsAdd(x, y []Comment) []Comment {
|
||||
return append(x[:len(x):len(x)], y...)
|
||||
}
|
||||
|
||||
func stringsAdd(x, y []string) []string {
|
||||
return append(x[:len(x):len(x)], y...)
|
||||
}
|
||||
|
||||
// A CommentBlock represents a top-level block of comments separate
|
||||
// from any rule.
|
||||
type CommentBlock struct {
|
||||
Comments
|
||||
Start Position
|
||||
}
|
||||
|
||||
func (x *CommentBlock) Span() (start, end Position) {
|
||||
return x.Start, x.Start
|
||||
}
|
||||
|
||||
// A Line is a single line of tokens.
|
||||
type Line struct {
|
||||
Comments
|
||||
Start Position
|
||||
Token []string
|
||||
InBlock bool
|
||||
End Position
|
||||
}
|
||||
|
||||
func (x *Line) Span() (start, end Position) {
|
||||
return x.Start, x.End
|
||||
}
|
||||
|
||||
// A LineBlock is a factored block of lines, like
|
||||
//
|
||||
// require (
|
||||
// "x"
|
||||
// "y"
|
||||
// )
|
||||
type LineBlock struct {
|
||||
Comments
|
||||
Start Position
|
||||
LParen LParen
|
||||
Token []string
|
||||
Line []*Line
|
||||
RParen RParen
|
||||
}
|
||||
|
||||
func (x *LineBlock) Span() (start, end Position) {
|
||||
return x.Start, x.RParen.Pos.add(")")
|
||||
}
|
||||
|
||||
// An LParen represents the beginning of a parenthesized line block.
|
||||
// It is a place to store suffix comments.
|
||||
type LParen struct {
|
||||
Comments
|
||||
Pos Position
|
||||
}
|
||||
|
||||
func (x *LParen) Span() (start, end Position) {
|
||||
return x.Pos, x.Pos.add(")")
|
||||
}
|
||||
|
||||
// An RParen represents the end of a parenthesized line block.
|
||||
// It is a place to store whole-line (before) comments.
|
||||
type RParen struct {
|
||||
Comments
|
||||
Pos Position
|
||||
}
|
||||
|
||||
func (x *RParen) Span() (start, end Position) {
|
||||
return x.Pos, x.Pos.add(")")
|
||||
}
|
||||
|
||||
// An input represents a single input file being parsed.
|
||||
type input struct {
|
||||
// Lexing state.
|
||||
filename string // name of input file, for errors
|
||||
complete []byte // entire input
|
||||
remaining []byte // remaining input
|
||||
tokenStart []byte // token being scanned to end of input
|
||||
token token // next token to be returned by lex, peek
|
||||
pos Position // current input position
|
||||
comments []Comment // accumulated comments
|
||||
|
||||
// Parser state.
|
||||
file *FileSyntax // returned top-level syntax tree
|
||||
parseErrors ErrorList // errors encountered during parsing
|
||||
|
||||
// Comment assignment state.
|
||||
pre []Expr // all expressions, in preorder traversal
|
||||
post []Expr // all expressions, in postorder traversal
|
||||
}
|
||||
|
||||
func newInput(filename string, data []byte) *input {
|
||||
return &input{
|
||||
filename: filename,
|
||||
complete: data,
|
||||
remaining: data,
|
||||
pos: Position{Line: 1, LineRune: 1, Byte: 0},
|
||||
}
|
||||
}
|
||||
|
||||
// parse parses the input file.
|
||||
func parse(file string, data []byte) (f *FileSyntax, err error) {
|
||||
// The parser panics for both routine errors like syntax errors
|
||||
// and for programmer bugs like array index errors.
|
||||
// Turn both into error returns. Catching bug panics is
|
||||
// especially important when processing many files.
|
||||
in := newInput(file, data)
|
||||
defer func() {
|
||||
if e := recover(); e != nil && e != &in.parseErrors {
|
||||
in.parseErrors = append(in.parseErrors, Error{
|
||||
Filename: in.filename,
|
||||
Pos: in.pos,
|
||||
Err: fmt.Errorf("internal error: %v", e),
|
||||
})
|
||||
}
|
||||
if err == nil && len(in.parseErrors) > 0 {
|
||||
err = in.parseErrors
|
||||
}
|
||||
}()
|
||||
|
||||
// Prime the lexer by reading in the first token. It will be available
|
||||
// in the next peek() or lex() call.
|
||||
in.readToken()
|
||||
|
||||
// Invoke the parser.
|
||||
in.parseFile()
|
||||
if len(in.parseErrors) > 0 {
|
||||
return nil, in.parseErrors
|
||||
}
|
||||
in.file.Name = in.filename
|
||||
|
||||
// Assign comments to nearby syntax.
|
||||
in.assignComments()
|
||||
|
||||
return in.file, nil
|
||||
}
|
||||
|
||||
// Error is called to report an error.
|
||||
// Error does not return: it panics.
|
||||
func (in *input) Error(s string) {
|
||||
in.parseErrors = append(in.parseErrors, Error{
|
||||
Filename: in.filename,
|
||||
Pos: in.pos,
|
||||
Err: errors.New(s),
|
||||
})
|
||||
panic(&in.parseErrors)
|
||||
}
|
||||
|
||||
// eof reports whether the input has reached end of file.
|
||||
func (in *input) eof() bool {
|
||||
return len(in.remaining) == 0
|
||||
}
|
||||
|
||||
// peekRune returns the next rune in the input without consuming it.
|
||||
func (in *input) peekRune() int {
|
||||
if len(in.remaining) == 0 {
|
||||
return 0
|
||||
}
|
||||
r, _ := utf8.DecodeRune(in.remaining)
|
||||
return int(r)
|
||||
}
|
||||
|
||||
// peekPrefix reports whether the remaining input begins with the given prefix.
|
||||
func (in *input) peekPrefix(prefix string) bool {
|
||||
// This is like bytes.HasPrefix(in.remaining, []byte(prefix))
|
||||
// but without the allocation of the []byte copy of prefix.
|
||||
for i := 0; i < len(prefix); i++ {
|
||||
if i >= len(in.remaining) || in.remaining[i] != prefix[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// readRune consumes and returns the next rune in the input.
|
||||
func (in *input) readRune() int {
|
||||
if len(in.remaining) == 0 {
|
||||
in.Error("internal lexer error: readRune at EOF")
|
||||
}
|
||||
r, size := utf8.DecodeRune(in.remaining)
|
||||
in.remaining = in.remaining[size:]
|
||||
if r == '\n' {
|
||||
in.pos.Line++
|
||||
in.pos.LineRune = 1
|
||||
} else {
|
||||
in.pos.LineRune++
|
||||
}
|
||||
in.pos.Byte += size
|
||||
return int(r)
|
||||
}
|
||||
|
||||
type token struct {
|
||||
kind tokenKind
|
||||
pos Position
|
||||
endPos Position
|
||||
text string
|
||||
}
|
||||
|
||||
type tokenKind int
|
||||
|
||||
const (
|
||||
_EOF tokenKind = -(iota + 1)
|
||||
_EOLCOMMENT
|
||||
_IDENT
|
||||
_STRING
|
||||
_COMMENT
|
||||
|
||||
// newlines and punctuation tokens are allowed as ASCII codes.
|
||||
)
|
||||
|
||||
func (k tokenKind) isComment() bool {
|
||||
return k == _COMMENT || k == _EOLCOMMENT
|
||||
}
|
||||
|
||||
// isEOL returns whether a token terminates a line.
|
||||
func (k tokenKind) isEOL() bool {
|
||||
return k == _EOF || k == _EOLCOMMENT || k == '\n'
|
||||
}
|
||||
|
||||
// startToken marks the beginning of the next input token.
|
||||
// It must be followed by a call to endToken, once the token's text has
|
||||
// been consumed using readRune.
|
||||
func (in *input) startToken() {
|
||||
in.tokenStart = in.remaining
|
||||
in.token.text = ""
|
||||
in.token.pos = in.pos
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// peek returns the kind of the next token returned by lex.
|
||||
func (in *input) peek() tokenKind {
|
||||
return in.token.kind
|
||||
}
|
||||
|
||||
// lex is called from the parser to obtain the next input token.
|
||||
func (in *input) lex() token {
|
||||
tok := in.token
|
||||
in.readToken()
|
||||
return tok
|
||||
}
|
||||
|
||||
// readToken lexes the next token from the text and stores it in in.token.
|
||||
func (in *input) readToken() {
|
||||
// Skip past spaces, stopping at non-space or EOF.
|
||||
for !in.eof() {
|
||||
c := in.peekRune()
|
||||
if c == ' ' || c == '\t' || c == '\r' {
|
||||
in.readRune()
|
||||
continue
|
||||
}
|
||||
|
||||
// Comment runs to end of line.
|
||||
if in.peekPrefix("//") {
|
||||
in.startToken()
|
||||
|
||||
// Is this comment the only thing on its line?
|
||||
// Find the last \n before this // and see if it's all
|
||||
// spaces from there to here.
|
||||
i := bytes.LastIndex(in.complete[:in.pos.Byte], []byte("\n"))
|
||||
suffix := len(bytes.TrimSpace(in.complete[i+1:in.pos.Byte])) > 0
|
||||
in.readRune()
|
||||
in.readRune()
|
||||
|
||||
// Consume comment.
|
||||
for len(in.remaining) > 0 && in.readRune() != '\n' {
|
||||
}
|
||||
|
||||
// If we are at top level (not in a statement), hand the comment to
|
||||
// the parser as a _COMMENT token. The grammar is written
|
||||
// to handle top-level comments itself.
|
||||
if !suffix {
|
||||
in.endToken(_COMMENT)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, save comment for later attachment to syntax tree.
|
||||
in.endToken(_EOLCOMMENT)
|
||||
in.comments = append(in.comments, Comment{in.token.pos, in.token.text, suffix})
|
||||
return
|
||||
}
|
||||
|
||||
if in.peekPrefix("/*") {
|
||||
in.Error("mod files must use // comments (not /* */ comments)")
|
||||
}
|
||||
|
||||
// Found non-space non-comment.
|
||||
break
|
||||
}
|
||||
|
||||
// Found the beginning of the next token.
|
||||
in.startToken()
|
||||
|
||||
// End of file.
|
||||
if in.eof() {
|
||||
in.endToken(_EOF)
|
||||
return
|
||||
}
|
||||
|
||||
// Punctuation tokens.
|
||||
switch c := in.peekRune(); c {
|
||||
case '\n', '(', ')', '[', ']', '{', '}', ',':
|
||||
in.readRune()
|
||||
in.endToken(tokenKind(c))
|
||||
return
|
||||
|
||||
case '"', '`': // quoted string
|
||||
quote := c
|
||||
in.readRune()
|
||||
for {
|
||||
if in.eof() {
|
||||
in.pos = in.token.pos
|
||||
in.Error("unexpected EOF in string")
|
||||
}
|
||||
if in.peekRune() == '\n' {
|
||||
in.Error("unexpected newline in string")
|
||||
}
|
||||
c := in.readRune()
|
||||
if c == quote {
|
||||
break
|
||||
}
|
||||
if c == '\\' && quote != '`' {
|
||||
if in.eof() {
|
||||
in.pos = in.token.pos
|
||||
in.Error("unexpected EOF in string")
|
||||
}
|
||||
in.readRune()
|
||||
}
|
||||
}
|
||||
in.endToken(_STRING)
|
||||
return
|
||||
}
|
||||
|
||||
// Checked all punctuation. Must be identifier token.
|
||||
if c := in.peekRune(); !isIdent(c) {
|
||||
in.Error(fmt.Sprintf("unexpected input character %#q", rune(c)))
|
||||
}
|
||||
|
||||
// Scan over identifier.
|
||||
for isIdent(in.peekRune()) {
|
||||
if in.peekPrefix("//") {
|
||||
break
|
||||
}
|
||||
if in.peekPrefix("/*") {
|
||||
in.Error("mod files must use // comments (not /* */ comments)")
|
||||
}
|
||||
in.readRune()
|
||||
}
|
||||
in.endToken(_IDENT)
|
||||
}
|
||||
|
||||
// isIdent reports whether c is an identifier rune.
|
||||
// We treat most printable runes as identifier runes, except for a handful of
|
||||
// ASCII punctuation characters.
|
||||
func isIdent(c int) bool {
|
||||
switch r := rune(c); r {
|
||||
case ' ', '(', ')', '[', ']', '{', '}', ',':
|
||||
return false
|
||||
default:
|
||||
return !unicode.IsSpace(r) && unicode.IsPrint(r)
|
||||
}
|
||||
}
|
||||
|
||||
// Comment assignment.
|
||||
// We build two lists of all subexpressions, preorder and postorder.
|
||||
// The preorder list is ordered by start location, with outer expressions first.
|
||||
// The postorder list is ordered by end location, with outer expressions last.
|
||||
// We use the preorder list to assign each whole-line comment to the syntax
|
||||
// immediately following it, and we use the postorder list to assign each
|
||||
// end-of-line comment to the syntax immediately preceding it.
|
||||
|
||||
// order walks the expression adding it and its subexpressions to the
|
||||
// preorder and postorder lists.
|
||||
func (in *input) order(x Expr) {
|
||||
if x != nil {
|
||||
in.pre = append(in.pre, x)
|
||||
}
|
||||
switch x := x.(type) {
|
||||
default:
|
||||
panic(fmt.Errorf("order: unexpected type %T", x))
|
||||
case nil:
|
||||
// nothing
|
||||
case *LParen, *RParen:
|
||||
// nothing
|
||||
case *CommentBlock:
|
||||
// nothing
|
||||
case *Line:
|
||||
// nothing
|
||||
case *FileSyntax:
|
||||
for _, stmt := range x.Stmt {
|
||||
in.order(stmt)
|
||||
}
|
||||
case *LineBlock:
|
||||
in.order(&x.LParen)
|
||||
for _, l := range x.Line {
|
||||
in.order(l)
|
||||
}
|
||||
in.order(&x.RParen)
|
||||
}
|
||||
if x != nil {
|
||||
in.post = append(in.post, x)
|
||||
}
|
||||
}
|
||||
|
||||
// assignComments attaches comments to nearby syntax.
|
||||
func (in *input) assignComments() {
|
||||
const debug = false
|
||||
|
||||
// Generate preorder and postorder lists.
|
||||
in.order(in.file)
|
||||
|
||||
// Split into whole-line comments and suffix comments.
|
||||
var line, suffix []Comment
|
||||
for _, com := range in.comments {
|
||||
if com.Suffix {
|
||||
suffix = append(suffix, com)
|
||||
} else {
|
||||
line = append(line, com)
|
||||
}
|
||||
}
|
||||
|
||||
if debug {
|
||||
for _, c := range line {
|
||||
fmt.Fprintf(os.Stderr, "LINE %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte)
|
||||
}
|
||||
}
|
||||
|
||||
// Assign line comments to syntax immediately following.
|
||||
for _, x := range in.pre {
|
||||
start, _ := x.Span()
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "pre %T :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte)
|
||||
}
|
||||
xcom := x.Comment()
|
||||
for len(line) > 0 && start.Byte >= line[0].Start.Byte {
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "ASSIGN LINE %q #%d\n", line[0].Token, line[0].Start.Byte)
|
||||
}
|
||||
xcom.Before = append(xcom.Before, line[0])
|
||||
line = line[1:]
|
||||
}
|
||||
}
|
||||
|
||||
// Remaining line comments go at end of file.
|
||||
in.file.After = append(in.file.After, line...)
|
||||
|
||||
if debug {
|
||||
for _, c := range suffix {
|
||||
fmt.Fprintf(os.Stderr, "SUFFIX %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte)
|
||||
}
|
||||
}
|
||||
|
||||
// Assign suffix comments to syntax immediately before.
|
||||
for i := len(in.post) - 1; i >= 0; i-- {
|
||||
x := in.post[i]
|
||||
|
||||
start, end := x.Span()
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "post %T :%d:%d #%d :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte, end.Line, end.LineRune, end.Byte)
|
||||
}
|
||||
|
||||
// Do not assign suffix comments to end of line block or whole file.
|
||||
// Instead assign them to the last element inside.
|
||||
switch x.(type) {
|
||||
case *FileSyntax:
|
||||
continue
|
||||
}
|
||||
|
||||
// Do not assign suffix comments to something that starts
|
||||
// on an earlier line, so that in
|
||||
//
|
||||
// x ( y
|
||||
// z ) // comment
|
||||
//
|
||||
// we assign the comment to z and not to x ( ... ).
|
||||
if start.Line != end.Line {
|
||||
continue
|
||||
}
|
||||
xcom := x.Comment()
|
||||
for len(suffix) > 0 && end.Byte <= suffix[len(suffix)-1].Start.Byte {
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "ASSIGN SUFFIX %q #%d\n", suffix[len(suffix)-1].Token, suffix[len(suffix)-1].Start.Byte)
|
||||
}
|
||||
xcom.Suffix = append(xcom.Suffix, suffix[len(suffix)-1])
|
||||
suffix = suffix[:len(suffix)-1]
|
||||
}
|
||||
}
|
||||
|
||||
// We assigned suffix comments in reverse.
|
||||
// If multiple suffix comments were appended to the same
|
||||
// expression node, they are now in reverse. Fix that.
|
||||
for _, x := range in.post {
|
||||
reverseComments(x.Comment().Suffix)
|
||||
}
|
||||
|
||||
// Remaining suffix comments go at beginning of file.
|
||||
in.file.Before = append(in.file.Before, suffix...)
|
||||
}
|
||||
|
||||
// reverseComments reverses the []Comment list.
|
||||
func reverseComments(list []Comment) {
|
||||
for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 {
|
||||
list[i], list[j] = list[j], list[i]
|
||||
}
|
||||
}
|
||||
|
||||
func (in *input) parseFile() {
|
||||
in.file = new(FileSyntax)
|
||||
var cb *CommentBlock
|
||||
for {
|
||||
switch in.peek() {
|
||||
case '\n':
|
||||
in.lex()
|
||||
if cb != nil {
|
||||
in.file.Stmt = append(in.file.Stmt, cb)
|
||||
cb = nil
|
||||
}
|
||||
case _COMMENT:
|
||||
tok := in.lex()
|
||||
if cb == nil {
|
||||
cb = &CommentBlock{Start: tok.pos}
|
||||
}
|
||||
com := cb.Comment()
|
||||
com.Before = append(com.Before, Comment{Start: tok.pos, Token: tok.text})
|
||||
case _EOF:
|
||||
if cb != nil {
|
||||
in.file.Stmt = append(in.file.Stmt, cb)
|
||||
}
|
||||
return
|
||||
default:
|
||||
in.parseStmt()
|
||||
if cb != nil {
|
||||
in.file.Stmt[len(in.file.Stmt)-1].Comment().Before = cb.Before
|
||||
cb = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (in *input) parseStmt() {
|
||||
tok := in.lex()
|
||||
start := tok.pos
|
||||
end := tok.endPos
|
||||
tokens := []string{tok.text}
|
||||
for {
|
||||
tok := in.lex()
|
||||
switch {
|
||||
case tok.kind.isEOL():
|
||||
in.file.Stmt = append(in.file.Stmt, &Line{
|
||||
Start: start,
|
||||
Token: tokens,
|
||||
End: end,
|
||||
})
|
||||
return
|
||||
|
||||
case tok.kind == '(':
|
||||
if next := in.peek(); next.isEOL() {
|
||||
// Start of block: no more tokens on this line.
|
||||
in.file.Stmt = append(in.file.Stmt, in.parseLineBlock(start, tokens, tok))
|
||||
return
|
||||
} else if next == ')' {
|
||||
rparen := in.lex()
|
||||
if in.peek().isEOL() {
|
||||
// Empty block.
|
||||
in.lex()
|
||||
in.file.Stmt = append(in.file.Stmt, &LineBlock{
|
||||
Start: start,
|
||||
Token: tokens,
|
||||
LParen: LParen{Pos: tok.pos},
|
||||
RParen: RParen{Pos: rparen.pos},
|
||||
})
|
||||
return
|
||||
}
|
||||
// '( )' in the middle of the line, not a block.
|
||||
tokens = append(tokens, tok.text, rparen.text)
|
||||
} else {
|
||||
// '(' in the middle of the line, not a block.
|
||||
tokens = append(tokens, tok.text)
|
||||
}
|
||||
|
||||
default:
|
||||
tokens = append(tokens, tok.text)
|
||||
end = tok.endPos
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (in *input) parseLineBlock(start Position, token []string, lparen token) *LineBlock {
|
||||
x := &LineBlock{
|
||||
Start: start,
|
||||
Token: token,
|
||||
LParen: LParen{Pos: lparen.pos},
|
||||
}
|
||||
var comments []Comment
|
||||
for {
|
||||
switch in.peek() {
|
||||
case _EOLCOMMENT:
|
||||
// Suffix comment, will be attached later by assignComments.
|
||||
in.lex()
|
||||
case '\n':
|
||||
// Blank line. Add an empty comment to preserve it.
|
||||
in.lex()
|
||||
if len(comments) == 0 && len(x.Line) > 0 || len(comments) > 0 && comments[len(comments)-1].Token != "" {
|
||||
comments = append(comments, Comment{})
|
||||
}
|
||||
case _COMMENT:
|
||||
tok := in.lex()
|
||||
comments = append(comments, Comment{Start: tok.pos, Token: tok.text})
|
||||
case _EOF:
|
||||
in.Error(fmt.Sprintf("syntax error (unterminated block started at %s:%d:%d)", in.filename, x.Start.Line, x.Start.LineRune))
|
||||
case ')':
|
||||
rparen := in.lex()
|
||||
// Don't preserve blank lines (denoted by a single empty comment, added above)
|
||||
// at the end of the block.
|
||||
if len(comments) == 1 && comments[0] == (Comment{}) {
|
||||
comments = nil
|
||||
}
|
||||
x.RParen.Before = comments
|
||||
x.RParen.Pos = rparen.pos
|
||||
if !in.peek().isEOL() {
|
||||
in.Error("syntax error (expected newline after closing paren)")
|
||||
}
|
||||
in.lex()
|
||||
return x
|
||||
default:
|
||||
l := in.parseLine()
|
||||
x.Line = append(x.Line, l)
|
||||
l.Comment().Before = comments
|
||||
comments = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (in *input) parseLine() *Line {
|
||||
tok := in.lex()
|
||||
if tok.kind.isEOL() {
|
||||
in.Error("internal parse error: parseLine at end of line")
|
||||
}
|
||||
start := tok.pos
|
||||
end := tok.endPos
|
||||
tokens := []string{tok.text}
|
||||
for {
|
||||
tok := in.lex()
|
||||
if tok.kind.isEOL() {
|
||||
return &Line{
|
||||
Start: start,
|
||||
Token: tokens,
|
||||
End: end,
|
||||
InBlock: true,
|
||||
}
|
||||
}
|
||||
tokens = append(tokens, tok.text)
|
||||
end = tok.endPos
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
slashSlash = []byte("//")
|
||||
moduleStr = []byte("module")
|
||||
)
|
||||
|
||||
// ModulePath returns the module path from the gomod file text.
|
||||
// If it cannot find a module path, it returns an empty string.
|
||||
// It is tolerant of unrelated problems in the go.mod file.
|
||||
func ModulePath(mod []byte) string {
|
||||
for len(mod) > 0 {
|
||||
line := mod
|
||||
mod = nil
|
||||
if i := bytes.IndexByte(line, '\n'); i >= 0 {
|
||||
line, mod = line[:i], line[i+1:]
|
||||
}
|
||||
if i := bytes.Index(line, slashSlash); i >= 0 {
|
||||
line = line[:i]
|
||||
}
|
||||
line = bytes.TrimSpace(line)
|
||||
if !bytes.HasPrefix(line, moduleStr) {
|
||||
continue
|
||||
}
|
||||
line = line[len(moduleStr):]
|
||||
n := len(line)
|
||||
line = bytes.TrimSpace(line)
|
||||
if len(line) == n || len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if line[0] == '"' || line[0] == '`' {
|
||||
p, err := strconv.Unquote(string(line))
|
||||
if err != nil {
|
||||
return "" // malformed quoted string or multiline module path
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
return string(line)
|
||||
}
|
||||
return "" // missing module path
|
||||
}
|
||||
1904
vendor/golang.org/x/mod/modfile/rule.go
generated
vendored
Normal file
1904
vendor/golang.org/x/mod/modfile/rule.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
333
vendor/golang.org/x/mod/modfile/work.go
generated
vendored
Normal file
333
vendor/golang.org/x/mod/modfile/work.go
generated
vendored
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
// Copyright 2021 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 modfile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A WorkFile is the parsed, interpreted form of a go.work file.
|
||||
type WorkFile struct {
|
||||
Go *Go
|
||||
Toolchain *Toolchain
|
||||
Godebug []*Godebug
|
||||
Use []*Use
|
||||
Replace []*Replace
|
||||
|
||||
Syntax *FileSyntax
|
||||
}
|
||||
|
||||
// A Use is a single directory statement.
|
||||
type Use struct {
|
||||
Path string // Use path of module.
|
||||
ModulePath string // Module path in the comment.
|
||||
Syntax *Line
|
||||
}
|
||||
|
||||
// ParseWork parses and returns a go.work file.
|
||||
//
|
||||
// file is the name of the file, used in positions and errors.
|
||||
//
|
||||
// data is the content of the file.
|
||||
//
|
||||
// fix is an optional function that canonicalizes module versions.
|
||||
// If fix is nil, all module versions must be canonical ([module.CanonicalVersion]
|
||||
// must return the same string).
|
||||
func ParseWork(file string, data []byte, fix VersionFixer) (*WorkFile, error) {
|
||||
fs, err := parse(file, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f := &WorkFile{
|
||||
Syntax: fs,
|
||||
}
|
||||
var errs ErrorList
|
||||
|
||||
for _, x := range fs.Stmt {
|
||||
switch x := x.(type) {
|
||||
case *Line:
|
||||
f.add(&errs, x, x.Token[0], x.Token[1:], fix)
|
||||
|
||||
case *LineBlock:
|
||||
if len(x.Token) > 1 {
|
||||
errs = append(errs, Error{
|
||||
Filename: file,
|
||||
Pos: x.Start,
|
||||
Err: fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")),
|
||||
})
|
||||
continue
|
||||
}
|
||||
switch x.Token[0] {
|
||||
default:
|
||||
errs = append(errs, Error{
|
||||
Filename: file,
|
||||
Pos: x.Start,
|
||||
Err: fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")),
|
||||
})
|
||||
continue
|
||||
case "godebug", "use", "replace":
|
||||
for _, l := range x.Line {
|
||||
f.add(&errs, l, x.Token[0], l.Token, fix)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// Cleanup cleans up the file f after any edit operations.
|
||||
// To avoid quadratic behavior, modifications like [WorkFile.DropRequire]
|
||||
// clear the entry but do not remove it from the slice.
|
||||
// Cleanup cleans out all the cleared entries.
|
||||
func (f *WorkFile) Cleanup() {
|
||||
w := 0
|
||||
for _, r := range f.Use {
|
||||
if r.Path != "" {
|
||||
f.Use[w] = r
|
||||
w++
|
||||
}
|
||||
}
|
||||
f.Use = f.Use[:w]
|
||||
|
||||
w = 0
|
||||
for _, r := range f.Replace {
|
||||
if r.Old.Path != "" {
|
||||
f.Replace[w] = r
|
||||
w++
|
||||
}
|
||||
}
|
||||
f.Replace = f.Replace[:w]
|
||||
|
||||
f.Syntax.Cleanup()
|
||||
}
|
||||
|
||||
func (f *WorkFile) AddGoStmt(version string) error {
|
||||
if !GoVersionRE.MatchString(version) {
|
||||
return fmt.Errorf("invalid language version %q", version)
|
||||
}
|
||||
if f.Go == nil {
|
||||
stmt := &Line{Token: []string{"go", version}}
|
||||
f.Go = &Go{
|
||||
Version: version,
|
||||
Syntax: stmt,
|
||||
}
|
||||
// Find the first non-comment-only block and add
|
||||
// the go statement before it. That will keep file comments at the top.
|
||||
i := 0
|
||||
for i = 0; i < len(f.Syntax.Stmt); i++ {
|
||||
if _, ok := f.Syntax.Stmt[i].(*CommentBlock); !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
f.Syntax.Stmt = append(append(f.Syntax.Stmt[:i:i], stmt), f.Syntax.Stmt[i:]...)
|
||||
} else {
|
||||
f.Go.Version = version
|
||||
f.Syntax.updateLine(f.Go.Syntax, "go", version)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *WorkFile) AddToolchainStmt(name string) error {
|
||||
if !ToolchainRE.MatchString(name) {
|
||||
return fmt.Errorf("invalid toolchain name %q", name)
|
||||
}
|
||||
if f.Toolchain == nil {
|
||||
stmt := &Line{Token: []string{"toolchain", name}}
|
||||
f.Toolchain = &Toolchain{
|
||||
Name: name,
|
||||
Syntax: stmt,
|
||||
}
|
||||
// Find the go line and add the toolchain line after it.
|
||||
// Or else find the first non-comment-only block and add
|
||||
// the toolchain line before it. That will keep file comments at the top.
|
||||
i := 0
|
||||
for i = 0; i < len(f.Syntax.Stmt); i++ {
|
||||
if line, ok := f.Syntax.Stmt[i].(*Line); ok && len(line.Token) > 0 && line.Token[0] == "go" {
|
||||
i++
|
||||
goto Found
|
||||
}
|
||||
}
|
||||
for i = 0; i < len(f.Syntax.Stmt); i++ {
|
||||
if _, ok := f.Syntax.Stmt[i].(*CommentBlock); !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
Found:
|
||||
f.Syntax.Stmt = append(append(f.Syntax.Stmt[:i:i], stmt), f.Syntax.Stmt[i:]...)
|
||||
} else {
|
||||
f.Toolchain.Name = name
|
||||
f.Syntax.updateLine(f.Toolchain.Syntax, "toolchain", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DropGoStmt deletes the go statement from the file.
|
||||
func (f *WorkFile) DropGoStmt() {
|
||||
if f.Go != nil {
|
||||
f.Go.Syntax.markRemoved()
|
||||
f.Go = nil
|
||||
}
|
||||
}
|
||||
|
||||
// DropToolchainStmt deletes the toolchain statement from the file.
|
||||
func (f *WorkFile) DropToolchainStmt() {
|
||||
if f.Toolchain != nil {
|
||||
f.Toolchain.Syntax.markRemoved()
|
||||
f.Toolchain = nil
|
||||
}
|
||||
}
|
||||
|
||||
// AddGodebug sets the first godebug line for key to value,
|
||||
// preserving any existing comments for that line and removing all
|
||||
// other godebug lines for key.
|
||||
//
|
||||
// If no line currently exists for key, AddGodebug adds a new line
|
||||
// at the end of the last godebug block.
|
||||
func (f *WorkFile) AddGodebug(key, value string) error {
|
||||
need := true
|
||||
for _, g := range f.Godebug {
|
||||
if g.Key == key {
|
||||
if need {
|
||||
g.Value = value
|
||||
f.Syntax.updateLine(g.Syntax, "godebug", key+"="+value)
|
||||
need = false
|
||||
} else {
|
||||
g.Syntax.markRemoved()
|
||||
*g = Godebug{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if need {
|
||||
f.addNewGodebug(key, value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// addNewGodebug adds a new godebug key=value line at the end
|
||||
// of the last godebug block, regardless of any existing godebug lines for key.
|
||||
func (f *WorkFile) addNewGodebug(key, value string) {
|
||||
line := f.Syntax.addLine(nil, "godebug", key+"="+value)
|
||||
g := &Godebug{
|
||||
Key: key,
|
||||
Value: value,
|
||||
Syntax: line,
|
||||
}
|
||||
f.Godebug = append(f.Godebug, g)
|
||||
}
|
||||
|
||||
func (f *WorkFile) DropGodebug(key string) error {
|
||||
for _, g := range f.Godebug {
|
||||
if g.Key == key {
|
||||
g.Syntax.markRemoved()
|
||||
*g = Godebug{}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *WorkFile) AddUse(diskPath, modulePath string) error {
|
||||
need := true
|
||||
for _, d := range f.Use {
|
||||
if d.Path == diskPath {
|
||||
if need {
|
||||
d.ModulePath = modulePath
|
||||
f.Syntax.updateLine(d.Syntax, "use", AutoQuote(diskPath))
|
||||
need = false
|
||||
} else {
|
||||
d.Syntax.markRemoved()
|
||||
*d = Use{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if need {
|
||||
f.AddNewUse(diskPath, modulePath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *WorkFile) AddNewUse(diskPath, modulePath string) {
|
||||
line := f.Syntax.addLine(nil, "use", AutoQuote(diskPath))
|
||||
f.Use = append(f.Use, &Use{Path: diskPath, ModulePath: modulePath, Syntax: line})
|
||||
}
|
||||
|
||||
func (f *WorkFile) SetUse(dirs []*Use) {
|
||||
need := make(map[string]string)
|
||||
for _, d := range dirs {
|
||||
need[d.Path] = d.ModulePath
|
||||
}
|
||||
|
||||
for _, d := range f.Use {
|
||||
if modulePath, ok := need[d.Path]; ok {
|
||||
d.ModulePath = modulePath
|
||||
} else {
|
||||
d.Syntax.markRemoved()
|
||||
*d = Use{}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(#45713): Add module path to comment.
|
||||
|
||||
for diskPath, modulePath := range need {
|
||||
f.AddNewUse(diskPath, modulePath)
|
||||
}
|
||||
f.SortBlocks()
|
||||
}
|
||||
|
||||
func (f *WorkFile) DropUse(path string) error {
|
||||
for _, d := range f.Use {
|
||||
if d.Path == path {
|
||||
d.Syntax.markRemoved()
|
||||
*d = Use{}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *WorkFile) AddReplace(oldPath, oldVers, newPath, newVers string) error {
|
||||
return addReplace(f.Syntax, &f.Replace, oldPath, oldVers, newPath, newVers)
|
||||
}
|
||||
|
||||
func (f *WorkFile) DropReplace(oldPath, oldVers string) error {
|
||||
for _, r := range f.Replace {
|
||||
if r.Old.Path == oldPath && r.Old.Version == oldVers {
|
||||
r.Syntax.markRemoved()
|
||||
*r = Replace{}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *WorkFile) SortBlocks() {
|
||||
f.removeDups() // otherwise sorting is unsafe
|
||||
|
||||
for _, stmt := range f.Syntax.Stmt {
|
||||
block, ok := stmt.(*LineBlock)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
slices.SortStableFunc(block.Line, compareLine)
|
||||
}
|
||||
}
|
||||
|
||||
// removeDups removes duplicate replace directives.
|
||||
//
|
||||
// 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 *WorkFile) removeDups() {
|
||||
removeDups(f.Syntax, nil, &f.Replace, nil, nil)
|
||||
}
|
||||
840
vendor/golang.org/x/mod/module/module.go
generated
vendored
Normal file
840
vendor/golang.org/x/mod/module/module.go
generated
vendored
Normal file
|
|
@ -0,0 +1,840 @@
|
|||
// Copyright 2018 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 module defines the module.Version type along with support code.
|
||||
//
|
||||
// The [module.Version] type is a simple Path, Version pair:
|
||||
//
|
||||
// type Version struct {
|
||||
// Path string
|
||||
// Version string
|
||||
// }
|
||||
//
|
||||
// There are no restrictions imposed directly by use of this structure,
|
||||
// but additional checking functions, most notably [Check], verify that
|
||||
// a particular path, version pair is valid.
|
||||
//
|
||||
// # Escaped Paths
|
||||
//
|
||||
// Module paths appear as substrings of file system paths
|
||||
// (in the download cache) and of web server URLs in the proxy protocol.
|
||||
// In general we cannot rely on file systems to be case-sensitive,
|
||||
// nor can we rely on web servers, since they read from file systems.
|
||||
// That is, we cannot rely on the file system to keep rsc.io/QUOTE
|
||||
// and rsc.io/quote separate. Windows and macOS don't.
|
||||
// Instead, we must never require two different casings of a file path.
|
||||
// Because we want the download cache to match the proxy protocol,
|
||||
// and because we want the proxy protocol to be possible to serve
|
||||
// from a tree of static files (which might be stored on a case-insensitive
|
||||
// file system), the proxy protocol must never require two different casings
|
||||
// of a URL path either.
|
||||
//
|
||||
// One possibility would be to make the escaped form be the lowercase
|
||||
// hexadecimal encoding of the actual path bytes. This would avoid ever
|
||||
// needing different casings of a file path, but it would be fairly illegible
|
||||
// to most programmers when those paths appeared in the file system
|
||||
// (including in file paths in compiler errors and stack traces)
|
||||
// in web server logs, and so on. Instead, we want a safe escaped form that
|
||||
// leaves most paths unaltered.
|
||||
//
|
||||
// The safe escaped form is to replace every uppercase letter
|
||||
// with an exclamation mark followed by the letter's lowercase equivalent.
|
||||
//
|
||||
// For example,
|
||||
//
|
||||
// github.com/Azure/azure-sdk-for-go -> github.com/!azure/azure-sdk-for-go.
|
||||
// github.com/GoogleCloudPlatform/cloudsql-proxy -> github.com/!google!cloud!platform/cloudsql-proxy
|
||||
// github.com/Sirupsen/logrus -> github.com/!sirupsen/logrus.
|
||||
//
|
||||
// Import paths that avoid upper-case letters are left unchanged.
|
||||
// Note that because import paths are ASCII-only and avoid various
|
||||
// problematic punctuation (like : < and >), the escaped form is also ASCII-only
|
||||
// and avoids the same problematic punctuation.
|
||||
//
|
||||
// Import paths have never allowed exclamation marks, so there is no
|
||||
// need to define how to escape a literal !.
|
||||
//
|
||||
// # Unicode Restrictions
|
||||
//
|
||||
// Today, paths are disallowed from using Unicode.
|
||||
//
|
||||
// Although paths are currently disallowed from using Unicode,
|
||||
// we would like at some point to allow Unicode letters as well, to assume that
|
||||
// file systems and URLs are Unicode-safe (storing UTF-8), and apply
|
||||
// the !-for-uppercase convention for escaping them in the file system.
|
||||
// But there are at least two subtle considerations.
|
||||
//
|
||||
// First, note that not all case-fold equivalent distinct runes
|
||||
// form an upper/lower pair.
|
||||
// For example, U+004B ('K'), U+006B ('k'), and U+212A ('K' for Kelvin)
|
||||
// are three distinct runes that case-fold to each other.
|
||||
// When we do add Unicode letters, we must not assume that upper/lower
|
||||
// are the only case-equivalent pairs.
|
||||
// Perhaps the Kelvin symbol would be disallowed entirely, for example.
|
||||
// Or perhaps it would escape as "!!k", or perhaps as "(212A)".
|
||||
//
|
||||
// Second, it would be nice to allow Unicode marks as well as letters,
|
||||
// but marks include combining marks, and then we must deal not
|
||||
// only with case folding but also normalization: both U+00E9 ('é')
|
||||
// and U+0065 U+0301 ('e' followed by combining acute accent)
|
||||
// look the same on the page and are treated by some file systems
|
||||
// as the same path. If we do allow Unicode marks in paths, there
|
||||
// must be some kind of normalization to allow only one canonical
|
||||
// encoding of any character used in an import path.
|
||||
package module
|
||||
|
||||
// IMPORTANT NOTE
|
||||
//
|
||||
// This file essentially defines the set of valid import paths for the go command.
|
||||
// There are many subtle considerations, including Unicode ambiguity,
|
||||
// security, network, and file system representations.
|
||||
//
|
||||
// This file also defines the set of valid module path and version combinations,
|
||||
// another topic with many subtle considerations.
|
||||
//
|
||||
// Changes to the semantics in this file require approval from rsc.
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
// A Version (for clients, a module.Version) is defined by a module path and version pair.
|
||||
// These are stored in their plain (unescaped) form.
|
||||
type Version struct {
|
||||
// Path is a module path, like "golang.org/x/text" or "rsc.io/quote/v2".
|
||||
Path string
|
||||
|
||||
// Version is usually a semantic version in canonical form.
|
||||
// There are three exceptions to this general rule.
|
||||
// First, the top-level target of a build has no specific version
|
||||
// and uses Version = "".
|
||||
// Second, during MVS calculations the version "none" is used
|
||||
// to represent the decision to take no version of a given module.
|
||||
// Third, filesystem paths found in "replace" directives are
|
||||
// represented by a path with an empty version.
|
||||
Version string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// String returns a representation of the Version suitable for logging
|
||||
// (Path@Version, or just Path if Version is empty).
|
||||
func (m Version) String() string {
|
||||
if m.Version == "" {
|
||||
return m.Path
|
||||
}
|
||||
return m.Path + "@" + m.Version
|
||||
}
|
||||
|
||||
// A ModuleError indicates an error specific to a module.
|
||||
type ModuleError struct {
|
||||
Path string
|
||||
Version string
|
||||
Err error
|
||||
}
|
||||
|
||||
// VersionError returns a [ModuleError] derived from a [Version] and error,
|
||||
// or err itself if it is already such an error.
|
||||
func VersionError(v Version, err error) error {
|
||||
var mErr *ModuleError
|
||||
if errors.As(err, &mErr) && mErr.Path == v.Path && mErr.Version == v.Version {
|
||||
return err
|
||||
}
|
||||
return &ModuleError{
|
||||
Path: v.Path,
|
||||
Version: v.Version,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *ModuleError) Error() string {
|
||||
if v, ok := e.Err.(*InvalidVersionError); ok {
|
||||
return fmt.Sprintf("%s@%s: invalid %s: %v", e.Path, v.Version, v.noun(), v.Err)
|
||||
}
|
||||
if e.Version != "" {
|
||||
return fmt.Sprintf("%s@%s: %v", e.Path, e.Version, e.Err)
|
||||
}
|
||||
return fmt.Sprintf("module %s: %v", e.Path, e.Err)
|
||||
}
|
||||
|
||||
func (e *ModuleError) Unwrap() error { return e.Err }
|
||||
|
||||
// An InvalidVersionError indicates an error specific to a version, with the
|
||||
// module path unknown or specified externally.
|
||||
//
|
||||
// A [ModuleError] may wrap an InvalidVersionError, but an InvalidVersionError
|
||||
// must not wrap a ModuleError.
|
||||
type InvalidVersionError struct {
|
||||
Version string
|
||||
Pseudo bool
|
||||
Err error
|
||||
}
|
||||
|
||||
// noun returns either "version" or "pseudo-version", depending on whether
|
||||
// e.Version is a pseudo-version.
|
||||
func (e *InvalidVersionError) noun() string {
|
||||
if e.Pseudo {
|
||||
return "pseudo-version"
|
||||
}
|
||||
return "version"
|
||||
}
|
||||
|
||||
func (e *InvalidVersionError) Error() string {
|
||||
return fmt.Sprintf("%s %q invalid: %s", e.noun(), e.Version, e.Err)
|
||||
}
|
||||
|
||||
func (e *InvalidVersionError) Unwrap() error { return e.Err }
|
||||
|
||||
// An InvalidPathError indicates a module, import, or file path doesn't
|
||||
// satisfy all naming constraints. See [CheckPath], [CheckImportPath],
|
||||
// and [CheckFilePath] for specific restrictions.
|
||||
type InvalidPathError struct {
|
||||
Kind string // "module", "import", or "file"
|
||||
Path string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *InvalidPathError) Error() string {
|
||||
return fmt.Sprintf("malformed %s path %q: %v", e.Kind, e.Path, e.Err)
|
||||
}
|
||||
|
||||
func (e *InvalidPathError) Unwrap() error { return e.Err }
|
||||
|
||||
// Check checks that a given module path, version pair is valid.
|
||||
// In addition to the path being a valid module path
|
||||
// and the version being a valid semantic version,
|
||||
// the two must correspond.
|
||||
// For example, the path "yaml/v2" only corresponds to
|
||||
// semantic versions beginning with "v2.".
|
||||
func Check(path, version string) error {
|
||||
if err := CheckPath(path); err != nil {
|
||||
return err
|
||||
}
|
||||
if !semver.IsValid(version) {
|
||||
return &ModuleError{
|
||||
Path: path,
|
||||
Err: &InvalidVersionError{Version: version, Err: errors.New("not a semantic version")},
|
||||
}
|
||||
}
|
||||
_, pathMajor, _ := SplitPathVersion(path)
|
||||
if err := CheckPathMajor(version, pathMajor); err != nil {
|
||||
return &ModuleError{Path: path, Err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// firstPathOK reports whether r can appear in the first element of a module path.
|
||||
// The first element of the path must be an LDH domain name, at least for now.
|
||||
// To avoid case ambiguity, the domain name must be entirely lower case.
|
||||
func firstPathOK(r rune) bool {
|
||||
return r == '-' || r == '.' ||
|
||||
'0' <= r && r <= '9' ||
|
||||
'a' <= r && r <= 'z'
|
||||
}
|
||||
|
||||
// modPathOK reports whether r can appear in a module path element.
|
||||
// Paths can be ASCII letters, ASCII digits, and limited ASCII punctuation: - . _ and ~.
|
||||
//
|
||||
// This matches what "go get" has historically recognized in import paths,
|
||||
// and avoids confusing sequences like '%20' or '+' that would change meaning
|
||||
// if used in a URL.
|
||||
//
|
||||
// TODO(rsc): We would like to allow Unicode letters, but that requires additional
|
||||
// care in the safe encoding (see "escaped paths" above).
|
||||
func modPathOK(r rune) bool {
|
||||
if r < utf8.RuneSelf {
|
||||
return r == '-' || r == '.' || r == '_' || r == '~' ||
|
||||
'0' <= r && r <= '9' ||
|
||||
'A' <= r && r <= 'Z' ||
|
||||
'a' <= r && r <= 'z'
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// importPathOK reports whether r can appear in a package import path element.
|
||||
//
|
||||
// Import paths are intermediate between module paths and file paths: we
|
||||
// disallow characters that would be confusing or ambiguous as arguments to
|
||||
// 'go get' (such as '@' and ' ' ), but allow certain characters that are
|
||||
// otherwise-unambiguous on the command line and historically used for some
|
||||
// binary names (such as '++' as a suffix for compiler binaries and wrappers).
|
||||
func importPathOK(r rune) bool {
|
||||
return modPathOK(r) || r == '+'
|
||||
}
|
||||
|
||||
// fileNameOK reports whether r can appear in a file name.
|
||||
// For now we allow all Unicode letters but otherwise limit to pathOK plus a few more punctuation characters.
|
||||
// If we expand the set of allowed characters here, we have to
|
||||
// work harder at detecting potential case-folding and normalization collisions.
|
||||
// See note about "escaped paths" above.
|
||||
func fileNameOK(r rune) bool {
|
||||
if r < utf8.RuneSelf {
|
||||
// Entire set of ASCII punctuation, from which we remove characters:
|
||||
// ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~
|
||||
// We disallow some shell special characters: " ' * < > ? ` |
|
||||
// (Note that some of those are disallowed by the Windows file system as well.)
|
||||
// We also disallow path separators / : and \ (fileNameOK is only called on path element characters).
|
||||
// We allow spaces (U+0020) in file names.
|
||||
const allowed = "!#$%&()+,-.=@[]^_{}~ "
|
||||
if '0' <= r && r <= '9' || 'A' <= r && r <= 'Z' || 'a' <= r && r <= 'z' {
|
||||
return true
|
||||
}
|
||||
return strings.ContainsRune(allowed, r)
|
||||
}
|
||||
// It may be OK to add more ASCII punctuation here, but only carefully.
|
||||
// For example Windows disallows < > \, and macOS disallows :, so we must not allow those.
|
||||
return unicode.IsLetter(r)
|
||||
}
|
||||
|
||||
// CheckPath checks that a module path is valid.
|
||||
// A valid module path is a valid import path, as checked by [CheckImportPath],
|
||||
// with three additional constraints.
|
||||
// First, the leading path element (up to the first slash, if any),
|
||||
// by convention a domain name, must contain only lower-case ASCII letters,
|
||||
// ASCII digits, dots (U+002E), and dashes (U+002D);
|
||||
// it must contain at least one dot and cannot start with a dash.
|
||||
// Second, for a final path element of the form /vN, where N looks numeric
|
||||
// (ASCII digits and dots) must not begin with a leading zero, must not be /v1,
|
||||
// and must not contain any dots. For paths beginning with "gopkg.in/",
|
||||
// this second requirement is replaced by a requirement that the path
|
||||
// follow the gopkg.in server's conventions.
|
||||
// Third, no path element may begin with a dot.
|
||||
func CheckPath(path string) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = &InvalidPathError{Kind: "module", Path: path, Err: err}
|
||||
}
|
||||
}()
|
||||
|
||||
if err := checkPath(path, modulePath); err != nil {
|
||||
return err
|
||||
}
|
||||
i := strings.Index(path, "/")
|
||||
if i < 0 {
|
||||
i = len(path)
|
||||
}
|
||||
if i == 0 {
|
||||
return fmt.Errorf("leading slash")
|
||||
}
|
||||
if !strings.Contains(path[:i], ".") {
|
||||
return fmt.Errorf("missing dot in first path element")
|
||||
}
|
||||
if path[0] == '-' {
|
||||
return fmt.Errorf("leading dash in first path element")
|
||||
}
|
||||
for _, r := range path[:i] {
|
||||
if !firstPathOK(r) {
|
||||
return fmt.Errorf("invalid char %q in first path element", r)
|
||||
}
|
||||
}
|
||||
if _, _, ok := SplitPathVersion(path); !ok {
|
||||
return fmt.Errorf("invalid version")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckImportPath checks that an import path is valid.
|
||||
//
|
||||
// A valid import path consists of one or more valid path elements
|
||||
// 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 ~.
|
||||
// It must not 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). 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
|
||||
// subtleties of Unicode.
|
||||
func CheckImportPath(path string) error {
|
||||
if err := checkPath(path, importPath); err != nil {
|
||||
return &InvalidPathError{Kind: "import", Path: path, Err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// pathKind indicates what kind of path we're checking. Module paths,
|
||||
// import paths, and file paths have different restrictions.
|
||||
type pathKind int
|
||||
|
||||
const (
|
||||
modulePath pathKind = iota
|
||||
importPath
|
||||
filePath
|
||||
)
|
||||
|
||||
// checkPath checks that a general path is valid. kind indicates what
|
||||
// specific constraints should be applied.
|
||||
//
|
||||
// checkPath returns an error describing why the path is not valid.
|
||||
// Because these checks apply to module, import, and file paths,
|
||||
// and because other checks may be applied, the caller is expected to wrap
|
||||
// this error with [InvalidPathError].
|
||||
func checkPath(path string, kind pathKind) error {
|
||||
if !utf8.ValidString(path) {
|
||||
return fmt.Errorf("invalid UTF-8")
|
||||
}
|
||||
if path == "" {
|
||||
return fmt.Errorf("empty string")
|
||||
}
|
||||
if path[0] == '-' && kind != filePath {
|
||||
return fmt.Errorf("leading dash")
|
||||
}
|
||||
if strings.Contains(path, "//") {
|
||||
return fmt.Errorf("double slash")
|
||||
}
|
||||
if path[len(path)-1] == '/' {
|
||||
return fmt.Errorf("trailing slash")
|
||||
}
|
||||
elemStart := 0
|
||||
for i, r := range path {
|
||||
if r == '/' {
|
||||
if err := checkElem(path[elemStart:i], kind); err != nil {
|
||||
return err
|
||||
}
|
||||
elemStart = i + 1
|
||||
}
|
||||
}
|
||||
if err := checkElem(path[elemStart:], kind); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkElem checks whether an individual path element is valid.
|
||||
func checkElem(elem string, kind pathKind) error {
|
||||
if elem == "" {
|
||||
return fmt.Errorf("empty path element")
|
||||
}
|
||||
if strings.Count(elem, ".") == len(elem) {
|
||||
return fmt.Errorf("invalid path element %q", elem)
|
||||
}
|
||||
if elem[0] == '.' && kind == modulePath {
|
||||
return fmt.Errorf("leading dot in path element")
|
||||
}
|
||||
if elem[len(elem)-1] == '.' {
|
||||
return fmt.Errorf("trailing dot in path element")
|
||||
}
|
||||
for _, r := range elem {
|
||||
ok := false
|
||||
switch kind {
|
||||
case modulePath:
|
||||
ok = modPathOK(r)
|
||||
case importPath:
|
||||
ok = importPathOK(r)
|
||||
case filePath:
|
||||
ok = fileNameOK(r)
|
||||
default:
|
||||
panic(fmt.Sprintf("internal error: invalid kind %v", kind))
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid char %q", r)
|
||||
}
|
||||
}
|
||||
|
||||
// Windows disallows a bunch of path elements, sadly.
|
||||
// See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
|
||||
short := elem
|
||||
if i := strings.Index(short, "."); i >= 0 {
|
||||
short = short[:i]
|
||||
}
|
||||
for _, bad := range badWindowsNames {
|
||||
if strings.EqualFold(bad, short) {
|
||||
return fmt.Errorf("%q disallowed as path element component on Windows", short)
|
||||
}
|
||||
}
|
||||
|
||||
if kind == filePath {
|
||||
// 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
|
||||
}
|
||||
|
||||
// CheckFilePath checks that a slash-separated file path is valid.
|
||||
// The definition of a valid file path is the same as the definition
|
||||
// of a valid import path except that the set of allowed characters is larger:
|
||||
// all Unicode letters, ASCII digits, the ASCII space character (U+0020),
|
||||
// and the ASCII punctuation characters
|
||||
// “!#$%&()+,-.=@[]^_{}~”.
|
||||
// (The excluded punctuation characters, " * < > ? ` ' | / \ and :,
|
||||
// have special meanings in certain shells or operating systems.)
|
||||
//
|
||||
// CheckFilePath may be less restrictive in the future, but see the
|
||||
// top-level package documentation for additional information about
|
||||
// subtleties of Unicode.
|
||||
func CheckFilePath(path string) error {
|
||||
if err := checkPath(path, filePath); err != nil {
|
||||
return &InvalidPathError{Kind: "file", Path: path, Err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// badWindowsNames are the reserved file path elements on Windows.
|
||||
// See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
|
||||
var badWindowsNames = []string{
|
||||
"CON",
|
||||
"PRN",
|
||||
"AUX",
|
||||
"NUL",
|
||||
"COM1",
|
||||
"COM2",
|
||||
"COM3",
|
||||
"COM4",
|
||||
"COM5",
|
||||
"COM6",
|
||||
"COM7",
|
||||
"COM8",
|
||||
"COM9",
|
||||
"LPT1",
|
||||
"LPT2",
|
||||
"LPT3",
|
||||
"LPT4",
|
||||
"LPT5",
|
||||
"LPT6",
|
||||
"LPT7",
|
||||
"LPT8",
|
||||
"LPT9",
|
||||
}
|
||||
|
||||
// SplitPathVersion returns prefix and major version such that prefix+pathMajor == path
|
||||
// and version is either empty or "/vN" for N >= 2.
|
||||
// As a special case, gopkg.in paths are recognized directly;
|
||||
// they require ".vN" instead of "/vN", and for all N, not just N >= 2.
|
||||
// SplitPathVersion returns with ok = false when presented with
|
||||
// a path whose last path element does not satisfy the constraints
|
||||
// applied by [CheckPath], such as "example.com/pkg/v1" or "example.com/pkg/v1.2".
|
||||
func SplitPathVersion(path string) (prefix, pathMajor string, ok bool) {
|
||||
if strings.HasPrefix(path, "gopkg.in/") {
|
||||
return splitGopkgIn(path)
|
||||
}
|
||||
|
||||
i := len(path)
|
||||
dot := false
|
||||
for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9' || path[i-1] == '.') {
|
||||
if path[i-1] == '.' {
|
||||
dot = true
|
||||
}
|
||||
i--
|
||||
}
|
||||
if i <= 1 || i == len(path) || path[i-1] != 'v' || path[i-2] != '/' {
|
||||
return path, "", true
|
||||
}
|
||||
prefix, pathMajor = path[:i-2], path[i-2:]
|
||||
if dot || len(pathMajor) <= 2 || pathMajor[2] == '0' || pathMajor == "/v1" {
|
||||
return path, "", false
|
||||
}
|
||||
return prefix, pathMajor, true
|
||||
}
|
||||
|
||||
// splitGopkgIn is like SplitPathVersion but only for gopkg.in paths.
|
||||
func splitGopkgIn(path string) (prefix, pathMajor string, ok bool) {
|
||||
if !strings.HasPrefix(path, "gopkg.in/") {
|
||||
return path, "", false
|
||||
}
|
||||
i := len(path)
|
||||
if strings.HasSuffix(path, "-unstable") {
|
||||
i -= len("-unstable")
|
||||
}
|
||||
for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9') {
|
||||
i--
|
||||
}
|
||||
if i <= 1 || path[i-1] != 'v' || path[i-2] != '.' {
|
||||
// All gopkg.in paths must end in vN for some N.
|
||||
return path, "", false
|
||||
}
|
||||
prefix, pathMajor = path[:i-2], path[i-2:]
|
||||
if len(pathMajor) <= 2 || pathMajor[2] == '0' && pathMajor != ".v0" {
|
||||
return path, "", false
|
||||
}
|
||||
return prefix, pathMajor, true
|
||||
}
|
||||
|
||||
// MatchPathMajor reports whether the semantic version v
|
||||
// matches the path major version pathMajor.
|
||||
//
|
||||
// MatchPathMajor returns true if and only if [CheckPathMajor] returns nil.
|
||||
func MatchPathMajor(v, pathMajor string) bool {
|
||||
return CheckPathMajor(v, pathMajor) == nil
|
||||
}
|
||||
|
||||
// CheckPathMajor returns a non-nil error if the semantic version v
|
||||
// does not match the path major version pathMajor.
|
||||
func CheckPathMajor(v, pathMajor string) error {
|
||||
// TODO(jayconrod): return errors or panic for invalid inputs. This function
|
||||
// (and others) was covered by integration tests for cmd/go, and surrounding
|
||||
// code protected against invalid inputs like non-canonical versions.
|
||||
if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") {
|
||||
pathMajor = strings.TrimSuffix(pathMajor, "-unstable")
|
||||
}
|
||||
if strings.HasPrefix(v, "v0.0.0-") && pathMajor == ".v1" {
|
||||
// Allow old bug in pseudo-versions that generated v0.0.0- pseudoversion for gopkg .v1.
|
||||
// For example, gopkg.in/yaml.v2@v2.2.1's go.mod requires gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405.
|
||||
return nil
|
||||
}
|
||||
m := semver.Major(v)
|
||||
if pathMajor == "" {
|
||||
if m == "v0" || m == "v1" || semver.Build(v) == "+incompatible" {
|
||||
return nil
|
||||
}
|
||||
pathMajor = "v0 or v1"
|
||||
} else if pathMajor[0] == '/' || pathMajor[0] == '.' {
|
||||
if m == pathMajor[1:] {
|
||||
return nil
|
||||
}
|
||||
pathMajor = pathMajor[1:]
|
||||
}
|
||||
return &InvalidVersionError{
|
||||
Version: v,
|
||||
Err: fmt.Errorf("should be %s, not %s", pathMajor, semver.Major(v)),
|
||||
}
|
||||
}
|
||||
|
||||
// PathMajorPrefix returns the major-version tag prefix implied by pathMajor.
|
||||
// An empty PathMajorPrefix allows either v0 or v1.
|
||||
//
|
||||
// Note that [MatchPathMajor] may accept some versions that do not actually begin
|
||||
// with this prefix: namely, it accepts a 'v0.0.0-' prefix for a '.v1'
|
||||
// pathMajor, even though that pathMajor implies 'v1' tagging.
|
||||
func PathMajorPrefix(pathMajor string) string {
|
||||
if pathMajor == "" {
|
||||
return ""
|
||||
}
|
||||
if pathMajor[0] != '/' && pathMajor[0] != '.' {
|
||||
panic("pathMajor suffix " + pathMajor + " passed to PathMajorPrefix lacks separator")
|
||||
}
|
||||
if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") {
|
||||
pathMajor = strings.TrimSuffix(pathMajor, "-unstable")
|
||||
}
|
||||
m := pathMajor[1:]
|
||||
if m != semver.Major(m) {
|
||||
panic("pathMajor suffix " + pathMajor + "passed to PathMajorPrefix is not a valid major version")
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// CanonicalVersion returns the canonical form of the version string v.
|
||||
// It is the same as [semver.Canonical] except that it preserves the special build suffix "+incompatible".
|
||||
func CanonicalVersion(v string) string {
|
||||
cv := semver.Canonical(v)
|
||||
if semver.Build(v) == "+incompatible" {
|
||||
cv += "+incompatible"
|
||||
}
|
||||
return cv
|
||||
}
|
||||
|
||||
// Sort sorts the list by Path, breaking ties by comparing [Version] fields.
|
||||
// The Version fields are interpreted as semantic versions (using [semver.Compare])
|
||||
// optionally followed by a tie-breaking suffix introduced by a slash character,
|
||||
// like in "v0.0.1/go.mod".
|
||||
func Sort(list []Version) {
|
||||
slices.SortFunc(list, func(i, j Version) int {
|
||||
if i.Path != j.Path {
|
||||
return strings.Compare(i.Path, j.Path)
|
||||
}
|
||||
// To help go.sum formatting, allow version/file.
|
||||
// Compare semver prefix by semver rules,
|
||||
// file by string order.
|
||||
vi := i.Version
|
||||
vj := j.Version
|
||||
var fi, fj string
|
||||
if k := strings.Index(vi, "/"); k >= 0 {
|
||||
vi, fi = vi[:k], vi[k:]
|
||||
}
|
||||
if k := strings.Index(vj, "/"); k >= 0 {
|
||||
vj, fj = vj[:k], vj[k:]
|
||||
}
|
||||
if vi != vj {
|
||||
return semver.Compare(vi, vj)
|
||||
}
|
||||
return cmp.Compare(fi, fj)
|
||||
})
|
||||
}
|
||||
|
||||
// EscapePath returns the escaped form of the given module path.
|
||||
// It fails if the module path is invalid.
|
||||
func EscapePath(path string) (escaped string, err error) {
|
||||
if err := CheckPath(path); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return escapeString(path)
|
||||
}
|
||||
|
||||
// EscapeVersion returns the escaped form of the given module version.
|
||||
// Versions are allowed to be in non-semver form but must be valid file names
|
||||
// and not contain exclamation marks.
|
||||
func EscapeVersion(v string) (escaped string, err error) {
|
||||
if err := checkElem(v, filePath); err != nil || strings.Contains(v, "!") {
|
||||
return "", &InvalidVersionError{
|
||||
Version: v,
|
||||
Err: fmt.Errorf("disallowed version string"),
|
||||
}
|
||||
}
|
||||
return escapeString(v)
|
||||
}
|
||||
|
||||
func escapeString(s string) (escaped string, err error) {
|
||||
haveUpper := false
|
||||
for _, r := range s {
|
||||
if r == '!' || r >= utf8.RuneSelf {
|
||||
// This should be disallowed by CheckPath, but diagnose anyway.
|
||||
// The correctness of the escaping loop below depends on it.
|
||||
return "", fmt.Errorf("internal error: inconsistency in EscapePath")
|
||||
}
|
||||
if 'A' <= r && r <= 'Z' {
|
||||
haveUpper = true
|
||||
}
|
||||
}
|
||||
|
||||
if !haveUpper {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
var buf []byte
|
||||
for _, r := range s {
|
||||
if 'A' <= r && r <= 'Z' {
|
||||
buf = append(buf, '!', byte(r+'a'-'A'))
|
||||
} else {
|
||||
buf = append(buf, byte(r))
|
||||
}
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
// UnescapePath returns the module path for the given escaped path.
|
||||
// It fails if the escaped path is invalid or describes an invalid path.
|
||||
func UnescapePath(escaped string) (path string, err error) {
|
||||
path, ok := unescapeString(escaped)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("invalid escaped module path %q", escaped)
|
||||
}
|
||||
if err := CheckPath(path); err != nil {
|
||||
return "", fmt.Errorf("invalid escaped module path %q: %v", escaped, err)
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// UnescapeVersion returns the version string for the given escaped version.
|
||||
// It fails if the escaped form is invalid or describes an invalid version.
|
||||
// Versions are allowed to be in non-semver form but must be valid file names
|
||||
// and not contain exclamation marks.
|
||||
func UnescapeVersion(escaped string) (v string, err error) {
|
||||
v, ok := unescapeString(escaped)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("invalid escaped version %q", escaped)
|
||||
}
|
||||
if err := checkElem(v, filePath); err != nil {
|
||||
return "", fmt.Errorf("invalid escaped version %q: %v", v, err)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func unescapeString(escaped string) (string, bool) {
|
||||
var buf []byte
|
||||
|
||||
bang := false
|
||||
for _, r := range escaped {
|
||||
if r >= utf8.RuneSelf {
|
||||
return "", false
|
||||
}
|
||||
if bang {
|
||||
bang = false
|
||||
if r < 'a' || 'z' < r {
|
||||
return "", false
|
||||
}
|
||||
buf = append(buf, byte(r+'A'-'a'))
|
||||
continue
|
||||
}
|
||||
if r == '!' {
|
||||
bang = true
|
||||
continue
|
||||
}
|
||||
if 'A' <= r && r <= 'Z' {
|
||||
return "", false
|
||||
}
|
||||
buf = append(buf, byte(r))
|
||||
}
|
||||
if bang {
|
||||
return "", false
|
||||
}
|
||||
return string(buf), true
|
||||
}
|
||||
|
||||
// MatchPrefixPatterns reports whether any path prefix of target matches one of
|
||||
// the glob patterns (as defined by [path.Match]) in the comma-separated globs
|
||||
// list. This implements the algorithm used when matching a module path to the
|
||||
// GOPRIVATE environment variable, as described by 'go help module-private'.
|
||||
//
|
||||
// It ignores any empty or malformed patterns in the list.
|
||||
// Trailing slashes on patterns are ignored.
|
||||
func MatchPrefixPatterns(globs, target string) bool {
|
||||
for globs != "" {
|
||||
// Extract next non-empty glob in comma-separated list.
|
||||
var glob string
|
||||
if before, after, ok := strings.Cut(globs, ","); ok {
|
||||
glob, globs = before, after
|
||||
} else {
|
||||
glob, globs = globs, ""
|
||||
}
|
||||
glob = strings.TrimSuffix(glob, "/")
|
||||
if glob == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// A glob with N+1 path elements (N slashes) needs to be matched
|
||||
// against the first N+1 path elements of target,
|
||||
// which end just before the N+1'th slash.
|
||||
n := strings.Count(glob, "/")
|
||||
prefix := target
|
||||
// Walk target, counting slashes, truncating at the N+1'th slash.
|
||||
for i := 0; i < len(target); i++ {
|
||||
if target[i] == '/' {
|
||||
if n == 0 {
|
||||
prefix = target[:i]
|
||||
break
|
||||
}
|
||||
n--
|
||||
}
|
||||
}
|
||||
if n > 0 {
|
||||
// Not enough prefix elements.
|
||||
continue
|
||||
}
|
||||
matched, _ := path.Match(glob, prefix)
|
||||
if matched {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
250
vendor/golang.org/x/mod/module/pseudo.go
generated
vendored
Normal file
250
vendor/golang.org/x/mod/module/pseudo.go
generated
vendored
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
// Copyright 2018 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.
|
||||
|
||||
// Pseudo-versions
|
||||
//
|
||||
// Code authors are expected to tag the revisions they want users to use,
|
||||
// including prereleases. However, not all authors tag versions at all,
|
||||
// and not all commits a user might want to try will have tags.
|
||||
// A pseudo-version is a version with a special form that allows us to
|
||||
// address an untagged commit and order that version with respect to
|
||||
// other versions we might encounter.
|
||||
//
|
||||
// A pseudo-version takes one of the general forms:
|
||||
//
|
||||
// (1) vX.0.0-yyyymmddhhmmss-abcdef123456
|
||||
// (2) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456
|
||||
// (3) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456+incompatible
|
||||
// (4) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456
|
||||
// (5) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456+incompatible
|
||||
//
|
||||
// If there is no recently tagged version with the right major version vX,
|
||||
// then form (1) is used, creating a space of pseudo-versions at the bottom
|
||||
// of the vX version range, less than any tagged version, including the unlikely v0.0.0.
|
||||
//
|
||||
// If the most recent tagged version before the target commit is vX.Y.Z or vX.Y.Z+incompatible,
|
||||
// then the pseudo-version uses form (2) or (3), making it a prerelease for the next
|
||||
// possible semantic version after vX.Y.Z. The leading 0 segment in the prerelease string
|
||||
// ensures that the pseudo-version compares less than possible future explicit prereleases
|
||||
// like vX.Y.(Z+1)-rc1 or vX.Y.(Z+1)-1.
|
||||
//
|
||||
// If the most recent tagged version before the target commit is vX.Y.Z-pre or vX.Y.Z-pre+incompatible,
|
||||
// then the pseudo-version uses form (4) or (5), making it a slightly later prerelease.
|
||||
|
||||
package module
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/mod/internal/lazyregexp"
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
var pseudoVersionRE = lazyregexp.New(`^v[0-9]+\.(0\.0-|\d+\.\d+-([^+]*\.)?0\.)\d{14}-[A-Za-z0-9]+(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$`)
|
||||
|
||||
const PseudoVersionTimestampFormat = "20060102150405"
|
||||
|
||||
// PseudoVersion returns a pseudo-version for the given major version ("v1")
|
||||
// preexisting older tagged version ("" or "v1.2.3" or "v1.2.3-pre"), revision time,
|
||||
// and revision identifier (usually a 12-byte commit hash prefix).
|
||||
func PseudoVersion(major, older string, t time.Time, rev string) string {
|
||||
if major == "" {
|
||||
major = "v0"
|
||||
}
|
||||
segment := fmt.Sprintf("%s-%s", t.UTC().Format(PseudoVersionTimestampFormat), rev)
|
||||
build := semver.Build(older)
|
||||
older = semver.Canonical(older)
|
||||
if older == "" {
|
||||
return major + ".0.0-" + segment // form (1)
|
||||
}
|
||||
if semver.Prerelease(older) != "" {
|
||||
return older + ".0." + segment + build // form (4), (5)
|
||||
}
|
||||
|
||||
// Form (2), (3).
|
||||
// Extract patch from vMAJOR.MINOR.PATCH
|
||||
i := strings.LastIndex(older, ".") + 1
|
||||
v, patch := older[:i], older[i:]
|
||||
|
||||
// Reassemble.
|
||||
return v + incDecimal(patch) + "-0." + segment + build
|
||||
}
|
||||
|
||||
// ZeroPseudoVersion returns a pseudo-version with a zero timestamp and
|
||||
// revision, which may be used as a placeholder.
|
||||
func ZeroPseudoVersion(major string) string {
|
||||
return PseudoVersion(major, "", time.Time{}, "000000000000")
|
||||
}
|
||||
|
||||
// incDecimal returns the decimal string incremented by 1.
|
||||
func incDecimal(decimal string) string {
|
||||
// Scan right to left turning 9s to 0s until you find a digit to increment.
|
||||
digits := []byte(decimal)
|
||||
i := len(digits) - 1
|
||||
for ; i >= 0 && digits[i] == '9'; i-- {
|
||||
digits[i] = '0'
|
||||
}
|
||||
if i >= 0 {
|
||||
digits[i]++
|
||||
} else {
|
||||
// digits is all zeros
|
||||
digits[0] = '1'
|
||||
digits = append(digits, '0')
|
||||
}
|
||||
return string(digits)
|
||||
}
|
||||
|
||||
// decDecimal returns the decimal string decremented by 1, or the empty string
|
||||
// if the decimal is all zeroes.
|
||||
func decDecimal(decimal string) string {
|
||||
// Scan right to left turning 0s to 9s until you find a digit to decrement.
|
||||
digits := []byte(decimal)
|
||||
i := len(digits) - 1
|
||||
for ; i >= 0 && digits[i] == '0'; i-- {
|
||||
digits[i] = '9'
|
||||
}
|
||||
if i < 0 {
|
||||
// decimal is all zeros
|
||||
return ""
|
||||
}
|
||||
if i == 0 && digits[i] == '1' && len(digits) > 1 {
|
||||
digits = digits[1:]
|
||||
} else {
|
||||
digits[i]--
|
||||
}
|
||||
return string(digits)
|
||||
}
|
||||
|
||||
// IsPseudoVersion reports whether v is a pseudo-version.
|
||||
func IsPseudoVersion(v string) bool {
|
||||
return strings.Count(v, "-") >= 2 && semver.IsValid(v) && pseudoVersionRE.MatchString(v)
|
||||
}
|
||||
|
||||
// IsZeroPseudoVersion returns whether v is a pseudo-version with a zero base,
|
||||
// timestamp, and revision, as returned by [ZeroPseudoVersion].
|
||||
func IsZeroPseudoVersion(v string) bool {
|
||||
return v == ZeroPseudoVersion(semver.Major(v))
|
||||
}
|
||||
|
||||
// PseudoVersionTime returns the time stamp of the pseudo-version v.
|
||||
// It returns an error if v is not a pseudo-version or if the time stamp
|
||||
// embedded in the pseudo-version is not a valid time.
|
||||
func PseudoVersionTime(v string) (time.Time, error) {
|
||||
_, timestamp, _, _, err := parsePseudoVersion(v)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
t, err := time.Parse("20060102150405", timestamp)
|
||||
if err != nil {
|
||||
return time.Time{}, &InvalidVersionError{
|
||||
Version: v,
|
||||
Pseudo: true,
|
||||
Err: fmt.Errorf("malformed time %q", timestamp),
|
||||
}
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// PseudoVersionRev returns the revision identifier of the pseudo-version v.
|
||||
// It returns an error if v is not a pseudo-version.
|
||||
func PseudoVersionRev(v string) (rev string, err error) {
|
||||
_, _, rev, _, err = parsePseudoVersion(v)
|
||||
return
|
||||
}
|
||||
|
||||
// PseudoVersionBase returns the canonical parent version, if any, upon which
|
||||
// the pseudo-version v is based.
|
||||
//
|
||||
// If v has no parent version (that is, if it is "vX.0.0-[…]"),
|
||||
// PseudoVersionBase returns the empty string and a nil error.
|
||||
func PseudoVersionBase(v string) (string, error) {
|
||||
base, _, _, build, err := parsePseudoVersion(v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch pre := semver.Prerelease(base); pre {
|
||||
case "":
|
||||
// vX.0.0-yyyymmddhhmmss-abcdef123456 → ""
|
||||
if build != "" {
|
||||
// Pseudo-versions of the form vX.0.0-yyyymmddhhmmss-abcdef123456+incompatible
|
||||
// are nonsensical: the "vX.0.0-" prefix implies that there is no parent tag,
|
||||
// but the "+incompatible" suffix implies that the major version of
|
||||
// the parent tag is not compatible with the module's import path.
|
||||
//
|
||||
// There are a few such entries in the index generated by proxy.golang.org,
|
||||
// but we believe those entries were generated by the proxy itself.
|
||||
return "", &InvalidVersionError{
|
||||
Version: v,
|
||||
Pseudo: true,
|
||||
Err: fmt.Errorf("lacks base version, but has build metadata %q", build),
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
|
||||
case "-0":
|
||||
// vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456 → vX.Y.Z
|
||||
// vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456+incompatible → vX.Y.Z+incompatible
|
||||
base = strings.TrimSuffix(base, pre)
|
||||
i := strings.LastIndexByte(base, '.')
|
||||
if i < 0 {
|
||||
panic("base from parsePseudoVersion missing patch number: " + base)
|
||||
}
|
||||
patch := decDecimal(base[i+1:])
|
||||
if patch == "" {
|
||||
// vX.0.0-0 is invalid, but has been observed in the wild in the index
|
||||
// generated by requests to proxy.golang.org.
|
||||
//
|
||||
// NOTE(bcmills): I cannot find a historical bug that accounts for
|
||||
// pseudo-versions of this form, nor have I seen such versions in any
|
||||
// actual go.mod files. If we find actual examples of this form and a
|
||||
// reasonable theory of how they came into existence, it seems fine to
|
||||
// treat them as equivalent to vX.0.0 (especially since the invalid
|
||||
// pseudo-versions have lower precedence than the real ones). For now, we
|
||||
// reject them.
|
||||
return "", &InvalidVersionError{
|
||||
Version: v,
|
||||
Pseudo: true,
|
||||
Err: fmt.Errorf("version before %s would have negative patch number", base),
|
||||
}
|
||||
}
|
||||
return base[:i+1] + patch + build, nil
|
||||
|
||||
default:
|
||||
// vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456 → vX.Y.Z-pre
|
||||
// vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456+incompatible → vX.Y.Z-pre+incompatible
|
||||
if !strings.HasSuffix(base, ".0") {
|
||||
panic(`base from parsePseudoVersion missing ".0" before date: ` + base)
|
||||
}
|
||||
return strings.TrimSuffix(base, ".0") + build, nil
|
||||
}
|
||||
}
|
||||
|
||||
var errPseudoSyntax = errors.New("syntax error")
|
||||
|
||||
func parsePseudoVersion(v string) (base, timestamp, rev, build string, err error) {
|
||||
if !IsPseudoVersion(v) {
|
||||
return "", "", "", "", &InvalidVersionError{
|
||||
Version: v,
|
||||
Pseudo: true,
|
||||
Err: errPseudoSyntax,
|
||||
}
|
||||
}
|
||||
build = semver.Build(v)
|
||||
v = strings.TrimSuffix(v, build)
|
||||
j := strings.LastIndex(v, "-")
|
||||
v, rev = v[:j], v[j+1:]
|
||||
i := strings.LastIndex(v, "-")
|
||||
if j := strings.LastIndex(v, "."); j > i {
|
||||
base = v[:j] // "vX.Y.Z-pre.0" or "vX.Y.(Z+1)-0"
|
||||
timestamp = v[j+1:]
|
||||
} else {
|
||||
base = v[:i] // "vX.0.0"
|
||||
timestamp = v[i+1:]
|
||||
}
|
||||
return base, timestamp, rev, build, nil
|
||||
}
|
||||
407
vendor/golang.org/x/mod/semver/semver.go
generated
vendored
Normal file
407
vendor/golang.org/x/mod/semver/semver.go
generated
vendored
Normal file
|
|
@ -0,0 +1,407 @@
|
|||
// Copyright 2018 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 semver implements comparison of semantic version strings.
|
||||
// In this package, semantic version strings must begin with a leading "v",
|
||||
// as in "v1.0.0".
|
||||
//
|
||||
// The general form of a semantic version string accepted by this package is
|
||||
//
|
||||
// vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]]
|
||||
//
|
||||
// where square brackets indicate optional parts of the syntax;
|
||||
// MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros;
|
||||
// PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers
|
||||
// using only alphanumeric characters and hyphens; and
|
||||
// all-numeric PRERELEASE identifiers must not have leading zeros.
|
||||
//
|
||||
// This package follows Semantic Versioning 2.0.0 (see semver.org)
|
||||
// with two exceptions. First, it requires the "v" prefix. Second, it recognizes
|
||||
// vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes)
|
||||
// as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0.
|
||||
package semver
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// parsed returns the parsed form of a semantic version string.
|
||||
type parsed struct {
|
||||
major string
|
||||
minor string
|
||||
patch string
|
||||
short string
|
||||
prerelease string
|
||||
build string
|
||||
}
|
||||
|
||||
// IsValid reports whether v is a valid semantic version string.
|
||||
func IsValid(v string) bool {
|
||||
_, ok := parse(v)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Canonical returns the canonical formatting of the semantic version v.
|
||||
// It fills in any missing .MINOR or .PATCH and discards build metadata.
|
||||
// Two semantic versions compare equal only if their canonical formatting
|
||||
// is an identical string.
|
||||
// The canonical invalid semantic version is the empty string.
|
||||
func Canonical(v string) string {
|
||||
p, ok := parse(v)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
if p.build != "" {
|
||||
return v[:len(v)-len(p.build)]
|
||||
}
|
||||
if p.short != "" {
|
||||
return v + p.short
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Major returns the major version prefix of the semantic version v.
|
||||
// For example, Major("v2.1.0") == "v2".
|
||||
// If v is an invalid semantic version string, Major returns the empty string.
|
||||
func Major(v string) string {
|
||||
pv, ok := parse(v)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return v[:1+len(pv.major)]
|
||||
}
|
||||
|
||||
// MajorMinor returns the major.minor version prefix of the semantic version v.
|
||||
// For example, MajorMinor("v2.1.0") == "v2.1".
|
||||
// If v is an invalid semantic version string, MajorMinor returns the empty string.
|
||||
func MajorMinor(v string) string {
|
||||
pv, ok := parse(v)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
i := 1 + len(pv.major)
|
||||
if j := i + 1 + len(pv.minor); j <= len(v) && v[i] == '.' && v[i+1:j] == pv.minor {
|
||||
return v[:j]
|
||||
}
|
||||
return v[:i] + "." + pv.minor
|
||||
}
|
||||
|
||||
// Prerelease returns the prerelease suffix of the semantic version v.
|
||||
// For example, Prerelease("v2.1.0-pre+meta") == "-pre".
|
||||
// If v is an invalid semantic version string, Prerelease returns the empty string.
|
||||
func Prerelease(v string) string {
|
||||
pv, ok := parse(v)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return pv.prerelease
|
||||
}
|
||||
|
||||
// Build returns the build suffix of the semantic version v.
|
||||
// For example, Build("v2.1.0+meta") == "+meta".
|
||||
// If v is an invalid semantic version string, Build returns the empty string.
|
||||
func Build(v string) string {
|
||||
pv, ok := parse(v)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return pv.build
|
||||
}
|
||||
|
||||
// Compare returns an integer comparing two versions according to
|
||||
// semantic version precedence.
|
||||
// The result will be 0 if v == w, -1 if v < w, or +1 if v > w.
|
||||
//
|
||||
// An invalid semantic version string is considered less than a valid one.
|
||||
// All invalid semantic version strings compare equal to each other.
|
||||
func Compare(v, w string) int {
|
||||
pv, ok1 := parse(v)
|
||||
pw, ok2 := parse(w)
|
||||
if !ok1 && !ok2 {
|
||||
return 0
|
||||
}
|
||||
if !ok1 {
|
||||
return -1
|
||||
}
|
||||
if !ok2 {
|
||||
return +1
|
||||
}
|
||||
if c := compareInt(pv.major, pw.major); c != 0 {
|
||||
return c
|
||||
}
|
||||
if c := compareInt(pv.minor, pw.minor); c != 0 {
|
||||
return c
|
||||
}
|
||||
if c := compareInt(pv.patch, pw.patch); c != 0 {
|
||||
return c
|
||||
}
|
||||
return comparePrerelease(pv.prerelease, pw.prerelease)
|
||||
}
|
||||
|
||||
// Max canonicalizes its arguments and then returns the version string
|
||||
// that compares greater.
|
||||
//
|
||||
// Deprecated: use [Compare] instead. In most cases, returning a canonicalized
|
||||
// version is not expected or desired.
|
||||
func Max(v, w string) string {
|
||||
v = Canonical(v)
|
||||
w = Canonical(w)
|
||||
if Compare(v, w) > 0 {
|
||||
return v
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
// ByVersion implements [sort.Interface] for sorting semantic version strings.
|
||||
type ByVersion []string
|
||||
|
||||
func (vs ByVersion) Len() int { return len(vs) }
|
||||
func (vs ByVersion) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
|
||||
func (vs ByVersion) Less(i, j int) bool { return compareVersion(vs[i], vs[j]) < 0 }
|
||||
|
||||
// Sort sorts a list of semantic version strings using [Compare] and falls back
|
||||
// to use [strings.Compare] if both versions are considered equal.
|
||||
func Sort(list []string) {
|
||||
slices.SortFunc(list, compareVersion)
|
||||
}
|
||||
|
||||
func compareVersion(a, b string) int {
|
||||
cmp := Compare(a, b)
|
||||
if cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
return strings.Compare(a, b)
|
||||
}
|
||||
|
||||
func parse(v string) (p parsed, ok bool) {
|
||||
if v == "" || v[0] != 'v' {
|
||||
return
|
||||
}
|
||||
p.major, v, ok = parseInt(v[1:])
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if v == "" {
|
||||
p.minor = "0"
|
||||
p.patch = "0"
|
||||
p.short = ".0.0"
|
||||
return
|
||||
}
|
||||
if v[0] != '.' {
|
||||
ok = false
|
||||
return
|
||||
}
|
||||
p.minor, v, ok = parseInt(v[1:])
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if v == "" {
|
||||
p.patch = "0"
|
||||
p.short = ".0"
|
||||
return
|
||||
}
|
||||
if v[0] != '.' {
|
||||
ok = false
|
||||
return
|
||||
}
|
||||
p.patch, v, ok = parseInt(v[1:])
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if len(v) > 0 && v[0] == '-' {
|
||||
p.prerelease, v, ok = parsePrerelease(v)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(v) > 0 && v[0] == '+' {
|
||||
p.build, v, ok = parseBuild(v)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
if v != "" {
|
||||
ok = false
|
||||
return
|
||||
}
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
func parseInt(v string) (t, rest string, ok bool) {
|
||||
if v == "" {
|
||||
return
|
||||
}
|
||||
if v[0] < '0' || '9' < v[0] {
|
||||
return
|
||||
}
|
||||
i := 1
|
||||
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
|
||||
i++
|
||||
}
|
||||
if v[0] == '0' && i != 1 {
|
||||
return
|
||||
}
|
||||
return v[:i], v[i:], true
|
||||
}
|
||||
|
||||
func parsePrerelease(v string) (t, rest string, ok bool) {
|
||||
// "A pre-release version MAY be denoted by appending a hyphen and
|
||||
// a series of dot separated identifiers immediately following the patch version.
|
||||
// Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-].
|
||||
// Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes."
|
||||
if v == "" || v[0] != '-' {
|
||||
return
|
||||
}
|
||||
i := 1
|
||||
start := 1
|
||||
for i < len(v) && v[i] != '+' {
|
||||
if !isIdentChar(v[i]) && v[i] != '.' {
|
||||
return
|
||||
}
|
||||
if v[i] == '.' {
|
||||
if start == i || isBadNum(v[start:i]) {
|
||||
return
|
||||
}
|
||||
start = i + 1
|
||||
}
|
||||
i++
|
||||
}
|
||||
if start == i || isBadNum(v[start:i]) {
|
||||
return
|
||||
}
|
||||
return v[:i], v[i:], true
|
||||
}
|
||||
|
||||
func parseBuild(v string) (t, rest string, ok bool) {
|
||||
if v == "" || v[0] != '+' {
|
||||
return
|
||||
}
|
||||
i := 1
|
||||
start := 1
|
||||
for i < len(v) {
|
||||
if !isIdentChar(v[i]) && v[i] != '.' {
|
||||
return
|
||||
}
|
||||
if v[i] == '.' {
|
||||
if start == i {
|
||||
return
|
||||
}
|
||||
start = i + 1
|
||||
}
|
||||
i++
|
||||
}
|
||||
if start == i {
|
||||
return
|
||||
}
|
||||
return v[:i], v[i:], true
|
||||
}
|
||||
|
||||
func isIdentChar(c byte) bool {
|
||||
return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-'
|
||||
}
|
||||
|
||||
func isBadNum(v string) bool {
|
||||
i := 0
|
||||
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
|
||||
i++
|
||||
}
|
||||
return i == len(v) && i > 1 && v[0] == '0'
|
||||
}
|
||||
|
||||
func isNum(v string) bool {
|
||||
i := 0
|
||||
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
|
||||
i++
|
||||
}
|
||||
return i == len(v)
|
||||
}
|
||||
|
||||
func compareInt(x, y string) int {
|
||||
if x == y {
|
||||
return 0
|
||||
}
|
||||
if len(x) < len(y) {
|
||||
return -1
|
||||
}
|
||||
if len(x) > len(y) {
|
||||
return +1
|
||||
}
|
||||
if x < y {
|
||||
return -1
|
||||
} else {
|
||||
return +1
|
||||
}
|
||||
}
|
||||
|
||||
func comparePrerelease(x, y string) int {
|
||||
// "When major, minor, and patch are equal, a pre-release version has
|
||||
// lower precedence than a normal version.
|
||||
// Example: 1.0.0-alpha < 1.0.0.
|
||||
// Precedence for two pre-release versions with the same major, minor,
|
||||
// and patch version MUST be determined by comparing each dot separated
|
||||
// identifier from left to right until a difference is found as follows:
|
||||
// identifiers consisting of only digits are compared numerically and
|
||||
// identifiers with letters or hyphens are compared lexically in ASCII
|
||||
// sort order. Numeric identifiers always have lower precedence than
|
||||
// non-numeric identifiers. A larger set of pre-release fields has a
|
||||
// higher precedence than a smaller set, if all of the preceding
|
||||
// identifiers are equal.
|
||||
// Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta <
|
||||
// 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0."
|
||||
if x == y {
|
||||
return 0
|
||||
}
|
||||
if x == "" {
|
||||
return +1
|
||||
}
|
||||
if y == "" {
|
||||
return -1
|
||||
}
|
||||
for x != "" && y != "" {
|
||||
x = x[1:] // skip - or .
|
||||
y = y[1:] // skip - or .
|
||||
var dx, dy string
|
||||
dx, x = nextIdent(x)
|
||||
dy, y = nextIdent(y)
|
||||
if dx != dy {
|
||||
ix := isNum(dx)
|
||||
iy := isNum(dy)
|
||||
if ix != iy {
|
||||
if ix {
|
||||
return -1
|
||||
} else {
|
||||
return +1
|
||||
}
|
||||
}
|
||||
if ix {
|
||||
if len(dx) < len(dy) {
|
||||
return -1
|
||||
}
|
||||
if len(dx) > len(dy) {
|
||||
return +1
|
||||
}
|
||||
}
|
||||
if dx < dy {
|
||||
return -1
|
||||
} else {
|
||||
return +1
|
||||
}
|
||||
}
|
||||
}
|
||||
if x == "" {
|
||||
return -1
|
||||
} else {
|
||||
return +1
|
||||
}
|
||||
}
|
||||
|
||||
func nextIdent(x string) (dx, rest string) {
|
||||
i := 0
|
||||
for i < len(x) && x[i] != '.' {
|
||||
i++
|
||||
}
|
||||
return x[:i], x[i:]
|
||||
}
|
||||
160
vendor/golang.org/x/sync/semaphore/semaphore.go
generated
vendored
Normal file
160
vendor/golang.org/x/sync/semaphore/semaphore.go
generated
vendored
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
// Copyright 2017 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 semaphore provides a weighted semaphore implementation.
|
||||
package semaphore // import "golang.org/x/sync/semaphore"
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type waiter struct {
|
||||
n int64
|
||||
ready chan<- struct{} // Closed when semaphore acquired.
|
||||
}
|
||||
|
||||
// NewWeighted creates a new weighted semaphore with the given
|
||||
// maximum combined weight for concurrent access.
|
||||
func NewWeighted(n int64) *Weighted {
|
||||
w := &Weighted{size: n}
|
||||
return w
|
||||
}
|
||||
|
||||
// Weighted provides a way to bound concurrent access to a resource.
|
||||
// The callers can request access with a given weight.
|
||||
type Weighted struct {
|
||||
size int64
|
||||
cur int64
|
||||
mu sync.Mutex
|
||||
waiters list.List
|
||||
}
|
||||
|
||||
// Acquire acquires the semaphore with a weight of n, blocking until resources
|
||||
// are available or ctx is done. On success, returns nil. On failure, returns
|
||||
// ctx.Err() and leaves the semaphore unchanged.
|
||||
func (s *Weighted) Acquire(ctx context.Context, n int64) error {
|
||||
done := ctx.Done()
|
||||
|
||||
s.mu.Lock()
|
||||
select {
|
||||
case <-done:
|
||||
// ctx becoming done has "happened before" acquiring the semaphore,
|
||||
// whether it became done before the call began or while we were
|
||||
// waiting for the mutex. We prefer to fail even if we could acquire
|
||||
// the mutex without blocking.
|
||||
s.mu.Unlock()
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
if s.size-s.cur >= n && s.waiters.Len() == 0 {
|
||||
// Since we hold s.mu and haven't synchronized since checking done, if
|
||||
// ctx becomes done before we return here, it becoming done must have
|
||||
// "happened concurrently" with this call - it cannot "happen before"
|
||||
// we return in this branch. So, we're ok to always acquire here.
|
||||
s.cur += n
|
||||
s.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
if n > s.size {
|
||||
// Don't make other Acquire calls block on one that's doomed to fail.
|
||||
s.mu.Unlock()
|
||||
<-done
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
ready := make(chan struct{})
|
||||
w := waiter{n: n, ready: ready}
|
||||
elem := s.waiters.PushBack(w)
|
||||
s.mu.Unlock()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
s.mu.Lock()
|
||||
select {
|
||||
case <-ready:
|
||||
// Acquired the semaphore after we were canceled.
|
||||
// Pretend we didn't and put the tokens back.
|
||||
s.cur -= n
|
||||
s.notifyWaiters()
|
||||
default:
|
||||
isFront := s.waiters.Front() == elem
|
||||
s.waiters.Remove(elem)
|
||||
// If we're at the front and there are extra tokens left, notify other waiters.
|
||||
if isFront && s.size > s.cur {
|
||||
s.notifyWaiters()
|
||||
}
|
||||
}
|
||||
s.mu.Unlock()
|
||||
return ctx.Err()
|
||||
|
||||
case <-ready:
|
||||
// Acquired the semaphore. Check that ctx isn't already done.
|
||||
// We check the done channel instead of calling ctx.Err because we
|
||||
// already have the channel, and ctx.Err is O(n) with the nesting
|
||||
// depth of ctx.
|
||||
select {
|
||||
case <-done:
|
||||
s.Release(n)
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// TryAcquire acquires the semaphore with a weight of n without blocking.
|
||||
// On success, returns true. On failure, returns false and leaves the semaphore unchanged.
|
||||
func (s *Weighted) TryAcquire(n int64) bool {
|
||||
s.mu.Lock()
|
||||
success := s.size-s.cur >= n && s.waiters.Len() == 0
|
||||
if success {
|
||||
s.cur += n
|
||||
}
|
||||
s.mu.Unlock()
|
||||
return success
|
||||
}
|
||||
|
||||
// Release releases the semaphore with a weight of n.
|
||||
func (s *Weighted) Release(n int64) {
|
||||
s.mu.Lock()
|
||||
s.cur -= n
|
||||
if s.cur < 0 {
|
||||
s.mu.Unlock()
|
||||
panic("semaphore: released more than held")
|
||||
}
|
||||
s.notifyWaiters()
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *Weighted) notifyWaiters() {
|
||||
for {
|
||||
next := s.waiters.Front()
|
||||
if next == nil {
|
||||
break // No more waiters blocked.
|
||||
}
|
||||
|
||||
w := next.Value.(waiter)
|
||||
if s.size-s.cur < w.n {
|
||||
// Not enough tokens for the next waiter. We could keep going (to try to
|
||||
// find a waiter with a smaller request), but under load that could cause
|
||||
// starvation for large requests; instead, we leave all remaining waiters
|
||||
// blocked.
|
||||
//
|
||||
// Consider a semaphore used as a read-write lock, with N tokens, N
|
||||
// readers, and one writer. Each reader can Acquire(1) to obtain a read
|
||||
// lock. The writer can Acquire(N) to obtain a write lock, excluding all
|
||||
// of the readers. If we allow the readers to jump ahead in the queue,
|
||||
// the writer will starve — there is always one token available for every
|
||||
// reader.
|
||||
break
|
||||
}
|
||||
|
||||
s.cur += w.n
|
||||
s.waiters.Remove(next)
|
||||
close(w.ready)
|
||||
}
|
||||
}
|
||||
27
vendor/golang.org/x/tools/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/tools/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
Copyright 2009 The Go Authors.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google LLC nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
22
vendor/golang.org/x/tools/PATENTS
generated
vendored
Normal file
22
vendor/golang.org/x/tools/PATENTS
generated
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
||||
663
vendor/golang.org/x/tools/go/ast/astutil/enclosing.go
generated
vendored
Normal file
663
vendor/golang.org/x/tools/go/ast/astutil/enclosing.go
generated
vendored
Normal file
|
|
@ -0,0 +1,663 @@
|
|||
// Copyright 2013 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 astutil
|
||||
|
||||
// This file defines utilities for working with source positions.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// PathEnclosingInterval returns the node that encloses the source
|
||||
// interval [start, end), and all its ancestors up to the AST root.
|
||||
//
|
||||
// The definition of "enclosing" used by this function considers
|
||||
// additional whitespace abutting a node to be enclosed by it.
|
||||
// In this example:
|
||||
//
|
||||
// z := x + y // add them
|
||||
// <-A->
|
||||
// <----B----->
|
||||
//
|
||||
// the ast.BinaryExpr(+) node is considered to enclose interval B
|
||||
// even though its [Pos()..End()) is actually only interval A.
|
||||
// This behaviour makes user interfaces more tolerant of imperfect
|
||||
// input.
|
||||
//
|
||||
// This function treats tokens as nodes, though they are not included
|
||||
// in the result. e.g. PathEnclosingInterval("+") returns the
|
||||
// enclosing ast.BinaryExpr("x + y").
|
||||
//
|
||||
// If start==end, the 1-char interval following start is used instead.
|
||||
//
|
||||
// The 'exact' result is true if the interval contains only path[0]
|
||||
// and perhaps some adjacent whitespace. It is false if the interval
|
||||
// overlaps multiple children of path[0], or if it contains only
|
||||
// interior whitespace of path[0].
|
||||
// In this example:
|
||||
//
|
||||
// z := x + y // add them
|
||||
// <--C--> <---E-->
|
||||
// ^
|
||||
// D
|
||||
//
|
||||
// intervals C, D and E are inexact. C is contained by the
|
||||
// z-assignment statement, because it spans three of its children (:=,
|
||||
// x, +). So too is the 1-char interval D, because it contains only
|
||||
// interior whitespace of the assignment. E is considered interior
|
||||
// whitespace of the BlockStmt containing the assignment.
|
||||
//
|
||||
// The resulting path is never empty; it always contains at least the
|
||||
// 'root' *ast.File. Ideally PathEnclosingInterval would reject
|
||||
// intervals that lie wholly or partially outside the range of the
|
||||
// file, but unfortunately ast.File records only the token.Pos of
|
||||
// the 'package' keyword, but not of the start of the file itself.
|
||||
func PathEnclosingInterval(root *ast.File, start, end token.Pos) (path []ast.Node, exact bool) {
|
||||
// fmt.Printf("EnclosingInterval %d %d\n", start, end) // debugging
|
||||
|
||||
// Precondition: node.[Pos..End) and adjoining whitespace contain [start, end).
|
||||
var visit func(node ast.Node) bool
|
||||
visit = func(node ast.Node) bool {
|
||||
path = append(path, node)
|
||||
|
||||
nodePos := node.Pos()
|
||||
nodeEnd := node.End()
|
||||
|
||||
// fmt.Printf("visit(%T, %d, %d)\n", node, nodePos, nodeEnd) // debugging
|
||||
|
||||
// Intersect [start, end) with interval of node.
|
||||
if start < nodePos {
|
||||
start = nodePos
|
||||
}
|
||||
if end > nodeEnd {
|
||||
end = nodeEnd
|
||||
}
|
||||
|
||||
// Find sole child that contains [start, end).
|
||||
children := childrenOf(node)
|
||||
l := len(children)
|
||||
for i, child := range children {
|
||||
// [childPos, childEnd) is unaugmented interval of child.
|
||||
childPos := child.Pos()
|
||||
childEnd := child.End()
|
||||
|
||||
// [augPos, augEnd) is whitespace-augmented interval of child.
|
||||
augPos := childPos
|
||||
augEnd := childEnd
|
||||
if i > 0 {
|
||||
augPos = children[i-1].End() // start of preceding whitespace
|
||||
}
|
||||
if i < l-1 {
|
||||
nextChildPos := children[i+1].Pos()
|
||||
// Does [start, end) lie between child and next child?
|
||||
if start >= augEnd && end <= nextChildPos {
|
||||
return false // inexact match
|
||||
}
|
||||
augEnd = nextChildPos // end of following whitespace
|
||||
}
|
||||
|
||||
// fmt.Printf("\tchild %d: [%d..%d)\tcontains interval [%d..%d)?\n",
|
||||
// i, augPos, augEnd, start, end) // debugging
|
||||
|
||||
// Does augmented child strictly contain [start, end)?
|
||||
if augPos <= start && end <= augEnd {
|
||||
if is[tokenNode](child) {
|
||||
return true
|
||||
}
|
||||
|
||||
// childrenOf elides the FuncType node beneath FuncDecl.
|
||||
// Add it back here for TypeParams, Params, Results,
|
||||
// all FieldLists). But we don't add it back for the "func" token
|
||||
// even though it is the tree at FuncDecl.Type.Func.
|
||||
if decl, ok := node.(*ast.FuncDecl); ok {
|
||||
if fields, ok := child.(*ast.FieldList); ok && fields != decl.Recv {
|
||||
path = append(path, decl.Type)
|
||||
}
|
||||
}
|
||||
|
||||
return visit(child)
|
||||
}
|
||||
|
||||
// Does [start, end) overlap multiple children?
|
||||
// i.e. left-augmented child contains start
|
||||
// but LR-augmented child does not contain end.
|
||||
if start < childEnd && end > augEnd {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// No single child contained [start, end),
|
||||
// so node is the result. Is it exact?
|
||||
|
||||
// (It's tempting to put this condition before the
|
||||
// child loop, but it gives the wrong result in the
|
||||
// case where a node (e.g. ExprStmt) and its sole
|
||||
// child have equal intervals.)
|
||||
if start == nodePos && end == nodeEnd {
|
||||
return true // exact match
|
||||
}
|
||||
|
||||
return false // inexact: overlaps multiple children
|
||||
}
|
||||
|
||||
// Ensure [start,end) is nondecreasing.
|
||||
if start > end {
|
||||
start, end = end, start
|
||||
}
|
||||
|
||||
if start < root.End() && end > root.Pos() {
|
||||
if start == end {
|
||||
end = start + 1 // empty interval => interval of size 1
|
||||
}
|
||||
exact = visit(root)
|
||||
|
||||
// Reverse the path:
|
||||
for i, l := 0, len(path); i < l/2; i++ {
|
||||
path[i], path[l-1-i] = path[l-1-i], path[i]
|
||||
}
|
||||
} else {
|
||||
// Selection lies within whitespace preceding the
|
||||
// first (or following the last) declaration in the file.
|
||||
// The result nonetheless always includes the ast.File.
|
||||
path = append(path, root)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// tokenNode is a dummy implementation of ast.Node for a single token.
|
||||
// They are used transiently by PathEnclosingInterval but never escape
|
||||
// this package.
|
||||
type tokenNode struct {
|
||||
pos token.Pos
|
||||
end token.Pos
|
||||
}
|
||||
|
||||
func (n tokenNode) Pos() token.Pos {
|
||||
return n.pos
|
||||
}
|
||||
|
||||
func (n tokenNode) End() token.Pos {
|
||||
return n.end
|
||||
}
|
||||
|
||||
func tok(pos token.Pos, len int) ast.Node {
|
||||
return tokenNode{pos, pos + token.Pos(len)}
|
||||
}
|
||||
|
||||
// childrenOf returns the direct non-nil children of ast.Node n.
|
||||
// It may include fake ast.Node implementations for bare tokens.
|
||||
// it is not safe to call (e.g.) ast.Walk on such nodes.
|
||||
func childrenOf(n ast.Node) []ast.Node {
|
||||
var children []ast.Node
|
||||
|
||||
// First add nodes for all true subtrees.
|
||||
ast.Inspect(n, func(node ast.Node) bool {
|
||||
if node == n { // push n
|
||||
return true // recur
|
||||
}
|
||||
if node != nil { // push child
|
||||
children = append(children, node)
|
||||
}
|
||||
return false // no recursion
|
||||
})
|
||||
|
||||
// TODO(adonovan): be more careful about missing (!Pos.Valid)
|
||||
// tokens in trees produced from invalid input.
|
||||
|
||||
// Then add fake Nodes for bare tokens.
|
||||
switch n := n.(type) {
|
||||
case *ast.ArrayType:
|
||||
children = append(children,
|
||||
tok(n.Lbrack, len("[")),
|
||||
tok(n.Elt.End(), len("]")))
|
||||
|
||||
case *ast.AssignStmt:
|
||||
children = append(children,
|
||||
tok(n.TokPos, len(n.Tok.String())))
|
||||
|
||||
case *ast.BasicLit:
|
||||
children = append(children,
|
||||
tok(n.ValuePos, len(n.Value)))
|
||||
|
||||
case *ast.BinaryExpr:
|
||||
children = append(children, tok(n.OpPos, len(n.Op.String())))
|
||||
|
||||
case *ast.BlockStmt:
|
||||
if n.Lbrace.IsValid() {
|
||||
children = append(children, tok(n.Lbrace, len("{")))
|
||||
}
|
||||
if n.Rbrace.IsValid() {
|
||||
children = append(children, tok(n.Rbrace, len("}")))
|
||||
}
|
||||
|
||||
case *ast.BranchStmt:
|
||||
children = append(children,
|
||||
tok(n.TokPos, len(n.Tok.String())))
|
||||
|
||||
case *ast.CallExpr:
|
||||
children = append(children,
|
||||
tok(n.Lparen, len("(")),
|
||||
tok(n.Rparen, len(")")))
|
||||
if n.Ellipsis != 0 {
|
||||
children = append(children, tok(n.Ellipsis, len("...")))
|
||||
}
|
||||
|
||||
case *ast.CaseClause:
|
||||
if n.List == nil {
|
||||
children = append(children,
|
||||
tok(n.Case, len("default")))
|
||||
} else {
|
||||
children = append(children,
|
||||
tok(n.Case, len("case")))
|
||||
}
|
||||
children = append(children, tok(n.Colon, len(":")))
|
||||
|
||||
case *ast.ChanType:
|
||||
switch n.Dir {
|
||||
case ast.RECV:
|
||||
children = append(children, tok(n.Begin, len("<-chan")))
|
||||
case ast.SEND:
|
||||
children = append(children, tok(n.Begin, len("chan<-")))
|
||||
case ast.RECV | ast.SEND:
|
||||
children = append(children, tok(n.Begin, len("chan")))
|
||||
}
|
||||
|
||||
case *ast.CommClause:
|
||||
if n.Comm == nil {
|
||||
children = append(children,
|
||||
tok(n.Case, len("default")))
|
||||
} else {
|
||||
children = append(children,
|
||||
tok(n.Case, len("case")))
|
||||
}
|
||||
children = append(children, tok(n.Colon, len(":")))
|
||||
|
||||
case *ast.Comment:
|
||||
// nop
|
||||
|
||||
case *ast.CommentGroup:
|
||||
// nop
|
||||
|
||||
case *ast.CompositeLit:
|
||||
children = append(children,
|
||||
tok(n.Lbrace, len("{")),
|
||||
tok(n.Rbrace, len("{")))
|
||||
|
||||
case *ast.DeclStmt:
|
||||
// nop
|
||||
|
||||
case *ast.DeferStmt:
|
||||
children = append(children,
|
||||
tok(n.Defer, len("defer")))
|
||||
|
||||
case *ast.Ellipsis:
|
||||
children = append(children,
|
||||
tok(n.Ellipsis, len("...")))
|
||||
|
||||
case *ast.EmptyStmt:
|
||||
// nop
|
||||
|
||||
case *ast.ExprStmt:
|
||||
// nop
|
||||
|
||||
case *ast.Field:
|
||||
// TODO(adonovan): Field.{Doc,Comment,Tag}?
|
||||
|
||||
case *ast.FieldList:
|
||||
if n.Opening.IsValid() {
|
||||
children = append(children, tok(n.Opening, len("(")))
|
||||
}
|
||||
if n.Closing.IsValid() {
|
||||
children = append(children, tok(n.Closing, len(")")))
|
||||
}
|
||||
|
||||
case *ast.File:
|
||||
// TODO test: Doc
|
||||
children = append(children,
|
||||
tok(n.Package, len("package")))
|
||||
|
||||
case *ast.ForStmt:
|
||||
children = append(children,
|
||||
tok(n.For, len("for")))
|
||||
|
||||
case *ast.FuncDecl:
|
||||
// TODO(adonovan): FuncDecl.Comment?
|
||||
|
||||
// Uniquely, FuncDecl breaks the invariant that
|
||||
// preorder traversal yields tokens in lexical order:
|
||||
// in fact, FuncDecl.Recv precedes FuncDecl.Type.Func.
|
||||
//
|
||||
// As a workaround, we inline the case for FuncType
|
||||
// here and order things correctly.
|
||||
// We also need to insert the elided FuncType just
|
||||
// before the 'visit' recursion.
|
||||
//
|
||||
children = nil // discard ast.Walk(FuncDecl) info subtrees
|
||||
children = append(children, tok(n.Type.Func, len("func")))
|
||||
if n.Recv != nil {
|
||||
children = append(children, n.Recv)
|
||||
}
|
||||
children = append(children, n.Name)
|
||||
if tparams := n.Type.TypeParams; tparams != nil {
|
||||
children = append(children, tparams)
|
||||
}
|
||||
if n.Type.Params != nil {
|
||||
children = append(children, n.Type.Params)
|
||||
}
|
||||
if n.Type.Results != nil {
|
||||
children = append(children, n.Type.Results)
|
||||
}
|
||||
if n.Body != nil {
|
||||
children = append(children, n.Body)
|
||||
}
|
||||
|
||||
case *ast.FuncLit:
|
||||
// nop
|
||||
|
||||
case *ast.FuncType:
|
||||
if n.Func != 0 {
|
||||
children = append(children,
|
||||
tok(n.Func, len("func")))
|
||||
}
|
||||
|
||||
case *ast.GenDecl:
|
||||
children = append(children,
|
||||
tok(n.TokPos, len(n.Tok.String())))
|
||||
if n.Lparen != 0 {
|
||||
children = append(children,
|
||||
tok(n.Lparen, len("(")),
|
||||
tok(n.Rparen, len(")")))
|
||||
}
|
||||
|
||||
case *ast.GoStmt:
|
||||
children = append(children,
|
||||
tok(n.Go, len("go")))
|
||||
|
||||
case *ast.Ident:
|
||||
children = append(children,
|
||||
tok(n.NamePos, len(n.Name)))
|
||||
|
||||
case *ast.IfStmt:
|
||||
children = append(children,
|
||||
tok(n.If, len("if")))
|
||||
|
||||
case *ast.ImportSpec:
|
||||
// TODO(adonovan): ImportSpec.{Doc,EndPos}?
|
||||
|
||||
case *ast.IncDecStmt:
|
||||
children = append(children,
|
||||
tok(n.TokPos, len(n.Tok.String())))
|
||||
|
||||
case *ast.IndexExpr:
|
||||
children = append(children,
|
||||
tok(n.Lbrack, len("[")),
|
||||
tok(n.Rbrack, len("]")))
|
||||
|
||||
case *ast.IndexListExpr:
|
||||
children = append(children,
|
||||
tok(n.Lbrack, len("[")),
|
||||
tok(n.Rbrack, len("]")))
|
||||
|
||||
case *ast.InterfaceType:
|
||||
children = append(children,
|
||||
tok(n.Interface, len("interface")))
|
||||
|
||||
case *ast.KeyValueExpr:
|
||||
children = append(children,
|
||||
tok(n.Colon, len(":")))
|
||||
|
||||
case *ast.LabeledStmt:
|
||||
children = append(children,
|
||||
tok(n.Colon, len(":")))
|
||||
|
||||
case *ast.MapType:
|
||||
children = append(children,
|
||||
tok(n.Map, len("map")))
|
||||
|
||||
case *ast.ParenExpr:
|
||||
children = append(children,
|
||||
tok(n.Lparen, len("(")),
|
||||
tok(n.Rparen, len(")")))
|
||||
|
||||
case *ast.RangeStmt:
|
||||
children = append(children,
|
||||
tok(n.For, len("for")),
|
||||
tok(n.TokPos, len(n.Tok.String())))
|
||||
|
||||
case *ast.ReturnStmt:
|
||||
children = append(children,
|
||||
tok(n.Return, len("return")))
|
||||
|
||||
case *ast.SelectStmt:
|
||||
children = append(children,
|
||||
tok(n.Select, len("select")))
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
// nop
|
||||
|
||||
case *ast.SendStmt:
|
||||
children = append(children,
|
||||
tok(n.Arrow, len("<-")))
|
||||
|
||||
case *ast.SliceExpr:
|
||||
children = append(children,
|
||||
tok(n.Lbrack, len("[")),
|
||||
tok(n.Rbrack, len("]")))
|
||||
|
||||
case *ast.StarExpr:
|
||||
children = append(children, tok(n.Star, len("*")))
|
||||
|
||||
case *ast.StructType:
|
||||
children = append(children, tok(n.Struct, len("struct")))
|
||||
|
||||
case *ast.SwitchStmt:
|
||||
children = append(children, tok(n.Switch, len("switch")))
|
||||
|
||||
case *ast.TypeAssertExpr:
|
||||
children = append(children,
|
||||
tok(n.Lparen-1, len(".")),
|
||||
tok(n.Lparen, len("(")),
|
||||
tok(n.Rparen, len(")")))
|
||||
|
||||
case *ast.TypeSpec:
|
||||
// TODO(adonovan): TypeSpec.{Doc,Comment}?
|
||||
|
||||
case *ast.TypeSwitchStmt:
|
||||
children = append(children, tok(n.Switch, len("switch")))
|
||||
|
||||
case *ast.UnaryExpr:
|
||||
children = append(children, tok(n.OpPos, len(n.Op.String())))
|
||||
|
||||
case *ast.ValueSpec:
|
||||
// TODO(adonovan): ValueSpec.{Doc,Comment}?
|
||||
|
||||
case *ast.BadDecl, *ast.BadExpr, *ast.BadStmt:
|
||||
// nop
|
||||
}
|
||||
|
||||
// TODO(adonovan): opt: merge the logic of ast.Inspect() into
|
||||
// the switch above so we can make interleaved callbacks for
|
||||
// both Nodes and Tokens in the right order and avoid the need
|
||||
// to sort.
|
||||
sort.Sort(byPos(children))
|
||||
|
||||
return children
|
||||
}
|
||||
|
||||
type byPos []ast.Node
|
||||
|
||||
func (sl byPos) Len() int {
|
||||
return len(sl)
|
||||
}
|
||||
func (sl byPos) Less(i, j int) bool {
|
||||
return sl[i].Pos() < sl[j].Pos()
|
||||
}
|
||||
func (sl byPos) Swap(i, j int) {
|
||||
sl[i], sl[j] = sl[j], sl[i]
|
||||
}
|
||||
|
||||
// NodeDescription returns a description of the concrete type of n suitable
|
||||
// for a user interface.
|
||||
//
|
||||
// TODO(adonovan): in some cases (e.g. Field, FieldList, Ident,
|
||||
// StarExpr) we could be much more specific given the path to the AST
|
||||
// root. Perhaps we should do that.
|
||||
func NodeDescription(n ast.Node) string {
|
||||
switch n := n.(type) {
|
||||
case *ast.ArrayType:
|
||||
return "array type"
|
||||
case *ast.AssignStmt:
|
||||
return "assignment"
|
||||
case *ast.BadDecl:
|
||||
return "bad declaration"
|
||||
case *ast.BadExpr:
|
||||
return "bad expression"
|
||||
case *ast.BadStmt:
|
||||
return "bad statement"
|
||||
case *ast.BasicLit:
|
||||
return "basic literal"
|
||||
case *ast.BinaryExpr:
|
||||
return fmt.Sprintf("binary %s operation", n.Op)
|
||||
case *ast.BlockStmt:
|
||||
return "block"
|
||||
case *ast.BranchStmt:
|
||||
switch n.Tok {
|
||||
case token.BREAK:
|
||||
return "break statement"
|
||||
case token.CONTINUE:
|
||||
return "continue statement"
|
||||
case token.GOTO:
|
||||
return "goto statement"
|
||||
case token.FALLTHROUGH:
|
||||
return "fall-through statement"
|
||||
}
|
||||
case *ast.CallExpr:
|
||||
if len(n.Args) == 1 && !n.Ellipsis.IsValid() {
|
||||
return "function call (or conversion)"
|
||||
}
|
||||
return "function call"
|
||||
case *ast.CaseClause:
|
||||
return "case clause"
|
||||
case *ast.ChanType:
|
||||
return "channel type"
|
||||
case *ast.CommClause:
|
||||
return "communication clause"
|
||||
case *ast.Comment:
|
||||
return "comment"
|
||||
case *ast.CommentGroup:
|
||||
return "comment group"
|
||||
case *ast.CompositeLit:
|
||||
return "composite literal"
|
||||
case *ast.DeclStmt:
|
||||
return NodeDescription(n.Decl) + " statement"
|
||||
case *ast.DeferStmt:
|
||||
return "defer statement"
|
||||
case *ast.Ellipsis:
|
||||
return "ellipsis"
|
||||
case *ast.EmptyStmt:
|
||||
return "empty statement"
|
||||
case *ast.ExprStmt:
|
||||
return "expression statement"
|
||||
case *ast.Field:
|
||||
// Can be any of these:
|
||||
// struct {x, y int} -- struct field(s)
|
||||
// struct {T} -- anon struct field
|
||||
// interface {I} -- interface embedding
|
||||
// interface {f()} -- interface method
|
||||
// func (A) func(B) C -- receiver, param(s), result(s)
|
||||
return "field/method/parameter"
|
||||
case *ast.FieldList:
|
||||
return "field/method/parameter list"
|
||||
case *ast.File:
|
||||
return "source file"
|
||||
case *ast.ForStmt:
|
||||
return "for loop"
|
||||
case *ast.FuncDecl:
|
||||
return "function declaration"
|
||||
case *ast.FuncLit:
|
||||
return "function literal"
|
||||
case *ast.FuncType:
|
||||
return "function type"
|
||||
case *ast.GenDecl:
|
||||
switch n.Tok {
|
||||
case token.IMPORT:
|
||||
return "import declaration"
|
||||
case token.CONST:
|
||||
return "constant declaration"
|
||||
case token.TYPE:
|
||||
return "type declaration"
|
||||
case token.VAR:
|
||||
return "variable declaration"
|
||||
}
|
||||
case *ast.GoStmt:
|
||||
return "go statement"
|
||||
case *ast.Ident:
|
||||
return "identifier"
|
||||
case *ast.IfStmt:
|
||||
return "if statement"
|
||||
case *ast.ImportSpec:
|
||||
return "import specification"
|
||||
case *ast.IncDecStmt:
|
||||
if n.Tok == token.INC {
|
||||
return "increment statement"
|
||||
}
|
||||
return "decrement statement"
|
||||
case *ast.IndexExpr:
|
||||
return "index expression"
|
||||
case *ast.IndexListExpr:
|
||||
return "index list expression"
|
||||
case *ast.InterfaceType:
|
||||
return "interface type"
|
||||
case *ast.KeyValueExpr:
|
||||
return "key/value association"
|
||||
case *ast.LabeledStmt:
|
||||
return "statement label"
|
||||
case *ast.MapType:
|
||||
return "map type"
|
||||
case *ast.Package:
|
||||
return "package"
|
||||
case *ast.ParenExpr:
|
||||
return "parenthesized " + NodeDescription(n.X)
|
||||
case *ast.RangeStmt:
|
||||
return "range loop"
|
||||
case *ast.ReturnStmt:
|
||||
return "return statement"
|
||||
case *ast.SelectStmt:
|
||||
return "select statement"
|
||||
case *ast.SelectorExpr:
|
||||
return "selector"
|
||||
case *ast.SendStmt:
|
||||
return "channel send"
|
||||
case *ast.SliceExpr:
|
||||
return "slice expression"
|
||||
case *ast.StarExpr:
|
||||
return "*-operation" // load/store expr or pointer type
|
||||
case *ast.StructType:
|
||||
return "struct type"
|
||||
case *ast.SwitchStmt:
|
||||
return "switch statement"
|
||||
case *ast.TypeAssertExpr:
|
||||
return "type assertion"
|
||||
case *ast.TypeSpec:
|
||||
return "type specification"
|
||||
case *ast.TypeSwitchStmt:
|
||||
return "type switch"
|
||||
case *ast.UnaryExpr:
|
||||
return fmt.Sprintf("unary %s operation", n.Op)
|
||||
case *ast.ValueSpec:
|
||||
return "value specification"
|
||||
|
||||
}
|
||||
panic(fmt.Sprintf("unexpected node type: %T", n))
|
||||
}
|
||||
|
||||
func is[T any](x any) bool {
|
||||
_, ok := x.(T)
|
||||
return ok
|
||||
}
|
||||
487
vendor/golang.org/x/tools/go/ast/astutil/imports.go
generated
vendored
Normal file
487
vendor/golang.org/x/tools/go/ast/astutil/imports.go
generated
vendored
Normal file
|
|
@ -0,0 +1,487 @@
|
|||
// Copyright 2013 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 astutil contains common utilities for working with the Go AST.
|
||||
package astutil // import "golang.org/x/tools/go/ast/astutil"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AddImport adds the import path to the file f, if absent.
|
||||
func AddImport(fset *token.FileSet, f *ast.File, path string) (added bool) {
|
||||
return AddNamedImport(fset, f, "", path)
|
||||
}
|
||||
|
||||
// AddNamedImport adds the import with the given name and path to the file f, if absent.
|
||||
// If name is not empty, it is used to rename the import.
|
||||
//
|
||||
// For example, calling
|
||||
//
|
||||
// AddNamedImport(fset, f, "pathpkg", "path")
|
||||
//
|
||||
// adds
|
||||
//
|
||||
// import pathpkg "path"
|
||||
func AddNamedImport(fset *token.FileSet, f *ast.File, name, path string) (added bool) {
|
||||
if imports(f, name, path) {
|
||||
return false
|
||||
}
|
||||
|
||||
newImport := &ast.ImportSpec{
|
||||
Path: &ast.BasicLit{
|
||||
Kind: token.STRING,
|
||||
Value: strconv.Quote(path),
|
||||
},
|
||||
}
|
||||
if name != "" {
|
||||
newImport.Name = &ast.Ident{Name: name}
|
||||
}
|
||||
|
||||
// Find an import decl to add to.
|
||||
// The goal is to find an existing import
|
||||
// whose import path has the longest shared
|
||||
// prefix with path.
|
||||
var (
|
||||
bestMatch = -1 // length of longest shared prefix
|
||||
lastImport = -1 // index in f.Decls of the file's final import decl
|
||||
impDecl *ast.GenDecl // import decl containing the best match
|
||||
impIndex = -1 // spec index in impDecl containing the best match
|
||||
|
||||
isThirdPartyPath = isThirdParty(path)
|
||||
)
|
||||
for i, decl := range f.Decls {
|
||||
gen, ok := decl.(*ast.GenDecl)
|
||||
if ok && gen.Tok == token.IMPORT {
|
||||
lastImport = i
|
||||
// Do not add to import "C", to avoid disrupting the
|
||||
// association with its doc comment, breaking cgo.
|
||||
if declImports(gen, "C") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Match an empty import decl if that's all that is available.
|
||||
if len(gen.Specs) == 0 && bestMatch == -1 {
|
||||
impDecl = gen
|
||||
}
|
||||
|
||||
// Compute longest shared prefix with imports in this group and find best
|
||||
// matched import spec.
|
||||
// 1. Always prefer import spec with longest shared prefix.
|
||||
// 2. While match length is 0,
|
||||
// - for stdlib package: prefer first import spec.
|
||||
// - for third party package: prefer first third party import spec.
|
||||
// We cannot use last import spec as best match for third party package
|
||||
// because grouped imports are usually placed last by goimports -local
|
||||
// flag.
|
||||
// See issue #19190.
|
||||
seenAnyThirdParty := false
|
||||
for j, spec := range gen.Specs {
|
||||
impspec := spec.(*ast.ImportSpec)
|
||||
p := importPath(impspec)
|
||||
n := matchLen(p, path)
|
||||
if n > bestMatch || (bestMatch == 0 && !seenAnyThirdParty && isThirdPartyPath) {
|
||||
bestMatch = n
|
||||
impDecl = gen
|
||||
impIndex = j
|
||||
}
|
||||
seenAnyThirdParty = seenAnyThirdParty || isThirdParty(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no import decl found, add one after the last import.
|
||||
if impDecl == nil {
|
||||
impDecl = &ast.GenDecl{
|
||||
Tok: token.IMPORT,
|
||||
}
|
||||
if lastImport >= 0 {
|
||||
impDecl.TokPos = f.Decls[lastImport].End()
|
||||
} else {
|
||||
// There are no existing imports.
|
||||
// Our new import, preceded by a blank line, goes after the package declaration
|
||||
// and after the comment, if any, that starts on the same line as the
|
||||
// package declaration.
|
||||
impDecl.TokPos = f.Package
|
||||
|
||||
file := fset.File(f.Package)
|
||||
pkgLine := file.Line(f.Package)
|
||||
for _, c := range f.Comments {
|
||||
if file.Line(c.Pos()) > pkgLine {
|
||||
break
|
||||
}
|
||||
// +2 for a blank line
|
||||
impDecl.TokPos = c.End() + 2
|
||||
}
|
||||
}
|
||||
f.Decls = append(f.Decls, nil)
|
||||
copy(f.Decls[lastImport+2:], f.Decls[lastImport+1:])
|
||||
f.Decls[lastImport+1] = impDecl
|
||||
}
|
||||
|
||||
// Insert new import at insertAt.
|
||||
insertAt := 0
|
||||
if impIndex >= 0 {
|
||||
// insert after the found import
|
||||
insertAt = impIndex + 1
|
||||
}
|
||||
impDecl.Specs = append(impDecl.Specs, nil)
|
||||
copy(impDecl.Specs[insertAt+1:], impDecl.Specs[insertAt:])
|
||||
impDecl.Specs[insertAt] = newImport
|
||||
pos := impDecl.Pos()
|
||||
if insertAt > 0 {
|
||||
// If there is a comment after an existing import, preserve the comment
|
||||
// position by adding the new import after the comment.
|
||||
if spec, ok := impDecl.Specs[insertAt-1].(*ast.ImportSpec); ok && spec.Comment != nil {
|
||||
pos = spec.Comment.End()
|
||||
} else {
|
||||
// Assign same position as the previous import,
|
||||
// so that the sorter sees it as being in the same block.
|
||||
pos = impDecl.Specs[insertAt-1].Pos()
|
||||
}
|
||||
}
|
||||
if newImport.Name != nil {
|
||||
newImport.Name.NamePos = pos
|
||||
}
|
||||
updateBasicLitPos(newImport.Path, pos)
|
||||
newImport.EndPos = pos
|
||||
|
||||
// Clean up parens. impDecl contains at least one spec.
|
||||
if len(impDecl.Specs) == 1 {
|
||||
// Remove unneeded parens.
|
||||
impDecl.Lparen = token.NoPos
|
||||
} else if !impDecl.Lparen.IsValid() {
|
||||
// impDecl needs parens added.
|
||||
impDecl.Lparen = impDecl.Specs[0].Pos()
|
||||
}
|
||||
|
||||
f.Imports = append(f.Imports, newImport)
|
||||
|
||||
if len(f.Decls) <= 1 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Merge all the import declarations into the first one.
|
||||
var first *ast.GenDecl
|
||||
for i := 0; i < len(f.Decls); i++ {
|
||||
decl := f.Decls[i]
|
||||
gen, ok := decl.(*ast.GenDecl)
|
||||
if !ok || gen.Tok != token.IMPORT || declImports(gen, "C") {
|
||||
continue
|
||||
}
|
||||
if first == nil {
|
||||
first = gen
|
||||
continue // Don't touch the first one.
|
||||
}
|
||||
// We now know there is more than one package in this import
|
||||
// declaration. Ensure that it ends up parenthesized.
|
||||
first.Lparen = first.Pos()
|
||||
// Move the imports of the other import declaration to the first one.
|
||||
for _, spec := range gen.Specs {
|
||||
updateBasicLitPos(spec.(*ast.ImportSpec).Path, first.Pos())
|
||||
first.Specs = append(first.Specs, spec)
|
||||
}
|
||||
f.Decls = slices.Delete(f.Decls, i, i+1)
|
||||
i--
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func isThirdParty(importPath string) bool {
|
||||
// Third party package import path usually contains "." (".com", ".org", ...)
|
||||
// This logic is taken from golang.org/x/tools/imports package.
|
||||
return strings.Contains(importPath, ".")
|
||||
}
|
||||
|
||||
// DeleteImport deletes the import path from the file f, if present.
|
||||
// If there are duplicate import declarations, all matching ones are deleted.
|
||||
func DeleteImport(fset *token.FileSet, f *ast.File, path string) (deleted bool) {
|
||||
return DeleteNamedImport(fset, f, "", path)
|
||||
}
|
||||
|
||||
// DeleteNamedImport deletes the import with the given name and path from the file f, if present.
|
||||
// If there are duplicate import declarations, all matching ones are deleted.
|
||||
func DeleteNamedImport(fset *token.FileSet, f *ast.File, name, path string) (deleted bool) {
|
||||
var (
|
||||
delspecs = make(map[*ast.ImportSpec]bool)
|
||||
delcomments = make(map[*ast.CommentGroup]bool)
|
||||
)
|
||||
|
||||
// Find the import nodes that import path, if any.
|
||||
for i := 0; i < len(f.Decls); i++ {
|
||||
gen, ok := f.Decls[i].(*ast.GenDecl)
|
||||
if !ok || gen.Tok != token.IMPORT {
|
||||
continue
|
||||
}
|
||||
for j := 0; j < len(gen.Specs); j++ {
|
||||
impspec := gen.Specs[j].(*ast.ImportSpec)
|
||||
if importName(impspec) != name || importPath(impspec) != path {
|
||||
continue
|
||||
}
|
||||
|
||||
// We found an import spec that imports path.
|
||||
// Delete it.
|
||||
delspecs[impspec] = true
|
||||
deleted = true
|
||||
gen.Specs = slices.Delete(gen.Specs, j, j+1)
|
||||
|
||||
// If this was the last import spec in this decl,
|
||||
// delete the decl, too.
|
||||
if len(gen.Specs) == 0 {
|
||||
f.Decls = slices.Delete(f.Decls, i, i+1)
|
||||
i--
|
||||
break
|
||||
} else if len(gen.Specs) == 1 {
|
||||
if impspec.Doc != nil {
|
||||
delcomments[impspec.Doc] = true
|
||||
}
|
||||
if impspec.Comment != nil {
|
||||
delcomments[impspec.Comment] = true
|
||||
}
|
||||
for _, cg := range f.Comments {
|
||||
// Found comment on the same line as the import spec.
|
||||
if cg.End() < impspec.Pos() && fset.Position(cg.End()).Line == fset.Position(impspec.Pos()).Line {
|
||||
delcomments[cg] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
spec := gen.Specs[0].(*ast.ImportSpec)
|
||||
|
||||
// Move the documentation right after the import decl.
|
||||
if spec.Doc != nil {
|
||||
for fset.Position(gen.TokPos).Line+1 < fset.Position(spec.Doc.Pos()).Line {
|
||||
fset.File(gen.TokPos).MergeLine(fset.Position(gen.TokPos).Line)
|
||||
}
|
||||
}
|
||||
for _, cg := range f.Comments {
|
||||
if cg.End() < spec.Pos() && fset.Position(cg.End()).Line == fset.Position(spec.Pos()).Line {
|
||||
for fset.Position(gen.TokPos).Line+1 < fset.Position(spec.Pos()).Line {
|
||||
fset.File(gen.TokPos).MergeLine(fset.Position(gen.TokPos).Line)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if j > 0 {
|
||||
lastImpspec := gen.Specs[j-1].(*ast.ImportSpec)
|
||||
lastLine := fset.PositionFor(lastImpspec.Path.ValuePos, false).Line
|
||||
line := fset.PositionFor(impspec.Path.ValuePos, false).Line
|
||||
|
||||
// We deleted an entry but now there may be
|
||||
// a blank line-sized hole where the import was.
|
||||
if line-lastLine > 1 || !gen.Rparen.IsValid() {
|
||||
// There was a blank line immediately preceding the deleted import,
|
||||
// so there's no need to close the hole. The right parenthesis is
|
||||
// invalid after AddImport to an import statement without parenthesis.
|
||||
// Do nothing.
|
||||
} else if line != fset.File(gen.Rparen).LineCount() {
|
||||
// There was no blank line. Close the hole.
|
||||
fset.File(gen.Rparen).MergeLine(line)
|
||||
}
|
||||
}
|
||||
j--
|
||||
}
|
||||
}
|
||||
|
||||
// Delete imports from f.Imports.
|
||||
before := len(f.Imports)
|
||||
f.Imports = slices.DeleteFunc(f.Imports, func(imp *ast.ImportSpec) bool {
|
||||
_, ok := delspecs[imp]
|
||||
return ok
|
||||
})
|
||||
if len(f.Imports)+len(delspecs) != before {
|
||||
// This can happen when the AST is invalid (i.e. imports differ between f.Decls and f.Imports).
|
||||
panic(fmt.Sprintf("deleted specs from Decls but not Imports: %v", delspecs))
|
||||
}
|
||||
|
||||
// Delete comments from f.Comments.
|
||||
f.Comments = slices.DeleteFunc(f.Comments, func(cg *ast.CommentGroup) bool {
|
||||
_, ok := delcomments[cg]
|
||||
return ok
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RewriteImport rewrites any import of path oldPath to path newPath.
|
||||
func RewriteImport(fset *token.FileSet, f *ast.File, oldPath, newPath string) (rewrote bool) {
|
||||
for _, imp := range f.Imports {
|
||||
if importPath(imp) == oldPath {
|
||||
rewrote = true
|
||||
// record old End, because the default is to compute
|
||||
// it using the length of imp.Path.Value.
|
||||
imp.EndPos = imp.End()
|
||||
imp.Path.Value = strconv.Quote(newPath)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// UsesImport reports whether a given import is used.
|
||||
// The provided File must have been parsed with syntactic object resolution
|
||||
// (not using go/parser.SkipObjectResolution).
|
||||
func UsesImport(f *ast.File, path string) (used bool) {
|
||||
if f.Scope == nil {
|
||||
panic("file f was not parsed with syntactic object resolution")
|
||||
}
|
||||
spec := importSpec(f, path)
|
||||
if spec == nil {
|
||||
return
|
||||
}
|
||||
|
||||
name := spec.Name.String()
|
||||
switch name {
|
||||
case "<nil>":
|
||||
// If the package name is not explicitly specified,
|
||||
// make an educated guess. This is not guaranteed to be correct.
|
||||
lastSlash := strings.LastIndex(path, "/")
|
||||
if lastSlash == -1 {
|
||||
name = path
|
||||
} else {
|
||||
name = path[lastSlash+1:]
|
||||
}
|
||||
case "_", ".":
|
||||
// Not sure if this import is used - err on the side of caution.
|
||||
return true
|
||||
}
|
||||
|
||||
ast.Walk(visitFn(func(n ast.Node) {
|
||||
sel, ok := n.(*ast.SelectorExpr)
|
||||
if ok && isTopName(sel.X, name) {
|
||||
used = true
|
||||
}
|
||||
}), f)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type visitFn func(node ast.Node)
|
||||
|
||||
func (fn visitFn) Visit(node ast.Node) ast.Visitor {
|
||||
fn(node)
|
||||
return fn
|
||||
}
|
||||
|
||||
// imports reports whether f has an import with the specified name and path.
|
||||
func imports(f *ast.File, name, path string) bool {
|
||||
for _, s := range f.Imports {
|
||||
if importName(s) == name && importPath(s) == path {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// importSpec returns the import spec if f imports path,
|
||||
// or nil otherwise.
|
||||
func importSpec(f *ast.File, path string) *ast.ImportSpec {
|
||||
for _, s := range f.Imports {
|
||||
if importPath(s) == path {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// importName returns the name of s,
|
||||
// or "" if the import is not named.
|
||||
func importName(s *ast.ImportSpec) string {
|
||||
if s.Name == nil {
|
||||
return ""
|
||||
}
|
||||
return s.Name.Name
|
||||
}
|
||||
|
||||
// importPath returns the unquoted import path of s,
|
||||
// or "" if the path is not properly quoted.
|
||||
func importPath(s *ast.ImportSpec) string {
|
||||
t, err := strconv.Unquote(s.Path.Value)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// declImports reports whether gen contains an import of path.
|
||||
func declImports(gen *ast.GenDecl, path string) bool {
|
||||
if gen.Tok != token.IMPORT {
|
||||
return false
|
||||
}
|
||||
for _, spec := range gen.Specs {
|
||||
impspec := spec.(*ast.ImportSpec)
|
||||
if importPath(impspec) == path {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// matchLen returns the length of the longest path segment prefix shared by x and y.
|
||||
func matchLen(x, y string) int {
|
||||
n := 0
|
||||
for i := 0; i < len(x) && i < len(y) && x[i] == y[i]; i++ {
|
||||
if x[i] == '/' {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// isTopName returns true if n is a top-level unresolved identifier with the given name.
|
||||
func isTopName(n ast.Expr, name string) bool {
|
||||
id, ok := n.(*ast.Ident)
|
||||
return ok && id.Name == name && id.Obj == nil
|
||||
}
|
||||
|
||||
// Imports returns the file imports grouped by paragraph.
|
||||
func Imports(fset *token.FileSet, f *ast.File) [][]*ast.ImportSpec {
|
||||
var groups [][]*ast.ImportSpec
|
||||
|
||||
for _, decl := range f.Decls {
|
||||
genDecl, ok := decl.(*ast.GenDecl)
|
||||
if !ok || genDecl.Tok != token.IMPORT {
|
||||
break
|
||||
}
|
||||
|
||||
group := []*ast.ImportSpec{}
|
||||
|
||||
var lastLine int
|
||||
for _, spec := range genDecl.Specs {
|
||||
importSpec := spec.(*ast.ImportSpec)
|
||||
pos := importSpec.Path.ValuePos
|
||||
line := fset.Position(pos).Line
|
||||
if lastLine > 0 && pos > 0 && line-lastLine > 1 {
|
||||
groups = append(groups, group)
|
||||
group = []*ast.ImportSpec{}
|
||||
}
|
||||
group = append(group, importSpec)
|
||||
lastLine = line
|
||||
}
|
||||
groups = append(groups, group)
|
||||
}
|
||||
|
||||
return groups
|
||||
}
|
||||
|
||||
// updateBasicLitPos updates lit.Pos,
|
||||
// ensuring that lit.End (if set) is displaced by the same amount.
|
||||
// (See https://go.dev/issue/76395.)
|
||||
func updateBasicLitPos(lit *ast.BasicLit, pos token.Pos) {
|
||||
len := lit.End() - lit.Pos()
|
||||
lit.ValuePos = pos
|
||||
// TODO(adonovan): after go1.26, simplify to:
|
||||
// lit.ValueEnd = pos + len
|
||||
v := reflect.ValueOf(lit).Elem().FieldByName("ValueEnd")
|
||||
if v.IsValid() && v.Int() != 0 {
|
||||
v.SetInt(int64(pos + len))
|
||||
}
|
||||
}
|
||||
490
vendor/golang.org/x/tools/go/ast/astutil/rewrite.go
generated
vendored
Normal file
490
vendor/golang.org/x/tools/go/ast/astutil/rewrite.go
generated
vendored
Normal file
|
|
@ -0,0 +1,490 @@
|
|||
// Copyright 2017 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 astutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// An ApplyFunc is invoked by Apply for each node n, even if n is nil,
|
||||
// before and/or after the node's children, using a Cursor describing
|
||||
// the current node and providing operations on it.
|
||||
//
|
||||
// The return value of ApplyFunc controls the syntax tree traversal.
|
||||
// See Apply for details.
|
||||
type ApplyFunc func(*Cursor) bool
|
||||
|
||||
// Apply traverses a syntax tree recursively, starting with root,
|
||||
// and calling pre and post for each node as described below.
|
||||
// Apply returns the syntax tree, possibly modified.
|
||||
//
|
||||
// If pre is not nil, it is called for each node before the node's
|
||||
// children are traversed (pre-order). If pre returns false, no
|
||||
// children are traversed, and post is not called for that node.
|
||||
//
|
||||
// If post is not nil, and a prior call of pre didn't return false,
|
||||
// post is called for each node after its children are traversed
|
||||
// (post-order). If post returns false, traversal is terminated and
|
||||
// Apply returns immediately.
|
||||
//
|
||||
// Only fields that refer to AST nodes are considered children;
|
||||
// i.e., token.Pos, Scopes, Objects, and fields of basic types
|
||||
// (strings, etc.) are ignored.
|
||||
//
|
||||
// Children are traversed in the order in which they appear in the
|
||||
// respective node's struct definition. A package's files are
|
||||
// traversed in the filenames' alphabetical order.
|
||||
func Apply(root ast.Node, pre, post ApplyFunc) (result ast.Node) {
|
||||
parent := &struct{ ast.Node }{root}
|
||||
defer func() {
|
||||
if r := recover(); r != nil && r != abort {
|
||||
panic(r)
|
||||
}
|
||||
result = parent.Node
|
||||
}()
|
||||
a := &application{pre: pre, post: post}
|
||||
a.apply(parent, "Node", nil, root)
|
||||
return
|
||||
}
|
||||
|
||||
var abort = new(int) // singleton, to signal termination of Apply
|
||||
|
||||
// A Cursor describes a node encountered during Apply.
|
||||
// Information about the node and its parent is available
|
||||
// from the Node, Parent, Name, and Index methods.
|
||||
//
|
||||
// If p is a variable of type and value of the current parent node
|
||||
// c.Parent(), and f is the field identifier with name c.Name(),
|
||||
// the following invariants hold:
|
||||
//
|
||||
// p.f == c.Node() if c.Index() < 0
|
||||
// p.f[c.Index()] == c.Node() if c.Index() >= 0
|
||||
//
|
||||
// The methods Replace, Delete, InsertBefore, and InsertAfter
|
||||
// can be used to change the AST without disrupting Apply.
|
||||
//
|
||||
// This type is not to be confused with [inspector.Cursor] from
|
||||
// package [golang.org/x/tools/go/ast/inspector], which provides
|
||||
// stateless navigation of immutable syntax trees.
|
||||
type Cursor struct {
|
||||
parent ast.Node
|
||||
name string
|
||||
iter *iterator // valid if non-nil
|
||||
node ast.Node
|
||||
}
|
||||
|
||||
// Node returns the current Node.
|
||||
func (c *Cursor) Node() ast.Node { return c.node }
|
||||
|
||||
// Parent returns the parent of the current Node.
|
||||
func (c *Cursor) Parent() ast.Node { return c.parent }
|
||||
|
||||
// Name returns the name of the parent Node field that contains the current Node.
|
||||
// If the parent is a *ast.Package and the current Node is a *ast.File, Name returns
|
||||
// the filename for the current Node.
|
||||
func (c *Cursor) Name() string { return c.name }
|
||||
|
||||
// Index reports the index >= 0 of the current Node in the slice of Nodes that
|
||||
// contains it, or a value < 0 if the current Node is not part of a slice.
|
||||
// The index of the current node changes if InsertBefore is called while
|
||||
// processing the current node.
|
||||
func (c *Cursor) Index() int {
|
||||
if c.iter != nil {
|
||||
return c.iter.index
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// field returns the current node's parent field value.
|
||||
func (c *Cursor) field() reflect.Value {
|
||||
return reflect.Indirect(reflect.ValueOf(c.parent)).FieldByName(c.name)
|
||||
}
|
||||
|
||||
// Replace replaces the current Node with n.
|
||||
// The replacement node is not walked by Apply.
|
||||
func (c *Cursor) Replace(n ast.Node) {
|
||||
if _, ok := c.node.(*ast.File); ok {
|
||||
file, ok := n.(*ast.File)
|
||||
if !ok {
|
||||
panic("attempt to replace *ast.File with non-*ast.File")
|
||||
}
|
||||
c.parent.(*ast.Package).Files[c.name] = file
|
||||
return
|
||||
}
|
||||
|
||||
v := c.field()
|
||||
if i := c.Index(); i >= 0 {
|
||||
v = v.Index(i)
|
||||
}
|
||||
v.Set(reflect.ValueOf(n))
|
||||
}
|
||||
|
||||
// Delete deletes the current Node from its containing slice.
|
||||
// If the current Node is not part of a slice, Delete panics.
|
||||
// As a special case, if the current node is a package file,
|
||||
// Delete removes it from the package's Files map.
|
||||
func (c *Cursor) Delete() {
|
||||
if _, ok := c.node.(*ast.File); ok {
|
||||
delete(c.parent.(*ast.Package).Files, c.name)
|
||||
return
|
||||
}
|
||||
|
||||
i := c.Index()
|
||||
if i < 0 {
|
||||
panic("Delete node not contained in slice")
|
||||
}
|
||||
v := c.field()
|
||||
l := v.Len()
|
||||
reflect.Copy(v.Slice(i, l), v.Slice(i+1, l))
|
||||
v.Index(l - 1).Set(reflect.Zero(v.Type().Elem()))
|
||||
v.SetLen(l - 1)
|
||||
c.iter.step--
|
||||
}
|
||||
|
||||
// InsertAfter inserts n after the current Node in its containing slice.
|
||||
// If the current Node is not part of a slice, InsertAfter panics.
|
||||
// Apply does not walk n.
|
||||
func (c *Cursor) InsertAfter(n ast.Node) {
|
||||
i := c.Index()
|
||||
if i < 0 {
|
||||
panic("InsertAfter node not contained in slice")
|
||||
}
|
||||
v := c.field()
|
||||
v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem())))
|
||||
l := v.Len()
|
||||
reflect.Copy(v.Slice(i+2, l), v.Slice(i+1, l))
|
||||
v.Index(i + 1).Set(reflect.ValueOf(n))
|
||||
c.iter.step++
|
||||
}
|
||||
|
||||
// InsertBefore inserts n before the current Node in its containing slice.
|
||||
// If the current Node is not part of a slice, InsertBefore panics.
|
||||
// Apply will not walk n.
|
||||
func (c *Cursor) InsertBefore(n ast.Node) {
|
||||
i := c.Index()
|
||||
if i < 0 {
|
||||
panic("InsertBefore node not contained in slice")
|
||||
}
|
||||
v := c.field()
|
||||
v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem())))
|
||||
l := v.Len()
|
||||
reflect.Copy(v.Slice(i+1, l), v.Slice(i, l))
|
||||
v.Index(i).Set(reflect.ValueOf(n))
|
||||
c.iter.index++
|
||||
}
|
||||
|
||||
// application carries all the shared data so we can pass it around cheaply.
|
||||
type application struct {
|
||||
pre, post ApplyFunc
|
||||
cursor Cursor
|
||||
iter iterator
|
||||
}
|
||||
|
||||
func (a *application) apply(parent ast.Node, name string, iter *iterator, n ast.Node) {
|
||||
// convert typed nil into untyped nil
|
||||
if v := reflect.ValueOf(n); v.Kind() == reflect.Pointer && v.IsNil() {
|
||||
n = nil
|
||||
}
|
||||
|
||||
// avoid heap-allocating a new cursor for each apply call; reuse a.cursor instead
|
||||
saved := a.cursor
|
||||
a.cursor.parent = parent
|
||||
a.cursor.name = name
|
||||
a.cursor.iter = iter
|
||||
a.cursor.node = n
|
||||
|
||||
if a.pre != nil && !a.pre(&a.cursor) {
|
||||
a.cursor = saved
|
||||
return
|
||||
}
|
||||
|
||||
// walk children
|
||||
// (the order of the cases matches the order of the corresponding node types in go/ast)
|
||||
switch n := n.(type) {
|
||||
case nil:
|
||||
// nothing to do
|
||||
|
||||
// Comments and fields
|
||||
case *ast.Comment:
|
||||
// nothing to do
|
||||
|
||||
case *ast.CommentGroup:
|
||||
if n != nil {
|
||||
a.applyList(n, "List")
|
||||
}
|
||||
|
||||
case *ast.Field:
|
||||
a.apply(n, "Doc", nil, n.Doc)
|
||||
a.applyList(n, "Names")
|
||||
a.apply(n, "Type", nil, n.Type)
|
||||
a.apply(n, "Tag", nil, n.Tag)
|
||||
a.apply(n, "Comment", nil, n.Comment)
|
||||
|
||||
case *ast.FieldList:
|
||||
a.applyList(n, "List")
|
||||
|
||||
// Expressions
|
||||
case *ast.BadExpr, *ast.Ident, *ast.BasicLit:
|
||||
// nothing to do
|
||||
|
||||
case *ast.Ellipsis:
|
||||
a.apply(n, "Elt", nil, n.Elt)
|
||||
|
||||
case *ast.FuncLit:
|
||||
a.apply(n, "Type", nil, n.Type)
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
|
||||
case *ast.CompositeLit:
|
||||
a.apply(n, "Type", nil, n.Type)
|
||||
a.applyList(n, "Elts")
|
||||
|
||||
case *ast.ParenExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
a.apply(n, "Sel", nil, n.Sel)
|
||||
|
||||
case *ast.IndexExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
a.apply(n, "Index", nil, n.Index)
|
||||
|
||||
case *ast.IndexListExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
a.applyList(n, "Indices")
|
||||
|
||||
case *ast.SliceExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
a.apply(n, "Low", nil, n.Low)
|
||||
a.apply(n, "High", nil, n.High)
|
||||
a.apply(n, "Max", nil, n.Max)
|
||||
|
||||
case *ast.TypeAssertExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
a.apply(n, "Type", nil, n.Type)
|
||||
|
||||
case *ast.CallExpr:
|
||||
a.apply(n, "Fun", nil, n.Fun)
|
||||
a.applyList(n, "Args")
|
||||
|
||||
case *ast.StarExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
|
||||
case *ast.UnaryExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
|
||||
case *ast.BinaryExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
a.apply(n, "Y", nil, n.Y)
|
||||
|
||||
case *ast.KeyValueExpr:
|
||||
a.apply(n, "Key", nil, n.Key)
|
||||
a.apply(n, "Value", nil, n.Value)
|
||||
|
||||
// Types
|
||||
case *ast.ArrayType:
|
||||
a.apply(n, "Len", nil, n.Len)
|
||||
a.apply(n, "Elt", nil, n.Elt)
|
||||
|
||||
case *ast.StructType:
|
||||
a.apply(n, "Fields", nil, n.Fields)
|
||||
|
||||
case *ast.FuncType:
|
||||
if tparams := n.TypeParams; tparams != nil {
|
||||
a.apply(n, "TypeParams", nil, tparams)
|
||||
}
|
||||
a.apply(n, "Params", nil, n.Params)
|
||||
a.apply(n, "Results", nil, n.Results)
|
||||
|
||||
case *ast.InterfaceType:
|
||||
a.apply(n, "Methods", nil, n.Methods)
|
||||
|
||||
case *ast.MapType:
|
||||
a.apply(n, "Key", nil, n.Key)
|
||||
a.apply(n, "Value", nil, n.Value)
|
||||
|
||||
case *ast.ChanType:
|
||||
a.apply(n, "Value", nil, n.Value)
|
||||
|
||||
// Statements
|
||||
case *ast.BadStmt:
|
||||
// nothing to do
|
||||
|
||||
case *ast.DeclStmt:
|
||||
a.apply(n, "Decl", nil, n.Decl)
|
||||
|
||||
case *ast.EmptyStmt:
|
||||
// nothing to do
|
||||
|
||||
case *ast.LabeledStmt:
|
||||
a.apply(n, "Label", nil, n.Label)
|
||||
a.apply(n, "Stmt", nil, n.Stmt)
|
||||
|
||||
case *ast.ExprStmt:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
|
||||
case *ast.SendStmt:
|
||||
a.apply(n, "Chan", nil, n.Chan)
|
||||
a.apply(n, "Value", nil, n.Value)
|
||||
|
||||
case *ast.IncDecStmt:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
|
||||
case *ast.AssignStmt:
|
||||
a.applyList(n, "Lhs")
|
||||
a.applyList(n, "Rhs")
|
||||
|
||||
case *ast.GoStmt:
|
||||
a.apply(n, "Call", nil, n.Call)
|
||||
|
||||
case *ast.DeferStmt:
|
||||
a.apply(n, "Call", nil, n.Call)
|
||||
|
||||
case *ast.ReturnStmt:
|
||||
a.applyList(n, "Results")
|
||||
|
||||
case *ast.BranchStmt:
|
||||
a.apply(n, "Label", nil, n.Label)
|
||||
|
||||
case *ast.BlockStmt:
|
||||
a.applyList(n, "List")
|
||||
|
||||
case *ast.IfStmt:
|
||||
a.apply(n, "Init", nil, n.Init)
|
||||
a.apply(n, "Cond", nil, n.Cond)
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
a.apply(n, "Else", nil, n.Else)
|
||||
|
||||
case *ast.CaseClause:
|
||||
a.applyList(n, "List")
|
||||
a.applyList(n, "Body")
|
||||
|
||||
case *ast.SwitchStmt:
|
||||
a.apply(n, "Init", nil, n.Init)
|
||||
a.apply(n, "Tag", nil, n.Tag)
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
|
||||
case *ast.TypeSwitchStmt:
|
||||
a.apply(n, "Init", nil, n.Init)
|
||||
a.apply(n, "Assign", nil, n.Assign)
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
|
||||
case *ast.CommClause:
|
||||
a.apply(n, "Comm", nil, n.Comm)
|
||||
a.applyList(n, "Body")
|
||||
|
||||
case *ast.SelectStmt:
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
|
||||
case *ast.ForStmt:
|
||||
a.apply(n, "Init", nil, n.Init)
|
||||
a.apply(n, "Cond", nil, n.Cond)
|
||||
a.apply(n, "Post", nil, n.Post)
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
|
||||
case *ast.RangeStmt:
|
||||
a.apply(n, "Key", nil, n.Key)
|
||||
a.apply(n, "Value", nil, n.Value)
|
||||
a.apply(n, "X", nil, n.X)
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
|
||||
// Declarations
|
||||
case *ast.ImportSpec:
|
||||
a.apply(n, "Doc", nil, n.Doc)
|
||||
a.apply(n, "Name", nil, n.Name)
|
||||
a.apply(n, "Path", nil, n.Path)
|
||||
a.apply(n, "Comment", nil, n.Comment)
|
||||
|
||||
case *ast.ValueSpec:
|
||||
a.apply(n, "Doc", nil, n.Doc)
|
||||
a.applyList(n, "Names")
|
||||
a.apply(n, "Type", nil, n.Type)
|
||||
a.applyList(n, "Values")
|
||||
a.apply(n, "Comment", nil, n.Comment)
|
||||
|
||||
case *ast.TypeSpec:
|
||||
a.apply(n, "Doc", nil, n.Doc)
|
||||
a.apply(n, "Name", nil, n.Name)
|
||||
if tparams := n.TypeParams; tparams != nil {
|
||||
a.apply(n, "TypeParams", nil, tparams)
|
||||
}
|
||||
a.apply(n, "Type", nil, n.Type)
|
||||
a.apply(n, "Comment", nil, n.Comment)
|
||||
|
||||
case *ast.BadDecl:
|
||||
// nothing to do
|
||||
|
||||
case *ast.GenDecl:
|
||||
a.apply(n, "Doc", nil, n.Doc)
|
||||
a.applyList(n, "Specs")
|
||||
|
||||
case *ast.FuncDecl:
|
||||
a.apply(n, "Doc", nil, n.Doc)
|
||||
a.apply(n, "Recv", nil, n.Recv)
|
||||
a.apply(n, "Name", nil, n.Name)
|
||||
a.apply(n, "Type", nil, n.Type)
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
|
||||
// Files and packages
|
||||
case *ast.File:
|
||||
a.apply(n, "Doc", nil, n.Doc)
|
||||
a.apply(n, "Name", nil, n.Name)
|
||||
a.applyList(n, "Decls")
|
||||
// Don't walk n.Comments; they have either been walked already if
|
||||
// they are Doc comments, or they can be easily walked explicitly.
|
||||
|
||||
case *ast.Package:
|
||||
// collect and sort names for reproducible behavior
|
||||
var names []string
|
||||
for name := range n.Files {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
a.apply(n, name, nil, n.Files[name])
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("Apply: unexpected node type %T", n))
|
||||
}
|
||||
|
||||
if a.post != nil && !a.post(&a.cursor) {
|
||||
panic(abort)
|
||||
}
|
||||
|
||||
a.cursor = saved
|
||||
}
|
||||
|
||||
// An iterator controls iteration over a slice of nodes.
|
||||
type iterator struct {
|
||||
index, step int
|
||||
}
|
||||
|
||||
func (a *application) applyList(parent ast.Node, name string) {
|
||||
// avoid heap-allocating a new iterator for each applyList call; reuse a.iter instead
|
||||
saved := a.iter
|
||||
a.iter.index = 0
|
||||
for {
|
||||
// must reload parent.name each time, since cursor modifications might change it
|
||||
v := reflect.Indirect(reflect.ValueOf(parent)).FieldByName(name)
|
||||
if a.iter.index >= v.Len() {
|
||||
break
|
||||
}
|
||||
|
||||
// element x may be nil in a bad AST - be cautious
|
||||
var x ast.Node
|
||||
if e := v.Index(a.iter.index); e.IsValid() {
|
||||
x = e.Interface().(ast.Node)
|
||||
}
|
||||
|
||||
a.iter.step = 1
|
||||
a.apply(parent, name, &a.iter, x)
|
||||
a.iter.index += a.iter.step
|
||||
}
|
||||
a.iter = saved
|
||||
}
|
||||
13
vendor/golang.org/x/tools/go/ast/astutil/util.go
generated
vendored
Normal file
13
vendor/golang.org/x/tools/go/ast/astutil/util.go
generated
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright 2015 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 astutil
|
||||
|
||||
import "go/ast"
|
||||
|
||||
// Unparen returns e with any enclosing parentheses stripped.
|
||||
// Deprecated: use [ast.Unparen].
|
||||
//
|
||||
//go:fix inline
|
||||
func Unparen(e ast.Expr) ast.Expr { return ast.Unparen(e) }
|
||||
28
vendor/modules.txt
vendored
28
vendor/modules.txt
vendored
|
|
@ -63,6 +63,11 @@ github.com/go-errors/errors
|
|||
github.com/go-logfmt/logfmt
|
||||
# github.com/google/go-cmp v0.7.0
|
||||
## explicit; go 1.21
|
||||
github.com/google/go-cmp/cmp
|
||||
github.com/google/go-cmp/cmp/internal/diff
|
||||
github.com/google/go-cmp/cmp/internal/flags
|
||||
github.com/google/go-cmp/cmp/internal/function
|
||||
github.com/google/go-cmp/cmp/internal/value
|
||||
# github.com/gookit/color v1.6.1
|
||||
## explicit; go 1.18
|
||||
github.com/gookit/color
|
||||
|
|
@ -91,8 +96,6 @@ github.com/karimkhaleel/jsonschema
|
|||
# github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515
|
||||
## explicit
|
||||
github.com/kr/logfmt
|
||||
# github.com/kr/pretty v0.3.1
|
||||
## explicit; go 1.12
|
||||
# github.com/kyokomi/emoji/v2 v2.2.13
|
||||
## explicit; go 1.14
|
||||
github.com/kyokomi/emoji/v2
|
||||
|
|
@ -128,8 +131,6 @@ github.com/pmezard/go-difflib/difflib
|
|||
# github.com/rivo/uniseg v0.4.7
|
||||
## explicit; go 1.18
|
||||
github.com/rivo/uniseg
|
||||
# github.com/rogpeppe/go-internal v1.14.1
|
||||
## explicit; go 1.23
|
||||
# github.com/sahilm/fuzzy v0.1.3
|
||||
## explicit; go 1.24.5
|
||||
github.com/sahilm/fuzzy
|
||||
|
|
@ -174,11 +175,18 @@ github.com/xo/terminfo
|
|||
## explicit; go 1.20
|
||||
golang.org/x/exp/constraints
|
||||
golang.org/x/exp/slices
|
||||
# golang.org/x/mod v0.35.0
|
||||
## explicit; go 1.25.0
|
||||
golang.org/x/mod/internal/lazyregexp
|
||||
golang.org/x/mod/modfile
|
||||
golang.org/x/mod/module
|
||||
golang.org/x/mod/semver
|
||||
# golang.org/x/net v0.55.0
|
||||
## explicit; go 1.25.0
|
||||
# golang.org/x/sync v0.21.0
|
||||
## explicit; go 1.25.0
|
||||
golang.org/x/sync/errgroup
|
||||
golang.org/x/sync/semaphore
|
||||
# golang.org/x/sys v0.46.0
|
||||
## explicit; go 1.25.0
|
||||
golang.org/x/sys/plan9
|
||||
|
|
@ -200,6 +208,9 @@ golang.org/x/text/language
|
|||
golang.org/x/text/runes
|
||||
golang.org/x/text/transform
|
||||
golang.org/x/text/unicode/norm
|
||||
# golang.org/x/tools v0.44.0
|
||||
## explicit; go 1.25.0
|
||||
golang.org/x/tools/go/ast/astutil
|
||||
# gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
|
||||
## explicit; go 1.11
|
||||
# gopkg.in/fsnotify.v1 v1.4.7
|
||||
|
|
@ -212,3 +223,12 @@ gopkg.in/ozeidan/fuzzy-patricia.v3/patricia
|
|||
# gopkg.in/yaml.v3 v3.0.1
|
||||
## explicit
|
||||
gopkg.in/yaml.v3
|
||||
# mvdan.cc/gofumpt v0.9.2
|
||||
## explicit; go 1.24.0
|
||||
mvdan.cc/gofumpt
|
||||
mvdan.cc/gofumpt/format
|
||||
mvdan.cc/gofumpt/internal/govendor/diff
|
||||
mvdan.cc/gofumpt/internal/govendor/go/doc/comment
|
||||
mvdan.cc/gofumpt/internal/govendor/go/format
|
||||
mvdan.cc/gofumpt/internal/govendor/go/printer
|
||||
mvdan.cc/gofumpt/internal/version
|
||||
|
|
|
|||
2
vendor/mvdan.cc/gofumpt/.gitattributes
vendored
Normal file
2
vendor/mvdan.cc/gofumpt/.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# To prevent CRLF breakages on Windows for fragile files, like testdata.
|
||||
* -text
|
||||
217
vendor/mvdan.cc/gofumpt/CHANGELOG.md
vendored
Normal file
217
vendor/mvdan.cc/gofumpt/CHANGELOG.md
vendored
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
# Changelog
|
||||
|
||||
## [v0.9.1] - 2025-09-07
|
||||
|
||||
This is a bugfix release to address a regression in detecting
|
||||
comment directives with special characters such as `//golangcitest:config_path`.
|
||||
|
||||
## [v0.9.0] - 2025-09-02
|
||||
|
||||
This release is based on Go 1.25's gofmt, and requires Go 1.24 or later.
|
||||
|
||||
A new rule is introduced to "clothe" naked returns for the sake of clarity.
|
||||
While there is nothing wrong with naming results in function signatures,
|
||||
using lone `return` statements can be confusing to the reader.
|
||||
|
||||
Go 1.25's `ignore` directives in `go.mod` files are now obeyed;
|
||||
any directories within the module matching any of the patterns
|
||||
are now omitted when walking directories, such as with `gofumpt -w .`.
|
||||
|
||||
Module information is now loaded via Go's [`x/mod/modfile` package](https://pkg.go.dev/golang.org/x/mod/modfile)
|
||||
rather than executing `go mod edit -json`, which is way faster.
|
||||
This should result in moderate speed-ups when formatting many directories.
|
||||
|
||||
## [v0.8.0] - 2025-04-13
|
||||
|
||||
This release is based on Go 1.24's gofmt, and requires Go 1.23 or later.
|
||||
|
||||
The following changes are included:
|
||||
|
||||
* Fail with `-d` if formatting any file resulted in a diff - #114
|
||||
* Do not panic when a `go.mod` file is missing a `go` directive - #317
|
||||
|
||||
## [v0.7.0] - 2024-08-16
|
||||
|
||||
This release is based on Go 1.23.0's gofmt, and requires Go 1.22 or later.
|
||||
|
||||
The following changes are included:
|
||||
|
||||
* Group `internal/...` imported packages as standard library - #307
|
||||
|
||||
## [v0.6.0] - 2024-01-28
|
||||
|
||||
This release is based on Go 1.21's gofmt, and requires Go 1.20 or later.
|
||||
|
||||
The following changes are included:
|
||||
|
||||
* Support `go` version strings from newer go.mod files - [#280]
|
||||
* Consider simple error checks even if they use the `=` operator - [#271]
|
||||
* Ignore `//line` directives to avoid panics - [#288]
|
||||
|
||||
## [v0.5.0] - 2023-04-09
|
||||
|
||||
This release is based on Go 1.20's gofmt, and requires Go 1.19 or later.
|
||||
|
||||
The biggest change in this release is that we now vendor copies of the packages
|
||||
`go/format`, `go/printer`, and `go/doc/comment` on top of `cmd/gofmt` itself.
|
||||
This allows for each gofumpt release to format code in exactly the same way
|
||||
no matter what Go version is used to build it, as Go versions can change those
|
||||
three packages in ways that alter formatting behavior.
|
||||
|
||||
This vendoring adds a small amount of duplication when using the
|
||||
`mvdan.cc/gofumpt/format` library, but it's the only way to make gofumpt
|
||||
versions consistent in their behavior and formatting, just like gofmt.
|
||||
|
||||
The jump to Go 1.20's `go/printer` should also bring a small performance
|
||||
improvement, as we contributed patches to make printing about 25% faster:
|
||||
|
||||
* https://go.dev/cl/412555
|
||||
* https://go.dev/cl/412557
|
||||
* https://go.dev/cl/424924
|
||||
|
||||
The following changes are included as well:
|
||||
|
||||
* Skip `testdata` dirs by default like we already do for `vendor` - [#260]
|
||||
* Avoid inserting newlines incorrectly in some func signatures - [#235]
|
||||
* Avoid joining some comments with the previous line - [#256]
|
||||
* Fix `gofumpt -version` for release archives - [#253]
|
||||
|
||||
## [v0.4.0] - 2022-09-27
|
||||
|
||||
This release is based on Go 1.19's gofmt, and requires Go 1.18 or later.
|
||||
We recommend building gofumpt with Go 1.19 for the best formatting results.
|
||||
|
||||
The jump from Go 1.18 brings diffing in pure Go, removing the need to exec `diff`,
|
||||
and a small parsing speed-up thanks to `go/parser.SkipObjectResolution`.
|
||||
|
||||
The following formatting fixes are included as well:
|
||||
|
||||
* Allow grouping declarations with comments - [#212]
|
||||
* Properly measure the length of case clauses - [#217]
|
||||
* Fix a few crashes found by Go's native fuzzing
|
||||
|
||||
## [v0.3.1] - 2022-03-21
|
||||
|
||||
This bugfix release resolves a number of issues:
|
||||
|
||||
* Avoid "too many open files" error regression introduced by [v0.3.0] - [#208]
|
||||
* Use the `go.mod` relative to each Go file when deriving flag defaults - [#211]
|
||||
* Remove unintentional debug prints when directly formatting files
|
||||
|
||||
## [v0.3.0] - 2022-02-22
|
||||
|
||||
This is gofumpt's third major release, based on Go 1.18's gofmt.
|
||||
The jump from Go 1.17's gofmt should bring a noticeable speed-up,
|
||||
as the tool can now format many files concurrently.
|
||||
On an 8-core laptop, formatting a large codebase is 4x as fast.
|
||||
|
||||
The following [formatting rules](https://github.com/mvdan/gofumpt#Added-rules) are added:
|
||||
|
||||
* Functions should separate `) {` where the indentation helps readability
|
||||
* Field lists should not have leading or trailing empty lines
|
||||
|
||||
The following changes are included as well:
|
||||
|
||||
* Generated files are now fully formatted when given as explicit arguments
|
||||
* Prepare for Go 1.18's module workspaces, which could cause errors
|
||||
* Import paths sharing a prefix with the current module path are no longer
|
||||
grouped with standard library imports
|
||||
* `format.Options` gains a `ModulePath` field per the last bullet point
|
||||
|
||||
## [v0.2.1] - 2021-12-12
|
||||
|
||||
This bugfix release resolves a number of issues:
|
||||
|
||||
* Add deprecated flags `-s` and `-r` once again, now giving useful errors
|
||||
* Avoid a panic with certain function declaration styles
|
||||
* Don't group interface members of different kinds
|
||||
* Account for leading comments in composite literals
|
||||
|
||||
## [v0.2.0] - 2021-11-10
|
||||
|
||||
This is gofumpt's second major release, based on Go 1.17's gofmt.
|
||||
The jump from Go 1.15's gofmt should bring a mild speed-up,
|
||||
as walking directories with `filepath.WalkDir` uses fewer syscalls.
|
||||
|
||||
gofumports is now removed, after being deprecated in [v0.1.0].
|
||||
Its main purpose was IDE integration; it is now recommended to use gopls,
|
||||
which in turn implements goimports and supports gofumpt natively.
|
||||
IDEs which don't integrate with gopls (such as GoLand) implement goimports too,
|
||||
so it is safe to use gofumpt as their "format on save" command.
|
||||
See the [installation instructions](https://github.com/mvdan/gofumpt#Installation)
|
||||
for more details.
|
||||
|
||||
The following [formatting rules](https://github.com/mvdan/gofumpt#Added-rules) are added:
|
||||
|
||||
* Composite literals should not have leading or trailing empty lines
|
||||
* No empty lines following an assignment operator
|
||||
* Functions using an empty line for readability should use a `) {` line instead
|
||||
* Remove unnecessary empty lines from interfaces
|
||||
|
||||
Finally, the following changes are made to the gofumpt tool:
|
||||
|
||||
* Initial support for Go 1.18's type parameters is added
|
||||
* The `-r` flag is removed in favor of `gofmt -r`
|
||||
* The `-s` flag is removed as it is always enabled
|
||||
* Vendor directories are skipped unless given as explicit arguments
|
||||
* The added rules are not applied to generated Go files
|
||||
* The `format` Go API now also applies the `gofmt -s` simplification
|
||||
* Add support for `//gofumpt:diagnose` comments
|
||||
|
||||
## [v0.1.1] - 2021-03-11
|
||||
|
||||
This bugfix release backports fixes for a few issues:
|
||||
|
||||
* Keep leading empty lines in func bodies if they help readability
|
||||
* Avoid breaking comment alignment on empty field lists
|
||||
* Add support for `//go-sumtype:` directives
|
||||
|
||||
## [v0.1.0] - 2021-01-05
|
||||
|
||||
This is gofumpt's first release, based on Go 1.15.x. It solidifies the features
|
||||
which have worked well for over a year.
|
||||
|
||||
This release will be the last to include `gofumports`, the fork of `goimports`
|
||||
which applies `gofumpt`'s rules on top of updating the Go import lines. Users
|
||||
who were relying on `goimports` in their editors or IDEs to apply both `gofumpt`
|
||||
and `goimports` in a single step should switch to gopls, the official Go
|
||||
language server. It is supported by many popular editors such as VS Code and
|
||||
Vim, and already bundles gofumpt support. Instructions are available [in the
|
||||
README](https://github.com/mvdan/gofumpt).
|
||||
|
||||
`gofumports` also added maintenance work and potential confusion to end users.
|
||||
In the future, there will only be one way to use `gofumpt` from the command
|
||||
line. We also have a [Go API](https://pkg.go.dev/mvdan.cc/gofumpt/format) for
|
||||
those building programs with gofumpt.
|
||||
|
||||
Finally, this release adds the `-version` flag, to print the tool's own version.
|
||||
The flag will work for "master" builds too.
|
||||
|
||||
[v0.9.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.9.0
|
||||
[v0.8.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.8.0
|
||||
[v0.7.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.7.0
|
||||
|
||||
[v0.6.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.6.0
|
||||
[#271]: https://github.com/mvdan/gofumpt/issues/271
|
||||
[#280]: https://github.com/mvdan/gofumpt/issues/280
|
||||
[#288]: https://github.com/mvdan/gofumpt/issues/288
|
||||
|
||||
[v0.5.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.5.0
|
||||
[#235]: https://github.com/mvdan/gofumpt/issues/235
|
||||
[#253]: https://github.com/mvdan/gofumpt/issues/253
|
||||
[#256]: https://github.com/mvdan/gofumpt/issues/256
|
||||
[#260]: https://github.com/mvdan/gofumpt/issues/260
|
||||
|
||||
[v0.4.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.4.0
|
||||
[#212]: https://github.com/mvdan/gofumpt/issues/212
|
||||
[#217]: https://github.com/mvdan/gofumpt/issues/217
|
||||
|
||||
[v0.3.1]: https://github.com/mvdan/gofumpt/releases/tag/v0.3.1
|
||||
[#208]: https://github.com/mvdan/gofumpt/issues/208
|
||||
[#211]: https://github.com/mvdan/gofumpt/pull/211
|
||||
|
||||
[v0.3.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.3.0
|
||||
[v0.2.1]: https://github.com/mvdan/gofumpt/releases/tag/v0.2.1
|
||||
[v0.2.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.2.0
|
||||
[v0.1.1]: https://github.com/mvdan/gofumpt/releases/tag/v0.1.1
|
||||
[v0.1.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.1.0
|
||||
27
vendor/mvdan.cc/gofumpt/LICENSE
vendored
Normal file
27
vendor/mvdan.cc/gofumpt/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2019, Daniel Martí. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
27
vendor/mvdan.cc/gofumpt/LICENSE.google
vendored
Normal file
27
vendor/mvdan.cc/gofumpt/LICENSE.google
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
698
vendor/mvdan.cc/gofumpt/README.md
vendored
Normal file
698
vendor/mvdan.cc/gofumpt/README.md
vendored
Normal file
|
|
@ -0,0 +1,698 @@
|
|||
# gofumpt
|
||||
|
||||
[](https://pkg.go.dev/mvdan.cc/gofumpt/format)
|
||||
|
||||
go install mvdan.cc/gofumpt@latest
|
||||
|
||||
Enforce a stricter format than `gofmt`, while being backwards compatible.
|
||||
That is, `gofumpt` is happy with a subset of the formats that `gofmt` is happy with.
|
||||
|
||||
The tool is a fork of `gofmt` as of Go 1.25.0, and requires Go 1.24 or later.
|
||||
It can be used as a drop-in replacement to format your Go code,
|
||||
and running `gofmt` after `gofumpt` should produce no changes.
|
||||
For example:
|
||||
|
||||
gofumpt -l -w .
|
||||
|
||||
Some of the Go source files in this repository belong to the Go project.
|
||||
The project includes copies of `go/printer` and `go/doc/comment` as of Go 1.25.0
|
||||
to ensure consistent formatting independent of what Go version is being used.
|
||||
The [added formatting rules](#Added-rules) are implemented in the `format` package.
|
||||
|
||||
`vendor` and `testdata` directories are skipped unless given as explicit arguments.
|
||||
Similarly, the added rules do not apply to generated Go files unless they are
|
||||
given as explicit arguments.
|
||||
|
||||
[`ignore` directives](https://go.dev/ref/mod#go-mod-file-ignore) in `go.mod` files are obeyed as well,
|
||||
unless directories or files within them are given as explicit arguments.
|
||||
|
||||
Finally, note that the `-r` rewrite flag is removed in favor of `gofmt -r`,
|
||||
and the `-s` flag is hidden as it is always enabled.
|
||||
|
||||
### Added rules
|
||||
|
||||
**No empty lines following an assignment operator**
|
||||
|
||||
<details><summary><i>Example</i></summary>
|
||||
|
||||
```go
|
||||
func foo() {
|
||||
foo :=
|
||||
"bar"
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
func foo() {
|
||||
foo := "bar"
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
**No empty lines around function bodies**
|
||||
|
||||
<details><summary><i>Example</i></summary>
|
||||
|
||||
```go
|
||||
func foo() {
|
||||
|
||||
println("bar")
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
func foo() {
|
||||
println("bar")
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
**Functions should separate `) {` where the indentation helps readability**
|
||||
|
||||
<details><summary><i>Example</i></summary>
|
||||
|
||||
```go
|
||||
func foo(s string,
|
||||
i int) {
|
||||
println("bar")
|
||||
}
|
||||
|
||||
// With an empty line it's slightly better, but still not great.
|
||||
func bar(s string,
|
||||
i int) {
|
||||
|
||||
println("bar")
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
func foo(s string,
|
||||
i int,
|
||||
) {
|
||||
println("bar")
|
||||
}
|
||||
|
||||
// With an empty line it's slightly better, but still not great.
|
||||
func bar(s string,
|
||||
i int,
|
||||
) {
|
||||
println("bar")
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
**No empty lines around a lone statement (or comment) in a block**
|
||||
|
||||
<details><summary><i>Example</i></summary>
|
||||
|
||||
```go
|
||||
if err != nil {
|
||||
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
**No empty lines before a simple error check**
|
||||
|
||||
<details><summary><i>Example</i></summary>
|
||||
|
||||
```go
|
||||
foo, err := processFoo()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
foo, err := processFoo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
**Composite literals should use newlines consistently**
|
||||
|
||||
<details><summary><i>Example</i></summary>
|
||||
|
||||
```go
|
||||
// A newline before or after an element requires newlines for the opening and
|
||||
// closing braces.
|
||||
var ints = []int{1, 2,
|
||||
3, 4}
|
||||
|
||||
// A newline between consecutive elements requires a newline between all
|
||||
// elements.
|
||||
var matrix = [][]int{
|
||||
{1},
|
||||
{2}, {
|
||||
3,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
var ints = []int{
|
||||
1, 2,
|
||||
3, 4,
|
||||
}
|
||||
|
||||
var matrix = [][]int{
|
||||
{1},
|
||||
{2},
|
||||
{
|
||||
3,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
**Empty field lists should use a single line**
|
||||
|
||||
<details><summary><i>Example</i></summary>
|
||||
|
||||
```go
|
||||
var V interface {
|
||||
} = 3
|
||||
|
||||
type T struct {
|
||||
}
|
||||
|
||||
func F(
|
||||
)
|
||||
```
|
||||
|
||||
```go
|
||||
var V interface{} = 3
|
||||
|
||||
type T struct{}
|
||||
|
||||
func F()
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
**`std` imports must be in a separate group at the top**
|
||||
|
||||
<details><summary><i>Example</i></summary>
|
||||
|
||||
```go
|
||||
import (
|
||||
"foo.com/bar"
|
||||
|
||||
"io"
|
||||
|
||||
"io/ioutil"
|
||||
)
|
||||
```
|
||||
|
||||
```go
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"foo.com/bar"
|
||||
)
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
**Short case clauses should take a single line**
|
||||
|
||||
<details><summary><i>Example</i></summary>
|
||||
|
||||
```go
|
||||
switch c {
|
||||
case 'a', 'b',
|
||||
'c', 'd':
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
switch c {
|
||||
case 'a', 'b', 'c', 'd':
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
**Multiline top-level declarations must be separated by empty lines**
|
||||
|
||||
<details><summary><i>Example</i></summary>
|
||||
|
||||
```go
|
||||
func foo() {
|
||||
println("multiline foo")
|
||||
}
|
||||
func bar() {
|
||||
println("multiline bar")
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
func foo() {
|
||||
println("multiline foo")
|
||||
}
|
||||
|
||||
func bar() {
|
||||
println("multiline bar")
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
**Single var declarations should not be grouped with parentheses**
|
||||
|
||||
<details><summary><i>Example</i></summary>
|
||||
|
||||
```go
|
||||
var (
|
||||
foo = "bar"
|
||||
)
|
||||
```
|
||||
|
||||
```go
|
||||
var foo = "bar"
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
**Contiguous top-level declarations should be grouped together**
|
||||
|
||||
<details><summary><i>Example</i></summary>
|
||||
|
||||
```go
|
||||
var nicer = "x"
|
||||
var with = "y"
|
||||
var alignment = "z"
|
||||
```
|
||||
|
||||
```go
|
||||
var (
|
||||
nicer = "x"
|
||||
with = "y"
|
||||
alignment = "z"
|
||||
)
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
**Simple var-declaration statements should use short assignments**
|
||||
|
||||
<details><summary><i>Example</i></summary>
|
||||
|
||||
```go
|
||||
var s = "somestring"
|
||||
```
|
||||
|
||||
```go
|
||||
s := "somestring"
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
**The `-s` code simplification flag is enabled by default**
|
||||
|
||||
<details><summary><i>Example</i></summary>
|
||||
|
||||
```go
|
||||
var _ = [][]int{[]int{1}}
|
||||
```
|
||||
|
||||
```go
|
||||
var _ = [][]int{{1}}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
**Octal integer literals should use the `0o` prefix on modules using Go 1.13 and later**
|
||||
|
||||
<details><summary><i>Example</i></summary>
|
||||
|
||||
```go
|
||||
const perm = 0755
|
||||
```
|
||||
|
||||
```go
|
||||
const perm = 0o755
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
**Comments which aren't Go directives should start with a whitespace**
|
||||
|
||||
<details><summary><i>Example</i></summary>
|
||||
|
||||
```go
|
||||
//go:noinline
|
||||
|
||||
//Foo is awesome.
|
||||
func Foo() {}
|
||||
```
|
||||
|
||||
```go
|
||||
//go:noinline
|
||||
|
||||
// Foo is awesome.
|
||||
func Foo() {}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
**Composite literals should not have leading or trailing empty lines**
|
||||
|
||||
<details><summary><i>Example</i></summary>
|
||||
|
||||
```go
|
||||
var _ = []string{
|
||||
|
||||
"foo",
|
||||
|
||||
}
|
||||
|
||||
var _ = map[string]string{
|
||||
|
||||
"foo": "bar",
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
var _ = []string{
|
||||
"foo",
|
||||
}
|
||||
|
||||
var _ = map[string]string{
|
||||
"foo": "bar",
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
**Field lists should not have leading or trailing empty lines**
|
||||
|
||||
<details><summary><i>Example</i></summary>
|
||||
|
||||
```go
|
||||
type Person interface {
|
||||
|
||||
Name() string
|
||||
|
||||
Age() int
|
||||
|
||||
}
|
||||
|
||||
type ZeroFields struct {
|
||||
|
||||
// No fields are needed here.
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
type Person interface {
|
||||
Name() string
|
||||
|
||||
Age() int
|
||||
}
|
||||
|
||||
type ZeroFields struct {
|
||||
// No fields are needed here.
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Extra rules behind `-extra`
|
||||
|
||||
**Adjacent parameters with the same type should be grouped together**
|
||||
|
||||
<details><summary><i>Example</i></summary>
|
||||
|
||||
```go
|
||||
func Foo(bar string, baz string) {}
|
||||
```
|
||||
|
||||
```go
|
||||
func Foo(bar, baz string) {}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
**Avoid naked returns for the sake of clarity**
|
||||
|
||||
<details><summary><i>Example</i></summary>
|
||||
|
||||
```go
|
||||
func Foo() (err error) {
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
func Foo() (err error) {
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Installation
|
||||
|
||||
`gofumpt` is a replacement for `gofmt`, so you can simply `go install` it as
|
||||
described at the top of this README and use it.
|
||||
|
||||
When using an IDE or editor with Go integration based on `gopls`,
|
||||
it's best to configure the editor to use the `gofumpt` support built into `gopls`.
|
||||
|
||||
The instructions below show how to set up `gofumpt` for some of the
|
||||
major editors out there.
|
||||
|
||||
#### Visual Studio Code
|
||||
|
||||
Enable the language server following [the official docs](https://github.com/golang/vscode-go#readme),
|
||||
and then enable gopls's `gofumpt` option. Note that VS Code will complain about
|
||||
the `gopls` settings, but they will still work.
|
||||
|
||||
```json
|
||||
"go.useLanguageServer": true,
|
||||
"gopls": {
|
||||
"formatting.gofumpt": true,
|
||||
},
|
||||
```
|
||||
|
||||
#### GoLand
|
||||
|
||||
GoLand doesn't use `gopls` so it should be configured to use `gofumpt` directly.
|
||||
Once `gofumpt` is installed, follow the steps below:
|
||||
|
||||
- Open **Settings** (File > Settings)
|
||||
- Open the **Tools** section
|
||||
- Find the *File Watchers* sub-section
|
||||
- Click on the `+` on the right side to add a new file watcher
|
||||
- Choose *Custom Template*
|
||||
|
||||
When a window asks for settings, you can enter the following:
|
||||
|
||||
* File Types: Select all .go files
|
||||
* Scope: Project Files
|
||||
* Program: Select your `gofumpt` executable
|
||||
* Arguments: `-w $FilePath$`
|
||||
* Output path to refresh: `$FilePath$`
|
||||
* Working directory: `$ProjectFileDir$`
|
||||
* Environment variables: `GOROOT=$GOROOT$;GOPATH=$GOPATH$;PATH=$GoBinDirs$`
|
||||
|
||||
To avoid unnecessary runs, you should disable all checkboxes in the *Advanced* section.
|
||||
|
||||
#### Vim
|
||||
|
||||
The configuration depends on the plugin you are using: [vim-go](https://github.com/fatih/vim-go)
|
||||
or [govim](https://github.com/govim/govim).
|
||||
|
||||
##### vim-go
|
||||
|
||||
To configure `gopls` to use `gofumpt`:
|
||||
|
||||
```vim
|
||||
let g:go_fmt_command="gopls"
|
||||
let g:go_gopls_gofumpt=1
|
||||
```
|
||||
|
||||
##### govim
|
||||
|
||||
To configure `gopls` to use `gofumpt`:
|
||||
|
||||
```vim
|
||||
call govim#config#Set("Gofumpt", 1)
|
||||
```
|
||||
|
||||
#### Neovim
|
||||
|
||||
When using [`lspconfig`](https://github.com/neovim/nvim-lspconfig), pass the `gofumpt` setting to `gopls`:
|
||||
|
||||
```lua
|
||||
require('lspconfig').gopls.setup({
|
||||
settings = {
|
||||
gopls = {
|
||||
gofumpt = true
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### Emacs
|
||||
|
||||
For [lsp-mode](https://emacs-lsp.github.io/lsp-mode/) users on version 8.0.0 or higher:
|
||||
|
||||
```elisp
|
||||
(setq lsp-go-use-gofumpt t)
|
||||
```
|
||||
|
||||
For users of `lsp-mode` before `8.0.0`:
|
||||
|
||||
```elisp
|
||||
(lsp-register-custom-settings
|
||||
'(("gopls.gofumpt" t)))
|
||||
```
|
||||
|
||||
For [eglot](https://github.com/joaotavora/eglot) users:
|
||||
|
||||
```elisp
|
||||
(setq-default eglot-workspace-configuration
|
||||
'((:gopls . ((gofumpt . t)))))
|
||||
```
|
||||
|
||||
#### Helix
|
||||
|
||||
When using the `gopls` language server, modify the Go settings in `~/.config/helix/languages.toml`:
|
||||
|
||||
```toml
|
||||
[language-server.gopls.config]
|
||||
"formatting.gofumpt" = true
|
||||
```
|
||||
|
||||
#### Sublime Text
|
||||
|
||||
With ST4, install the Sublime Text LSP extension according to [the documentation](https://github.com/sublimelsp/LSP),
|
||||
and enable `gopls`'s `gofumpt` option in the LSP package settings,
|
||||
including setting `lsp_format_on_save` to `true`.
|
||||
|
||||
```json
|
||||
"lsp_format_on_save": true,
|
||||
"clients":
|
||||
{
|
||||
"gopls":
|
||||
{
|
||||
"enabled": true,
|
||||
"initializationOptions": {
|
||||
"gofumpt": true,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Zed
|
||||
For `gofumpt` to be used in Zed, you need to set the `gofumpt` option in the LSP settings. This is done by providing the `"gofumpt": true` in `initialization_options`.
|
||||
|
||||
```json
|
||||
"lsp": {
|
||||
"gopls": {
|
||||
"initialization_options": {
|
||||
"gofumpt": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Roadmap
|
||||
|
||||
This tool is a place to experiment. In the long term, the features that work
|
||||
well might be proposed for `gofmt` itself.
|
||||
|
||||
The tool is also compatible with `gofmt` and is aimed to be stable, so you can
|
||||
rely on it for your code as long as you pin a version of it.
|
||||
|
||||
### Frequently Asked Questions
|
||||
|
||||
> Why attempt to replace `gofmt` instead of building on top of it?
|
||||
|
||||
Our design is to build on top of `gofmt`, and we'll never add rules which
|
||||
disagree with its formatting. So we extend `gofmt` rather than compete with it.
|
||||
|
||||
The tool is a modified copy of `gofmt`, for the purpose of allowing its use as a
|
||||
drop-in replacement in editors and scripts.
|
||||
|
||||
> Why are my module imports being grouped with standard library imports?
|
||||
|
||||
Any import paths that don't start with a domain name like `foo.com` are
|
||||
effectively [reserved by the Go toolchain](https://github.com/golang/go/issues/32819).
|
||||
Third party modules should either start with a domain name,
|
||||
even a local one like `foo.local`, or use [a reserved path prefix](https://github.com/golang/go/issues/37641).
|
||||
|
||||
For backwards compatibility with modules set up before these rules were clear,
|
||||
`gofumpt` will treat any import path sharing a prefix with the current module
|
||||
path as third party. For example, if the current module is `mycorp/mod1`, then
|
||||
all import paths in `mycorp/...` will be considered third party.
|
||||
|
||||
> How can I use `gofumpt` if I already use `goimports` to replace `gofmt`?
|
||||
|
||||
Most editors have replaced the `goimports` program with the same functionality
|
||||
provided by a language server like `gopls`. This mechanism is significantly
|
||||
faster and more powerful, since the language server has more information that is
|
||||
kept up to date, necessary to add missing imports.
|
||||
|
||||
As such, the general recommendation is to let your editor fix your imports -
|
||||
either via `gopls`, such as VSCode or vim-go, or via their own custom
|
||||
implementation, such as GoLand. Then follow the install instructions above to
|
||||
enable the use of `gofumpt` instead of `gofmt`.
|
||||
|
||||
If you want to avoid integrating with `gopls`, and are OK with the overhead of
|
||||
calling `goimports` from scratch on each save, you should be able to call both
|
||||
tools; for example, `goimports file.go && gofumpt file.go`.
|
||||
|
||||
### Contributing
|
||||
|
||||
Issues and pull requests are welcome! Please open an issue to discuss a feature
|
||||
before sending a pull request.
|
||||
|
||||
We also use the `#gofumpt` channel over at the
|
||||
[Gophers Slack](https://invite.slack.golangbridge.org/) to chat.
|
||||
|
||||
When reporting a formatting bug, insert a `//gofumpt:diagnose` comment.
|
||||
The comment will be rewritten to include useful debugging information.
|
||||
For instance:
|
||||
|
||||
```
|
||||
$ cat f.go
|
||||
package p
|
||||
|
||||
//gofumpt:diagnose
|
||||
$ gofumpt f.go
|
||||
package p
|
||||
|
||||
//gofumpt:diagnose v0.1.1-0.20211103104632-bdfa3b02e50a -lang=go1.16
|
||||
```
|
||||
|
||||
### License
|
||||
|
||||
Note that much of the code is copied from Go's `gofmt` command. You can tell
|
||||
which files originate from the Go repository from their copyright headers. Their
|
||||
license file is `LICENSE.google`.
|
||||
|
||||
`gofumpt`'s original source files are also under the 3-clause BSD license, with
|
||||
the separate file `LICENSE`.
|
||||
5
vendor/mvdan.cc/gofumpt/doc.go
vendored
Normal file
5
vendor/mvdan.cc/gofumpt/doc.go
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (c) 2023, Daniel Martí <mvdan@mvdan.cc>
|
||||
// See LICENSE for licensing information
|
||||
|
||||
// gofumpt enforces a stricter format than gofmt, while being backwards compatible.
|
||||
package main
|
||||
1136
vendor/mvdan.cc/gofumpt/format/format.go
vendored
Normal file
1136
vendor/mvdan.cc/gofumpt/format/format.go
vendored
Normal file
File diff suppressed because it is too large
Load diff
113
vendor/mvdan.cc/gofumpt/format/rewrite.go
vendored
Normal file
113
vendor/mvdan.cc/gofumpt/format/rewrite.go
vendored
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
// Copyright 2009 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 format
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"reflect"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Values/types for special cases.
|
||||
var (
|
||||
identType = reflect.TypeOf((*ast.Ident)(nil))
|
||||
objectPtrType = reflect.TypeOf((*ast.Object)(nil))
|
||||
positionType = reflect.TypeOf(token.NoPos)
|
||||
callExprType = reflect.TypeOf((*ast.CallExpr)(nil))
|
||||
)
|
||||
|
||||
func isWildcard(s string) bool {
|
||||
rune, size := utf8.DecodeRuneInString(s)
|
||||
return size == len(s) && unicode.IsLower(rune)
|
||||
}
|
||||
|
||||
// match reports whether pattern matches val,
|
||||
// recording wildcard submatches in m.
|
||||
// If m == nil, match checks whether pattern == val.
|
||||
func match(m map[string]reflect.Value, pattern, val reflect.Value) bool {
|
||||
// Wildcard matches any expression. If it appears multiple
|
||||
// times in the pattern, it must match the same expression
|
||||
// each time.
|
||||
if m != nil && pattern.IsValid() && pattern.Type() == identType {
|
||||
name := pattern.Interface().(*ast.Ident).Name
|
||||
if isWildcard(name) && val.IsValid() {
|
||||
// wildcards only match valid (non-nil) expressions.
|
||||
if _, ok := val.Interface().(ast.Expr); ok && !val.IsNil() {
|
||||
if old, ok := m[name]; ok {
|
||||
return match(nil, old, val)
|
||||
}
|
||||
m[name] = val
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, pattern and val must match recursively.
|
||||
if !pattern.IsValid() || !val.IsValid() {
|
||||
return !pattern.IsValid() && !val.IsValid()
|
||||
}
|
||||
if pattern.Type() != val.Type() {
|
||||
return false
|
||||
}
|
||||
|
||||
// Special cases.
|
||||
switch pattern.Type() {
|
||||
case identType:
|
||||
// For identifiers, only the names need to match
|
||||
// (and none of the other *ast.Object information).
|
||||
// This is a common case, handle it all here instead
|
||||
// of recursing down any further via reflection.
|
||||
p := pattern.Interface().(*ast.Ident)
|
||||
v := val.Interface().(*ast.Ident)
|
||||
return p == nil && v == nil || p != nil && v != nil && p.Name == v.Name
|
||||
case objectPtrType, positionType:
|
||||
// object pointers and token positions always match
|
||||
return true
|
||||
case callExprType:
|
||||
// For calls, the Ellipsis fields (token.Position) must
|
||||
// match since that is how f(x) and f(x...) are different.
|
||||
// Check them here but fall through for the remaining fields.
|
||||
p := pattern.Interface().(*ast.CallExpr)
|
||||
v := val.Interface().(*ast.CallExpr)
|
||||
if p.Ellipsis.IsValid() != v.Ellipsis.IsValid() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
p := reflect.Indirect(pattern)
|
||||
v := reflect.Indirect(val)
|
||||
if !p.IsValid() || !v.IsValid() {
|
||||
return !p.IsValid() && !v.IsValid()
|
||||
}
|
||||
|
||||
switch p.Kind() {
|
||||
case reflect.Slice:
|
||||
if p.Len() != v.Len() {
|
||||
return false
|
||||
}
|
||||
for i := range p.Len() {
|
||||
if !match(m, p.Index(i), v.Index(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
case reflect.Struct:
|
||||
for i := range p.NumField() {
|
||||
if !match(m, p.Field(i), v.Field(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
case reflect.Interface:
|
||||
return match(m, p.Elem(), v.Elem())
|
||||
}
|
||||
|
||||
// Handle token integers, etc.
|
||||
return p.Interface() == v.Interface()
|
||||
}
|
||||
169
vendor/mvdan.cc/gofumpt/format/simplify.go
vendored
Normal file
169
vendor/mvdan.cc/gofumpt/format/simplify.go
vendored
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
// Copyright 2010 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 format
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type simplifier struct{}
|
||||
|
||||
func (s simplifier) Visit(node ast.Node) ast.Visitor {
|
||||
switch n := node.(type) {
|
||||
case *ast.CompositeLit:
|
||||
// array, slice, and map composite literals may be simplified
|
||||
outer := n
|
||||
var keyType, eltType ast.Expr
|
||||
switch typ := outer.Type.(type) {
|
||||
case *ast.ArrayType:
|
||||
eltType = typ.Elt
|
||||
case *ast.MapType:
|
||||
keyType = typ.Key
|
||||
eltType = typ.Value
|
||||
}
|
||||
|
||||
if eltType != nil {
|
||||
var ktyp reflect.Value
|
||||
if keyType != nil {
|
||||
ktyp = reflect.ValueOf(keyType)
|
||||
}
|
||||
typ := reflect.ValueOf(eltType)
|
||||
for i, x := range outer.Elts {
|
||||
px := &outer.Elts[i]
|
||||
// look at value of indexed/named elements
|
||||
if t, ok := x.(*ast.KeyValueExpr); ok {
|
||||
if keyType != nil {
|
||||
s.simplifyLiteral(ktyp, keyType, t.Key, &t.Key)
|
||||
}
|
||||
x = t.Value
|
||||
px = &t.Value
|
||||
}
|
||||
s.simplifyLiteral(typ, eltType, x, px)
|
||||
}
|
||||
// node was simplified - stop walk (there are no subnodes to simplify)
|
||||
return nil
|
||||
}
|
||||
|
||||
case *ast.SliceExpr:
|
||||
// a slice expression of the form: s[a:len(s)]
|
||||
// can be simplified to: s[a:]
|
||||
// if s is "simple enough" (for now we only accept identifiers)
|
||||
//
|
||||
// Note: This may not be correct because len may have been redeclared in
|
||||
// the same package. However, this is extremely unlikely and so far
|
||||
// (April 2022, after years of supporting this rewrite feature)
|
||||
// has never come up, so let's keep it working as is (see also #15153).
|
||||
//
|
||||
// Also note that this code used to use go/ast's object tracking,
|
||||
// which was removed in exchange for go/parser.Mode.SkipObjectResolution.
|
||||
// False positives are extremely unlikely as described above,
|
||||
// and go/ast's object tracking is incomplete in any case.
|
||||
if n.Max != nil {
|
||||
// - 3-index slices always require the 2nd and 3rd index
|
||||
break
|
||||
}
|
||||
if s, _ := n.X.(*ast.Ident); s != nil {
|
||||
// the array/slice object is a single identifier
|
||||
if call, _ := n.High.(*ast.CallExpr); call != nil && len(call.Args) == 1 && !call.Ellipsis.IsValid() {
|
||||
// the high expression is a function call with a single argument
|
||||
if fun, _ := call.Fun.(*ast.Ident); fun != nil && fun.Name == "len" {
|
||||
// the function called is "len"
|
||||
if arg, _ := call.Args[0].(*ast.Ident); arg != nil && arg.Name == s.Name {
|
||||
// the len argument is the array/slice object
|
||||
n.High = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Note: We could also simplify slice expressions of the form s[0:b] to s[:b]
|
||||
// but we leave them as is since sometimes we want to be very explicit
|
||||
// about the lower bound.
|
||||
// An example where the 0 helps:
|
||||
// x, y, z := b[0:2], b[2:4], b[4:6]
|
||||
// An example where it does not:
|
||||
// x, y := b[:n], b[n:]
|
||||
|
||||
case *ast.RangeStmt:
|
||||
// - a range of the form: for x, _ = range v {...}
|
||||
// can be simplified to: for x = range v {...}
|
||||
// - a range of the form: for _ = range v {...}
|
||||
// can be simplified to: for range v {...}
|
||||
if isBlank(n.Value) {
|
||||
n.Value = nil
|
||||
}
|
||||
if isBlank(n.Key) && n.Value == nil {
|
||||
n.Key = nil
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s simplifier) simplifyLiteral(typ reflect.Value, astType, x ast.Expr, px *ast.Expr) {
|
||||
ast.Walk(s, x) // simplify x
|
||||
|
||||
// if the element is a composite literal and its literal type
|
||||
// matches the outer literal's element type exactly, the inner
|
||||
// literal type may be omitted
|
||||
if inner, ok := x.(*ast.CompositeLit); ok {
|
||||
if match(nil, typ, reflect.ValueOf(inner.Type)) {
|
||||
inner.Type = nil
|
||||
}
|
||||
}
|
||||
// if the outer literal's element type is a pointer type *T
|
||||
// and the element is & of a composite literal of type T,
|
||||
// the inner &T may be omitted.
|
||||
if ptr, ok := astType.(*ast.StarExpr); ok {
|
||||
if addr, ok := x.(*ast.UnaryExpr); ok && addr.Op == token.AND {
|
||||
if inner, ok := addr.X.(*ast.CompositeLit); ok {
|
||||
if match(nil, reflect.ValueOf(ptr.X), reflect.ValueOf(inner.Type)) {
|
||||
inner.Type = nil // drop T
|
||||
*px = inner // drop &
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isBlank(x ast.Expr) bool {
|
||||
ident, ok := x.(*ast.Ident)
|
||||
return ok && ident.Name == "_"
|
||||
}
|
||||
|
||||
func simplify(f *ast.File) {
|
||||
// remove empty declarations such as "const ()", etc
|
||||
removeEmptyDeclGroups(f)
|
||||
|
||||
var s simplifier
|
||||
ast.Walk(s, f)
|
||||
}
|
||||
|
||||
func removeEmptyDeclGroups(f *ast.File) {
|
||||
i := 0
|
||||
for _, d := range f.Decls {
|
||||
if g, ok := d.(*ast.GenDecl); !ok || !isEmpty(f, g) {
|
||||
f.Decls[i] = d
|
||||
i++
|
||||
}
|
||||
}
|
||||
f.Decls = f.Decls[:i]
|
||||
}
|
||||
|
||||
func isEmpty(f *ast.File, g *ast.GenDecl) bool {
|
||||
if g.Doc != nil || g.Specs != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, c := range f.Comments {
|
||||
// if there is a comment in the declaration, it is not considered empty
|
||||
if g.Pos() <= c.Pos() && c.End() <= g.End() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
697
vendor/mvdan.cc/gofumpt/gofmt.go
vendored
Normal file
697
vendor/mvdan.cc/gofumpt/gofmt.go
vendored
Normal file
|
|
@ -0,0 +1,697 @@
|
|||
// Copyright 2009 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 main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/mod/modfile"
|
||||
"golang.org/x/sync/semaphore"
|
||||
|
||||
gformat "mvdan.cc/gofumpt/format"
|
||||
"mvdan.cc/gofumpt/internal/govendor/diff"
|
||||
"mvdan.cc/gofumpt/internal/govendor/go/printer"
|
||||
gversion "mvdan.cc/gofumpt/internal/version"
|
||||
)
|
||||
|
||||
//go:generate go run gen_govendor.go
|
||||
//go:generate go run . -w internal/govendor
|
||||
|
||||
var (
|
||||
// main operation modes
|
||||
list = flag.Bool("l", false, "")
|
||||
write = flag.Bool("w", false, "")
|
||||
doDiff = flag.Bool("d", false, "")
|
||||
allErrors = flag.Bool("e", false, "")
|
||||
|
||||
// debugging
|
||||
cpuprofile = flag.String("cpuprofile", "", "")
|
||||
|
||||
// gofumpt's own flags
|
||||
langVersion = flag.String("lang", "", "")
|
||||
modulePath = flag.String("modpath", "", "")
|
||||
extraRules = flag.Bool("extra", false, "")
|
||||
showVersion = flag.Bool("version", false, "")
|
||||
|
||||
// DEPRECATED
|
||||
rewriteRule = flag.String("r", "", "")
|
||||
simplifyAST = flag.Bool("s", false, "")
|
||||
)
|
||||
|
||||
var version = ""
|
||||
|
||||
// Keep these in sync with go/format/format.go.
|
||||
const (
|
||||
tabWidth = 8
|
||||
printerMode = printer.UseSpaces | printer.TabIndent | printerNormalizeNumbers
|
||||
|
||||
// printerNormalizeNumbers means to canonicalize number literal prefixes
|
||||
// and exponents while printing. See https://golang.org/doc/go1.13#gofmt.
|
||||
//
|
||||
// This value is defined in go/printer specifically for go/format and cmd/gofmt.
|
||||
printerNormalizeNumbers = 1 << 30
|
||||
)
|
||||
|
||||
// fdSem guards the number of concurrently-open file descriptors.
|
||||
//
|
||||
// For now, this is arbitrarily set to 200, based on the observation that many
|
||||
// platforms default to a kernel limit of 256. Ideally, perhaps we should derive
|
||||
// it from rlimit on platforms that support that system call.
|
||||
//
|
||||
// File descriptors opened from outside of this package are not tracked,
|
||||
// so this limit may be approximate.
|
||||
var fdSem = make(chan bool, 200)
|
||||
|
||||
var (
|
||||
fileSet = token.NewFileSet() // per process FileSet
|
||||
parserMode parser.Mode
|
||||
)
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintf(os.Stderr, `usage: gofumpt [flags] [path ...]
|
||||
-version show version and exit
|
||||
|
||||
-d display diffs instead of rewriting files
|
||||
-e report all errors (not just the first 10 on different lines)
|
||||
-l list files whose formatting differs from gofumpt's
|
||||
-w write result to (source) file instead of stdout
|
||||
-extra enable extra rules which should be vetted by a human
|
||||
|
||||
-lang str target Go version in the form "go1.X" (default from go.mod)
|
||||
-modpath str Go module path containing the source file (default from go.mod)
|
||||
`)
|
||||
}
|
||||
|
||||
func initParserMode() {
|
||||
parserMode = parser.ParseComments | parser.SkipObjectResolution
|
||||
if *allErrors {
|
||||
parserMode |= parser.AllErrors
|
||||
}
|
||||
}
|
||||
|
||||
func isGoFilename(name string) bool {
|
||||
return !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
|
||||
}
|
||||
|
||||
var rxCodeGenerated = regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`)
|
||||
|
||||
func isGenerated(file *ast.File) bool {
|
||||
for _, cg := range file.Comments {
|
||||
if cg.Pos() > file.Package {
|
||||
return false
|
||||
}
|
||||
for _, line := range cg.List {
|
||||
if rxCodeGenerated.MatchString(line.Text) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// A sequencer performs concurrent tasks that may write output, but emits that
|
||||
// output in a deterministic order.
|
||||
type sequencer struct {
|
||||
maxWeight int64
|
||||
sem *semaphore.Weighted // weighted by input bytes (an approximate proxy for memory overhead)
|
||||
prev <-chan *reporterState // 1-buffered
|
||||
}
|
||||
|
||||
// newSequencer returns a sequencer that allows concurrent tasks up to maxWeight
|
||||
// and writes tasks' output to out and err.
|
||||
func newSequencer(maxWeight int64, out, err io.Writer) *sequencer {
|
||||
sem := semaphore.NewWeighted(maxWeight)
|
||||
prev := make(chan *reporterState, 1)
|
||||
prev <- &reporterState{out: out, err: err}
|
||||
return &sequencer{
|
||||
maxWeight: maxWeight,
|
||||
sem: sem,
|
||||
prev: prev,
|
||||
}
|
||||
}
|
||||
|
||||
// exclusive is a weight that can be passed to a sequencer to cause
|
||||
// a task to be executed without any other concurrent tasks.
|
||||
const exclusive = -1
|
||||
|
||||
// Add blocks until the sequencer has enough weight to spare, then adds f as a
|
||||
// task to be executed concurrently.
|
||||
//
|
||||
// If the weight is either negative or larger than the sequencer's maximum
|
||||
// weight, Add blocks until all other tasks have completed, then the task
|
||||
// executes exclusively (blocking all other calls to Add until it completes).
|
||||
//
|
||||
// f may run concurrently in a goroutine, but its output to the passed-in
|
||||
// reporter will be sequential relative to the other tasks in the sequencer.
|
||||
//
|
||||
// If f invokes a method on the reporter, execution of that method may block
|
||||
// until the previous task has finished. (To maximize concurrency, f should
|
||||
// avoid invoking the reporter until it has finished any parallelizable work.)
|
||||
//
|
||||
// If f returns a non-nil error, that error will be reported after f's output
|
||||
// (if any) and will cause a nonzero final exit code.
|
||||
func (s *sequencer) Add(weight int64, f func(*reporter) error) {
|
||||
if weight < 0 || weight > s.maxWeight {
|
||||
weight = s.maxWeight
|
||||
}
|
||||
if err := s.sem.Acquire(context.TODO(), weight); err != nil {
|
||||
// Change the task from "execute f" to "report err".
|
||||
weight = 0
|
||||
f = func(*reporter) error { return err }
|
||||
}
|
||||
|
||||
r := &reporter{prev: s.prev}
|
||||
next := make(chan *reporterState, 1)
|
||||
s.prev = next
|
||||
|
||||
// Start f in parallel: it can run until it invokes a method on r, at which
|
||||
// point it will block until the previous task releases the output state.
|
||||
go func() {
|
||||
if err := f(r); err != nil {
|
||||
r.Report(err)
|
||||
}
|
||||
next <- r.getState() // Release the next task.
|
||||
s.sem.Release(weight)
|
||||
}()
|
||||
}
|
||||
|
||||
// AddReport prints an error to s after the output of any previously-added
|
||||
// tasks, causing the final exit code to be nonzero.
|
||||
func (s *sequencer) AddReport(err error) {
|
||||
s.Add(0, func(*reporter) error { return err })
|
||||
}
|
||||
|
||||
// GetExitCode waits for all previously-added tasks to complete, then returns an
|
||||
// exit code for the sequence suitable for passing to os.Exit.
|
||||
func (s *sequencer) GetExitCode() int {
|
||||
c := make(chan int, 1)
|
||||
s.Add(0, func(r *reporter) error {
|
||||
c <- r.ExitCode()
|
||||
return nil
|
||||
})
|
||||
return <-c
|
||||
}
|
||||
|
||||
// A reporter reports output, warnings, and errors.
|
||||
type reporter struct {
|
||||
prev <-chan *reporterState
|
||||
state *reporterState
|
||||
}
|
||||
|
||||
// reporterState carries the state of a reporter instance.
|
||||
//
|
||||
// Only one reporter at a time may have access to a reporterState.
|
||||
type reporterState struct {
|
||||
out, err io.Writer
|
||||
exitCode int
|
||||
}
|
||||
|
||||
// getState blocks until any prior reporters are finished with the reporter
|
||||
// state, then returns the state for manipulation.
|
||||
func (r *reporter) getState() *reporterState {
|
||||
if r.state == nil {
|
||||
r.state = <-r.prev
|
||||
}
|
||||
return r.state
|
||||
}
|
||||
|
||||
// Warnf emits a warning message to the reporter's error stream,
|
||||
// without changing its exit code.
|
||||
func (r *reporter) Warnf(format string, args ...any) {
|
||||
fmt.Fprintf(r.getState().err, format, args...)
|
||||
}
|
||||
|
||||
// Write emits a slice to the reporter's output stream.
|
||||
//
|
||||
// Any error is returned to the caller, and does not otherwise affect the
|
||||
// reporter's exit code.
|
||||
func (r *reporter) Write(p []byte) (int, error) {
|
||||
return r.getState().out.Write(p)
|
||||
}
|
||||
|
||||
// Report emits a non-nil error to the reporter's error stream,
|
||||
// changing its exit code to a nonzero value.
|
||||
func (r *reporter) Report(err error) {
|
||||
if err == nil {
|
||||
panic("Report with nil error")
|
||||
}
|
||||
st := r.getState()
|
||||
switch err.(type) {
|
||||
case printedDiff:
|
||||
st.exitCode = 1
|
||||
default:
|
||||
scanner.PrintError(st.err, err)
|
||||
st.exitCode = 2
|
||||
}
|
||||
}
|
||||
|
||||
func (r *reporter) ExitCode() int {
|
||||
return r.getState().exitCode
|
||||
}
|
||||
|
||||
type printedDiff struct{}
|
||||
|
||||
func (printedDiff) Error() string { return "printed a diff, exiting with status code 1" }
|
||||
|
||||
// If info == nil, we are formatting stdin instead of a file.
|
||||
// If in == nil, the source is the contents of the file with the given filename.
|
||||
func processFile(filename string, info fs.FileInfo, in io.Reader, r *reporter, explicit bool) error {
|
||||
src, err := readFile(filename, info, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileSet := token.NewFileSet()
|
||||
fragmentOk := false
|
||||
if info == nil {
|
||||
// If we are formatting stdin, we accept a program fragment in lieu of a
|
||||
// complete source file.
|
||||
fragmentOk = true
|
||||
}
|
||||
file, sourceAdj, indentAdj, err := parse(fileSet, filename, src, fragmentOk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ast.SortImports(fileSet, file)
|
||||
|
||||
// Apply gofumpt's changes before we print the code in gofumpt's format.
|
||||
|
||||
// If either -lang or -modpath aren't set, fetch them from go.mod.
|
||||
lang := *langVersion
|
||||
modpath := *modulePath
|
||||
if lang == "" || modpath == "" {
|
||||
path, err := filepath.Abs(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mod := loadModule(filepath.Dir(path)); mod != nil {
|
||||
if lang == "" {
|
||||
if mod.file.Go == nil {
|
||||
// If the go directive is missing, go 1.16 is assumed.
|
||||
// https://go.dev/ref/mod#go-mod-file-go
|
||||
lang = "go1.16"
|
||||
} else {
|
||||
lang = "go" + mod.file.Go.Version
|
||||
}
|
||||
}
|
||||
if modpath == "" {
|
||||
modpath = mod.file.Module.Mod.Path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We always apply the gofumpt formatting rules to explicit files, including stdin.
|
||||
// Otherwise, we don't apply them on generated files.
|
||||
// We also skip walking vendor directories entirely, but that happens elsewhere.
|
||||
if explicit || !isGenerated(file) {
|
||||
gformat.File(fileSet, file, gformat.Options{
|
||||
LangVersion: lang,
|
||||
ModulePath: modpath,
|
||||
ExtraRules: *extraRules,
|
||||
})
|
||||
}
|
||||
|
||||
res, err := format(fileSet, file, sourceAdj, indentAdj, src, printer.Config{Mode: printerMode, Tabwidth: tabWidth})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !bytes.Equal(src, res) {
|
||||
// formatting has changed
|
||||
if *list {
|
||||
fmt.Fprintln(r, filename)
|
||||
}
|
||||
if *write {
|
||||
if info == nil {
|
||||
panic("-w should not have been allowed with stdin")
|
||||
}
|
||||
// make a temporary backup before overwriting original
|
||||
perm := info.Mode().Perm()
|
||||
bakname, err := backupFile(filename+".", src, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fdSem <- true
|
||||
err = os.WriteFile(filename, res, perm)
|
||||
<-fdSem
|
||||
if err != nil {
|
||||
os.Rename(bakname, filename)
|
||||
return err
|
||||
}
|
||||
err = os.Remove(bakname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if *doDiff {
|
||||
newName := filepath.ToSlash(filename)
|
||||
oldName := newName + ".orig"
|
||||
r.Write(diff.Diff(oldName, src, newName, res))
|
||||
return printedDiff{}
|
||||
}
|
||||
}
|
||||
|
||||
if !*list && !*write && !*doDiff {
|
||||
_, err = r.Write(res)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// readFile reads the contents of filename, described by info.
|
||||
// If in is non-nil, readFile reads directly from it.
|
||||
// Otherwise, readFile opens and reads the file itself,
|
||||
// with the number of concurrently-open files limited by fdSem.
|
||||
func readFile(filename string, info fs.FileInfo, in io.Reader) ([]byte, error) {
|
||||
if in == nil {
|
||||
fdSem <- true
|
||||
var err error
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
in = f
|
||||
defer func() {
|
||||
f.Close()
|
||||
<-fdSem
|
||||
}()
|
||||
}
|
||||
|
||||
// Compute the file's size and read its contents with minimal allocations.
|
||||
//
|
||||
// If we have the FileInfo from filepath.WalkDir, use it to make
|
||||
// a buffer of the right size and avoid ReadAll's reallocations.
|
||||
//
|
||||
// If the size is unknown (or bogus, or overflows an int), fall back to
|
||||
// a size-independent ReadAll.
|
||||
size := -1
|
||||
if info != nil && info.Mode().IsRegular() && int64(int(info.Size())) == info.Size() {
|
||||
size = int(info.Size())
|
||||
}
|
||||
if size+1 <= 0 {
|
||||
// The file is not known to be regular, so we don't have a reliable size for it.
|
||||
var err error
|
||||
src, err := io.ReadAll(in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return src, nil
|
||||
}
|
||||
|
||||
// We try to read size+1 bytes so that we can detect modifications: if we
|
||||
// read more than size bytes, then the file was modified concurrently.
|
||||
// (If that happens, we could, say, append to src to finish the read, or
|
||||
// proceed with a truncated buffer — but the fact that it changed at all
|
||||
// indicates a possible race with someone editing the file, so we prefer to
|
||||
// stop to avoid corrupting it.)
|
||||
src := make([]byte, size+1)
|
||||
n, err := io.ReadFull(in, src)
|
||||
switch err {
|
||||
case nil, io.EOF, io.ErrUnexpectedEOF:
|
||||
// io.ReadFull returns io.EOF (for an empty file) or io.ErrUnexpectedEOF
|
||||
// (for a non-empty file) if the file was changed unexpectedly. Continue
|
||||
// with comparing file sizes in those cases.
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
if n < size {
|
||||
return nil, fmt.Errorf("error: size of %s changed during reading (from %d to %d bytes)", filename, size, n)
|
||||
} else if n > size {
|
||||
return nil, fmt.Errorf("error: size of %s changed during reading (from %d to >=%d bytes)", filename, size, len(src))
|
||||
}
|
||||
return src[:n], nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Arbitrarily limit in-flight work to 2MiB times the number of threads.
|
||||
//
|
||||
// The actual overhead for the parse tree and output will depend on the
|
||||
// specifics of the file, but this at least keeps the footprint of the process
|
||||
// roughly proportional to GOMAXPROCS.
|
||||
maxWeight := (2 << 20) * int64(runtime.GOMAXPROCS(0))
|
||||
s := newSequencer(maxWeight, os.Stdout, os.Stderr)
|
||||
|
||||
// call gofmtMain in a separate function
|
||||
// so that it can use defer and have them
|
||||
// run before the exit.
|
||||
gofmtMain(s)
|
||||
os.Exit(s.GetExitCode())
|
||||
}
|
||||
|
||||
func gofmtMain(s *sequencer) {
|
||||
// Ensure our parsed files never start with base 1,
|
||||
// to ensure that using token.NoPos+1 will panic.
|
||||
fileSet.AddFile("gofumpt_base.go", 1, 10)
|
||||
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
if *simplifyAST {
|
||||
fmt.Fprintf(os.Stderr, "warning: -s is deprecated as it is always enabled\n")
|
||||
}
|
||||
if *rewriteRule != "" {
|
||||
fmt.Fprintf(os.Stderr, `the rewrite flag is no longer available; use "gofmt -r" instead`+"\n")
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// Print the gofumpt version if the user asks for it.
|
||||
if *showVersion {
|
||||
fmt.Println(gversion.String(version))
|
||||
return
|
||||
}
|
||||
|
||||
if *cpuprofile != "" {
|
||||
fdSem <- true
|
||||
f, err := os.Create(*cpuprofile)
|
||||
if err != nil {
|
||||
s.AddReport(fmt.Errorf("creating cpu profile: %s", err))
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
f.Close()
|
||||
<-fdSem
|
||||
}()
|
||||
pprof.StartCPUProfile(f)
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
initParserMode()
|
||||
|
||||
args := flag.Args()
|
||||
if len(args) == 0 {
|
||||
if *write {
|
||||
s.AddReport(fmt.Errorf("error: cannot use -w with standard input"))
|
||||
return
|
||||
}
|
||||
s.Add(0, func(r *reporter) error {
|
||||
// TODO: test explicit==true
|
||||
return processFile("<standard input>", nil, os.Stdin, r, true)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
for _, arg := range args {
|
||||
// Walk each given argument as a directory tree.
|
||||
// If the argument is not a directory, it's always formatted as a Go file.
|
||||
// If the argument is a directory, we walk it, ignoring non-Go files.
|
||||
arg = filepath.Clean(arg) // ensure consistency
|
||||
if err := filepath.WalkDir(arg, func(path string, d fs.DirEntry, err error) error {
|
||||
explicit := path == arg
|
||||
switch {
|
||||
case err != nil:
|
||||
return err
|
||||
case d.IsDir():
|
||||
if !explicit && shouldIgnore(path) {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil // simply recurse into directories
|
||||
case explicit:
|
||||
// non-directories given as explicit arguments are always formatted
|
||||
case !isGoFilename(d.Name()):
|
||||
return nil // skip walked non-Go files
|
||||
}
|
||||
info, err := d.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Add(fileWeight(path, info), func(r *reporter) error {
|
||||
return processFile(path, info, nil, r, explicit)
|
||||
})
|
||||
return nil
|
||||
}); err != nil {
|
||||
s.AddReport(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func shouldIgnore(path string) bool {
|
||||
switch filepath.Base(path) {
|
||||
case "vendor", "testdata":
|
||||
return true
|
||||
}
|
||||
path, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return false // unclear how this could happen; don't ignore in any case
|
||||
}
|
||||
mod := loadModule(path)
|
||||
if mod == nil {
|
||||
return false // no module file to declare ignore paths
|
||||
}
|
||||
relPath, err := filepath.Rel(mod.absDir, path)
|
||||
if err != nil {
|
||||
return false // unclear how this could happen; don't ignore in any case
|
||||
}
|
||||
relPath = normalizePath(relPath)
|
||||
for _, ignore := range mod.file.Ignore {
|
||||
if matchIgnore(ignore.Path, relPath) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// normalizePath adds slashes to the front and end of the given path.
|
||||
func normalizePath(path string) string {
|
||||
path = filepath.ToSlash(path) // ensure Windows support
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
path += "/"
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func matchIgnore(ignore, relPath string) bool {
|
||||
ignore, rooted := strings.CutPrefix(ignore, "./")
|
||||
ignore = normalizePath(ignore)
|
||||
// Note that we only match the directory to be ignored itself,
|
||||
// and not any directories underneath it.
|
||||
// This way, using `gofumpt -w ignored` allows `ignored/subdir` to be formatted.
|
||||
if rooted {
|
||||
return relPath == ignore
|
||||
}
|
||||
return strings.HasSuffix(relPath, ignore)
|
||||
}
|
||||
|
||||
// A nil entry means the directory is not part of a Go module,
|
||||
// or a go.mod file was found but it's invalid.
|
||||
// A non-nil entry means this directory, or a parent, is in a valid Go module.
|
||||
var cachedModuleByDir sync.Map // map[dirString]*cachedModfile
|
||||
|
||||
type cachedModule struct {
|
||||
absDir string // the directory where the go.mod file was found
|
||||
file *modfile.File
|
||||
}
|
||||
|
||||
func loadModule(dir string) *cachedModule {
|
||||
if cached, ok := cachedModuleByDir.Load(dir); ok {
|
||||
mf, _ := cached.(*cachedModule)
|
||||
return mf
|
||||
}
|
||||
mod := func() *cachedModule {
|
||||
path := filepath.Join(dir, "go.mod")
|
||||
fdSem <- true
|
||||
data, err := os.ReadFile(path)
|
||||
<-fdSem
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
parent := filepath.Dir(dir)
|
||||
if parent == "." {
|
||||
panic("loadModule was not given an absolute path?")
|
||||
}
|
||||
if parent == dir {
|
||||
return nil // reached the filesystem root
|
||||
}
|
||||
return loadModule(parent) // try the parent directory
|
||||
}
|
||||
if err != nil {
|
||||
return nil // some other file reading error
|
||||
}
|
||||
file, err := modfile.Parse(filepath.Join(dir, "go.mod"), data, nil)
|
||||
if err != nil {
|
||||
return nil // invalid go.mod file
|
||||
}
|
||||
return &cachedModule{
|
||||
absDir: dir,
|
||||
file: file,
|
||||
}
|
||||
}()
|
||||
if mod != nil {
|
||||
cachedModuleByDir.Store(dir, mod)
|
||||
} else {
|
||||
cachedModuleByDir.Store(dir, nil)
|
||||
}
|
||||
return mod
|
||||
}
|
||||
|
||||
func fileWeight(path string, info fs.FileInfo) int64 {
|
||||
if info == nil {
|
||||
return exclusive
|
||||
}
|
||||
if info.Mode().Type() == fs.ModeSymlink {
|
||||
var err error
|
||||
info, err = os.Stat(path)
|
||||
if err != nil {
|
||||
return exclusive
|
||||
}
|
||||
}
|
||||
if !info.Mode().IsRegular() {
|
||||
// For non-regular files, FileInfo.Size is system-dependent and thus not a
|
||||
// reliable indicator of weight.
|
||||
return exclusive
|
||||
}
|
||||
return info.Size()
|
||||
}
|
||||
|
||||
const chmodSupported = runtime.GOOS != "windows"
|
||||
|
||||
// backupFile writes data to a new file named filename<number> with permissions perm,
|
||||
// with <number randomly chosen such that the file name is unique. backupFile returns
|
||||
// the chosen file name.
|
||||
func backupFile(filename string, data []byte, perm fs.FileMode) (string, error) {
|
||||
fdSem <- true
|
||||
defer func() { <-fdSem }()
|
||||
|
||||
// create backup file
|
||||
f, err := os.CreateTemp(filepath.Dir(filename), filepath.Base(filename))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
bakname := f.Name()
|
||||
if chmodSupported {
|
||||
err = f.Chmod(perm)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
os.Remove(bakname)
|
||||
return bakname, err
|
||||
}
|
||||
}
|
||||
|
||||
// write data to backup file
|
||||
_, err = f.Write(data)
|
||||
if err1 := f.Close(); err == nil {
|
||||
err = err1
|
||||
}
|
||||
|
||||
return bakname, err
|
||||
}
|
||||
177
vendor/mvdan.cc/gofumpt/internal.go
vendored
Normal file
177
vendor/mvdan.cc/gofumpt/internal.go
vendored
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
// Copyright 2015 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.
|
||||
|
||||
// TODO(gri): This file and the file src/go/format/internal.go are
|
||||
// the same (but for this comment and the package name). Do not modify
|
||||
// one without the other. Determine if we can factor out functionality
|
||||
// in a public API. See also #11844 for context.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"strings"
|
||||
|
||||
"mvdan.cc/gofumpt/internal/govendor/go/printer"
|
||||
)
|
||||
|
||||
// parse parses src, which was read from the named file,
|
||||
// as a Go source file, declaration, or statement list.
|
||||
func parse(fset *token.FileSet, filename string, src []byte, fragmentOk bool) (
|
||||
file *ast.File,
|
||||
sourceAdj func(src []byte, indent int) []byte,
|
||||
indentAdj int,
|
||||
err error,
|
||||
) {
|
||||
// Try as whole source file.
|
||||
file, err = parser.ParseFile(fset, filename, src, parserMode)
|
||||
// If there's no error, return. If the error is that the source file didn't begin with a
|
||||
// package line and source fragments are ok, fall through to
|
||||
// try as a source fragment. Stop and return on any other error.
|
||||
if err == nil || !fragmentOk || !strings.Contains(err.Error(), "expected 'package'") {
|
||||
return file, sourceAdj, indentAdj, err
|
||||
}
|
||||
|
||||
// If this is a declaration list, make it a source file
|
||||
// by inserting a package clause.
|
||||
// Insert using a ';', not a newline, so that the line numbers
|
||||
// in psrc match the ones in src.
|
||||
psrc := append([]byte("package p;"), src...)
|
||||
file, err = parser.ParseFile(fset, filename, psrc, parserMode)
|
||||
if err == nil {
|
||||
sourceAdj = func(src []byte, indent int) []byte {
|
||||
// Remove the package clause.
|
||||
// Gofmt has turned the ';' into a '\n'.
|
||||
src = src[indent+len("package p\n"):]
|
||||
return bytes.TrimSpace(src)
|
||||
}
|
||||
return file, sourceAdj, indentAdj, err
|
||||
}
|
||||
// If the error is that the source file didn't begin with a
|
||||
// declaration, fall through to try as a statement list.
|
||||
// Stop and return on any other error.
|
||||
if !strings.Contains(err.Error(), "expected declaration") {
|
||||
return file, sourceAdj, indentAdj, err
|
||||
}
|
||||
|
||||
// If this is a statement list, make it a source file
|
||||
// by inserting a package clause and turning the list
|
||||
// into a function body. This handles expressions too.
|
||||
// Insert using a ';', not a newline, so that the line numbers
|
||||
// in fsrc match the ones in src. Add an extra '\n' before the '}'
|
||||
// to make sure comments are flushed before the '}'.
|
||||
fsrc := append(append([]byte("package p; func _() {"), src...), '\n', '\n', '}')
|
||||
file, err = parser.ParseFile(fset, filename, fsrc, parserMode)
|
||||
if err == nil {
|
||||
sourceAdj = func(src []byte, indent int) []byte {
|
||||
// Cap adjusted indent to zero.
|
||||
if indent < 0 {
|
||||
indent = 0
|
||||
}
|
||||
// Remove the wrapping.
|
||||
// Gofmt has turned the "; " into a "\n\n".
|
||||
// There will be two non-blank lines with indent, hence 2*indent.
|
||||
src = src[2*indent+len("package p\n\nfunc _() {"):]
|
||||
// Remove only the "}\n" suffix: remaining whitespaces will be trimmed anyway
|
||||
src = src[:len(src)-len("}\n")]
|
||||
return bytes.TrimSpace(src)
|
||||
}
|
||||
// Gofmt has also indented the function body one level.
|
||||
// Adjust that with indentAdj.
|
||||
indentAdj = -1
|
||||
}
|
||||
|
||||
// Succeeded, or out of options.
|
||||
return file, sourceAdj, indentAdj, err
|
||||
}
|
||||
|
||||
// format formats the given package file originally obtained from src
|
||||
// and adjusts the result based on the original source via sourceAdj
|
||||
// and indentAdj.
|
||||
func format(
|
||||
fset *token.FileSet,
|
||||
file *ast.File,
|
||||
sourceAdj func(src []byte, indent int) []byte,
|
||||
indentAdj int,
|
||||
src []byte,
|
||||
cfg printer.Config,
|
||||
) ([]byte, error) {
|
||||
if sourceAdj == nil {
|
||||
// Complete source file.
|
||||
var buf bytes.Buffer
|
||||
err := cfg.Fprint(&buf, fset, file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Partial source file.
|
||||
// Determine and prepend leading space.
|
||||
i, j := 0, 0
|
||||
for j < len(src) && isSpace(src[j]) {
|
||||
if src[j] == '\n' {
|
||||
i = j + 1 // byte offset of last line in leading space
|
||||
}
|
||||
j++
|
||||
}
|
||||
var res []byte
|
||||
res = append(res, src[:i]...)
|
||||
|
||||
// Determine and prepend indentation of first code line.
|
||||
// Spaces are ignored unless there are no tabs,
|
||||
// in which case spaces count as one tab.
|
||||
indent := 0
|
||||
hasSpace := false
|
||||
for _, b := range src[i:j] {
|
||||
switch b {
|
||||
case ' ':
|
||||
hasSpace = true
|
||||
case '\t':
|
||||
indent++
|
||||
}
|
||||
}
|
||||
if indent == 0 && hasSpace {
|
||||
indent = 1
|
||||
}
|
||||
for range indent {
|
||||
res = append(res, '\t')
|
||||
}
|
||||
|
||||
// Format the source.
|
||||
// Write it without any leading and trailing space.
|
||||
cfg.Indent = indent + indentAdj
|
||||
var buf bytes.Buffer
|
||||
err := cfg.Fprint(&buf, fset, file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := sourceAdj(buf.Bytes(), cfg.Indent)
|
||||
|
||||
// If the adjusted output is empty, the source
|
||||
// was empty but (possibly) for white space.
|
||||
// The result is the incoming source.
|
||||
if len(out) == 0 {
|
||||
return src, nil
|
||||
}
|
||||
|
||||
// Otherwise, append output to leading space.
|
||||
res = append(res, out...)
|
||||
|
||||
// Determine and append trailing space.
|
||||
i = len(src)
|
||||
for i > 0 && isSpace(src[i-1]) {
|
||||
i--
|
||||
}
|
||||
return append(res, src[i:]...), nil
|
||||
}
|
||||
|
||||
// isSpace reports whether the byte is a space character.
|
||||
// isSpace defines a space as being among the following bytes: ' ', '\t', '\n' and '\r'.
|
||||
func isSpace(b byte) bool {
|
||||
return b == ' ' || b == '\t' || b == '\n' || b == '\r'
|
||||
}
|
||||
261
vendor/mvdan.cc/gofumpt/internal/govendor/diff/diff.go
vendored
Normal file
261
vendor/mvdan.cc/gofumpt/internal/govendor/diff/diff.go
vendored
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
// Copyright 2022 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 diff
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A pair is a pair of values tracked for both the x and y side of a diff.
|
||||
// It is typically a pair of line indexes.
|
||||
type pair struct{ x, y int }
|
||||
|
||||
// Diff returns an anchored diff of the two texts old and new
|
||||
// in the “unified diff” format. If old and new are identical,
|
||||
// Diff returns a nil slice (no output).
|
||||
//
|
||||
// Unix diff implementations typically look for a diff with
|
||||
// the smallest number of lines inserted and removed,
|
||||
// which can in the worst case take time quadratic in the
|
||||
// number of lines in the texts. As a result, many implementations
|
||||
// either can be made to run for a long time or cut off the search
|
||||
// after a predetermined amount of work.
|
||||
//
|
||||
// In contrast, this implementation looks for a diff with the
|
||||
// smallest number of “unique” lines inserted and removed,
|
||||
// where unique means a line that appears just once in both old and new.
|
||||
// We call this an “anchored diff” because the unique lines anchor
|
||||
// the chosen matching regions. An anchored diff is usually clearer
|
||||
// than a standard diff, because the algorithm does not try to
|
||||
// reuse unrelated blank lines or closing braces.
|
||||
// The algorithm also guarantees to run in O(n log n) time
|
||||
// instead of the standard O(n²) time.
|
||||
//
|
||||
// Some systems call this approach a “patience diff,” named for
|
||||
// the “patience sorting” algorithm, itself named for a solitaire card game.
|
||||
// We avoid that name for two reasons. First, the name has been used
|
||||
// for a few different variants of the algorithm, so it is imprecise.
|
||||
// Second, the name is frequently interpreted as meaning that you have
|
||||
// to wait longer (to be patient) for the diff, meaning that it is a slower algorithm,
|
||||
// when in fact the algorithm is faster than the standard one.
|
||||
func Diff(oldName string, old []byte, newName string, new []byte) []byte {
|
||||
if bytes.Equal(old, new) {
|
||||
return nil
|
||||
}
|
||||
x := lines(old)
|
||||
y := lines(new)
|
||||
|
||||
// Print diff header.
|
||||
var out bytes.Buffer
|
||||
fmt.Fprintf(&out, "diff %s %s\n", oldName, newName)
|
||||
fmt.Fprintf(&out, "--- %s\n", oldName)
|
||||
fmt.Fprintf(&out, "+++ %s\n", newName)
|
||||
|
||||
// Loop over matches to consider,
|
||||
// expanding each match to include surrounding lines,
|
||||
// and then printing diff chunks.
|
||||
// To avoid setup/teardown cases outside the loop,
|
||||
// tgs returns a leading {0,0} and trailing {len(x), len(y)} pair
|
||||
// in the sequence of matches.
|
||||
var (
|
||||
done pair // printed up to x[:done.x] and y[:done.y]
|
||||
chunk pair // start lines of current chunk
|
||||
count pair // number of lines from each side in current chunk
|
||||
ctext []string // lines for current chunk
|
||||
)
|
||||
for _, m := range tgs(x, y) {
|
||||
if m.x < done.x {
|
||||
// Already handled scanning forward from earlier match.
|
||||
continue
|
||||
}
|
||||
|
||||
// Expand matching lines as far as possible,
|
||||
// establishing that x[start.x:end.x] == y[start.y:end.y].
|
||||
// Note that on the first (or last) iteration we may (or definitely do)
|
||||
// have an empty match: start.x==end.x and start.y==end.y.
|
||||
start := m
|
||||
for start.x > done.x && start.y > done.y && x[start.x-1] == y[start.y-1] {
|
||||
start.x--
|
||||
start.y--
|
||||
}
|
||||
end := m
|
||||
for end.x < len(x) && end.y < len(y) && x[end.x] == y[end.y] {
|
||||
end.x++
|
||||
end.y++
|
||||
}
|
||||
|
||||
// Emit the mismatched lines before start into this chunk.
|
||||
// (No effect on first sentinel iteration, when start = {0,0}.)
|
||||
for _, s := range x[done.x:start.x] {
|
||||
ctext = append(ctext, "-"+s)
|
||||
count.x++
|
||||
}
|
||||
for _, s := range y[done.y:start.y] {
|
||||
ctext = append(ctext, "+"+s)
|
||||
count.y++
|
||||
}
|
||||
|
||||
// If we're not at EOF and have too few common lines,
|
||||
// the chunk includes all the common lines and continues.
|
||||
const C = 3 // number of context lines
|
||||
if (end.x < len(x) || end.y < len(y)) &&
|
||||
(end.x-start.x < C || (len(ctext) > 0 && end.x-start.x < 2*C)) {
|
||||
for _, s := range x[start.x:end.x] {
|
||||
ctext = append(ctext, " "+s)
|
||||
count.x++
|
||||
count.y++
|
||||
}
|
||||
done = end
|
||||
continue
|
||||
}
|
||||
|
||||
// End chunk with common lines for context.
|
||||
if len(ctext) > 0 {
|
||||
n := end.x - start.x
|
||||
if n > C {
|
||||
n = C
|
||||
}
|
||||
for _, s := range x[start.x : start.x+n] {
|
||||
ctext = append(ctext, " "+s)
|
||||
count.x++
|
||||
count.y++
|
||||
}
|
||||
done = pair{start.x + n, start.y + n}
|
||||
|
||||
// Format and emit chunk.
|
||||
// Convert line numbers to 1-indexed.
|
||||
// Special case: empty file shows up as 0,0 not 1,0.
|
||||
if count.x > 0 {
|
||||
chunk.x++
|
||||
}
|
||||
if count.y > 0 {
|
||||
chunk.y++
|
||||
}
|
||||
fmt.Fprintf(&out, "@@ -%d,%d +%d,%d @@\n", chunk.x, count.x, chunk.y, count.y)
|
||||
for _, s := range ctext {
|
||||
out.WriteString(s)
|
||||
}
|
||||
count.x = 0
|
||||
count.y = 0
|
||||
ctext = ctext[:0]
|
||||
}
|
||||
|
||||
// If we reached EOF, we're done.
|
||||
if end.x >= len(x) && end.y >= len(y) {
|
||||
break
|
||||
}
|
||||
|
||||
// Otherwise start a new chunk.
|
||||
chunk = pair{end.x - C, end.y - C}
|
||||
for _, s := range x[chunk.x:end.x] {
|
||||
ctext = append(ctext, " "+s)
|
||||
count.x++
|
||||
count.y++
|
||||
}
|
||||
done = end
|
||||
}
|
||||
|
||||
return out.Bytes()
|
||||
}
|
||||
|
||||
// lines returns the lines in the file x, including newlines.
|
||||
// If the file does not end in a newline, one is supplied
|
||||
// along with a warning about the missing newline.
|
||||
func lines(x []byte) []string {
|
||||
l := strings.SplitAfter(string(x), "\n")
|
||||
if l[len(l)-1] == "" {
|
||||
l = l[:len(l)-1]
|
||||
} else {
|
||||
// Treat last line as having a message about the missing newline attached,
|
||||
// using the same text as BSD/GNU diff (including the leading backslash).
|
||||
l[len(l)-1] += "\n\\ No newline at end of file\n"
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// tgs returns the pairs of indexes of the longest common subsequence
|
||||
// of unique lines in x and y, where a unique line is one that appears
|
||||
// once in x and once in y.
|
||||
//
|
||||
// The longest common subsequence algorithm is as described in
|
||||
// Thomas G. Szymanski, “A Special Case of the Maximal Common
|
||||
// Subsequence Problem,” Princeton TR #170 (January 1975),
|
||||
// available at https://research.swtch.com/tgs170.pdf.
|
||||
func tgs(x, y []string) []pair {
|
||||
// Count the number of times each string appears in a and b.
|
||||
// We only care about 0, 1, many, counted as 0, -1, -2
|
||||
// for the x side and 0, -4, -8 for the y side.
|
||||
// Using negative numbers now lets us distinguish positive line numbers later.
|
||||
m := make(map[string]int)
|
||||
for _, s := range x {
|
||||
if c := m[s]; c > -2 {
|
||||
m[s] = c - 1
|
||||
}
|
||||
}
|
||||
for _, s := range y {
|
||||
if c := m[s]; c > -8 {
|
||||
m[s] = c - 4
|
||||
}
|
||||
}
|
||||
|
||||
// Now unique strings can be identified by m[s] = -1+-4.
|
||||
//
|
||||
// Gather the indexes of those strings in x and y, building:
|
||||
// xi[i] = increasing indexes of unique strings in x.
|
||||
// yi[i] = increasing indexes of unique strings in y.
|
||||
// inv[i] = index j such that x[xi[i]] = y[yi[j]].
|
||||
var xi, yi, inv []int
|
||||
for i, s := range y {
|
||||
if m[s] == -1+-4 {
|
||||
m[s] = len(yi)
|
||||
yi = append(yi, i)
|
||||
}
|
||||
}
|
||||
for i, s := range x {
|
||||
if j, ok := m[s]; ok && j >= 0 {
|
||||
xi = append(xi, i)
|
||||
inv = append(inv, j)
|
||||
}
|
||||
}
|
||||
|
||||
// Apply Algorithm A from Szymanski's paper.
|
||||
// In those terms, A = J = inv and B = [0, n).
|
||||
// We add sentinel pairs {0,0}, and {len(x),len(y)}
|
||||
// to the returned sequence, to help the processing loop.
|
||||
J := inv
|
||||
n := len(xi)
|
||||
T := make([]int, n)
|
||||
L := make([]int, n)
|
||||
for i := range T {
|
||||
T[i] = n + 1
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
k := sort.Search(n, func(k int) bool {
|
||||
return T[k] >= J[i]
|
||||
})
|
||||
T[k] = J[i]
|
||||
L[i] = k + 1
|
||||
}
|
||||
k := 0
|
||||
for _, v := range L {
|
||||
if k < v {
|
||||
k = v
|
||||
}
|
||||
}
|
||||
seq := make([]pair, 2+k)
|
||||
seq[1+k] = pair{len(x), len(y)} // sentinel at end
|
||||
lastj := n
|
||||
for i := n - 1; i >= 0; i-- {
|
||||
if L[i] == k && J[i] < lastj {
|
||||
seq[k] = pair{xi[i], yi[J[i]]}
|
||||
k--
|
||||
}
|
||||
}
|
||||
seq[0] = pair{0, 0} // sentinel at start
|
||||
return seq
|
||||
}
|
||||
36
vendor/mvdan.cc/gofumpt/internal/govendor/go/doc/comment/doc.go
vendored
Normal file
36
vendor/mvdan.cc/gofumpt/internal/govendor/go/doc/comment/doc.go
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2022 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 comment implements parsing and reformatting of Go doc comments,
|
||||
(documentation comments), which are comments that immediately precede
|
||||
a top-level declaration of a package, const, func, type, or var.
|
||||
|
||||
Go doc comment syntax is a simplified subset of Markdown that supports
|
||||
links, headings, paragraphs, lists (without nesting), and preformatted text blocks.
|
||||
The details of the syntax are documented at https://go.dev/doc/comment.
|
||||
|
||||
To parse the text associated with a doc comment (after removing comment markers),
|
||||
use a [Parser]:
|
||||
|
||||
var p comment.Parser
|
||||
doc := p.Parse(text)
|
||||
|
||||
The result is a [*Doc].
|
||||
To reformat it as a doc comment, HTML, Markdown, or plain text,
|
||||
use a [Printer]:
|
||||
|
||||
var pr comment.Printer
|
||||
os.Stdout.Write(pr.Text(doc))
|
||||
|
||||
The [Parser] and [Printer] types are structs whose fields can be
|
||||
modified to customize the operations.
|
||||
For details, see the documentation for those types.
|
||||
|
||||
Use cases that need additional control over reformatting can
|
||||
implement their own logic by inspecting the parsed syntax itself.
|
||||
See the documentation for [Doc], [Block], [Text] for an overview
|
||||
and links to additional types.
|
||||
*/
|
||||
package comment
|
||||
169
vendor/mvdan.cc/gofumpt/internal/govendor/go/doc/comment/html.go
vendored
Normal file
169
vendor/mvdan.cc/gofumpt/internal/govendor/go/doc/comment/html.go
vendored
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
// Copyright 2022 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 comment
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// An htmlPrinter holds the state needed for printing a [Doc] as HTML.
|
||||
type htmlPrinter struct {
|
||||
*Printer
|
||||
tight bool
|
||||
}
|
||||
|
||||
// HTML returns an HTML formatting of the [Doc].
|
||||
// See the [Printer] documentation for ways to customize the HTML output.
|
||||
func (p *Printer) HTML(d *Doc) []byte {
|
||||
hp := &htmlPrinter{Printer: p}
|
||||
var out bytes.Buffer
|
||||
for _, x := range d.Content {
|
||||
hp.block(&out, x)
|
||||
}
|
||||
return out.Bytes()
|
||||
}
|
||||
|
||||
// block prints the block x to out.
|
||||
func (p *htmlPrinter) block(out *bytes.Buffer, x Block) {
|
||||
switch x := x.(type) {
|
||||
default:
|
||||
fmt.Fprintf(out, "?%T", x)
|
||||
|
||||
case *Paragraph:
|
||||
if !p.tight {
|
||||
out.WriteString("<p>")
|
||||
}
|
||||
p.text(out, x.Text)
|
||||
out.WriteString("\n")
|
||||
|
||||
case *Heading:
|
||||
out.WriteString("<h")
|
||||
h := strconv.Itoa(p.headingLevel())
|
||||
out.WriteString(h)
|
||||
if id := p.headingID(x); id != "" {
|
||||
out.WriteString(` id="`)
|
||||
p.escape(out, id)
|
||||
out.WriteString(`"`)
|
||||
}
|
||||
out.WriteString(">")
|
||||
p.text(out, x.Text)
|
||||
out.WriteString("</h")
|
||||
out.WriteString(h)
|
||||
out.WriteString(">\n")
|
||||
|
||||
case *Code:
|
||||
out.WriteString("<pre>")
|
||||
p.escape(out, x.Text)
|
||||
out.WriteString("</pre>\n")
|
||||
|
||||
case *List:
|
||||
kind := "ol>\n"
|
||||
if x.Items[0].Number == "" {
|
||||
kind = "ul>\n"
|
||||
}
|
||||
out.WriteString("<")
|
||||
out.WriteString(kind)
|
||||
next := "1"
|
||||
for _, item := range x.Items {
|
||||
out.WriteString("<li")
|
||||
if n := item.Number; n != "" {
|
||||
if n != next {
|
||||
out.WriteString(` value="`)
|
||||
out.WriteString(n)
|
||||
out.WriteString(`"`)
|
||||
next = n
|
||||
}
|
||||
next = inc(next)
|
||||
}
|
||||
out.WriteString(">")
|
||||
p.tight = !x.BlankBetween()
|
||||
for _, blk := range item.Content {
|
||||
p.block(out, blk)
|
||||
}
|
||||
p.tight = false
|
||||
}
|
||||
out.WriteString("</")
|
||||
out.WriteString(kind)
|
||||
}
|
||||
}
|
||||
|
||||
// inc increments the decimal string s.
|
||||
// For example, inc("1199") == "1200".
|
||||
func inc(s string) string {
|
||||
b := []byte(s)
|
||||
for i := len(b) - 1; i >= 0; i-- {
|
||||
if b[i] < '9' {
|
||||
b[i]++
|
||||
return string(b)
|
||||
}
|
||||
b[i] = '0'
|
||||
}
|
||||
return "1" + string(b)
|
||||
}
|
||||
|
||||
// text prints the text sequence x to out.
|
||||
func (p *htmlPrinter) text(out *bytes.Buffer, x []Text) {
|
||||
for _, t := range x {
|
||||
switch t := t.(type) {
|
||||
case Plain:
|
||||
p.escape(out, string(t))
|
||||
case Italic:
|
||||
out.WriteString("<i>")
|
||||
p.escape(out, string(t))
|
||||
out.WriteString("</i>")
|
||||
case *Link:
|
||||
out.WriteString(`<a href="`)
|
||||
p.escape(out, t.URL)
|
||||
out.WriteString(`">`)
|
||||
p.text(out, t.Text)
|
||||
out.WriteString("</a>")
|
||||
case *DocLink:
|
||||
url := p.docLinkURL(t)
|
||||
if url != "" {
|
||||
out.WriteString(`<a href="`)
|
||||
p.escape(out, url)
|
||||
out.WriteString(`">`)
|
||||
}
|
||||
p.text(out, t.Text)
|
||||
if url != "" {
|
||||
out.WriteString("</a>")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// escape prints s to out as plain text,
|
||||
// escaping < & " ' and > to avoid being misinterpreted
|
||||
// in larger HTML constructs.
|
||||
func (p *htmlPrinter) escape(out *bytes.Buffer, s string) {
|
||||
start := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '<':
|
||||
out.WriteString(s[start:i])
|
||||
out.WriteString("<")
|
||||
start = i + 1
|
||||
case '&':
|
||||
out.WriteString(s[start:i])
|
||||
out.WriteString("&")
|
||||
start = i + 1
|
||||
case '"':
|
||||
out.WriteString(s[start:i])
|
||||
out.WriteString(""")
|
||||
start = i + 1
|
||||
case '\'':
|
||||
out.WriteString(s[start:i])
|
||||
out.WriteString("'")
|
||||
start = i + 1
|
||||
case '>':
|
||||
out.WriteString(s[start:i])
|
||||
out.WriteString(">")
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
out.WriteString(s[start:])
|
||||
}
|
||||
188
vendor/mvdan.cc/gofumpt/internal/govendor/go/doc/comment/markdown.go
vendored
Normal file
188
vendor/mvdan.cc/gofumpt/internal/govendor/go/doc/comment/markdown.go
vendored
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
// Copyright 2022 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 comment
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// An mdPrinter holds the state needed for printing a Doc as Markdown.
|
||||
type mdPrinter struct {
|
||||
*Printer
|
||||
headingPrefix string
|
||||
raw bytes.Buffer
|
||||
}
|
||||
|
||||
// Markdown returns a Markdown formatting of the Doc.
|
||||
// See the [Printer] documentation for ways to customize the Markdown output.
|
||||
func (p *Printer) Markdown(d *Doc) []byte {
|
||||
mp := &mdPrinter{
|
||||
Printer: p,
|
||||
headingPrefix: strings.Repeat("#", p.headingLevel()) + " ",
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
for i, x := range d.Content {
|
||||
if i > 0 {
|
||||
out.WriteByte('\n')
|
||||
}
|
||||
mp.block(&out, x)
|
||||
}
|
||||
return out.Bytes()
|
||||
}
|
||||
|
||||
// block prints the block x to out.
|
||||
func (p *mdPrinter) block(out *bytes.Buffer, x Block) {
|
||||
switch x := x.(type) {
|
||||
default:
|
||||
fmt.Fprintf(out, "?%T", x)
|
||||
|
||||
case *Paragraph:
|
||||
p.text(out, x.Text)
|
||||
out.WriteString("\n")
|
||||
|
||||
case *Heading:
|
||||
out.WriteString(p.headingPrefix)
|
||||
p.text(out, x.Text)
|
||||
if id := p.headingID(x); id != "" {
|
||||
out.WriteString(" {#")
|
||||
out.WriteString(id)
|
||||
out.WriteString("}")
|
||||
}
|
||||
out.WriteString("\n")
|
||||
|
||||
case *Code:
|
||||
md := x.Text
|
||||
for md != "" {
|
||||
var line string
|
||||
line, md, _ = strings.Cut(md, "\n")
|
||||
if line != "" {
|
||||
out.WriteString("\t")
|
||||
out.WriteString(line)
|
||||
}
|
||||
out.WriteString("\n")
|
||||
}
|
||||
|
||||
case *List:
|
||||
loose := x.BlankBetween()
|
||||
for i, item := range x.Items {
|
||||
if i > 0 && loose {
|
||||
out.WriteString("\n")
|
||||
}
|
||||
if n := item.Number; n != "" {
|
||||
out.WriteString(" ")
|
||||
out.WriteString(n)
|
||||
out.WriteString(". ")
|
||||
} else {
|
||||
out.WriteString(" - ") // SP SP - SP
|
||||
}
|
||||
for i, blk := range item.Content {
|
||||
const fourSpace = " "
|
||||
if i > 0 {
|
||||
out.WriteString("\n" + fourSpace)
|
||||
}
|
||||
p.text(out, blk.(*Paragraph).Text)
|
||||
out.WriteString("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// text prints the text sequence x to out.
|
||||
func (p *mdPrinter) text(out *bytes.Buffer, x []Text) {
|
||||
p.raw.Reset()
|
||||
p.rawText(&p.raw, x)
|
||||
line := bytes.TrimSpace(p.raw.Bytes())
|
||||
if len(line) == 0 {
|
||||
return
|
||||
}
|
||||
switch line[0] {
|
||||
case '+', '-', '*', '#':
|
||||
// Escape what would be the start of an unordered list or heading.
|
||||
out.WriteByte('\\')
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
i := 1
|
||||
for i < len(line) && '0' <= line[i] && line[i] <= '9' {
|
||||
i++
|
||||
}
|
||||
if i < len(line) && (line[i] == '.' || line[i] == ')') {
|
||||
// Escape what would be the start of an ordered list.
|
||||
out.Write(line[:i])
|
||||
out.WriteByte('\\')
|
||||
line = line[i:]
|
||||
}
|
||||
}
|
||||
out.Write(line)
|
||||
}
|
||||
|
||||
// rawText prints the text sequence x to out,
|
||||
// without worrying about escaping characters
|
||||
// that have special meaning at the start of a Markdown line.
|
||||
func (p *mdPrinter) rawText(out *bytes.Buffer, x []Text) {
|
||||
for _, t := range x {
|
||||
switch t := t.(type) {
|
||||
case Plain:
|
||||
p.escape(out, string(t))
|
||||
case Italic:
|
||||
out.WriteString("*")
|
||||
p.escape(out, string(t))
|
||||
out.WriteString("*")
|
||||
case *Link:
|
||||
out.WriteString("[")
|
||||
p.rawText(out, t.Text)
|
||||
out.WriteString("](")
|
||||
out.WriteString(t.URL)
|
||||
out.WriteString(")")
|
||||
case *DocLink:
|
||||
url := p.docLinkURL(t)
|
||||
if url != "" {
|
||||
out.WriteString("[")
|
||||
}
|
||||
p.rawText(out, t.Text)
|
||||
if url != "" {
|
||||
out.WriteString("](")
|
||||
url = strings.ReplaceAll(url, "(", "%28")
|
||||
url = strings.ReplaceAll(url, ")", "%29")
|
||||
out.WriteString(url)
|
||||
out.WriteString(")")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// escape prints s to out as plain text,
|
||||
// escaping special characters to avoid being misinterpreted
|
||||
// as Markdown markup sequences.
|
||||
func (p *mdPrinter) escape(out *bytes.Buffer, s string) {
|
||||
start := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '\n':
|
||||
// Turn all \n into spaces, for a few reasons:
|
||||
// - Avoid introducing paragraph breaks accidentally.
|
||||
// - Avoid the need to reindent after the newline.
|
||||
// - Avoid problems with Markdown renderers treating
|
||||
// every mid-paragraph newline as a <br>.
|
||||
out.WriteString(s[start:i])
|
||||
out.WriteByte(' ')
|
||||
start = i + 1
|
||||
continue
|
||||
case '`', '_', '*', '[', '<', '\\':
|
||||
// Not all of these need to be escaped all the time,
|
||||
// but is valid and easy to do so.
|
||||
// We assume the Markdown is being passed to a
|
||||
// Markdown renderer, not edited by a person,
|
||||
// so it's fine to have escapes that are not strictly
|
||||
// necessary in some cases.
|
||||
out.WriteString(s[start:i])
|
||||
out.WriteByte('\\')
|
||||
out.WriteByte(s[i])
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
out.WriteString(s[start:])
|
||||
}
|
||||
1260
vendor/mvdan.cc/gofumpt/internal/govendor/go/doc/comment/parse.go
vendored
Normal file
1260
vendor/mvdan.cc/gofumpt/internal/govendor/go/doc/comment/parse.go
vendored
Normal file
File diff suppressed because it is too large
Load diff
288
vendor/mvdan.cc/gofumpt/internal/govendor/go/doc/comment/print.go
vendored
Normal file
288
vendor/mvdan.cc/gofumpt/internal/govendor/go/doc/comment/print.go
vendored
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
// Copyright 2022 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 comment
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A Printer is a doc comment printer.
|
||||
// The fields in the struct can be filled in before calling
|
||||
// any of the printing methods
|
||||
// in order to customize the details of the printing process.
|
||||
type Printer struct {
|
||||
// HeadingLevel is the nesting level used for
|
||||
// HTML and Markdown headings.
|
||||
// If HeadingLevel is zero, it defaults to level 3,
|
||||
// meaning to use <h3> and ###.
|
||||
HeadingLevel int
|
||||
|
||||
// HeadingID is a function that computes the heading ID
|
||||
// (anchor tag) to use for the heading h when generating
|
||||
// HTML and Markdown. If HeadingID returns an empty string,
|
||||
// then the heading ID is omitted.
|
||||
// If HeadingID is nil, h.DefaultID is used.
|
||||
HeadingID func(h *Heading) string
|
||||
|
||||
// DocLinkURL is a function that computes the URL for the given DocLink.
|
||||
// If DocLinkURL is nil, then link.DefaultURL(p.DocLinkBaseURL) is used.
|
||||
DocLinkURL func(link *DocLink) string
|
||||
|
||||
// DocLinkBaseURL is used when DocLinkURL is nil,
|
||||
// passed to [DocLink.DefaultURL] to construct a DocLink's URL.
|
||||
// See that method's documentation for details.
|
||||
DocLinkBaseURL string
|
||||
|
||||
// TextPrefix is a prefix to print at the start of every line
|
||||
// when generating text output using the Text method.
|
||||
TextPrefix string
|
||||
|
||||
// TextCodePrefix is the prefix to print at the start of each
|
||||
// preformatted (code block) line when generating text output,
|
||||
// instead of (not in addition to) TextPrefix.
|
||||
// If TextCodePrefix is the empty string, it defaults to TextPrefix+"\t".
|
||||
TextCodePrefix string
|
||||
|
||||
// TextWidth is the maximum width text line to generate,
|
||||
// measured in Unicode code points,
|
||||
// excluding TextPrefix and the newline character.
|
||||
// If TextWidth is zero, it defaults to 80 minus the number of code points in TextPrefix.
|
||||
// If TextWidth is negative, there is no limit.
|
||||
TextWidth int
|
||||
}
|
||||
|
||||
func (p *Printer) headingLevel() int {
|
||||
if p.HeadingLevel <= 0 {
|
||||
return 3
|
||||
}
|
||||
return p.HeadingLevel
|
||||
}
|
||||
|
||||
func (p *Printer) headingID(h *Heading) string {
|
||||
if p.HeadingID == nil {
|
||||
return h.DefaultID()
|
||||
}
|
||||
return p.HeadingID(h)
|
||||
}
|
||||
|
||||
func (p *Printer) docLinkURL(link *DocLink) string {
|
||||
if p.DocLinkURL != nil {
|
||||
return p.DocLinkURL(link)
|
||||
}
|
||||
return link.DefaultURL(p.DocLinkBaseURL)
|
||||
}
|
||||
|
||||
// DefaultURL constructs and returns the documentation URL for l,
|
||||
// using baseURL as a prefix for links to other packages.
|
||||
//
|
||||
// The possible forms returned by DefaultURL are:
|
||||
// - baseURL/ImportPath, for a link to another package
|
||||
// - baseURL/ImportPath#Name, for a link to a const, func, type, or var in another package
|
||||
// - baseURL/ImportPath#Recv.Name, for a link to a method in another package
|
||||
// - #Name, for a link to a const, func, type, or var in this package
|
||||
// - #Recv.Name, for a link to a method in this package
|
||||
//
|
||||
// If baseURL ends in a trailing slash, then DefaultURL inserts
|
||||
// a slash between ImportPath and # in the anchored forms.
|
||||
// For example, here are some baseURL values and URLs they can generate:
|
||||
//
|
||||
// "/pkg/" → "/pkg/math/#Sqrt"
|
||||
// "/pkg" → "/pkg/math#Sqrt"
|
||||
// "/" → "/math/#Sqrt"
|
||||
// "" → "/math#Sqrt"
|
||||
func (l *DocLink) DefaultURL(baseURL string) string {
|
||||
if l.ImportPath != "" {
|
||||
slash := ""
|
||||
if strings.HasSuffix(baseURL, "/") {
|
||||
slash = "/"
|
||||
} else {
|
||||
baseURL += "/"
|
||||
}
|
||||
switch {
|
||||
case l.Name == "":
|
||||
return baseURL + l.ImportPath + slash
|
||||
case l.Recv != "":
|
||||
return baseURL + l.ImportPath + slash + "#" + l.Recv + "." + l.Name
|
||||
default:
|
||||
return baseURL + l.ImportPath + slash + "#" + l.Name
|
||||
}
|
||||
}
|
||||
if l.Recv != "" {
|
||||
return "#" + l.Recv + "." + l.Name
|
||||
}
|
||||
return "#" + l.Name
|
||||
}
|
||||
|
||||
// DefaultID returns the default anchor ID for the heading h.
|
||||
//
|
||||
// The default anchor ID is constructed by converting every
|
||||
// rune that is not alphanumeric ASCII to an underscore
|
||||
// and then adding the prefix “hdr-”.
|
||||
// For example, if the heading text is “Go Doc Comments”,
|
||||
// the default ID is “hdr-Go_Doc_Comments”.
|
||||
func (h *Heading) DefaultID() string {
|
||||
// Note: The “hdr-” prefix is important to avoid DOM clobbering attacks.
|
||||
// See https://pkg.go.dev/github.com/google/safehtml#Identifier.
|
||||
var out strings.Builder
|
||||
var p textPrinter
|
||||
p.oneLongLine(&out, h.Text)
|
||||
s := strings.TrimSpace(out.String())
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
out.Reset()
|
||||
out.WriteString("hdr-")
|
||||
for _, r := range s {
|
||||
if r < 0x80 && isIdentASCII(byte(r)) {
|
||||
out.WriteByte(byte(r))
|
||||
} else {
|
||||
out.WriteByte('_')
|
||||
}
|
||||
}
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type commentPrinter struct {
|
||||
*Printer
|
||||
}
|
||||
|
||||
// Comment returns the standard Go formatting of the [Doc],
|
||||
// without any comment markers.
|
||||
func (p *Printer) Comment(d *Doc) []byte {
|
||||
cp := &commentPrinter{Printer: p}
|
||||
var out bytes.Buffer
|
||||
for i, x := range d.Content {
|
||||
if i > 0 && blankBefore(x) {
|
||||
out.WriteString("\n")
|
||||
}
|
||||
cp.block(&out, x)
|
||||
}
|
||||
|
||||
// Print one block containing all the link definitions that were used,
|
||||
// and then a second block containing all the unused ones.
|
||||
// This makes it easy to clean up the unused ones: gofmt and
|
||||
// delete the final block. And it's a nice visual signal without
|
||||
// affecting the way the comment formats for users.
|
||||
for i := 0; i < 2; i++ {
|
||||
used := i == 0
|
||||
first := true
|
||||
for _, def := range d.Links {
|
||||
if def.Used == used {
|
||||
if first {
|
||||
out.WriteString("\n")
|
||||
first = false
|
||||
}
|
||||
out.WriteString("[")
|
||||
out.WriteString(def.Text)
|
||||
out.WriteString("]: ")
|
||||
out.WriteString(def.URL)
|
||||
out.WriteString("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out.Bytes()
|
||||
}
|
||||
|
||||
// blankBefore reports whether the block x requires a blank line before it.
|
||||
// All blocks do, except for Lists that return false from x.BlankBefore().
|
||||
func blankBefore(x Block) bool {
|
||||
if x, ok := x.(*List); ok {
|
||||
return x.BlankBefore()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// block prints the block x to out.
|
||||
func (p *commentPrinter) block(out *bytes.Buffer, x Block) {
|
||||
switch x := x.(type) {
|
||||
default:
|
||||
fmt.Fprintf(out, "?%T", x)
|
||||
|
||||
case *Paragraph:
|
||||
p.text(out, "", x.Text)
|
||||
out.WriteString("\n")
|
||||
|
||||
case *Heading:
|
||||
out.WriteString("# ")
|
||||
p.text(out, "", x.Text)
|
||||
out.WriteString("\n")
|
||||
|
||||
case *Code:
|
||||
md := x.Text
|
||||
for md != "" {
|
||||
var line string
|
||||
line, md, _ = strings.Cut(md, "\n")
|
||||
if line != "" {
|
||||
out.WriteString("\t")
|
||||
out.WriteString(line)
|
||||
}
|
||||
out.WriteString("\n")
|
||||
}
|
||||
|
||||
case *List:
|
||||
loose := x.BlankBetween()
|
||||
for i, item := range x.Items {
|
||||
if i > 0 && loose {
|
||||
out.WriteString("\n")
|
||||
}
|
||||
out.WriteString(" ")
|
||||
if item.Number == "" {
|
||||
out.WriteString(" - ")
|
||||
} else {
|
||||
out.WriteString(item.Number)
|
||||
out.WriteString(". ")
|
||||
}
|
||||
for i, blk := range item.Content {
|
||||
const fourSpace = " "
|
||||
if i > 0 {
|
||||
out.WriteString("\n" + fourSpace)
|
||||
}
|
||||
p.text(out, fourSpace, blk.(*Paragraph).Text)
|
||||
out.WriteString("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// text prints the text sequence x to out.
|
||||
func (p *commentPrinter) text(out *bytes.Buffer, indent string, x []Text) {
|
||||
for _, t := range x {
|
||||
switch t := t.(type) {
|
||||
case Plain:
|
||||
p.indent(out, indent, string(t))
|
||||
case Italic:
|
||||
p.indent(out, indent, string(t))
|
||||
case *Link:
|
||||
if t.Auto {
|
||||
p.text(out, indent, t.Text)
|
||||
} else {
|
||||
out.WriteString("[")
|
||||
p.text(out, indent, t.Text)
|
||||
out.WriteString("]")
|
||||
}
|
||||
case *DocLink:
|
||||
out.WriteString("[")
|
||||
p.text(out, indent, t.Text)
|
||||
out.WriteString("]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// indent prints s to out, indenting with the indent string
|
||||
// after each newline in s.
|
||||
func (p *commentPrinter) indent(out *bytes.Buffer, indent, s string) {
|
||||
for s != "" {
|
||||
line, rest, ok := strings.Cut(s, "\n")
|
||||
out.WriteString(line)
|
||||
if ok {
|
||||
out.WriteString("\n")
|
||||
out.WriteString(indent)
|
||||
}
|
||||
s = rest
|
||||
}
|
||||
}
|
||||
51
vendor/mvdan.cc/gofumpt/internal/govendor/go/doc/comment/std.go
vendored
Normal file
51
vendor/mvdan.cc/gofumpt/internal/govendor/go/doc/comment/std.go
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright 2022 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.
|
||||
|
||||
// Code generated by 'go generate' DO NOT EDIT.
|
||||
//disabled go:generate ./mkstd.sh
|
||||
|
||||
package comment
|
||||
|
||||
var stdPkgs = []string{
|
||||
"bufio",
|
||||
"bytes",
|
||||
"cmp",
|
||||
"context",
|
||||
"crypto",
|
||||
"embed",
|
||||
"encoding",
|
||||
"errors",
|
||||
"expvar",
|
||||
"flag",
|
||||
"fmt",
|
||||
"hash",
|
||||
"html",
|
||||
"image",
|
||||
"io",
|
||||
"iter",
|
||||
"log",
|
||||
"maps",
|
||||
"math",
|
||||
"mime",
|
||||
"net",
|
||||
"os",
|
||||
"path",
|
||||
"plugin",
|
||||
"reflect",
|
||||
"regexp",
|
||||
"runtime",
|
||||
"slices",
|
||||
"sort",
|
||||
"strconv",
|
||||
"strings",
|
||||
"structs",
|
||||
"sync",
|
||||
"syscall",
|
||||
"testing",
|
||||
"time",
|
||||
"unicode",
|
||||
"unique",
|
||||
"unsafe",
|
||||
"weak",
|
||||
}
|
||||
337
vendor/mvdan.cc/gofumpt/internal/govendor/go/doc/comment/text.go
vendored
Normal file
337
vendor/mvdan.cc/gofumpt/internal/govendor/go/doc/comment/text.go
vendored
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
// Copyright 2022 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 comment
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// A textPrinter holds the state needed for printing a Doc as plain text.
|
||||
type textPrinter struct {
|
||||
*Printer
|
||||
long strings.Builder
|
||||
prefix string
|
||||
codePrefix string
|
||||
width int
|
||||
}
|
||||
|
||||
// Text returns a textual formatting of the [Doc].
|
||||
// See the [Printer] documentation for ways to customize the text output.
|
||||
func (p *Printer) Text(d *Doc) []byte {
|
||||
tp := &textPrinter{
|
||||
Printer: p,
|
||||
prefix: p.TextPrefix,
|
||||
codePrefix: p.TextCodePrefix,
|
||||
width: p.TextWidth,
|
||||
}
|
||||
if tp.codePrefix == "" {
|
||||
tp.codePrefix = p.TextPrefix + "\t"
|
||||
}
|
||||
if tp.width == 0 {
|
||||
tp.width = 80 - utf8.RuneCountInString(tp.prefix)
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
for i, x := range d.Content {
|
||||
if i > 0 && blankBefore(x) {
|
||||
out.WriteString(tp.prefix)
|
||||
writeNL(&out)
|
||||
}
|
||||
tp.block(&out, x)
|
||||
}
|
||||
anyUsed := false
|
||||
for _, def := range d.Links {
|
||||
if def.Used {
|
||||
anyUsed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if anyUsed {
|
||||
writeNL(&out)
|
||||
for _, def := range d.Links {
|
||||
if def.Used {
|
||||
fmt.Fprintf(&out, "[%s]: %s\n", def.Text, def.URL)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out.Bytes()
|
||||
}
|
||||
|
||||
// writeNL calls out.WriteByte('\n')
|
||||
// but first trims trailing spaces on the previous line.
|
||||
func writeNL(out *bytes.Buffer) {
|
||||
// Trim trailing spaces.
|
||||
data := out.Bytes()
|
||||
n := 0
|
||||
for n < len(data) && (data[len(data)-n-1] == ' ' || data[len(data)-n-1] == '\t') {
|
||||
n++
|
||||
}
|
||||
if n > 0 {
|
||||
out.Truncate(len(data) - n)
|
||||
}
|
||||
out.WriteByte('\n')
|
||||
}
|
||||
|
||||
// block prints the block x to out.
|
||||
func (p *textPrinter) block(out *bytes.Buffer, x Block) {
|
||||
switch x := x.(type) {
|
||||
default:
|
||||
fmt.Fprintf(out, "?%T\n", x)
|
||||
|
||||
case *Paragraph:
|
||||
out.WriteString(p.prefix)
|
||||
p.text(out, "", x.Text)
|
||||
|
||||
case *Heading:
|
||||
out.WriteString(p.prefix)
|
||||
out.WriteString("# ")
|
||||
p.text(out, "", x.Text)
|
||||
|
||||
case *Code:
|
||||
text := x.Text
|
||||
for text != "" {
|
||||
var line string
|
||||
line, text, _ = strings.Cut(text, "\n")
|
||||
if line != "" {
|
||||
out.WriteString(p.codePrefix)
|
||||
out.WriteString(line)
|
||||
}
|
||||
writeNL(out)
|
||||
}
|
||||
|
||||
case *List:
|
||||
loose := x.BlankBetween()
|
||||
for i, item := range x.Items {
|
||||
if i > 0 && loose {
|
||||
out.WriteString(p.prefix)
|
||||
writeNL(out)
|
||||
}
|
||||
out.WriteString(p.prefix)
|
||||
out.WriteString(" ")
|
||||
if item.Number == "" {
|
||||
out.WriteString(" - ")
|
||||
} else {
|
||||
out.WriteString(item.Number)
|
||||
out.WriteString(". ")
|
||||
}
|
||||
for i, blk := range item.Content {
|
||||
const fourSpace = " "
|
||||
if i > 0 {
|
||||
writeNL(out)
|
||||
out.WriteString(p.prefix)
|
||||
out.WriteString(fourSpace)
|
||||
}
|
||||
p.text(out, fourSpace, blk.(*Paragraph).Text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// text prints the text sequence x to out.
|
||||
func (p *textPrinter) text(out *bytes.Buffer, indent string, x []Text) {
|
||||
p.oneLongLine(&p.long, x)
|
||||
words := strings.Fields(p.long.String())
|
||||
p.long.Reset()
|
||||
|
||||
var seq []int
|
||||
if p.width < 0 || len(words) == 0 {
|
||||
seq = []int{0, len(words)} // one long line
|
||||
} else {
|
||||
seq = wrap(words, p.width-utf8.RuneCountInString(indent))
|
||||
}
|
||||
for i := 0; i+1 < len(seq); i++ {
|
||||
if i > 0 {
|
||||
out.WriteString(p.prefix)
|
||||
out.WriteString(indent)
|
||||
}
|
||||
for j, w := range words[seq[i]:seq[i+1]] {
|
||||
if j > 0 {
|
||||
out.WriteString(" ")
|
||||
}
|
||||
out.WriteString(w)
|
||||
}
|
||||
writeNL(out)
|
||||
}
|
||||
}
|
||||
|
||||
// oneLongLine prints the text sequence x to out as one long line,
|
||||
// without worrying about line wrapping.
|
||||
// Explicit links have the [ ] dropped to improve readability.
|
||||
func (p *textPrinter) oneLongLine(out *strings.Builder, x []Text) {
|
||||
for _, t := range x {
|
||||
switch t := t.(type) {
|
||||
case Plain:
|
||||
out.WriteString(string(t))
|
||||
case Italic:
|
||||
out.WriteString(string(t))
|
||||
case *Link:
|
||||
p.oneLongLine(out, t.Text)
|
||||
case *DocLink:
|
||||
p.oneLongLine(out, t.Text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wrap wraps words into lines of at most max runes,
|
||||
// minimizing the sum of the squares of the leftover lengths
|
||||
// at the end of each line (except the last, of course),
|
||||
// with a preference for ending lines at punctuation (.,:;).
|
||||
//
|
||||
// The returned slice gives the indexes of the first words
|
||||
// on each line in the wrapped text with a final entry of len(words).
|
||||
// Thus the lines are words[seq[0]:seq[1]], words[seq[1]:seq[2]],
|
||||
// ..., words[seq[len(seq)-2]:seq[len(seq)-1]].
|
||||
//
|
||||
// The implementation runs in O(n log n) time, where n = len(words),
|
||||
// using the algorithm described in D. S. Hirschberg and L. L. Larmore,
|
||||
// “[The least weight subsequence problem],” FOCS 1985, pp. 137-143.
|
||||
//
|
||||
// [The least weight subsequence problem]: https://doi.org/10.1109/SFCS.1985.60
|
||||
func wrap(words []string, max int) (seq []int) {
|
||||
// The algorithm requires that our scoring function be concave,
|
||||
// meaning that for all i₀ ≤ i₁ < j₀ ≤ j₁,
|
||||
// weight(i₀, j₀) + weight(i₁, j₁) ≤ weight(i₀, j₁) + weight(i₁, j₀).
|
||||
//
|
||||
// Our weights are two-element pairs [hi, lo]
|
||||
// ordered by elementwise comparison.
|
||||
// The hi entry counts the weight for lines that are longer than max,
|
||||
// and the lo entry counts the weight for lines that are not.
|
||||
// This forces the algorithm to first minimize the number of lines
|
||||
// that are longer than max, which correspond to lines with
|
||||
// single very long words. Having done that, it can move on to
|
||||
// minimizing the lo score, which is more interesting.
|
||||
//
|
||||
// The lo score is the sum for each line of the square of the
|
||||
// number of spaces remaining at the end of the line and a
|
||||
// penalty of 64 given out for not ending the line in a
|
||||
// punctuation character (.,:;).
|
||||
// The penalty is somewhat arbitrarily chosen by trying
|
||||
// different amounts and judging how nice the wrapped text looks.
|
||||
// Roughly speaking, using 64 means that we are willing to
|
||||
// end a line with eight blank spaces in order to end at a
|
||||
// punctuation character, even if the next word would fit in
|
||||
// those spaces.
|
||||
//
|
||||
// We care about ending in punctuation characters because
|
||||
// it makes the text easier to skim if not too many sentences
|
||||
// or phrases begin with a single word on the previous line.
|
||||
|
||||
// A score is the score (also called weight) for a given line.
|
||||
// add and cmp add and compare scores.
|
||||
type score struct {
|
||||
hi int64
|
||||
lo int64
|
||||
}
|
||||
add := func(s, t score) score { return score{s.hi + t.hi, s.lo + t.lo} }
|
||||
cmp := func(s, t score) int {
|
||||
switch {
|
||||
case s.hi < t.hi:
|
||||
return -1
|
||||
case s.hi > t.hi:
|
||||
return +1
|
||||
case s.lo < t.lo:
|
||||
return -1
|
||||
case s.lo > t.lo:
|
||||
return +1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// total[j] is the total number of runes
|
||||
// (including separating spaces) in words[:j].
|
||||
total := make([]int, len(words)+1)
|
||||
total[0] = 0
|
||||
for i, s := range words {
|
||||
total[1+i] = total[i] + utf8.RuneCountInString(s) + 1
|
||||
}
|
||||
|
||||
// weight returns weight(i, j).
|
||||
weight := func(i, j int) score {
|
||||
// On the last line, there is zero weight for being too short.
|
||||
n := total[j] - 1 - total[i]
|
||||
if j == len(words) && n <= max {
|
||||
return score{0, 0}
|
||||
}
|
||||
|
||||
// Otherwise the weight is the penalty plus the square of the number of
|
||||
// characters remaining on the line or by which the line goes over.
|
||||
// In the latter case, that value goes in the hi part of the score.
|
||||
// (See note above.)
|
||||
p := wrapPenalty(words[j-1])
|
||||
v := int64(max-n) * int64(max-n)
|
||||
if n > max {
|
||||
return score{v, p}
|
||||
}
|
||||
return score{0, v + p}
|
||||
}
|
||||
|
||||
// The rest of this function is “The Basic Algorithm” from
|
||||
// Hirschberg and Larmore's conference paper,
|
||||
// using the same names as in the paper.
|
||||
f := []score{{0, 0}}
|
||||
g := func(i, j int) score { return add(f[i], weight(i, j)) }
|
||||
|
||||
bridge := func(a, b, c int) bool {
|
||||
k := c + sort.Search(len(words)+1-c, func(k int) bool {
|
||||
k += c
|
||||
return cmp(g(a, k), g(b, k)) > 0
|
||||
})
|
||||
if k > len(words) {
|
||||
return true
|
||||
}
|
||||
return cmp(g(c, k), g(b, k)) <= 0
|
||||
}
|
||||
|
||||
// d is a one-ended deque implemented as a slice.
|
||||
d := make([]int, 1, len(words))
|
||||
d[0] = 0
|
||||
bestleft := make([]int, 1, len(words))
|
||||
bestleft[0] = -1
|
||||
for m := 1; m < len(words); m++ {
|
||||
f = append(f, g(d[0], m))
|
||||
bestleft = append(bestleft, d[0])
|
||||
for len(d) > 1 && cmp(g(d[1], m+1), g(d[0], m+1)) <= 0 {
|
||||
d = d[1:] // “Retire”
|
||||
}
|
||||
for len(d) > 1 && bridge(d[len(d)-2], d[len(d)-1], m) {
|
||||
d = d[:len(d)-1] // “Fire”
|
||||
}
|
||||
if cmp(g(m, len(words)), g(d[len(d)-1], len(words))) < 0 {
|
||||
d = append(d, m) // “Hire”
|
||||
// The next few lines are not in the paper but are necessary
|
||||
// to handle two-word inputs correctly. It appears to be
|
||||
// just a bug in the paper's pseudocode.
|
||||
if len(d) == 2 && cmp(g(d[1], m+1), g(d[0], m+1)) <= 0 {
|
||||
d = d[1:]
|
||||
}
|
||||
}
|
||||
}
|
||||
bestleft = append(bestleft, d[0])
|
||||
|
||||
// Recover least weight sequence from bestleft.
|
||||
n := 1
|
||||
for m := len(words); m > 0; m = bestleft[m] {
|
||||
n++
|
||||
}
|
||||
seq = make([]int, n)
|
||||
for m := len(words); m > 0; m = bestleft[m] {
|
||||
n--
|
||||
seq[n] = m
|
||||
}
|
||||
return seq
|
||||
}
|
||||
|
||||
// wrapPenalty is the penalty for inserting a line break after word s.
|
||||
func wrapPenalty(s string) int64 {
|
||||
switch s[len(s)-1] {
|
||||
case '.', ',', ':', ';':
|
||||
return 0
|
||||
}
|
||||
return 64
|
||||
}
|
||||
134
vendor/mvdan.cc/gofumpt/internal/govendor/go/format/format.go
vendored
Normal file
134
vendor/mvdan.cc/gofumpt/internal/govendor/go/format/format.go
vendored
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
// Copyright 2012 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 format implements standard formatting of Go source.
|
||||
//
|
||||
// Note that formatting of Go source code changes over time, so tools relying on
|
||||
// consistent formatting should execute a specific version of the gofmt binary
|
||||
// instead of using this package. That way, the formatting will be stable, and
|
||||
// the tools won't need to be recompiled each time gofmt changes.
|
||||
//
|
||||
// For example, pre-submit checks that use this package directly would behave
|
||||
// differently depending on what Go version each developer uses, causing the
|
||||
// check to be inherently fragile.
|
||||
package format
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io"
|
||||
|
||||
"mvdan.cc/gofumpt/internal/govendor/go/printer"
|
||||
)
|
||||
|
||||
// Keep these in sync with cmd/gofmt/gofmt.go.
|
||||
const (
|
||||
tabWidth = 8
|
||||
printerMode = printer.UseSpaces | printer.TabIndent | printerNormalizeNumbers
|
||||
|
||||
// printerNormalizeNumbers means to canonicalize number literal prefixes
|
||||
// and exponents while printing. See https://golang.org/doc/go1.13#gofmt.
|
||||
//
|
||||
// This value is defined in mvdan.cc/gofumpt/internal/govendor/go/printer specifically for mvdan.cc/gofumpt/internal/govendor/go/format and cmd/gofmt.
|
||||
printerNormalizeNumbers = 1 << 30
|
||||
)
|
||||
|
||||
var config = printer.Config{Mode: printerMode, Tabwidth: tabWidth}
|
||||
|
||||
const parserMode = parser.ParseComments | parser.SkipObjectResolution
|
||||
|
||||
// Node formats node in canonical gofmt style and writes the result to dst.
|
||||
//
|
||||
// The node type must be *[ast.File], *[printer.CommentedNode], [][ast.Decl],
|
||||
// [][ast.Stmt], or assignment-compatible to [ast.Expr], [ast.Decl], [ast.Spec],
|
||||
// or [ast.Stmt]. Node does not modify node. Imports are not sorted for
|
||||
// nodes representing partial source files (for instance, if the node is
|
||||
// not an *[ast.File] or a *[printer.CommentedNode] not wrapping an *[ast.File]).
|
||||
//
|
||||
// The function may return early (before the entire result is written)
|
||||
// and return a formatting error, for instance due to an incorrect AST.
|
||||
func Node(dst io.Writer, fset *token.FileSet, node any) error {
|
||||
// Determine if we have a complete source file (file != nil).
|
||||
var file *ast.File
|
||||
var cnode *printer.CommentedNode
|
||||
switch n := node.(type) {
|
||||
case *ast.File:
|
||||
file = n
|
||||
case *printer.CommentedNode:
|
||||
if f, ok := n.Node.(*ast.File); ok {
|
||||
file = f
|
||||
cnode = n
|
||||
}
|
||||
}
|
||||
|
||||
// Sort imports if necessary.
|
||||
if file != nil && hasUnsortedImports(file) {
|
||||
// Make a copy of the AST because ast.SortImports is destructive.
|
||||
// TODO(gri) Do this more efficiently.
|
||||
var buf bytes.Buffer
|
||||
err := config.Fprint(&buf, fset, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file, err = parser.ParseFile(fset, "", buf.Bytes(), parserMode)
|
||||
if err != nil {
|
||||
// We should never get here. If we do, provide good diagnostic.
|
||||
return fmt.Errorf("format.Node internal error (%s)", err)
|
||||
}
|
||||
ast.SortImports(fset, file)
|
||||
|
||||
// Use new file with sorted imports.
|
||||
node = file
|
||||
if cnode != nil {
|
||||
node = &printer.CommentedNode{Node: file, Comments: cnode.Comments}
|
||||
}
|
||||
}
|
||||
|
||||
return config.Fprint(dst, fset, node)
|
||||
}
|
||||
|
||||
// Source formats src in canonical gofmt style and returns the result
|
||||
// or an (I/O or syntax) error. src is expected to be a syntactically
|
||||
// correct Go source file, or a list of Go declarations or statements.
|
||||
//
|
||||
// If src is a partial source file, the leading and trailing space of src
|
||||
// is applied to the result (such that it has the same leading and trailing
|
||||
// space as src), and the result is indented by the same amount as the first
|
||||
// line of src containing code. Imports are not sorted for partial source files.
|
||||
func Source(src []byte) ([]byte, error) {
|
||||
fset := token.NewFileSet()
|
||||
file, sourceAdj, indentAdj, err := parse(fset, "", src, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sourceAdj == nil {
|
||||
// Complete source file.
|
||||
// TODO(gri) consider doing this always.
|
||||
ast.SortImports(fset, file)
|
||||
}
|
||||
|
||||
return format(fset, file, sourceAdj, indentAdj, src, config)
|
||||
}
|
||||
|
||||
func hasUnsortedImports(file *ast.File) bool {
|
||||
for _, d := range file.Decls {
|
||||
d, ok := d.(*ast.GenDecl)
|
||||
if !ok || d.Tok != token.IMPORT {
|
||||
// Not an import declaration, so we're done.
|
||||
// Imports are always first.
|
||||
return false
|
||||
}
|
||||
if d.Lparen.IsValid() {
|
||||
// For now assume all grouped imports are unsorted.
|
||||
// TODO(gri) Should check if they are sorted already.
|
||||
return true
|
||||
}
|
||||
// Ungrouped imports are sorted by default.
|
||||
}
|
||||
return false
|
||||
}
|
||||
177
vendor/mvdan.cc/gofumpt/internal/govendor/go/format/internal.go
vendored
Normal file
177
vendor/mvdan.cc/gofumpt/internal/govendor/go/format/internal.go
vendored
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
// Copyright 2015 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.
|
||||
|
||||
// TODO(gri): This file and the file src/cmd/gofmt/internal.go are
|
||||
// the same (but for this comment and the package name). Do not modify
|
||||
// one without the other. Determine if we can factor out functionality
|
||||
// in a public API. See also #11844 for context.
|
||||
|
||||
package format
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"strings"
|
||||
|
||||
"mvdan.cc/gofumpt/internal/govendor/go/printer"
|
||||
)
|
||||
|
||||
// parse parses src, which was read from the named file,
|
||||
// as a Go source file, declaration, or statement list.
|
||||
func parse(fset *token.FileSet, filename string, src []byte, fragmentOk bool) (
|
||||
file *ast.File,
|
||||
sourceAdj func(src []byte, indent int) []byte,
|
||||
indentAdj int,
|
||||
err error,
|
||||
) {
|
||||
// Try as whole source file.
|
||||
file, err = parser.ParseFile(fset, filename, src, parserMode)
|
||||
// If there's no error, return. If the error is that the source file didn't begin with a
|
||||
// package line and source fragments are ok, fall through to
|
||||
// try as a source fragment. Stop and return on any other error.
|
||||
if err == nil || !fragmentOk || !strings.Contains(err.Error(), "expected 'package'") {
|
||||
return file, sourceAdj, indentAdj, err
|
||||
}
|
||||
|
||||
// If this is a declaration list, make it a source file
|
||||
// by inserting a package clause.
|
||||
// Insert using a ';', not a newline, so that the line numbers
|
||||
// in psrc match the ones in src.
|
||||
psrc := append([]byte("package p;"), src...)
|
||||
file, err = parser.ParseFile(fset, filename, psrc, parserMode)
|
||||
if err == nil {
|
||||
sourceAdj = func(src []byte, indent int) []byte {
|
||||
// Remove the package clause.
|
||||
// Gofmt has turned the ';' into a '\n'.
|
||||
src = src[indent+len("package p\n"):]
|
||||
return bytes.TrimSpace(src)
|
||||
}
|
||||
return file, sourceAdj, indentAdj, err
|
||||
}
|
||||
// If the error is that the source file didn't begin with a
|
||||
// declaration, fall through to try as a statement list.
|
||||
// Stop and return on any other error.
|
||||
if !strings.Contains(err.Error(), "expected declaration") {
|
||||
return file, sourceAdj, indentAdj, err
|
||||
}
|
||||
|
||||
// If this is a statement list, make it a source file
|
||||
// by inserting a package clause and turning the list
|
||||
// into a function body. This handles expressions too.
|
||||
// Insert using a ';', not a newline, so that the line numbers
|
||||
// in fsrc match the ones in src. Add an extra '\n' before the '}'
|
||||
// to make sure comments are flushed before the '}'.
|
||||
fsrc := append(append([]byte("package p; func _() {"), src...), '\n', '\n', '}')
|
||||
file, err = parser.ParseFile(fset, filename, fsrc, parserMode)
|
||||
if err == nil {
|
||||
sourceAdj = func(src []byte, indent int) []byte {
|
||||
// Cap adjusted indent to zero.
|
||||
if indent < 0 {
|
||||
indent = 0
|
||||
}
|
||||
// Remove the wrapping.
|
||||
// Gofmt has turned the "; " into a "\n\n".
|
||||
// There will be two non-blank lines with indent, hence 2*indent.
|
||||
src = src[2*indent+len("package p\n\nfunc _() {"):]
|
||||
// Remove only the "}\n" suffix: remaining whitespaces will be trimmed anyway
|
||||
src = src[:len(src)-len("}\n")]
|
||||
return bytes.TrimSpace(src)
|
||||
}
|
||||
// Gofmt has also indented the function body one level.
|
||||
// Adjust that with indentAdj.
|
||||
indentAdj = -1
|
||||
}
|
||||
|
||||
// Succeeded, or out of options.
|
||||
return file, sourceAdj, indentAdj, err
|
||||
}
|
||||
|
||||
// format formats the given package file originally obtained from src
|
||||
// and adjusts the result based on the original source via sourceAdj
|
||||
// and indentAdj.
|
||||
func format(
|
||||
fset *token.FileSet,
|
||||
file *ast.File,
|
||||
sourceAdj func(src []byte, indent int) []byte,
|
||||
indentAdj int,
|
||||
src []byte,
|
||||
cfg printer.Config,
|
||||
) ([]byte, error) {
|
||||
if sourceAdj == nil {
|
||||
// Complete source file.
|
||||
var buf bytes.Buffer
|
||||
err := cfg.Fprint(&buf, fset, file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Partial source file.
|
||||
// Determine and prepend leading space.
|
||||
i, j := 0, 0
|
||||
for j < len(src) && isSpace(src[j]) {
|
||||
if src[j] == '\n' {
|
||||
i = j + 1 // byte offset of last line in leading space
|
||||
}
|
||||
j++
|
||||
}
|
||||
var res []byte
|
||||
res = append(res, src[:i]...)
|
||||
|
||||
// Determine and prepend indentation of first code line.
|
||||
// Spaces are ignored unless there are no tabs,
|
||||
// in which case spaces count as one tab.
|
||||
indent := 0
|
||||
hasSpace := false
|
||||
for _, b := range src[i:j] {
|
||||
switch b {
|
||||
case ' ':
|
||||
hasSpace = true
|
||||
case '\t':
|
||||
indent++
|
||||
}
|
||||
}
|
||||
if indent == 0 && hasSpace {
|
||||
indent = 1
|
||||
}
|
||||
for i := 0; i < indent; i++ {
|
||||
res = append(res, '\t')
|
||||
}
|
||||
|
||||
// Format the source.
|
||||
// Write it without any leading and trailing space.
|
||||
cfg.Indent = indent + indentAdj
|
||||
var buf bytes.Buffer
|
||||
err := cfg.Fprint(&buf, fset, file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := sourceAdj(buf.Bytes(), cfg.Indent)
|
||||
|
||||
// If the adjusted output is empty, the source
|
||||
// was empty but (possibly) for white space.
|
||||
// The result is the incoming source.
|
||||
if len(out) == 0 {
|
||||
return src, nil
|
||||
}
|
||||
|
||||
// Otherwise, append output to leading space.
|
||||
res = append(res, out...)
|
||||
|
||||
// Determine and append trailing space.
|
||||
i = len(src)
|
||||
for i > 0 && isSpace(src[i-1]) {
|
||||
i--
|
||||
}
|
||||
return append(res, src[i:]...), nil
|
||||
}
|
||||
|
||||
// isSpace reports whether the byte is a space character.
|
||||
// isSpace defines a space as being among the following bytes: ' ', '\t', '\n' and '\r'.
|
||||
func isSpace(b byte) bool {
|
||||
return b == ' ' || b == '\t' || b == '\n' || b == '\r'
|
||||
}
|
||||
156
vendor/mvdan.cc/gofumpt/internal/govendor/go/printer/comment.go
vendored
Normal file
156
vendor/mvdan.cc/gofumpt/internal/govendor/go/printer/comment.go
vendored
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
// Copyright 2022 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 printer
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"strings"
|
||||
|
||||
"mvdan.cc/gofumpt/internal/govendor/go/doc/comment"
|
||||
)
|
||||
|
||||
// formatDocComment reformats the doc comment list,
|
||||
// returning the canonical formatting.
|
||||
func formatDocComment(list []*ast.Comment) []*ast.Comment {
|
||||
// Extract comment text (removing comment markers).
|
||||
var kind, text string
|
||||
var directives []*ast.Comment
|
||||
if len(list) == 1 && strings.HasPrefix(list[0].Text, "/*") {
|
||||
kind = "/*"
|
||||
text = list[0].Text
|
||||
if !strings.Contains(text, "\n") || allStars(text) {
|
||||
// Single-line /* .. */ comment in doc comment position,
|
||||
// or multiline old-style comment like
|
||||
// /*
|
||||
// * Comment
|
||||
// * text here.
|
||||
// */
|
||||
// Should not happen, since it will not work well as a
|
||||
// doc comment, but if it does, just ignore:
|
||||
// reformatting it will only make the situation worse.
|
||||
return list
|
||||
}
|
||||
text = text[2 : len(text)-2] // cut /* and */
|
||||
} else if strings.HasPrefix(list[0].Text, "//") {
|
||||
kind = "//"
|
||||
var b strings.Builder
|
||||
for _, c := range list {
|
||||
after, found := strings.CutPrefix(c.Text, "//")
|
||||
if !found {
|
||||
return list
|
||||
}
|
||||
// Accumulate //go:build etc lines separately.
|
||||
if isDirective(after) {
|
||||
directives = append(directives, c)
|
||||
continue
|
||||
}
|
||||
b.WriteString(strings.TrimPrefix(after, " "))
|
||||
b.WriteString("\n")
|
||||
}
|
||||
text = b.String()
|
||||
} else {
|
||||
// Not sure what this is, so leave alone.
|
||||
return list
|
||||
}
|
||||
|
||||
if text == "" {
|
||||
return list
|
||||
}
|
||||
|
||||
// Parse comment and reformat as text.
|
||||
var p comment.Parser
|
||||
d := p.Parse(text)
|
||||
|
||||
var pr comment.Printer
|
||||
text = string(pr.Comment(d))
|
||||
|
||||
// For /* */ comment, return one big comment with text inside.
|
||||
slash := list[0].Slash
|
||||
if kind == "/*" {
|
||||
c := &ast.Comment{
|
||||
Slash: slash,
|
||||
Text: "/*\n" + text + "*/",
|
||||
}
|
||||
return []*ast.Comment{c}
|
||||
}
|
||||
|
||||
// For // comment, return sequence of // lines.
|
||||
var out []*ast.Comment
|
||||
for text != "" {
|
||||
var line string
|
||||
line, text, _ = strings.Cut(text, "\n")
|
||||
if line == "" {
|
||||
line = "//"
|
||||
} else if strings.HasPrefix(line, "\t") {
|
||||
line = "//" + line
|
||||
} else {
|
||||
line = "// " + line
|
||||
}
|
||||
out = append(out, &ast.Comment{
|
||||
Slash: slash,
|
||||
Text: line,
|
||||
})
|
||||
}
|
||||
if len(directives) > 0 {
|
||||
out = append(out, &ast.Comment{
|
||||
Slash: slash,
|
||||
Text: "//",
|
||||
})
|
||||
for _, c := range directives {
|
||||
out = append(out, &ast.Comment{
|
||||
Slash: slash,
|
||||
Text: c.Text,
|
||||
})
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// isDirective reports whether c is a comment directive.
|
||||
// See go.dev/issue/37974.
|
||||
// This code is also in go/ast.
|
||||
func isDirective(c string) bool {
|
||||
// "//line " is a line directive.
|
||||
// "//extern " is for gccgo.
|
||||
// "//export " is for cgo.
|
||||
// (The // has been removed.)
|
||||
if strings.HasPrefix(c, "line ") || strings.HasPrefix(c, "extern ") || strings.HasPrefix(c, "export ") {
|
||||
return true
|
||||
}
|
||||
|
||||
// "//[a-z0-9]+:[a-z0-9]"
|
||||
// (The // has been removed.)
|
||||
colon := strings.Index(c, ":")
|
||||
if colon <= 0 || colon+1 >= len(c) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i <= colon+1; i++ {
|
||||
if i == colon {
|
||||
continue
|
||||
}
|
||||
b := c[i]
|
||||
if !('a' <= b && b <= 'z' || '0' <= b && b <= '9') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// allStars reports whether text is the interior of an
|
||||
// old-style /* */ comment with a star at the start of each line.
|
||||
func allStars(text string) bool {
|
||||
for i := 0; i < len(text); i++ {
|
||||
if text[i] == '\n' {
|
||||
j := i + 1
|
||||
for j < len(text) && (text[j] == ' ' || text[j] == '\t') {
|
||||
j++
|
||||
}
|
||||
if j < len(text) && text[j] != '*' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
170
vendor/mvdan.cc/gofumpt/internal/govendor/go/printer/gobuild.go
vendored
Normal file
170
vendor/mvdan.cc/gofumpt/internal/govendor/go/printer/gobuild.go
vendored
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
// Copyright 2020 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 printer
|
||||
|
||||
import (
|
||||
"go/build/constraint"
|
||||
"slices"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
func (p *printer) fixGoBuildLines() {
|
||||
if len(p.goBuild)+len(p.plusBuild) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Find latest possible placement of //go:build and // +build comments.
|
||||
// That's just after the last blank line before we find a non-comment.
|
||||
// (We'll add another blank line after our comment block.)
|
||||
// When we start dropping // +build comments, we can skip over /* */ comments too.
|
||||
// Note that we are processing tabwriter input, so every comment
|
||||
// begins and ends with a tabwriter.Escape byte.
|
||||
// And some newlines have turned into \f bytes.
|
||||
insert := 0
|
||||
for pos := 0; ; {
|
||||
// Skip leading space at beginning of line.
|
||||
blank := true
|
||||
for pos < len(p.output) && (p.output[pos] == ' ' || p.output[pos] == '\t') {
|
||||
pos++
|
||||
}
|
||||
// Skip over // comment if any.
|
||||
if pos+3 < len(p.output) && p.output[pos] == tabwriter.Escape && p.output[pos+1] == '/' && p.output[pos+2] == '/' {
|
||||
blank = false
|
||||
for pos < len(p.output) && !isNL(p.output[pos]) {
|
||||
pos++
|
||||
}
|
||||
}
|
||||
// Skip over \n at end of line.
|
||||
if pos >= len(p.output) || !isNL(p.output[pos]) {
|
||||
break
|
||||
}
|
||||
pos++
|
||||
|
||||
if blank {
|
||||
insert = pos
|
||||
}
|
||||
}
|
||||
|
||||
// If there is a //go:build comment before the place we identified,
|
||||
// use that point instead. (Earlier in the file is always fine.)
|
||||
if len(p.goBuild) > 0 && p.goBuild[0] < insert {
|
||||
insert = p.goBuild[0]
|
||||
} else if len(p.plusBuild) > 0 && p.plusBuild[0] < insert {
|
||||
insert = p.plusBuild[0]
|
||||
}
|
||||
|
||||
var x constraint.Expr
|
||||
switch len(p.goBuild) {
|
||||
case 0:
|
||||
// Synthesize //go:build expression from // +build lines.
|
||||
for _, pos := range p.plusBuild {
|
||||
y, err := constraint.Parse(p.commentTextAt(pos))
|
||||
if err != nil {
|
||||
x = nil
|
||||
break
|
||||
}
|
||||
if x == nil {
|
||||
x = y
|
||||
} else {
|
||||
x = &constraint.AndExpr{X: x, Y: y}
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
// Parse //go:build expression.
|
||||
x, _ = constraint.Parse(p.commentTextAt(p.goBuild[0]))
|
||||
}
|
||||
|
||||
var block []byte
|
||||
if x == nil {
|
||||
// Don't have a valid //go:build expression to treat as truth.
|
||||
// Bring all the lines together but leave them alone.
|
||||
// Note that these are already tabwriter-escaped.
|
||||
for _, pos := range p.goBuild {
|
||||
block = append(block, p.lineAt(pos)...)
|
||||
}
|
||||
for _, pos := range p.plusBuild {
|
||||
block = append(block, p.lineAt(pos)...)
|
||||
}
|
||||
} else {
|
||||
block = append(block, tabwriter.Escape)
|
||||
block = append(block, "//go:build "...)
|
||||
block = append(block, x.String()...)
|
||||
block = append(block, tabwriter.Escape, '\n')
|
||||
if len(p.plusBuild) > 0 {
|
||||
lines, err := constraint.PlusBuildLines(x)
|
||||
if err != nil {
|
||||
lines = []string{"// +build error: " + err.Error()}
|
||||
}
|
||||
for _, line := range lines {
|
||||
block = append(block, tabwriter.Escape)
|
||||
block = append(block, line...)
|
||||
block = append(block, tabwriter.Escape, '\n')
|
||||
}
|
||||
}
|
||||
}
|
||||
block = append(block, '\n')
|
||||
|
||||
// Build sorted list of lines to delete from remainder of output.
|
||||
toDelete := append(p.goBuild, p.plusBuild...)
|
||||
slices.Sort(toDelete)
|
||||
|
||||
// Collect output after insertion point, with lines deleted, into after.
|
||||
var after []byte
|
||||
start := insert
|
||||
for _, end := range toDelete {
|
||||
if end < start {
|
||||
continue
|
||||
}
|
||||
after = appendLines(after, p.output[start:end])
|
||||
start = end + len(p.lineAt(end))
|
||||
}
|
||||
after = appendLines(after, p.output[start:])
|
||||
if n := len(after); n >= 2 && isNL(after[n-1]) && isNL(after[n-2]) {
|
||||
after = after[:n-1]
|
||||
}
|
||||
|
||||
p.output = p.output[:insert]
|
||||
p.output = append(p.output, block...)
|
||||
p.output = append(p.output, after...)
|
||||
}
|
||||
|
||||
// appendLines is like append(x, y...)
|
||||
// but it avoids creating doubled blank lines,
|
||||
// which would not be gofmt-standard output.
|
||||
// It assumes that only whole blocks of lines are being appended,
|
||||
// not line fragments.
|
||||
func appendLines(x, y []byte) []byte {
|
||||
if len(y) > 0 && isNL(y[0]) && // y starts in blank line
|
||||
(len(x) == 0 || len(x) >= 2 && isNL(x[len(x)-1]) && isNL(x[len(x)-2])) { // x is empty or ends in blank line
|
||||
y = y[1:] // delete y's leading blank line
|
||||
}
|
||||
return append(x, y...)
|
||||
}
|
||||
|
||||
func (p *printer) lineAt(start int) []byte {
|
||||
pos := start
|
||||
for pos < len(p.output) && !isNL(p.output[pos]) {
|
||||
pos++
|
||||
}
|
||||
if pos < len(p.output) {
|
||||
pos++
|
||||
}
|
||||
return p.output[start:pos]
|
||||
}
|
||||
|
||||
func (p *printer) commentTextAt(start int) string {
|
||||
if start < len(p.output) && p.output[start] == tabwriter.Escape {
|
||||
start++
|
||||
}
|
||||
pos := start
|
||||
for pos < len(p.output) && p.output[pos] != tabwriter.Escape && !isNL(p.output[pos]) {
|
||||
pos++
|
||||
}
|
||||
return string(p.output[start:pos])
|
||||
}
|
||||
|
||||
func isNL(b byte) bool {
|
||||
return b == '\n' || b == '\f'
|
||||
}
|
||||
1999
vendor/mvdan.cc/gofumpt/internal/govendor/go/printer/nodes.go
vendored
Normal file
1999
vendor/mvdan.cc/gofumpt/internal/govendor/go/printer/nodes.go
vendored
Normal file
File diff suppressed because it is too large
Load diff
1432
vendor/mvdan.cc/gofumpt/internal/govendor/go/printer/printer.go
vendored
Normal file
1432
vendor/mvdan.cc/gofumpt/internal/govendor/go/printer/printer.go
vendored
Normal file
File diff suppressed because it is too large
Load diff
57
vendor/mvdan.cc/gofumpt/internal/version/version.go
vendored
Normal file
57
vendor/mvdan.cc/gofumpt/internal/version/version.go
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) 2020, Daniel Martí <mvdan@mvdan.cc>
|
||||
// See LICENSE for licensing information
|
||||
|
||||
package version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
const ourModulePath = "mvdan.cc/gofumpt"
|
||||
|
||||
func findModule(info *debug.BuildInfo, modulePath string) *debug.Module {
|
||||
if info.Main.Path == modulePath {
|
||||
return &info.Main
|
||||
}
|
||||
for _, dep := range info.Deps {
|
||||
if dep.Path == modulePath {
|
||||
return dep
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func gofumptVersion() string {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return "(no build info)"
|
||||
}
|
||||
// Note that gofumpt may be used as a library via the format package,
|
||||
// so we cannot assume it is the main module in the build.
|
||||
mod := findModule(info, ourModulePath)
|
||||
if mod == nil {
|
||||
return "(module not found)"
|
||||
}
|
||||
if mod.Replace != nil {
|
||||
mod = mod.Replace
|
||||
}
|
||||
return mod.Version
|
||||
}
|
||||
|
||||
func goVersion() string {
|
||||
// For the tests, as we don't want the Go version to change over time.
|
||||
if testVersion := os.Getenv("GO_VERSION_TEST"); testVersion != "" {
|
||||
return testVersion
|
||||
}
|
||||
return runtime.Version()
|
||||
}
|
||||
|
||||
func String(injected string) string {
|
||||
if injected != "" {
|
||||
return fmt.Sprintf("%s (%s)", injected, goVersion())
|
||||
}
|
||||
return fmt.Sprintf("%s (%s)", gofumptVersion(), goVersion())
|
||||
}
|
||||
Loading…
Reference in a new issue